IOLI-crackme0x01-0x05 writeup

上一篇开了个头, 使用Radare2并用3中方法来解决crackme0x00, 由于是第一篇, 所以解释得事无巨细, 今天就稍微加快点步伐, 分析一下另外几个crackme.

如果你忘记了crackme的来源, 那就再告诉你一遍, 它们都是来自IOLI-crackme.

crackme0x01

直接用radare2打开分析:

[0x080483e4]> pdf @ main
            ;-- main:
/ (fcn) main 113
|   main ();
|           ; var int pInput @ ebp-0x4
|              ; DATA XREF from 0x08048347 (entry0)
|           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"
|           0x08048407      e810ffffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804840c      c70424418504.  mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: "
|           0x08048413      e804ffffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048418      8d45fc         lea eax, [pInput]
|           0x0804841b      89442404       mov dword [esp + 4], eax
|           0x0804841f      c704244c8504.  mov dword [esp], 0x804854c  ; [0x804854c:4]=0x49006425
|           0x08048426      e8e1feffff     call sym.imp.scanf          ; int scanf(const char *format)
|           0x0804842b      817dfc9a1400.  cmp dword [pInput], 0x149a  ; [0x149a:4]=-1
|       ,=< 0x08048432      740e           je 0x8048442
[0x080483e4]> ps @ 0x804854c
%d

还是scanf获取用户输入, 不过这次是%d即用户输入一个整数, 然后和0x149a比较, 使用rax2转换数据格式:

$ rax2 0x149a
5274

所以:

$ ./crackme0x01 
IOLI Crackme Level 0x01
Password: 5274
Password OK :)

密码正确! 和crackme0x00差不多, 逻辑比较简单.

crackme0x02

先运行一下, 发现和之前一样还是要输入密码. radare2打开:

[0x080483e4]> pdf @ main
            ;-- main:
/ (fcn) main 144
|   main ();
|           ; var int local_ch @ ebp-0xc
|           ; var int local_8h @ ebp-0x8
|           ; var int local_4h @ ebp-0x4
|           ; var int local_4h_2 @ esp+0x4
|              ; DATA XREF from 0x08048347 (entry0)
|           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"
|           0x08048407      e810ffffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804840c      c70424618504.  mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: "
|           0x08048413      e804ffffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048418      8d45fc         lea eax, [local_4h]
|           0x0804841b      89442404       mov dword [local_4h_2], eax
|           0x0804841f      c704246c8504.  mov dword [esp], 0x804856c  ; [0x804856c:4]=0x50006425
|           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, [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
[0x080483e4]> ps @ 0x804856c
%d

这个就比之前复杂一点, main函数有三个本地变量, local_ch, local_8hlocal_4h, 但似乎没有初始值. 由0x08048418~0x08048426这几句可以发现local_4h是用户的输入, 且类型为整数. 分析一下用户输入后的逻辑, 先给两个本地变量分别赋值为0x5a和0x1ec, 然后进行数学运算, 先改几个名字方便阅读:

[0x080483e4]> afv-local_4h_2
[0x080483e4]> afvn local_4h input
[0x080483e4]> afvn local_8h a
[0x080483e4]> afvn local_ch b
[0x080483e4]> pd 10 @ 0x0804842b
0x0804842b      c745f85a0000.  mov dword [a], 0x5a         ; 'Z' ; 90
0x08048432      c745f4ec0100.  mov dword [b], 0x1ec        ; 492
0x08048439      8b55f4         mov edx, dword [b]
0x0804843c      8d45f8         lea eax, [a]
0x0804843f      0110           add dword [eax], edx
0x08048441      8b45f8         mov eax, dword [a]
0x08048444      0faf45f8       imul eax, dword [a]
0x08048448      8945f4         mov dword [b], eax
0x0804844b      8b45fc         mov eax, dword [input]
0x0804844e      3b45f4         cmp eax, dword [b]

重新打印scanf之后的10条汇编, 转换成伪代码大意是:

int a = 0x5a
int b = 0x1ec
a = b + a
a = a * a
b = a
if input == b

所以最后和input比较的是(a+b)*(a+b)=582*582=338724, 验证一下:

$ ./crackme0x02 
IOLI Crackme Level 0x02
Password: 338724
Password OK :)

