An Easy Linux Crackme

Posted on Sun 11 May 2014 in Reverse Engineering

The website http://crackmes.de contains a huge collection of applications that have been specifically created for people to practice reverse engineering, software cracking and keygen writing.

This solution tutorial is for a very easy one but one that can be cracked without much reverse engineering experience and knowledge. It will, however, help if you understand how function calls work at the assembly level and how the stack works.

The App

Here we will take on this challenge: http://crackmes.de/users/seveb/crackme1/

We will work on the 32 bit version, as we will see this version is actually broken but the answer that we get works fine on the 64 bit version.

Get To Know The App

Lets try to find out some information about this application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
root@dev:~# tar vxzf crackme01.tar.gz 
crackmes/
crackmes/crackme1_64bit
crackmes/crackme1_32bit
root@dev:~# cd crackmes
root@dev:~/crackmes# file ./crackme1_32bit 
./crackme1_32bit: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x54fefefcb89fa2ccc70e24ac5f15fe5f5f44ef8b, not stripped
root@dev:~/crackmes# readelf -h ./crackme1_32bit 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048510
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4508 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         30
  Section header string table index: 27
root@dev:~/crackmes# ./crackme1_32bit 
Please enter the secret number: 0123456789
Nope.
root@dev:~/crackmes# echo $?
1
root@dev:~/crackmes# strings ./crackme1_32bit 
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
fflush
exit
__isoc99_scanf
puts
__stack_chk_fail
stdin
printf
strlen
atoi
strcmp
__libc_start_main
__gmon_start__
GLIBC_2.7
GLIBC_2.4
GLIBC_2.0
PTRh0
QVhL
D$L1
D$6<9u  
D$5<6t
D$-E
\$Le3
[^_]
Nope.
Good job.
Please enter the secret number: 
%23s
Evilzone
The Password translates into %s, 
;*2$"

We have got a lot of information here, firstly we run file on line 6 and can see that the file is actually a 32 bit ELF file. This is the file format used for Linux executables.

Looking at the elf headers using readelf -h tells us the entry point address of the application (on line 19), meaning this is the point in memory where execution begins, this could be useful later.

Running the application, it asks us for a "secret number". Putting in something random gives us the output Nope. (on line 31) and exits with exit code 1 (on line 33).

Lastly we've run strings against the application (on line 34) which gives us a list of all of the clear text strings in the executable. 2 things stand out, the Good job. string on line 62, which looks like this is printed to screen if you input the right number, and the Evilzone as well as the The Password translates into %s, stings on lines 65 and 66 respectively. Based on these last 2 strings it looks like the secret number has something to do with the string Evilzone.

Disassemble / Debug The App

Normally now we could use objdump but as this is such an easy one lets go straight into live debugging with gdb:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
root@dev:~/crackmes# gdb -q ./crackme1_32bit
Reading symbols from /root/crackme/crackmes/crackme1_32bit...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) info functions
All defined functions:

