October 03, 2013

CrackMe v1 walkthrough

Couple of days ago I have received a crack.me_.32 binary to hack. This was my first crackme challenge and I would like to thank Geyslan (Geyslan G Bem) for giving me this task. Also I must to say Big Thanks to Vivek Ramachandran ‘s Linux Assembly Expert (SLAE) course that gave me great Assembly understanding.

The walkthrough

The crackme binary itself is a 7.7K Linux executable file. When you run it, it asks for a password.

[arno@centos ~]$ ./crack.me.32
Please tell me my password: idk123
No! No! No! No! Try again.

Every attempt to debug it – fails

(gdb) run
Starting program: /home/arno/crack.me.32
I'm sorry GDB! You are not allowed!

Seems it has also tracing protection

[arno@centos ~]$ strace ./crack.me.32 2>&1  |tail -3
write(1, "Tracing is not allowed... Bye\n", 30Tracing is not allowed... Bye
) = 30
exit_group(1)                           = ?

Neither objdump nor ndisasm could help. The crackme binary has corrupted header.

[arno@centos ~]$ objdump -d ./crack.me.32 -M intel

./crack.me.32:     file format elf32-i386

[arno@centos ~]$ file crack.me.32
crack.me.32: ELF 32-bit LSB executable, Intel 80386, invalid version (SYSV), for GNU/Linux 2.6.24, dynamically linked (uses shared libs), corrupted section header size

[arno@centos ~]$ readelf -h ./crack.me.32  |grep Entry
  Entry point address:               0x8048530
[arno@centos ~]$ cat ./crack.me.32 |ndisasm -b32 -o 0x8048530 - |head -14
08048530  7F45              jg 0x8048577
08048532  4C                dec esp
08048533  46                inc esi
08048534  0101              add [ecx],eax
08048536  0100              add [eax],eax
08048538  0000              add [eax],al
0804853A  0000              add [eax],al
0804853C  0000              add [eax],al
0804853E  0000              add [eax],al
08048540  0200              add al,[eax]
08048542  0300              add eax,[eax]
08048544  0000              add [eax],al
08048546  0000              add [eax],al
08048548  308504083400      xor [ebp+0x340804],al

Disassembly looks rubbish. The crackme binary’s entry point address is 0x8048530. To get proper disassembly, ndisasm needs correct offset of the loadable segment.

[arno@centos ~]$ readelf -l ./crack.me.32

Elf file type is EXEC (Executable file)
Entry point 0x8048530
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x00adc 0x00adc R E 0x1000
  LOAD           0x000f04 0x08049f04 0x08049f04 0x00144 0x00184 RW  0x1000
  DYNAMIC        0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x00096c 0x0804896c 0x0804896c 0x0004c 0x0004c R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x000f04 0x08049f04 0x08049f04 0x000fc 0x000fc R   0x1

The loadable segment address is 0x08048000 (PhysAddr of LOAD). Now we can calculate the needed offset: 0x8048530-0x08048000 = 0x530 0x530 = 1328 in decimal

With this, we can now see disassembly properly

[arno@centos ~]$ cat ./crack.me.32 |ndisasm -b32 -o 0x8048530 -e1328 - |head -14
08048530  31ED              xor ebp,ebp
08048532  5E                pop esi
08048533  89E1              mov ecx,esp
08048535  83E4F0            and esp,byte -0x10
08048538  50                push eax
08048539  54                push esp
0804853A  52                push edx
0804853B  6890880408        push dword 0x8048890
08048540  6820880408        push dword 0x8048820
08048545  51                push ecx
08048546  56                push esi
08048547  6834870408        push dword 0x8048734
0804854C  E89FFFFFFF        call dword 0x80484f0
08048551  F4                hlt

We can also notice a call instruction there. Ok, that seems good enough to continue, however I realized that disassembling with the ndisasm takes a lot of time. That is why I decided to break the anti-GDB and anti-ptrace protections.

Breaking anti-GDB & anti-ptrace protections

In order to break anti-GDB protection, you can simply attach the GDB to already running process

[arno@centos ~]$ ps -ef|grep crack
arno      1347  1178  0 18:42 pts/0    00:00:00 ./crack.me.32

[arno@centos ~]$ sudo gdb -q ./crack.me.32 1347
Reading symbols from /home/arno/crack.me.32...
warning: no loadable sections found in added symbol-file /home/arno/crack.me.32
(no debugging symbols found)...done.
Attaching to program: /home/arno/crack.me.32, process 1347
ptrace: Operation not permitted.
/home/arno/1347: No such file or directory.

