[HackingWeek 2015] [Reverse2] Write up

Ok, the key is the password of the given crackme. We start with our binary crackme-02, let’s discover some information about it

$ file crackme-02
crackme-02: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.32, dynamically linked (uses shared libs), stripped
$ readelf -h crackme-02
En-tête ELF:
Magique:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Classe:                            ELF32
Données:                          complément à 2, système à octets de poids faible d'abord (little endian)
Version:                           1 (current)
OS/ABI:                            UNIX - System V
Version ABI:                       0
Type:                              EXEC (fichier exécutable)
Machine:                           Intel 80386
Version:                           0x1
Adresse du point d'entrée:         0x8048390
Début des en-têtes de programme:   52 (octets dans le fichier)
Début des en-têtes de section:     0 (octets dans le fichier)
Fanions:                           0x0
Taille de cet en-tête:             52 (bytes)
Taille de l'en-tête du programme:  32 (bytes)
Nombre d'en-tête du programme:     8
Taille des en-têtes de section:    40 (bytes)
Nombre d'en-têtes de section:      0
Table d'indexes des chaînes d'en-tête de section: 0

It’s a stripped binary, meaning there are no symbols we can use. We need to find the entry point in order to break at the beginning of the binary execution flow. readelf command makes it easy to find it (as show above). We then load our binary into gdb and break on the EP (Entry Point)

$ gdb crackme-02
(gdb) break *0x8048390

Since we don’t have any symbols, we can’t output main function disassembly. We need to use this command in order to print assembly code :

x/10i $pc

This command displays the 1st ten instructions starting at $pc (Program Counter, equivalent of EIP register)

(gdb) r
Starting program: /home/betezed/cm2

Breakpoint 1, 0x08048390 in ?? ()
(gdb) x/14i $pc
=> 0x8048390:    xor    %ebp,%ebp
   0x8048392:    pop    %esi
   0x8048393:    mov    %esp,%ecx
   0x8048395:    and    $0xfffffff0,%esp
   0x8048398:    push   %eax
   0x8048399:    push   %esp
   0x804839a:    push   %edx
   0x804839b:    push   $0x8048680
   0x80483a0:    push   $0x8048610
   0x80483a5:    push   %ecx
   0x80483a6:    push   %esi
   0x80483a7:    push   $0x8048505 (1)
   0x80483ac:    call   0x8048370 (2)
   0x80483b1:    hlt

We find ourselves at the very beginning of our binary execution flow. These lines prepare __libc_start_main call. In the man page, we can see that the first argument of this function is a pointer to main function. That’s why we can see our main function address in (1) , last pushed on the stack (since it’s the first argument of __libc_start_main), then __libc_start_main is called in (2). We now know that main starts at 0x8048505. We break on this address, keep on executing the binary, and display main instructions

(gdb) b *0x8048505
Breakpoint 2 at 0x8048505
(gdb) c

Breakpoint 2, 0x08048505 in ?? ()
(gdb) set disassembly-flavor intel
(gdb) x/40i $pc
=> 0x8048505:    mov    ebp,esp
   0x8048507:    sub    esp,0xc
   0x804850a:    and    esp,0xfffffff0
   0x804850d:    add    esp,0xc
   0x8048510:    push   0x0
   0x8048512:    push   0x1
   0x8048514:    push   0x0
   0x8048516:    call   0x8048380
   0x804851b:    mov    esp,ebp
   0x804851d:    call   0x8048522
   0x8048522:    pop    ebx
   0x8048523:    add    ebx,0x7
   0x8048526:    jmp    ebx
   0x8048528:    call   0xce0bfa6d
   0x804852d:    add    eax,0x8048595
   0x8048532:    nop
   0x8048533:    mov    ebp,esp
   0x8048535:    sub    esp,0x4
   0x8048538:    and    esp,0xfffffff0
   0x804853b:    add    esp,0x4
   0x804853e:    push   0x8048490
   0x8048543:    call   0x8048340                // printf("Enter password:\n") (1)
   0x8048548:    mov    esp,ebp
   0x804854a:    mov    ebp,esp
   0x804854c:    sub    esp,0xc
   0x804854f:    and    esp,0xfffffff0
   0x8048552:    add    esp,0xc
   0x8048555:    push   0xb
   0x8048557:    push   0x80484a1
   0x804855c:    push   0x0
   0x804855e:    call   0x8048330                // read(0, 0x80484a1, 11) (2)
   0x8048563:    mov    esp,ebp
   0x8048565:    mov    edx,eax
   0x8048567:    dec    edx
   0x8048568:    mov    BYTE PTR [eax+0x80484a0],0x0
   0x804856f:    mov    esi,0x80485a4
   0x8048574:    mov    edi,esi
   0x8048576:    mov    ecx,0x15
   0x804857b:    lods   al,BYTE PTR ds:[esi]     // LOOP : Loop start (3)
   0x804857c:    xor    al,0xaa
   0x804857e:    call   0x8048583
   0x8048583:    pop    ebx
   0x8048584:    add    ebx,0x7
   0x8048587:    jmp    ebx
   0x8048589:    call   0x8d9cc30e
   0x804858e:    add    al,0x8
   0x8048590:    int3                            
   0x8048591:    jne    0x8048595
   0x8048593:    inc    al
   0x8048595:    stos   BYTE PTR es:[edi],al
   0x8048596:    loop   0x804857b                // Loop to LOOP
   0x8048598:    mov    esi,0x80484a1
   0x804859d:    mov    edi,0x80485ae
   0x80485a2:    mov    ecx,edx
   0x80485a4:    push   es
   0x80485a5:    xchg   BYTE PTR [eax+0x4880df04],ch
   0x80485ab:    push   edx
   0x80485ac:    inc    ecx
   0x80485ad:    mov    eax,ds:0xcefedefb
   0x80485b2:    out    dx,eax
   0x80485b3:    cdq
   0x80485b4:    int3
   0x80485b5:    retf
   0x80485b6:    cwde
   0x80485b7:    enter  0x89aa,0xe5              // Start of call to /bin/sh
   0x80485bb:    sub    esp,0x4
   0x80485be:    and    esp,0xfffffff0
   0x80485c1:    add    esp,0x4
   0x80485c4:    push   0x80485ef
   0x80485c9:    call   0x8048350
   0x80485ce:    mov    esp,ebp
   0x80485d0:    xor    eax,eax
   0x80485d2:    jmp    0x80485ee                // End of call to /bin/sh
   0x80485d4:    mov    ebp,esp                  // Start of call to printf "Bad Password\n"
   0x80485d6:    sub    esp,0x4
   0x80485d9:    and    esp,0xfffffff0
   0x80485dc:    add    esp,0x4
   0x80485df:    push   0x80485f7
   0x80485e4:    call   0x8048340
   0x80485e9:    mov    esp,ebp
   0x80485eb:    xor    eax,eax
   0x80485ed:    inc    eax
   0x80485ee:    ret                             // End of call to printf "Bad Password\n"

