TGHACK 2019 — lolbinary (Forensics) Write-up (part 2 of 2)

oR10n
7 min readApr 22, 2019

--

Hi everyone. This is part 2 of how we solved the lolbinary forensics challenge. In this post, I will discuss how I reverse engineered the extracted binary and figured out how to obtain the flag from the C2 server.

Woot. First blood!

While doing initial static analysis, I noted that there is a base64 encoded PE file on the resource section of the binary. We can easily spot this since “TVqQAA” is consistent with the base64 encoded form of the MZ header’s first few bytes.

Base64 Encoded PE file

Using a PE tool such as CFF explorer, we can easily extract this data and do a base64 decode with Cyberchef.

Decoding the resource file

Upon further static analysis, I also noted that the PE file was UPX packed.

UPX Packed

We can easily decode the file using UPX itself since it has a built-in unpacking capability. However, do take note that not all packers have a built-in unpacking capability. One of the reasons why UPX have this feature is that it was originally designed as an open-source packer for executable compression and not for obfuscation.

Unpacking with UPX

What the?! How come it wasn’t unpacked by UPX?

This is a common technique employed by malware authors to hinder reversers who are just starting out. By doing some slight modifications, they can prevent UPX from unpacking the binary automatically.

Now, there are several ways to proceed with this. Of course, we can try to manually unpack the binary without using UPX. There are several tutorials on the Internet regarding this (here is a great one). The basic idea here is to:

  • Debug the PE file and step through the unpacking routine to find the real Original Entry Point
  • At the Original Entry Point, dump the fully unpacked program to disk
  • Fix the Import Table

However, we don’t have to do all of this for this challenge. I actually noted that the modification done for this UPX packed binary lies on the section header of the PE file. Since UPX by default searches for the section headers named as “UPX0”, “UPX1”, etc., simply modifying the section header name can fool UPX.

Modified Section Header Names

To overcome this, we can simply rename the headers “LOL0” and “LOL1” to “UPX0” and “UPX1” respectively. After renaming, we can try unpacking again with UPX.

Unpacking with UPX (Success!)

Will this work all the time? No. There may be instances where malware authors will modify the UPX algorithm itself and this simple trick definitely won’t work. In that case, you will have no choice but to manually unpack it yourself.

Now that we have the unpacked PE file, we can load it to a disassembler and debugger for a deeper analysis on what’s happening under the hood.

The main function of the binary is located at sub_4016B0.

Main Function

Looking at the main function, we can immediately see some encrypted strings being MOVed to local variables. Right after the MOV instructions, we can see that an address pointing to an encrypted string was assigned to EDX, another address was assigned to ECX, and a call was made to a function located in 0x401010. These operations were done two times. Based from these info, we can assume that sub_401010 is some kind of decryption routine.

Main — call to sub_401010

We can confirm this assumption using a debugger.

Confirming the Decryption Routine

As you can see from the registers pane, we can see that the return value of sub_401010, is “google.com”. This confirms that the sub_401010 is a decryption routine that accepts an encrypted string as one of its arguments. Do we need to reverse it? At least for now.. not yet.

By stepping through the program using a debugger, we can identify that the data in offsets aMeemfoIeg and a_ are “google.com” and “/” respectively. These information tells us that the binary may try to contact the URL “google.com/” on the succeeding instructions.

Further down the main function, we can see that a call to sub_4010F0 determines the code path taken by the program. This code construct is consistent with a simple if statement that determines whether sub_4010F0 returns a zero or a non-zero value.

Main — call to sub_4010F0

Digging deeper into sub_4010F0, we can immediately see that the function is responsible for doing some network functionality. This is obvious since a call to WSAStartup initializes the Winsock library.

sub_4010F0 — call to WSAStartup

Apart from the call to WSAStartup, we can also see various calls to different networking API functions. Based on these API calls, we can assume that this function is used to send data to a specific host via the low-level networking APIs.

sub_4010F0 — function calls

Apart from the networking API calls, we can also note that multiple calls to sub_401010 were made which indicate that several encrypted strings are decrypted in this function as well.

sub_4010F0- calls to sub_401010

Stepping through this function with a debugger, we can see that the function attempts to:

  • Resolve the IP address of “google.com”
sub_4010F0 — call to getaddrinfo
  • Opens a socket and connects to the resolved IP address of “google.com”
sub_4010F0 — calls to socket and connect
  • Decrypt multiple strings used for an HTTP GET request. One of which is the user-agent string “secretstringkey” (hmm.. suspicious)
sub_4010F0 — calls to sub_401010
  • Initiate an HTTP GET request to “google.com” with a user-agent string of “secretstringkey”.
sub_4010F0 — call to send

By now, we pretty much understand what is happening with the binary. Our key takeaway here is the fact that the binary uses a custom user-agent string in HTTP GET request. With this information, we can assume that “google.com” and “/” must be placeholders for the actual C2 domain and URI. But how can we locate those?

Remember this?

Main — call to sub_401010

As we’ve seen from our previous analysis, this is where the encrypted domain and URI gets decrypted. However, if we look closely, there are actually two other strings which were never referenced in the binary at all.

In order to decrypt these strings, we can modify the value of EDX on a debugger right before the call to sub_401010. This is actually a shortcut if you’re too lazy to reverse the encryption routine.

By default, it points to the address 004042C0 which is the encrypted “google.com” string. We can change it to 004042D0 to decrypt the other string.

Modifying EDX

As you can see, the encrypted string at 004042D0 was decrypted to “notc2.tghack.no”.

Decrypting the other C2 domain

We can also do the same thing for the other encrypted string at 004042E0.

Decrypting the other URI

Now that we know the actual C2 and URI, we can fire up burp repeater to send an HTTP GET request to “notc2.tghack.no/get_flag” with a customized user-agent string “secretstringkey”.

Upon sending the HTTP request, the following data was sent back via the HTTP response:

4b582e26646c6a7c77407e406d7a6c706a6d7c7a796a73407d76717e6d6662

Getting the actual flag is just a matter of doing an XOR brute-force on the data.

XOR Brute-force

That’s it! I hope you learned something new from this write-up and thanks for taking the time to read it.

Hi! I’m oR10n and I’m one of the co-founders of hackstreetboys; a CTF team from the Philippines!

While you’re at it, please:

--

--

oR10n

Forensicator. RE n00b. Co-founder of hackstreetboys.