Non-debugging symbols:
0x08048420  _init
0x08048460  strcmp
0x08048460  strcmp@plt
0x08048470  printf
0x08048470  printf@plt
0x08048480  fflush
0x08048480  fflush@plt
0x08048490  __stack_chk_fail
0x08048490  __stack_chk_fail@plt
0x080484a0  puts
0x080484a0  puts@plt
0x080484b0  __gmon_start__
0x080484b0  __gmon_start__@plt
0x080484c0  exit
0x080484c0  exit@plt
0x080484d0  strlen
0x080484d0  strlen@plt
0x080484e0  __libc_start_main
0x080484e0  __libc_start_main@plt
0x080484f0  __isoc99_scanf
0x080484f0  __isoc99_scanf@plt
0x08048500  atoi
0x08048500  atoi@plt
0x08048510  _start
0x08048540  __x86.get_pc_thunk.bx
0x08048550  deregister_tm_clones
0x08048580  register_tm_clones
0x080485c0  __do_global_dtors_aux
0x080485e0  frame_dummy
0x0804860d  nope
0x08048638  yes
0x0804864c  main
0x080487c0  __libc_csu_init
0x08048830  __libc_csu_fini
0x08048834  _fini
(gdb) disassemble main
Dump of assembler code for function main:
   0x0804864c <+0>: push   ebp
   0x0804864d <+1>: mov    ebp,esp
   0x0804864f <+3>: push   ebx
   0x08048650 <+4>: and    esp,0xfffffff0
   0x08048653 <+7>: sub    esp,0x50
   0x08048656 <+10>:    mov    eax,gs:0x14
   0x0804865c <+16>:    mov    DWORD PTR [esp+0x4c],eax
   0x08048660 <+20>:    xor    eax,eax
   0x08048662 <+22>:    mov    DWORD PTR [esp],0x8048860
   0x08048669 <+29>:    call   0x8048470 <printf@plt>
   0x0804866e <+34>:    lea    eax,[esp+0x35]
   0x08048672 <+38>:    mov    DWORD PTR [esp+0x4],eax
   0x08048676 <+42>:    mov    DWORD PTR [esp],0x8048881
   0x0804867d <+49>:    call   0x80484f0 <__isoc99_scanf@plt>
   0x08048682 <+54>:    mov    DWORD PTR [esp+0x28],eax
   0x08048686 <+58>:    cmp    DWORD PTR [esp+0x28],0x1
   0x0804868b <+63>:    jne    0x804869f <main+83>
   0x0804868d <+65>:    movzx  eax,BYTE PTR [esp+0x36]
   0x08048692 <+70>:    cmp    al,0x39
   0x08048694 <+72>:    jne    0x804869f <main+83>
   0x08048696 <+74>:    movzx  eax,BYTE PTR [esp+0x35]
   0x0804869b <+79>:    cmp    al,0x36
   0x0804869d <+81>:    je     0x80486a6 <main+90>
   0x0804869f <+83>:    call   0x804860d <nope>
   0x080486a4 <+88>:    jmp    0x80486b3 <main+103>
   0x080486a6 <+90>:    mov    eax,ds:0x804a040
   0x080486ab <+95>:    mov    DWORD PTR [esp],eax
   0x080486ae <+98>:    call   0x8048480 <fflush@plt>
   0x080486b3 <+103>:   mov    DWORD PTR [esp+0x2d],0x0
   0x080486bb <+111>:   mov    DWORD PTR [esp+0x31],0x0
   0x080486c3 <+119>:   mov    BYTE PTR [esp+0x2d],0x45
   0x080486c8 <+124>:   mov    DWORD PTR [esp+0x18],0x1
   0x080486d0 <+132>:   mov    DWORD PTR [esp+0x1c],0x2
   0x080486d8 <+140>:   mov    DWORD PTR [esp+0x20],0x3
   0x080486e0 <+148>:   mov    DWORD PTR [esp+0x24],0x4
   0x080486e8 <+156>:   jmp    0x804875a <main+270>
   0x080486ea <+158>:   lea    edx,[esp+0x35]
   0x080486ee <+162>:   mov    eax,DWORD PTR [esp+0x1c]
   0x080486f2 <+166>:   add    eax,edx
   0x080486f4 <+168>:   movzx  eax,BYTE PTR [eax]
   0x080486f7 <+171>:   mov    BYTE PTR [esp+0x15],al
   0x080486fb <+175>:   lea    edx,[esp+0x35]
   0x080486ff <+179>:   mov    eax,DWORD PTR [esp+0x20]
   0x08048703 <+183>:   add    eax,edx
   0x08048705 <+185>:   movzx  eax,BYTE PTR [eax]
   0x08048708 <+188>:   mov    BYTE PTR [esp+0x16],al
   0x0804870c <+192>:   lea    edx,[esp+0x35]
   0x08048710 <+196>:   mov    eax,DWORD PTR [esp+0x24]
   0x08048714 <+200>:   add    eax,edx
   0x08048716 <+202>:   movzx  eax,BYTE PTR [eax]
   0x08048719 <+205>:   mov    BYTE PTR [esp+0x17],al
   0x0804871d <+209>:   lea    eax,[esp+0x2d]
   0x08048721 <+213>:   mov    DWORD PTR [esp],eax
   0x08048724 <+216>:   call   0x80484d0 <strlen@plt>
   0x08048729 <+221>:   cmp    eax,0x7
   0x0804872c <+224>:   ja     0x8048746 <main+250>
   0x0804872e <+226>:   lea    eax,[esp+0x15]
   0x08048732 <+230>:   mov    DWORD PTR [esp],eax
   0x08048735 <+233>:   call   0x8048500 <atoi@plt>
   0x0804873a <+238>:   lea    ecx,[esp+0x2d]
   0x0804873e <+242>:   mov    edx,DWORD PTR [esp+0x18]
   0x08048742 <+246>:   add    edx,ecx
   0x08048744 <+248>:   mov    BYTE PTR [edx],al
   0x08048746 <+250>:   add    DWORD PTR [esp+0x18],0x1
   0x0804874b <+255>:   add    DWORD PTR [esp+0x1c],0x3
   0x08048750 <+260>:   add    DWORD PTR [esp+0x20],0x3
   0x08048755 <+265>:   add    DWORD PTR [esp+0x24],0x3
   0x0804875a <+270>:   cmp    DWORD PTR [esp+0x1c],0x14
   0x0804875f <+275>:   jle    0x80486ea <main+158>
   0x08048761 <+277>:   mov    DWORD PTR [esp+0x4],0x8048886
   0x08048769 <+285>:   lea    eax,[esp+0x2d]
   0x0804876d <+289>:   mov    DWORD PTR [esp],eax
   0x08048770 <+292>:   call   0x8048460 <strcmp@plt>
