Crackme 8: ELF, fake instructions

Mar 18, 2018

Link: (binary)

This program contains a bunch of made-up code interspersed with the input validation to confuse a reverser. main looks at argc and exits if there’s no command line parameter. Then, a block allocates a buffer, copies a string into it:

mov        dword [esp+0xc0+var_C0], 0x1d ; argument "__size" for method j_malloc, CODE XREF=main+44
call       j_malloc     ; malloc
mov        dword [ebp-0x94], eax
mov        dword [esp+0xc0+var_B8], 0x1f ; argument "__n" for method j_memcpy
mov        dword [esp+0xc0+var_BC], 0x8048910 ; argument "__src" for method j_memcpy
mov        eax, dword [ebp-0x94]
mov        dword [esp+0xc0+var_C0], eax ; argument "__dest" for method j_memcpy
call       j_memcpy     ; memcpy

Below is an example of the obfuscation from this crackme, a block testing if (0x64 < 0x4). Since it’s never the case, the instructions between the jump and the label are always executed:

mov        eax, 0x64
cmp        eax, 0x4
jb         loc_804861c ; just below

mov        dword [ebp-0xac], 0x19
mov        edi, dword [ebp-0xa4]
mov        ecx, dword [ebp-0xac]
mov        eax, dword [ebp-0xa8]
rep stosd  dword [edi], eax


The code that follows performs some string manipulation through memcpy and strcpy. There is no branching logic so we can continue until we find a test. A local function pointer is set to WPA, a function using a deceptive name:

mov        dword [function_ptr.2175], WPA ; function_ptr.2175

WPA calls blowfish and RS4, also fake names. The function pointer is called using our input and one of the buffers that was computed:

mov        edx, dword [function_ptr.2175] ; function_ptr.2175
mov        eax, dword [ebp+var_94]
mov        dword [esp+0xc0+var_BC], eax
lea        eax, dword [ebp+var_2A]
mov        dword [esp+0xc0+var_C0], eax
call       edx

Following in WPA, a validation message is printed (the string at 0x804893c says it’s about to verify the password)

080486dc         mov        dword [esp], 0x804893c                              ; argument "__s" for method j_puts
080486e3         call       j_puts   

Indeed there is a comparison:

080486eb         mov        dword [esp+4], eax     ; argument "__s2" for method j_strcmp
080486ef         mov        eax, dword [ebp+8]
080486f2         mov        dword [esp], eax       ; argument "__s1" for method j_strcmp
080486f5         call       j_strcmp               ; strcmp
080486fa         test       eax, eax
080486fc         jne        WPA+75                 ; jumps just below to `call RS4`
080486fe         call       blowfish               ; blowfish
08048703         mov        dword [esp], 0x0       ; argument "__status" for method j_exit
0804870a         call       j_exit                 ; exit
0804870f         call       RS4

From here, we can see that RS4 prints the failure message while blowfish seems to construct a string and print it. We therefore know that the two inputs to strcmp need to match. Since reconstructing the string manually would involve de-obfuscating the code above, let’s try instead to use gdb to dump its value.

# gdb ./crackme
GNU gdb (Debian 7.12-6)
gdb$ b *0x080486f5
Breakpoint 1 at 0x80486f5
gdb$ r hello
Starting program: ./crackme hello
Vérification de votre mot de passe..
  EAX: 0xFFFFDD3E  EBX: 0x00000000  ECX: 0xFBAD0084  EDX: 0xF7FCD870  o d I t s Z a P c
  ESI: 0x00000002  EDI: 0xFFFFDD3E  EBP: 0xFFFFDCA8  ESP: 0xFFFFDCA0  EIP: 0x080486F5
  CS: 0023  DS: 002B  ES: 002B  FS: 0000  GS: 0063  SS: 002B
=> 0x80486f5 <WPA+49>:  call   0x804847c <strcmp@plt>
   0x80486fa <WPA+54>:  test   eax,eax
   0x80486fc <WPA+56>:  jne    0x804870f <WPA+75>
   0x80486fe <WPA+58>:  call   0x804872c <blowfish>
   0x8048703 <WPA+63>:  mov    DWORD PTR [esp],0x0
   0x804870a <WPA+70>:  call   0x804848c <exit@plt>
   0x804870f <WPA+75>:  call   0x8048803 <RS4>
   0x8048714 <WPA+80>:  mov    DWORD PTR [esp],0x8048964

Breakpoint 1, 0x080486f5 in WPA ()

Let’s see what’s being compared, by dumping the __s1 and __s2 values listed above.

gdb$ x/s $eax
0xffffdd3e: "hello"
gdb$ x/s $esp+4
0xffffdca4: "\b\260\004\bh\335\377\377\246\206\004\b>\335\377\377\b\260\004\b\r"

We have a few options now. We could pass this string in, or we could also neutralize the conditional jump. In Hopper, select test eax, eax and jne WPA+75, and switch to hex mode:

Change the sequence to four 0x90 bytes:

And switch back to the disassembly view, which now shows:

080486f5         call       j_strcmp
080486fa         nop
080486fb         nop
080486fc         nop
080486fd         nop
080486fe         call       blowfish           

Let’s save the program and run it again:

$ ./crackme.nops hello
Vérification de votre mot de passe..
'+) Authentification réussie...
 U'r root!

 sh 3.0 # password: liberté!

We could also have called blowfish() directly from gdb, without modifying it.

gdb$ b main
Breakpoint 1 at 0x8048563
gdb$ r
Starting program: ./crackme
  EAX: 0xF7FCDDBC  EBX: 0x00000000  ECX: 0xFFFFDD90  EDX: 0xFFFFDDB4  o d I t S z a p c
  ESI: 0x00000001  EDI: 0xF7FCC000  EBP: 0xFFFFDD78  ESP: 0xFFFFDD70  EIP: 0x08048563
  CS: 0023  DS: 002B  ES: 002B  FS: 0000  GS: 0063  SS: 002B
=> 0x8048563 <main+15>: sub    esp,0xb0
   0x8048569 <main+21>: mov    eax,DWORD PTR [ecx+0x4]
   0x804856c <main+24>: mov    DWORD PTR [ebp-0x9c],eax
   0x8048572 <main+30>: mov    eax,gs:0x14
   0x8048578 <main+36>: mov    DWORD PTR [ebp-0xc],eax
   0x804857b <main+39>: xor    eax,eax
   0x804857d <main+41>: cmp    DWORD PTR [ecx],0x2
   0x8048580 <main+44>: je     0x80485ae <main+90>

Breakpoint 1, 0x08048563 in main ()
gdb$ call blowfish()
'+) Authentification réussie...
 U'r root!

 sh 3.0 # password: liberté!

Finally, a third alternative would have been to change the JNE (0x75) to a JE (0x74), which would have caused the program to accept only invalid inputs.