Loading...

Malware Busters CTF: Go Malware Reverse Engineering - Complete Technical Writeup

Challenge Overview

This writeup documents the complete analysis of the "Malware Busters" CTF challenge. The challenge required reverse engineering a garble-obfuscated Go malware binary, decrypting its XOR-encrypted configuration file, and decoding the C2 (command and control) protocol to extract the flag.

Duration: December 10-21, 2025 (~2 weeks, 15+ analysis sessions)

Challenge Description

You are presented with an unknown and odd binary in a compromised environment. Your job is to analyze the binary as best you can. Your analysis should include:

  • Describe the actions performed by the malware
  • Find the C2 server the malware communicates with
  • Decrypt the malware's C2 protocol

By following these steps you will find the hidden flag to complete the challenge.

Hints Provided

  1. "Looking up UPX might nudge you in the right direction" - Pointed to corrupted UPX packing
  2. "Malware can be a curious beast. Sometimes, it refuses to function outside its native habitat" - Environment validation requirement

Phase 1: Initial Analysis & UPX Unpacking

Binary Overview

Property Value
Filename buu
Size (packed) ~2 MB
Size (unpacked) ~4.7 MB
Format ELF 64-bit executable
Language Go (garble-obfuscated)
Packing UPX with corrupted headers

The UPX Anti-Unpacking Trick

Initial attempt to unpack with upx -d buu failed with NotPackedException: not packed by UPX. Investigation revealed the UPX magic bytes had been deliberately corrupted.

Using grep -oba "WTRT" buu revealed four locations where UPX! had been replaced with WTRT:

Offset (decimal) Offset (hex) Original Corrupted
236 0xEC UPX! WTRT
2014771 0x1EB633 UPX! WTRT
2016022 0x1EBB16 UPX! WTRT
2016032 0x1EBB20 UPX! WTRT

Solution: Patching the Magic Bytes

cp buu buu_fix
printf '\x55\x50\x58\x21' | dd of=buu_fix bs=1 seek=236 conv=notrunc
printf '\x55\x50\x58\x21' | dd of=buu_fix bs=1 seek=2014771 conv=notrunc
printf '\x55\x50\x58\x21' | dd of=buu_fix bs=1 seek=2016022 conv=notrunc
printf '\x55\x50\x58\x21' | dd of=buu_fix bs=1 seek=2016032 conv=notrunc
upx -d buu_fix -o buu_unpacked

Result: Successfully unpacked to buu_unpacked (4.7 MB)

Phase 2: Static Analysis

Tools Used

  • Ghidra - Primary disassembler for initial analysis
  • Binary Ninja - Secondary analysis, better Go support
  • strings/grep - Quick string extraction
  • Python - Scripting for decryption attempts

Key Findings from String Analysis

The unpacked binary revealed several important strings:

Obfuscated Function Names (garble)

  • main.qvHPoPlV - Main config parsing/C2 communication
  • main.ZZzgf2nH, main.yprG5We4, main.WG2BdUVb
  • main.dT6K8IGR, main.LVEnGayC

Crypto Libraries

  • crypto/aes - AES encryption
  • crypto/cipher - Cipher modes
  • vendor/golang.org/x/crypto/chacha20poly1305 - Initially suspected but not used for C2
  • encoding/hex - Hex encoding/decoding

Configuration Path

  • /tmp/.X11/cnf - Hidden configuration file location

Red Herring: The 96-Byte Hex String

Found a 96-byte hex string in the binary that appeared to be a cryptographic key.

Initial assumption: This was the XOR key for config decryption.

Reality: This is the NIST P-256 elliptic curve base point - a standard cryptographic constant, NOT a custom key. This consumed significant analysis time before being identified as a red herring.

Another Red Herring: ChaCha20-Poly1305

The binary imports chacha20poly1305, leading to extensive attempts to decrypt C2 responses using ChaCha20. This was a fruitless path - the actual encryption used was AES-128-CBC.

Phase 3: Dynamic Analysis & Environment Discovery

The "Native Habitat" Requirement

Running the binary outside the CTF environment produced:

I don't belong here...

The malware performs environment validation:

  1. Checks hostname equals monthly-challenge
  2. Reads config from /tmp/.X11/cnf
  3. Only proceeds if all checks pass

