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), 如下所示:

r2-view

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;
}

还是检测和之前类似, 所以综上所述, 要想密码正确, 则需满足以下输入条件:

  1. 输入的前X位(数字)之和为16, X为任意值.
  2. 输入的环境变量中要有一个key的前3字节为LOL.
  3. 输入的必须是个合法的数字而且为偶数.

验证:

$ LOL= ./crackme0x09 
IOLI Crackme Level 0x09
Password: 88
Password OK!
$ LOLTHATSFUN=1 ./crackme0x09 
IOLI Crackme Level 0x09
Password: 19621234
Password OK!

大功告成! 其实这题除了PIC之外和前一题依旧是类似的.

后记

由于这篇writeup写的时间跨度比较长, 所以前面逆了一个到后面不知不觉又当成是新的程序来分析, 其实他们之间有很多相似的地方. 不过从练习的角度来说这也算好事, 汇编代码看多了之后发现一些 原来要花很多时间分析的地方再遇到类似的模式时就能很快反应过来, 也算是熟能生巧吧.

这两篇也算是我使用radare2做静态分析的一个笔记, 因为程序都比较简单所以就没有进行调试. 一般来说遇到现实生活中的样本(比如病毒或者木马), 进行分析时还需要进行动态调试分析才能对其 功能有个整体了解, 此外也衍生出了一些反分析(加花, 加壳)以及反调试手段. 所以做安全还是 要不断学习和挑战, 切忌在自己的舒适区里止步不前啊.