Introduction
During the WIZ Cloud Security Championship, I encountered a fascinating malware analysis challenge called “Malware Busters.” The scenario presented us with terminal access to a compromised environment containing a suspicious binary named “buu.” This writeup details the complete analysis workflow—from safe extraction and manual unpacking through reverse engineering, configuration decryption, and ultimately intercepting C2 communications to capture the flag.
This challenge showcased several realistic malware techniques including modified UPX packing, configuration obfuscation, and encrypted command-and-control (C2) communications. Let’s dive into the technical details.
Before touching any malware sample, proper isolation is critical. For this analysis, I set up a dedicated malware analysis lab consisting of:
- REMnux VM: Primary analysis workstation for static and dynamic analysis
- INetSim VM: Network simulation for capturing malicious traffic
- Isolated network: Both VMs connected via internal network with no external connectivity
- Docker containers: For executing the malware in controlled environments with specific GLIBC versions
This multi-layered approach ensures the malware cannot escape or cause damage while allowing us to observe its behavior comprehensively.
Stage 1: Safe Binary Extraction
The first challenge was extracting the binary from the compromised environment for offline analysis. The target file “buu” was a 2MB ELF 64-bit executable:
$ file buu
buu: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header
$ sha256sum buu
128aa33a7d605a05f7fb2671dc12a7f4a44d46b6ec95eed8f55f7c464e673288 buu
The environment had several restrictions that made extraction challenging:
HISTSIZElimited to 10,000 lines- No network connectivity (no
ipornetstatcommands) - No direct file transfer capabilities
The Extraction Method
I devised a safe extraction strategy using base64 encoding to neutralize the malware during transfer:
# Encode the binary to base64
$ base64 buu > buu.enc64
# Split into manageable chunks of 5000 lines each
$ split -l 5000 buu.enc64 buu_part_
$ ls -la buu_part_*
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_aa
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_ab
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_ac
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_ad
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_ae
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_af
-rw-r--r-- 1 user user 385000 Dec 12 23:17 buu_part_ag
-rw-r--r-- 1 user user 28462 Dec 12 23:17 buu_part_ah
Each file could be displayed with cat and manually copied from the terminal. After transferring all parts to my analysis machine, I reconstructed and decoded the binary:
# Reassemble the parts
$ cat buu_part_* > buu.enc64
# Decode from base64
$ base64 -d buu.enc64 > buu
# Verify integrity
$ sha256sum buu
128aa33a7d605a05f7fb2671dc12a7f4a44d46b6ec95eed8f55f7c464e673288 buu
Perfect match! The binary transferred successfully. A critical step here was clearing my clipboard afterward to prevent any accidental leaks of the malware sample.
Stage 2: Initial Static Analysis
With the sample safely in my analysis environment, I began basic static analysis using the strings utility:
$ strings buu -n 7 | tail -20
...
PROT_EXEC|PROT_WRITE failed.
$Info: g
executable packer http://upx.sf.net $
$Id: UPX 3.96 Copyright (C) 1996-2020 the UPX Team. All Rights Reserved. $
j"AZR^j
/proc/self/exe
UPX Packer Detection
The strings output immediately revealed the presence of UPX (Ultimate Packer for eXecutables) version 3.96. UPX is a legitimate executable packer commonly abused by malware authors to:
- Reduce file size
- Obfuscate code and strings
- Hinder static analysis
- Evade signature-based detection
The natural next step would be to unpack it using the UPX utility:
$ upx -d buu
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: buu: NotPackedException: not packed by UPX
Unpacked 0 files.
Interesting! The UPX tool refused to unpack the binary. This suggests the malware author modified the UPX header to prevent automated unpacking—a common anti-analysis technique.
Modified UPX Confirmation
To confirm this theory, I used Detect it Easy (DiE), a powerful signature-based file analyzer:
ELF64
Operation system: Unix(0)[AMD64, 64-bit, EXEC]
Packer: UPX(3.96)[NRV2E_LE32,brute,Modified(54525457)]
DiE confirmed our suspicions: the binary is UPX-packed with modifications to prevent standard unpacking. We’ll need to manually unpack it.
Stage 3: Manual Unpacking with GDB
Manual unpacking requires understanding how UPX operates. The packer decompresses the original executable in memory at runtime, then jumps to the Original Entry Point (OEP). Our strategy is to:
- Let UPX decompress the executable in memory
- Capture the unpacked code after decompression but before execution
- Dump the memory to a new file
Resolving GLIBC Dependency Issues
First attempt to run the binary under strace revealed GLIBC version issues:
$ strace ./buu
./buu: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./buu)
./buu: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./buu)
Solution: Use a Docker container with Ubuntu 22.04 which includes the required GLIBC versions:
$ docker run -it --rm -v $(pwd):/workspace -w /workspace ubuntu:22.04 bash
/workspace $ apt install gdb gdbserver libc6 strace
Tracing the Unpacking Process
Now we can trace the system calls to understand the unpacking routine:
/workspace $ strace ./buu
execve("./buu", ["./buu"], 0x7ffdbe568000 /* 8 vars */) = 0
open("/proc/self/exe", O_RDONLY) = 3
mmap(NULL, 2016342, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f09a43e8000
...
munmap(0x7f09a43e8000, 2016342) = 0
...
openat(AT_FDCWD, "/tmp/.X11/cnf", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
write(1, "I don't belong here...\n", 23) = 23
exit_group(0) = ?
Key observations:
- The binary reads itself via
/proc/self/exe - It allocates memory and performs unpacking operations
- After unpacking (visible via the
munmapcall), it jumps to the OEP - It then tries to open
/tmp/.X11/cnfand exits if not found
Dumping the Unpacked Binary
Using GDB, we’ll set a breakpoint at the first munmap syscall, which signals the end of unpacking:
(gdb) catch syscall munmap
Catchpoint 1 (syscall 'munmap' [11])
(gdb) run
Starting program: /workspace/buu
Catchpoint 1 (call to syscall munmap), 0x000000000063b6e2 in ?? ()
(gdb) ni
0x00007f448e11a290 in ?? ()
(gdb) info proc mappings
process 3460
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x400000 0x63c000 0x23c000 0x0 r-xp
0x63c000 0x850000 0x214000 0x0 r--p
0x850000 0x8c6000 0x76000 0x0 rw-p
0x7f448e0f9000 0x7f448e0fa000 0x1000 0x0 r--p /workspace/buu
...
The memory mappings show the unpacked code from 0x400000 to 0x8c6000. Let’s dump it:
(gdb) dump memory upx_full.raw 0x400000 0x8c6000
Verify the unpacked binary:
/workspace # file upx_full.raw
upx_full.raw: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped
/workspace # chmod +x upx_full.raw
/workspace # ./upx_full.raw
I don't belong here...
Success! We now have a functional unpacked binary ready for deeper analysis.
Alternative Unpacking Method: Magic Byte Restoration
Note: After completing this challenge, I discovered a simpler alternative approach. The UPX modification that prevented automated unpacking was simply a change to the magic bytes. The standard UPX header uses the magic bytes UPX! (0x55505821), but this binary had been modified to use TRTW (0x54525457) instead.
By restoring the original magic bytes, the standard upx -d command would have successfully unpacked the binary:
# Find and replace the modified magic bytes
$ hexeditor buu
# Locate: 54 52 54 57 (TRTW)
# Replace with: 55 50 58 21 (UPX!)
# Now UPX unpacks successfully
$ upx -d buu -o buu_unpacked
This technique demonstrates that while manual unpacking with GDB is valuable for understanding the unpacking process, sometimes the simplest solution is to identify and revert the anti-analysis modifications. Both approaches are valid—manual unpacking teaches fundamentals, while magic byte restoration is more efficient.
Stage 4: Deep Reverse Engineering
With the unpacked binary, I loaded it into IDA Pro for comprehensive static analysis. Several critical indicators emerged immediately:
Go Binary Identification
The binary is written in Go (Golang), identifiable by:
- Large binary size (typical for statically compiled Go binaries)
- Go runtime functions (
runtime_mapassign_faststr,runtime_intstring, etc.) - Go standard library references
Key Functionality Discovery
The strings and function analysis revealed the malware’s core capabilities:
Cryptographic Operations:
crypto_aes_NewCipher
crypto_cipher_NewCBCEncrypter
crypto_cipher_NewCBCDecrypter
ciphertext too short
ciphertext is not a multiple of the block size
plaintext is empty
invalid padding
Network Communications:
server
enc_key
application/octet-stream
BAD URL
Bad Response
failed to unmarshal json: %w
Configuration File:
- The binary attempts to read
/tmp/.X11/cnfduring startup - If this file doesn’t exist, it prints “I don’t belong here…” and exits
- This file likely contains C2 configuration
Control Flow Analysis
The main execution flow follows this pattern:
- Initialization: Read and parse
/tmp/.X11/cnf - Configuration Decryption: Apply XOR decryption to the config file
- JSON Parsing: Deserialize configuration to extract
serverandenc_keyvalues - C2 Loop: Continuously poll C2 server for commands
- Command Execution: Execute received commands and exfiltrate results
Stage 5: Configuration File Analysis
The configuration file /tmp/.X11/cnf was available on the compromised machine. I copied it to my analysis environment for examination.
Initial Attempts: The Hard Way
Initially, I took a complex approach using dynamic analysis. I set breakpoints after the deserialize_to_json function to inspect the Go JSON struct in memory:
(gdb) b *0x63b2cb
(gdb) run
(gdb) info reg rax
rax 0xc000072c60 824634190944
(gdb) x/8gx 0xc000096360
0xc000096360: 0x000000000000086c 0x000000c000018338
0xc000096370: 0x0000000000000006 0x000000c000018350
(gdb) x/s 0x000000c000018338
0xc000018338: "server"
(gdb) x/s 0x000000c000018350
0xc000018350: "enc_key"
This confirmed the JSON structure contains server and enc_key fields, but didn’t help decrypt the file.
The Simple Solution: Keep It Simple, Stupid (KISS)
Upon closer inspection of the disassembly, I discovered a function called immediately after os_ReadFile that I had initially overlooked. This function performs a simple 4-byte rolling XOR operation on the configuration data.
The XOR implementation uses two hardcoded keys:
- KEY1:
861204156(0x3355CCCC) - KEY2:
-289655980(0xEEBBDD44)
The function takes a boolean parameter to select which key to use, allowing for easy key rotation during compilation.
I wrote a Python script to decrypt the configuration file:
KEY1 = 861204156
KEY2 = -289655980
with open("conf", "rb") as f:
data = f.read()
def decrypt(data, key=False):
decrypted = bytearray()
if key == False:
key_bytes = KEY2.to_bytes(4, byteorder='little', signed=True)
else:
key_bytes = KEY1.to_bytes(4, byteorder='little', signed=True)
for i in range(0, len(data), 4):
decrypted.append(data[i + 2] ^ key_bytes[0])
decrypted.append(data[i + 3] ^ key_bytes[1])
decrypted.append(data[i] ^ key_bytes[2])
decrypted.append(data[i + 1] ^ key_bytes[3])
return decrypted
if __name__ == "__main__":
decrypted_data = decrypt(data)
with open("decrypted_conf", "wb") as f:
f.write(decrypted_data)
Running this script revealed the decrypted configuration:
{
"server": "https://wehiy6oj3hpaud3yske7nrt5xu0lcovj.lambda-url.us-east-2.on.aws/command",
"enc_key": "73eeac3fa1a0ce48f381ca1e6d71f077"
}
Excellent! We now have:
- C2 Server URL: AWS Lambda function endpoint
- Encryption Key: Hex-encoded AES key
Stage 6: Understanding C2 Communication
With the configuration decrypted, I analyzed how the malware communicates with its C2 server.
URL Parameter Analysis
The disassembly revealed the malware constructs URLs with two parameters:
Parameter n (hostname):
v66[0] = "-n";
v66[1] = 2;
hostname = exec_commands("uname", 5, v66, 1, 1);
v7 = (__int64 *)runtime_mapassign_faststr(&RTYPE_url_Values, &v69, "n", 1);
The malware executes uname -n to get the hostname and passes it as the n parameter.
Parameter s (sequence number):
size_as_string = runtime_intstring(0, len, v12, v14);
v56 = size_as_string._r0;
v48 = size_as_string._r1;
v16 = (__int64 *)runtime_mapassign_faststr(&RTYPE_url_Values, &v69, "s", 1);
The s parameter is a sequence number that:
- Starts at 48
- Increments by 1 after each request
- Likely used for command ordering or replay prevention
v6 = 48;
while ( 1 )
{
v14 = v6;
v20 = recv_data(r0, v6);
// ... command execution ...
v6 = v14 + 1;
}
Communication Encryption
All data exchanged with the C2 is encrypted using AES-256-CBC:
- Key: The
enc_keyfrom the configuration (hex-decoded) - IV: First 16 bytes of each message
- Format:
[16-byte IV][encrypted_data]
Stage 7: C2 Impersonation and Flag Capture
First, I attempted to connect from my analysis machine:
$ curl -X GET 'https://wehiy6oj3hpaud3yske7nrt5xu0lcovj.lambda-url.us-east-2.on.aws/command?n=remnux&s=48' -v
< HTTP/1.1 401 Unauthorized
< Content-Type: text/plain
< Content-Length: 13
What are you?
The C2 rejected my connection with a 401 Unauthorized response. This indicates hostname-based authentication—the C2 only accepts connections from known infected machines.
Successful Authentication
Using the hostname from the compromised environment (monthly-challenge):
$ curl -X GET 'https://wehiy6oj3hpaud3yske7nrt5xu0lcovj.lambda-url.us-east-2.on.aws/command?n=monthly-challenge&s=48' -v --output enc_data
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
< Content-Length: 32
{ [32 bytes data]
Success! The server responded with 32 bytes of encrypted data.
Decryption Implementation
I implemented an AES-CBC decryption function in Python:
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import binascii
KEY_HEX = "73eeac3fa1a0ce48f381ca1e6d71f077"
def get_key() -> bytes:
return binascii.unhexlify(KEY_HEX)
def decrypt_aes_cbc(data: bytes) -> bytes:
"""
Decrypt AES-CBC data.
Input format: [16-byte IV][ciphertext...]
"""
key = get_key()
iv = data[:16]
ciphertext = data[16:]
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
return plaintext
Decrypting the initial response:
$ python3 decrypt.py enc_data
whoami
The C2 sent a simple whoami command. Let’s automate the process and enumerate multiple sequence numbers.
Automated C2 Enumeration
I wrote a script to iterate through different s values and decrypt each response:
import requests
from aes_cbc_crypto import decrypt_aes_cbc
BASE_URL = "https://wehiy6oj3hpaud3yske7nrt5xu0lcovj.lambda-url.us-east-2.on.aws/command"
HOSTNAME = "monthly-challenge"
def fetch_and_decrypt(s_value: int):
url = f"{BASE_URL}?n={HOSTNAME}&s={s_value}"
r = requests.get(url)
if r.status_code != 200:
print(f"s={s_value}: HTTP error {r.status_code}")
return
encrypted = r.content
try:
plaintext = decrypt_aes_cbc(encrypted)
print(f"s={s_value}: {plaintext.decode()}")
except Exception as e:
print(f"s={s_value}: Decryption failed ({e})")
def main():
for s in range(-10, 101):
fetch_and_decrypt(s)
if __name__ == "__main__":
main()
Flag Discovery
Running the enumeration script:
$ python3 fetch_and_decrypt.py
s=-10: whoami
s=-9: whoami
s=-8: whoami
...
s=0: whoami
s=1: id
s=2: cat /etc/passwd
s=3: DONE WIZ_CTF{\*\*\*\*\*\*\*\*\*}
s=4: whoami
s=5: whoami
...
Flag captured! At sequence number 3, the C2 sent a special “DONE” message containing the flag: WIZ_CTF{*********}
Technical Summary
This challenge demonstrated a realistic malware analysis workflow involving multiple sophisticated techniques:
Malware Techniques Observed
- Modified UPX Packing: Custom header modifications to prevent automated unpacking
- Multi-layer Obfuscation: Configuration XOR encryption + AES-CBC communication encryption
- Hostname-based Authentication: C2 whitelist to prevent unauthorized analysis
- AWS Lambda C2: Using serverless infrastructure for command and control
- Rolling XOR Configuration: Simple but effective configuration obfuscation with switchable keys
Analysis Techniques Applied
- Safe Sample Extraction: Base64 encoding for inert transfer
- Manual Unpacking: GDB-based memory dumping at OEP
- Static Analysis: IDA Pro reverse engineering of Go binaries
- Dynamic Analysis: Controlled detonation with GDB/strace
- Cryptanalysis: Identifying and implementing XOR and AES decryption
- Network Analysis: C2 protocol reverse engineering and impersonation
Key Architectural Insights
The malware follows this control flow:
The configuration encryption uses a 4-byte rolling XOR with compile-time selectable keys, while network communications use AES-256-CBC with a shared symmetric key. The C2 implements hostname-based access control, likely checking against a whitelist of infected machines.
Defensive Recommendations
Based on this analysis, defenders should:
- Monitor for UPX-packed binaries with header anomalies
- Detect suspicious file locations like
/tmp/.X11/cnf - Analyze AWS Lambda traffic patterns for C2 communication
- Implement network-based AES-CBC traffic detection
- Look for Go binary artifacts in unknown executables
- Monitor command execution patterns (whoami, id, cat /etc/passwd sequences)
Conclusion
This challenge provided excellent hands-on experience with modern malware analysis techniques. The combination of modified packing, multi-layer encryption, and cloud-based C2 infrastructure represents real-world malware sophistication.
The key lessons learned:
- Always KISS: Don’t overcomplicate analysis (I initially over-engineered the config decryption)
- Layer your security: The malware used multiple defensive techniques that each required different approaches
- Understand the environment: Proper lab setup is crucial for safe malware analysis
- Document everything: Maintaining clear notes helps when you hit roadblocks
Thanks to WIZ for creating this engaging challenge that mirrors real-world malware analysis scenarios!