抬走, 下一个.

crackme0x03

流程和之前一样:

$ ./crackme0x03 
IOLI Crackme Level 0x03
Password: 12345
Invalid Password!

不过这次似乎里面的字符串被混淆了, 没有找到Invalid Password出现的地方:

$ rabin2 -z ./crackme0x03 
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:

radare2打开并分析main函数, 发现用户输入后调用了test函数, 如下:

...忽略
|           0x080484c0      c70424298604.  mov dword [esp], str.Password: ; [0x8048629:4]=0x73736150 ; "Password: "
|           0x080484c7      e884feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x080484cc      8d45fc         lea eax, [local_4h]
|           0x080484cf      89442404       mov dword [local_4h_2], eax
|           0x080484d3      c70424348604.  mov dword [esp], 0x8048634  ; [0x8048634:4]=0x6425
|           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, [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

main函数内同样有三个本地变量, 面对这种多层调用的目标时候, 可以选择深度优先或者广度优先分析, 这里选择深度优先, 即先分析sym.test函数:

[0x08048498]> pdf @ sym.test
/ (fcn) sym.test 42
|   sym.test (int arg_8h, int arg_ch);
|           ; arg int arg_8h @ ebp+0x8
|           ; arg int arg_ch @ ebp+0xc
|              ; CALL XREF from 0x0804850c (sym.main)
|           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
|      |`-> 0x0804848a      c70424fe8504.  mov dword [esp], str.Sdvvzrug_RN ; [0x80485fe:4]=0x76766453 ; "Sdvvzrug#RN$$$#=,"
|      |    0x08048491      e87effffff     call sym.shift
|      |       ; JMP XREF from 0x08048488 (sym.test)
|      `--> 0x08048496      c9             leave
\           0x08048497      c3             ret

可以看到该函数接受2个参数, 值得一提的是根据(x86)cdecl调用约定, 函数参数通过栈传递, 并且顺序为从右到左. 可以看到test函数中调用了shift函数, 接受1个字符串参数, 估计是解密字符串相关的函数, 先看看它:

[0x08048498]> pdf @ sym.shift
/ (fcn) sym.shift 90
|   sym.shift (int arg_8h);
|           ; var int local_7ch @ ebp-0x7c
|           ; var int local_78h @ ebp-0x78
|           ; arg int arg_8h @ ebp+0x8
|           ; var int local_4h @ esp+0x4
|              ; CALL XREF from 0x08048491 (sym.test)
|              ; CALL XREF from 0x08048483 (sym.test)
|           0x08048414      55             push ebp
|           0x08048415      89e5           mov ebp, esp
|           0x08048417      81ec98000000   sub esp, 0x98
|           0x0804841d      c74584000000.  mov dword [local_7ch], 0
|              ; JMP XREF from 0x0804844e (sym.shift)
|       .-> 0x08048424      8b4508         mov eax, dword [arg_8h]     ; [0x8:4]=-1 ; 8
|       :   0x08048427      890424         mov dword [esp], eax
|       :   0x0804842a      e811ffffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|       :   0x0804842f      394584         cmp dword [local_7ch], eax  ; [0x13:4]=-1 ; 19
|      ,==< 0x08048432      731c           jae 0x8048450
|      |:   0x08048434      8d4588         lea eax, [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 [arg_8h]
|      |:   0x08048442      0fb600         movzx eax, byte [eax]
|      |:   0x08048445      2c03           sub al, 3
|      |:   0x08048447      8802           mov byte [edx], al
|      |:   0x08048449      8d4584         lea eax, [local_7ch]
|      |:   0x0804844c      ff00           inc dword [eax]
|      |`=< 0x0804844e      ebd4           jmp 0x8048424
|      `--> 0x08048450      8d4588         lea eax, [local_78h]
|           0x08048453      034584         add eax, dword [local_7ch]
|           0x08048456      c60000         mov byte [eax], 0
|           0x08048459      8d4588         lea eax, [local_78h]
|           0x0804845c      89442404       mov dword [local_4h], eax
|           0x08048460      c70424e88504.  mov dword [esp], 0x80485e8  ; [0x80485e8:4]=0xa7325
|           0x08048467      e8e4feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804846c      c9             leave
\           0x0804846d      c3             ret

可以看到shift的作用是接受一个字符串->处理->printf, 其实我们可以不用分析shift函数的逻辑, 因为开启gdb一调就知道在test函数中哪个分支是"Password OK"了, 甚至都不用调试, 因为一共就2个分支, 非此即彼, 但秉承着知易行难的原则, 还是分析了一遍, shift函数伪代码如下:

void shift(char *src) {
    int i;
    char dst[N];
    for (i = 0; i < strlen(src); i++) {
        dst[i] = src[i] - 3;
    }
    dst[i] = 0;
    printf("%s", dst);
}

写个python脚本验证下之前rabin2发现.rodata段的两个字符串解密:

# /usr/bin/env python2
# shift.py
def shift(src):
    dst = []
    for i in src:
        dst.append(chr(ord(i)-3))
    print(''.join(dst))
shift('Lqydolg#Sdvvzrug$')
shift('Sdvvzrug#RN$$$#=,')

运行:

$ python shift.py
Invalid Password!
Password OK!!! :)

OK, 现在回到test函数, 这个函数比较简单, 接受2个参数, 如果第二个参数等于第一个参数, 则进入我们想要的分支. 再回到main函数, scanf接受一个整数input, 然后进行数学运算, 如下(重命名了一些变量名称):

0x080484df      c745f85a0000.  mov dword [a], 0x5a         ; 'Z' ; 90
0x080484e6      c745f4ec0100.  mov dword [b], 0x1ec        ; 492
0x080484ed      8b55f4         mov edx, dword [b]
0x080484f0      8d45f8         lea eax, [a]
0x080484f3      0110           add dword [eax], edx
0x080484f5      8b45f8         mov eax, dword [a]
0x080484f8      0faf45f8       imul eax, dword [a]
0x080484fc      8945f4         mov dword [b], eax
0x080484ff      8b45f4         mov eax, dword [b]
0x08048502      89442404       mov dword [esp + 4], eax
0x08048506      8b45fc         mov eax, dword [input]
0x08048509      890424         mov dword [esp], eax
0x0804850c      e85dffffff     call sym.test

转化为人类语言就是:

int a = 0x5a, b = 0x1ec;
a = a + b;
b = a * a;
test(input, b)

好吧, 结果还是要用输入和(0x5a*0x1ec)^2=338724比较, 若相等则通过, 验证下:

$ ./crackme0x03 
IOLI Crackme Level 0x03
Password: 338724
Password OK!!! :)

