October 03, 2013

CrackMe v3 walkthrough

Hello all,

Here is a walk-through for the crackme v3

The binary is here crackme.03

[arno crackme3]$ ls -la crackme.03.32
-rwx------. 1 arno arno 372 May  5 22:50 crackme.03.32
[arno crackme3]$ ./crackme.03.32
Try to find the string of success and make me print it.

Not a surprise GDB can’t load it

[arno crackme3]$ file crackme.03.32
crackme.03.32: ELF 32-bit invalid byte order (SYSV)
[arno crackme3]$ gdb -q ./crackme.03.32
"/mnt/mypartition.blk/devel/geyslan/crackme3/crackme.03.32": not in executable format: File format not recognized

Next step is to have a look at the entry point and try to loop the program so that we could attach GDB to a process

[arno crackme3]$ readelf -l crackme.03.32
readelf: Error: Unable to read in 0x140c bytes of section headers
readelf: Error: Unable to read in 0x9c116c0 bytes of section headers

Elf file type is EXEC (Executable file)
Entry point 0x10020
There are 1 program headers, starting at offset 4

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00010000 0x00030002 0x10020 0x10020 R   0xc0312ab3

Calculating the size of program headers by subtracting VirtAddr from Entry point, we get 32. 0x10020-0x00010000 = 32

Actually we could get the same value by looking at the headers with readelf -h Size of program headers: 32 (bytes)

Loop technique

Looking at the entry point, I decided to make a loop at the 00010022

[arno crackme3]$ cat crackme.03.32 |ndisasm -b32 -o 0x10020 -e32 - |head
00010020  B32A              mov bl,0x2a
00010022  31C0              xor eax,eax
00010024  40                inc eax
00010025  EB12              jmp short 0x10039
00010027  003400            add [eax+eax],dh
0001002A  2000              and [eax],al
0001002C  0100              add [eax],eax
0001002E  0C14              or al,0x14
00010030  90                nop
00010031  7C97              jl 0xffca

“EB FE” opcode stands for “jmp short $”

[arno crackme3]$ sed 's/\x31\xc0/\xeb\xfe/' crackme.03.32 > crackme.03.32.loop
[arno crackme3]$ chmod +x ./crackme.03.32.loop
[arno crackme3]$ ./crackme.03.32.loop

Ok, it is looping now and we can attach GDB to a running process

Also you can see the difference – what was changed

[arno crackme3]$ diff <(hexdump -C crackme.03.32) <(hexdump -C crackme.03.32.loop)
3c3
< 00000020  b3 2a 31 c0 40 eb 12 00  34 00 20 00 01 00 0c 14  |.*1.@...4. .....| --- > 00000020  b3 2a eb fe 40 eb 12 00  34 00 20 00 01 00 0c 14  |.*..@...4. .....|

[arno crackme3]$ diff <(cat crackme.03.32 |ndisasm -b32 -o 0x10020 -e32 -) <(cat crackme.03.32.loop |ndisasm -b32 -o 0x10020 -e32 -)
2c2
< 00010022  31C0              xor eax,eax --- > 00010022  EBFE              jmp short 0x10022

Attaching to the process and debugging it

$ gdb -q -p $(ps -ef|grep crackme |grep -v grep |awk '{print $2}')
Attaching to process 15058
"/mnt/mypartition.blk/devel/geyslan/crackme3/crackme.03.32.loop": not in executable format: File format not recognized
(gdb) disassemble $eip,+15
Dump of assembler code from 0x10022 to 0x10031:
=> 0x00010022:    jmp    0x10022
   0x00010024:    inc    eax
   0x00010025:    jmp    0x10039
   0x00010027:    add    BYTE PTR [eax+eax*1],dh
   0x0001002a:    and    BYTE PTR [eax],al
   0x0001002c:    add    DWORD PTR [eax],eax
   0x0001002e:    or     al,0x14
   0x00010030:    nop
End of assembler dump.
(gdb)

Rolling back our loop-modification

(gdb) set $i = $pc
(gdb) set *(unsigned char*)$i++ = 0x31
(gdb) set *(unsigned char*)$i++ = 0xc0
(gdb) disassemble $eip,+5
Dump of assembler code from 0x10022 to 0x10027:
=> 0x00010022:    xor    eax,eax
   0x00010024:    inc    eax
   0x00010025:    jmp    0x10039

(gdb) display /x $eax
(gdb) display /x $ebx
(gdb) display /x $ecx
(gdb) display /x $edx
(gdb) display /x $edi
(gdb) display /x $esi
(gdb) display /x $ebp
(gdb) display /x $esp