---Type <return> to continue, or q <return> to quit---
   0x08048775 <+297>:   test   eax,eax
   0x08048777 <+299>:   jne    0x8048794 <main+328>
   0x08048779 <+301>:   lea    eax,[esp+0x2d]
   0x0804877d <+305>:   mov    DWORD PTR [esp+0x4],eax
   0x08048781 <+309>:   mov    DWORD PTR [esp],0x8048890
   0x08048788 <+316>:   call   0x8048470 <printf@plt>
   0x0804878d <+321>:   call   0x8048638 <yes>
   0x08048792 <+326>:   jmp    0x8048799 <main+333>
   0x08048794 <+328>:   call   0x804860d <nope>
   0x08048799 <+333>:   mov    eax,0x0
   0x0804879e <+338>:   mov    ebx,DWORD PTR [esp+0x4c]
   0x080487a2 <+342>:   xor    ebx,DWORD PTR gs:0x14
   0x080487a9 <+349>:   je     0x80487b0 <main+356>
   0x080487ab <+351>:   call   0x8048490 <__stack_chk_fail@plt>
   0x080487b0 <+356>:   mov    ebx,DWORD PTR [ebp-0x4]
   0x080487b3 <+359>:   leave  
   0x080487b4 <+360>:   ret    
End of assembler dump.

Firstly on line 3 I set the disassembly flavor to intel, this is because I'm more confortable with assembly in intel syntax, it defaults to AT&T.

Looking at the output of info functions its obvious that this application was written in C due to the calls to functions in the C standard library like strcmp on line 9 and printf on line 11. So we can assume that the main function on line 39 is the start of the application from the programmers point of view so we disassemble that function on line 43.

From the disassembly it looks like +29 (line 54) is where its printing 'Please enter the secret number:' and +49 (line 58) is where its getting my input. There are some cmp's going on at +58 (line 60), +70 (line 63) and +79 (line 66), lets run it in gdb, set a breakpoint just after the call to scanf (at +49 or line 58) and step through it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(gdb) break *0x08048682
Breakpoint 1 at 0x8048682
(gdb) r
Starting program: /root/crackme/crackmes/crackme1_32bit
Please enter the secret number: 12345678

