IOLI-crackme0x06-0x09 writeup
前几天写了使用Radare2并用3中方法来解决crackme0x00, 然后紧接着第二天 就写了另外5个writeup, 如果认真看会发现那几个crackme的分析也是一开始 走了很多弯路, 但玩多了也就熟悉了. 趁热打铁, 把最后几关也过一过.
本文所使用的crackmes都来自IOLI-crackme.
crackme0x06
虽然隔了几天, 但r2打开反汇编时还是发现结构和crackme0x05差不多, 先看main函数:
[0x08048607]> pdf @ sym.main
;-- main:
/ (fcn) sym.main 99
| sym.main (int arg_10h);
| ; var int local_78h @ ebp-0x78
| ; arg int arg_10h @ ebp+0x10
| ; var int local_4h @ esp+0x4
| ; DATA XREF from 0x08048417 (entry0)
| 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"
| 0x0804862d e886fdffff call sym.imp.printf ; int printf(const char *format)
| 0x08048632 c704247c8704. mov dword [esp], str.Password: ; [0x804877c:4]=0x73736150 ; "Password: "
| 0x08048639 e87afdffff call sym.imp.printf ; int printf(const char *format)
| 0x0804863e 8d4588 lea eax, [local_78h]
| 0x08048641 89442404 mov dword [local_4h], eax
| 0x08048645 c70424878704. mov dword [esp], 0x8048787 ; [0x8048787:4]=0x7325
| 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, [local_78h]
| 0x0804865b 890424 mov dword [esp], eax
| 0x0804865e e825ffffff call sym.check
| 0x08048663 b800000000 mov eax, 0
| 0x08048668 c9 leave
\ 0x08048669 c3 ret
:> ps @ 0x8048787
%s
local_4h
还是scanf输入的用户密码, 但这里值得一提的是arg_10h
这个地址(ebp+0x10),
我们知道main函数接受2个参数int main(int argc, char *argv[])
, 其中argc对应ebp+8,
argv对于ebp+12, 而返回地址在ebp+4, 那么ebp+16又是啥? 第三个参数? 其实这是GCC编译器的一个
特性, 第三个参数envp也是一个字符串指针, 表示的是环境变量的值, 所以这里main函数相当于
int main(int argc, char *argv[], char *envp[])
, 只不过前两个参数并未用到.
接下来分析check函数. 对于流程比较复杂的程序, 我们可以在视图模式中按p
键切换到极简试图,
然后按TAB
,Shift+TAB
键切换不同的代码块(Basic Block), 如下所示:
check
接受两个参数, 第一个为输入字符串, 第二个为字符串指针. 伪代码如下:
void check(char *input, char *envp[]) {
int i;
char local_dh[2];
int local_8h = 0;
for(i = 0; i < strlen(input); i++) {
local_dh[0] = input[i];
local_dh[1] = 0;
sscanf(local_dh, "%d", local_4h);
local_8h += local_4h;
if (local_8h == 0x10) {
parell(input, envp);
}
}
printf("Password Incorrect!\n");
return;
}
根据循环的跳转情况可知ebp-0xc
为循环变量, 因此重命名为i
,
其他尚未知具体作用的变量保留. 该函数的作用是将输入的每一字节都转成数字并累加,
当累加和为0x10时调用parell:
[0x08048588]> pdf @ sym.parell
/ (fcn) sym.parell 110
| sym.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 0x080485ed (sym.check)
| 0x0804851a 55 push ebp
| 0x0804851b 89e5 mov ebp, esp
| 0x0804851d 83ec18 sub esp, 0x18
| 0x08048520 8d45fc lea eax, [local_4h]
| 0x08048523 89442408 mov dword [local_8h], eax
| 0x08048527 c74424043d87. mov dword [local_4h_2], 0x804873d ; [0x804873d:4]=0x50006425
| 0x0804852f 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| 0x08048532 890424 mov dword [esp], eax
| 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 [local_4h_2], 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
| | ; JMP XREF from 0x08048584 (sym.parell)
| .--> 0x08048557 837df809 cmp dword [local_8h_2], 9 ; [0x9:4]=-1 ; 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"
| ||:| 0x0804856e e845feffff call sym.imp.printf ; int printf(const char *format)
| ||:| 0x08048573 c70424000000. mov dword [esp], 0
| ||:| 0x0804857a e869feffff call sym.imp.exit ; void exit(int status)
| `----> 0x0804857f 8d45f8 lea eax, [local_8h_2]
| |:| 0x08048582 ff00 inc dword [eax]
| |`==< 0x08048584 ebd1 jmp 0x8048557
| `-`-> 0x08048586 c9 leave
\ 0x08048587 c3 ret
parell接受2个参数, 伪代码如下:
void parell(char *input, char *envp[]) {
sscanf(input, "%d", local_4h);
if (dummy(local_4h, envp) == 0) {
return;
}
for (int i = 0; i <= 9; i++) {
if (local_4h & 1 == 0) {
printf("Password OK!\n");
exit(0);
}
}
return;
}
作用就是判断输入数字的奇偶性, 不过增加了dummy函数返回不为0的限制. 而且这里的循环居然是个障眼法= =.
好吧, 再看看dummy函数:
[0x08048607]> pdf @ sym.dummy
/ (fcn) sym.dummy 102
| sym.dummy (int arg_ch);
| ; var int local_8h_2 @ ebp-0x8
| ; var int local_4h @ ebp-0x4
| ; arg int arg_ch @ ebp+0xc
| ; CALL XREF from 0x08048547 (sym.parell)
| 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, [eax*4]
| : 0x080484cb 8b450c mov eax, dword [arg_ch] ; [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, [eax*4]
| |: 0x080484de 8b550c mov edx, dword [arg_ch] ; [0xc:4]=-1 ; 12
| |: 0x080484e1 8d45fc lea eax, [local_4h]
| |: 0x080484e4 ff00 inc dword [eax]
| |: 0x080484e6 c74424080300. mov dword [esp + 8], 3
| |: 0x080484ee c74424043887. mov dword [esp + 4], str.LOLO ; [0x8048738:4]=0x4f4c4f4c ; "LOLO"
| |: 0x080484f6 8b0411 mov eax, dword [ecx + edx]
| |: 0x080484f9 890424 mov dword [esp], eax
| |: 0x080484fc e8d7feffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
| |: 0x08048501 85c0 test eax, eax
| |`=< 0x08048503 75bc jne 0x80484c1
| | 0x08048505 c745f8010000. mov dword [local_8h_2], 1
| |,=< 0x0804850c eb07 jmp 0x8048515
| `--> 0x0804850e c745f8000000. mov dword [local_8h_2], 0
| | ; JMP XREF from 0x0804850c (sym.dummy)
| `-> 0x08048515 8b45f8 mov eax, dword [local_8h_2]
| 0x08048518 c9 leave
\ 0x08048519 c3 ret
从汇编可以发现, dummy函数虽然传入了2个参数, 但只用到了第二个, 先写个粗略的伪代码:
int dummy(char *input, char *envp[]) {
local_4h = 0;
while(1) {
if (envp[local_4h] == 0) {
return 0;
}
char *s = envp[local_4h];
local_4h++;
if (strncmp(s, "LOLO", 3) != 0) {
continue;
} else {
return 1;
}
}
}
作用就很明显了, 判断输入的环境变量前3字节是否为LOL, 若是则返回1. 所以我们只需要 传入环境变量, 并且输入一个前X为和为0x10的偶数即可:
$ env LOL=1 ./crackme0x06
IOLI Crackme Level 0x06
Password: 886
Password OK!
crackme0x07
先看主函数:
[0x0804867d]> pdf @ main
/ (fcn) main 99
| main (int arg_10h);
| ; var int local_78h @ ebp-0x78
| ; arg int arg_10h @ ebp+0x10
| ; var int local_4h @ esp+0x4
| ; DATA XREF from 0x08048417 (entry0)
| 0x0804867d 55 push ebp
| 0x0804867e 89e5 mov ebp, esp
| 0x08048680 81ec88000000 sub esp, 0x88
| 0x08048686 83e4f0 and esp, 0xfffffff0
| 0x08048689 b800000000 mov eax, 0
| 0x0804868e 83c00f add eax, 0xf
| 0x08048691 83c00f add eax, 0xf
| 0x08048694 c1e804 shr eax, 4
| 0x08048697 c1e004 shl eax, 4
| 0x0804869a 29c4 sub esp, eax
| 0x0804869c c70424d98704. mov dword [esp], str.IOLI_Crackme_Level_0x07 ; [0x80487d9:4]=0x494c4f49 ; "IOLI Crackme Level 0x07\n"
| 0x080486a3 e810fdffff call sym.imp.printf ; int printf(const char *format)
| 0x080486a8 c70424f28704. mov dword [esp], str.Password: ; [0x80487f2:4]=0x73736150 ; "Password: "
| 0x080486af e804fdffff call sym.imp.printf ; int printf(const char *format)
| 0x080486b4 8d4588 lea eax, [local_78h]
| 0x080486b7 89442404 mov dword [local_4h], eax
| 0x080486bb c70424fd8704. mov dword [esp], 0x80487fd ; [0x80487fd:4]=0x7325
| 0x080486c2 e8d1fcffff call sym.imp.scanf ; int scanf(const char *format)
| 0x080486c7 8b4510 mov eax, dword [arg_10h] ; [0x10:4]=-1 ; 16
| 0x080486ca 89442404 mov dword [local_4h], eax
| 0x080486ce 8d4588 lea eax, [local_78h]
| 0x080486d1 890424 mov dword [esp], eax
| 0x080486d4 e8e0feffff call fcn.080485b9
| 0x080486d9 b800000000 mov eax, 0
| 0x080486de c9 leave
\ 0x080486df c3 ret
[0x0804867d]> ps @ 0x80487fd
%s
大意就是用户输入密码,然后调用fcn.080485b9
函数,其中第一个参数为输入字符串,第二个参数为arg_10h
,
即上节说过的envp. 再看看fcn.080485b9
这个函数:
[0x080485b9]> pdf @ fcn.080485b9
/ (fcn) fcn.080485b9 196
| fcn.080485b9 (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 @ 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_2 @ esp+0x8
| ; CALL XREF from 0x080486d4 (main)
| 0x080485b9 55 push ebp
| 0x080485ba 89e5 mov ebp, esp
| 0x080485bc 83ec28 sub esp, 0x28 ; '('
| 0x080485bf c745f8000000. mov dword [local_8h], 0
| 0x080485c6 c745f4000000. mov dword [local_ch], 0
| ; JMP XREF from 0x08048628 (fcn.080485b9)
| .-> 0x080485cd 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| : 0x080485d0 890424 mov dword [esp], eax
| : 0x080485d3 e8d0fdffff call sym.imp.strlen ; size_t strlen(const char *s)
| : 0x080485d8 3945f4 cmp dword [local_ch], eax ; [0x13:4]=-1 ; 19
| ,==< 0x080485db 734d jae 0x804862a
| |: 0x080485dd 8b45f4 mov eax, dword [local_ch]
| |: 0x080485e0 034508 add eax, dword [arg_8h]
| |: 0x080485e3 0fb600 movzx eax, byte [eax]
| |: 0x080485e6 8845f3 mov byte [local_dh], al
| |: 0x080485e9 8d45fc lea eax, [local_4h]
| |: 0x080485ec 89442408 mov dword [local_8h_2], eax
| |: 0x080485f0 c7442404c287. mov dword [local_4h_2], 0x80487c2 ; [0x80487c2:4]=0x50006425
| |: 0x080485f8 8d45f3 lea eax, [local_dh]
| |: 0x080485fb 890424 mov dword [esp], eax
| |: 0x080485fe e8c5fdffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...)
| |: 0x08048603 8b55fc mov edx, dword [local_4h]
| |: 0x08048606 8d45f8 lea eax, [local_8h]
| |: 0x08048609 0110 add dword [eax], edx
| |: 0x0804860b 837df810 cmp dword [local_8h], 0x10 ; [0x10:4]=-1 ; 16
| ,===< 0x0804860f 7512 jne 0x8048623
| ||: 0x08048611 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| ||: 0x08048614 89442404 mov dword [local_4h_2], eax
| ||: 0x08048618 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| ||: 0x0804861b 890424 mov dword [esp], eax
| ||: 0x0804861e e81fffffff call 0x8048542
| `---> 0x08048623 8d45f4 lea eax, [local_ch]
| |: 0x08048626 ff00 inc dword [eax]
| |`=< 0x08048628 eba3 jmp 0x80485cd
| `--> 0x0804862a e8f5feffff call 0x8048524
| 0x0804862f 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| 0x08048632 89442404 mov dword [local_4h_2], eax
| 0x08048636 8b45fc mov eax, dword [local_4h]
| 0x08048639 890424 mov dword [esp], eax
| 0x0804863c e873feffff call 0x80484b4
| 0x08048641 85c0 test eax, eax
| ,=< 0x08048643 7436 je 0x804867b
| | 0x08048645 c745f4000000. mov dword [local_ch], 0
| | ; JMP XREF from 0x08048679 (fcn.080485b9)
| .--> 0x0804864c 837df409 cmp dword [local_ch], 9 ; [0x9:4]=-1 ; 9
| ,===< 0x08048650 7f29 jg 0x804867b
| |:| 0x08048652 8b45fc mov eax, dword [local_4h]
| |:| 0x08048655 83e001 and eax, 1
| |:| 0x08048658 85c0 test eax, eax
| ,====< 0x0804865a 7518 jne 0x8048674
| ||:| 0x0804865c c70424d38704. mov dword [esp], str.wtf ; [0x80487d3:4]=0x3f667477 ; "wtf?\n"
| ||:| 0x08048663 e850fdffff call sym.imp.printf ; int printf(const char *format)
| ||:| 0x08048668 c70424000000. mov dword [esp], 0
| ||:| 0x0804866f e874fdffff call sym.imp.exit ; void exit(int status)
| `----> 0x08048674 8d45f4 lea eax, [local_ch]
| |:| 0x08048677 ff00 inc dword [eax]
| |`==< 0x08048679 ebd1 jmp 0x804864c
| `-`-> 0x0804867b c9 leave
\ 0x0804867c c3 ret
有点小长,可以用视图模式去看有个比较直观的印象,这个函数也没个名字,就还是称之为check
吧,
重命名一下函数:
[0x080485b9]> afn check fcn.080485b9
写个粗糙的伪代码:
void check(char *input, char *envp[]) {
int i,j = 0;
while(1) {
if (i >= strlen(input)) {
fcn.08048524();
}
local_dh = input[i];
sscanf(local_dh, "%d", &local_4h);
j += local_4h;
if (j == 0x10) {
fcn.8048542(input ,envp);
}
i++;
continue;
}
}
有几个无名的函数, 先看fcn.08048524
:
[0x08048542]> pdf @ 0x08048524
/ (fcn) fail 30
| fail (int arg_8h);
| ; var int local_ch @ ebp-0xc
| ; var int local_8h @ ebp-0x8
| ; arg int arg_8h @ ebp+0x8
| ; CALL XREF from 0x0804862a (check)
| 0x08048524 55 push ebp
| 0x08048525 89e5 mov ebp, esp
| 0x08048527 83ec08 sub esp, 8
| 0x0804852a c70424ad8704. mov dword [esp], str.Password_Incorrect ; [0x80487ad:4]=0x73736150 ; "Password Incorrect!\n"
| 0x08048531 e882feffff call sym.imp.printf ; int printf(const char *format)
| 0x08048536 c70424000000. mov dword [esp], 0
\ 0x0804853d e8a6feffff call sym.imp.exit ; void exit(int status)
作用是打印密码出错信息然后退出, 所以我们将其重命名为fail
了. 其实fail函数之后还有很多逻辑,
但我们都可以不用分析, 因为那部分逻辑是不会进入的.
再看函数fcn.8048542
:
[0x08048542]> pdf @ 0x8048542
/ (fcn) fcn.08048542 119
| fcn.08048542 (int arg_8h, int arg_9h, 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 @ ebp-0x4
| ; arg int arg_8h @ ebp+0x8
| ; arg int arg_9h @ ebp+0x9
| ; arg int arg_ch @ ebp+0xc
| ; var int local_4h_2 @ esp+0x4
| ; var int local_8h_2 @ esp+0x8
| ; CALL XREF from 0x0804861e (check)
| 0x08048542 55 push ebp
| 0x08048543 89e5 mov ebp, esp
| 0x08048545 83ec18 sub esp, 0x18
| 0x08048548 8d45fc lea eax, [local_4h]
| 0x0804854b 89442408 mov dword [local_8h_2], eax
| 0x0804854f c7442404c287. mov dword [local_4h_2], 0x80487c2 ; [0x80487c2:4]=0x50006425
| 0x08048557 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| 0x0804855a 890424 mov dword [esp], eax
| 0x0804855d e866feffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...)
| 0x08048562 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| 0x08048565 89442404 mov dword [local_4h_2], eax
| 0x08048569 8b45fc mov eax, dword [local_4h]
| 0x0804856c 890424 mov dword [esp], eax
| 0x0804856f e840ffffff call fcn.080484b4
| 0x08048574 85c0 test eax, eax
| ,=< 0x08048576 743f je 0x80485b7
| | 0x08048578 c745f8000000. mov dword [local_8h], 0
| | ; JMP XREF from 0x080485b5 (fcn.08048542)
| .--> 0x0804857f 837df809 cmp dword [local_8h], 9 ; [0x9:4]=-1 ; 9
| ,===< 0x08048583 7f32 jg 0x80485b7
| |:| 0x08048585 8b45fc mov eax, dword [local_4h]
| |:| 0x08048588 83e001 and eax, 1
| |:| 0x0804858b 85c0 test eax, eax
| ,====< 0x0804858d 7521 jne 0x80485b0
| ||:| 0x0804858f 833d2ca00408. cmp dword [0x804a02c], 1 ; [0x1:4]=-1 ; 1
| ,=====< 0x08048596 750c jne 0x80485a4
| |||:| 0x08048598 c70424c58704. mov dword [esp], str.Password_OK ; [0x80487c5:4]=0x73736150 ; "Password OK!\n"
| |||:| 0x0804859f e814feffff call sym.imp.printf ; int printf(const char *format)
| `-----> 0x080485a4 c70424000000. mov dword [esp], 0
| ||:| 0x080485ab e838feffff call sym.imp.exit ; void exit(int status)
| `----> 0x080485b0 8d45f8 lea eax, [local_8h]
| |:| 0x080485b3 ff00 inc dword [eax]
| |`==< 0x080485b5 ebc8 jmp 0x804857f
| `-`-> 0x080485b7 c9 leave
\ 0x080485b8 c3 ret
看来是做进一步的校验, 姑且命名为double_check
吧, 不知道是不是r2的bug, 居然显示这个函数有3个参数,
但我们前面知道其实它只有两个, 所以不管什么原因, 先把arg_9h
去掉再说, 然后两个参数改个像样的名字:
[0x08048542]> afv-arg_9h
[0x08048542]> afvn arg_8h input
[0x08048542]> afvn arg_ch envp
again, 写一下它的伪代码:
void double_check(char *input, char *envp[])
{
int local_4h;
int i;
sscanf(input, "%d", &local_4h);
if (0 == fcn.080484b4(local_4h, envp)) {
return;
}
for (i = 0; i <= 9; i++) {
if(local_4h & 1 != 0) {
continue;
}
if ((int)*0x804a02c == 1) {
printf("Password OK!\n");
}
exit(0);
}
}
由代码可以得知要想进入正确分支, 必须要满足函数fcn.080484b4
返回不为0,
而且输入的数字要是个偶数. 有意思的是还要额外判断一个硬编码的地址(0x804a02c)
里的值是否为1, 若为1才打印密码正确. dump这个地址出来看看:
:> px @ 0x804a02c
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x0804a02c 0000 0000 ffff ffff ffff ffff ffff ffff ................
0x0804a03c ffff ffff ffff ffff ffff ffff ffff ffff ................
发现其默认值是0, 所以必然是运行时修改了这个地址, 那么在哪儿呢? 猜测是fcn.080484b4
这函数, 因为只有它没被分析过了, 当然r2也提供了查看引用的功能:
:> axt @ 0x804a02c
fcn.080484b4 0x8048505 [data] mov dword [0x804a02c], 1
果然是你, 那就来看看这个检查环境变量和输入的函数吧:
[0x0804851f]> pdf @ fcn.080484b4
/ (fcn) fcn.080484b4 112
| fcn.080484b4 (int arg_ch);
| ; var int local_8h @ ebp-0x8
| ; var int local_4h @ ebp-0x4
| ; arg int arg_ch @ ebp+0xc
| ; CALL XREF from 0x0804863c (check)
| ; CALL XREF from 0x0804856f (double_check)
| 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, [eax*4]
| : 0x080484cb 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| : 0x080484ce 833c0200 cmp dword [edx + eax], 0
| ,==< 0x080484d2 7444 je 0x8048518
| |: 0x080484d4 8b45fc mov eax, dword [local_4h]
| |: 0x080484d7 8d0c85000000. lea ecx, [eax*4]
| |: 0x080484de 8b550c mov edx, dword [arg_ch] ; [0xc:4]=-1 ; 12
| |: 0x080484e1 8d45fc lea eax, [local_4h]
| |: 0x080484e4 ff00 inc dword [eax]
| |: 0x080484e6 c74424080300. mov dword [esp + 8], 3
| |: 0x080484ee c7442404a887. mov dword [esp + 4], str.LOLO ; [0x80487a8:4]=0x4f4c4f4c ; "LOLO"
| |: 0x080484f6 8b0411 mov eax, dword [ecx + edx]
| |: 0x080484f9 890424 mov dword [esp], eax
| |: 0x080484fc e8d7feffff call sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n)
| |: 0x08048501 85c0 test eax, eax
| |`=< 0x08048503 75bc jne 0x80484c1
| | 0x08048505 c7052ca00408. mov dword [0x804a02c], 1 ; [0x804a02c:4]=0
| | 0x0804850f c745f8010000. mov dword [local_8h], 1
| |,=< 0x08048516 eb07 jmp 0x804851f
| `--> 0x08048518 c745f8000000. mov dword [local_8h], 0
| | ; JMP XREF from 0x08048516 (fcn.080484b4)
| `-> 0x0804851f 8b45f8 mov eax, dword [local_8h]
| 0x08048522 c9 leave
\ 0x08048523 c3 ret
虽然调用时传了两个参数, 但实际上只用到了第二个即环境变量char *envp[]
. 伪代码如下:
int check_env(char *input, char *envp[]) {
int local_4h = 0;
while(1) {
if (0 == envp[local_4h * 4]) {
local_8h = 0;
break;
}
local_4h++;
if (0 != strncmp(envp[local_4h * 4], "LOLO", 3))
continue;
*0x804a02c = 1;
local_8h = 1;
break;
}
return local_8h;
}
所以结果还是查看环境变量里的key前3位是不是LOL, 然后要求输入是个偶数, 并且前n位相加为16就行了, 所以基本上和上题一样, 只不过加了一大堆障眼法而已罢了. 验证结果:
$ LOL= ./crackme0x07
IOLI Crackme Level 0x07
Password: 88
Password OK!
$ LOLO=1 ./crackme0x07
IOLI Crackme Level 0x07
Password: 196110
Password OK!
crackme0x08
还是老样子, 从main开始分析:
[0x08048400]> pdf @ main
;-- main:
/ (fcn) sym.main 99
| sym.main (int arg_10h);
| ; var int local_78h @ ebp-0x78
| ; arg int arg_10h @ ebp+0x10
| ; var int local_4h @ esp+0x4
| ; DATA XREF from 0x08048417 (entry0)
| 0x0804867d 55 push ebp
| 0x0804867e 89e5 mov ebp, esp
| 0x08048680 81ec88000000 sub esp, 0x88
| 0x08048686 83e4f0 and esp, 0xfffffff0
| 0x08048689 b800000000 mov eax, 0
| 0x0804868e 83c00f add eax, 0xf
| 0x08048691 83c00f add eax, 0xf
| 0x08048694 c1e804 shr eax, 4
| 0x08048697 c1e004 shl eax, 4
| 0x0804869a 29c4 sub esp, eax
| 0x0804869c c70424d98704. mov dword [esp], str.IOLI_Crackme_Level_0x08 ; [0x80487d9:4]=0x494c4f49 ; "IOLI Crackme Level 0x08\n"
| 0x080486a3 e810fdffff call sym.imp.printf ; int printf(const char *format)
| 0x080486a8 c70424f28704. mov dword [esp], str.Password: ; [0x80487f2:4]=0x73736150 ; "Password: "
| 0x080486af e804fdffff call sym.imp.printf ; int printf(const char *format)
| 0x080486b4 8d4588 lea eax, [local_78h]
| 0x080486b7 89442404 mov dword [local_4h], eax
| 0x080486bb c70424fd8704. mov dword [esp], 0x80487fd ; [0x80487fd:4]=0x7325
| 0x080486c2 e8d1fcffff call sym.imp.scanf ; int scanf(const char *format)
| 0x080486c7 8b4510 mov eax, dword [arg_10h] ; [0x10:4]=-1 ; 16
| 0x080486ca 89442404 mov dword [local_4h], eax
| 0x080486ce 8d4588 lea eax, [local_78h]
| 0x080486d1 890424 mov dword [esp], eax
| 0x080486d4 e8e0feffff call sym.check
| 0x080486d9 b800000000 mov eax, 0
| 0x080486de c9 leave
\ 0x080486df c3 ret
似曾相识? 再看看sym.check
:
[0x08048400]> pdf @ sym.check
/ (fcn) sym.check 196
| sym.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 @ 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_2 @ esp+0x8
| ; CALL XREF from 0x080486d4 (sym.main)
| 0x080485b9 55 push ebp
| 0x080485ba 89e5 mov ebp, esp
| 0x080485bc 83ec28 sub esp, 0x28 ; '('
| 0x080485bf c745f8000000. mov dword [local_8h], 0
| 0x080485c6 c745f4000000. mov dword [local_ch], 0
| ; JMP XREF from 0x08048628 (sym.check)
| .-> 0x080485cd 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| : 0x080485d0 890424 mov dword [esp], eax
| : 0x080485d3 e8d0fdffff call sym.imp.strlen ; size_t strlen(const char *s)
| : 0x080485d8 3945f4 cmp dword [local_ch], eax ; [0x13:4]=-1 ; 19
| ,==< 0x080485db 734d jae 0x804862a
| |: 0x080485dd 8b45f4 mov eax, dword [local_ch]
| |: 0x080485e0 034508 add eax, dword [arg_8h]
| |: 0x080485e3 0fb600 movzx eax, byte [eax]
| |: 0x080485e6 8845f3 mov byte [local_dh], al
| |: 0x080485e9 8d45fc lea eax, [local_4h]
| |: 0x080485ec 89442408 mov dword [local_8h_2], eax
| |: 0x080485f0 c7442404c287. mov dword [local_4h_2], 0x80487c2 ; [0x80487c2:4]=0x50006425
| |: 0x080485f8 8d45f3 lea eax, [local_dh]
| |: 0x080485fb 890424 mov dword [esp], eax
| |: 0x080485fe e8c5fdffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...)
| |: 0x08048603 8b55fc mov edx, dword [local_4h]
| |: 0x08048606 8d45f8 lea eax, [local_8h]
| |: 0x08048609 0110 add dword [eax], edx
| |: 0x0804860b 837df810 cmp dword [local_8h], 0x10 ; [0x10:4]=-1 ; 16
| ,===< 0x0804860f 7512 jne 0x8048623
| ||: 0x08048611 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| ||: 0x08048614 89442404 mov dword [local_4h_2], eax
| ||: 0x08048618 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| ||: 0x0804861b 890424 mov dword [esp], eax
| ||: 0x0804861e e81fffffff call sym.parell
| ||: ; JMP XREF from 0x0804860f (sym.check)
| `---> 0x08048623 8d45f4 lea eax, [local_ch]
| |: 0x08048626 ff00 inc dword [eax]
| |`=< 0x08048628 eba3 jmp 0x80485cd
| | ; JMP XREF from 0x080485db (sym.check)
| `--> 0x0804862a e8f5feffff call sym.che
| 0x0804862f 8b450c mov eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| 0x08048632 89442404 mov dword [local_4h_2], eax
| 0x08048636 8b45fc mov eax, dword [local_4h]
| 0x08048639 890424 mov dword [esp], eax
| 0x0804863c e873feffff call sym.dummy
| 0x08048641 85c0 test eax, eax
| ,=< 0x08048643 7436 je 0x804867b
| | 0x08048645 c745f4000000. mov dword [local_ch], 0
| | ; JMP XREF from 0x08048679 (sym.check)
| .--> 0x0804864c 837df409 cmp dword [local_ch], 9 ; [0x9:4]=-1 ; 9
| ,===< 0x08048650 7f29 jg 0x804867b
| |:| 0x08048652 8b45fc mov eax, dword [local_4h]
| |:| 0x08048655 83e001 and eax, 1
| |:| 0x08048658 85c0 test eax, eax
| ,====< 0x0804865a 7518 jne 0x8048674
| ||:| 0x0804865c c70424d38704. mov dword [esp], str.wtf ; [0x80487d3:4]=0x3f667477 ; "wtf?\n"
| ||:| 0x08048663 e850fdffff call sym.imp.printf ; int printf(const char *format)
| ||:| 0x08048668 c70424000000. mov dword [esp], 0
| ||:| 0x0804866f e874fdffff call sym.imp.exit ; void exit(int status)
| ||:| ; JMP XREF from 0x0804865a (sym.check)
| `----> 0x08048674 8d45f4 lea eax, [local_ch]
| |:| 0x08048677 ff00 inc dword [eax]
| |`==< 0x08048679 ebd1 jmp 0x804864c
| | | ; JMP XREF from 0x08048643 (sym.check)
| | | ; JMP XREF from 0x08048650 (sym.check)
| `-`-> 0x0804867b c9 leave
\ 0x0804867c c3 ret
还是似层相识. 有了上一题的经历, 这次就不重新分析了, 直接用radiff2来看看和上一题的不同之处:
$ radiff2 -A -C ./crackme0x07 ./crackme0x08
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
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)
lucky! crackme0x08居然和crackme0x07几乎一样, 不同之处在于crackme0x08的符号没有去掉, 虽然crackme0x07有几个新的函数, 不过大多都在init, fini部分. 所以用上题的密码来验证下:
$ LOL= ./crackme0x08
IOLI Crackme Level 0x08
Password: 88
Password OK!
$ LOLO=1 ./crackme0x08
IOLI Crackme Level 0x08
Password: 196110
Password OK!
有同学就问了, 要是没有那么lucky怎么办? 那也没关系, 只要分析不同的函数, 也能达到事半功倍的效果, 或者重新分析, 那也比crackme0x07简单嘛, 至少不用重命名函数了.
crackme0x09
最后一关了, 还是从main开始:
[0x080486ee]> pdf @ main
/ (fcn) main 120
| main (int arg_10h);
| ; 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 0x08048437 (entry0)
| 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, [ebx - 0x178b]
| 0x0804871f 890424 mov dword [esp], eax
| 0x08048722 e8b9fcffff call sym.imp.printf ; int printf(const char *format)
| 0x08048727 8d838ee8ffff lea eax, [ebx - 0x1772]
| 0x0804872d 890424 mov dword [esp], eax
| 0x08048730 e8abfcffff call sym.imp.printf ; int printf(const char *format)
| 0x08048735 8d4588 lea eax, [local_78h]
| 0x08048738 89442404 mov dword [local_4h], eax
| 0x0804873c 8d8399e8ffff lea eax, [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, [local_78h]
| 0x08048754 890424 mov dword [esp], eax
| 0x08048757 e8bafeffff call sub.strlen_616 ; size_t strlen(const char *s)
| 0x0804875c b800000000 mov eax, 0
| 0x08048761 8b5dfc mov ebx, dword [local_4h_2]
| 0x08048764 c9 leave
\ 0x08048765 c3 ret
上来就调用了函数fcn.08048766
, 看看是干嘛用的呢:
[0x080486ee]> pdf @ fcn.08048766
/ (fcn) fcn.08048766 4
| fcn.08048766 ();
| ; XREFS: CALL 0x080486f8 CALL 0x0804861d CALL 0x080484db CALL 0x08048564 CALL 0x08048590
| ; XREFS: CALL 0x08048778
| 0x08048766 8b1c24 mov ebx, dword [esp]
\ 0x08048769 c3 ret
WOW, 好像引用的地方还不少, 作用似乎是把栈指针指向的值保存在ebx里然后返回,
值得一提的是, call xxx
指令相当于:
push $EIP + sizof instruction
jmp xxx
所以该函数作用就是把下一条指令的地址保存在ebx中. 从main函数来看,
调用的一些函数传参似乎就用了ebx来做计算, 这是什么骚操作?
网上google了一下得知, 这种模式似乎是因为编译时指定了-fPIC
参数,
即产生位置无关代码, 好处是这样程序可以加载到任意内存地址, 并且可以正常访问全局变量.
关于这种模式的介绍可以查看这个stackoverflow上的回答. 简而言之,
这样我们便将ebx作为PIC寄存器, 用以访问got.plt表. 以上面的main函数为例,
先把函数fcn.08048766
改名为pic
, 看到调用pic之后ebx应该等于0x080486fd,
然后加上0x18f7, 再减去0x178b, 最后的值作为第一个printf的参数:
[0x080486ee]> ps @ 0x080486fd+0x18f7-0x178b
IOLI Crackme Level 0x09
当然也可以通过r2分析的符号来计算:
[0x080486ee]> ps @ section_end..got - 0x178b
IOLI Crackme Level 0x09
虽然过程曲折了点, 但总算开了个头. 接下来就可以继续分析main函数了, 还是scanf输入一个
字符串, 然后调用sub.strlen_616
, 接受2个参数, 分别是用户输入input和环境变量envp:
[0x080486ee]> pdf @ sub.strlen_616
/ (fcn) sub.strlen_616 216
| sub.strlen_616 (int input, int envp);
| ; 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 input @ ebp+0x8
| ; arg int envp @ ebp+0xc
| ; var int local_4h @ esp+0x4
| ; var int local_8h_2 @ esp+0x8
| ; CALL XREF from 0x08048757 (main)
| 0x08048616 55 push ebp
| 0x08048617 89e5 mov ebp, esp
| 0x08048619 53 push ebx
| 0x0804861a 83ec24 sub esp, 0x24 ; '$'
| 0x0804861d e844010000 call pic
| 0x08048622 81c3d2190000 add ebx, 0x19d2
| 0x08048628 c745f4000000. mov dword [local_ch], 0
| 0x0804862f c745f0000000. mov dword [local_10h], 0
| ; JMP XREF from 0x08048693 (sub.strlen_616)
| .-> 0x08048636 8b4508 mov eax, dword [input] ; [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 ; [0x13:4]=-1 ; 19
| ,==< 0x08048644 734f jae 0x8048695
| |: 0x08048646 8b45f0 mov eax, dword [local_10h]
| |: 0x08048649 034508 add eax, dword [input]
| |: 0x0804864c 0fb600 movzx eax, byte [eax]
| |: 0x0804864f 8845ef mov byte [local_11h], al
| |: 0x08048652 8d45f8 lea eax, [local_8h]
| |: 0x08048655 89442408 mov dword [local_8h_2], eax
| |: 0x08048659 8d835ee8ffff lea eax, [ebx - 0x17a2]
| |: 0x0804865f 89442404 mov dword [local_4h], eax
| |: 0x08048663 8d45ef lea eax, [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, [local_ch]
| |: 0x08048674 0110 add dword [eax], edx
| |: 0x08048676 837df410 cmp dword [local_ch], 0x10 ; [0x10:4]=-1 ; 16
| ,===< 0x0804867a 7512 jne 0x804868e
| ||: 0x0804867c 8b450c mov eax, dword [envp] ; [0xc:4]=-1 ; 12
| ||: 0x0804867f 89442404 mov dword [local_4h], eax
| ||: 0x08048683 8b4508 mov eax, dword [input] ; [0x8:4]=-1 ; 8
| ||: 0x08048686 890424 mov dword [esp], eax
| ||: 0x08048689 e8fbfeffff call sub.sscanf_589 ; int sscanf(const char *s, const char *format, ...)
| ||: ; JMP XREF from 0x0804867a (sub.strlen_616)
| `---> 0x0804868e 8d45f0 lea eax, [local_10h]
| |: 0x08048691 ff00 inc dword [eax]
| |`=< 0x08048693 eba1 jmp 0x8048636
| | ; JMP XREF from 0x08048644 (sub.strlen_616)
| `--> 0x08048695 e8c3feffff call sub.printf_55d ; int printf(const char *format)
| 0x0804869a 8b450c mov eax, dword [envp] ; [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 sub.strncmp_4d4 ; int strncmp(const char *s1, const char *s2, size_t n)
| 0x080486ac 85c0 test eax, eax
| ,=< 0x080486ae 7438 je 0x80486e8
| | 0x080486b0 c745f0000000. mov dword [local_10h], 0
| | ; JMP XREF from 0x080486e6 (sub.strlen_616)
| .--> 0x080486b7 837df009 cmp dword [local_10h], 9 ; [0x9:4]=-1 ; 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, [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)
| ||:| ; JMP XREF from 0x080486c5 (sub.strlen_616)
| `----> 0x080486e1 8d45f0 lea eax, [local_10h]
| |:| 0x080486e4 ff00 inc dword [eax]
| |`==< 0x080486e6 ebcf jmp 0x80486b7
| | | ; JMP XREF from 0x080486ae (sub.strlen_616)
| | | ; JMP XREF from 0x080486bb (sub.strlen_616)
| `-`-> 0x080486e8 83c424 add esp, 0x24 ; '$'
| 0x080486eb 5b pop ebx
| 0x080486ec 5d pop ebp
\ 0x080486ed c3 ret
由于启用了位置无关代码, 一眼看不出来哪个分支是我们要去的, 所以还是按照顺序来,
首先计算出GOT(末尾)地址是0x08048622 + 0x19d2 = 0x8049ff4 (= section_end..got)
,
写伪代码:
void strlen_616(char *input, char *envp[]) {
int local_ch = 0;
int local_8h;
char c[2];
for(i=0; i<strlen(input); i++) {
c[0] = input[i];
c[1] = 0;
sscanf(c, "%d", &local_8h);
local_ch += local_8h;
if (local_ch == 0x10) {
sscanf_589(input, envp);
}
}
printf_55d();
// junk codes
}
printf_55d
的代码如下, 作用是打印密码错误信息并且调用exit退出, 所以其后的代码基本上
都是垃圾代码, 就不分析了.
:> pdf @ sub.printf_55d
/ (fcn) sub.printf_55d 44
| sub.printf_55d ();
| 0x0804855d 55 push ebp
| 0x0804855e 89e5 mov ebp, esp
| 0x08048560 53 push ebx
| 0x08048561 83ec04 sub esp, 4
| 0x08048564 e8fd010000 call pic
| 0x08048569 81c38b1a0000 add ebx, 0x1a8b
| 0x0804856f 8d8349e8ffff lea eax, [ebx - 0x17b7]
| 0x08048575 890424 mov dword [esp], eax
| 0x08048578 e863feffff call sym.imp.printf
| 0x0804857d c70424000000. mov dword [esp], 0
\ 0x08048584 e887feffff call sym.imp.exit
:> ps @ section_end..got - 0x17b7
Password Incorrect!
而循环里的逻辑和之前差不多, 也是检测每一位输入的累加和, 等于0x10时调用sscanf_589
:
:> pdf @ sub.sscanf_589
/ (fcn) sub.sscanf_589 141
| sub.sscanf_589 (int arg_8h, int arg_9h, int arg_ch);
| ; var int local_ch @ ebp-0xc
| ; var int local_8h @ ebp-0x8
| ; arg int arg_8h @ ebp+0x8
| ; arg int arg_9h @ ebp+0x9
| ; arg int arg_ch @ ebp+0xc
| 0x08048589 55 push ebp
| 0x0804858a 89e5 mov ebp, esp
| 0x0804858c 53 push ebx
| 0x0804858d 83ec14 sub esp, 0x14
| 0x08048590 e8d1010000 call pic
| 0x08048595 81c35f1a0000 add ebx, 0x1a5f
| 0x0804859b 8d45f8 lea eax, [local_8h]
| 0x0804859e 89442408 mov dword [esp + 8], eax
| 0x080485a2 8d835ee8ffff lea eax, [ebx - 0x17a2]
| 0x080485a8 89442404 mov dword [esp + 4], eax
| 0x080485ac 8b4508 mov eax, dword [arg_8h]
| 0x080485af 890424 mov dword [esp], eax
| 0x080485b2 e839feffff call sym.imp.sscanf
| 0x080485b7 8b450c mov eax, dword [arg_ch]
| 0x080485ba 89442404 mov dword [esp + 4], eax
| 0x080485be 8b45f8 mov eax, dword [local_8h]
| 0x080485c1 890424 mov dword [esp], eax
| 0x080485c4 e80bffffff call sub.strncmp_4d4
| 0x080485c9 85c0 test eax, eax
| ,=< 0x080485cb 7443 je 0x8048610
| | 0x080485cd c745f4000000. mov dword [local_ch], 0
| .--> 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, [ebx - 0x179f]
| |||:| 0x080485f5 890424 mov dword [esp], eax
| |||:| 0x080485f8 e8e3fdffff call sym.imp.printf
| `-----> 0x080485fd c70424000000. mov dword [esp], 0
| ||:| 0x08048604 e807feffff call sym.imp.exit
| `----> 0x08048609 8d45f4 lea eax, [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
同样, arg_9h
也是不存在的, 应该先去掉, 然后再写下伪代码:
void sscanf_589(char *input, char *envp[]) {
int local_8h;
int local_ch;
sscanf(input, "%d", &local_8h);
if (0 == strncmp_4d4(local_8h, envp)){
return;
}
for (local_ch = 0; local_ch < 9; local_ch++) {
if (local_8h & 1 == 0) {
if((int)*0x08049ff0 == 1) {
printf("Password OK!\n");
}
exit(0);
}
}
}
最后看下strncmp_4d4
:
:> pdf @ sub.strncmp_4d4
/ (fcn) sub.strncmp_4d4 137
| sub.strncmp_4d4 (int arg_ch);
| ; var int local_ch @ ebp-0xc
| ; var int local_8h @ ebp-0x8
| ; arg int arg_ch @ ebp+0xc
| 0x080484d4 55 push ebp
| 0x080484d5 89e5 mov ebp, esp
| 0x080484d7 53 push ebx
| 0x080484d8 83ec14 sub esp, 0x14
| 0x080484db e886020000 call pic
| 0x080484e0 81c3141b0000 add ebx, 0x1b14
| 0x080484e6 c745f8000000. mov dword [local_8h], 0
| .-> 0x080484ed 8b45f8 mov eax, dword [local_8h]
| : 0x080484f0 8d1485000000. lea edx, [eax*4]
| : 0x080484f7 8b450c mov eax, dword [arg_ch]
| : 0x080484fa 833c0200 cmp dword [edx + eax], 0
| ,==< 0x080484fe 7448 je 0x8048548
| |: 0x08048500 8b45f8 mov eax, dword [local_8h]
| |: 0x08048503 8d1485000000. lea edx, [eax*4]
| |: 0x0804850a 8b4d0c mov ecx, dword [arg_ch]
| |: 0x0804850d 8d45f8 lea eax, [local_8h]
| |: 0x08048510 ff00 inc dword [eax]
| |: 0x08048512 8d8344e8ffff lea eax, [ebx - 0x17bc]
| |: 0x08048518 c74424080300. mov dword [esp + 8], 3
| |: 0x08048520 89442404 mov dword [esp + 4], eax
| |: 0x08048524 8b040a mov eax, dword [edx + ecx]
| |: 0x08048527 890424 mov dword [esp], eax
| |: 0x0804852a e8d1feffff call sym.imp.strncmp
| |: 0x0804852f 85c0 test eax, eax
| |`=< 0x08048531 75ba jne 0x80484ed
| | 0x08048533 8b83fcffffff mov eax, dword [ebx - 4]
| | 0x08048539 c70001000000 mov dword [eax], 1
| | 0x0804853f c745f4010000. mov dword [local_ch], 1
| |,=< 0x08048546 eb0c jmp 0x8048554
| `--> 0x08048548 c70424ffffff. mov dword [esp], 0xffffffff
| | 0x0804854f e8bcfeffff call sym.imp.exit
| `-> 0x08048554 8b45f4 mov eax, dword [local_ch]
| 0x08048557 83c414 add esp, 0x14
| 0x0804855a 5b pop ebx
| 0x0804855b 5d pop ebp
\ 0x0804855c c3 ret
:> ps @ section_end..got - 0x17bc
LOLO
伪代码:
int strncmp_4d4(char *input, char *envp[]) {
int i; //local_8h
int local_ch;
for (i=0;;i++) {
if (envp[i * 4] == 0) {
exit(0);
}
if (0 == strncmp(3, "LOLO", envp[i * 4])) {
*0x08049ff0 = 1;
local_ch = 1;
break;
}
}
return local_ch;
}
还是检测和之前类似, 所以综上所述, 要想密码正确, 则需满足以下输入条件:
- 输入的前X位(数字)之和为16, X为任意值.
- 输入的环境变量中要有一个key的前3字节为LOL.
- 输入的必须是个合法的数字而且为偶数.
验证:
$ LOL= ./crackme0x09
IOLI Crackme Level 0x09
Password: 88
Password OK!
$ LOLTHATSFUN=1 ./crackme0x09
IOLI Crackme Level 0x09
Password: 19621234
Password OK!
大功告成! 其实这题除了PIC之外和前一题依旧是类似的.
后记
由于这篇writeup写的时间跨度比较长, 所以前面逆了一个到后面不知不觉又当成是新的程序来分析, 其实他们之间有很多相似的地方. 不过从练习的角度来说这也算好事, 汇编代码看多了之后发现一些 原来要花很多时间分析的地方再遇到类似的模式时就能很快反应过来, 也算是熟能生巧吧.
这两篇也算是我使用radare2做静态分析的一个笔记, 因为程序都比较简单所以就没有进行调试. 一般来说遇到现实生活中的样本(比如病毒或者木马), 进行分析时还需要进行动态调试分析才能对其 功能有个整体了解, 此外也衍生出了一些反分析(加花, 加壳)以及反调试手段. 所以做安全还是 要不断学习和挑战, 切忌在自己的舒适区里止步不前啊.