Malware Busters!
Challenge Description

Table of Contents
Solution Overview
This challenge demonstrates comprehensive malware analysis and reverse engineering techniques on a sophisticated Go-based backdoor:
Binary Extraction - Retrieving the malware sample from a compromised host
Anti-Analysis Evasion - Identifying and bypassing environmental checks
Binary Unpacking - Restoring and unpacking an obfuscated UPX binary
Reverse Engineering - Analyzing Go binary internals and cryptographic implementations
C2 Protocol Analysis - Understanding encrypted command-and-control communications
Traffic Decryption - Replicating the malware's decryption routine to retrieve the flag
Key Vulnerability: A Go-based backdoor malware with multiple layers of obfuscation including modified UPX packing, custom XOR cipher configuration encryption, and AES-CBC encrypted C2 communications. Analysis reveals the complete C2 protocol and allows extraction of commands from the command-and-control infrastructure.
Extracting the Binary
We are given a binary called buu. However, it's extremely limited to perform any kind of reverse engineering over a web shell. Luckily, the host is internet-connected, so I established a reverse shell to extract the buu binary.
Running a reverse shell over ngrok:

Catching the shell and extracting the buu binary with base64 encoding:

Note: There's probably a cleaner way to extract this, but base64 encoding works reliably for binary transfer over text-based channels.
Verifying Binary Integrity
Before performing any analysis, let's verify the integrity of the exported binary:


Comparing the MD5 hashes confirms we successfully extracted the buu binary without corruption.
Initial Dynamic Analysis
First, let's attempt to perform some basic dynamic analysis.
Executing the binary on my Kali machine produces this error message:

While executing it on the user VM returns the output of whoami, id, and /etc/passwd:

It appears there's some kind of anti-analysis or sanity check to ensure that people don't run the malware on random systems.
Running strace (a system call tracing utility) on the binary reveals that it's looking for the file /tmp/.X11/cnf:

On the user virtual machine, the file contains encrypted data:

Creating an empty file at this path on our Kali VM and executing the binary produces a Go runtime error:

Initial Static Analysis
Now let's perform some basic static analysis to gather more information:

Unpacking the binary
Looking at the strings, we can immediately identify that the binary is packed using UPX (Ultimate Packer for eXecutables). However, attempting to unpack it returns an error:

Normally, a binary packed with UPX will have specific magic bytes in the header that identify it as UPX-packed.
To verify this, I compiled a simple "Hello World" binary and packed it using UPX:

Looking at the hexdump of the main binary, we can clearly see the UPX! magic bytes in the header:

However, looking at the hexdump of buu, we can see the UPX! magic bytes have been patched to WTRT:

The difference becomes obvious when comparing them side by side:

Referring to this article from Nozomi Networks:

We can see that buu uses similar obfuscation techniques.
The article links to an open-source tool that can detect and repair corrupted UPX headers.
Installation instructions:
Running the tool successfully patches the binary. The analysis shows that only l_info is corrupted while p_info is intact. This means we could have simply performed a find-and-replace of WTRT with UPX! to achieve the same result:

Now we can successfully unpack the buu binary:

Reverse Engineering
From the initial triaging, we know this is a Go binary. Let's load it into IDA (or your favorite decompiler) for further analysis. I'll be using IDA Pro 9.2.
Loading it into IDA, we can see there are multiple main_* functions, which is typical for Go binaries:

Looking at the main_main function (the entry point), we can see that it first disables TLS certificate validation:

It then attempts to read the file /tmp/.X11/cnf and prints an error message if the file doesn't exist:

If the binary is able to read the cnf file, it then calls the main_kYgXL_QA() function.
main_kYgXL_QA() - Configuration Decryption Function
main_kYgXL_QA() - Configuration Decryption FunctionLooking at the main_kYgXL_QA() function:

Between lines 18-22, it sets a 4-byte XOR key based on the a4 parameter:
v18 = 0x3354EEBC(always set)If
a4is true (non-zero): usesv17 = 0xEEBC3354(byte-reversed version)If
a4is false (zero): usesv18 = 0x3354EEBCv4points to whichever key was selected
Note: 0xEEBC3354 is just 0x3354EEBC with bytes reversed:
0x3354EEBC= bytes[BC, EE, 54, 33]0xEEBC3354= bytes[54, 33, BC, EE]
This handles endianness based on the parameter a4, which indicates whether to use big-endian or little-endian byte order:
The main_kYgXL_QA() is called with 1 as the last parameter a4, so it will always be true. Therefore, the XOR key used is v17 or 0xEEBC3354 → [54, 33, BC, EE] in bytes.
The next part performs input validation to check if the a2 data length is a multiple of four:
What it does:
(a2 & 3) != 0- Checks ifa2(data length) is a multiple of 4a2 & 3is equivalent toa2 % 4If the remainder is non-zero, the length is invalid
Error handling (if length is invalid):
Creates an error message:
"input data length (%d) must be a multiple of 4"Returns with all data pointers set to 0 (nil) and error fields populated

