FlareOn-9 Writeups 05 T8

chall 05 banner

Introduction

Description:

FLARE FACT #823: Studies show that C++ Reversers have fewer friends on average than normal people do. That’s why you’re here, reversing this, instead of with them, because they don’t exist.

We’ve found an unknown executable on one of our hosts. The file has been there for a while, but our networking logs only show suspicious traffic on one day. Can you tell us what happened?

After being taunted by the challenge description, let’s prove that C++ reverse engineering can be, at least, a bit of fun :)

As always, an x86 Windows executable is provided under the name t8.exe along with a network capture traffic.pcapng.

A quick look to that .pcapng file reveal that it only contains two HTTP sessions between 192.168.10.15 (the infected computer) and 13.13.37.33 (the C2 server): chall 05 pcap overview

We can spot two HTTP POST requests made to the root of the web-server with a weird looking user-agent an unintelligible piece of data.

As a response, a base64 encoded string is sent back. Unfortunately, the base64 does not contain plaintext values when decoded. Guess we have to figure this out by taking a closer look to the malware.

Struct definitions and C++ cleanup

To be honest, this main function looks very ugly, and contains a lot of stuff that I really don’t like: structs, global variables, weird pointers and freaking xmm registers. We are entering C++ territories for sure: chall 05 ugly stuff

Before trying to understand anything, we have to clean our IDA database and define the C++ class of that program.

A quick tip before doing anything is to force IDA to generate the C code for the whole binary, which will fix a lot of incoherence in our pseudocode view. Sometime, when entering a function and returning to the caller, the pseudocode is updated on the fly because IDA found new variables types or references. Instead of entering every function by hand and having to reanalyze the caller, we can go to File -> Produce File -> Create C File to achieve the same result on the whole binary.

Our main function already looks a bit better, as we don’t have these unknown global variable anymore: chall 05 main

Time to hunt for some structs.

We can spot that a new memory area of 0x4C is created and set to zero (new(0x4C) + memset()) before being passed as the first argument to sub_1734C0.

This is the standard layout for a class constructor.

Let’s follow this variable through the function (which is certainly a reference to this): chall 05 struuuct

And looks like we found the class constructor function. Time to clean this and makes it a proper structure.

As we don’t know anything about the class members yet, we’ll simply fill the struct with a bunch of DWORD, and in case of any inconsistencies, we would be able to spot it easily. For now, using a set of DWORD is the safer option as it will most likely keep the alignment clean: chall 05 def

This is definitively better for this constructor function: chall 05 clean constructor

The first member of this struct is labeled as a vtable (basically a C++ jump table to some function pointers).

If we check the main function, we can observe that one of the weird pointer manipulation we observed earlier is a call to a function of this vtable (referenced by an offset into the vtable): chall 05 vtable call

As this is ugly and not convenient to work with (the target functions are lost in the current state), let’s create a proper structure for that too.

We can observe that the vtable is composed of 12 functions pointers: chall 05 vtable functions

So we need to convert this to a new struct (right click -> create struct from selection): chall 05 vtable struct

Next, we can set the type of the first struct member to our new vtable structure: chall 05 vtable ref

And now, the weird pointer manipulation is correctly resolved in the main function, leading to something much more enjoyable: chall 05 clean vtable ref

And a bit deeper in the function, everything seems to point to the proper class members and resolve correctly: chall 05 clean vtable ref

We can now start our analysis and rename functions and variable.

Class members and variables

The first function, the one with xmm registers is actually checking if the current date correspond to a specific date: chall 05 time check

I honestly did not understand how this function works, as its inside looks very obscure: chall 05 time check boo

With a bit of guessing and our debugger, we can successfully bypass this check by setting our computer to “14/06/2022”, which is the date we can observe in the .pcapng.

Then, the C2 server address is decoded from an optimized xmm XOR operation: chall 05 xmm xor

In our case, we already know from the network traffic file that the C2 server is flare-on.com.