Breakpoint 1, 0x08048682 in main ()
(gdb) disassemble $eip,+10
Dump of assembler code from 0x8048682 to 0x804868c:
=> 0x08048682 <main+54>:        mov    DWORD PTR [esp+0x28],eax
   0x08048686 <main+58>:        cmp    DWORD PTR [esp+0x28],0x1
   0x0804868b <main+63>:        jne    0x804869f <main+83>
End of assembler dump.
(gdb) p $eax
$1 = 1

So the EAX register contains the value 1, this value is put into the the memory address pointed to by ESP+0x28 on line 10, which is most likely a pointer variable to a int or unsigned int on the stack. This value is then compared to 0x1 (or 1 in decimal) on line 11 and finally if the comparisons are not equal execution jumps to 0x804869f.

0x804869f is on line 68 (+83) of the disassembly above and all it does is call the nope function. Lets disassemble the nope function and see what it does:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(gdb) disassemble nope
Dump of assembler code for function nope:
   0x0804860d <+0>:     push   ebp
   0x0804860e <+1>:     mov    ebp,esp
   0x08048610 <+3>:     sub    esp,0x18
   0x08048613 <+6>:     mov    eax,ds:0x804a040
   0x08048618 <+11>:    mov    DWORD PTR [esp],eax
   0x0804861b <+14>:    call   0x8048480 <fflush@plt>
   0x08048620 <+19>:    mov    DWORD PTR [esp],0x8048850
   0x08048627 <+26>:    call   0x80484a0 <puts@plt>
   0x0804862c <+31>:    mov    DWORD PTR [esp],0x1
   0x08048633 <+38>:    call   0x80484c0 <exit@plt>
(gdb) x/s 0x8048850
0x8048850:       "Nope."

Clearly this would be bad as it seems to be printing the value Nope. using the puts command on line 10 and exit's the application with exit code 1 on line 12. Looking at man scanf, it says:

These functions return the number of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.

The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs. EOF is also returned if a read error occurs

Lets look at the other comparisons:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(gdb) delete 1
(gdb) break *0x0804868d
Breakpoint 2 at 0x804868d
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/crackmes/crackme1_32bit
Please enter the secret number: 12345678

Breakpoint 2, 0x0804868d in main ()
(gdb) disass $eip,+10
Dump of assembler code from 0x804868d to 0x8048697:
=> 0x0804868d <main+65>:        movzx  eax,BYTE PTR [esp+0x36]
   0x08048692 <main+70>:        cmp    al,0x39
   0x08048694 <main+72>:        jne    0x804869f <main+83>
   0x08048696 <main+74>:        movzx  eax,BYTE PTR [esp+0x35]
End of assembler dump.
(gdb) x/xb $esp+0x36
0xbffffc66:     0x32

This is comparing 0x32 (or 2 in ascii) with 0x39 (or 9 in ascii), so this going to fail and jump to the nope call. As I only put 1 '2' in my number I guess we can assume that this is the 2nd value in the number, lets replace that and see what happens:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/crackmes/crackme1_32bit
Please enter the secret number: 19345678

Breakpoint 2, 0x0804868d in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x804868d to 0x80486a1:
=> 0x0804868d <main+65>:        movzx  eax,BYTE PTR [esp+0x36]
   0x08048692 <main+70>:        cmp    al,0x39
   0x08048694 <main+72>:        jne    0x804869f <main+83>
   0x08048696 <main+74>:        movzx  eax,BYTE PTR [esp+0x35]
   0x0804869b <main+79>:        cmp    al,0x36
   0x0804869d <main+81>:        je     0x80486a6 <main+90>
   0x0804869f <main+83>:        call   0x804860d <nope>
End of assembler dump.
(gdb) x/xb $esp+0x36
0xbffffc66:     0x39
(gdb) x/xb $esp+0x35
0xbffffc65:     0x31

So we have our 2nd number, but looking at the next comparison, we need 6 as our first number, lets start again:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(gdb) delete 2
(gdb) break *0x0804869d
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/crackme/crackmes/crackme1_32bit
Please enter the secret number: 69345678