密码和上一题一样, 囧~

crackme0x04

老样子, 直接跳转到main函数然后查看汇编:

[0x08048509]> pdf @ main
...
0x08048528      c704245e8604.  mov dword [esp], str.IOLI_Crackme_Level_0x04 ; [0x804865e:4]=0x494c4f49 ; "IOLI Crackme Level 0x04\n"
0x0804852f      e860feffff     call sym.imp.printf         ; int printf(const char *format)
0x08048534      c70424778604.  mov dword [esp], str.Password: ; [0x8048677:4]=0x73736150 ; "Password: "
0x0804853b      e854feffff     call sym.imp.printf         ; int printf(const char *format)
0x08048540      8d4588         lea eax, [local_78h]
0x08048543      89442404       mov dword [local_4h], eax
0x08048547      c70424828604.  mov dword [esp], 0x8048682  ; [0x8048682:4]=0x7325
0x0804854e      e821feffff     call sym.imp.scanf          ; int scanf(const char *format)
0x08048553      8d4588         lea eax, [local_78h]
0x08048556      890424         mov dword [esp], eax
0x08048559      e826ffffff     call sym.check
...
[0x08048509]> ps @ 0x8048682
%s

这回main函数挺简单, 主要是scanf输入一个字符串, 然后调用check函数, 汇编如下:

