Crackme 8: ELF, fake instructions
Link: https://www.root-me.org/en/Challenges/Cracking/ELF-Fake-Instructions (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:
loc_80485ae:
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
loc_804861c:
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) 7.12.0.20161007-git
[...]
gdb$ b *0x080486f5
Breakpoint 1 at 0x80486f5
gdb$ r hello
Starting program: ./crackme hello
Vérification de votre mot de passe..
--------------------------------------------------------------------------[regs]
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
--------------------------------------------------------------------------[code]
=> 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
--------------------------------------------------------------------------[regs]
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
--------------------------------------------------------------------------[code]
=> 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.