We switch to intel syntax, since it cleaner. We see that (1) Call to printf function :

0x804853e:    push   0x8048490
0x8048543:    call   0x8048340

which prints

(gdb) x/s 0x8048490
0x8048490:     "Enter password:\n"

(2) Call to read function :

0x8048555:    push   0xb
0x8048557:    push   0x80484a1
0x804855c:    push   0x0
0x804855e:    call   0x8048330 // read call, 0xb is password length

Our buffer sent to read will be saved in memory at address 0x080484a1. The argument 0x0 pushed on the stack is the file descriptor (stdin) If we break after the read function and display our buffer (we sent some ‘AAA…’), we can find them at expected address.

(gdb) x/s 0x80484a1
0x8 0484a1:     'A' 

So far, so good. Later on, we have different bloc of instructions

0x80485b9:   mov    ebp,esp
0x80485bb:   sub    esp,0x4
0x80485be:   and    esp,0xfffffff0
0x80485c1:   add    esp,0x4
0x80485c4:   push   0x80485ef
0x80485c9:   call   0x8048350
0x80485ce:   mov    esp,ebp
0x80485d0:   xor    eax,eax
0x80485d2:   jmp    0x80485ee

Which calls /bin/sh

(gdb) x/s 0x80485ef >> "/bin/sh"

And this bloc

0x80485d4:   mov    ebp,esp
0x80485d6:   sub    esp,0x4
0x80485d9:   and    esp,0xfffffff0
0x80485dc:   add    esp,0x4
0x80485df:   push   0x80485f7
0x80485e4:   call   0x8048340
0x80485e9:   mov    esp,ebp
0x80485eb:   xor    eax,eax
0x80485ed:   inc    eax
0x80485ee:   ret

Which calls printf(‘Bad password\n’)

(gdb) x/s 0x80485f7 >> "Bad password\n"

The trick is to find where is the condition making the execution flow jumping to one or the other bloc. In (3), there is a loop, modifying following code. If we break after the loop, we find this :

0x8048598:    mov    esi,0x80484a1
0x804859d:    mov    edi,0x80485ae
0x80485a2:    mov    ecx,edx
0x80485a4:    lods   al,BYTE PTR ds:[esi]
0x80485a5:    sub    al,0x2
0x80485a7:    scas   al,BYTE PTR es:[edi]
0x80485a8:    jne    0x80485d4                Pointer to printf('Bad password\n')
0x80485aa:    loop   0x80485a4                Loop on our buffer caracters
0x80485ac:    jmp    0x80485b9                If we didn't jump to "Bad password", we jump to '/bin/sh'

When we reach the beginning of the loop

(gdb) x/8i $pc
=> 0x80485a4:    lods   al,BYTE PTR ds:[esi]
   0x80485a5:    sub    al,0x2
   0x80485a7:    scas   al,BYTE PTR es:[edi]
   0x80485a8:    jne    0x80485d4
   0x80485aa:    loop   0x80485a4
   0x80485ac:    jmp    0x80485b9
   0x80485ae:    push   ecx
   0x80485af:    je     0x8048605
(gdb) i r $ecx
ecx            0xa    10

ECX is the loop counter, it equals 10 (11 is the null byte). It means that each character will be loaded in al for each loop, 0x2 will be subtracted from it, and then it will be compared from the corresponding character stored in EDI. If we check EDI’s content

(gdb) x/s $edi
0x80485ae:       "QtTdE3fa2b"

We then guess that the password is the content of EDI, but each letter must be incremented by 2 FLAG : SvVfG5hc4d

Bypass ptrace protection

In order to bypass segfault encountered in debug in gdb, we can create our ptrace function, always returning 0, and then load it (by giving it to LD_PRELOAD environment variable)

$ vi ptrace.c

int ptrace(int i, int j, int k, int l)
    return 0;

$ sudo gcc -m32 -shared ptrace.c -o ptrace.so
$ gdb crackme-02
(gdb) set environment LD_PRELOAD ./ptrace.so
(gdb) run

And binary will be executed without any problem.

Leave a Reply

Your email address will not be published. Required fields are marked *