The next chunk of code implements a 4-byte XOR cipher with byte reordering:
What's happening in each iteration:
Input block:
[byte0, byte1, byte2, byte3]Operations:
Reorder:
[byte2, byte3, byte0, byte1]XOR with key
[0x54, 0x33, 0xBC, 0xEE]
Output block:
[byte2^0x54, byte3^0x33, byte0^0xBC, byte1^0xEE]
This function performs a custom cipher combining:
Byte reordering (transpose: positions 0↔2, 1↔3)
XOR encryption with 4-byte repeating key
Based on this analysis, we can write a Python script to decrypt the configuration:
The decrypted output is a JSON configuration file:
Summary: The main_kYgXL_QA function decrypts the /tmp/.X11/cnf file and returns the C2 server address and the encryption key. Let's continue analyzing the main function.

The next part is removing the padding and passing the unpadded data to the main_ZZzgf2nH function.
main_ZZzgf2nH - JSON Parser
main_ZZzgf2nH - JSON Parser
Based on the error messages and function calls, we can safely assume this function parses the JSON data and returns the C2 address and encryption key.
Back to the main_main function:

It takes the output of main_ZZzgf2nH and pass it to the main_qvHPoPlV function.
main_qvHPoPlV - C2 Communication Handler
main_qvHPoPlV - C2 Communication HandlerLooking at the main_qvHPoPlV function, it first retrieves the URL from the JSON config:

It then crafts the command uname -n and passes it to the main_WG2BdUVb function:

The main_WG2BdUVb function uses os_exec_Command to execute a shell command and returns the output from the stdout pipe:

Interestingly, it uses Scanner_scan, which reads only the first line of the output:

This explains why in the dynamic analysis portion earlier, we only saw one line of output from /etc/passwd:

This part builds the URL query parameters:
n= victim's hostname (fromuname -n)s= command sequence number (starting from 0)
It then formats the full URL and makes an HTTP GET request:

Example URL:
Replicating the request manually:

However, the output from the C2 server is encrypted. Let's continue analyzing the function to understand the decryption process:

Line 180: Checks if the HTTP response status is 200 (successful), then reads the entire response body. Lines 182-188: Reads the HTTP response body Lines 189-195: Reads the encryption key and converts it from hex to bytes.
It then calls main_MG2pKkLO with:
v58, v34, v50- AES key (16 bytes)v66, v54, r2- Encrypted data from C2
main_MG2pKkLO - AES-CBC Decryption Function
main_MG2pKkLO - AES-CBC Decryption Function
What this function does:
Line 24: Creates AES cipher block using Go's crypto/aes.NewCipher(key) Line 33: Validates ciphertext length, ensuring it's at least 16 bytes (IV size) Line 41: Checks for block alignment (must be a multiple of AES block size) Lines 57-58: Extracts the IV (first 16 bytes) and creates CBC mode decrypter
Interacting with C2 to Get the Flag
With a solid understanding of how the C2 communication works, we can now write a Python script to interact with the C2 server and decrypt the traffic:

However, fetching from sequence 48 onwards only returns the whoami command repeatedly:

Let's try starting from sequence 0 instead:

Starting from sequence 0 reveals all the C2 commands, including the flag in the command history!
Summary:
Binary Extraction - Established reverse shell via ngrok and extracted the
buumalware sample using base64 encodingAnti-Analysis Detection - Identified environmental checks requiring
/tmp/.X11/cnfconfiguration fileUPX Unpacking - Discovered modified UPX header (magic bytes changed from
UPX!toWTRT), restored using upx-recovery-toolConfiguration Decryption - Reverse engineered custom XOR cipher with byte reordering (key:
0xEEBC3354) to decrypt C2 configurationProtocol Analysis - Analyzed Go binary internals to understand AES-CBC encrypted C2 communication protocol with query parameters
n(hostname) ands(sequence)Flag Retrieval - Replicated C2 client to fetch and decrypt commands from sequence 0, revealing the flag in command history
Flag: WIZ_CTF{The_Ghost_In_The_Machine}
Last updated