Reversing ASM to source code - Challenge 2

Reversing raw x86 asm to identical c++ source code.

9 months ago

Latest Post Hacking an NES Classic by Colin Senner

I had a lot of fun after the first challenge and since we spent most of our time figuring out the specific compiler settings more than reversing, I figured we’d up the game a bit. I sent a simple string encryption program to my friend and asked that he write a keygen for it. I, in turn, received a new program as well to reverse back to the original source. The goal is to get as close as possible to the original source, and at the very least make something that is _functionally_ the same. Let’s give it a go.

Looking at the file in CFF Explorer we see the existence of the .msvcjmc section indicating “Just My Code” debugging is on. We’ll turn that on in visual studio. As well as stack frames and security check.

Yes (/JMC)
Stack Frames (/RTCs)
Enable Security Check (/GS)

Let’s get started on some test source code to see if we can replicate the prologue correctly with the JMC additions.

Hrm…not quite right. At the moment I’m not sure where this is coming from, it’s not in ours.

mov eax, dword ptr ds:[0x0118A024]

Based on the fact that there’s a ‘xor eax, ebp’ instruction after this appears to be a stack check, but it doesn’t manifest in ours. Let’s add a local variable in our source and see if it pops up.

Results in

Nope, no change there, but the ‘mov ecx, 3D’ did go up by 0xD bytes so that’s a start on figuring out that mystery.

Let’s figure out how much space is allocated for local vars and continue on. In the prologue we see ‘sub esp, 130’.

From our original example with no local variables we see it allocated 0xC0 bytes, so 0x130 – 0xC0 = 0x70 bytes. Let’s make a variable to use that many bytes and confirm our local variable space is correct.

Nope, too many. We’re looking for 0x130 bytes total.

This code gives us the correct allocation of local variables, but 90 bytes is a strange number, changing it to 91 bytes, gives the same result, we’ll have to figure this out when we know a bit more about how they’re used.

Moving on.

There is a local variable that starts at ebp-C. Here is it’s initialization code.

It starts at ebp-C because that was automatically generated and initialized to all CC’s, (int3’s) for overflow protection.

Luckily we already know how to generate this code. It’s a little trick in asm that’s useful for hiding strings and data. It’s 5 bytes initialized to 0x2D, 0x32, 0x27, 0x2C, and 0x42. X64dbg helps us and gives us the corresponding ascii characters. Let’s take a stab at generating that asm.

Turns out in reading this we missed a zero initialized local variable at ‘ebp-10’. Let’s add that in too.

The resulting asm is:

Hey! Look at that. The security cookie was added and now our code has the cookie and the ‘xor eax, ebp’ instruction added.

Let’s move onto the second bit of initialized local variables now. Here’s the relevant code.

From first glance, there’s 2 variables here. the variable at ‘ebp-40’ and the variable at ‘ebp-60’. The first one from 40-2C, is 0x18 bytes. The second is 60-49 is 0x18 bytes. And I just realized there’s one more dword at ‘ebp-6C’. We’ll just make an int to fill that space.

The next part is a series of jumps. I’m going to implement it as a for loop initially. Judging by the fact a JGE jump instruction was created, I’m going to assume this is done on unsigned bytes, because I think that it would have generated a JAE instruction if the loop was created on signed bytes. Here’s the next part of the asm

Resulting asm

Couple of things that are different. It looks like that unknown 4 bytes we allocated for an integer is probably just part of the for loop counter, so we can remove that. Also we generated a JAE where the original code used a JGE so my guess was wrong, we are probably using a signed integer in the for loop. Stackoverflow per Intel’s manual explains it this way:

So we’ll need to make sure our for loop uses a signed integer. And since our comparison in our code is just using ‘sizeof(str)’, which will always return an unsigned value, we’ll need to substitute the constant ’24’. The programmer should probably have used sizeof() in this case as changing the string would be easier if the length of the string to decode changed, it’s trivial but worth noting.

The 24 bytes of the string xor’d with 0x42 decodes to: ‘’

Let’s continue to the last part of the code.

A simple call to ShellExecuteA

ShellExecuteA(0, operation, decrypted, NULL, NULL, SW_SHOWNORMAL);

Final source code is this

Apart from the addresses of the stack cookie and calls to CheckESP, security_cookie_check, and ShellExecute. The files are spot on.

Some lingering questions I still have though. Why on earth is the local variable that’s supplied to ShellExecute’s lpOperation 5 bytes!? Was this intentional misdirection? A simple

char lpOperation[] = "";

Would be equivalent and would be less bytes (1). So no idea there. Also the additional of an unused variable that I initially thought of as a key was strange. It appears to just be misdirection. Perhaps we’ll have to outlaw any tricks like this in the future? I suppose it does represent a bit of a real-world scenario in which misdirection is placed (often programmatically). Seems like a simple cheap shot to me :).

Until next time.

I got clarification from the author on the misdirection. It was not purposeful, It turns out he intended to use the key, but forgot, so the bytes were allotted for it, but it went unused, (this is why we turn on /W4 and /Werror in our compiler warnings :)). We found a bug via the assembly, pretty neat!

Colin Senner

Published 9 months ago