IOLI crackme分析——從應用中學習使用radare2
Crackme0x00 - writeup
我現在開始看radare2book了,現在剛看1/3,有些無聊,因為之前也看過一些radare2的例項講解,所以現在先試著做一下里面的crackme練習。
先執行一下craceme0x00這個檔案,看來是要把密碼找出來。
[email protected]:~/IOLIcrackme/bin-linux# ./crackme0x00 IOLI Crackme Level 0x00 Password: 1234 Invalid Password!
現在看一下string,使用iz命令。
[email protected]:~/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.
[email protected]:~/IOLIcrackme/bin-linux# ./crackme0x00 IOLI Crackme Level 0x00 Password: 250382 Password OK :)
成功。
看來真的只是一個簡單的熱身。
Crackme0x01 - writeup
還是先執行一下(其實我也不太確定是不是每次都要先執行,因為如果是真的maleware可能執行之後就直接game over了,不過這裡只是簡單的練習,所以就不考慮這個問題了。)
[email protected]:~/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
[email protected]:~/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,再執行一下程式測試一下
[email protected]:~/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得知的那兩個像是亂碼一樣的字串做了一些變換,生成最後的提示資訊。
[email protected]:~/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之後並不會處理之後的字元,而是直接通過,讓我們來試一下
[email protected]:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 1234 Password Incorrect! [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 555 Password OK! [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 555555555555555 Password OK! [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x04 IOLI Crackme Level 0x04 Password: 5541 Password OK! [email protected]:~/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)
測試一下:
[email protected]:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 4156 Password OK! [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 41561 Password Incorrect! 奇數 [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 41562 Password OK! [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 2147483646 Password Incorrect! 前N位和不是15 [email protected]:~/IOLIcrackme/bin-linux# ./crackme0x05 IOLI Crackme Level 0x05 Password: 2147283646 Password OK! [email protected]:~/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)的棧結構:
ebp+0x10 |
envp |
ebp+0xc |
argv |
ebp+0x8 |
argc |
ebp+0x4 |
Return address |
ebp |
Previous ebp |
所以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 ;