Continuing debugging and watching registers I have found several important parts of the program.

Header checksum check

Here it reads first 46 (0x2e) bytes of itself, starting from the very beginning

=> 0x0001003b:    mov    ecx,0x10000         ; sets the counter to 0x10000
   0x00010040:    xor    edx,edx
   0x00010042:    xor    ebx,ebx
   0x00010044:    mov    bl,BYTE PTR [ecx]   ; reads the 1st byte of itself
   0x00010046:    add    edx,ebx             ; adds the first byte to the next byte of itself
   0x00010048:    inc    ecx                 ; increase the counter
   0x00010049:    cmp    ecx,0x1002e         ; until read 46 bytes
   0x0001004f:    jne    0x10044             ; repeat

These are the first 46 bytes of the program

[arno crackme3]$ cat crackme.03.32 |head -c46 |hexdump -C
00000000  7f 45 4c 46 01 00 00 00  00 00 00 00 00 00 01 00  |.ELF............|
00000010  02 00 03 00 20 00 01 00  20 00 01 00 04 00 00 00  |.... ... .......|
00000020  b3 2a 31 c0 40 eb 12 00  34 00 20 00 01 00        |.*1.@...4. ...|
0000002e

The code 0×00010044 to 0x0001004f does the following EDX=EDX+EBX, where EDX = result of EDX+EBX, and EDX = is the result of EDX+EBX, where EBX is the next byte of the program itself. So that

0+7f = 0x7f
7f+45 = 0xc4
c4+4c = 0x110
0x110+46 = 0x156
0x156+01 = 0x157

… and so on.

EDX contains the checksum value of the program headers.

=> 0x00010051:    shl    edx,0x2                 ; multiplies EDX by 4
   0x00010054:    cmp    dx,WORD PTR ds:0x1002e  ; compares result with the value stored at 0x1002e address

(gdb) x/xh 0x1002e
0x1002e:    0x140c

[arno crackme3]$ cat crackme.03.32 |head -c48 |tail -c2 |hexdump -C
00000000  0c 14                                             |..|
00000002

word (4 bytes) at the 0x1002e address (0x10000 is a VirtAddr and 2e = 46 in decimal)
[arno crackme3]$ dd if=./crackme.03.32 bs=1 count=2 skip=46 2>/dev/null|hexdump -C
00000000  0c 14                                             |..|
00000002

   0x0001005b:    jne    0x10086           ; if checksum KO, then program will print original message and exit
   0x0001005d:    xor    ebp,ebp
   0x0001005f:    mov    edi,edx           ; moves checksum EDX (0x140c) to EDI
   0x00010061:    inc    ebp               ; EBP = 1
   0x00010062:    mov    eax,0x8010        ; 1: /x $eax = 0x8010
   0x00010067:    inc    ebp               ; EBP = 2
   0x00010068:    mul    ebp               ; EBP * EAX which is 0x8010*0x2 and results EAX = 0x10020
   0x0001006a:    xchg   esi,eax           ; now ESI = 0x10020 and EAX = 0
   0x0001006b:    mov    eax,esi           ; also now EAX is back and = 0x10020
   0x0001006d:    sub    ax,WORD PTR ds:0x10014    ; $ax = 0x20; [0x10014] = 0x00010020, so that result EAX = 0x10000
   0x00010074:    jne    0x10086           ; if not equal jump to 0x10086 (then to 0x100c2 and print 0x1008a, exit), otherwise continue
   0x00010076:    sub    esi,edi           ; ESI = ESI-EDI = 0x10020-0x140c = 0xec14
   0x00010078:    xor    si,0xec14         ; SI = 0xec14, xor with 0xec14 = gives 0
   0x0001007d:    jne    0x10086           ; if result is <> 0 , then jump to exit (0x10086)
   0x0001007f:    jmp    0x10082           ; continue

   0x00010082:    xor    eax,eax           ; EAX = 0
   0x00010084:    jne    0x100e4           ; if KO, then jump to 0x100e4 (** TO CHECK THIS FUNCTION LATER)
   0x00010086:    sub    edx,edx           ; EDX = 0
   0x00010088:    je     0x100c2           ; goto 0x100c2 (print message and exit)