[0x080484fb]> pdf @ sym.check
/ (fcn) sym.check 133
|   sym.check (char *input);
|           ; 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 char * input @ ebp+0x8
|              ; CALL XREF from 0x08048559 (sym.main)
|           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
|              ; JMP XREF from 0x080484f9 (sym.check)
|       .-> 0x08048498      8b4508         mov eax, dword [input]      ; [0x8:4]=-1 ; 8
|       :   0x0804849b      890424         mov dword [esp], eax
|       :   0x0804849e      e8e1feffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|       :   0x080484a3      3945f4         cmp dword [local_ch], eax   ; [0x13:4]=-1 ; 19
|      ,==< 0x080484a6      7353           jae 0x80484fb
|      |:   0x080484a8      8b45f4         mov eax, dword [local_ch]
|      |:   0x080484ab      034508         add eax, dword [input]
|      |:   0x080484ae      0fb600         movzx eax, byte [eax]
|      |:   0x080484b1      8845f3         mov byte [local_dh], al
|      |:   0x080484b4      8d45fc         lea eax, [local_4h]
|      |:   0x080484b7      89442408       mov dword [esp + 8], eax
|      |:   0x080484bb      c74424043886.  mov dword [esp + 4], 0x8048638 ; [0x8048638:4]=0x50006425
|      |:   0x080484c3      8d45f3         lea eax, [local_dh]
|      |:   0x080484c6      890424         mov dword [esp], eax
|      |:   0x080484c9      e8d6feffff     call sym.imp.sscanf         ; int sscanf(const char *s, const char *format,   ...)
|      |:   0x080484ce      8b55fc         mov edx, dword [local_4h]
|      |:   0x080484d1      8d45f8         lea eax, [local_8h]
|      |:   0x080484d4      0110           add dword [eax], edx
|      |:   0x080484d6      837df80f       cmp dword [local_8h], 0xf   ; [0xf:4]=-1 ; 15
|     ,===< 0x080484da      7518           jne 0x80484f4
|     ||:   0x080484dc      c704243b8604.  mov dword [esp], str.Password_OK ; [0x804863b:4]=0x73736150 ; "Password OK!\n"
|     ||:   0x080484e3      e8acfeffff     call sym.imp.printf         ; int printf(const char *format)
|     ||:   0x080484e8      c70424000000.  mov dword [esp], 0
|     ||:   0x080484ef      e8c0feffff     call sym.imp.exit           ; void exit(int status)
|     ||:      ; JMP XREF from 0x080484da (sym.check)
|     `---> 0x080484f4      8d45f4         lea eax, [local_ch]
|      |:   0x080484f7      ff00           inc dword [eax]
|      |`=< 0x080484f9      eb9d           jmp 0x8048498
|      |       ; JMP XREF from 0x080484a6 (sym.check)
|      `--> 0x080484fb      c70424498604.  mov dword [esp], str.Password_Incorrect ; [0x8048649:4]=0x73736150 ; "Password Incorrect!\n"
|           0x08048502      e88dfeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048507      c9             leave
\           0x08048508      c3             ret

这个函数比之前的复杂一点, 所以我们用视图模式先有个大局观:

[0x08048484]> VV @ sym.check
[0x08048484]> VV @ sym.check (nodes 6 edges 6 zoom 100%) BB-SUMM mouse:canvas-y mov-speed:5                 


                                       .--------------------.                              
                                       |  0x8048484 ;[ga]   |                              
                                       `--------------------'                              
                                           |                                               
                                        .--'                                               
 .--------------------------------------.                                                  
 |                                      |                                                  
 |                                      |                                                  
 |                                  .---------------------------.                          
 |                                  |  0x8048498 ;[gd]          |                          
 |                                  | 0x0804849e sym.imp.strlen |                          
 |                                  `---------------------------'                          
 |                                          | |                                            
 |                                          | '---------.                                  
 |                        .-----------------'           |                                  
 |                        |                             |                                  
 |                        |                             |                                  
 |                .---------------------------.   .-----------------------------------.    
 |                |  0x80484a8 ;[gg]          |   | [0x80484fb] ;[gc]                 |    
 |                | 0x080484c9 sym.imp.sscanf |   | 0x080484fb str.Password_Incorrect |    
 |                `---------------------------'   | 0x08048502 sym.imp.printf         |    
 |                        | |                     `-----------------------------------'    
 |                        | |                                                              
 |                        | '-------------.                                                
 |        .---------------'               |                                                
 |        |                               |                                                
 |        |                               |                                                
 |.----------------------------.    .--------------------.                                 
 ||  0x80484dc ;[gj]           |    |  0x80484f4 ;[gf]   |                                 
 || 0x080484dc str.Password_OK |    `--------------------'                                 
 || 0x080484e3 sym.imp.printf  |        |                                                  
 || 0x080484ef sym.imp.exit    |        |                                                  
 |`----------------------------'        |                                                  
 |                                      |                                                  
 `--------------------------------------'              