This decoded address string is latter passed to the class constructor: chall 05 address ref

Which allows us to rename the first element in our struct this->target_server_hostname: chall 05 add class variable

Another class variable that we can observe is the one that hold the actual HTTP verb used for communication (POST, created as a stack string): chall 05 add http verbe variable

After more similar work, we managed to identify the relevant class variables: chall 05 class

And to retrieve the whole vtable content: chall 05 vtable full

So far we didn’t look at the malware properly, only setting up the groundwork to do so, but we managed to glimpse little information:

  • Obviously, the malware is using HTTP communications to contact the C2 server (as we saw in the pcap file).
  • Some base64 encoding / decoding is happening from the network communications.
  • Some MD5 functions are used (we need to dig this latter).
  • The malware is only supposed to work on a specific day of the year.
  • Some RC4 encryption/decryption functions are available in the vtable.

Analysis

Alright, time to understand this code (which is actually small after all).

The two main functions we want to focus on are the ones making the HTTP POST request that we can observe in the .pcapng file.

The first one is computing a precise timestamp (precision to the millisecond) converted to a 4 or 5 bytes length numeric value: chall 05 numeric value

To follow along with some concrete value, we can generate this value (and the one that are going to follow latter) using our debugger. From our example, the precise timestamp generated is 1084.

Then, a MD5 hash is generated by concatenating this value with the hardcoded string FO9, which produced 84795f07c5e4bd2613bdf544f38ea45c in our test-case: chall 05 md5

After that, the timestamp is concatenated to the hardcoded string ahoy to form 1084ahoy.

This string is then RC4 encrypted using the MD5 hash as a key, and the result is base64 encoded (and kept as a Unicode string): chall 05 md5

The result is sent to the server. Of course, the value we have from our dynamic analysis are wrong and does not correspond to the one in the .pcapng file.

The response received from the server is base64 decoded by the program and RC4 decrypted using the same key (the MD5 hash).

The second POST request is basically doing the same thing, but using the string sce instead of the ahoy one.

One way or another, the C2 server is able to recover the randomly generated RC4 key from this communication to properly transmit new orders (or whatever) to the malware.

Flag

Since the seed for this key is almost random, there is no way that we can brute force it, as the timestamp is too precise. Plus, we can’t use the timestamp of the exchanges in the .pcapng file, as we only have the time of receiving from the server, which is not precise enough.

However, by taking a closer look at the .pcapng, we notice something interesting in the user-agent strings being used: chall 05 user agent hint

They are not the same, and in fact, the first communication user-agent is used to send the numerical timestamp to the server !

We can observe it by comparing the two request’s user-agent. The timestamp value is replaced by CLR in the second request.

This can also be observed and confirmed in the code: chall 05 user agent construction

Good, we have everything we need to decrypt this specific communication.

The precise timestamp value used in the pcap is 11950.

A small detail to look for is that since this value is 5 bytes long, it is going to be truncated to 4 bytes when concatenated with the ahoy string. This value becomes 1195ahoy (and not 11950ahoy). But for the MD5 key, the full length value 11950 is used.

Anyway, time to script our way to the flag.

from arc4 import ARC4
import hashlib
import base64

# Create the md5sum
const = "FO9"
timestamp_str = "11950"
time_id = const + timestamp_str
# IMPORTANT: convert to wide string (utf-16-le)
md5sum = hashlib.md5(time_id.encode('utf-16-le')).hexdigest()
print("MD5:", md5sum)
tmp = ""
for a in md5sum:
    tmp += hex(int(ord(a)))[2:] + " 00 "
print(tmp)
# RC4 encrypt the hello message
rc4_key = md5sum
#rc4_hello = "sce"
rc4_hello = "ahoy"
arc4 = ARC4(rc4_key.encode('utf-16-le'))
cipher = arc4.encrypt(rc4_hello.encode('utf-16-le'))
print("RC4 encrypt:", cipher)

# B64 before sending
base64_bytes = base64.b64encode(cipher)
print("B64 value:", base64_bytes)

