These are my (rather long) solutions to Fireeye's FLARE challenge. This is just not the solution but other ways that I tried. This was a great learning experience for me so I am writing this post to document everything I tried. As a result, this post is somewhat long.
If you have any feedback, please let me know. I spent a lot of time on this writeup and I am always happy to learn new stuff. My email and twitter handle are in the sidebar.
I am a bit late to the party. There
were two are now other three solutions posted (that I know of). Check them out.
- Detailed Solutions to FireEye FLARE Challenge
- A Walk through for FLARE RE Challenges
- The FLARE On Challenge Solutions by Fireye
Links to Individual Challenges
This post is quite long (I didn't want to strip them into different posts), use the following links to jump to any specific challenge:
I used a Windows XP SP3 Virtual Machine for most challenges using VirtualBox. For challenge 6 I used a Kali 64-bit VM. I used IDA/Immunity on my host OS with some other utilities.
- PE-Studio: Gain information about the binary without running it. It also sends a hash (MD5 I think) of the file to Virustotal so if you want to keep your samples secret, don't give it internet access
- dotPeek: Free .NET decompiler by JetBrains
- .NET Reflector: .NET decompiler. Not free but comes with a 2-week trial period
- HxD: Free Windows hex editor
- Notepad++: Slick FOSS text-editor
- Immunity Debugger: Windows debugger. Very similar to OllyDbg
- pyew: A Python tool for static malware analysis. I used it for PDF analysis
- IDA: What can I say? It's great but also costs an arm and a leg. Except challenge 6, the trial and free version are enough for us
- Bless: Linux Hex editor
- API Monitor: Free utility to monitor API calls in Windows. It can monitor calls for standard windows APIs or we can add application-specific Dlls and monitor them
- Wireshark: FOSS network monitoring/capturing tool. Needs administrator access on Windows to install libpcap
- Microsoft Network Monitor: Microsoft network monitoring/capturing tool. Does not need administrator access. Replaced by Microsoft Message Analyzer
The challenge starts with going to their website at http://flare-on.com and downloading a binary. The binary is a self-extracting zip file which is supposed to show you the challenge EULA. It didn't work on my VM.
I opened it with
7-zip to get
Challenge1.exe. By dropping it into
PE-Studio I gained more information:
The Image is a fake Microsoft executable # Company name is Microsoft but it is not signed? The Manifest Identity name (MyApplication.app) is different than the Image name The Version Information 'OriginalFilename' (rev_challenge_1.exe) is different than the Image name The Debug Symbol File Name () is different than the Image name (challenge1) The image is Managed (.NET)
So it appears to be a .Net binary. Let's run it.
Hey I love this guy. Let's press
Look at that garbled data. We can decompile it (remember it's a .Net binary). Using
dotPeek we can see the code for
Line 4 reads
dat_secret and the rest of the function manipulates it before displaying it on the form. To save this file expand
resources and select
rev_challenge_1.dat_secret.encode. Right click and select
Save Resource to File.
HxD to look at the contents.
Let's run the code with
dat_secret and print the result after each level (i.e.
str2, str3 and str4). One option is to use the provided C# code. I re-wrote the code in Python and ran it online using repl.it. Str1 is the answer so we don't care about the rest:
Level 1 flag: firstname.lastname@example.org
Well done! Looks like you kicked that one. I've attached the next challenge for your reversing pleasure. The password to this zip archive is "malware". We saw what looked like attacker activity to this site, can you figure out what the attackers changed? Hopefully you'll knock this one out too, Good luck! -FLARE
Inside the archive seems to be a copy of the original http://flare-on.com with a launch date countdown timer. I will be calling the html page from the website
original_html and the one in the zip file
The original web page looks a bit different.
-rwx------+ 1 TyRaX None 6254 The FLARE On Challenge.htm
-rwx------+ 1 TyRaX None 116290 bootstrap.css
-rwx------+ 1 TyRaX None 6596 flare-on-V2.png
The timer threw me off track. Is it really a countdown timer? When does it reach zero?
I changed the time in my VM to mess with it but it synced up with host.
Changing the time did not mess with anything.
We can diff the htmls or use Notepad++'s compare plugin.
Most differences are aesthetic. There are two interesting differences. In line 54,
<img src="The%20FLARE%20On%20Challenge_files/flare-on-V2.png"> while
<img src="img/flare-on.png">. So the file in the website is version 2 of the image. Later in the
challenge_html we see more evidence of this image file
<?php include "img/flare-on-V3.png" ?>. But wait a minute, the filesize of these images were different:
The challenge png is bigger. I used
HxD to compare these two files (as they are not text) and at the end of
flare-on.png I saw some PHP code. To be honest I was thinking of steganography or some Ange Albertini magic. But that would have been too hard for level 2. Here is the PHP code (beautified):
Find an online tool to run this PHP code or re-write it in Python . My Python code:
Produces the following PHP code:
$___="\x62\141\x73\145\x36\64\x5f\144\x65\143\x6f\144\x65"; // base64_decode
$__ are clearly encoded in
base64_decode. Base64 can be decoded in Python by calling
Line #4 can be re-written as
So it must decode the first base64 blob and eval it. Let's decode it:
This looks like a POST request. The characters look like a mix of ASCII and Hex values. Let's print them using Python and hope this is the last encoding:
Fortunately, we are done.
Level 2 flag: a11DOTthatDOTjava5crapATflareDASHonDOTcom or email@example.com
Nice job, you're really knocking these out! Here's the next binary. The password to the zip archive is "malware" again. Keep up the good work, and good luck! -FLARE
Challenge 3 is a Win32 binary called
PE-Studio does not tell us much.
Running it will result in this message:
I cheated in this challenge. I just dropped the executable in
Immunity Debugger, ran it and looked in memory when the message box popped up and the email was there:
Level 3 flag: firstname.lastname@example.org
Well done! Such dedication, much work, wow. Here's the next challenge, password is the same as last time. We'll talk more when you figure it out. -FLARE
It's a two page PDF named
APT9001.pdf. First page is a picture of APT1 report and second page is empty.
We can just open the PDF in a
HxD but it won't tell us much.
There are tools that will help us parse the PDF. I used
pyew. You can find a good tutorial for PDF analysis here.
Let's follow the tutorial:
Seems like streams 1,2 and 3 are interesting. According to the tutorial
pdfvi displays them.
- FlateDecode: Decompress. In Python do
- ASCIIHexDecode: Decode from ASCII Hex
- JBIG2Decode: Decode as a black and white image
What really threw me off was the
JBIG2Decode decoder for stream 2. There was a vulnerability associated with it. It is too short to be the email (14 bytes). It is not compressed (lacks the magic headers).
Pyew also displays the disassembly but it is not shellcode either (if it is, then I didn't recognize it). It is also not an image (hence the
Let's take a look at stream 1 using
IxTUQnOvHg looks suspicious. A large number of bytes are unescaped. After reading some guides, I found out how to decode this.
%u72f9 should be converted to
0xf972. I wrote a simple Python program to do this decoding. Read 6 characters, discard the first two (%u), swap characters 3 and 4 with 5 and 6. The end result is some shellcode. I used this website to convert it to an executable: http://sandsprite.com/shellcode_2_exe.php.
After running the executable in Immunity debugger a message box pops up with an encoded message. If we look inside memory, we can find this string:
The length is close to the email (29 bytes). Here's what I thought. If it is the email then the last 13 bytes should be
@flare-on.com. It's probably xor-ed with a key. If the key is smaller than 13 bytes then it is repeated and we can easily find it. How? xor is transitive. If
plaintext xor key = ciphertext then
key = plaintext xor ciphertext. If we xor the last 13 bytes of ciphertext with
@flare-on.com then we will find the last 13 bytes of the key. If key is smaller than plain/ciphertext (if key is as long as plain/ciphertext then we will have a
one time pad) it is repeated.
The following Python code does it. On a side note, we really need a string xor operator in Python. I wrote one which is probably not that good.
Nope. Doesn't look like it.
I usually wander around in the debugger and look at memory. Run the executable in Immunity and look around in memory after the message box pops up. A little bit further up from the original message we see more
OWNED!!! strings (title of the message box). Right before two owneds I saw another string. This one is longer and looks more promising. Right click on it and select
Follow in Dump. Select the string and again right click and select
Copy > To clipboard. It's in Unicode so
5 represented as
0x0035 instead of
Let's apply the xor-logic on this string too.
Bingo. The key is
BEEF. It is also in the initial shellcode as a string. Let's xor it with the complete string and get the flag.
Level 4 flag: email@example.com
Another one bites the dust! Here's some more fun for you, password is the same as always. -FLARE
Be sure to run this challenge in a VM.
The file inside the challenge zip is named
5get_it and is around 100KBs. A quick look with
HxD says it's a Portable Executable (MZ and PE magic bytes). Let's get some help from
PE-Studio. It has a VirusTotal score of 29/55 with most AVs calling it a generic trojan or keylogger. Click on
Imported Symbols and look at the red symbols. Some of them are more interesting than others. To get more information about any of them, right click and select
Query MSDN inside PE-Studio (handy, neh?).
Also, let's run
strings on it. I used cygwin. I have omitted the garbage and only kept the interesting strings:
While doing the challenge only the first and last two lines were interesting to me.
- It references
svchost.logbut there is no such file (Windows has
svchost.exein that location).
- There is also
c:\windows\system32\rundll32.exe c:\windows\system32\svchost.dllwhich means this file is most probably a DLL and should be executed like that. There are no parameters, so whatever this DLL is doing should be in
By this time you probably know what this file is supposed to do (also look at the registry key). However, at that time I did not make the connection :(
Let's drop this into
IDA and jump into DllMain. I used IDA Pro but both IDA free and trial and Immunity Debugger work for this challenge (and also challenge 7). Put a breakpoint at the start of this function (
If we attempt to execute the tile. IDA will complain. It's a DLL and cannot be run by itself. But we already know how to run it thanks to the strings inside the binary. In IDA first select
Local Win32 Debugger then go to
Debugger menu and select
Process Options. In the
Application textbox enter
Parameters enter the path to the DLL. Don't forget to rename the file, add dll extension and include double-quotes around the path if it contains spaces (e.g.
"c:\Flare Challenges\Ch5\5get_it.dll",0). It didn't work without the dll extension for me.
Let's start debugging. We observe standard stuff until we reach
.text:1000A6BB call sub_1000A570.
Inside the function we encounter RegOpenKeyEx that opens a registry key. Full registry key is a combination of
hKey can be one of the predefined keys. The constants for the predefined keys needed a bit of googling because the MSDN page didn't list them. Here they are:
. | Key | Constant | |---------------------|----------| | HKEY_CLASSES_ROOT | 0 | | HKEY_CURRENT_USER | 1 | | HKEY_LOCAL_MACHINE | 2 | | HKEY_USERS | 3 | | HKEY_CURRENT_CONFIG | 5 | .
The binary is pushing
lpSubKey which will result in the full path
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run. If function succeeds it will return
ERROR_SUCCESS which is 0 according to this page, otherwise it will return another error code.
The binary will check if it has access to registry at that path. If so then the return value (in eax) will be 0 and it will jump right (JZ will succeed).
RegQueryValueEx checks if there is a registry key at an open path. It is looking for a registry key named
svchost at that path. If such key exists, function will return 0. In this case, it returned 2 which stands for
ERROR_FILE_NOT_FOUND meaning there was no such key. Then it will call RegCloseKey and closes the open registry path. This function's return value is saved in
var_110 (we will need it later):
. | Condition | Return Value | |------------------------------|-------------------------------| |Registry key cannot be opened | 1 | |Registry key does not exist | 2 | |Registry key exists | 1000A6BB or DllMain(x,x,x)+3B | .
After that function, we see that it is calling
sub_1000A610 in lines 3-8 and checks the return value in line 9. The return value for GetModuleHandleEx will be non-zero, otherwise it will be zero. If call was not successful then last error will be printed to file.
GetModuleHandleEx was successful it will land here. GetModuleFileName is called which will return the full path for the specified module in
hModule. In this case, the binary retrieves its own path (line 9) and saves it in
[ebp+Filename]. In line 10, return value of
sub_1000A570 is compared with 2.
If registry key did not exist, we will continue.
We have already seen the strings being loaded in lines 1 and 2. Then
CopyFile is called to copy itself to
Line 9 pushes
c:\windows\system32\rundll32.exe c:\windows\system32\svchost.dll to the stack and calls
sub_1000A610 in line 10. Based on this string and checking for existence of the registry key we can guess what is going to happen in this function.
Inside this function we see that RegCreateKey to open
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run. If the key does not exist, it will create it.
If call was successful, execution continues to line 14. It is adding a new registry key named
svchost to that path with the specified value. Then function will return with the result value of RegSetValueEx. If it was successful, it will be 0.
The Dll copied itself to system32 and it will run every time Windows starts.
After we return from
sub_1000A610, we land in line 5. Return value will be saved in
var_114 (0 is key was created). If we highlight this variable and press
x in IDA to get external references (meaning where else this variable is being referenced and manipulated. It is not referenced anymore so we do not care about it. In line 8, a new function is called
sub_1000A4C0. Let's go inside.
sub_1000A4C0 we can see that the jump to return is never taken. Because eax is set to 1 and then checked for being zero and if zero the function will return. So let's look at the other branch.
Line 1 calls
rand and the result is modified a few times by doing some calculations in lines 3-8. In line 9, it is pushed to stack as argument for
malloc. So a random number of bytes are allocated. Seems like it is allocating an array of type
size_t. This is reinforced because the number is multiplied by 16 (size of size_t) in line 8 before being pushed to the stack. After the
malloc, the pointer to the allocated memory is stored in
var_C. In lines 13-19 we see that this array is reset to zero by
memset. Line 22 calls sleep with 10 miliseconds. Last line compares the calculated size of array with 0 and if so then no memory was allocated and program jumps back to the start of the function and tries to allocate memory and initialize memory again. If memory was allocated we continue to line 32 sleep for 10 miliseconds and call
This is what we are looking for. First
var_4 is set to 0 in line 10, then it is compared with 222 in lines 15-16 . If it is larger, we jump to
If not we will reach line 18 where
var_4 (initially 0) is stored in eax and pushed to stack as parameter for
GetAsyncKeyState. We already know what this function does. If
vKey has been pressed since last call to
GetAsyncKeyState, it will return a value. Otherwise it will return 0. This forum thread from 2007 discusses this usecase.
If the key was not pressed, we jump to line 26 and then 29 where
var_4 is increased by 1. Then we go back to line 14 where
var_4 is compared with 222 and the cycle is repeated.
Now we know that the application loops through ascii characters from 0 to 222 checking to see if a key was pressed. If so we will not jump at line 23 and continue. Let's take a look at that.
This code is a series of cases for a switch statement (as IDA has detected). It checks what key was pressed performs specific actions for each key (taking the red arrows). It checks from
0x60. By looking at an ASCII table, we can see that the application checks for some special characters, number and letters. I am not going to describe what each one does but I went through each function and looked at the code. Most of them were the same and looked uninteresting but the function for
0x4D caught my eye. Finding the code for
M and clicking on the red arrow besides it.
Nice, IDA has even tagged it as M for us. First we see that a
dword_10017000 is compared to 0. and if it is larger than 0, two functions are called:
sub_10001240. Then returns with value
__cfltcvt_init sets one variable to 1 and resets the rest (including
sub_10001240 creates a large array, initializes it with some values and then calles
DialogBoxIndirectParam. I put a breakpoint in the end. Change the IP to the start of this function and ran the program.
Nice! So to get this ASCII art we have to press M and
dword_10017000 needs to be 0. Let's get back to
sub_10009AF0 and investigate
dword_10017000 and press
x in IDA to see where this variable is being set to 1 (which will make the if true). There is only one place.
o? Now see that variable
dword_100194F8 needs to be 1 to reach this line (top right). Follow that using
So we have
m and then
o. If we follow the chain and then reverse it, we have the flag. The binary is a keylogger, it saves all keystrokes to
Level 5 flag: l0gging.Ur.firstname.lastname@example.org
Great success! We've got another evil one for you, see if you can figure this out. This one will be rougher. Good luck! -FLARE
While I was writing this solution, I saw this alternative way of solving the challenge. Great read: Solving FireEye's Flare On Six via Side Channels.
New binary. Named
e7bc5d2c0cf4480348f5504196561297. Let's google it and first result is interesting. Filename has the
exe extension but it is a 64-bit ELF executable. Opening the file in
HxD shows us the ELF magic bytes.
I started a Kali 64-bit VM in VirtualBox. Less mess with it a bit. I used IDA Remote Linux Debugger. IDA was running on my host OS and the binary was in the Kali 64-bit VM.
I did not try executing the binary with different number of arguments at the start. I tried different argument lengths, really long arguments (e.g. 'A'*40000). In the end I decided that two arguments was the correct way to run the binary. While debugging I realized that the binary crashes with a segfault message. While it is fine without the debugging. So some anti-debugging protections must be at work. We ran
ltrace and didn't see any shared library calls. Let's run
strace to get system calls.
Syscalls are similar in all traces except with two arguments. We can see that
ptrace is being called in line 37. It's a common anti-debug protection in Linux. "[a]n executable can only call ptrace once. if ptrace() was already called by the strace executable, we can detect it in runtime." So we need to bypass
ptrace. Searching for
ptrace in IDA does not turn up anything. I learned that syscalls are not called that way by name (he he). The argument for
syscall is moved to
eax and then it is called. So I search for the text
syscall in IDA and then commented each call according to Linux System Call Table for x86_64 by
Later I realized there was a much easier way to find it instead of discovering all calls. Running
-i switch will print the instruction pointer
at the time of call after the syscall returns. Let's run
ptrace on the binary with two arguments with this new swtich and look at the results.
Look at IP at the time of
ptrace in line 9:
47431b. Now look at the IDA screenshot above.
So this function calls
ptrace. To find out where this function is being called, highlight it and press
x in IDA. There is only one call.
Return value from
ptrace is manipulated and then checked to see if it is zero. If non-zero, the program continues to line 11 and prints the segfault message in line 12 (I have renamed it). As you have noticed I have enabled opcodes in the last code snippet. In IDA go to the
Option menu and then
General. Change the
number of opcode bytes.
To patch the binary to bypass ptrace we need to change the
jz instruction in line 9 to
jmp. In this short jump
0x74 stands for
0x14 means thee number of bytes to jump (in this case 14 bytes ahead). To patch it to
0xEB. Open the binary in a hex editor (e.g. Bless). Now we need to find this offset. I do what I call
lazy patching. Search for opcodes for the last few instructions before and
jnz in hex editor. In this case we are looking for
48 C1 E8 3F 84 C0 74 14. There is probably only one place in the binary with this sequence of bytes. Find it and change
0xEB. Now we have bypassed
ptrace. Another alternative is to replace the
call calls_ptrace in line 6 with NOPs. NOP is short for No Operation and has the opcode
0x90. It actually stands for
xchg eax, eax. Both of them work.
So I bypassed
ptrace. There were a lot of calculations. Random strings were loaded and manipulated. After stepping around the code in IDA I gave up. At this point I had two leads:
- The binary prints
no. Put breakpoints on all
sys_writecalls and trace the print back
- The application needs to manipulate the arguments somehow. Search for string instructions, breakpoint them and see if we hit one
I chose option 2, searched for string instructions and assigned breakpoints. After running the program I hit a
repne scasb do?
repne scasb will scan the string in
di/edi/rdi for the byte (
scasb is the byte version of
scas instruction) in
ax/eax/rax and decrease
cx/ecx/rcx by one after each execution. It stops if
cx/ecx/rcx reaches zero or if a match is found.
Null terminator or
0x00 is saved in eax in line 6. Line 7 has
rcx. We don't want
rcx to reach zero before the end of the string. First argument is saved in
rdi in line 8 and finally line 9 calls
repne scasb. This is basically
strlen(arg1). In line 14, it is checked if the length of first argument is 10. If so we will jump.
We can see that arg1 is saved to
rdi in line 5 and
sub_468BB0 is called. We can get inside
sub_468BB0 but it is basically
malloc. It allocates a string and initializes it with first argument. Return value is in
rax which is a pointer to the newly created string. It is saved to
[rbp+arg1_2] (I have renamed the variables). Finally there is an unconditional jump.
We see another
repne scasb. We have seen these instructions before. At the end of the code snippet, we go back to the top (notice the offsets for first and last line). This code loops through first argument and xors it with
If the loop is done, the
jnz in line 19 will not be triggered and we land somewhere else.
Application loads the string
bngcg`debd and compares the result of
arg1 xor 0x56 with it. If both are equal,
jz in line 7 will be taken.
We have already seen the transitive property of xor so we can calculate the correct value of first argument which is
4815162342. We could also patch the
jmp and enter any 10 characters for argument one.
Now it gets a bit hazy and very painful. There are tons of loops and function calls. Some random strings are loaded in different functions and not used for anything. I started to see patterns such as this instruction
mov cs:byte_729AC2, al. At that address, there are bytes being written and they are in
base64. I was stepping through until suddenly everything stopped and I saw that a
nanosleep syscall was executed.
I patched it and continued. Application crashed a few times in between and I had to get back to my latest breakpoint. I got into the habit of copying the base64 bytes and setting up breakpoints every once in a while to get back to a checkpoint after each crash. Finally all the bytes were written and sub_401164 was called. This function decodes the bytes from base64 (although I though it is a different implementation and stepped through it for an hour before realizing that it is just a standard decoder).
This is obviously shellcode, pushed to the stack and called. Bytes of the second argument are manipulated and then compared with some hardcoded value. I have only included the first 2 bytes here. For example
arg2 ror 0xF2 must equal 0x1B, otherwise
jz will be called and application will terminate in
loc_7FFF3A5AC3A6. I saw around 30 checks meaning that argument 2 must be 30 bytes or so. I wrote the following Python code to calculate the second argument.
Python does not have
rol binary operators so I stole them from here.
Level 6 flag: email@example.com
Alright! Last one, can you get to the finish line? Keep it up! -FLARE
By this time we have already fallen into a pre-check routine. Filename is
d69650fa6d4825ec2ddeecdc6a92228d (MD5 hash) and googling brings up no notable results.
- Win32 executable
- VirusTotal score: 5 / 55
- Imported libraries: ws2_32.dll, kernel32.dll and wininet.dll.
wininet.dllis for the interwebz
- Imported symbols: Lots of them. Functions for creating network sockets, hostname lookups, creating, reading and writing files and general anti-debug/anti-vm stuff
- Strings: Not as many strings as challenge 6. cmd.exe and 127.0.0.1 look interesting
API Monitor to observe application's API calls. It crashed after a while and API Monitor flagged 230k calls. Sifting through them is not practical but a lot of them are redundant and do not look interesting. For example there are a lot of
LocalFree calls. Right click any call and select
Exclude > API Name to filter it. After excluding a lot of stuff, there was still so much crap. So instead I tried to look at API calls to certain Dlls for example
Monitored Processes navigate to
Modules and then select a specific Dll to only see its calls. Let's search for specific calls that we noticed in PE-Studio. API Monitor also supports searching in MSDN. Double click a call or right click and select
Online Help (MSDN).
I searched for gethostbyname and found some interesting results:
I was curious about these connections so I captured the traffic using
Wireshark from launch to crash.
www.twitter.com. Then TLS handshake in lines 7-11 and finally a request to twitter (line 12) and reply (lines 13-14). Let's search for "twitter" in API Monitor and we see this
InternetOpenUrlW ( 0x00cc0004, "https://twitter.com/FireEye/status/484033515538116608", NULL, 0, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_PRAGMA_NOCACHE, 0 ). Let's find that tweet and it looks normal.
After migrating to Hugo, I can embed tweets now.
Because this challenge employs a good number of Anti-Debug/Anti-VM protections, I will try to explain what I learned at each stage. Even after finishing the challenge I went back and looked at some steps again to learn more.
Here are some useful resources:
Practical Malware Analysis book chapters 16 and 17 deal with Anti-Debugging and Anti-VM techniques
Five Anti-Analysis Tricks That Sometimes Fool Analysts was published when I was writing this post
main and put a breakpoint on it. As we go through main we reach a bunch of function calls. Let's start with the first one.
Function 1 - isDebuggerPresent?
The result of a function call
isDebuggerPresent is compared with 0 by
test eax, eax. This function will return 1 if the application is being debugged. In our case it will return 1 and the jump fails. Before the compare we see a value
1073728 is loaded into
esi. On both sides we see a string being loaded and then we enter a loop. If we step through the loop and look at the xor line, we can see that it is xor-ing
oh happy dayz with the data at
byte_4131F8. If we reach the end of the string it will restart from the first character. This loop will go on for
1073728 bytes which seems to be length of data starting at
byte_4131F8. I am going to rename it to
blob and the number
If debugger is present, we go left and the string
oh happy dayz is xor-ed with the blob. If no debugger is present, we jump to the right branch and string
the final countdown is xor-ed with the
Function 2 - BeingDebugged?
Let's forget about the first compare and look at the outcome. Something is being compared with 1. If the compare succeeds then the first jump happens and we skip reseting
var_4 to zero. The next jump will only happen if
var_4 is zero which means that the last jump should not have happened. If the first compare succeeds (meaning
[eax+2] is 1) then we go left and otherwise right.
In both cases a string
omglob are loaded along with address
byte_4131F8 before a function call
sub_401000. The address points to a long stream of data.
sub_401000. At the start
blob_length is loaded into
ecx. Then we enter a loop. If we step through the loop and look at the xor line, we can see that it is xor-ing
UNACCEPTABLE! with the data at
byte_4131F8. If we reach the end of the string it will restart from the first character. This loop will go on for
1073728 bytes which seems to be length of data starting at
string xor blob.
Now let's go back to the first compare.
What is the significance of
fs:30h? It is the
Process Environment Block (PEB) in the
Thread Information Block (TIB). According to MSDN it has the following structure. The application is comparing the 3rd byte with 1. The 3rd byte is called
BeingDebugged and is set to 1 if the application is being debugged. If we are running the application with a debugger it will be set to 1 and
UNACCEPTABLE! will be xor-ed with the
omglob. More information about the
PEB can be found in the first section of the PDF
At this point we can rewrite this function in Python
Function 3 - VMware Detection via Red Pill
sub_401130 we see an old anti-VM technique. This is called The Red Pill. Each CPU core has its own
Interrupt Descriptor Table (IDT}. IDT is essentially an interrupt vector table. Because the VM manager is juggling two operating systems but there is one location per core, it has to relocate IDT of guest OS in memory. The application can check this location for known addresses assigned by VM managers and determine if it is running in a VM.
But how is this accomplished? Each core has one register called the
Interrupt Descriptor Table Register (IDTR) that points to this location in memory. The userland (ring3) instruction
SIDT will save this register. VM managers store the relocated tables in different places and the value of this register can act as a VM manager fingerprint.
According to this post by Alain Zidouemba these are some of the addresses:
| | VM Manager | Address | |------------|------------| | Windows | 0x80FFFFFF | | Virtual PC | 0xE8XXXXXX | | VMware | 0xFFXXXXXX | |
The above compares the first byte of IDT address with
0xFF. According to our table it is looking for
VMware. But we are not running it. If this check passes (meaning we are not running VMware) the string
you're so bad is going to be xor-ed with the blob, otherwise it will be
you're so good. The address
0xBAB3C590 did not change during my runs in one VM. I will have to try with a different VM in VirtualBox to see if it changes or if it has a pattern. If you know please let me know.
Function 4 - VMware Detection 2: Electric Boogaloo
What's in the box?
Function will create its own exception handler, it will return the execution to
loc_401232 if an exception occurs. Then we have some interesting instructions. If we look at the Malware Bytes article, it is named
VMware I/O port. These are the magic instructions:
It's a quick way to find if the application is running in a VMware VM. If
in eax, dx is successful, it will save the magic number in
ebx and then
var_1C. If not, it will raise an exception. But the function has an exception handler and execution will be transferred back to the function. Then
var_1C is compared to the magic number to determine if the application is in a VMware VM or not.
I was running the application in VirtualBox. Apparently Fireeye thinks we are all rich and use VMware ;) So the check failed.
The rest of the function is pretty simple, if the check fails
f) will be xor-ed with the blob. If running in VMware
Function 5 - OutputDebugString
This is almost the same as listing 16-1 in page 353 of
Practical Malware Analysis book (Link to p.353 on Google Books). First the current error code is set to
OutputDebugString is called with string
bah!. An error occurs if a debugger is not attached to the application and current error code changes, otherwise there is no error and last error code remains
0x1234. Later, last error code is retrieved by calling
GetLastError, if this value is not changed then a debugger is attached to the application and string
Sandboxes are fun to play in is xor-ed with blob. In the absence of a debugger,
I'm gonna sandbox your face is used.
Function 6 - I Can Haz Breakpoint?
Offsets from two functions are loaded and then compared. The first one calls
isDebuggerPresent and the second one just prints something and exits. We have seen this function before, it is the first check.
None of these functions are called. But their offsets are compared. If offset of
calls_isDebuggerPresent is larger than
sub_401780 then we jump down and string
I can haz decode? is xor-ed with the blob. Otherwise we go right. I am not quite sure what this check is for. I think it is trying to find if calls to
isDebuggerPresent are redirected or not (by the debugger?) as the address of the first function is
0x401030 and is smaller than
0x401780. If you know what this means please let me know and I will update this section. In all of my runs the jump does not happen and execution continues to the right.
To the right we can see a pretty standard
0xCC is the code for
INT 3 and is used by debuggers to set breakpoints. It is simply checking if
0xCC bytes are present in the function code. If
0xCC is present
ecx is increased by 2, otherwise by one. In the end this number is compared with
0x55. If the check does not pass it will jump to left (same as above) and
I can haz decode? is xor-ed with the blob. If the number is
Such fire. Much burn. Wow. is xor-ed with the blob.
Function 7 - NtGlobalFlag
This one is pretty straightforward. A field inside the
PEB (we have already seen it) is called
NtGlobalFlag. This flag is at offset
0x68 in 32-bit versions of Windows (and
0xBC for 64-bit). Usually it is set to zero but it can be changed. A process that is started by a debugger will have this field set to
0x70. To read more about it, please look at the Anti-Debugging reference.
NtGlobalFlag is not
\x09\x00\x00\x01 will be xor-ed with the blob, otherwise
Feel the sting of the monarch!.
Function 8 - Sands of Time
This is not a countermeasure but a simple check. First
time64 is called and returns the number of seconds since January 1st 1970. Then
localtime64 converts it to readabled format stored in a structure of type
tm according to MSDN:
cmp dword ptr [eax+18h], 5 compares 24th (0x16) byte of the structure with 5. Because each field is of type
int and 4 bytes, 24th byte will be the current day of the week. Sunday is 0, so Friday is 5. The application simply checks if it is Friday. If so, it will xor
! 50 1337 with the blob and if it is not Friday blob will be xor-ed with
Function 9 - Backdoge.exe
Before next function, executable's complete path is saved into
sub_4014F0 is called.
Again, this is just a check. Executable's name is compared with
backdoge.exe two characters in each iteration.
The rest is pretty easy. If filename check passes,
MATH IS HARD will be xor-ed with the blob and if not
LETS GO SHOPPING.
Function 10 - Dogecoin.com IP Check
inet_ntoa is converting the IP to ASCII IPv4 format (e.g. 192.168.0.1) and comparing it to
127.0.0.1 two characters at a time like last check.
The xor-string is
LETS GO MATH if the resolved IP address is not
127.0.0.1. If the IP address is
h_addrtype is not
SHOPPING IS HARD will be xor-ed with the blob.
Function 11 - Hour of the Wolf
Again, we see the familiar
localtime64 calls. This time offset 8 of the
tm structure (copied below) is compared with
17. This offset contains the number of hours after midnight, so the application is checking if it is between 5 and 6 PM.
If time check passes, blob is xor-ed with
\x01\x02\x03\x05\x00\x78\x30\x38\x0d otherwise it will be xor-ed with
Interlude - 12 - Fullpath xor
We finished the first 10 functions, YAY. Now we see that the full path of binary is xor-ed with the blob. However, keep in mind that one of the checks compared full path with
Function 13 - Internet Rootz
Two more functions. We're getting there.
We have seen this type of code. This function pushes
e.root-servers.net to stack and then calls
gethostbyname to retrieve its IP
188.8.131.52. If the result is not zero,
h_addrtype is checked for 2 (
AF_INET) and retrieved IP is converted into ASCII format.
The rest is pretty simple.
184.108.40.206 is xor-ed with the blob.
Function 14 - jackRAT
We see InternetOpen called. This function initialises the WinINet functions. Agent name is
ZBot which is an alternate name for the
Zeus trojan horse. Access type is
INTERNET_OPEN_TYPE_DIRECT which means direct access without the use of any proxies. If a NULL handle is returned then function will exit (line 28). If not it will jump to
loc_4018E5 (line 21).
InternetOpenUrl opens a handle to a resource.
dwFlags is set to
0x00400100. I could not find the exact meaning of this flag value. However, according to this page it could be the
OR of two flags (does it work that way?):
Lines 9 to 35 are saving the URL, we know what it is without even looking at it. We have seen it in Wireshark before. The URL is
Line 37 saves return value which is a "valid handle to the URL if the connection is successfully established, or NULL if the connection fails". Then it is checked for being NULL, if so we will jump to
loc_4018D4 and function returns immediately. If we have a handle to the tweet, execution continues.
InternetReadFile retrieves the tweet. A buffer is created to hold the retrieved data. Documentation says "[a] normal read retrieves the specified dwNumberOfBytesToRead for each call to InternetReadFile until the end of the file is reached. To ensure all data is retrieved, an application must continue to call the InternetReadFile function until the function returns TRUE and the lpdwNumberOfBytesRead parameter equals zero." This is happening in lines 31-35. We keep reading until
NumberofBytesRead is zero.
After we are done, the jump in line 32 is not taken and we land here:
We retrieved the tweet. Now strstr is called to find the first instance of
Secluded Hi in the tweet. The return value is a pointer to the start of
Secluded HijackRAT http://t.co/ckx18JHdkb .... The application adds
0x0B or 11 to the start of the string to skip
Secluded Hi and point to
jackRAT http://t.co/ckx18JHdkb .... A new 8 character buffer is created and passed to strncpy.
strncpy is called to copy 7 bytes from the start to the newly created buffer which will be
jackRAT. The rest is simple,
jackRAT is xor-ed with the blob and finally InternetCloseHandle is called three times to close the three function calls.
Are we there yet? gratz but not yet
The application crashed in line 5 over and over again. When I looked inside ecx I saw empty space but looking around I saw the application's complete path. After a while I realized that the code is trying to read arguments. The rest is obvious from the code. First two characters of first argument are written over the first two characters of the blob. First and second characters of second argument are written at offset
Then fopen is called to create/open a file named
gratz.exe for writing in binary mode ("wb"). Then blob is written to it by calling fwrite and finally it is closed with fclose. Then command
gratz.exe is run via the system call. So we are writing the blob to a file and then executing it.
What is special about first two bytes in a Windows binary? It's the start of the DOS stub with the magic bytes
MZ and you have already guessed that the second argument should be
How do I XOR?
But how do we get the correct binary. As we have already seen, there are a series of checks and depending on the checks, different strings are xor-ed with the original blob. A correct sequence of strings will produce a correct binary. The path is probably known at this point, just bypass any Anti-VM/Anti-Debug countermeasures and other checks. But I am lazy and instead wrote a bruteforcer. In order for the bruteforcer to work, we need the original blob before any xors. That is easy. Set a breakpoint before any of the functions. Then set the Instruction Pointer to
00401B8D and step through after the breakpoint. Stop before the
system call and copy the
gratz.exe file from disk.
Here's my bruteforcer. This is not good code but at that point I just wanted to finish.
It's a bad bruteforcer but it does the job. To speed things up, it only performs the xor-es with the first
0x80 bytes of the binary which is the
DOS Stub. In the end, it compares the first two bytes with
MZ and then xor-es the whole binary before writing it to a file.
I got two files and after opening them in hex editors, one was clearly a false positive. I executed the correct binary.
But we cannot see the email. Augh. This is a .NET application. We need to decompile it like the first challenge.
We can either write code or paste it into an online C# compiler. In the end we have the flag:
Level 7 flag: firstname.lastname@example.org
And the email:
Alright, we give in. You've done it. Your reversing-fu is strong. I'll pass your info on to the FLARE team and someone will be in touch. -FLARE