radare2在视图模式下可以通过p/P切换视图, 通过O切换asm的类型. 直接按?键可以查看快捷键的帮助.

让我们F5一下, 噢忘了没有F5, 那就人肉反编译一下, check函数有4个本地变量, 但还不知道他们的作用, 有一个参数我已经改成了char *input, 先来个伪代码:

local_8h = 0, local_ch = 0;
BEGIN:
if (local_ch >= strlen(input)) {
    printf("Password Incorrect!\n");
    return;
}
eax = input + local_ch;
eax = (int)*eax;
(char*)&local_dh[0] = eax;
sscanf(local_dh, "%d", local_4h);
local_8h = local_4h + local_8h;
if (local_8h != 0xf) {
    local_ch ++;
    goto BEGIN;
}
printf("Password OK!\n");
return;

这里要注意mov byte [local_dh], al的意思是把eax中的最低字节移动到local_dh 的第一字节. 也就是说, check对输入的字符串的每个字节都进行sscanf扫描, 如果是个整数 就累加local_8h里, 只要其等于0xf(=15), 则通过, 所以密码可以有多个, 最简单就是15个1:

$ ./crackme0x04 
IOLI Crackme Level 0x04
Password: 111111111111111
Password OK!

只要满足条件都可以, 比如最短的9+6=15:

$ ./crackme0x04 
IOLI Crackme Level 0x04
Password: 96
Password OK!

crackme0x05

这题和0x04一样, 都是用户输入一个字符串, 然后调用check, 但是check函数有所不同:

