I left my last post off by mentioning that stage 1 of the Malwarebytes 2017 CrackMe performs process replacement/hollowing in order to execute stage 2. In this post I will finish discussing the CrackMe by investigating stage 2, and ultimately discovering the flag. If you have not already read my previous post, you can find it here.

I began by opening the stage 2 executable, which was dumped and decoded in the last post, in Ghidra. Perusing through the decompiled code, a call to ExpandEnvironmentStringsA with the same parameters as the code in stage 1 can be seen. Furthermore, there is a call to GetModuleFileNameA as well as code which performs xor operations on an input string. Some basic sleuthing reveals that the xor_leet function shown below will calculate a single value based on input data. This seems to me to be some sort of checksum function. With that in mind, it becomes clear that the code is comparing the process’s path to its expected value, which, based on how stage 1 loads stage 2, should include rundll32.exe.

Next, I loaded the program in x32dbg on my Windows VM while also reversing it in Ghidra. To get around the environment strings comparison mentioned above, I simply patched the file in the debugger, just as I did in stage 1 time and time again.

I continued examining the listing in Ghidra. After the first comparison successfully executes, the program calls EnumWindows. Upon consulting Windows documentation for this API call, I learned that it is enumerating all open windows on the screen and passing the window handles to a callback function. This callback function is defined by the “malware” author, and compares the checksum of each window name with that of a hardcoded checksum value. It is looking for a specific process, but I do not know which one. In the end, it doesn’t matter what process it is looking for, which will become evident later.

Deeper within the main function, there is a call to CreateRemoteThread, as well as RtlCreateUserThread and a function call which in turn calls ZwCreateThreadEx. As a fun little aside, global function pointers are setup in stage 2 to point to RtlCreateUserThread and ZwCreateThreadEx, as well as a few other Windows API calls, to hide what the imports are. Anyways, those function calls indicate that there may be some more process injection occurring. The code decides which of those three API calls to perform, based on some tick count.

Regardless of which method is used to create a thread, I need to find out what process is being run. The lpStartAddress which is used when creating the thread is determined by a function which creates and maps a section so it can copy data from the current process to the injected process. Following the trail indicates the data being copied is global data. This data should be the shellcode. But, before I can dump the shellcode in x32dbg, there is one bit of code that needs attention. The CrackMe xors the shellcode before it is injected. The xor key is determined by what value is stored in PEB[2]. Looking at the Windows documentation for the Process Environment Block, PEB[2] would be BeingDebugged. Originally, I thought this was an antidebugging check and that PEB[2] should be zero (which it was while running x32dbg). So, I continued by dumping the shellcode from memory to a file. I dumped 0x177 bytes since memcpy and some other functions took that in as the length of the buffer storing the shellcode.

I had to do some research to learn how to debug shellcode. I eventually stumbled upon shellcode2exe. The Python version of the script did not work for me, so I used the portable package found here. I used the program to generate an exectuable from the shellcode I dumped to a file. I tried running the program created from the shellcode, but nothing really happened beyond x32dbg freezing. I thought that maybe it was a problem with shellcode2exe, so I resolved to force stage 2 to inject into notepad.exe. I did this by creating a conditional breakpoint within the code which compares the window names, forcing it to break when it finds “Notepad” in the EnumWindows callback. I then forced the program to accept Notepad as the process it was searching for by manipulating EIP to execute the if-statement body. This actually worked, except it didn’t. It injected the code, allowing me to attach to notepad.exe in another debugger instance, but it was again seemingly invalid shellcode which prevented the program from doing anything.

Since my previous attempts failed, I decided to look at the code which xors the shellcode again. On a whim, after PEB[2], or BeingDebugged, is loaded into eax, I changed the value from 0 to 1. There is one part of stage 2 which I have failed to mention until this point. The program performs a checksum on the shellcode and compares it against a hardcoded value. I unwisely ignored this until now. Previously, when PEB[2] was 0, the checksum always failed. But now, when I set it to 1, the checksum passed! I guessed that this meant that the xor was successful, and that I can grab the real shellcode. I dumped it again and passed it through shellcode2exe.

I opened the new executable and ran it. It just gave me the flag!

Overall this was a pretty fun CrackMe challenge. The challenge used some basic process injection techniques, as well as a couple of antidebugging tricks I was unaware of, so it was a nice chance to learn and practice. I am planning on doing some of the other Malwarebytes CrackMes soon, as well as some other malware analysis and reverse engineering challenges. For now though I am planning on taking a break and diving into something unrelated in my next post.