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)
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.
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.
(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.
; 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.