ctf-resources/htb/hacktheboo2024/rev/[Very Easy] CryptOfTheUndead
2024-10-23 11:10:43 +02:00
..
release added official hacktheboo2024 writeups 2024-10-23 11:10:43 +02:00
src added official hacktheboo2024 writeups 2024-10-23 11:10:43 +02:00
README.md added official hacktheboo2024 writeups 2024-10-23 11:10:43 +02:00

CryptOfTheUndead

9th 10 24 / Document No. D24.102.170

Prepared By: clubby789

Challenge Author: clubby789

Difficulty: Very Easy

Classification: Official

Synopsis

CryptOfTheUndead is a Very Easy reversing challenge. Players will reverse engineer a ransomware binary to uncover a static key, then decrypt a file to gain the flag.

Skills Required

- Decompiler usage

Skills Learned

- Recognizing cryptographic primitives
- Bypassing program checks

Solution

We're provided two files; crypt and flag.txt.undead. Running crypt gives us an error:

Usage: ./crypt file_to_encrypt

so we can surmise that this is possibly some kind of ransomware, used to encrypt files. flag.txt.undead seems to contain random bytes, so it is likely encrypted.

Analysis

Opening it in a decompiler, we can see the program is not stripped.

int32_t main(int32_t argc, char** argv, char** envp)
int32_t return

if (argc s<= 1) {
    char const* const program_name = "crypt"
    
    if (argc == 1)
        program_name = *argv
    
    return = 1
    printf(format: "Usage: %s file_to_encrypt\n", program_name)

We begin by checking the args, and printing a help string if used incorrectly.

We then check the file argument passed, and ensure it doesn't end with .undead:

    char* old_filename = argv[1]
    
    if (ends_with(old_filename, ".undead") != 0) {
        return = 2
        puts(str: "error: that which is undead may not be encrypted")

We'll then allocate space for our new filename, likely used for the post-encryption fike, and append .undead to the old filename.

    uint64_t new_filename_len = strlen(old_filename) + 9
    char* new_filename_buf = malloc(bytes: new_filename_len)
    strncpy(new_filename_buf, old_filename, new_filename_len)
    *(new_filename_buf + strlen(new_filename_buf)) = '.undead'

We then perform the encryption.

    if (read_file(old_filename, &nbytes_1, &buf_1) != 0)
        return main.cold() __tailcall

    uint64_t nbytes = nbytes_1
    void* buf = buf_1
    encrypt_buf(buf, nbytes, "BRAAAAAAAAAAAAAAAAAAAAAAAAAINS!!")
    int32_t return_1 = rename(old: old_filename, new: new_filename_buf)
    return = return_1

main.cold is a short function that prints an error message then exits. If the read is successful, we call encrypt_buf on it. We then use rename to rename the file to the new name.

Looking into encrypt_buf:

int64_t encrypt_buf(char* buf, uint64_t buflen, uint8_t* key)
uint8_t nonce[0xc]
nonce[0].q = 0
nonce[8].d = 0
struct chacha_ctx ctx
chacha20_init_context(&ctx, key, &nonce, 0)
chacha20_xor(&ctx, buf, buflen)

We can see it uses chacha20 to encrypt a buffer with a given key and a nonce of 0. Even if this file was stripped, we could recognize this as chacha20 by the cryptographic constant used in chacha20_init_context:

__builtin_strncpy(dest: &arg1[0x10], src: "expand 32-byte k", n: 0x10)

Decryption

Knowing the algorithm (chacha20) and the key (BRAAAAAAAAAAAAAAAAAAAAAAAAAINS!!) is enough to encrypt this file. However, we can use a useful property of chacha20 instead. It is a reversible stream cipher, meaning that the 'encrypt' operation can be re-run on ciphertext with the same key to produce the plaintext.

The binary prevents this by checking for the .undead suffix. However, we can either patch that check out, or simply rename flag.txt.undead to flag.txt. We then run the binary on flag.txt, and read flag.txt.undead to reveal the flag.