Breakpoint 3, 0x0804869d in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x804869d to 0x80486b1:
=> 0x0804869d <main+81>:        je     0x80486a6 <main+90>
   0x0804869f <main+83>:        call   0x804860d <nope>
   0x080486a4 <main+88>:        jmp    0x80486b3 <main+103>
   0x080486a6 <main+90>:        mov    eax,ds:0x804a040
   0x080486ab <main+95>:        mov    DWORD PTR [esp],eax
   0x080486ae <main+98>:        call   0x8048480 <fflush@plt>
End of assembler dump.
(gdb) stepi
0x080486a6 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x80486a6 to 0x80486ba:
=> 0x080486a6 <main+90>:        mov    eax,ds:0x804a040
   0x080486ab <main+95>:        mov    DWORD PTR [esp],eax
   0x080486ae <main+98>:        call   0x8048480 <fflush@plt>
   0x080486b3 <main+103>:       mov    DWORD PTR [esp+0x2d],0x0
End of assembler dump.

That worked, now we have the knowledge to create the first part of the application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdlib.h>

void nope()
{
    puts("Nope.");
    exit(1);
}

int main()
{
    int r;
    char input[50];
    printf("Please enter the secret number: ");
    r = scanf("%49s", input);
    if (1 != r)
        nope();
    if ('9' != input[1])
        nope();
    if ('6' != input[0])
        nope();
    fflush(stdin);
    /* REST OF APPLICATION */
    return 0;
}

The actual source code might not be exactly the same but this application fragment will give the same result.

Now lets look at the rest of the code, specifically the calls to strlen, atoi and strcmp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(gdb) delete 3
(gdb) break *0x08048724
Breakpoint 4 at 0x8048724
(gdb) break *0x08048735
Breakpoint 5 at 0x8048735
(gdb) break *0x08048770
Breakpoint 6 at 0x8048770
(gdb) c
Continuing.

Breakpoint 4, 0x08048724 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x8048724 to 0x8048738:
=> 0x08048724 <main+216>:       call   0x80484d0 <strlen@plt>
   0x08048729 <main+221>:       cmp    eax,0x7
   0x0804872c <main+224>:       ja     0x8048746 <main+250>
   0x0804872e <main+226>:       lea    eax,[esp+0x15]
   0x08048732 <main+230>:       mov    DWORD PTR [esp],eax
   0x08048735 <main+233>:       call   0x8048500 <atoi@plt>
End of assembler dump.

So we are at the strlen, looking at the next instruction the result of this is compared with 0x7, lets step inside it and look at the arguments:

1
2
3
4
5
6
(gdb) stepi
0x080484d0 in strlen@plt ()
(gdb) x/3xw $esp
0xbffffc2c:     0x08048729      0xbffffc5d      0xbffffc65
(gdb) x/s 0xbffffc5d
0xbffffc5d:      "E"

Hmmm... ok, we have an 'E' as the argument, lets continue...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(gdb) c
Continuing.

Breakpoint 5, 0x08048735 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x8048735 to 0x8048749:
=> 0x08048735 <main+233>:       call   0x8048500 <atoi@plt>
   0x0804873a <main+238>:       lea    ecx,[esp+0x2d]
   0x0804873e <main+242>:       mov    edx,DWORD PTR [esp+0x18]
   0x08048742 <main+246>:       add    edx,ecx
   0x08048744 <main+248>:       mov    BYTE PTR [edx],al
   0x08048746 <main+250>:       add    DWORD PTR [esp+0x18],0x1
End of assembler dump.

We are now at the atoi call, again lets step inside and have a peek at the arguments:

1
2
3
4
5
6
(gdb) stepi
0x08048500 in atoi@plt ()
(gdb) x/3xw $esp
0xbffffc2c:     0x0804873a      0xbffffc45      0xbffffc65
(gdb) x/s 0xbffffc45
0xbffffc45:      "345\001"

Ok, so that looks like the next 3 numbers I put in as my secret number (69345678), lets continue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(gdb) c
Continuing.

