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.


Let's look for it in the binary in dnspy (ctrl+f). We find this method with the comparison.


That's it! The flag is "DotNetOP".


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.


One could bypass this simply by renaming the tools to something else.
Right above this method in the binary, there's this one which, based on the strings, validates the challenges.


It decode something from base64 and passes it alongside a key to another method.
The other method just performs a XOR decryption. The result is then compared to the user input.


Let's do it in Cyberchef. 

That's the flag!

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


Let's rename it to xor_decrypt and move on.

A binary is decrypted and put in a file


A process is then created with the resulting 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.


Let's open the file in DnSpy. It looks the same, what ? 
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.


That's the flag!

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

let's start from the top.
- 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.

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


Let's call the address of our input &our_input.
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.


EAX and EDX now both contain (&our_input[0] - 0x100) ^ our_input[0] ^ 0x54

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.


EAX now contains (&our_input[0] - 0x100) ^ 0xf2 ^ 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.


The next instructions jump to another address after putting a return address in the stack


Let's take a look at that function

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"


Then "3"


Then "s"

Finally "S"


Finally, 

That's the flag!

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.


Let's inspect the SEH function. It can be found in the SEH tab.



The code of the SEH function is the following


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.


The function is at the address in EBX . Let's follow it in the disassembler.


Recall that [EAX+B0] contains the context value of EAX.
Therefore, this VEH Function subtracts that number from the EAX register.

The part of the program that causes the exception is this


After loading the 3rd 4 bytes of our input in the EAX register and moving that number into EBX.
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"


This is a strong giveaway that the executable is obfuscated with ConfuserEx.
Also, when you check other methods, you can see that they invalid (or corrupted). 


It's just encrypted and decrypted at runtime.

Let's follow the constructor from before and look for the decryption routine.
This is it!


A giveaway is this method


Which is nothing but VirtualProtect


Let's break at advanced stage where this decryption method finished executing.

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).


Execution reached after the decryption method


No the binary in memory is decrypted.
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"


Right click on the CALL instruction and choose "NOP Instruction" and click OK.




Let's save it. Click on Files -> Save Module and enable the MD Writer Options and Click OK.



So we managed to unpack it. The methods are decompiled


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


Each correct character is decoded from base64 and decypted with XOR and then compared with our input 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


The rest of the checks are just character to character comparisons. 
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.

References:


Comments