(gdb) disassemble 0x100c2,+33
Dump of assembler code from 0x100c2 to 0x100e3:
   0x000100c2:    mov    eax,0x4
   0x000100c7:    mov    ebx,0x1
   0x000100cc:    mov    ecx,0x1008a
   0x000100d1:    mov    edx,0x38
   0x000100d6:    int    0x80             ; print the message at ECX
   0x000100d8:    mov    eax,0x1
   0x000100dd:    mov    ebx,0x0
   0x000100e2:    int    0x80             ; exit 0
(gdb) x/s 0x1008a
0x1008a:    "Try to find the string of success and make me print it.\n\270\004"

In normal circumstances the program will just print original message and exit (function at 0x100c2)…

Now I’m going to set break on 0×00010084 address and change ‘jne’ to ‘je’ in order to see the function at the 0x100e4.

Analysing 0x100e4

(gdb) b *0x00010084
Breakpoint 2 at 0x10084
(gdb) c
Continuing.
Dump of assembler code from 0x10084 to 0x100a2:
=> 0x00010084:    jne    0x100e4
   0x00010086:    sub    edx,edx
   0x00010088:    je     0x100c2
   0x0001008a:    push   esp
(gdb) print /x $pc
$5 = 0x10084
(gdb) set $i = $pc
(gdb) set *(unsigned char*)$i++ = 0x74
(gdb) hook-stop
Dump of assembler code from 0x10084 to 0x100a2:
=> 0x00010084:    je     0x100e4
   0x00010086:    sub    edx,edx
   0x00010088:    je     0x100c2
   0x0001008a:    push   esp
(gdb) si
Dump of assembler code from 0x100e4 to 0x10102:
=> 0x000100e4:    xor    edx,edx
   0x000100e6:    push   0x10039                  ; ESP now has value 0x10039
   0x000100eb:    sub    WORD PTR [esp],0xb       ; 39-0xb = 46 (size of headers)
   0x000100f0:    pop    esi                      ; esi = 0x1002e
   0x000100f1:    lea    esi,[esi+0x1]            ; esi now has address 0x1002f that points to 47th byte in the program from the top
   0x000100f4:    sub    ecx,ecx
   0x000100f6:    jne    0x100e4
   0x000100f8:    inc    esi                      ; esi now points to the 48th byte in the program
   0x000100f9:    jmp    0x100fc

=> 0x000100fc:    mov    dl,BYTE PTR [esi]        ; move first byte starting at 48 to EDX
   0x000100fe:    mov    BYTE PTR [esp+ecx*1],dl  ; move it to to ESP + ECX*1 (ECX is a counter)
   0x00010101:    inc    ecx
   0x00010102:    cmp    ecx,0x9                  ; do it until we reach 9. (reading from 48 to 56)
   0x00010105:    jne    0x100f8

Above code of the function at 0x100e4 was reading following bytes from the program

[arno crackme3]$ dd if=./crackme.03.32 bs=1 count=8 skip=48 2>/dev/null|hexdump -C
00000000  90 7c 97 ad b6 b6 c6 c0                           |.|......|
00000008

Obviously this line is the hidden message, but is still encrypted.

Decryption

; when ECX = 0x9, ESP was fully set
(gdb) x/8x $esp
0xff95e250:    0x90    0x7c    0x97    0xad    0xb6    0xb6    0xc6    0xc0

   0x00010107:    sub    edx,edx
   0x00010109:    xor    ecx,ecx                  ; reset the counter
   0x0001010b:    inc    ecx                      ; start from 0
   0x0001010c:    mov    dl,BYTE PTR [esp+ecx*1]  ; take the first letter x/1x $esp+$ecx*1  = 0x7c
   0x0001010f:    sub    dl,0x9                   ; subtract 0x9 from 0x7c
   0x00010112:    xor    dl,0xac                  ; xor result (0x73) with 0xAC = get 0xDF
   0x00010115:    jmp    0x10119

=> 0x00010119:    xor    dl,BYTE PTR [esp+ecx*1-0x1]   ; xor 0xDF with 0x90 = get 0x4F
   0x0001011d:    mov    BYTE PTR [esp+ecx*1],dl       ; mov 0x4F on the place of original 0x7C
   0x00010120:    cmp    ecx,0x8                       ; repeat the whole thing until 8th byte
   0x00010123:    jne    0x1010b                       ; go for the next letter 0x97, subtract 0x9; 0x8e xor 0xAC = 0x22; 0x22 xor 0x4F = 0x6D ...
   0x00010125:    inc    ecx
   0x00010126:    mov    BYTE PTR [esp+ecx*1],0xa      ; set 9th letter to 0xA which stands for End Of Line
   0x0001012a:    dec    ecx
   0x0001012b:    xchg   ecx,edx
   0x0001012d:    inc    edx
   0x0001012e:    inc    esp
   0x0001012f:    jmp    0x10132