[0x080484c8]> VV @ sym.check (nodes 7 edges 8 zoom 100%) BB-SUMM mouse:canvas-y mov-speed:5                 

                       .--------------------.                                                            
                       |  0x80484c8 ;[ga]   |                                                            
                       `--------------------'                                                            
                           |                                                                             
                        .--'                                                                             
 .----------------------.                                                                                
 |                      |                                                                                
 |                      |                                                                                
 |                  .---------------------------.                                                        
 |                  |  0x80484dc ;[gd]          |                                                        
 |                  | 0x080484e2 sym.imp.strlen |                                                        
 |                  `---------------------------'                                                        
 |                          | |                                                                          
 |                          | '---------.                                                                
 |        .-----------------'           |                                                                
 |        |                             |                                                                
 |        |                             |                                                                
 |.---------------------------.   .-----------------------------------.                                  
 ||  0x80484ec ;[gg]          |   | [0x8048532] ;[gc]                 |                                  
 || 0x0804850d sym.imp.sscanf |   | 0x08048532 str.Password_Incorrect |                                  
 |`---------------------------'   | 0x08048539 sym.imp.printf         |                                  
 |        | |                     `-----------------------------------'                                  
 |        | |                                                                                            
 |        | '---------------------------------------------------------.                                  
 |        '-.                                                         |                                  
 |          |                                                         |                                  
 |          |                                                         |                                  
 |  .-----------------------.                                         |                                  
 |  |  0x8048520 ;[gi]      |                                         |                                  
 |  | 0x08048526 sym.parell |                                         |                                  
 |  `-----------------------'                                         |                                  
 |      |                                                             |                                  
 |      '---------------------------.                                 |                                  
 |                                  | .-------------------------------'                                  
 |                                  | |                                                                  
 |                                  | |
 |                              .--------------------.                                                   
 |                              |  0x804852b ;[gf]   |                                                   
 |                              `--------------------'                                                   
 |                                  |                                                                    
 `----------------------------------'                                                                    

我们待会再来看它, check函数里还调用了parell函数, 其流程图如下:

[0x08048484]> VV @ sym.parell (nodes 3 edges 2 zoom 100%) BB-NORM mouse:canvas-y mov-speed:5                

                .---------------------------------------------.                                             
                | [0x8048484] ;[gc]                           |                                             
                | (fcn) sym.parell 68                         |                                             
                |   sym.parell (int arg_8h);                  |
                | ; var int local_4h @ ebp-0x4                |                                             
                | ; arg int arg_8h @ ebp+0x8                  |                                             
                |    ; CALL XREF from 0x08048526 (sym.check)  |                                             
                | push ebp                                    |                                             
                | mov ebp, esp                                |                                             
                | sub esp, 0x18                               |                                             
                | lea eax, [local_4h]                         |                                             
                | mov dword [esp + 8], eax                    |                                             
                | mov dword [esp + 4], 0x8048668              |                                             
                | mov eax, dword [arg_8h]                     |                                             
                | mov dword [esp], eax                        |                                             
                | call sym.imp.sscanf;[ga]                    |                                             
                | mov eax, dword [local_4h]                   |                                             
                | and eax, 1                                  |                                             
                | test eax, eax                               |                                             
                | jne 0x80484c6;[gb]                          |                                             
                `---------------------------------------------'                                             
                        | |                                                                                 
                        | '-------------------------.                                                       
          .-------------'                           |                                                       
          |                                         |                                                       
  .--------------------------------------.    .--------------------.                                        
  |  0x80484ae ;[gf]                     |    |  0x80484c6 ;[gb]   |                                        
  |   ; [0x804866b:4]=0x73736150         |    | leave              |                                        
  |   ; "Password OK!\n"                 |    | ret                |                                        
  | mov dword [esp], str.Password_OK     |    `--------------------'                                        
  | call sym.imp.printf;[gd]             |                                                                  
  | mov dword [esp], 0                   |                                                                  
  | call sym.imp.exit;[ge]               |                                                                  
  `--------------------------------------'                                      

其接受一个参数, 并且经过一顿操作后选择静默返回或者进入正确分支并退出程序. 试着写下伪代码:

void parrel(arg) {
    int local_4h;
    sscanf(arg, "%d", &local_4h);
    local_4h &= 1; // 除了最后一位全部清0
    if (local_4h != 0) {
        return;
    }
    printf("Password_OK\n");
    exit(0);
}

可以猜测arg应该是char *类型, 该函数意思是将输入转化为整数, 如果结果的最低有效位为1则通过. 现在可以回到check函数了. 该函数有4个本地变量, 姑且先将其命名为a,b,c,d:

