Crackme0x00 - writeup
我现在开始看radare2book了,现在刚看1/3,有些无聊,因为之前也看过一些radare2的实例讲解,所以现在先试着做一下里面的crackme练习。
先执行一下craceme0x00这个文件,看来是要把密码找出来。
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x00 IOLI Crackme Level 0x00 Password: 1234 Invalid Password!
现在看一下string,使用iz命令。
root@kali:~/IOLIcrackme/bin-linux# radare2 -A crackme0x00 [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) [x] Analyze len bytes of instructions for references (aar) [x] Constructing a function name for fcn.* and sym.func.* functions (aan) [x] Type matching analysis for all functions (afta) [x] Use -AA or aaaa to perform additional experimental analysis. [0x08048360]> iz? | iz|izj Strings in data sections (in JSON/Base64) | izz Search for Strings in the whole binary | izzz Dump Strings from whole binary to r2 shell (for huge files) | iz- [addr] Purge string via bin.strpurge [0x08048360]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x00000568 0x08048568 24 25 (.rodata) ascii IOLI Crackme Level 0x00\n 001 0x00000581 0x08048581 10 11 (.rodata) ascii Password: 002 0x0000058f 0x0804858f 6 7 (.rodata) ascii 250382 003 0x00000596 0x08048596 18 19 (.rodata) ascii Invalid Password!\n 004 0x000005a9 0x080485a9 15 16 (.rodata) ascii Password OK :)\n
好吧,我觉得我找到密码了,虽然不太肯定,但是可以试一下——250382.
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x00 IOLI Crackme Level 0x00 Password: 250382 Password OK :)
成功。
看来真的只是一个简单的热身。
Crackme0x01 - writeup
还是先执行一下(其实我也不太确定是不是每次都要先执行,因为如果是真的maleware可能执行之后就直接game over了,不过这里只是简单的练习,所以就不考虑这个问题了。)
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x01 IOLI Crackme Level 0x01 Password: 12345 Invalid Password!
好吧,仍旧是密码。
那么进入radare2,还是查看string。
[0x08048330]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x00000528 0x08048528 24 25 (.rodata) ascii IOLI Crackme Level 0x01\n 001 0x00000541 0x08048541 10 11 (.rodata) ascii Password: 002 0x0000054f 0x0804854f 18 19 (.rodata) ascii Invalid Password!\n 003 0x00000562 0x08048562 15 16 (.rodata) ascii Password OK :)\n
这次没有直接显示了,但是显然“Password OK”是在告诉我们密码验证通过了,现在看一下这个字符串在哪里引用了
[0x08048330]> axt 0x08048562 main 0x8048442 [DATA] mov dword [esp], str.Password_OK_:
是在main函数里面,直接跳到main函数,然后pdf一下
[0x08048330]> pdf? Usage: pdf[bf] disassemble function | pdf disassemble function | pdfs disassemble function summary [0x08048330]> s main [0x080483e4]> pdf / (fcn) main 113 | main (int argc, char **argv, char **envp); | ; var unsigned int local_4h @ ebp-0x4 | ; var int local_4h_2 @ esp+0x4 | ; DATA XREF from entry0 (0x8048347) | 0x080483e4 55 push ebp | 0x080483e5 89e5 mov ebp, esp | 0x080483e7 83ec18 sub esp, 0x18 | 0x080483ea 83e4f0 and esp, 0xfffffff0 | 0x080483ed b800000000 mov eax, 0 | 0x080483f2 83c00f add eax, 0xf | 0x080483f5 83c00f add eax, 0xf | 0x080483f8 c1e804 shr eax, 4 | 0x080483fb c1e004 shl eax, 4 | 0x080483fe 29c4 sub esp, eax | 0x08048400 c70424288504. mov dword [esp], str.IOLI_Crackme_Level_0x01 ; [0x8048528:4]=0x494c4f49 ; "IOLI Crackme Level 0x01\n" ; const char *format | 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) | 0x0804840c c70424418504. mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: " ; const char *format | 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format) | 0x08048418 8d45fc lea eax, dword [local_4h] | 0x0804841b 89442404 mov dword [local_4h_2], eax | 0x0804841f c704244c8504. mov dword [esp], 0x804854c ; [0x804854c:4]=0x49006425 ; const char *format | 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) | 0x0804842b 817dfc9a1400. cmp dword [local_4h], 0x149a | ,=< 0x08048432 740e je 0x8048442 | | 0x08048434 c704244f8504. mov dword [esp], str.Invalid_Password ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format | | 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format) | ,==< 0x08048440 eb0c jmp 0x804844e | || ; CODE XREF from main (0x8048432) | |`-> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_: ; [0x8048562:4]=0x73736150 ; "Password OK :)\n" ; const char *format | | 0x08048449 e8cefeffff call sym.imp.printf ; int printf(const char *format) | | ; CODE XREF from main (0x8048440) | `--> 0x0804844e b800000000 mov eax, 0 | 0x08048453 c9 leave \ 0x08048454 c3 ret
注意高亮代码,前面是读入用户输入的密码,高亮部分进行了判断,然后跳转输出密码是否正确的提示。
所以用户输入的代码是和0x149a进行了比较,so,密码是
[0x080483e4]> ? 0x149a
hex 0x149a
octal 012232
unit 5.2K
segment 0000:049a
int32 5274
string "\x9a\x14"
binary 0b0001010010011010
fvalue: 5274.0
float: 0.000000f
double: 0.000000
trits 0t21020100
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x01 IOLI Crackme Level 0x01 Password: 5274 Password OK :)
5274!
Crackme0x02 – writeup
这里就不再单独说程序执行的结果了,我之前也没了解过,IOLIcrackme都是查验密码的,直接进入radare2里面了。
前面的步骤都是一样的,这里直接贴pdf 的结果了。
[0x080483e4]> pdf / (fcn) main 144 | main (int argc, char **argv, char **envp); | ; var unsigned int local_ch @ ebp-0xc | ; var signed int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; var int local_4h_2 @ esp+0x4 | ; DATA XREF from entry0 (0x8048347) | 0x080483e4 55 push ebp | 0x080483e5 89e5 mov ebp, esp | 0x080483e7 83ec18 sub esp, 0x18 | 0x080483ea 83e4f0 and esp, 0xfffffff0 | 0x080483ed b800000000 mov eax, 0 | 0x080483f2 83c00f add eax, 0xf | 0x080483f5 83c00f add eax, 0xf | 0x080483f8 c1e804 shr eax, 4 | 0x080483fb c1e004 shl eax, 4 | 0x080483fe 29c4 sub esp, eax | 0x08048400 c70424488504. mov dword [esp], str.IOLI_Crackme_Level_0x02 ; [0x8048548:4]=0x494c4f49 ; "IOLI Crackme Level 0x02\n" ; const char *format | 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) | 0x0804840c c70424618504. mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: " ; const char *format | 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format) | 0x08048418 8d45fc lea eax, dword [local_4h] | 0x0804841b 89442404 mov dword [local_4h_2], eax | 0x0804841f c704246c8504. mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425 ; const char *format | 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) | 0x0804842b c745f85a0000. mov dword [local_8h], 0x5a ; 'Z' ; 90 | 0x08048432 c745f4ec0100. mov dword [local_ch], 0x1ec ; 492 | 0x08048439 8b55f4 mov edx, dword [local_ch] | 0x0804843c 8d45f8 lea eax, dword [local_8h] | 0x0804843f 0110 add dword [eax], edx | 0x08048441 8b45f8 mov eax, dword [local_8h] | 0x08048444 0faf45f8 imul eax, dword [local_8h] | 0x08048448 8945f4 mov dword [local_ch], eax | 0x0804844b 8b45fc mov eax, dword [local_4h] | 0x0804844e 3b45f4 cmp eax, dword [local_ch] | ,=< 0x08048451 750e jne 0x8048461 | | 0x08048453 c704246f8504. mov dword [esp], str.Password_OK_: ; [0x804856f:4]=0x73736150 ; "Password OK :)\n" ; const char *format | | 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format) | ,==< 0x0804845f eb0c jmp 0x804846d | || ; CODE XREF from main (0x8048451) | |`-> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format | | 0x08048468 e8affeffff call sym.imp.printf ; int printf(const char *format) | | ; CODE XREF from main (0x804845f) | `--> 0x0804846d b800000000 mov eax, 0 | 0x08048472 c9 leave \ 0x08048473 c3 ret
仍旧关注高亮部分。这里进行了一些数学运算,然后用结果与用户输入的密码进行比较,所以关键就是运算结果。
其实这个运算挺简单的,看代码就可以很快看出来:(90+492)*(90+492)=338724.
可以在调试模式下验证一下(这里的教程我还没看,完全是根据help自己找的方法,所以可能比较笨一点。)
在调试模式(r2 -Ad crackme0x02)下,把断点设置在0x0804844b(db 0x0804844b),然后执行(dc),这时查看寄存器的值(dr)
[0xf7f270b0]> db 0x0804844b
[0xf7f270b0]> dc
IOLI Crackme Level 0x02
Password: 1234
hit breakpoint at: 804844b
[0x0804844b]> dr
eax = 0x00052b24
ebx = 0x00000000
ecx = 0xff844340
edx = 0x000001ec
esi = 0xf7ef9000
edi = 0xf7ef9000
esp = 0xff8447a0
ebp = 0xff8447c8
eip = 0x0804844b
eflags = 0x00000206
oeax = 0xffffffff
[0x0804844b]> ? 0x00052b24
hex 0x52b24
octal 01225444
unit 330.8K
segment 5000:0b24
int32 338724
string "$+\x05"
binary 0b000001010010101100100100
fvalue: 338724.0
float: 0.000000f
double: 0.000000
trits 0t122012122100
确实是338724,再执行一下程序测试一下
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x02 IOLI Crackme Level 0x02 Password: 338724 Password OK :)
成功!
Crackme0x03 – writeup
进入radare2之后,iz的结果出现了变化
[0x08048360]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x000005ec 0x080485ec 17 18 (.rodata) ascii Lqydolg#Sdvvzrug$ 001 0x000005fe 0x080485fe 17 18 (.rodata) ascii Sdvvzrug#RN$$$#=, 002 0x00000610 0x08048610 24 25 (.rodata) ascii IOLI Crackme Level 0x03\n 003 0x00000629 0x08048629 10 11 (.rodata) ascii Password:
先看一下main函数吧
;-- main: / (fcn) sym.main 128 | sym.main (int argc, char **argv, char **envp); | ; var int local_ch @ ebp-0xc | ; var signed int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; var int local_4h_2 @ esp+0x4 | ; DATA XREF from entry0 (0x8048377) | 0x08048498 55 push ebp | 0x08048499 89e5 mov ebp, esp | 0x0804849b 83ec18 sub esp, 0x18 | 0x0804849e 83e4f0 and esp, 0xfffffff0 | 0x080484a1 b800000000 mov eax, 0 | 0x080484a6 83c00f add eax, 0xf | 0x080484a9 83c00f add eax, 0xf | 0x080484ac c1e804 shr eax, 4 | 0x080484af c1e004 shl eax, 4 | 0x080484b2 29c4 sub esp, eax | 0x080484b4 c70424108604. mov dword [esp], str.IOLI_Crackme_Level_0x03 ; [0x8048610:4]=0x494c4f49 ; "IOLI Crackme Level 0x03\n" ; const char *format | 0x080484bb e890feffff call sym.imp.printf ; int printf(const char *format) | 0x080484c0 c70424298604. mov dword [esp], str.Password: ; [0x8048629:4]=0x73736150 ; "Password: " ; const char *format | 0x080484c7 e884feffff call sym.imp.printf ; int printf(const char *format) | 0x080484cc 8d45fc lea eax, dword [local_4h] | 0x080484cf 89442404 mov dword [local_4h_2], eax | 0x080484d3 c70424348604. mov dword [esp], 0x8048634 ; [0x8048634:4]=0x6425 ; const char *format | 0x080484da e851feffff call sym.imp.scanf ; int scanf(const char *format) | 0x080484df c745f85a0000. mov dword [local_8h], 0x5a ; 'Z' ; 90 | 0x080484e6 c745f4ec0100. mov dword [local_ch], 0x1ec ; 492 | 0x080484ed 8b55f4 mov edx, dword [local_ch] | 0x080484f0 8d45f8 lea eax, dword [local_8h] | 0x080484f3 0110 add dword [eax], edx | 0x080484f5 8b45f8 mov eax, dword [local_8h] | 0x080484f8 0faf45f8 imul eax, dword [local_8h] | 0x080484fc 8945f4 mov dword [local_ch], eax | 0x080484ff 8b45f4 mov eax, dword [local_ch] | 0x08048502 89442404 mov dword [local_4h_2], eax | 0x08048506 8b45fc mov eax, dword [local_4h] | 0x08048509 890424 mov dword [esp], eax | 0x0804850c e85dffffff call sym.test | 0x08048511 b800000000 mov eax, 0 | 0x08048516 c9 leave \ 0x08048517 c3 ret
发现最后调用了sym.test函数,在调用test函数之前还进行了一些数学运算,和crackme0x02中的数学运算时一样的,结果(338724)与用户输入密码一起作为sym.test的参数,我们来看一下test函数:
[0x08048498]> s sym.test [0x0804846e]> pdf / (fcn) sym.test 42 | sym.test (int arg_8h, unsigned int arg_ch); | ; arg int arg_8h @ ebp+0x8 | ; arg unsigned int arg_ch @ ebp+0xc | ; CALL XREF from sym.main (0x804850c) | 0x0804846e 55 push ebp | 0x0804846f 89e5 mov ebp, esp | 0x08048471 83ec08 sub esp, 8 | 0x08048474 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | 0x08048477 3b450c cmp eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | ,=< 0x0804847a 740e je 0x804848a | | 0x0804847c c70424ec8504. mov dword [esp], str.Lqydolg_Sdvvzrug ; [0x80485ec:4]=0x6479714c ; "Lqydolg#Sdvvzrug$" | | 0x08048483 e88cffffff call sym.shift | ,==< 0x08048488 eb0c jmp 0x8048496 | || ; CODE XREF from sym.test (0x804847a) | |`-> 0x0804848a c70424fe8504. mov dword [esp], str.Sdvvzrug_RN ; [0x80485fe:4]=0x76766453 ; "Sdvvzrug#RN$$$#=," | | 0x08048491 e87effffff call sym.shift | | ; CODE XREF from sym.test (0x8048488) | `--> 0x08048496 c9 leave \ 0x08048497 c3 ret
嗯…338724与用户输入进行了比较,之后调用了sym.shift函数,根据比较结果不同,shift函数的参数也不同(所以shift函数只有一个参数,我有了一个猜测…),下面看一下shift函数:
[0x0804846e]> pdf @sym.shift / (fcn) sym.shift 90 | sym.shift (char *s); | ; var unsigned int local_7ch @ ebp-0x7c | ; var int local_78h @ ebp-0x78 | ; arg char *s @ ebp+0x8 | ; var int local_4h @ esp+0x4 | ; CALL XREFS from sym.test (0x8048483, 0x8048491) | 0x08048414 55 push ebp | 0x08048415 89e5 mov ebp, esp | 0x08048417 81ec98000000 sub esp, 0x98 | 0x0804841d c74584000000. mov dword [local_7ch], 0 | ; CODE XREF from sym.shift (0x804844e) | .-> 0x08048424 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | : 0x08048427 890424 mov dword [esp], eax ; const char *s | : 0x0804842a e811ffffff call sym.imp.strlen ; size_t strlen(const char *s) | : 0x0804842f 394584 cmp dword [local_7ch], eax | ,==< 0x08048432 731c jae 0x8048450 | |: 0x08048434 8d4588 lea eax, dword [local_78h] | |: 0x08048437 89c2 mov edx, eax | |: 0x08048439 035584 add edx, dword [local_7ch] | |: 0x0804843c 8b4584 mov eax, dword [local_7ch] | |: 0x0804843f 034508 add eax, dword [s] | |: 0x08048442 0fb600 movzx eax, byte [eax] | |: 0x08048445 2c03 sub al, 3 | |: 0x08048447 8802 mov byte [edx], al | |: 0x08048449 8d4584 lea eax, dword [local_7ch] | |: 0x0804844c ff00 inc dword [eax] | |`=< 0x0804844e ebd4 jmp 0x8048424 | | ; CODE XREF from sym.shift (0x8048432) | `--> 0x08048450 8d4588 lea eax, dword [local_78h] | 0x08048453 034584 add eax, dword [local_7ch] | 0x08048456 c60000 mov byte [eax], 0 | 0x08048459 8d4588 lea eax, dword [local_78h] | 0x0804845c 89442404 mov dword [local_4h], eax | 0x08048460 c70424e88504. mov dword [esp], 0x80485e8 ; [0x80485e8:4]=0xa7325 ; const char *format | 0x08048467 e8e4feffff call sym.imp.printf ; int printf(const char *format) | 0x0804846c c9 leave \ 0x0804846d c3 ret
确实,shift只有一个参数,主体进行了一些数学运算,然后调用了printf函数。
回忆一下程序的功能,输出提示信息 -> 用户输入密码 -> 程序判断密码正确性 -> 输出提示信息
而这里shift函数只是根据338724与用户输入的比较结果,输出不同的信息,所以,我们完全不需要知道shift这里到底做了什么,密码应该就是338724。而且应该也能猜到,shift这里就是把一开始我们通过iz得知的那两个像是乱码一样的字符串做了一些变换,生成最后的提示信息。
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x03 IOLI Crackme Level 0x03 Password: 338724 Password OK!!! :)
Bingo!
Crackme0x04 – writeup
这个程序的iz结果是正常的
[0x080483d0]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x0000063b 0x0804863b 13 14 (.rodata) ascii Password OK!\n 001 0x00000649 0x08048649 20 21 (.rodata) ascii Password Incorrect!\n 002 0x0000065e 0x0804865e 24 25 (.rodata) ascii IOLI Crackme Level 0x04\n 003 0x00000677 0x08048677 10 11 (.rodata) ascii Password:
main函数中,用户输入密码后,程序直接调用了check函数。
;-- main: / (fcn) sym.main 92 | sym.main (int argc, char **argv, char **envp); | ; var int local_78h @ ebp-0x78 | ; var int local_4h @ esp+0x4 | ; DATA XREF from entry0 (0x80483e7) | 0x08048509 55 push ebp | 0x0804850a 89e5 mov ebp, esp | 0x0804850c 81ec88000000 sub esp, 0x88 | 0x08048512 83e4f0 and esp, 0xfffffff0 | 0x08048515 b800000000 mov eax, 0 | 0x0804851a 83c00f add eax, 0xf | 0x0804851d 83c00f add eax, 0xf | 0x08048520 c1e804 shr eax, 4 | 0x08048523 c1e004 shl eax, 4 | 0x08048526 29c4 sub esp, eax | 0x08048528 c704245e8604. mov dword [esp], str.IOLI_Crackme_Level_0x04 ; [0x804865e:4]=0x494c4f49 ; "IOLI Crackme Level 0x04\n" ; const char *format | 0x0804852f e860feffff call sym.imp.printf ; int printf(const char *format) | 0x08048534 c70424778604. mov dword [esp], str.Password: ; [0x8048677:4]=0x73736150 ; "Password: " ; const char *format | 0x0804853b e854feffff call sym.imp.printf ; int printf(const char *format) | 0x08048540 8d4588 lea eax, dword [local_78h] | 0x08048543 89442404 mov dword [local_4h], eax | 0x08048547 c70424828604. mov dword [esp], 0x8048682 ; [0x8048682:4]=0x7325 ; const char *format | 0x0804854e e821feffff call sym.imp.scanf ; int scanf(const char *format) | 0x08048553 8d4588 lea eax, dword [local_78h] | 0x08048556 890424 mov dword [esp], eax | 0x08048559 e826ffffff call sym.check | 0x0804855e b800000000 mov eax, 0 | 0x08048563 c9 leave \ 0x08048564 c3 ret
下面看check函数干了些什么
[0x08048509]> pdf @sym.check / (fcn) sym.check 133 | sym.check (char *s); | ; var char *local_dh @ ebp-0xd | ; var unsigned int local_ch @ ebp-0xc | ; var unsigned int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg char *s @ ebp+0x8 | ; var char *format @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from sym.main (0x8048559) | 0x08048484 55 push ebp | 0x08048485 89e5 mov ebp, esp | 0x08048487 83ec28 sub esp, 0x28 ; '(' | 0x0804848a c745f8000000. mov dword [local_8h], 0 | 0x08048491 c745f4000000. mov dword [local_ch], 0 | ; CODE XREF from sym.check (0x80484f9) | .-> 0x08048498 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | : 0x0804849b 890424 mov dword [esp], eax ; const char *s | : 0x0804849e e8e1feffff call sym.imp.strlen ; size_t strlen(const char *s) | : 0x080484a3 3945f4 cmp dword [local_ch], eax ; local_ch: 当前检查第几个字符 | ,==< 0x080484a6 7353 jae 0x80484fb | |: 0x080484a8 8b45f4 mov eax, dword [local_ch] | |: 0x080484ab 034508 add eax, dword [s] | |: 0x080484ae 0fb600 movzx eax, byte [eax] | |: 0x080484b1 8845f3 mov byte [local_dh], al ; local_dh: 当前检查的字符 | |: 0x080484b4 8d45fc lea eax, dword [local_4h] | |: 0x080484b7 89442408 mov dword [local_8h_2], eax ; local_4h/local_8h_2: sscanf的第三个参数,保存格式化结果 | |: 0x080484bb c74424043886. mov dword [format], 0x8048638 ; format: sscanf的第二个参数,格式 | |: 0x080484c3 8d45f3 lea eax, dword [local_dh] | |: 0x080484c6 890424 mov dword [esp], eax ; const char *s | |: 0x080484c9 e8d6feffff call sym.imp.sscanf ; sscanf对当前检查字符进行了格式化,由字符转化为整型数字 | |: 0x080484ce 8b55fc mov edx, dword [local_4h] | |: 0x080484d1 8d45f8 lea eax, dword [local_8h] | |: 0x080484d4 0110 add dword [eax], edx | |: 0x080484d6 837df80f cmp dword [local_8h], 0xf ; local_8h: 对格式化结果求和,并与0xf比较,若相等,则通过 | ,===< 0x080484da 7518 jne 0x80484f4 | ||: 0x080484dc c704243b8604. mov dword [esp], str.Password_OK ; [0x804863b:4]=0x73736150 ; "Password OK!\n" ; const char *format | ||: 0x080484e3 e8acfeffff call sym.imp.printf ; int printf(const char *format) | ||: 0x080484e8 c70424000000. mov dword [esp], 0 ; int status | ||: 0x080484ef e8c0feffff call sym.imp.exit ; void exit(int status) | ||: ; CODE XREF from sym.check (0x80484da) | `---> 0x080484f4 8d45f4 lea eax, dword [local_ch] | |: 0x080484f7 ff00 inc dword [eax] | |`=< 0x080484f9 eb9d jmp 0x8048498 | | ; CODE XREF from sym.check (0x80484a6) | `--> 0x080484fb c70424498604. mov dword [esp], str.Password_Incorrect ; [0x8048649:4]=0x73736150 ; "Password Incorrect!\n" ; const char *format | 0x08048502 e88dfeffff call sym.imp.printf ; int printf(const char *format) | 0x08048507 c9 leave \ 0x08048508 c3 ret
我在代码上注解了各局部变量的意义,注意check函数中调用了sscanf函数,其中第二个参数是0x8048638,存储的是转换格式,我们看一下是什么
[0x08048509]> ps 15 @0x8048638 %d\x00Password OK!
所以sscanf就是把数字字符转换成了数字,以方便之后的求和运算。
总结一下,check函数逐个检查用户输入的字符串,首先将字符转换为数字,逐步求和直到和为15,则密码通过;如果直到检查到字符串结束都不能得到和为15的结果,则密码错误。
所以这个程序的正确密码应该有无限个,因为它在和为15之后并不会处理之后的字符,而是直接通过,让我们来试一下
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 1234 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 555 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 555555555555555 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 5541 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 55414365474 Password OK!
可以看到,前N个数字和为15的密码均可以通过测试。
Crackme0x05 - writeup
这个程序和cackme0x04很像,也是在main函数中调用check函数,我们直接看check函数
/ (fcn) sym.check 120 | sym.check (char *s); | ; var char *local_dh @ ebp-0xd | ; var unsigned int local_ch @ ebp-0xc | ; var unsigned int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg char *s @ ebp+0x8 | ; var char *format @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from sym.main (0x8048590) | 0x080484c8 55 push ebp | 0x080484c9 89e5 mov ebp, esp | 0x080484cb 83ec28 sub esp, 0x28 ; '(' | 0x080484ce c745f8000000. mov dword [local_8h], 0 | 0x080484d5 c745f4000000. mov dword [local_ch], 0 | ; CODE XREF from sym.check (0x8048530) | .-> 0x080484dc 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | : 0x080484df 890424 mov dword [esp], eax ; const char *s | : 0x080484e2 e89dfeffff call sym.imp.strlen ; size_t strlen(const char *s) | : 0x080484e7 3945f4 cmp dword [local_ch], eax | ,==< 0x080484ea 7346 jae 0x8048532 | |: 0x080484ec 8b45f4 mov eax, dword [local_ch] | |: 0x080484ef 034508 add eax, dword [s] | |: 0x080484f2 0fb600 movzx eax, byte [eax] | |: 0x080484f5 8845f3 mov byte [local_dh], al | |: 0x080484f8 8d45fc lea eax, dword [local_4h] | |: 0x080484fb 89442408 mov dword [local_8h_2], eax ; ... | |: 0x080484ff c74424046886. mov dword [format], 0x8048668 ; [0x8048668:4]=0x50006425 ; const char *format | |: 0x08048507 8d45f3 lea eax, dword [local_dh] | |: 0x0804850a 890424 mov dword [esp], eax ; const char *s | |: 0x0804850d e892feffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | |: 0x08048512 8b55fc mov edx, dword [local_4h] | |: 0x08048515 8d45f8 lea eax, dword [local_8h] | |: 0x08048518 0110 add dword [eax], edx | |: 0x0804851a 837df810 cmp dword [local_8h], 0x10 | ,===< 0x0804851e 750b jne 0x804852b | ||: 0x08048520 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | ||: 0x08048523 890424 mov dword [esp], eax | ||: 0x08048526 e859ffffff call sym.parell | ||: ; CODE XREF from sym.check (0x804851e) | `---> 0x0804852b 8d45f4 lea eax, dword [local_ch] | |: 0x0804852e ff00 inc dword [eax] | |`=< 0x08048530 ebaa jmp 0x80484dc | | ; CODE XREF from sym.check (0x80484ea) | `--> 0x08048532 c70424798604. mov dword [esp], str.Password_Incorrect ; [0x8048679:4]=0x73736150 ; "Password Incorrect!\n" ; const char *format | 0x08048539 e856feffff call sym.imp.printf ; int printf(const char *format) | 0x0804853e c9 leave \ 0x0804853f c3 ret
可以看到check函数仍旧是调用了sscanf函数,然后求和,只不过这次是和0x10比较,如果相等,函数再一次调用了parell函数,参数为用户输入的密码,看一下parell函数:
[0x08048540]> pdf @sym.parell / (fcn) sym.parell 68 | sym.parell (char *s); | ; var int local_4h @ ebp-0x4 | ; arg char *s @ ebp+0x8 | ; var char *format @ esp+0x4 | ; var int local_8h @ esp+0x8 | ; CALL XREF from sym.check (0x8048526) | 0x08048484 55 push ebp | 0x08048485 89e5 mov ebp, esp | 0x08048487 83ec18 sub esp, 0x18 | 0x0804848a 8d45fc lea eax, dword [local_4h] | 0x0804848d 89442408 mov dword [local_8h], eax ; ... | 0x08048491 c74424046886. mov dword [format], 0x8048668 ; [0x8048668:4]=0x50006425 ; const char *format | 0x08048499 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | 0x0804849c 890424 mov dword [esp], eax ; const char *s | 0x0804849f e800ffffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | 0x080484a4 8b45fc mov eax, dword [local_4h] | 0x080484a7 83e001 and eax, 1 | 0x080484aa 85c0 test eax, eax | ,=< 0x080484ac 7518 jne 0x80484c6 | | 0x080484ae c704246b8604. mov dword [esp], str.Password_OK ; [0x804866b:4]=0x73736150 ; "Password OK!\n" ; const char *format | | 0x080484b5 e8dafeffff call sym.imp.printf ; int printf(const char *format) | | 0x080484ba c70424000000. mov dword [esp], 0 ; int status | | 0x080484c1 e8eefeffff call sym.imp.exit ; void exit(int status) | | ; CODE XREF from sym.parell (0x80484ac) | `-> 0x080484c6 c9 leave \ 0x080484c7 c3 ret
parell函数里,对用户输入的密码进行了格式转换,转换为了整型数字。注意高亮部分,通过这里的运算,判断用户输入密码是否为偶数,若为偶数,则密码通过。
总结一下密码的要求:
- 偶数
- 前N个数字和为16
- 最大不超过int的范围(-2147483648~2147483647)
测试一下:
root@kali:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 4156 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 41561 Password Incorrect! 奇数 root@kali:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 41562 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 2147483646 Password Incorrect! 前N位和不是15 root@kali:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 2147283646 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 2237283646 Password Incorrect! 超过int范围
Crackme0x06 – writeup
这个程序里面的函数调用流程和crackme0x05差不多,但是还是有一些不同,看一下main函数和check函数
;-- main: / (fcn) sym.main 99 | sym.main (int argc, char **argv, char **envp); | ; var int local_78h @ ebp-0x78 | ; arg int arg_10h @ ebp+0x10 | ; var int local_4h @ esp+0x4 | ; DATA XREF from entry0 (0x8048417) | 0x08048607 55 push ebp | 0x08048608 89e5 mov ebp, esp | 0x0804860a 81ec88000000 sub esp, 0x88 | 0x08048610 83e4f0 and esp, 0xfffffff0 | 0x08048613 b800000000 mov eax, 0 | 0x08048618 83c00f add eax, 0xf | 0x0804861b 83c00f add eax, 0xf | 0x0804861e c1e804 shr eax, 4 | 0x08048621 c1e004 shl eax, 4 | 0x08048624 29c4 sub esp, eax | 0x08048626 c70424638704. mov dword [esp], str.IOLI_Crackme_Level_0x06 ; [0x8048763:4]=0x494c4f49 ; "IOLI Crackme Level 0x06\n" ; const char *format | 0x0804862d e886fdffff call sym.imp.printf ; int printf(const char *format) | 0x08048632 c704247c8704. mov dword [esp], str.Password: ; [0x804877c:4]=0x73736150 ; "Password: " ; const char *format | 0x08048639 e87afdffff call sym.imp.printf ; int printf(const char *format) | 0x0804863e 8d4588 lea eax, dword [local_78h] | 0x08048641 89442404 mov dword [local_4h], eax | 0x08048645 c70424878704. mov dword [esp], 0x8048787 ; [0x8048787:4]=0x7325 ; const char *format | 0x0804864c e847fdffff call sym.imp.scanf ; int scanf(const char *format) | 0x08048651 8b4510 mov eax, dword [arg_10h] ; [0x10:4]=-1 ; 16 | 0x08048654 89442404 mov dword [local_4h], eax | 0x08048658 8d4588 lea eax, dword [local_78h] | 0x0804865b 890424 mov dword [esp], eax | 0x0804865e e825ffffff call sym.check | 0x08048663 b800000000 mov eax, 0 | 0x08048668 c9 leave \ 0x08048669 c3 ret
[0x08048400]> pdf @sym.check / (fcn) sym.check 127 | sym.check (char *s, int arg_ch); | ; var char *local_dh @ ebp-0xd | ; var unsigned int local_ch @ ebp-0xc | ; var unsigned int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg char *s @ ebp+0x8 | ; arg int arg_ch @ ebp+0xc | ; var char *format @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from sym.main (0x804865e) | 0x08048588 55 push ebp | 0x08048589 89e5 mov ebp, esp | 0x0804858b 83ec28 sub esp, 0x28 ; '(' | 0x0804858e c745f8000000. mov dword [local_8h], 0 | 0x08048595 c745f4000000. mov dword [local_ch], 0 | ; CODE XREF from sym.check (0x80485f7) | .-> 0x0804859c 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | : 0x0804859f 890424 mov dword [esp], eax ; const char *s | : 0x080485a2 e801feffff call sym.imp.strlen ; size_t strlen(const char *s) | : 0x080485a7 3945f4 cmp dword [local_ch], eax | ,==< 0x080485aa 734d jae 0x80485f9 | |: 0x080485ac 8b45f4 mov eax, dword [local_ch] | |: 0x080485af 034508 add eax, dword [s] | |: 0x080485b2 0fb600 movzx eax, byte [eax] | |: 0x080485b5 8845f3 mov byte [local_dh], al | |: 0x080485b8 8d45fc lea eax, dword [local_4h] | |: 0x080485bb 89442408 mov dword [local_8h_2], eax ; ... | |: 0x080485bf c74424043d87. mov dword [format], 0x804873d ; [0x804873d:4]=0x50006425 ; const char *format | |: 0x080485c7 8d45f3 lea eax, dword [local_dh] | |: 0x080485ca 890424 mov dword [esp], eax ; const char *s | |: 0x080485cd e8f6fdffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | |: 0x080485d2 8b55fc mov edx, dword [local_4h] | |: 0x080485d5 8d45f8 lea eax, dword [local_8h] | |: 0x080485d8 0110 add dword [eax], edx | |: 0x080485da 837df810 cmp dword [local_8h], 0x10 | ,===< 0x080485de 7512 jne 0x80485f2 | ||: 0x080485e0 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | ||: 0x080485e3 89442404 mov dword [format], eax | ||: 0x080485e7 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | ||: 0x080485ea 890424 mov dword [esp], eax | ||: 0x080485ed e828ffffff call sym.parell | ||: ; CODE XREF from sym.check (0x80485de) | `---> 0x080485f2 8d45f4 lea eax, dword [local_ch] | |: 0x080485f5 ff00 inc dword [eax] | |`=< 0x080485f7 eba3 jmp 0x804859c | | ; CODE XREF from sym.check (0x80485aa) | `--> 0x080485f9 c704244e8704. mov dword [esp], str.Password_Incorrect ; [0x804874e:4]=0x73736150 ; "Password Incorrect!\n" ; const char *format | 0x08048600 e8b3fdffff call sym.imp.printf ; int printf(const char *format) | 0x08048605 c9 leave \ 0x08048606 c3 ret
注意高亮部分,check函数有两个参数,第一个参数仍旧是用户输入的密码,第二个字符串是main函数的参数ebp+0x10,那么这是哪个参数呢?
注意main(int argc, char **argv, char **envp)的栈结构:
所以check函数多了一个参数——程序执行的环境变量,除此之外,main -> check -> parell的函数调用流程,以及函数内部的操作没有变化,下面看parell函数
[0x08048400]> pdf @sym.parell / (fcn) sym.parell 110 | sym.parell (char *s, char *arg_ch); | ; var signed int local_8h_2 @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg char *s @ ebp+0x8 | ; arg char *arg_ch @ ebp+0xc | ; var char *format @ esp+0x4 | ; var int local_8h @ esp+0x8 | ; CALL XREF from sym.check (0x80485ed) | 0x0804851a 55 push ebp | 0x0804851b 89e5 mov ebp, esp | 0x0804851d 83ec18 sub esp, 0x18 | 0x08048520 8d45fc lea eax, dword [local_4h] | 0x08048523 89442408 mov dword [local_8h], eax ; ... | 0x08048527 c74424043d87. mov dword [format], 0x804873d ; [0x804873d:4]=0x50006425 ; const char *format | 0x0804852f 8b4508 mov eax, dword [s] ; [0x8:4]=-1 ; 8 | 0x08048532 890424 mov dword [esp], eax ; const char *s | 0x08048535 e88efeffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | 0x0804853a 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | 0x0804853d 89442404 mov dword [format], eax | 0x08048541 8b45fc mov eax, dword [local_4h] | 0x08048544 890424 mov dword [esp], eax | 0x08048547 e868ffffff call sym.dummy | 0x0804854c 85c0 test eax, eax | ,=< 0x0804854e 7436 je 0x8048586 | | 0x08048550 c745f8000000. mov dword [local_8h_2], 0 | | ; CODE XREF from sym.parell (0x8048584) | .--> 0x08048557 837df809 cmp dword [local_8h_2], 9 | ,===< 0x0804855b 7f29 jg 0x8048586 | |:| 0x0804855d 8b45fc mov eax, dword [local_4h] | |:| 0x08048560 83e001 and eax, 1 | |:| 0x08048563 85c0 test eax, eax ; 检查用户输入密码是否为偶数 | ,====< 0x08048565 7518 jne 0x804857f | ||:| 0x08048567 c70424408704. mov dword [esp], str.Password_OK ; [0x8048740:4]=0x73736150 ; "Password OK!\n" ; const char *format | ||:| 0x0804856e e845feffff call sym.imp.printf ; int printf(const char *format) | ||:| 0x08048573 c70424000000. mov dword [esp], 0 ; int status | ||:| 0x0804857a e869feffff call sym.imp.exit ; void exit(int status) | ||:| ; CODE XREF from sym.parell (0x8048565) | `----> 0x0804857f 8d45f8 lea eax, dword [local_8h_2] | |:| 0x08048582 ff00 inc dword [eax] | |`==< 0x08048584 ebd1 jmp 0x8048557 | | | ; CODE XREFS from sym.parell (0x804854e, 0x804855b) | `-`-> 0x08048586 c9 leave \ 0x08048587 c3 ret
parell函数在检查用户输入是否为偶像之前,调用了dummy函数,从高亮部分看,栈中压入了两个参数,一个是转换为整型数字的用户输入,一个是环境变量。注意检查用户输入是否为偶数的代码外面套了一个循环,我没发现这个循环有什么用处。下面看dummy函数干了什么
[0x08048400]> pdf @sym.dummy / (fcn) sym.dummy 102 | sym.dummy (char **s1); | ; var int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg char **s1 @ ebp+0xc | ; var char *s2 @ esp+0x4 | ; var size_t *n @ esp+0x8 | ; CALL XREF from sym.parell (0x8048547) | 0x080484b4 55 push ebp | 0x080484b5 89e5 mov ebp, esp | 0x080484b7 83ec18 sub esp, 0x18 | 0x080484ba c745fc000000. mov dword [local_4h], 0 | .-> 0x080484c1 8b45fc mov eax, dword [local_4h] | : 0x080484c4 8d1485000000. lea edx, dword [eax*4] | : 0x080484cb 8b450c mov eax, dword [s1] ; [0xc:4]=-1 ; 12 | : 0x080484ce 833c0200 cmp dword [edx + eax], 0 ; 检查是否到字符串结尾 | ,==< 0x080484d2 743a je 0x804850e | |: 0x080484d4 8b45fc mov eax, dword [local_4h] | |: 0x080484d7 8d0c85000000. lea ecx, dword [eax*4] | |: 0x080484de 8b550c mov edx, dword [s1] ; [0xc:4]=-1 ; 12 | |: 0x080484e1 8d45fc lea eax, dword [local_4h] | |: 0x080484e4 ff00 inc dword [eax] | |: 0x080484e6 c74424080300. mov dword [n], 3 ; size_t n | |: 0x080484ee c74424043887. mov dword [s2], str.LOLO ; [0x8048738:4]=0x4f4c4f4c ; "LOLO" ; const char *s2 | |: 0x080484f6 8b0411 mov eax, dword [ecx + edx] | |: 0x080484f9 890424 mov dword [esp], eax ; const char *s1 | |: 0x080484fc e8d7feffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n) | |: 0x08048501 85c0 test eax, eax ; 检查前三个字符是否为LOL | |`=< 0x08048503 75bc jne 0x80484c1 | | 0x08048505 c745f8010000. mov dword [local_8h], 1 | |,=< 0x0804850c eb07 jmp 0x8048515 | || ; CODE XREF from sym.dummy (0x80484d2) | `--> 0x0804850e c745f8000000. mov dword [local_8h], 0 | | ; CODE XREF from sym.dummy (0x804850c) | `-> 0x08048515 8b45f8 mov eax, dword [local_8h] | 0x08048518 c9 leave \ 0x08048519 c3 ret
注意高亮部分,dummy函数使用了第二个参数,即环境变量,代码中检查了每个环境变量,看是否存在某个环境变量的开始三个字符为“LOL”。
所以想要通过该程序测试,
- 密码满足crackme0x05的要求
- 存在一个环境变量,前三个字符为"LOL"
测试一下:
root@kali:~/IOLIcrackme/bin-linux# export LOLTTT=test root@kali:~/IOLIcrackme/bin-linux# ./crackme0x06 IOLI Crackme Level 0x06 Password: 4156 Password OK!
Crackme0x07 – writeup
这个程序我是在visual mode下查看的,因为的函数调用比较多,也有一些修改。
查看main函数的代码,发现有一个sub.strlen_5b9函数,查看该函数,和crackme0x06中的check函数结构很像,但是代码更长一些,为了和crackme0x06保持一致,首先将该函数重命名(dr)为check,然后在原本parell函数的位置,有一个函数call 0x8048542
[0x080485e3 160 /root/IOLIcrackme/bin-linux/crackme0x07]> diq;?0;f t.. @ entry0+483 # 0x80485e3 none at 0x00000000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0xff89b020 0100 0000 68b5 89ff 0000 0000 76b5 89ff ....h.......v... 0xff89b030 58bb 89ff 6fbb 89ff 80bb 89ff 95bb 89ff X...o........... 0xff89b040 a0bb 89ff b4bb 89ff c2bb 89ff cdbb 89ff ................ 0xff89b050 f3bb 89ff 04bc 89ff 0ebc 89ff 24bc 89ff ............$... eax 0x00000000 ebx 0x00000000 ecx 0x00000000 edx 0x00000000 esi 0x00000000 edi 0x00000000 esp 0xff89b020 ebp 0x00000000 eip 0xf7eed0b0 eflags I oeax 0x0000000b | : 0x080485e3 0fb600 movzx eax, byte [eax] | : 0x080485e6 8845f3 mov byte [local_dh], al | : 0x080485e9 8d45fc lea eax, dword [local_4h_2] | : 0x080485ec 89442408 mov dword [local_8h_2], eax | : 0x080485f0 c7442404c287. mov dword [local_4h], 0x80487c2 ; [0x80487c2:4]=0x50006425 | : 0x080485f8 8d45f3 lea eax, dword [local_dh] | : 0x080485fb 890424 mov dword [esp], eax | : 0x080485fe e8c5fdffff call sym.imp.sscanf ;[1] ; int sscanf(const char *s, const char *format, ...) | : 0x08048603 8b55fc mov edx, dword [local_4h_2] | : 0x08048606 8d45f8 lea eax, dword [local_8h] | : 0x08048609 0110 add dword [eax], edx | : 0x0804860b 837df810 cmp dword [local_8h], 0x10 | ,==< 0x0804860f 7512 jne 0x8048623 ;[2] | |: 0x08048611 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | |: 0x08048614 89442404 mov dword [local_4h], eax | |: 0x08048618 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | |: 0x0804861b 890424 mov dword [esp], eax | |: 0x0804861e e81fffffff call 0x8048542 ;[3] | `--> 0x08048623 8d45f4 lea eax, dword [local_ch] | : 0x08048626 ff00 inc dword [eax] | `=< 0x08048628 eba3 jmp 0x80485cd ;[4] | 0x0804862a e8f5feffff call 0x8048524 ;[5]
点击数字键3,进入该函数
[0x08048543 160 /root/IOLIcrackme/bin-linux/crackme0x07]> diq;?0;f t.. @ entry0+323 # 0x8048543 none at 0x00000000 - offset - 0 1 2 3 4 5 6 7 8 9 A B 0123456789AB 0xff89b020 0100 0000 68b5 89ff 0000 0000 ....h....... 0xff89b02c 76b5 89ff 58bb 89ff 6fbb 89ff v...X...o... 0xff89b038 80bb 89ff 95bb 89ff a0bb 89ff ............ 0xff89b044 b4bb 89ff c2bb 89ff cdbb 89ff ............ 0xff89b050 f3bb 89ff 04bc 89ff 0ebc 89ff ............ 0xff89b05c 24bc 89ff $... eax 0x00000000 ebx 0x00000000 ecx 0x00000000 edx 0x00000000 esi 0x00000000 edi 0x00000000 esp 0xff89b020 ebp 0x00000000 eip 0xf7eed0b0 eflags I oeax 0x0000000b | 0x08048543 mov ebp, esp | 0x08048545 sub esp, 0x18 | 0x08048548 lea eax, dword [local_4h] | 0x0804854b mov dword [local_8h], eax | 0x0804854f mov dword [local_4h_2], 0x80487c2 ; [0x80487c2:4]=0x50006425 | 0x08048557 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | 0x0804855a mov dword [esp], eax | 0x0804855d call sym.imp.sscanf ;[1] ; int sscanf(const char *s, const char *format, ...) | 0x08048562 mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | 0x08048565 mov dword [local_4h_2], eax | 0x08048569 mov eax, dword [local_4h] | 0x0804856c mov dword [esp], eax | 0x0804856f call fcn.080484b4 ;[2] | 0x08048574 test eax, eax | ,=< 0x08048576 je 0x80485b7 ;[3] | | 0x08048578 mov dword [local_8h_2], 0 | | ; CODE XREF from parell (0x80485b5) | .--> 0x0804857f cmp dword [local_8h_2], 9 | ,===< 0x08048583 jg 0x80485b7 ;[3]
发现该函数和parell函数的结构类似,fcn.080484b4应该是函数dummy,所以把0x8048542定义为一个函数,并重命名为parell,fcn.080484b4重命名为dummy,进入dummy函数,可以验证其确实是crackme0x06中的dummy函数。
现在重新回到parell函数,函数验证正确的消息就是从这里输出的,看一下调用dummy函数后,函数进行了哪些运算
[0x0804867d]> pdf @parell / (fcn) parell 119 | parell (int arg_8h, int arg_ch); | ; var int local_8h_2 @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg int arg_8h @ ebp+0x8 | ; arg int arg_ch @ ebp+0xc | ; var int local_4h_2 @ esp+0x4 | ; var int local_8h @ esp+0x8 | ; CALL XREF from check (0x804861e) | 0x08048542 b push ebp | 0x08048543 mov ebp, esp | 0x08048545 sub esp, 0x18 | 0x08048548 lea eax, dword [local_4h] | 0x0804854b mov dword [local_8h], eax | 0x0804854f mov dword [local_4h_2], 0x80487c2 ; [0x80487c2:4]=0x50006425 | 0x08048557 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | 0x0804855a mov dword [esp], eax | 0x0804855d call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | 0x08048562 mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | 0x08048565 mov dword [local_4h_2], eax | 0x08048569 mov eax, dword [local_4h] | 0x0804856c mov dword [esp], eax | 0x0804856f call dummy | 0x08048574 test eax, eax ; 检查dummy返回值,为1时继续判断 | ,=< 0x08048576 je 0x80485b7 | | 0x08048578 mov dword [local_8h_2], 0 | | ; CODE XREF from parell (0x80485b5) | .--> 0x0804857f cmp dword [local_8h_2], 9 | ,===< 0x08048583 jg 0x80485b7 | |:| 0x08048585 mov eax, dword [local_4h] | |:| 0x08048588 and eax, 1 | |:| 0x0804858b test eax, eax | ,====< 0x0804858d jne 0x80485b0 | ||:| 0x0804858f cmp dword [0x804a02c], 1 ; [0x804a02c:4]=0 | ,=====< 0x08048596 jne 0x80485a4 | |||:| 0x08048598 mov dword [esp], str.Password_OK ; [0x80487c5:4]=0x73736150 ; "Password OK!\n" | |||:| 0x0804859f call sym.imp.printf ; int printf(const char *format) | `-----> 0x080485a4 mov dword [esp], 0 | ||:| 0x080485ab call sym.imp.exit ; void exit(int status) | `----> 0x080485b0 lea eax, dword [local_8h_2] | |:| 0x080485b3 inc dword [eax] | |`==< 0x080485b5 jmp 0x804857f | `-`-> 0x080485b7 leave \ 0x080485b8 ret
注意到这里多了一个判断(高亮部分),看一下哪里引用了0x804a02c这个地址
[0x0804867d]> axt 0x804a02c dummy 0x8048505 [DATA] mov dword [0x804a02c], 1 parell 0x804858f [DATA] cmp dword [0x804a02c], 1
在dummy函数中
[0x0804867d]> pdf @dummy / (fcn) dummy 112 | dummy (int arg_ch); | ; var int local_8h @ ebp-0x8 | ; var int local_4h @ ebp-0x4 | ; arg int arg_ch @ ebp+0xc | ; var int local_4h_2 @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from parell (0x804856f) | ; CALL XREF from check (0x804863c) | 0x080484b4 b push ebp | 0x080484b5 mov ebp, esp | 0x080484b7 sub esp, 0x18 | 0x080484ba mov dword [local_4h], 0 | .-> 0x080484c1 mov eax, dword [local_4h] | : 0x080484c4 lea edx, dword [eax*4] | : 0x080484cb mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | : 0x080484ce cmp dword [edx + eax], 0 | ,==< 0x080484d2 je 0x8048518 | |: 0x080484d4 mov eax, dword [local_4h] | |: 0x080484d7 lea ecx, dword [eax*4] | |: 0x080484de mov edx, dword [arg_ch] ; [0xc:4]=-1 ; 12 | |: 0x080484e1 lea eax, dword [local_4h] | |: 0x080484e4 inc dword [eax] | |: 0x080484e6 mov dword [local_8h_2], 3 | |: 0x080484ee mov dword [local_4h_2], str.LOLO ; [0x80487a8:4]=0x4f4c4f4c ; "LOLO" | |: 0x080484f6 mov eax, dword [ecx + edx] | |: 0x080484f9 mov dword [esp], eax | |: 0x080484fc call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n) | |: 0x08048501 test eax, eax | |`=< 0x08048503 jne 0x80484c1 | | 0x08048505 mov dword [0x804a02c], 1 ; [0x804a02c:4]=0 | | 0x0804850f mov dword [local_8h], 1 | |,=< 0x08048516 jmp 0x804851f | `--> 0x08048518 mov dword [local_8h], 0 | | ; CODE XREF from dummy (0x8048516) | `-> 0x0804851f mov eax, dword [local_8h] | 0x08048522 leave \ 0x08048523 ret
注意高亮部分,可以得知,只要dummy函数中strncmp函数返回0,0x804a02c就会被赋值为1,所以prarell函数中的这个比较在dummy函数通过后是必然成立的。
在main -> check -> parell -> dummy这个函数调用流程中,我们已经检查了parell和dummy函数和crackme0x06中的区别,发现对最终密码的检查并没有区别,下面看check函数:
[0x0804867d]> pdf @check / (fcn) check 196 | check (int arg_8h, int arg_ch); | ; var int local_dh @ ebp-0xd | ; var int local_ch @ ebp-0xc | ; var int local_8h @ ebp-0x8 | ; var int local_4h_2 @ ebp-0x4 | ; arg int arg_8h @ ebp+0x8 | ; arg int arg_ch @ ebp+0xc | ; var int local_4h @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from main (0x80486d4) | 0x080485b9 push ebp | 0x080485ba mov ebp, esp | 0x080485bc sub esp, 0x28 ; '(' | 0x080485bf mov dword [local_8h], 0 | 0x080485c6 mov dword [local_ch], 0 | ; CODE XREF from check (0x8048628) | .-> 0x080485cd mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | : 0x080485d0 mov dword [esp], eax | : 0x080485d3 call sym.imp.strlen ; size_t strlen(const char *s) | : 0x080485d8 cmp dword [local_ch], eax | ,==< 0x080485db jae 0x804862a | |: 0x080485dd mov eax, dword [local_ch] | |: 0x080485e0 add eax, dword [arg_8h] | |: 0x080485e3 movzx eax, byte [eax] | |: 0x080485e6 mov byte [local_dh], al | |: 0x080485e9 lea eax, dword [local_4h_2] | |: 0x080485ec mov dword [local_8h_2], eax | |: 0x080485f0 mov dword [local_4h], 0x80487c2 ; [0x80487c2:4]=0x50006425 | |: 0x080485f8 lea eax, dword [local_dh] | |: 0x080485fb mov dword [esp], eax | |: 0x080485fe call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | |: 0x08048603 mov edx, dword [local_4h_2] | |: 0x08048606 lea eax, dword [local_8h] | |: 0x08048609 add dword [eax], edx | |: 0x0804860b cmp dword [local_8h], 0x10 | ,===< 0x0804860f jne 0x8048623 | ||: 0x08048611 mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | ||: 0x08048614 mov dword [local_4h], eax | ||: 0x08048618 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | ||: 0x0804861b mov dword [esp], eax | ||: 0x0804861e call parell | `---> 0x08048623 lea eax, dword [local_ch] | |: 0x08048626 inc dword [eax] | |`=< 0x08048628 jmp 0x80485cd | `--> 0x0804862a call 0x8048524 | 0x0804862f mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | 0x08048632 mov dword [local_4h], eax | 0x08048636 mov eax, dword [local_4h_2] | 0x08048639 mov dword [esp], eax | 0x0804863c call dummy | 0x08048641 test eax, eax | ,=< 0x08048643 je 0x804867b | | 0x08048645 mov dword [local_ch], 0 | | ; CODE XREF from check (0x8048679) | .--> 0x0804864c cmp dword [local_ch], 9 | ,===< 0x08048650 jg 0x804867b | |:| 0x08048652 mov eax, dword [local_4h_2] | |:| 0x08048655 and eax, 1 | |:| 0x08048658 test eax, eax | ,====< 0x0804865a jne 0x8048674 | ||:| 0x0804865c mov dword [esp], str.wtf ; [0x80487d3:4]=0x3f667477 ; "wtf?\n" | ||:| 0x08048663 call sym.imp.printf ; int printf(const char *format) | ||:| 0x08048668 mov dword [esp], 0 | ||:| 0x0804866f call sym.imp.exit ; void exit(int status) | `----> 0x08048674 lea eax, dword [local_ch] | |:| 0x08048677 inc dword [eax] | |`==< 0x08048679 jmp 0x804864c | `-`-> 0x0804867b leave \ 0x0804867c ret
check函数中,调用了parell函数之后,又多了一大段代码,调用parell函数下面的call 0x8048524是字符数字累加循环的判断条件为false时的跳转目标。
0x08048524 push ebp 0x08048525 mov ebp, esp 0x08048527 sub esp, 8 0x0804852a mov dword [esp], str.Password_Incorrect ; [0x80487ad:4]=0x73736150 ; "Password Incorrect!\n" 0x08048531 call sym.imp.printf ;[4] ; int printf(const char *format) 0x08048536 mov dword [esp], 0 0x0804853d call sym.imp.exit ;[5] ; void exit(int status)
可以看到该函数会直接退出程序。而除此之外,check函数中没有其他代码可以跳转到0x804862f下面的代码,即下方代码并不会执行。
综上,该程序和crackme0x06的密码验证条件应该是相同的。
测试一下:
root@kali:~/IOLIcrackme/bin-linux# export LOLTTT=test root@kali:~/IOLIcrackme/bin-linux# ./crackme0x07 IOLI Crackme Level 0x07 Password: 4156 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x07 IOLI Crackme Level 0x07 Password: 41561 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x07 IOLI Crackme Level 0x07 Password: 41562 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x07 IOLI Crackme Level 0x07 Password: 2147483646 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x07 IOLI Crackme Level 0x07 Password: 2147283646 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x07 IOLI Crackme Level 0x07 Password: 2237283646 Password Incorrect!
Crackme0x08 – writeup
进入radare2,先在visual mode下走一遍这个程序的流程:
Main -> check -> parell -> dummy
发现主要的流程仍然不变,粗看下来计算过程也差不多,用radiff2比较一下两个程序
fcn.08048360 23 0x8048360 | MATCH (1.000000) | 0x8048360 23 sym._init sym.imp.__libc_start_main 6 0x8048388 | MATCH (1.000000) | 0x8048388 6 sym.imp.__libc_start_main sym.imp.scanf 6 0x8048398 | MATCH (1.000000) | 0x8048398 6 sym.imp.scanf sym.imp.strlen 6 0x80483a8 | MATCH (1.000000) | 0x80483a8 6 sym.imp.strlen sym.imp.printf 6 0x80483b8 | MATCH (1.000000) | 0x80483b8 6 sym.imp.printf sym.imp.sscanf 6 0x80483c8 | MATCH (1.000000) | 0x80483c8 6 sym.imp.sscanf sym.imp.strncmp 6 0x80483d8 | MATCH (1.000000) | 0x80483d8 6 sym.imp.strncmp sym.imp.exit 6 0x80483e8 | MATCH (1.000000) | 0x80483e8 6 sym.imp.exit entry0 33 0x8048400 | MATCH (1.000000) | 0x8048400 33 entry0 fcn.08048424 33 0x8048424 | MATCH (1.000000) | 0x8048424 33 fcn.08048424 fcn.08048450 47 0x8048450 | MATCH (1.000000) | 0x8048450 47 sym.__do_global_dtors_aux fcn.08048480 50 0x8048480 | MATCH (1.000000) | 0x8048480 50 sym.frame_dummy sub.LOLO_4b4 112 0x80484b4 | MATCH (1.000000) | 0x80484b4 112 sym.dummy sub.Password_Incorrect_524 30 0x8048524 | MATCH (1.000000) | 0x8048524 30 sym.che sub.sscanf_542 119 0x8048542 | MATCH (1.000000) | 0x8048542 119 sym.parell sub.strlen_5b9 196 0x80485b9 | MATCH (1.000000) | 0x80485b9 196 sym.check main 99 0x804867d | MATCH (1.000000) | 0x804867d 99 sym.main fcn.08048755 4 0x8048755 | MATCH (1.000000) | 0x8048755 4 sym.__i686.get_pc_thunk.bx fcn.08048760 35 0x8048760 | MATCH (1.000000) | 0x8048760 35 sym.__do_global_ctors_aux fcn.0804878d 17 0x804878d | NEW (0.000000) sym.__libc_csu_init 99 0x80486e0 | NEW (0.000000) sym.__libc_csu_fini 5 0x8048750 | NEW (0.000000) sym._fini 26 0x8048784 | NEW (0.000000)
发现流程中的主要函数都相同,我们可以猜测此程序和crackme0x07程序对于密码的检测要求是先相同的,测试一下:
root@kali:~/IOLIcrackme/bin-linux# export LOLTTT=test root@kali:~/IOLIcrackme/bin-linux# ./crackme0x08 IOLI Crackme Level 0x08 Password: 4156 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x08 IOLI Crackme Level 0x08 Password: 41561 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x08 IOLI Crackme Level 0x08 Password: 41562 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x08 IOLI Crackme Level 0x08 Password: 2147483646 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x08 IOLI Crackme Level 0x08 Password: 2147283646 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x08 IOLI Crackme Level 0x08 Password: 2237283646 Password Incorrect!
Crackme0x09 – writeup
这个程序比前面几个看起来复杂一些,如果之前没有看到过前9个程序,直接看这个程序,可能要花费更大的力气才能看懂,但是由于之前已经看了9个类似的程序了,只靠猜测也能猜出crackme0x09这个程序中每个函数的意思。
看一下main函数
[0xf7fb50b0]> pdf @main / (fcn) main 120 | main (int argc, char **argv, char **envp); | ; var int local_78h @ ebp-0x78 | ; var int local_4h_2 @ ebp-0x4 | ; arg int arg_10h @ ebp+0x10 | ; var int local_4h @ esp+0x4 | ; DATA XREF from entry0 (0x8048437) | 0x080486ee 55 push ebp | 0x080486ef 89e5 mov ebp, esp | 0x080486f1 53 push ebx | 0x080486f2 81ec84000000 sub esp, 0x84 | 0x080486f8 e869000000 call fcn.08048766 | 0x080486fd 81c3f7180000 add ebx, 0x18f7 | 0x08048703 83e4f0 and esp, 0xfffffff0 | 0x08048706 b800000000 mov eax, 0 | 0x0804870b 83c00f add eax, 0xf | 0x0804870e 83c00f add eax, 0xf | 0x08048711 c1e804 shr eax, 4 | 0x08048714 c1e004 shl eax, 4 | 0x08048717 29c4 sub esp, eax | 0x08048719 8d8375e8ffff lea eax, dword [ebx - 0x178b] | 0x0804871f 890424 mov dword [esp], eax | 0x08048722 e8b9fcffff call sym.imp.printf ; int printf(const char *format) | 0x08048727 8d838ee8ffff lea eax, dword [ebx - 0x1772] | 0x0804872d 890424 mov dword [esp], eax | 0x08048730 e8abfcffff call sym.imp.printf ; int printf(const char *format) | 0x08048735 8d4588 lea eax, dword [local_78h] | 0x08048738 89442404 mov dword [local_4h], eax | 0x0804873c 8d8399e8ffff lea eax, dword [ebx - 0x1767] | 0x08048742 890424 mov dword [esp], eax | 0x08048745 e876fcffff call sym.imp.scanf ; int scanf(const char *format) | 0x0804874a 8b4510 mov eax, dword [arg_10h] ; [0x10:4]=-1 ; 16 | 0x0804874d 89442404 mov dword [local_4h], eax | 0x08048751 8d4588 lea eax, dword [local_78h] | 0x08048754 890424 mov dword [esp], eax | 0x08048757 e8bafeffff call sub.strlen_616 | 0x0804875c b800000000 mov eax, 0 | 0x08048761 8b5dfc mov ebx, dword [local_4h_2] | 0x08048764 c9 leave \ 0x08048765 c3 ret
程序开头就调用了一个函数fcn.08048766,看一下这个函数干了什么
/ (fcn) fcn.08048766 4 | fcn.08048766 (); | ; CALL XREF from sub.strlen_616 (0x804861d) | ; CALL XREF from main (0x80486f8) | 0x08048766 8b1c24 mov ebx, dword [esp] \ 0x08048769 c3 ret
向ebx中传入了一个值。
再回到main函数,它依次调用了printf -> printf -> scanf -> strlen_616,根据前面九个程序的经验,我们已经可以猜到这几个函数都干了什么,可以验证一下,比如说第一个printf,它输出的应该是“IOLI Crackme Level 0x09”我们看到它的参数是eax寄存器中的值,而不是像前面的程序一样直接传入字符串的地址,所以可以看一下eax寄存器中的值是不是该字符串的地址:
[0xf7fb50b0]> db 0x08048722 [0xf7fb50b0]> dc hit breakpoint at: 8048722 [0x08048722]> dr eax = 0x08048869 ebx = 0x08049ff4 ecx = 0xae65ce84 edx = 0xffa19754 esi = 0xf7f87000 edi = 0xf7f87000 esp = 0xffa19690 ebp = 0xffa19728 eip = 0x08048722 eflags = 0x00000286 oeax = 0xffffffff [0x08048722]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x00000838 0x08048838 4 5 (.rodata) ascii LOLO 001 0x0000083d 0x0804883d 20 21 (.rodata) ascii Password Incorrect!\n 002 0x00000855 0x08048855 13 14 (.rodata) ascii Password OK!\n 003 0x00000863 0x08048863 5 6 (.rodata) ascii wtf?\n 004 0x00000869 0x08048869 24 25 (.rodata) ascii IOLI Crackme Level 0x09\n 005 0x00000882 0x08048882 10 11 (.rodata) ascii Password:
可以看到当程序执行到第一个printf语句时,eax寄存器中的值和“IOLO Crackme Level 0x09”字符串的地址是一样的。所以main函数和之前的程序没有什么不同,只是传参的地址取值方式变了,下面继续,看一下strlen_616函数,该函数和之前的check函数类似,后面仍旧有一小段代码是访问不到的,只看上面会访问到的代码:
[0x08048722]> pdf @sub.strlen_616 / (fcn) sub.strlen_616 216 | sub.strlen_616 (int arg_8h, int arg_ch); | ; var int local_11h @ ebp-0x11 | ; var int local_10h @ ebp-0x10 | ; var int local_ch @ ebp-0xc | ; var int local_8h @ ebp-0x8 | ; arg int arg_8h @ ebp+0x8 | ; arg int arg_ch @ ebp+0xc | ; var int local_4h @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from main (0x8048757) | 0x08048616 55 push ebp | 0x08048617 89e5 mov ebp, esp | 0x08048619 53 push ebx | 0x0804861a 83ec24 sub esp, 0x24 ; '$' | 0x0804861d e844010000 call fcn.08048766 | 0x08048622 81c3d2190000 add ebx, 0x19d2 | 0x08048628 c745f4000000. mov dword [local_ch], 0 | 0x0804862f c745f0000000. mov dword [local_10h], 0 | ; CODE XREF from sub.strlen_616 (0x8048693) | .-> 0x08048636 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | : 0x08048639 890424 mov dword [esp], eax | : 0x0804863c e88ffdffff call sym.imp.strlen ; size_t strlen(const char *s) | : 0x08048641 3945f0 cmp dword [local_10h], eax | ,==< 0x08048644 734f jae 0x8048695 | |: 0x08048646 8b45f0 mov eax, dword [local_10h] | |: 0x08048649 034508 add eax, dword [arg_8h] | |: 0x0804864c 0fb600 movzx eax, byte [eax] | |: 0x0804864f 8845ef mov byte [local_11h], al | |: 0x08048652 8d45f8 lea eax, dword [local_8h] | |: 0x08048655 89442408 mov dword [local_8h_2], eax | |: 0x08048659 8d835ee8ffff lea eax, dword [ebx - 0x17a2] | |: 0x0804865f 89442404 mov dword [local_4h], eax | |: 0x08048663 8d45ef lea eax, dword [local_11h] | |: 0x08048666 890424 mov dword [esp], eax | |: 0x08048669 e882fdffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | |: 0x0804866e 8b55f8 mov edx, dword [local_8h] | |: 0x08048671 8d45f4 lea eax, dword [local_ch] | |: 0x08048674 0110 add dword [eax], edx | |: 0x08048676 837df410 cmp dword [local_ch], 0x10 | ,===< 0x0804867a 7512 jne 0x804868e | ||: 0x0804867c 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | ||: 0x0804867f 89442404 mov dword [local_4h], eax | ||: 0x08048683 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | ||: 0x08048686 890424 mov dword [esp], eax | ||: 0x08048689 e8fbfeffff call 0x8048589 | `---> 0x0804868e 8d45f0 lea eax, dword [local_10h] | |: 0x08048691 ff00 inc dword [eax] | |`=< 0x08048693 eba1 jmp 0x8048636 | `--> 0x08048695 e8c3feffff call 0x804855d | 0x0804869a 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | 0x0804869d 89442404 mov dword [local_4h], eax | 0x080486a1 8b45f8 mov eax, dword [local_8h] | 0x080486a4 890424 mov dword [esp], eax | 0x080486a7 e828feffff call 0x80484d4 | 0x080486ac 85c0 test eax, eax | ,=< 0x080486ae 7438 je 0x80486e8 | | 0x080486b0 c745f0000000. mov dword [local_10h], 0 | | ; CODE XREF from sub.strlen_616 (0x80486e6) | .--> 0x080486b7 837df009 cmp dword [local_10h], 9 | ,===< 0x080486bb 7f2b jg 0x80486e8 | |:| 0x080486bd 8b45f8 mov eax, dword [local_8h] | |:| 0x080486c0 83e001 and eax, 1 | |:| 0x080486c3 85c0 test eax, eax | ,====< 0x080486c5 751a jne 0x80486e1 | ||:| 0x080486c7 8d836fe8ffff lea eax, dword [ebx - 0x1791] | ||:| 0x080486cd 890424 mov dword [esp], eax | ||:| 0x080486d0 e80bfdffff call sym.imp.printf ; int printf(const char *format) | ||:| 0x080486d5 c70424000000. mov dword [esp], 0 | ||:| 0x080486dc e82ffdffff call sym.imp.exit ; void exit(int status) | `----> 0x080486e1 8d45f0 lea eax, dword [local_10h] | |:| 0x080486e4 ff00 inc dword [eax] | |`==< 0x080486e6 ebcf jmp 0x80486b7 | `-`-> 0x080486e8 83c424 add esp, 0x24 ; '$' | 0x080486eb 5b pop ebx | 0x080486ec 5d pop ebp \ 0x080486ed c3 ret
主要涉及两个函数0x8048589和0x804855d,其中0x8048589应该就是parell函数,0x804855d应该是输出密码错误信息的函数,先看一下0x804855d:
0x0804855d 55 push ebp 0x0804855e 89e5 mov ebp, esp 0x08048560 53 push ebx 0x08048561 83ec04 sub esp, 4 0x08048564 e8fd010000 call fcn.08048766 ;[1] 0x08048569 81c38b1a0000 add ebx, 0x1a8b 0x0804856f 8d8349e8ffff lea eax, dword [ebx - 0x17b7] 0x08048575 890424 mov dword [esp], eax 0x08048578 e863feffff call sym.imp.printf ;[2] ; int printf(const char *format) 0x0804857d c70424000000. mov dword [esp], 0 0x08048584 e887feffff call sym.imp.exit ;[3] ; void exit(int status)
可以看一下输出的字符串是什么:
[0x0804855d]> db 0x08048578 [0x0804855d]> dc IOLI Crackme Level 0x09 Password: 1234 hit breakpoint at: 8048578 [0x0804855d]> dr eax = 0x0804883d ebx = 0x08049ff4 ecx = 0x00000030 edx = 0x00000004 esi = 0xf7f87000 edi = 0xf7f87000 esp = 0xffa19650 ebp = 0xffa19658 eip = 0x08048578 eflags = 0x00000212 oeax = 0xffffffff [0x0804855d]> iz [Strings] Num Paddr Vaddr Len Size Section Type String 000 0x00000838 0x08048838 4 5 (.rodata) ascii LOLO 001 0x0000083d 0x0804883d 20 21 (.rodata) ascii Password Incorrect!\n 002 0x00000855 0x08048855 13 14 (.rodata) ascii Password OK!\n 003 0x00000863 0x08048863 5 6 (.rodata) ascii wtf?\n 004 0x00000869 0x08048869 24 25 (.rodata) ascii IOLI Crackme Level 0x09\n 005 0x00000882 0x08048882 10 11 (.rodata) ascii Password:
果然,输出的是密码错误的提示信息。
还是回到类似check函数的strlen_616函数,看一下我们认为是parell函数的0x8048589:
[0x080486ee]> pdf @fcn.08048589 / (fcn) fcn.08048589 141 | fcn.08048589 (int arg_8h, int arg_ch); | ; var int local_ch @ ebp-0xc | ; var int local_8h @ ebp-0x8 | ; arg int arg_8h @ ebp+0x8 | ; arg int arg_ch @ ebp+0xc | ; var int local_4h @ esp+0x4 | ; var int local_8h_2 @ esp+0x8 | ; CALL XREF from sub.strlen_616 (0x8048689) | 0x08048589 55 push ebp | 0x0804858a 89e5 mov ebp, esp | 0x0804858c 53 push ebx | 0x0804858d 83ec14 sub esp, 0x14 | 0x08048590 e8d1010000 call fcn.08048766 | 0x08048595 81c35f1a0000 add ebx, 0x1a5f | 0x0804859b 8d45f8 lea eax, dword [local_8h] | 0x0804859e 89442408 mov dword [local_8h_2], eax | 0x080485a2 8d835ee8ffff lea eax, dword [ebx - 0x17a2] | 0x080485a8 89442404 mov dword [local_4h], eax | 0x080485ac 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8 | 0x080485af 890424 mov dword [esp], eax | 0x080485b2 e839feffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...) | 0x080485b7 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12 | 0x080485ba 89442404 mov dword [local_4h], eax | 0x080485be 8b45f8 mov eax, dword [local_8h] | 0x080485c1 b 890424 mov dword [esp], eax | 0x080485c4 e80bffffff call fcn.080484d4 | 0x080485c9 85c0 test eax, eax | ,=< 0x080485cb 7443 je 0x8048610 | | 0x080485cd c745f4000000. mov dword [local_ch], 0 | | ; CODE XREF from fcn.08048589 (0x804860e) | .--> 0x080485d4 837df409 cmp dword [local_ch], 9 | ,===< 0x080485d8 7f36 jg 0x8048610 | |:| 0x080485da 8b45f8 mov eax, dword [local_8h] | |:| 0x080485dd 83e001 and eax, 1 | |:| 0x080485e0 85c0 test eax, eax | ,====< 0x080485e2 7525 jne 0x8048609 | ||:| 0x080485e4 8b83fcffffff mov eax, dword [ebx - 4] | ||:| 0x080485ea 833801 cmp dword [eax], 1 | ,=====< 0x080485ed 750e jne 0x80485fd | |||:| 0x080485ef 8d8361e8ffff lea eax, dword [ebx - 0x179f] | |||:| 0x080485f5 890424 mov dword [esp], eax | |||:| 0x080485f8 e8e3fdffff call sym.imp.printf ; int printf(const char *format) | `-----> 0x080485fd c70424000000. mov dword [esp], 0 | ||:| 0x08048604 e807feffff call sym.imp.exit ; void exit(int status) | `----> 0x08048609 8d45f4 lea eax, dword [local_ch] | |:| 0x0804860c ff00 inc dword [eax] | |`==< 0x0804860e ebc4 jmp 0x80485d4 | `-`-> 0x08048610 83c414 add esp, 0x14 | 0x08048613 5b pop ebx | 0x08048614 5d pop ebp \ 0x08048615 c3 ret
发现和parell的操作确实相同,其中的0x80484d4应该就是dummy函数。
…..好吧,其实后面步骤都差不多,看一下0x80484d4和dummy函数是不是一样,查看输出的字符串内容。
其中根据iz命令显示的字符串,我们已经差不多可以肯定这个程序对密码的检测和前面几个程序同样是相同的,直接测试一下
root@kali:~/IOLIcrackme/bin-linux# export LOLTTT=test root@kali:~/IOLIcrackme/bin-linux# ./crackme0x09 IOLI Crackme Level 0x09 Password: 4156 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x09 IOLI Crackme Level 0x09 Password: 41561 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x09 IOLI Crackme Level 0x09 Password: 41562 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x09 IOLI Crackme Level 0x09 Password: 2147483646 Password Incorrect! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x09 IOLI Crackme Level 0x09 Password: 2147283646 Password OK! root@kali:~/IOLIcrackme/bin-linux# ./crackme0x09 IOLI Crackme Level 0x09 Password: 2237283646 Password Incorrect!
总结
这十个程序程序的功能是一样的,只是对密码的检查计算过程不同。
一开始只是简单的和正确的密码进行比较,其中正确密码的引用方式又有不同;
之后是需要密码满足一系列的要求,不断引入不同的要求,并加入最终不起作用或者不用被执行的混淆代码。
在对程序进行破解的过程中,我主要是采用静态分析,使用了s、pdf、iz、Vpp等命令,有时为了验证,查看相关寄存器的值,也引入了动态分析,在debug模式下,使用db、dc、dr等命令进行操作。
由于我对radare2还并不是特别熟悉,虽然最终都得到了答案,但是可能过程并不是特别简洁。而且由于这十个程序的功能过于一致,所以只能用来对radare2进行初步的熟悉和了解,进一步的使用还需要接触更多的实例应用。