Zigerions
A VMProtect-packed dropper chain whose final payload is an unrunnable ELF carrying an AES key next to its own ciphertext.
psctf{U_sh0u1dvebeen_@_g@m3r_0rHighIQ!} Reverse Engineering | malware analysis, VMProtect, AES
Overview
A 3-stage dropper chain (update.exe → stage3.exe, VMProtect-packed) wrapped in a “maze with no wrong answers” UI flavour built around WTSSendMessageW message boxes. The maze itself turned out to be misdirection.
Investigation
- Extracted the challenge archive (password
infected) withextract.py, statically unwrapping all 3 stages with no execution. stage3.exeis VMProtect-protected and anti-VM: it detects a hypervisor-present bit (Windows 11’s own VBS/Core Isolation sets this system-wide) and bails with aMessageBoxWbefore reaching any interesting logic.- Once runnable, a Frida hook (
hook.py) traced the process and caught it dropping two files into%TEMP%before any WTS-related activity fired.
The actual payload
Of the two dropped files, AURA.gb was a decoy referenced in a wsprintfA/ShellExecuteA call but never actually written to disk.
The real payload was a file named svchost — not a runnable executable at all despite an ELF header (e_type=EXEC, e_machine=x86-64): _start is undefined and no section carries the executable flag. It is a data mule: an ELF shell built purely to smuggle a hand-rolled AES-128 implementation and its ciphertext past casual inspection.
| symbol | section | size | content |
|---|---|---|---|
__3 | .rodata | 16 B | ASCII M68K_AES_FLAGKEY (doubles as the AES-128 key) |
__2 | .rodata | 11 B | 00 01 02 04 08 10 20 40 80 1b 36 (AES Rcon table) |
__1 | .rodata | 256 B | AES S-box |
__0 | .rodata | 256 B | AES inverse S-box |
__4 | .data | 48 B | ciphertext (3 AES blocks) |
Decrypt
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
d = open(r'%TEMP%\svchost', 'rb').read()
ct = d[0x2220:0x2220 + 0x30] # .data section
key = b'M68K_AES_FLAGKEY'
print(unpad(AES.new(key, AES.MODE_ECB).decrypt(ct), 16).decode())
Result
AES-128-ECB with the key sitting right next to its own ciphertext, PKCS7-unpadded, yields the flag directly — no interaction with the maze UI was ever required. VMProtect was only protecting the delivery wrapper (anti-VM/anti-debug), not this stage.