Setting Up the Environment

hostname  # Returns: monthly-challenge
cat /tmp/.X11/cnf | xxd  # 144 bytes of encrypted config

Using strace for Behavior Analysis

strace -f -s 2000 -e trace=read,write,connect ./buu 2>&1

Observations:

  • Reads 144 bytes from /tmp/.X11/cnf
  • Performs DNS lookup for AWS Lambda endpoint
  • Establishes TLS connection on port 443
  • Sends/receives encrypted data

Phase 4: Configuration Decryption

The Encrypted Config

The 144-byte encrypted configuration was extracted from /tmp/.X11/cnf. The data was XOR-encrypted with a 4-byte repeating key.

Finding the XOR Key

Through Ghidra analysis of main.kYgXL_QA, found a hardcoded value:

v18 = 0x[REDACTED]

The Byte-Swap Transformation

The XOR decryption required a specific byte transformation within 4-byte blocks:

config = bytes.fromhex("9cce2f39d99c7640...")
key = bytes.fromhex("[REDACTED]")  # Little-endian representation

decrypted = bytes([config[i] ^ key[i % 4] for i in range(len(config))])

Decrypted Configuration

{
  "myhostname": "monthly-challenge",
  "url": "https://[REDACTED].lambda-url.us-east-2.on.aws",
  "enc_key": "[REDACTED - 32 hex chars]"
}

Key Discovery: The enc_key field contains the AES key for C2 decryption!

Phase 5: C2 Protocol Analysis

Traffic Interception

Used a combination of:

  • strace for syscall tracing
  • Custom Python MITM proxy
  • Direct endpoint probing with curl

C2 Protocol Structure

Component Value
Endpoint https://[REDACTED].lambda-url.us-east-2.on.aws
Path /command
Parameters n=monthly-challenge&s=<sequence>
Method GET (receive commands), POST (send results)

Sequence Parameter

The s parameter increments with each request:

  • s=1, s=2, s=3, ... s=10

Each GET request returns an encrypted command.

Captured Encrypted Responses

Sequence Response Size
1 32 bytes
2 32 bytes
3 64 bytes (anomaly!)
4-10 32 bytes each

Note: Sequence 3 returned 64 bytes instead of 32 - a hint that something important was there!

Phase 6: C2 Decryption Breakthrough

Failed Attempts

  1. AES-GCM with various nonce formats - Tag verification failed
  2. ChaCha20-Poly1305 - No valid output
  3. XOR with 96-byte key - Garbage output
  4. Various HKDF derivations - No success

The Solution: AES-128-CBC

After many failed attempts with AES-GCM, tried AES-CBC:

from Crypto.Cipher import AES

enc_key = bytes.fromhex("[REDACTED]")

def decrypt_command(hex_data):
    data = bytes.fromhex(hex_data)
    iv = data[:16]
    ciphertext = data[16:]

    cipher = AES.new(enc_key, AES.MODE_CBC, iv=iv)
    plaintext = cipher.decrypt(ciphertext)

    # Remove PKCS7 padding
    pad = plaintext[-1]
    if 1 <= pad <= 16:
        plaintext = plaintext[:-pad]

    return plaintext.decode('utf-8', errors='replace')

Decrypted C2 Commands

Sequence Decrypted Command
1 id
2 cat /etc/passwd
3 DONE WIZ_CTF{[REDACTED]}
4 whoami
5 hostname
6 uname -a
7 pwd
8 ls -la
9 env
10 exit

FLAG FOUND in sequence 3!

Complete Attack Chain Summary

+------------------------------------------------------------------+
|                         ATTACK FLOW                              |
+------------------------------------------------------------------+
|                                                                  |
|  1. UPX Magic Byte Corruption Detection                         |
|         |                                                        |
|         v                                                        |
|  2. Patch WTRT -> UPX! at four offsets                          |
|         |                                                        |
|         v                                                        |
|  3. Unpack binary with upx -d                                   |
|         |                                                        |
|         v                                                        |
|  4. Static analysis reveals config path and crypto imports      |
|         |                                                        |
|         v                                                        |
|  5. Run in CTF environment (hostname=monthly-challenge)         |
|         |                                                        |
|         v                                                        |
|  6. Extract encrypted config from /tmp/.X11/cnf                 |
|         |                                                        |
|         v                                                        |
|  7. Find XOR key (0x3354EEBC) in Ghidra                        |
|         |                                                        |
|         v                                                        |
|  8. Decrypt config -> get C2 URL and AES key                   |
|         |                                                        |
|         v                                                        |
|  9. Probe C2 endpoint with sequence parameter                   |
|         |                                                        |
|         v                                                        |
|  10. Decrypt responses with AES-128-CBC                         |
|         |                                                        |
|         v                                                        |
|  11. Flag found in sequence 3 response                          |
|                                                                  |
+------------------------------------------------------------------+