Breakpoint 4, 0x08048724 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x8048724 to 0x8048738:
=> 0x08048724 <main+216>:       call   0x80484d0 <strlen@plt>
   0x08048729 <main+221>:       cmp    eax,0x7
   0x0804872c <main+224>:       ja     0x8048746 <main+250>
   0x0804872e <main+226>:       lea    eax,[esp+0x15]
   0x08048732 <main+230>:       mov    DWORD PTR [esp],eax
   0x08048735 <main+233>:       call   0x8048500 <atoi@plt>
End of assembler dump.
(gdb) stepi
0x080484d0 in strlen@plt ()
(gdb) x/3xw $esp
0xbffffc2c:     0x08048729      0xbffffc5d      0xbffffc65
(gdb) x/s 0xbffffc5d
0xbffffc5d:      "EY"

O..K.., that's unusual, continuing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(gdb) c
Continuing.

Breakpoint 5, 0x08048735 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x8048735 to 0x8048749:
=> 0x08048735 <main+233>:       call   0x8048500 <atoi@plt>
   0x0804873a <main+238>:       lea    ecx,[esp+0x2d]
   0x0804873e <main+242>:       mov    edx,DWORD PTR [esp+0x18]
   0x08048742 <main+246>:       add    edx,ecx
   0x08048744 <main+248>:       mov    BYTE PTR [edx],al
   0x08048746 <main+250>:       add    DWORD PTR [esp+0x18],0x1
End of assembler dump.
(gdb) stepi
0x08048500 in atoi@plt ()
(gdb) x/3xw $esp
0xbffffc2c:     0x0804873a      0xbffffc45      0xbffffc65
(gdb) x/s 0xbffffc45
0xbffffc45:      "678\002"

Hopefully you can see what I'm doing by now, so it looks like there is a loop which is going through my input number 3 by 3, starting from the 3rd character, and converting them into ascii characters. Lets remove the breakpoint at the calls to atoi and strlen and see what that strcmp is doing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) delete 4
(gdb) delete 5
(gdb) c
Continuing.

Breakpoint 6, 0x08048770 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x8048770 to 0x8048784:
=> 0x08048770 <main+292>:       call   0x8048460 <strcmp@plt>
   0x08048775 <main+297>:       test   eax,eax
   0x08048777 <main+299>:       jne    0x8048794 <main+328>
   0x08048779 <main+301>:       lea    eax,[esp+0x2d]
   0x0804877d <main+305>:       mov    DWORD PTR [esp+0x4],eax
   0x08048781 <main+309>:       mov    DWORD PTR [esp],0x8048890
End of assembler dump.
(gdb) stepi
0x08048460 in strcmp@plt ()
(gdb) x/3xw $esp
0xbffffc2c:     0x08048775      0xbffffc5d      0x08048886
(gdb) x/s 0xbffffc5d
0xbffffc5d:      "EY\246"
(gdb) x/s 0x08048886
0x8048886:       "Evilzone"

Ahha! So it looks like its comparing the converted string with the string found earlier with the strings command (Evilzone). 'E' is equal to 069 on the ascii table (see man ascii for more information), this explains why the first 2 numbers had to be 69. Using the ascii table to work out the rest of the number is easy, it turns out to be 69118105108122111110101.

It doesn't work on the 32 bit version as explained earlier so to test that it is the right number use the 64 bit version:

1
2
3
root@dev64:~/crackmes# ./crackme1_64bit 
Please enter the secret number: 69118105108122111110101
The Password translates into Evilzone, Good job.

Investigating The Bug

Great! Challenge cracked. Now lets run this through gdb and you can see why the 32 bit version of this challenge is broken:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/crackme/crackmes/crackme1_32bit
Please enter the secret number: 69118105108122111110101

Breakpoint 6, 0x08048770 in main ()
(gdb) disass $eip,+20
Dump of assembler code from 0x8048770 to 0x8048784:
=> 0x08048770 <main+292>:       call   0x8048460 <strcmp@plt>
   0x08048775 <main+297>:       test   eax,eax
   0x08048777 <main+299>:       jne    0x8048794 <main+328>
   0x08048779 <main+301>:       lea    eax,[esp+0x2d]
   0x0804877d <main+305>:       mov    DWORD PTR [esp+0x4],eax
   0x08048781 <main+309>:       mov    DWORD PTR [esp],0x8048890
