Solving the White Rabbit CrackMe
The crackme itself can be found here: https://hshrzd.wordpress.com/2018/02/03/white-rabbit-crackme/.
After struggling a bit with Windows Defender (which detects it as a malware) the crackme finally runs and looks like this:
Okay, the first thing to do is to load the executable into the disassembler (I’ll be using a just released free version of IDA 7 for this) to see what’s going on. A good start is to search for “Password#1” string and see where it is referenced, and what actually happens before and afterwards.
Here it is! One could see it is referenced from sub_4034D0. Now we’ll follow the reference and see what is going on there.
So, there’s some kind of initialization before (sub_403D90), and then there’s branching after comparing result of sub_404150 with a suspicious magic value 0x57585384 afterwards. The subroutine sub_403990 is likely a function that prints the prompt, and everything afterwards is probably related to reading user input.
Let’s have a closer look at initialization part (sub_403D90) first:
The function takes two arguments and looks pretty straigtforward: it finds the resource by provided identifier, loads it, determines its size, and then allocates memory and copies resource data into it. It returns the pointer to newly allocated memory, and stores resource size in the first argument.
The only concern now is sub_406A70. It takes 3 arguments (target pointer, source pointer and data size) and looks pretty much like memcpy (or memmove, which doesn’t matter for us since memory regions don’t overlap). But its code contains some heavy branching and is hard to analyze, so we cannot be sure it doesn’t modify data in some way (e.g. decrypts it) during copying. The easiest way to check it is to run the program in debugger and compare source and target memory right after it returns.
We would use x64Dbg for that. After launching it we’ll open the target executable. It will automatically start it and pause on the entry point:
Now we need to set a breakpoint right after the function of interest return. The address of the instruction is 0x00403DF9 (given the base address for .text section at 0x00401000), but from Memory Map tab we know the actual base address .text is loaded at (in my case it is 0x00281000). So the actual breakpoint address should be 0x00283DF9.
After we set a breakpoint (with “bp 0x00283DF9” command) we continue the execution, and wait until the breakpoint is hit. Then we can right-click on ebx and edi register values in the right pane and follow the memory they’re pointing to side-by-side in two separate memory dump windows.
So, now we have confirmed that sub_406A70 just copies memory “as is”, and can be safely renamed to memcpy for better readability. We’ll also rename sub_403D90 to loadResource.
Now let’s proceed to sub_404150:
The first thing to look at is a constant value 0x82F63B78. Google knows that it it is a well-known polynomial for CRC32 computation. Looking at the code also shows it XORs accumulator value with each byte from the input buffer and then shifts/XORs it 8 times. So it is actually a crc32c computation function. It lacks the classic table lookup though, but the speed is not a concern here :)
So let’s have another look at the code after our renamings and initial analysis:
Note: There could be some confusion about lea/cmovnb instruction pair. The explanation is pretty simple though: the value at lpPasswordText is actually a structure which looks like this:
This is likely a representation of std::string on stack. When the string is short enough to fit the static buffer, no extra memory is allocated (and the address of the static buffer is loaded with lea). Otherwise cmovnb gets the pointer to allocated memory from the dynamic field. In the end, eax will receive the pointer to the actual string data, regardless of its location.
So, sub_401000 reads the keyboard input into std::string, which is later passed to crc32c. Now we know that our password should have CRC32 0x57585384. We cannot recover it from this knowledge, but it’s a good way to check if we got it correctly later.
Now let’s assume that the password has actually matched given CRC32 value and see what happens next:
The first point of interest here is sub_403C90, since it takes both password and resource memory as arguments:
It is pretty obvious this is a XOR encryption routine. It first determines the password length, and then XORs every byte of the input buffer with the corresponding password character.
Then the temporary file name is generated, and the contents of decrypted resource are written to it (by sub_403090). All this is fine and good, but doesn’t give us any clue about the password yet. Let’s look a bit further at sub_403D20, which receives the newly created file name and does something with it:
Ok, now things are becoming clearer. It attempts to set the generated file as a desktop wallpaper, so it obviously should be an image.
Now it’s time to extract that resource from the executable and see what we can learn from it. It can be done with any resource editor, e.g. Resource Hacker:
We can see it’s size is 6,220,854 bytes, which is pretty big for an image. An educated guess would be that it is an uncompressed BMP.
BMP format for Windows is well-known and documented. It starts with “BM” signature, then 4-byte file size (in little-endian), then two 4-byte reserved fields (zeroes), then 4-byte offset to the image data, followed by the 40-byte DIB header (prefixed with its size). Next goes various information about the bitmap, but we don’t know it yet.
Since we know actual file size, we can pretty much speculate about first 18 bytes of the file:
Bytes in resource:
24 22 5A 80 31 77 5F 64 61 5F 44 61 62 62 41 74 7A 66
42 4D 36 EC 5E 00 00 00 00 00 36 00 00 00 28 00 00 00
Now let’s XOR actual and expected values with each other, so we can recover part of the key. Or even the full key if we’re lucky ;)
66 6F 6C 6C 6F 77 5F 64 61 5F 72 61 62 62 69 74 7A 66
This gives us a string “follow_da_rabbitzf”. The last “f” might be either from the key started all over again (and we have got the full key), or it is just a continuation of the key. The easiest way to check this is to enter it and see what happens next.
Yay, we got it right, let’s move on!
Now we have a cool wallpaper on the desktop, and still have another password to crack. Again, search for “Password#2” string and follow the reference:
This looks pretty much similar to the previous one, so let’s move straight to the next blocks where the decryption happens:
The interesting part here is sub_403E10, which decrypts data before writing it to file:
The entire diagram doesn’t fit the screen, but the essential part is like this. It derives an AES128 key from the password (using SHA256 as key derivation function) and uses it to decrypt the resource.
It would be useless to try breaking the AES encryption (probably even NSA can’t do this), and we only know CRC32 of the password. This is obviously not enough to recover it with brute force (I tried!). But wait, we have a wallpaper! Probably there’s some kind of a hidden message there!
Let’s open it in the graphics editor and try to perform some magic with “Color Select” tool:
This might pretty much be a key we’re looking for! And, here we go:
But this is not over yet. Now we have a decrypted executable in the temp folder, and we still don’t have a flag. The disassembler still has work to do.
Since the second executable doesn’t produce any messages, it may be hard to determine where to start. So let’s take a look at the imports:
There’s a bunch of functions from ws2_32.dll imported by their ordinals, which gives us two clues:
- It has something to do with network sockets, and
- It has something to hide!
So the first step would be to go where these functions are called, and give them meaningful names for better readability. Correspondence between ordinals and function names can be easily found in Google.
Now we know all the network interaction happens in sub_404480, so let’s take a closer look at it. It starts with a standard stuff (WSAStartup/socket/bind/listen), so there’s no point to highlight it. The interesting part goes next:
So it accepts an incoming connection, reads up to 4 bytes from it, performs some magic in sub_404640 based on static buffer buf and incoming data, and if the operation was successful (function returned non-zero) it sends contents of the buf back to the client and closes the connection, otherwise it closes the connection and listens for the new one. All the operations are synchronous, so it won’t exit the function until sub_404640 succeeds.
Let’s have a look at sub_404640:
This looks pretty much like a tiny state machine, which returns 1 in case the transition to the next state was successful. The following transitions are possible:
- From initial (0) state to ‘Y’ (if received ‘9’)
- From ‘Y’ state to ‘E’ (if received ‘3’)
- From ‘E’ state to ‘S’ (if received ‘5’)
Receiving anything else would reset state machine to initial (0) state.
So, we would probably need to make 3 sequential connections to this “server” each advancing the state machine to the next state.
But we still have two issues to sort out:
- We don’t know which port to connect to yet (it takes port number as an argument), and
- The listening socket is closed after every successful transition.
So we need to find all the places where the function is called, and keep track of which port it is started on.
As expected, it is called 3 times (since there are 3 valid transitions), and luckily, it is always called from the same procedure:
Here it is:
So the server is initially started on port 1337, then on 1338, and then on 1339. So we first need to connect to port 1337 and send ‘9’, then connect to 1338 and send ‘3’, and finally connect to 1339 and send ‘5’. We can use built-in telnet tool to do this.
Doing so would result in opening a YouTube page with a short video:
So we’ve successfully retrieved the flag. The End.