|You are here:|
We have to deal with a ELF 32 bit:
$ file reverseMe reverseMe: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped
When the program is run without argument, it outputs the following message:
$ ./reverseMe Format
And when it is run with an argument (incorrect key), it outputs another message, as follows:
$ ./reverseMe aldeid Wrong!
Let's start our analysis in IDA-Pro. Immediately, we notice that we have to deal with a self-modifying code:
Starting from offset, there is a loop that XORs bytes with between offsets and . Then, the code jumps to .
We can use the following python script:
from random import randint def patchme(begin_loc, stop_loc, x): c = randint(0x000000, 0xffffff) for loc in range(begin_loc, stop_loc-1, -1): decoded_byte = Byte(loc) ^ x PatchByte(loc, decoded_byte) print "patched loc %s" % hex(loc) SetColor(loc, CIC_ITEM, c) patchme(0x8048A5C, 0x8048A45, 0xBC)
Once run in IDA-Pro, the script transforms the code as follows:
Static analysis vs dynamic analysis
After many attempts to patch the code, I came to the following script:
from random import randint def patchme(begin_loc, stop_loc, x): c = randint(0x000000, 0xffffff) for loc in range(begin_loc, stop_loc-1, -1): decoded_byte = Byte(loc) ^ x PatchByte(loc, decoded_byte) print "patched loc %s" % hex(loc) SetColor(loc, CIC_ITEM, c) patchme(0x8048A5C, 0x8048A45, 0xBC) patchme(0x8048EA8, 0x8048E91, 0x18) patchme(0x8048F25, 0x8048F0E, 0x21) patchme(0x804978B, 0x8049774, 0x9B) patchme(0x804A03C, 0x804A025, 0x8E) patchme(0x804909C, 0x8049085, 0xF3) patchme(0x8049998, 0x8049981, 0x8F) patchme(0x8048DAE, 0x8048D97, 0x9C) patchme(0x8049D35, 0x8049D1E, 0xFD) patchme(0x80492F4, 0x80492DD, 0x81) patchme(0x804AF5F, 0x804AF48, 0x6A) patchme(0x804AC8A, 0x804AC73, 0x0B) patchme(0x804A3D9, 0x804A3C2, 0x2D) patchme(0x804A5B4, 0x804A59D, 0x08) patchme(0x8049119, 0x8049102, 0x96) patchme(0x804A19A, 0x804A183, 0x4F) patchme(0x804897B, 0x8048964, 0x4D) patchme(0x804AC26, 0x804AC0F, 0xF7) patchme(0x804B027, 0x804B010, 0x9A) patchme(0x804AD9D, 0x804AD86, 0x67) patchme(0x80485DE, 0x80485C7, 0x3F) patchme(0x804A857, 0x804A840, 0x63) patchme(0x8048836, 0x804881F, 0x3D) patchme(0x8049614, 0x80495FD, 0xCF) patchme(0x804A8D4, 0x804A8BD, 0x8B) patchme(0x8048804, 0x80487ED, 0x70) patchme(0x8049759, 0x8049742, 0x69) patchme(0x8048A43, 0x8048A2C, 0x60) patchme(0x8048309, 0x80482F2, 0x17) patchme(0x804A9CE, 0x804A9B7, 0xAB) patchme(0x80489DF, 0x80489C8, 0x6F) patchme(0x8049213, 0x80491FC, 0xC9) patchme(0x804A537, 0x804A520, 0xFB) patchme(0x804986c, 0x8049855, 0xc7) patchme(0x8048642, 0x804862b, 0x6d) patchme(0x804adcf, 0x804adb8, 0x13) patchme(0x8048322, 0x804830b, 0xfc) patchme(0x804aa7d ,0x804aa66, 0xe8) patchme(0x804a4ba, 0x804a4a3, 0x6a) patchme(0x8048e2b, 0x8048e14, 0x98) patchme(0x8049821, 0x804980a, 0x13) patchme(0x8048548, 0x8048531, 0x78) patchme(0x80491fa, 0x80491e3, 0x5d) patchme(0x8049164, 0x804914d, 0xf4) patchme(0x804ad84, 0x804ad6d, 0xeb) patchme(0x80496dc, 0x80496c5, 0x6e) patchme(0x8049dcb, 0x8049db4, 0x2d) patchme(0x804a9e7, 0x804a9d0, 0x4e) patchme(0x8049a92, 0x8049a7b, 0x57) patchme(0x804a7f3, 0x804a7dc, 0x82) patchme(0x80490b5, 0x804909e, 0x91) patchme(0x80496f5, 0x80496de, 0x9e) patchme(0x8048ac0, 0x8048aa9, 0xab) ...
This is where I started to think it was maybe not a so good idea to focus on static analysis and had to think of a different strategy. Indeed, if the program is decrypting itself, there must be some point where it will be fully decrypted in memory and if we can set a breakpoint there, that would be more efficient.
At this stage of my analysis, it appears that static analysis could be a very long process and it might be a better idea to do a dynamic analysis instead. Using strace, you can notice that the error message is displayed at offset :
$ strace -i ./reverseMe aldeid [00007fc0b01a97b7] execve("./reverseMe", ["./reverseMe", "aldeid"], [/* 46 vars */]) = 0 [ Process PID=6775 runs in 32 bit mode. ] [0804b1fc] write(1, "Wrong!\n", 7Wrong! ) = 7 [0804b27c] _exit(0) = ? [????????] +++ exited with 0 +++
The idea is to run the program in a debugger and set a breakpoint at this offset. Chances are that the program will be fully decrypted. Let's use edb to dump the debugged process:
$ ./edb --run reverseMe aldeid
In edb, let's create a hardware breakpoint ( > > ) as follows:
Then run the program. It will reach our breakpoint. Right clik in the memory dump panel and select.
Open the dump in IDA-Pro. At the end of the file, you will notice a serie of XOR instructions as follows:
Chances are that the XOR keys correspond to the secret because they are ASCII characters:
>>> a = [0x41, 0x6C, 0x70, 0x68, 0x34] >>> ''.join([chr(i) for i in a]) 'Alph4'
The opcodes for these instructions are, followed by either or and the XOR key follows, as depicted below:
|80 34 0B 34||xor byte ptr [ebx+ecx], 34h|
|80 34 19 6C||xor byte ptr [ecx+ebx], 6Ch|
The idea here would be to use a python script in IDA-Pro to easily extract all keys (probably the characters of the expected secret).
The following script can be run in IDA-Pro. It will extract all XOR keys of the same type, convert them to characters and reverse the string:
heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA())) secret =  for loc in heads: if (Byte(loc)==0x80 and Byte(loc+1)==0x34): secret.append(Byte(loc+3)) print ''.join([chr(i) for i in secret])[::-1]
Once run, it provides us with the following key:
We can confirm that it is correct:
$ ./reverseMe 'Alph4t3stf0rc3mille!!!!!!!!!' Well done!