Then the above code (from 0x0001010b to 0×00010123) makes following operations to each byte of the line 90 7c 97 ad b6 b6 c6 c0

0x7c - 0x9 = 0x73
0x73 XOR 0xAC = 0xDF
0xDF XOR 0x90 = 0x4F (first letter)

0x97 - 0x9 = 0x8e
0x8e XOR 0xAC = 0x22
0x22 XOR 0x4F = 0x6D (2nd letter)

0xad - 0x9 = 0xa4
0xa4 XOR 0xAC = 0x8
0x8 XOR 0x6D = 0x65 (3rd letter)
...
=> 0x00010132:    mov    eax,0x4
   0x00010137:    mov    ebx,0x1
   0x0001013c:    mov    ecx,esp
   0x0001013e:    pusha
   0x0001013f:    xor    ecx,ecx
   0x00010141:    push   ecx
   0x00010142:    mov    ecx,0x10000                 ; set the counter to 0x10000
   0x00010147:    pop    edx                         ; EDX equals to ESP now
   0x00010148:    mov    ebx,edx
   0x0001014a:    mov    bl,BYTE PTR [ecx]
   0x0001014c:    add    edx,ebx
   0x0001014e:    inc    ecx
   0x0001014f:    cmp    ecx,0x10172                 ; repeat until ECX = 0x10172, 0x172 = 370
   0x00010155:    jne    0x1014a
   0x00010157:    jmp    0x1015a

Meaning of the above part of code was not very clear for me, however then I could already see important comparison check which I could workaround.

(gdb) disassemble 0x1015a,+15
Dump of assembler code from 0x1015a to 0x10169:
   0x0001015a:    cmp    dx,WORD PTR ds:0x10172      ; x/xh 0x10172 = 0x7f6d; however in my case EDX was equal to 0x7f4d
   0x00010161:    jne    0x10086                     ; if comparison fails, then the program goes to 0x10086 - print original message and exit
   0x00010167:    popa
   0x00010168:    jmp    0x1016b

; we will workaround it by chaning "jne" 0x85 to "je" 0x84 opcode
(gdb) set $i = $pc+1
(gdb) set *(unsigned char*)$i++ = 0x84
(gdb) hook-stop
Dump of assembler code from 0x10161 to 0x1017f:
=> 0x00010161:    je     0x10086                     ; skipping the exit now
   0x00010167:    popa
   0x00010168:    jmp    0x1016b

=> 0x0001016b:    int    0x80                        ; print the message (at ECX)
   0x0001016d:    jmp    0x100d8

8: /x $esp = 0xffa41871
7: /x $ebp = 0x2
6: /x $esi = 0x10038
5: /x $edi = 0x140c
4: /x $edx = 0x9
3: /x $ecx = 0xffa41871
2: /x $ebx = 0x1
1: /x $eax = 0x4
(gdb) x/xs 0xffa41871
0xffa41871:    "Omedetou\n"

=> 0x000100d8:    mov    eax,0x1
   0x000100dd:    mov    ebx,0x0
   0x000100e2:    int    0x80                        ; exit

To make a patch I had only to exchange two bytes

(gdb) disassemble /r $eip,+5
Dump of assembler code from 0x10084 to 0x10089:
=> 0x00010084:    75 5e    jne    0x100e4
   0x00010086:    29 d2    sub    edx,edx
   0x00010088:    74 38    je     0x100c2

(gdb) disassemble /r $eip,+15
Dump of assembler code from 0x10161 to 0x10170:
=> 0x00010161:    0f 85 1f ff ff ff    jne    0x10086
   0x00010167:    61    popa
   0x00010168:    eb 01    jmp    0x1016b

[arno crackme3]$ sed 's/\x75\x5e/\x74\x5e/;s/\x85/\x84/' crackme.03.32 > crackme.03.32.cracked

[arno crackme3]$ diff <(cat crackme.03.32 |ndisasm -b32 -o 0x10020 -e32 -) <(cat crackme.03.32.cracked |ndisasm -b32 -o 0x10020 -e32 -)
42c42
< 00010084  755E              jnz 0x100e4 --- > 00010084  745E              jz 0x100e4
118c118
< 00010161  0F851FFFFFFF      jnz dword 0x10086 --- > 00010161  0F841FFFFFFF      jz dword 0x10086

[arno crackme3]$ ./crackme.03.32.cracked
Omedetou

Omedetou – is the hidden message.