End of assembler dump.
(gdb) stepi
0x08048460 in strcmp@plt ()
(gdb) x/3xw $esp
0xbffffc2c:     0x08048775      0xbffffc5d      0x08048886
(gdb) x/s 0xbffffc5d
0xbffffc5d:      "Evilzone69118105108122111110101"
(gdb) x/s 0x08048886
0x8048886:       "Evilzone"

Ok so this will still fail but why? Look at where our original number is stored:

1
2
3
4
(gdb) x/s $esp+0x35
0xbffffc65:      "69118105108122111110101"
(gdb) x/s 0xbffffc5d
0xbffffc5d:      "Evilzone69118105108122111110101"

0xbffffc65 (Where our original number is stored in memory), 0xbffffc5d (Where the string converted from the original number is stored in memory), 0xbffffc65 - 0xbffffc5d = 8, so these are 8 bytes apart, the string 'Evilzone' is 8 bytes, therefore once our string is calculated, its no longer null terminated.

Rewriting The App

Using the knowledge we have gained about this application, we should now be able to build it ourselves, the following is my implementation of the application written in C, please remember that the real source code may vary:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ANSWER "Evilzone"

void nope()
{
    puts("Nope.");
    exit(1);
}

void good()
{
    puts("Good job.");
}

int main()
{
    int r, i, c;
    char converted[8], input[24], check[4];
    printf("Please enter the secret number: ");
    r = scanf("%23s", input);
    if (1 != r)
        nope();
    if ('9' != input[1])
        nope();
    if ('6' != input[0])
        nope();
    fflush(stdin);
    converted[0] = 'E';
    check[3] = '\0';
    for (i = 2, c = 1; strlen(converted) < 8 && i < strlen(input); i += 3, c++) {
        check[0] = input[i];
        check[1] = input[i+1];
        check[2] = input[i+2];
        converted[c] = atoi(check);
    }
    if (strcmp(converted, ANSWER) == 0) {
        printf("The Password translates into %s, ", converted);
        good();
    }
    else
        nope();
    return 0;
}

This application has the same issue as the original application, it doesn't work on 32 bit systems.

Fixing The App

In my application there were 2 main reasons that it wasn't working on my 32 bit machine, the first was because I wasn't zero'ing out the character array that I use to store the converted string in (converted), because of this the value returned by strlen in the for loop, on line 33, was never less than 8 and the for loop would never be executed. Secondly the string again was not being null terminated, here is the fixed application:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ANSWER "Evilzone"

void nope()
{
    puts("Nope.");
    exit(1);
}

void good()
{
    puts("Good job.");
}

int main()
{
    int r, i, c;
    char converted[9], input[24], check[4];
    printf("Please enter the secret number: ");
    r = scanf("%23s", input);
    if (1 != r)
        nope();
    if ('9' != input[1])
        nope();
    if ('6' != input[0])
        nope();
    fflush(stdin);
    memset(converted, 0, sizeof converted);
    converted[0] = 'E';
    check[3] = '\0';
    for (i = 2, c = 1; strlen(converted) < 8 && i < strlen(input); i += 3, c++) {
        check[0] = input[i];
        check[1] = input[i+1];
        check[2] = input[i+2];
        converted[c] = atoi(check);
    }
    converted[c] = '\0';
    if (strcmp(converted, ANSWER) == 0) {
        printf("The Password translates into %s, ", converted);
        good();
    }
    else
        nope();
    return 0;
}

This works on both 64 bit and 32 bit systems. There are only 3 changes here, the size of the character array converted on line 21 has increased to 9, meaning it now has the space to store the extra null terminator. The call to memset to fill the array with null characters and lastly the explicit null terminator on line 40, it should already be null from the call to memset but just incase.

That concludes this crackme solution. I hope you enjoyed it.

Happy Hacking :-)