GreHack-2015/150-reverseMe

From aldeid
Jump to navigation Jump to search
You are here:
150-reverseMe

Description

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!

Analysis

Self-modifying code

Let's start our analysis in IDA-Pro. Immediately, we notice that we have to deal with a self-modifying code:

Starting from offset 0x8048E7E, there is a loop that XORs bytes with 0xBC between offsets 0x8048A45 and 0x8048A5C. Then, the code jumps to 0x8048A45.

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.

Dynamic analysis

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 0x804b1fc:

$ 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 (Plugins > Hardware BreakpointManager > Hardware Breakpoints) as follows:

Then run the program. It will reach our breakpoint. Right clik in the memory dump panel and select Save to File.

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 0x80 0x34, followed by either 0x0B or 0x19 and the XOR key follows, as depicted below:

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

Solution

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:

Alph4t3stf0rc3mille!!!!!!!!!

We can confirm that it is correct:

$ ./reverseMe 'Alph4t3stf0rc3mille!!!!!!!!!' 
Well done!

Comments

Keywords: grehack-2015 assembly reverse-engineering crackme