Unfortunately, ptrace operation was blocked by the crack.me.32 binary. To remedy this protection, I wrote fake ptrace function.

[arno@centos ~]$ cat ptrace.c
int ptrace(int i, int j, int k, int l)
{
    printf("fake ptrace called\n");
}
[arno@centos ~]$ gcc -shared ptrace.c -o ptrace.so

[arno@centos ~]$ LD_PRELOAD=./ptrace.so ./crack.me.32
fake ptrace called
Please tell me my password:

It worked! The fake ptrace was called. Now I could try attaching GDB to a running process

[arno@centos ~]$ ps -ef|grep crack
arno      1414  1353  0 18:49 pts/1    00:00:00 ./crack.me.32

[arno@centos ~]$ sudo gdb -q ./crack.me.32 1414
Reading symbols from /home/arno/crack.me.32...
warning: no loadable sections found in added symbol-file /home/arno/crack.me.32
(no debugging symbols found)...done.
Attaching to program: /home/arno/crack.me.32, process 1414
Reading symbols from ./ptrace.so...(no debugging symbols found)...done.
Loaded symbols for ./ptrace.so
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0x00d0e424 in __kernel_vsyscall ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.el6_3.7.i686

Perfect, it worked. We hooked at the __kernel_vsyscall () !

Let’s see where we are now and how did we get there, running backtrace

(gdb) disassemble
Dump of assembler code for function __kernel_vsyscall:
   0x00d0e414 <+0>:    push   %ecx
   0x00d0e415 <+1>:    push   %edx
   0x00d0e416 <+2>:    push   %ebp
   0x00d0e417 <+3>:    mov    %esp,%ebp
   0x00d0e419 <+5>:    sysenter
   0x00d0e41b <+7>:    nop
   0x00d0e41c <+8>:    nop
   0x00d0e41d <+9>:    nop
   0x00d0e41e <+10>:    nop
   0x00d0e41f <+11>:    nop
   0x00d0e420 <+12>:    nop
   0x00d0e421 <+13>:    nop
   0x00d0e422 <+14>:    jmp    0xd0e417 <__kernel_vsyscall+3>
=> 0x00d0e424 <+16>:    pop    %ebp
   0x00d0e425 <+17>:    pop    %edx
   0x00d0e426 <+18>:    pop    %ecx
   0x00d0e427 <+19>:    ret
End of assembler dump.

(gdb) bt
#0  0x00d0e424 in __kernel_vsyscall ()
#1  0x001f22e3 in __read_nocancel () from /lib/libc.so.6
#2  0x0018c29b in _IO_new_file_underflow () from /lib/libc.so.6
#3  0x0018dfbb in _IO_default_uflow_internal () from /lib/libc.so.6
#4  0x0018f5ca in __uflow () from /lib/libc.so.6
#5  0x00181fbc in _IO_getline_info_internal () from /lib/libc.so.6
#6  0x00181f01 in _IO_getline_internal () from /lib/libc.so.6
#7  0x00180e3a in fgets () from /lib/libc.so.6
#8  0x0804878b in ?? ()
#9  0x00136ce6 in __libc_start_main () from /lib/libc.so.6
#10 0x08048551 in ?? ()

Ok, we are somewhere in the middle of fgets () function, because the crackme binary is waiting us to prompt the password.

While going further (stepi, enter, enter… in gdb), I realized that it can also take a lot of time. Thus, I set the break-pointer to an address that sits before the #7 0x00180e3a in fgets () and continue

(gdb) break *0x0804878b
Breakpoint 1 at 0x804878b

(gdb) continue

The GDB has just froze on “continue” without any response. Logically, it is waiting for the password prompt in the crackme. So for the password I use “test” and hit Enter

[arno@centos ~]$ LD_PRELOAD=./ptrace.so ./crack.me.32
fake ptrace called
Please tell me my password: test

Now I can see that GDB is continuing and stops at the next breakpoint (which I have just set above) – 0x0804878b

Continuing.

Breakpoint 1, 0x0804878b in ?? ()
(gdb) disassemble
No function contains program counter for selected frame.
(gdb) disassemble $eip, +25
Dump of assembler code from 0x804878b to 0x80487a4:
=> 0x0804878b:    lea    0x14(%esp),%eax
   0x0804878f:    mov    %eax,(%esp)
   0x08048792:    call   0x80486ae
   0x08048797:    movl   $0x804a040,0x4(%esp)
   0x0804879f:    lea    0x14(%esp),%eax
   0x080487a3:    mov    %eax,(%esp)
