Crackme 12: ELF, anti-debug

Mar 19, 2018

Link: https://www.root-me.org/en/Challenges/Cracking/ELF-Anti-debug (binary)

The binary is very small and has no symbols, it was likely handcrafted in assembly. Right from the start, we find strange code. The initial JMP lands on the first MOV, which sets eax to 0x30, ebx to 5, and ecx to 0x80480e2 before calling int 0x80. This interrupt triggers a system call. The system call table describes 0x30 as sys_signal, where ebx is the signal number (here 5 is SIGABRT) and ecx is the signal handler. Once the handler is set up, we jump to loc_8048077 which calls int3 and would cause a debugger to stop.

EntryPoint:
08048060         jmp        loc_8048063
08048062         db  0xe8 ; '.'

loc_8048063:
08048063         mov        eax, 0x30
08048068         mov        ebx, 0x5
0804806d         mov        ecx, 0x80480e2
08048072         int        0x80
08048074         jmp        loc_8048077
08048076         into

loc_8048077:
08048077         int3

If we go over these instructions step by step, we end up at 0x80480e2 which the Hopper disassembly recognized as the middle of an instruction. We need to re-interpret the bits there, which gives us some more interesting code.

We initialize eax to 0x8048104, which is right below the instructions disassembled by Hopper. A tangled loop goes over these bytes:

080480e2         mov        eax, 0x8048104
080480e7         jmp        sub_80480da+39          ; this is 08048101 below
080480e9         cmp        eax, 0x80482e8          ; 0x80482e8 is the end of the program
080480ee         je         sub_80480da+41          ; if we've reached it, jump to the ret below
080480f0         jmp        sub_80480da+25          ; otherwise go to the XOR at 080480f3
080480f2         db         0xe8
080480f3         xor        dword [eax], 0x8048fc1  ; we XOR the block of code pointed by eax with this constant
080480f9         add        eax, 0x4                ; then move forward 4 bytes
080480fc         jmp        sub_80480da+37          ; jump immediately below
080480fe         jmp        sub_80480da+17          ; and jump back to the CMP at 080480e9.
08048100         db  0xe8 ; '.'
08048101         jmp        sub_80480da+15          ; we jump back up to 080480e9
08048103         ret                

Wow, this code is self-modifying. Everything from 0x08048104 until the end is XOR’d dword by dword with the constant 0x8048fc1, before we return. We can write a small program to XOR this section and replace it before saving the output to a new binary. I used Hex Fiend for this, but any hex editor will do.

When we disassemble it again, some relevant code is revealed, and we finally see some plaintext strings:

08048100         call       0xc3c867f0
08048105         add        dword [eax], eax
08048107         add        byte [eax], al
08048109         mov        ecx, aEnterThePasswo ; "Enter the password: "
0804810e         mov        edx, 0x14
08048113         call       sub_80481cd

There is also a first loop that transforms an array at 0x8048251, until a null byte is found:

sub_8048138:
08048138         mov        eax, 0x8048251

loc_804813d:
0804813d         cmp        byte [eax], 0x0
08048140         je         loc_8048148

08048142         xor        byte [eax], 0xfc
08048145         inc        eax
08048146         jmp        loc_804813d ; jumps back up

A verification loop is also visible, after the transformation. It compares the XOR’d array above with a constant array, and expect matching values:

sub_8048149:
08048149         mov        eax, 0x8048251
0804814e         mov        ebx, 0x80482d1

loc_8048153:
08048153         mov        cl, byte [eax]
08048155         cmp        cl, byte [ebx]
08048157         jne        loc_8048162

08048159         cmp        cl, 0x0
0804815c         je         loc_804817b

0804815e         inc        eax
0804815f         inc        ebx
08048160         jmp        loc_8048153 ; a few lines above.

The bytes at 080482d1 are: [A5 CF 9D B4 DD 88 B4 95 AF 95 AF 88 B4 CF 97 B9 85 DD 00]. So we need our input XOR 0xFC to match these values. Note that our loop stops when cl is zero.

In Python:

>>> ''.join(map(lambda i: chr(i ^ 0xfc), [0xA5, 0xCF, 0x9D, 0xB4, 0xDD, 0x88, 0xB4, 0x95, 0xAF, 0x95, 0xAF, 0x88, 0xB4, 0xCF, 0x97, 0xB9, 0x85, 0xDD]))
'Y3aH!tHiSiStH3kEy!'