Last updated
Last updated
This year, my friends and I participated in the Capture the Flag (CTF) competition and secured 4th place.
One particularly noteworthy challenge was created by Kaspersky. It required analyzing a traffic record with Wireshark to uncover that a malicious actor had exploited a service using a memory corruption bug, allowing them to execute shellcode on the machine.
To solve the challenge, we had to analyze the shellcode and discovered that it was packing additional code. After writing an unpacker, we noted that it was encrypting a file and sending it to the attacker. We then developed a program to decrypt the exfiltrated data and successfully retrieved the flag
The challenge provided us with a file called "attacker_traffic.pcapng."
Upon opening it in Wireshark, we observed a large number of recorded packets.
Scrolling down a bit, I noticed an intriguing packet requesting a “token” for the “SHINRA CORPORATION SERVER.”
Upon examining the TCP stream, it became evident that instead of sending a token, the user transmitted a lengthy series of ‘A’ characters. This behaviour resembles an attempt to overflow a buffer to manipulate the stack pointer and execute code.
Following the sequence of ‘A’ characters, there appears to be a series of NOPs, typically used to ensure the execution flow reaches the shellcode placed afterward.
As observed, the first phase of the shellcode appears normal, but the second part seems nonsensical. To further investigate, let’s dump all the data sent by the attacker and save it to a file using CyberChef.
When attempting to import the program into Ghidra, since it is not an executable file like an ELF or a PE, Ghidra prompted for the architecture. For the initial attempt, I selected the Intel/AMD 64-bit x86 architecture.
After decompiling the program, I simplified the code returned by Ghidra for clarity. Here is the edited version:
As I mentioned before, Ghidra could only decompile the first stage of the code because the valid instructions were present only in that stage.
The first stage is simply a for loop that performs an XOR operation with the key 0x55 on the code starting at address 0xa1 and stopping at address 0x3b50. Doing this operation in CyberChef, we can immediately strings about the GLIBC and some imported symbols.
Finally, we can see what the payload is doing for real:
Decompiling the first function, “FUN_00401489,” yielded the following pseudo-code:
This function iterates over param_1
until param_2
, xoring each character by 0x33
. Consequently, every invocation of this function decrypts a string. Following a thorough analysis of the references pertaining to this function, three strings were uncovered.
Getting the strings and applying the same xor operation, we can get these results:
5P3R_S3CR3T_K3Y
: an encryption key, we need to check what the function which use it does
/home/critical_sl_server/core.db
: the file being extracted by the attacker
192.168.134.133
: the attacker IP to receive the file
So, after renaming the strings variable name we can verify the second called function “local_10 = (int *)FUN_004014d7(&file_path,&local_20)
”:
This function is opening the the core.db
file using the open
syscall, saving it size into local_20
and mapping the file content to the local_18 variable. If any of those calls fails, the function returns 0x00, else it return the content of core.db
.
Following the instruction flow, the program checks if the file content starts with 0xef
and allocate a buffer to local_18 with the size of the file.
The next called function is the FUN_004015b0
:
This function is getting the time and storing it into the local_20
variable. It stores the date in the format “yy/mm/dd” to the local_13
variable. With that, it appends that formated date to the end of the 5P3R_S3CR3T_K3Y and save it to DAT_004040b
.
Back in Wireshark, we can see when the Arrival Time of the package, which was “Oct 26, 2023”.
So, with these information we can restore the string used in the time of the attack, which is: “5P3R_S3CR3T_K3Y23/10/26”. To make it easy to recognise, I’ll rename DAT_004040b
to “final_key”
The next interesting function is FUN_00401314(&final_key,file_data,file_size,file_buffer,&local_28)
:
Right off the bat we can see it is encrypting the file content.
Before it starting encrypt the file content itself, it calls the function FUN_00401226(local_128,final_key,final_key_length)
.
For the simplification sake, this is the function rewritten to be more understandable:
This is a function to shuffle the key to let it ready to use to encrypt the file content.
Here is a more understandable version of the function FUN_00401314:
As all the operations are xor, we can easily decrypt the data just replicating this algorithm with the shuffled key and the encrypted data.
The next function being called is FUN_00401659(&attacker_ip,0x1337,file_buffer,local_28)
. As it contains the attacker_ip
followed by the 0x1337 (4919 in decimal) and the encrypted data (local_28
), I assumed it is sending the encrypted data to 192.168.134.133:4919.
Looking all the connections on the port 4919 I found just one packet but the TCP handshake:
Saving the data to a file we are ready to start to write a program which do the same symetric encryption algorithm in order to get the decrypted data.
After executing it, we got the flag.