End of assembler dump.

Now, since we know that I entered “test” word as a password, it will be very interesting to see at what location in memory the “test” word is and what is going to happen with it? Logically the crackme has to compare my password with the real one.

(gdb) x/s $eax
0xbfbea0f4:     "test\n"
(gdb) stepi
0x0804878f in ?? ()
(gdb) disassemble
No function contains program counter for selected frame.
(gdb) disassemble $eip, +25
Dump of assembler code from 0x804878f to 0x80487a8:
=> 0x0804878f:    mov    DWORD PTR [esp],eax
   0x08048792:    call   0x80486ae
   0x08048797:    mov    DWORD PTR [esp+0x4],0x804a040
   0x0804879f:    lea    eax,[esp+0x14]
   0x080487a3:    mov    DWORD PTR [esp],eax
   0x080487a6:    call   0x80486e1
End of assembler dump.

Normally, EAX register keeps return value of most syscalls, that is why it was quite simple to find it. ($eax has a PTR 0xbfbea0f4 = “test\n”) Before continuing with GDB step-by-step, I will define hook-stop once, so that after each stepi we know where we are.

(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble $eip, +25
>end

(gdb) stepi
Dump of assembler code from 0x8048792 to 0x80487ab:
=> 0x08048792:    call   0x80486ae
   0x08048797:    mov    DWORD PTR [esp+0x4],0x804a040
   0x0804879f:    lea    eax,[esp+0x14]
   0x080487a3:    mov    DWORD PTR [esp],eax
   0x080487a6:    call   0x80486e1
End of assembler dump.
0x08048792 in ?? ()

First important finding

After several stepi’s I have noticed one very interesting loop function

(gdb)        (Enter - repeats last stepi command)
Dump of assembler code from 0x80486bd to 0x80486d6:
=> 0x080486bd:    mov    edx,DWORD PTR [ebp-0x4]
   0x080486c0:    mov    eax,DWORD PTR [ebp+0x8]
   0x080486c3:    add    edx,eax
   0x080486c5:    mov    ecx,DWORD PTR [ebp-0x4]
   0x080486c8:    mov    eax,DWORD PTR [ebp+0x8]
   0x080486cb:    add    eax,ecx
   0x080486cd:    movzx  eax,BYTE PTR [eax]
   0x080486d0:    xor    eax,0x6c
   0x080486d3:    mov    BYTE PTR [edx],al
   0x080486d5:    add    DWORD PTR [ebp-0x4],0x1
End of assembler dump.
0x080486bd in ?? ()

I will not insert here further output as it will take a lot of space here. But I will try to explain what I did. Basically I continued with ‘stepi’ and noticed that this function repeats on and on – it moves the data to EDX and EAX, then it does XOR EAX with 0x6c. (xor eax,0x6c) I found this interesting and enabled watchers for all registers. However it was enough just to watch EDX and EAX registers:

(gdb) stepi
7: x/s $edx  0x0:     < Address 0x0 out of bounds>
4: x/s $eax  0xbfbea0f4:     "test\n"

(gdb) stepi
7: x/s $edx  0xbfbea0f4:     "test\n"
4: x/s $eax  0xbfbea0f4:     "test\n"

# Then I noticed that EDX is changing
7: x/s $edx  0xbfbea0f4:     "\030est\n"

# Then the result goes to EAX and EDX shifts
7: x/s $edx  0xbfbea0f5:     "est\n"
4: x/s $eax  0xbfbea0f4:     "\030est\n"

On and on... (stepi)
7: x/s $edx  0xbfbea0f5:     "\tst\n"
4: x/s $eax  0xbfbea0f4:     "\030est\n"
...
7: x/s $edx  0xbfbea0f6:     "st\n"
4: x/s $eax  0xbfbea0f4:     "\030\tst\n"

...
7: x/s $edx  0xbfbea0f6:     "\037t\n"
...
4: x/s $eax  0xbfbea0f4:     "\030\t\037t\n"

When EDX got empty, the crackme went further to a much more interesting function! It started to compare my encoded (XOR’ed with 0x6c) password “test” with, obviously, – the real one. :)

Discovering the real password

Moving forward with the gdb (stepi) we are finally getting to the key instruction that compares our key with the real key.