Red Herrings & Fruitless Paths

1. The 96-Byte NIST P-256 Constant

Time wasted: ~4 hours

Assumed this was the XOR key. Actually a standard elliptic curve parameter.

2. ChaCha20-Poly1305 Assumption

Time wasted: ~6 hours

Binary imports this library, but C2 uses AES-CBC instead.

3. AES-GCM Format Attempts

Time wasted: ~3 hours

Tried numerous nonce/tag arrangements before discovering CBC mode.

4. HKDF Key Derivation

Time wasted: ~2 hours

Saw HKDF references in binary, attempted various derivations.

5. The /tmp/b64.txt File

Time wasted: ~1 hour

Found a 2.7MB base64 file on the system - turned out to be unrelated.

6. Second 84-Byte Hex String

Time wasted: ~2 hours

Another hex blob in the binary that proved irrelevant to decryption.

Tools Summary

Tool Purpose
Ghidra Static analysis, function identification
Binary Ninja Go binary analysis, decompilation
UPX Binary unpacking (after patching)
Python + pycryptodome Cryptographic operations
strace System call tracing
curl C2 endpoint probing
xxd/od Hex dumps and binary inspection
grep Pattern searching

Key Insights & Lessons Learned

  1. Anti-analysis techniques are layered - UPX corruption was just the first obstacle.
  2. Red herrings are intentional - The NIST P-256 constant was a deliberate distraction.
  3. Try simpler crypto first - AES-CBC worked when GCM kept failing.
  4. The "native habitat" hint was crucial - Running in the correct environment was necessary.
  5. Sequence anomalies matter - The 64-byte response at sequence 3 indicated something special.
  6. Known plaintext attacks work - Knowing the JSON structure helped derive the XOR key.

Timeline

Date Milestone
Dec 10-11 UPX unpacking, initial static analysis
Dec 11-12 Config file discovery, XOR key hunting
Dec 12-13 C2 endpoint discovery, traffic capture
Dec 13-14 Ghidra deep-dive, crypto function analysis
Dec 14-16 Failed decryption attempts (GCM, ChaCha20)
Dec 16-17 Binary Ninja analysis, HKDF investigation
Dec 17-21 Config decryption breakthrough
Dec 21 AES-CBC discovery, FLAG CAPTURED

Security Implications

1. UPX Header Corruption

A simple but effective anti-analysis technique. Organizations should:

  • Train analysts to recognize corrupted packer signatures
  • Use tools that can identify packing regardless of magic bytes
  • Maintain databases of known corruption patterns

2. Environment Validation

The hostname check prevents sandbox analysis:

  • Configure analysis environments to match expected conditions
  • Use dynamic instrumentation to bypass environment checks
  • Patch binary to remove validation logic

3. Encrypted Configuration Files

Hiding C2 details in encrypted config files is common:

  • Look for hardcoded keys in binary strings and constants
  • Monitor file system access during dynamic analysis
  • Use memory dumping to capture decrypted configs at runtime

Conclusion

The "Malware Busters" challenge was an excellent test of reverse engineering skills, combining:

  • Binary unpacking with anti-analysis bypass
  • Go binary analysis with obfuscation
  • Cryptanalysis with multiple encryption layers
  • Protocol reverse engineering

The flag was hiding in plain sight within the C2 command stream, waiting to be decrypted with the key extracted from the configuration file. The deliberate red herrings (NIST P-256 constants, ChaCha20 imports) added significant complexity and required methodical elimination of false paths.

Challenge created by Gili Tikochinski as part of The Ultimate Cloud Security Championship by Wiz. Writeup completed: December 21, 2025