# B64 decode the server's answer
answer = "TdQdBRa1nxGU06dbB27E7SQ7TJ2+cd7zstLXRQcLbmh2nTvDm1p5IfT/Cu0JxShk6tHQBRWwPlo9zA1dISfslkLgGDs41WK12ibWIflqLE4Yq3OYIEnLNjwVHrjL2U4Lu3ms+HQc4nfMWXPgcOHb4fhokk93/AJd5GTuC5z+4YsmgRh1Z90yinLBKB+fmGUyagT6gon/KHmJdvAOQ8nAnl8K/0XG+8zYQbZRwgY6tHvvpfyn9OXCyuct5/cOi8KWgALvVHQWafrp8qB/JtT+t5zmnezQlp3zPL4sj2CJfcUTK5copbZCyHexVD4jJN+LezJEtrDXP1DJNg=="
b64dec = base64.b64decode(answer)

arc4 = ARC4(rc4_key.encode('utf-16-le'))
decipher = arc4.encrypt(b64dec)
print(decipher)
python3 solve_05.py

MD5: a5c6993299429aa7b900211d4a279848
61 00 35 00 63 00 36 00 39 00 39 00 33 00 32 00 39 00 39 00 34 00 32 00 39 00 61 00 61 00 37 00 62 00 39 00 30 00 30 00 32 00 31 00 31 00 64 00 34 00 61 00 32 00 37 00 39 00 38 00 34 00 38 00 
RC4 encrypt: b'\xc9\xd3|\x05z\xb5\xe9\x11'
B64 value: b'ydN8BXq16RE='
b'\xe5\x07\t\x00\x03\x00\x0f\x00\r\x00%\x00\x03\x00b\x02,\x00\xdc\x07\n\x00\x06\x00\r\x00\r\x00%\x00\t\x00*\x03,\x00\xe1\x07\x0c\x00\x04\x00\x07\x00\r\x00%\x00$\x00\xe5\x00,\x00\xe0\x07\x05\x00\x05\x00\x06\x00\r\x00%\x00\x0b\x00&\x00,\x00\xe2\x07\n\x00\x01\x00\x08\x00\r\x00%\x00\x1f\x00E\x03,\x00\xe6\x07\x03\x00\x02\x00\x01\x00\r\x00%\x002\x00\xda\x00,\x00\xde\x07\x07\x00\x02\x00\x16\x00\r\x00%\x006\x00\xd1\x02,\x00\xde\x07\x05\x00\x03\x00\x0e\x00\r\x00%\x00\x01\x00\xe8\x00,\x00\xda\x07\x04\x00\x01\x00\x05\x00\r\x00%\x00:\x00\x0b\x00,\x00\xdd\x07\n\x00\x04\x00\x03\x00\r\x00%\x00\x16\x00\x16\x03,\x00\xde\x07\x01\x00\x02\x00\x0e\x00\r\x00%\x00\x10\x00\xc9\x00,\x00\xdc\x07\x0c\x00\x01\x00\n\x00\r\x00%\x000\x00\x0c\x02,\x00\xe6\x07\x02\x00\x01\x00\x1c\x00\r\x00%\x00"\x00K\x01,\x00\xe6\x07\t\x00\x05\x00\t\x00\r\x00%\x00!\x00m\x01'

The decrypted value is further processed by the malware before being used by the malware in the function vtable->func_9().

Since I started to get a little bored with this challenge, I simply injected the RC4 decrypted buffer in the malware at runtime to get the proper decoded content: chall 05 flag part 1

Which gives us the string i_s33_you_m00n.

Doing the same with the second HTTP communication gives us the flag suffix @flare-on.com.

So our flag to validate this challenge becomes:

i_s33_you_m00n@flare-on.com

This was a nice challenge that shows the importance of labeling our IDA database correctly and to take the time to properly reconstruct struct members. The C++ part was intimidating at first but actually transparent once everything’s properly label and setup.

chall 05 flag