(gdb)
Dump of assembler code from 0x8048702 to 0x804871b:
=> 0x08048702:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048705:    movzx  edx,BYTE PTR [eax]
   0x08048708:    mov    eax,DWORD PTR [ebp+0xc]
   0x0804870b:    movzx  eax,BYTE PTR [eax]
   0x0804870e:    cmp    dl,al
   0x08048710:    je     0x80486e6
   0x08048712:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048715:    movzx  eax,BYTE PTR [eax]
   0x08048718:    test   al,al
   0x0804871a:    jne    0x804872d
End of assembler dump.
0x08048702 in ?? ()

Just before 0x0804870e: cmp dl,al instruction start, I have found out the 1st XOR’ed letter of my word “test” and the 1st letter of the real key

(gdb)
Dump of assembler code from 0x804870e to 0x8048727:
=> 0x0804870e:    cmp    dl,al
   0x08048710:    je     0x80486e6
   0x08048712:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048715:    movzx  eax,BYTE PTR [eax]
   0x08048718:    test   al,al
   0x0804871a:    jne    0x804872d
   0x0804871c:    mov    eax,DWORD PTR [ebp+0xc]
   0x0804871f:    movzx  eax,BYTE PTR [eax]
   0x08048722:    test   al,al
   0x08048724:    jne    0x804872d
   0x08048726:    mov    eax,0x0
End of assembler dump.
0x0804870e in ?? ()
7: x/s $edx  0x18:     < Address 0x18 out of bounds>
6: x/s $ecx  0x5:     < Address 0x5 out of bounds>
5: x/s $ebx  0x2b1ff4:     "|\035\031"
4: x/s $eax  0x1b:     < Address 0x1b out of bounds>
3: x/s $esi  0x0:     < Address 0x0 out of bounds>
2: x/s $esp  0xbfbea0d8:     "\b\241\276\277\253\207\004\b\364\240\276\277@\240\004\b@$+"
1: x/s $ebp  0xbfbea0d8:     "\b\241\276\277\253\207\004\b\364\240\276\277@\240\004\b@$+"

So you can see $EDX = 0×18, which is: 0×18 XOR 0x6c = 0×74 , and 0×74 is a “t” (first letter of my password “test”). Okay, so it obviously compares it with the 1st letter of the real key which is in $EAX = 0x1b. 0x1b XOR 0x6c = 0×77 and it is “w”.

Great, the 1st letter of the key has been discovered!

In order to continue, I have to set $EDX to 0x1b – otherwise comparing instruction (0x0804870e: cmp dl,al) will fail.

(gdb) set $edx = 0x1b
(gdb) print /x $edx
$9 = 0x1b
(gdb) print /x $eax
$10 = 0x1b

To make the password discovery process faster, I will set the break to the comparing instruction itself and continue watching EAX (key) and changing the EDX (my wrong key) registers.

(gdb) break *0x0804870e
Breakpoint 2 at 0x804870e
(gdb) c
Continuing.
Dump of assembler code from 0x804870e to 0x8048727:
=> 0x0804870e:    cmp    dl,al
   0x08048710:    je     0x80486e6
   0x08048712:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048715:    movzx  eax,BYTE PTR [eax]
   0x08048718:    test   al,al
   0x0804871a:    jne    0x804872d
   0x0804871c:    mov    eax,DWORD PTR [ebp+0xc]
   0x0804871f:    movzx  eax,BYTE PTR [eax]
   0x08048722:    test   al,al
   0x08048724:    jne    0x804872d
   0x08048726:    mov    eax,0x0
End of assembler dump.

Collecting the real password, byte-by-byte. NOTE: Each time you continue, don’t forget to substitute the next letter (EDX) of the wrong password (“test”) to the expected-discovered one (EAX).

7: x/s $edx  0x9:     < Address 0x9 out of bounds>
4: x/s $eax  0x4:     < Address 0x4 out of bounds>

>>> hex(0x6c^0x4) = '0x68' = h  (The 2nd letter of the key decrypted !)

(gdb) set $edx = 0x4
(gdb) c

7: x/s $edx  0x1f:     < Address 0x1f out of bounds>
4: x/s $eax  0x15:     < Address 0x15 out of bounds>

>>> hex(0x6c^0x15) = '0x79' = y  (3rd letter of the key)
(gdb) set $edx = 0x15
(gdb) c

7: x/s $edx  0x18:     < Address 0x18 out of bounds>
4: x/s $eax  0x2:     < Address 0x2 out of bounds>

>>> hex(0x6c^0x2) = '0x6e' = n
(gdb) set $edx = 0x2
(gdb) c

Continuing this way I’ve got the whole password which is “whyn0t” !

[arno@centos ~]$ ./crack.me.32
Please tell me my password: whyn0t
The password is correct!
Congratulations!!!