In this post I am going to show how to analyse crackme with radare2 framework. The crackme is from geslan. You can download the source code (crackme.03.asm) and create the binray file (crackme.03).
# nasm -f bin crackme.03.asm
# chmod +x crackme.03
./crackme.03
Try to find the string of success and make me print it.
Before starting dynamic analysis we shall see what information we can extract from binary file using utility rabin2
.
# rabin2 -I crackme.03
havecode true
pic false
canary false
nx false
crypto false
va true
bintype elf
class ELF32
lang c
arch x86
bits 32
machine Intel 80386
os linux
minopsz 1
maxopsz 16
pcalign 0
subsys linux
endian little
stripped false
static true
linenum true
lsyms true
relocs true
rpath NONE
binsz 372
Some basic information about binary can be obtained. It can be seen this is a ELF32 file format binary with enabled nx
protection which hasn't been stripped, which means radare2 should be able to find main function starting address.
Let's check main function starting by
# rabin2 -MRsSz crackme.03
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
[Sections]
idx=00 vaddr=0x00010000 paddr=0x00000000 sz=65568 vsz=65568 perm=m-r-- name=LOAD0
idx=01 vaddr=0x00010000 paddr=0x00000000 sz=52 vsz=52 perm=m-rw- name=ehdr
2 sections
[Symbols]
0 symbols
[Relocations]
0 relocations
A small file, with no symbols, no strings, no sections. Looks like a hand-crafted binary!
Now when we have some information about binary let's say with dynamic analysis. We'll load it up in r2 using the r2 command:
# r2 ./crackme.03
[0x00010020]> aa
[Cannot find function 'entry0' at 0x00010020 entry0 (aa)
[0x00010020]> s entry0
[0x00010020]> pdf
p: Cannot find function at 0x00010020
[0x00010020]> s 0x00010020
[0x00010020]> pd
| ;-- entry0:
| 0x00010020 b32a mov bl, 0x2a ; '*' ; 42
| 0x00010022 31c0 xor eax, eax
| 0x00010024 40 inc eax
,==< 0x00010025 eb12 jmp 0x10039
|| 0x00010027 003400 add byte [eax + eax], dh
|| 0x0001002a 2000 and byte [eax], al
|| 0x0001002c 0100 add dword [eax], eax
|| 0x0001002e 0c14 or al, 0x14
|| 0x00010030 90 nop
|`=< 0x00010031 7c97 jl 0xffca
| 0x00010033 ad lodsd eax, dword [esi]
| ;-- section_end.ehdr:
| 0x00010034 b6b6 mov dh, 0xb6 ; 182
| 0x00010036 c6c0bf mov al, 0xbf ; 191
`--> 0x00010039 29c9 sub ecx, ecx
0x0001003b b900000100 mov ecx, 0x10000 ; section.ehdr
0x00010040 31d2 xor edx, edx
0x00010042 31db xor ebx, ebx
.-> 0x00010044 8a19 mov bl, byte [ecx]
| 0x00010046 01da add edx, ebx
| 0x00010048 41 inc ecx
| 0x00010049 81f92e000100 cmp ecx, 0x1002e
`=< 0x0001004f 75f3 jne 0x10044
0x00010051 c1e202 shl edx, 2
0x00010054 663b152e0001. cmp dx, word [0x1002e] ; [0x1002e:2]=0x140c
,=< 0x0001005b 7529 jne 0x10086
| 0x0001005d 31ed xor ebp, ebp
| 0x0001005f 89d7 mov edi, edx
| 0x00010061 45 inc ebp
| 0x00010062 b810800000 mov eax, 0x8010
| 0x00010067 45 inc ebp
| 0x00010068 f7e5 mul ebp
| 0x0001006a 96 xchg eax, esi
| 0x0001006b 89f0 mov eax, esi
| 0x0001006d 662b05140001. sub ax, word [0x10014]
,==< 0x00010074 7510 jne 0x10086
|| 0x00010076 29fe sub esi, edi
|| 0x00010078 6681f614ec xor si, 0xec14
,===< 0x0001007d 7507 jne 0x10086
,====< 0x0001007f eb01 jmp 0x10082
|||| 0x00010081 d431 aam 0x31
||| 0x00010083 c0755e29 sal byte [ebp + 0x5e], 0x29
0x00010087 d2743854 sal byte [eax + edi + 0x54], cl
,=< 0x0001008b 7279 jb 0x10106
| 0x0001008d 20746f20 and byte [edi + ebp*2 + 0x20], dh
| 0x00010091 66696e642074 imul bp, word [esi + 0x64], 0x7420
| 0x00010097 6865207374 push 0x74732065
,==< 0x0001009c 7269 jb 0x10107
|| 0x0001009e 6e outsb dx, byte [esi]
|| 0x0001009f 67206f66 and byte [bx + 0x66], ch
|| 0x000100a3 207375 and byte [ebx + 0x75], dh
|| 0x000100a6 636365 arpl word [ebx + 0x65], sp
,===< 0x000100a9 7373 jae 0x1011e
||| 0x000100ab 20616e and byte [ecx + 0x6e], ah
||| 0x000100ae 64206d61 and byte fs:[ebp + 0x61], ch
||| 0x000100b2 6b65206d imul esp, dword [ebp + 0x20], 0x6d
||| 0x000100b6 65207072 and byte gs:[eax + 0x72], dh
||| 0x000100ba 696e74206974. imul ebp, dword [esi + 0x74], 0x2e746920
||| 0x000100c1 0ab804000000 or bh, byte [eax + 4]
||| 0x000100c7 bb01000000 mov ebx, 1
||| 0x000100cc b98a000100 mov ecx, 0x1008a
||| 0x000100d1 ba38000000 mov edx, 0x38 ; '8' ; 56
||| 0x000100d6 cd80 int 0x80
||| 0x000100d8 b801000000 mov eax, 1
||| 0x000100dd bb00000000 mov ebx, 0
It seems that the block between 0x00010025 and 0x00010039 is not used. We can hide it with:
[0x00010020]> s 0x00010027
[0x00010027]> Ch 0x00010039 - 0x00010027
[0x00010027]> s-
[0x00010020]> pdf
p: Cannot find function at 0x00010020
[0x00010020]> s 0x00010020
[0x00010020]> pd
;-- entry0:
0x00010020 b32a mov bl, 0x2a ; '*' ; 42
0x00010022 31c0 xor eax, eax
0x00010024 40 inc eax
,=< 0x00010025 eb12 jmp 0x10039
| 0x00010027 (18 bytes hidden)
`-> 0x00010039 29c9 sub ecx, ecx
0x0001003b b900000100 mov ecx, 0x10000 ; section.ehdr
0x00010040 31d2 xor edx, edx
0x00010042 31db xor ebx, ebx
.-> 0x00010044 8a19 mov bl, byte [ecx]
| 0x00010046 01da add edx, ebx
| 0x00010048 41 inc ecx
| 0x00010049 81f92e000100 cmp ecx, 0x1002e
`=< 0x0001004f 75f3 jne 0x10044
0x00010051 c1e202 shl edx, 2
0x00010054 663b152e0001. cmp dx, word [0x1002e] ; [0x1002e:2]=0x140c
,=< 0x0001005b 7529 jne 0x10086
| 0x0001005d 31ed xor ebp, ebp
| 0x0001005f 89d7 mov edi, edx
| 0x00010061 45 inc ebp
| 0x00010062 b810800000 mov eax, 0x8010
| 0x00010067 45 inc ebp
| 0x00010068 f7e5 mul ebp
| 0x0001006a 96 xchg eax, esi
| 0x0001006b 89f0 mov eax, esi
| 0x0001006d 662b05140001. sub ax, word [0x10014]
,==< 0x00010074 7510 jne 0x10086
|| 0x00010076 29fe sub esi, edi
|| 0x00010078 6681f614ec xor si, 0xec14
,===< 0x0001007d 7507 jne 0x10086
,====< 0x0001007f eb01 jmp 0x10082
|||| 0x00010081 d431 aam 0x31
||| 0x00010083 c0755e29 sal byte [ebp + 0x5e], 0x29
0x00010087 d2743854 sal byte [eax + edi + 0x54], cl
,=< 0x0001008b 7279 jb 0x10106
| 0x0001008d 20746f20 and byte [edi + ebp*2 + 0x20], dh
| 0x00010091 66696e642074 imul bp, word [esi + 0x64], 0x7420
| 0x00010097 6865207374 push 0x74732065
,==< 0x0001009c 7269 jb 0x10107
|| 0x0001009e 6e outsb dx, byte [esi]
|| 0x0001009f 67206f66 and byte [bx + 0x66], ch
|| 0x000100a3 207375 and byte [ebx + 0x75], dh
|| 0x000100a6 636365 arpl word [ebx + 0x65], sp
,===< 0x000100a9 7373 jae 0x1011e
||| 0x000100ab 20616e and byte [ecx + 0x6e], ah
||| 0x000100ae 64206d61 and byte fs:[ebp + 0x61], ch
||| 0x000100b2 6b65206d imul esp, dword [ebp + 0x20], 0x6d
||| 0x000100b6 65207072 and byte gs:[eax + 0x72], dh
||| 0x000100ba 696e74206974. imul ebp, dword [esi + 0x74], 0x2e746920
||| 0x000100c1 0ab804000000 or bh, byte [eax + 4]
||| 0x000100c7 bb01000000 mov ebx, 1
||| 0x000100cc b98a000100 mov ecx, 0x1008a
||| 0x000100d1 ba38000000 mov edx, 0x38 ; '8' ; 56
||| 0x000100d6 cd80 int 0x80
||| 0x000100d8 b801000000 mov eax, 1
||| 0x000100dd bb00000000 mov ebx, 0
||| 0x000100e2 cd80 int 0x80
||| 0x000100e4 31d2 xor edx, edx
||| 0x000100e6 6839000100 push 0x10039
||| 0x000100eb 66832c240b sub word [esp], 0xb
||| 0x000100f0 5e pop esi
||| 0x000100f1 8d7601 lea esi, dword [esi + 1] ; 0x1
||| 0x000100f4 29c9 sub ecx, ecx
||| 0x000100f6 75ec jne 0x100e4
We can seee a short loop starting 0x00010044, that loads 0x10000. Since the headers are screwed to prevent loading in GNU Tools, this is likely a checksum to prevent modifications. Further, they are 3 jumps to 0x10086. This may be a badboy.
[0x00010020]> pd @0x10086
0x00010086 29d2 sub edx, edx
,=< 0x00010088 7438 je 0x100c2
Classic trick to fool automatic analyzers. Of cource edx - edx
is always to zero, the jumb is taken. If arrows are annoying you, feel free to turn them off with e asm.lines = false.
[0x00010020]> pd @0x100c2
| 0x000100c2 b804000000 mov eax, 4
| 0x000100c7 bb01000000 mov ebx, 1
| 0x000100cc b98a000100 mov ecx, 0x1008a
| 0x000100d1 ba38000000 mov edx, 0x38 ; '8' ; 56
| 0x000100d6 cd80 int 0x80
.--> 0x000100d8 b801000000 mov eax, 1
|| 0x000100dd bb00000000 mov ebx, 0
|| 0x000100e2 cd80 int 0x80
Time to take a look at the syscall reference, you can also check your local syscall.h. Looks like the first one is a write, and the second one is an exit. Just to be sure, let's check what is printed sys_write takes:
- Like every syscall, the call number in eax (4)
- The file descriptor in ebx (1, aka stdout)
- The buffer to print in ecx (0x1008a), this is an address, and the lenth in edx (0x38).
What is at 0x1008a?
[0x00010020]> ps 0x38 @0x1008a
Try to find the string of success and make me print it.
[0x00010020]>
We should add a comment at 0x1008a:
[0x00010020]> Cca 0x1008a BADBOY
We should focus on avoid jumps to this location.
Since no input/output operations occurs until this jump, you can bet that all this part was a checksum. Let's reverse the checksum, first go back to 0x1003b.
[0x00010020]> pd @0x1003b
0x0001003b b900000100 mov ecx, 0x10000 ; section.ehdr
0x00010040 31d2 xor edx, edx
0x00010042 31db xor ebx, ebx
.-> 0x00010044 8a19 mov bl, byte [ecx]
| 0x00010046 01da add edx, ebx
| 0x00010048 41 inc ecx
| 0x00010049 81f92e000100 cmp ecx, 0x1002e
`=< 0x0001004f 75f3 jne 0x10044
0x00010051 c1e202 shl edx, 2
0x00010054 663b152e0001. cmp dx, word [0x1002e] ; [0x1002e:2]=0x140c
,=< 0x0001005b 7529 jne 0x10086
This code will load the address 0x10000 (Pointing to the first of the binary) in ecx, ebx and ebx are set to zero, and the loop starts:
1. bl = *ecx
2. edx = edx + ebx
3. ecx++
4. goto 1. if ecx != 0x1002e
5. edx = edx * 2
6. goto 0x10086 (badboy) if edx != [0x1002e]
Did you notice that the loop is increasing ecx, and not the value 'pointed' by ecx? This loop will add every bytes between 0x10000 and 0x1002e, and the sum must be equal to the value at 0x1002e.
[0x00010020]> pfw @0x1002e
0x0001002e = 0x140c
This is indeed a checksum to check the integrity of the header.
,=< 0x0001005b 7529 jne 0x10086
| 0x0001005d 31ed xor ebp, ebp
| 0x0001005f 89d7 mov edi, edx
| 0x00010061 45 inc ebp
| 0x00010062 b810800000 mov eax, 0x8010
| 0x00010067 45 inc ebp
| 0x00010068 f7e5 mul ebp
| 0x0001006a 96 xchg eax, esi
| 0x0001006b 89f0 mov eax, esi
| 0x0001006d 662b05140001. sub ax, word [0x10014]
,==< 0x00010074 7510 jne 0x10086
|| 0x00010076 29fe sub esi, edi
|| 0x00010078 6681f614ec xor si, 0xec14
,===< 0x0001007d 7507 jne 0x10086
,====< 0x0001007f eb01 jmp 0x10082
- ebp = 0
- ebp = ebp + 1 + 1
- eax = 0x8010
- eax = ebp*eax
- eax = eax - [0x10014] = eax - 0x140C
- goto badboy if eax != 0
Note: mul ebp
is equivalent to mul eax, ebp
.
- esi = eax = 0x8010 * 2
- esi = esi - edx = esi - 0x140C
- si = si^0xec14
- goto badboy if si != 0
[0x00010020]> pd @0x10082
0x00010082 31c0 xor eax, eax
,=< 0x00010084 755e jne 0x100e4
| 0x00010086 29d2 sub edx, edx
The jump is never taken, and we'll end up in badboy. It seems that we should patch here. To load the file within radare2 in write mode, you can use the -w
option. If the jump was taken, we'll land right after the badboy block.
Let's go to 0x100e4:
[0x00010320]> pd @0x100e4
.---> 0x000100e4 31d2 xor edx, edx
||| 0x000100e6 6839000100 push 0x10039
||| 0x000100eb 66832c240b sub word [esp], 0xb
||| 0x000100f0 5e pop esi
||| 0x000100f1 8d7601 lea esi, dword [esi + 1] ; 0x1
||| 0x000100f4 29c9 sub ecx, ecx
`===< 0x000100f6 75ec jne 0x100e4
.---> 0x000100f8 46 inc esi
,====< 0x000100f9 eb01 jmp 0x100fc
|||| 0x000100fb c3 ret
`----> 0x000100fc 8a16 mov dl, byte [esi]
||| 0x000100fe 88140c mov byte [esp + ecx], dl
||| 0x00010101 41 inc ecx
||| 0x00010102 83f909 cmp ecx, 9
`===< 0x00010105 75f1 jne 0x100f8
Fancy push/pop trick at 0x000100e6:
- 0x10039 is pushed on the stack
- 0xb is substracted from [esp], which is indeed 0x10039
- The top of the stack (0x10039 - 0xb) is popped into esi.
The routine looks roughly like:
- esi = 0x10039
- esi = esi - 0xb + 1 + 1 = 0x10030
It seems that 9 bytes are also pushed on the stack "manually", at 0x000100fe, in a small loop.
|| 0x00010107 29d2 sub edx, edx
|| 0x00010109 31c9 xor ecx, ecx
|| 0x0001010b 41 inc ecx
|| 0x0001010c 8a140c mov dl, byte [esp + ecx]
|| 0x0001010f 80ea09 sub dl, 9
|| 0x00010112 80f2ac xor dl, 0xac
,===< 0x00010115 eb02 jmp 0x10119
||| 0x00010117 e84132540c call 0xc55335d
|| 0x0001011c ff88140c83f9 dec dword [eax - 0x67cf3ec]
|| 0x00010122 0875e6 or byte [ebp - 0x1a], dh
|| 0x00010125 41 inc ecx
|| 0x00010126 c6040c0a mov byte [esp + ecx], 0xa
|| 0x0001012a 49 dec ecx
|| 0x0001012b 87d1 xchg ecx, edx
|| 0x0001012d 42 inc edx
|| 0x0001012e 44 inc esp
,===< 0x0001012f eb01 jmp 0x10132
[0x00010320]> pd @0x10119
||| 0x00010119 32540cff xor dl, byte [esp + ecx - 1]
||| 0x0001011d 88140c mov byte [esp + ecx], dl
||| 0x00010120 83f908 cmp ecx, 8
`===< 0x00010123 75e6 jne 0x1010b
|| 0x00010125 41 inc ecx
|| 0x00010126 c6040c0a mov byte [esp + ecx], 0xa
|| 0x0001012a 49 dec ecx
|| 0x0001012b 87d1 xchg ecx, edx
|| 0x0001012d 42 inc edx
|| 0x0001012e 44 inc esp
,===< 0x0001012f eb01 jmp 0x10132
This looks like a decryption one. Nothing complicated.
- edx = 0
- ecx = 0
- ecx = ecx + 1
- dl = esp + ecx
- dl = dl - 9
- dl = dl^0xac
- dl = dl^(esp+ecx=1)
- goto 3. if ecx != 8
Because the crypted string is: 0x90,0x7c,0x97,0xad,0xb6,0xb6,0xc6,0xc0,0xbf
. We can use python to do the decryption process:
>>> array = [ 0x90, 0x7C, 0x97, 0xAD, 0xB6, 0xB6, 0xC6, 0xC0, 0xBF ]
>>> for i in range(0,8):
... array[i+1] = (array[i+1] - 9) ^ 0xac ^ array[i]
>>> print ''.join([chr(i) for i in array])
�Omedetou
This prints �Omedetou
, a Japanese word meaning Congratulations. The weird char on the front will likely be skip later. Looks like we're on the right track.
Let's go the 0x10132:
[0x00010320]> pd @0x10132
|| 0x00010132 b804000000 mov eax, 4
|| 0x00010137 bb01000000 mov ebx, 1
|| 0x0001013c 89e1 mov ecx, esp
|| 0x0001013e 60 pushal
|| 0x0001013f 31c9 xor ecx, ecx
|| 0x00010141 51 push ecx
|| 0x00010142 b900000100 mov ecx, 0x10000 ; section.ehdr
|| 0x00010147 5a pop edx
|| 0x00010148 89d3 mov ebx, edx
.---> 0x0001014a 8a19 mov bl, byte [ecx]
||| 0x0001014c 01da add edx, ebx
||| 0x0001014e 41 inc ecx
||| 0x0001014f 81f972010100 cmp ecx, 0x10172
`===< 0x00010155 75f3 jne 0x1014a
,===< 0x00010157 eb01 jmp 0x1015a
||| 0x00010159 cd66 int 0x66 ; 'f'
|| 0x0001015b 3b1572010100 cmp edx, dword [0x10172] ; [0x10172:4]=0xffff7f6d
|`=< 0x00010161 0f851fffffff jne 0x10086
| 0x00010167 61 popal
|,=< 0x00010168 eb01 jmp 0x1016b
|| 0x0001016a c9 leave
|`-> 0x0001016b cd80 int 0x80
`==< 0x0001016d e966ffffff jmp 0x100d8
0x00010172 6d insd dword es:[edi], dx
,=< 0x00010173 7fff jg 0x10174
[0x00010320]> pd @0x100d8
.--> 0x000100d8 b801000000 mov eax, 1
|| 0x000100dd bb00000000 mov ebx, 0
|| 0x000100e2 cd80 int 0x80
[0x00010320]> pfw @0x10172
0x00010172 = 0x7f6d
[0x00010320]> ?d pushad
push all general-purpose registers
Once again, a pushad. Since the pushad opcode might not be obvious for everyone. This will push on the stack the following registers:
1. eax = 0x4
2. ebx = 0x1
3. ecx = the previously deciphered text + 1
4. edx = 0x8
After the checksum, registers are poped back, and a syscall (0x80) occurs. It's likely a call to sys_write; Then a jump to the sys_exit of badboy.
To sum up, we need to invert the jump at 0x10084, bypass the final chechsum at 0x10161 (Since it will detect the modification at 0x10084).
First patch:
[0x00010320]> s 0x10084
[0x00010084]> pd 1
,=< 0x00010084 755e jne 0x100e4
[0x00010084]> oo+
File ./crackme.03 reopened in read-write mode
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
[0x00010084]> wx 74
[0x00010084]> pd 1
,=< 0x00010084 745e je 0x100e4
[0x00010084]> oo
File ./crackme.03 reopened in read-only mode
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
If you open your intel manual, you'll see that the opcode for jnz is 75, and the one for jz is 74. But you may not know every correspondences. Fortunately, radare2 provides more convenients way.
Second patch:
[0x00010020]> s 0x10161
[0x00010161]> pd 1
`=< 0x00010161 0f851fffffff jne 0x10086
[0x00010161]> oo+
File ./crackme.03 reopened in read-write mode
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
[0x00010161]> wa je 0x10086
Written 6 bytes (je 0x10086) = wx 0f841fffffff
[0x00010161]> pd 1
`=< 0x00010161 0f841fffffff je 0x10086
[0x00010161]> oo
File ./crackme.03 reopened in read-only mode
Warning: Cannot initialize section headers
Warning: Cannot initialize strings table
Warning: Cannot initialize dynamic strings
Let's check that everything is working:
# ./crackme.03
Omedetou