/ (fcn) sym.check 120
|   sym.check (int input);
|           ; var int a @ ebp-0xd
|           ; var int b @ ebp-0xc
|           ; var int c @ ebp-0x8
|           ; var int d @ ebp-0x4
|           ; arg int input @ ebp+0x8
|              ; CALL XREF from 0x08048590 (sym.main)
|           0x080484c8      55             push ebp
|           0x080484c9      89e5           mov ebp, esp
|           0x080484cb      83ec28         sub esp, 0x28               ; '('
|           0x080484ce      c745f8000000.  mov dword [c], 0
|           0x080484d5      c745f4000000.  mov dword [b], 0
|              ; JMP XREF from 0x08048530 (sym.check)
|       .-> 0x080484dc      8b4508         mov eax, dword [input]      ; [0x8:4]=-1 ; 8
|       :   0x080484df      890424         mov dword [esp], eax
|       :   0x080484e2      e89dfeffff     call sym.imp.strlen         ; size_t strlen(const char *s)
|       :   0x080484e7      3945f4         cmp dword [b], eax          ; [0x13:4]=-1 ; 19
|      ,==< 0x080484ea      7346           jae 0x8048532
|      |:   0x080484ec      8b45f4         mov eax, dword [b]
|      |:   0x080484ef      034508         add eax, dword [input]
|      |:   0x080484f2      0fb600         movzx eax, byte [eax]
|      |:   0x080484f5      8845f3         mov byte [a], al
|      |:   0x080484f8      8d45fc         lea eax, [d]
|      |:   0x080484fb      89442408       mov dword [esp + 8], eax
|      |:   0x080484ff      c74424046886.  mov dword [esp + 4], 0x8048668 ; [0x8048668:4]=0x50006425
|      |:   0x08048507      8d45f3         lea eax, [a]
|      |:   0x0804850a      890424         mov dword [esp], eax
|      |:   0x0804850d      e892feffff     call sym.imp.sscanf         ; int sscanf(const char *s, const char *format,   ...)
|      |:   0x08048512      8b55fc         mov edx, dword [d]
|      |:   0x08048515      8d45f8         lea eax, [c]
|      |:   0x08048518      0110           add dword [eax], edx
|      |:   0x0804851a      837df810       cmp dword [c], 0x10         ; [0x10:4]=-1 ; 16
|     ,===< 0x0804851e      750b           jne 0x804852b
|     ||:   0x08048520      8b4508         mov eax, dword [input]      ; [0x8:4]=-1 ; 8
|     ||:   0x08048523      890424         mov dword [esp], eax
|     ||:   0x08048526      e859ffffff     call sym.parell
|     `---> 0x0804852b      8d45f4         lea eax, [b]
|      |:   0x0804852e      ff00           inc dword [eax]
|      |`=< 0x08048530      ebaa           jmp 0x80484dc
|      `--> 0x08048532      c70424798604.  mov dword [esp], str.Password_Incorrect ; [0x8048679:4]=0x73736150 ; "Password Incorrect!\n"
|           0x08048539      e856feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804853e      c9             leave
\           0x0804853f      c3             ret

看到有个反向的跳转, 所以b应该是个循环变量, 重命名为i, 写下伪代码:

int c = 0;
int i = 0;
int d;
char a[2];
while(1) {
    if (i >= strlen(input)) {
        printf("Password Incorrect!\n");
        return;
    }
    (char*)a[0] = input[i];
    (char*)a[1] = 0;
    sscanf(a,"%d",&d);
    c += d;
    if (c==0x10) {
        parell(input)
    }
    i++;
    continue;
}

呃…写得有点渣, 不过能看明白逻辑就行了, 意思就是将输入的每个字符转为整数并累加, 如果累加的结果等于16(0x10)则调用parell函数, 前面分析了parrel的作用是将整个字符串 转换为整数, 并判断其最低有效位是否是0(即该数字是否为偶数), 是偶数则通过. 所以我们要输入的密码应该是个偶数, 而且前X位加起来是16就可以了:

$ ./crackme0x05
IOLI Crackme Level 0x05
Password: 88
Password OK!
$ ./crackme0x05
IOLI Crackme Level 0x05
Password: 88666
Password OK!

完美解决!

后记

说实话我一开始对汇编还不是很熟悉, 但动手写了几个writeup之后也逐渐有了点感觉. 对于不熟悉的指令, 比如movzx等可以查看X86的手册, 比如这里:Opcode of programming language, 而对于不熟悉的语法, 比如Size Directives或者Calling Conventions, 可以参考x86 Assembly Guide 以及维基百科. 总之, 熟能生巧, 汇编也不是那么可怕嘛!