Writeups of several PE Reverse Engineering on root-me.org
Introduction
In this blogpost, I'll be going through PE Reverse Engineering challenges on root-me.org . I'll be writing the article and solving the challenges at the same time. So, the blogpost might not include the solutions to all the challenges for the moment
PE x86 - 0 protection
We're given a PE File called ch15.exe , let's load it in detect it easy.
It's a 32bit binary written in C. Going through the strings, nothing is interesting. Let's fire up IDA.
Our goal is to find the main. The start function calls some other functions
Let's recall that the main function takes two parameters argc and argv. Going through both functions we find what we looking for in the second function (after return).
Either IDA messed up or the author is trying to confuse us. As the function called main is not the real main but rather the function taking argc and argv as arguments
One thing one should always do is fix the types and rename functions and variables to make things more readable.
So, apparently it expects an argument. It computes its length and passes them both to another function.
After fixing the type, renaming things and transforming the bytes to ascii characters by clicking on "r". We can see what it's doing.
It checks if the passed argument is of length 7 and it compares it character by character to "SPaCIoS" .
That's the flag!
PE DotNet - 0 protection
This one is quite straightforward.
Since it's a .NET File. Open it in DnSpy. Go through the methods.
When you enter a wrong password you ge this message.
PE DotNet - Basic Anti-Debug
We're given a .NET binary. It gets flagged by SmartScreen (no problem xD). It supposedly implements some anti-debug techniques.
Usually, I just fire up DnSpy and start looking through the methods.
The anti-debug method is very simple; It iterates through the list of processes and looks for "dnspy" and "ilspy" which are .NET decompilers/disassembler.
Right above this method in the binary, there's this one which, based on the strings, validates the challenges.
The other method just performs a XOR decryption. The result is then compared to the user input.
PE DotNet - Basic Crackme
We're given a .NET binary. As usual let's fire up DnSpy and go to the entrypoint.
There's the Main function and another function called ROT13ENCODE, it's a misleading name since it's performing XOR decryption
A binary is decrypted and put in a file
Let's set a breakpoint before the process is started at the end of the main function and run the binary.
The file's content is inside the byte2 variable. Let's save it in a file.
But when we check the size it's different, almost the half.
Let's go through the same steps. We have another executable now. Also half the size.
Let's open it up in DnSpy. Sigh! It has a different name.
Going through the methods, we find the winning one.
PE x86 - Xor Madness
Let's open the file in IDA
It seems like it's reading an input and performing some XOR operation. Seems bad xd! Let's resort to some dynamic analysis with x32dbg.
There are recurring patterns made mostly of XOR and CALL.
After a long time of thinking and steping over instruction, it turns out that instead of allocating space on the stack in the usual way (sub esp, 4 for example), it does so by calling the next instruction with the CALL instruction which pushes the return address onto the stack, and it can be zeroed and overwritten with what's meant to be written to the stack in the first place.
Take this part of code
- The first call instruction pushes an address to the stack and jumps to the next instructions.
- xor eax, eax : zeroes the eax register.
- xor eax,dword ptr ss:[esp] : Since eax register is Zero, it moves the top of the stack (address pushed by the call) to the eax register
- xor dword ptr ss:[esp],eax : This Zeroes the top of the stack, since both operands contains the same value
- xor dword ptr ss:[esp],203A : Move 0x203A to the top of the stack replacing the Zeroes.
These steps are similar to pushing 0x203A onto the stack.
The same goes for the other values. They are moved into the stack.
This stack string is "Password: " which appears when you run the binary.
This stack string is "Password: " which appears when you run the binary.
The same thing goes for the format string "%63s" which is passed as argument in eax to scanf()
At this point, we give a certain input that is read by scanf(). Its address is put EDI register.
Following our input, by stepping through the debugger.
The content of EDI is moved to ESP.
0x100 is subtracted from ESP.
And in the end our input is pointed to by the ESI register. The first byte is then moved to bl by XOR. Finally, ebx which contains the first byte of our input is XORed with the number.
Next, we remove the other 3 bytes from the ebx register and only keep the result of XOR of our byte with the least significant byte which is 0x54
We established earlier that ESP = &our_input[0] - 0x100.
The next set of instructions copy ESP into EAX, XORes EBX with EAX then copies EAX into EDX.
Next, the content pointed to by EAX is Zeroed by Xoring.
Then, ESP ( = &our_input[0] - 0x100) is moved to EAX which XORed with 0xf2 and 0x82.
Next, the value at the address EAX is set to 0x1 and at the end of this set of instructions EDX ( = (&our_input[0] - 0x100) ^ our_input[0] ^ 0x54 ) is dereferenced and the result is put into EDX itself.
These instructions first copy an older return address to eax then subtract from it the current return address on the stack then the result is multiplied by dl which was set to 0x1 before.Then it returns to one of the addresses depending on whether the value at dl was 0x1 or 0x0.
So, we need to make (&our_input[0] - 0x100) ^ 0xf2 ^ 0x82 and (&our_input[0] - 0x100) ^ our_input[0] ^ 0x54 ) equal which makes our_input[0] = 0xf2 ^ 0x82 ^ 0x54 = 0x24 = "$" in ascii.
When they match, we jump to this function
We notice the pattern of XORing and we guess the next byte is 0x67 ^ 0xBA ^ 0xA8 = 0x75 = "u" in ASCII.
The pattern is XOR the least significant byte of the big number with the two small number in the end.
The next one gives "C"
PE x86 - SEHVEH
This challenges tests quite well one's understanding of how exception handling works on Windows.
The first 4 bytes of our input are loaded into EAX with the loadsd instruction and then a check is made
So, our input must verify the constraint (our_input[:4] ^ 0x5A643059) == 0x3628552E which makes our_input = 0x3628552E ^ 0x5A643059 = "lLew" (actually "weLl" due to endianness)
Next, we encounter a "int 1" which causes an exception to occur and the SEH handler to be executed.
SEH Functions take a context as argument. This context contains a snapshot of all registers before the SEH funciton is invoked. [ESP+C] contains a pointer the CONTEXT structure which moved into EAX.
[EAX+B0] : contains the context value of EAX which holds the second 4 bytes of our input (loaded via loadsd above the "int 1" instruction).
[EAX+B8] : contains a the context value of EIP
So, we can build again a constraint (our_input[4:8]+0x48335621) ^ 0x495F4265 == 0xFF2CF8E5
which makes our_input[4:8] = "_d@n" (taking into account endianness)
Next, a Vector Exception Handler is registered with First value = 1 which means this VEH function will be executed first in case of an exception.
Therefore, this VEH Function subtracts that number from the EAX register.
The part of the program that causes the exception is this
The next instruction moves EBX into the memory referenced by EBP wich is 0 which causes and ACCESS VIOLATION.
The VEH function gets executed and after it the SEH function is executed as well.
So, we can build a constraint with these values
(our_input[8:12] - 0x21486553+0x48335621) ^ 0x74406653 = 0x3C4C7440 which can be solved directly which makes our_input[8:12] = "E!!!" considering the endianness.
So the full correct input should be "weLl_d@nE!!!"
PE DotNet - KeygenMe
This is challenges almsot made me crazy but I learned a new trick.
As always we load the .NET file in DnSpy. When you to the constructor you see "Koi"
Also, when you check other methods, you can see that they invalid (or corrupted).
Let's follow the constructor from before and look for the decryption routine.
This is it!
So I'm gonna set a breakpoint somewhere in the function, run the binary and continue execution until the functions returns (click on the arrow button pointing upwards in the top).
One small problem to handle is that. If we save the module as it is right now. It'd still execute the decryption routine which will cause an error.
Handling this issue is simple. We overwrite the call to the decryption routine in the constructor will NOP (0x90).
Right click on the instruction and choose "Edit IL Instruction"
Let's save it. Click on Files -> Save Module and enable the MD Writer Options and Click OK.
Since the flag is the key to the username "Root-Me", we'll enter it as a username and some random key and follow execution.
It compares our key to the correct key character by character
There's also a check to see if our key contains two string (value and value2)
These two strings are computed based on our username. They can be retrieved with a simple breakpoint
You can set a breakpoint on the return value of the XOR decryption function and build you key character by character.
There are two valid keys, since the checks on the value and value2 strings doesn't depend on their location in the key and they have the same length. So, they're interchangeable. So try both as the flag.
Comments
Post a Comment