CS:APP二进制炸弹phase6

来源:互联网 发布:南风知我意2百度云 编辑:程序博客网 时间:2024/06/05 10:09

写在前面

哇哦,终于到了最后一个阶段了,估计会比较难!let's go!!!


分析

先找到调用函数phase_6的地方如下:


反汇编函数phase_6如下:

(gdb) disassemble /m phase_6Dump of assembler code for function phase_6:   0x00000000004010f4 <+0>:     push   %r14   0x00000000004010f6 <+2>:     push   %r13   0x00000000004010f8 <+4>:     push   %r12   0x00000000004010fa <+6>:     push   %rbp   0x00000000004010fb <+7>:     push   %rbx   0x00000000004010fc <+8>:     sub    $0x50,%rsp   0x0000000000401100 <+12>:    mov    %rsp,%r13   0x0000000000401103 <+15>:    mov    %rsp,%rsi   0x0000000000401106 <+18>:    callq  0x40145c <read_six_numbers>   0x000000000040110b <+23>:    mov    %rsp,%r14   0x000000000040110e <+26>:    mov    $0x0,%r12d   0x0000000000401114 <+32>:    mov    %r13,%rbp   0x0000000000401117 <+35>:    mov    0x0(%r13),%eax   0x000000000040111b <+39>:    sub    $0x1,%eax   0x000000000040111e <+42>:    cmp    $0x5,%eax   0x0000000000401121 <+45>:    jbe    0x401128 <phase_6+52>   0x0000000000401123 <+47>:    callq  0x40143a <explode_bomb>   0x0000000000401128 <+52>:    add    $0x1,%r12d   0x000000000040112c <+56>:    cmp    $0x6,%r12d   0x0000000000401130 <+60>:    je     0x401153 <phase_6+95>   0x0000000000401132 <+62>:    mov    %r12d,%ebx   0x0000000000401135 <+65>:    movslq %ebx,%rax   0x0000000000401138 <+68>:    mov    (%rsp,%rax,4),%eax   0x000000000040113b <+71>:    cmp    %eax,0x0(%rbp)   0x000000000040113e <+74>:    jne    0x401145 <phase_6+81>   0x0000000000401140 <+76>:    callq  0x40143a <explode_bomb>   0x0000000000401145 <+81>:    add    $0x1,%ebx   0x0000000000401148 <+84>:    cmp    $0x5,%ebx   0x000000000040114b <+87>:    jle    0x401135 <phase_6+65>   0x000000000040114d <+89>:    add    $0x4,%r13   0x0000000000401151 <+93>:    jmp    0x401114 <phase_6+32>   0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi   0x0000000000401158 <+100>:   mov    %r14,%rax   0x000000000040115b <+103>:   mov    $0x7,%ecx   0x0000000000401160 <+108>:   mov    %ecx,%edx   0x0000000000401162 <+110>:   sub    (%rax),%edx   0x0000000000401164 <+112>:   mov    %edx,(%rax)   0x0000000000401166 <+114>:   add    $0x4,%rax   0x000000000040116a <+118>:   cmp    %rsi,%rax   0x000000000040116d <+121>:   jne    0x401160 <phase_6+108>   0x000000000040116f <+123>:   mov    $0x0,%esi   0x0000000000401174 <+128>:   jmp    0x401197 <phase_6+163>   0x0000000000401176 <+130>:   mov    0x8(%rdx),%rdx   0x000000000040117a <+134>:   add    $0x1,%eax   0x000000000040117d <+137>:   cmp    %ecx,%eax   0x000000000040117f <+139>:   jne    0x401176 <phase_6+130>   0x0000000000401181 <+141>:   jmp    0x401188 <phase_6+148>   0x0000000000401183 <+143>:   mov    $0x6032d0,%edx   0x0000000000401188 <+148>:   mov    %rdx,0x20(%rsp,%rsi,2)   0x000000000040118d <+153>:   add    $0x4,%rsi   0x0000000000401191 <+157>:   cmp    $0x18,%rsi   0x0000000000401195 <+161>:   je     0x4011ab <phase_6+183>   0x0000000000401197 <+163>:   mov    (%rsp,%rsi,1),%ecx   0x000000000040119a <+166>:   cmp    $0x1,%ecx   0x000000000040119d <+169>:   jle    0x401183 <phase_6+143>   0x000000000040119f <+171>:   mov    $0x1,%eax   0x00000000004011a4 <+176>:   mov    $0x6032d0,%edx   0x00000000004011a9 <+181>:   jmp    0x401176 <phase_6+130>   0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx   0x00000000004011b0 <+188>:   lea    0x28(%rsp),%rax   0x00000000004011b5 <+193>:   lea    0x50(%rsp),%rsi   0x00000000004011ba <+198>:   mov    %rbx,%rcx   0x00000000004011bd <+201>:   mov    (%rax),%rdx   0x00000000004011c0 <+204>:   mov    %rdx,0x8(%rcx)   0x00000000004011c4 <+208>:   add    $0x8,%rax   0x00000000004011c8 <+212>:   cmp    %rsi,%rax   0x00000000004011cb <+215>:   je     0x4011d2 <phase_6+222>   0x00000000004011cd <+217>:   mov    %rdx,%rcx   0x00000000004011d0 <+220>:   jmp    0x4011bd <phase_6+201>   0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)   0x00000000004011da <+230>:   mov    $0x5,%ebp   0x00000000004011df <+235>:   mov    0x8(%rbx),%rax   0x00000000004011e3 <+239>:   mov    (%rax),%eax   0x00000000004011e5 <+241>:   cmp    %eax,(%rbx)   0x00000000004011e7 <+243>:   jge    0x4011ee <phase_6+250>   0x00000000004011e9 <+245>:   callq  0x40143a <explode_bomb>   0x00000000004011ee <+250>:   mov    0x8(%rbx),%rbx   0x00000000004011f2 <+254>:   sub    $0x1,%ebp   0x00000000004011f5 <+257>:   jne    0x4011df <phase_6+235>   0x00000000004011f7 <+259>:   add    $0x50,%rsp   0x00000000004011fb <+263>:   pop    %rbx   0x00000000004011fc <+264>:   pop    %rbp   0x00000000004011fd <+265>:   pop    %r12   0x00000000004011ff <+267>:   pop    %r13   0x0000000000401201 <+269>:   pop    %r14   0x0000000000401203 <+271>:   retqEnd of assembler dump.(gdb)

果然是最后一个阶段,汇编指令多了很多,不过好在没有什么新的函数要去分析,都是前面遇到过的函数。开始分析吧。

突然现在指令一多,gdb的输出有点不适合分析了,看多了眼睛花。于是网上发现有个radare2工具很是完美,pdf @sym.phase_6反汇编输出如下:

/ (fcn) sym.phase_6 272|   sym.phase_6 ();|           ; var int local_18h @ rsp+0x18|           ; var int local_20h @ rsp+0x20|           ; var int local_28h @ rsp+0x28|           ; var int local_50h @ rsp+0x50|           ; CALL XREF from 0x00400ec6 (sym.main)|           0x004010f4      4156           push r14|           0x004010f6      4155           push r13|           0x004010f8      4154           push r12|           0x004010fa      55             push rbp|           0x004010fb      53             push rbx|           0x004010fc      4883ec50       sub rsp, 0x50               ; 'P'|           0x00401100      4989e5         mov r13, rsp|           0x00401103      4889e6         mov rsi, rsp|           0x00401106      e851030000     call sym.read_six_numbers  ; ssize_t read(int fildes, void *buf, size_t nbyte);|           0x0040110b      4989e6         mov r14, rsp|           0x0040110e      41bc00000000   mov r12d, 0|           ; JMP XREF from 0x00401151 (sym.phase_6)|       .-> 0x00401114      4c89ed         mov rbp, r13|       |   0x00401117      418b4500       mov eax, dword [r13]|       |   0x0040111b      83e801         sub eax, 1|       |   0x0040111e      83f805         cmp eax, 5|      ,==< 0x00401121      7605           jbe 0x401128|      ||   0x00401123      e812030000     call sym.explode_bomb      ; long double expl(long double x);|      ||   ; JMP XREF from 0x00401121 (sym.phase_6)|      `--> 0x00401128      4183c401       add r12d, 1|       |   0x0040112c      4183fc06       cmp r12d, 6|      ,==< 0x00401130      7421           je 0x401153|      ||   0x00401132      4489e3         mov ebx, r12d|      ||   ; JMP XREF from 0x0040114b (sym.phase_6)|     .---> 0x00401135      4863c3         movsxd rax, ebx|     |||   0x00401138      8b0484         mov eax, dword [rsp + rax*4]|     |||   0x0040113b      394500         cmp dword [rbp], eax        ; [0x13:4]=256|    ,====< 0x0040113e      7505           jne 0x401145|    ||||   0x00401140      e8f5020000     call sym.explode_bomb      ; long double expl(long double x);|    ||||   ; JMP XREF from 0x0040113e (sym.phase_6)|    `----> 0x00401145      83c301         add ebx, 1|     |||   0x00401148      83fb05         cmp ebx, 5|     `===< 0x0040114b      7ee8           jle 0x401135|      ||   0x0040114d      4983c504       add r13, 4|      |`=< 0x00401151      ebc1           jmp 0x401114|      |    ; JMP XREF from 0x00401130 (sym.phase_6)|      `--> 0x00401153      488d742418     lea rsi, qword [rsp + local_18h] ; 0x18|           0x00401158      4c89f0         mov rax, r14|           0x0040115b      b907000000     mov ecx, 7|           ; JMP XREF from 0x0040116d (sym.phase_6)|       .-> 0x00401160      89ca           mov edx, ecx|       |   0x00401162      2b10           sub edx, dword [rax]|       |   0x00401164      8910           mov dword [rax], edx|       |   0x00401166      4883c004       add rax, 4|       |   0x0040116a      4839f0         cmp rax, rsi|       `=< 0x0040116d      75f1           jne 0x401160|           0x0040116f      be00000000     mov esi, 0|       ,=< 0x00401174      eb21           jmp 0x401197|       |   ; JMP XREF from 0x004011a9 (sym.phase_6)|       |   ; JMP XREF from 0x0040117f (sym.phase_6)|     ..--> 0x00401176      488b5208       mov rdx, qword [rdx + 8]    ; [0x8:8]=0|     |||   0x0040117a      83c001         add eax, 1|     |||   0x0040117d      39c8           cmp eax, ecx|     `===< 0x0040117f      75f5           jne 0x401176|     ,===< 0x00401181      eb05           jmp 0x401188|     |||   ; JMP XREF from 0x0040119d (sym.phase_6)|    .----> 0x00401183      bad0326000     mov edx, obj.node1          ; "L." @ 0x6032d0|    ||||   ; JMP XREF from 0x00401181 (sym.phase_6)|    |`---> 0x00401188      4889547420     mov qword [rsp + rsi*2 + 0x20], rdx|    | ||   0x0040118d      4883c604       add rsi, 4|    | ||   0x00401191      4883fe18       cmp rsi, 0x18|    |,===< 0x00401195      7414           je 0x4011ab|    ||||   ; JMP XREF from 0x00401174 (sym.phase_6)|    |||`-> 0x00401197      8b0c34         mov ecx, dword [rsp + rsi]|    |||    0x0040119a      83f901         cmp ecx, 1|    `====< 0x0040119d      7ee4           jle 0x401183|     ||    0x0040119f      b801000000     mov eax, 1|     ||    0x004011a4      bad0326000     mov edx, obj.node1          ; "L." @ 0x6032d0|     |`==< 0x004011a9      ebcb           jmp 0x401176|     |     ; JMP XREF from 0x00401195 (sym.phase_6)|     `---> 0x004011ab      488b5c2420     mov rbx, qword [rsp + local_20h] ; [0x20:8]=64 ; "@" 0x00000020|           0x004011b0      488d442428     lea rax, qword [rsp + local_28h] ; 0x28 ; '('|           0x004011b5      488d742450     lea rsi, qword [rsp + local_50h] ; 0x50 ; "@" ; 'P'|           0x004011ba      4889d9         mov rcx, rbx|           ; JMP XREF from 0x004011d0 (sym.phase_6)|       .-> 0x004011bd      488b10         mov rdx, qword [rax]|       |   0x004011c0      48895108       mov qword [rcx + 8], rdx|       |   0x004011c4      4883c008       add rax, 8|       |   0x004011c8      4839f0         cmp rax, rsi|      ,==< 0x004011cb      7405           je 0x4011d2|      ||   0x004011cd      4889d1         mov rcx, rdx|      |`=< 0x004011d0      ebeb           jmp 0x4011bd|      |    ; JMP XREF from 0x004011cb (sym.phase_6)|      `--> 0x004011d2      48c742080000.  mov qword [rdx + 8], 0|           0x004011da      bd05000000     mov ebp, 5|           ; JMP XREF from 0x004011f5 (sym.phase_6)|       .-> 0x004011df      488b4308       mov rax, qword [rbx + 8]    ; [0x8:8]=0|       |   0x004011e3      8b00           mov eax, dword [rax]|       |   0x004011e5      3903           cmp dword [rbx], eax        ; [0x13:4]=256|      ,==< 0x004011e7      7d05           jge 0x4011ee|      ||   0x004011e9      e84c020000     call sym.explode_bomb      ; long double expl(long double x);|      ||   ; JMP XREF from 0x004011e7 (sym.phase_6)|      `--> 0x004011ee      488b5b08       mov rbx, qword [rbx + 8]    ; [0x8:8]=0|       |   0x004011f2      83ed01         sub ebp, 1|       `=< 0x004011f5      75e8           jne 0x4011df|           0x004011f7      4883c450       add rsp, 0x50               ; 'P'|           0x004011fb      5b             pop rbx|           0x004011fc      5d             pop rbp|           0x004011fd      415c           pop r12|           0x004011ff      415d           pop r13|           0x00401201      415e           pop r14\           0x00401203      c3             ret

这下清晰多了吧?

前6行很老套,分别将寄存器%r14、%r13、%r12、%rbp和%rbx入栈,然后开辟栈空间,这次开辟的栈空间可不小哦,足足0x50个字节。

接下来调用函数read_six_numbers,这个函数我们可不是第一次遇到了哦!在phase_2中详细分析过,这个函数需要两个参数,第一个是我们输入的字符串,目前存储在寄存器%rdi中;第二个参数是一个6个int型元素数组的首地址,这里通过寄存器%rsi传递。我们断定phase_6内定义了int a[6];它的首地址就是当前栈顶,这点要牢记,因为后续有很多以%rsp为基址的内存访问,看到就会意识到是在访问数组a。

接来下的汇编代码就比较复杂了,复杂在它跳来跳去。既然跳来跳去,那判断肯定是循环。一开始我以为是常规的for或者while循环,但是仔细分许,你会发现代码一走来并没有判断条件,而是把这个判断条件搬到了循环中间或者是循环最后。搬到循环中间,一般是一个死循环,中间通过判断条件而break结束循环。搬到最后的,一般就是do {} while循环了。通篇分析下来,我发现都是后两者循环。我估计是bomb设计者故意这样写的,为的就是增加难度。

mov r14, rsp,使寄存器指向栈顶,也就是数组a的首地址。

mov r12d, 0,寄存器%r12d初始化为0,从后续的add r12d, 1来看,它应该是充当一个计数器的作用。

mov rbp, r13,%r13是指向数组a首地址的,因此这里是使寄存器%rbp指向数组a首地址。到目前为止,指向数组a首地址的寄存器有不少,分别为%r13、%r14、%rsi、%rbp。接着观察到后续地址0x0000000000401151处的jmp指令调回到当前指令,可以判断从当前指令开始是最外层循环的开始。

mov eax, dword [r13]

sub eax, 1

cmp eax, 5

jbe 0x401128

call sym.explode_bomb

这5条汇编指令很清晰,取出当前%rbp指向的数组a元素值与6比较,如果大于6则触发炸弹,否则跳转到0x401128继续执行。由此得出结论,数组a的每个元素值大小不能超过6。

跳转到地址0x0000000000401128处,寄存器%r12d这个计数器增加1,接着判断是否等于6了,如果等于6了,则跳转到地址0x401153处,观察到这里已经跳出了最外层循环,所以可以判断%12d是否等于6是结束循环的条件。

|      ||   0x00401132      4489e3         mov ebx, r12d
|      ||   ; JMP XREF from 0x0040114b (sym.phase_6)
|     .---> 0x00401135      4863c3         movsxd rax, ebx
|     |||   0x00401138      8b0484         mov eax, dword [rsp + rax*4]
|     |||   0x0040113b      394500         cmp dword [rbp], eax        ; [0x13:4]=256
|    ,====< 0x0040113e      7505           jne 0x401145
|    ||||   0x00401140      e8f5020000     call sym.explode_bomb      ; long double expl(long double x);
|    ||||   ; JMP XREF from 0x0040113e (sym.phase_6)
|    `----> 0x00401145      83c301         add ebx, 1
|     |||   0x00401148      83fb05         cmp ebx, 5
|     `===< 0x0040114b      7ee8           jle 0x401135

如果%12d小于6,继续执行。将%12d的值赋给寄存器%ebx后,后面的一些列操作又是一个循环,从地址0x0040114b处的jmp指令可以判断出来。这个循环做了什么呢?很简单,就是将后续的数组a元素值与a[%12d]比较,如果相等则触发炸弹。这意味着什么? 意思是说a[%12d ~ 5]和a[%12d]不能相等。

add r13, 4,是因为int型占4个字节,因此%r13现在指向下一个数组a元素。开始下一轮大循环。

大循环结束,到了这里,我们得出结论:数组a的6个元素值不能大于,并且它们彼此不相等


跳出循环后,从地址0x0000000000401153处继续运行。

|      `--> 0x00401153      488d742418     lea rsi, qword [rsp + local_18h] ; 0x18
|           0x00401158      4c89f0         mov rax, r14
|           0x0040115b      b907000000     mov ecx, 7
|           ; JMP XREF from 0x0040116d (sym.phase_6)
|       .-> 0x00401160      89ca           mov edx, ecx
|       |   0x00401162      2b10           sub edx, dword [rax]
|       |   0x00401164      8910           mov dword [rax], edx
|       |   0x00401166      4883c004       add rax, 4
|       |   0x0040116a      4839f0         cmp rax, rsi
|       `=< 0x0040116d      75f1           jne 0x401160

这段比较简单,寄存器%rax指向数值a首地址,而寄存器%rsi指向a[6],即数组a的最后一个元素的末端,以此为循环条件,分别用7减去数组a的元素,结果再存回数组a中。伪代码如下:

%rax = &a[0];

%rsi = &a[6];

do {

*rax = 7 - %rax;

++%rax; 

} while (%rax != %rsi);

是不是很形象?

接下来的一段代码也是比较复杂,花了不少时间调试并且假设才理通。

|       ,=< 0x00401174      eb21           jmp 0x401197
|       |   ; JMP XREF from 0x004011a9 (sym.phase_6)
|       |   ; JMP XREF from 0x0040117f (sym.phase_6)
|     ..--> 0x00401176      488b5208       mov rdx, qword [rdx + 8]    ; [0x8:8]=0
|     |||   0x0040117a      83c001         add eax, 1
|     |||   0x0040117d      39c8           cmp eax, ecx
|     `===< 0x0040117f      75f5           jne 0x401176
|     ,===< 0x00401181      eb05           jmp 0x401188
|     |||   ; JMP XREF from 0x0040119d (sym.phase_6)
|    .----> 0x00401183      bad0326000     mov edx, obj.node1          ; "L." @ 0x6032d0
|    ||||   ; JMP XREF from 0x00401181 (sym.phase_6)
|    |`---> 0x00401188      4889547420     mov qword [rsp + rsi*2 + 0x20], rdx
|    | ||   0x0040118d      4883c604       add rsi, 4
|    | ||   0x00401191      4883fe18       cmp rsi, 0x18
|    |,===< 0x00401195      7414           je 0x4011ab
|    ||||   ; JMP XREF from 0x00401174 (sym.phase_6)
|    |||`-> 0x00401197      8b0c34         mov ecx, dword [rsp + rsi]
|    |||    0x0040119a      83f901         cmp ecx, 1
|    `====< 0x0040119d      7ee4           jle 0x401183
|     ||    0x0040119f      b801000000     mov eax, 1
|     ||    0x004011a4      bad0326000     mov edx, obj.node1          ; "L." @ 0x6032d0
|     |`==< 0x004011a9      ebcb           jmp 0x401176
|     |     ; JMP XREF from 0x00401195 (sym.phase_6)
|     `---> 0x004011ab      488b5c2420     mov rbx, qword [rsp + local_20h] ; [0x20:8]=64 ; "@" 0x00000020

首先这里有两层循环,外层循环以寄存器%esi为计数器,初始值为0; 里层循环以寄存器%eax为计数器,并且初值为1。

最为关键的是这里牵扯到两个内存地址,一个是以%rsp + 0x20为基址的内存区域,步长为8,这个肯定是函数phase_6内的局部变量,就跟数组a一样。

另一个地址为0x6032d0,通过调试,发现这个地址是位于数据段.data内的地址,用x命令观察如下:


通过观察,可以判定node1~node6是全局变量,因为他们存储在数据段。并且它们最后一个成员是指针,而且类型和自己相同,猜到什么了? 对了,就是链表!!!

目前可以猜测nodeX的类型是个结构体,最后一个成员是指向同类型的指针,其他成员类型不得而知。

struct node {

// ....

struct node *next;

};

但是根据最后的几行汇编代码:

|       .-> 0x004011df      488b4308       mov rax, qword [rbx + 8]    ; [0x8:8]=0
|       |   0x004011e3      8b00           mov eax, dword [rax]
|       |   0x004011e5      3903           cmp dword [rbx], eax        ; [0x13:4]=256
|      ,==< 0x004011e7      7d05           jge 0x4011ee

可以看到这里在比较结构体struct node的前4个字节,因此可以初步判断出struct node的第一个成员是int型的整数。

struct node {

int value;

struct node *next;

};

继续观察, 后续代码中访问next时,为什么都是结构体首地址加上偏移值8呢?例如地址处的mov rdx, qword [rdx + 8] ,还有地址0x004011df处的mov rax, qword [rbx + 8]。原因在于这是x64平台,指针大小占8个字节,而int型占4个字节,因此struct node中的next成员为了内存对齐的需要,会在value和next之间填充4个字节的padding,不过仔细观察上面的截图,这里所谓的padding分别是1、2、3、4、5和6,这些数值好像不是内存里的随意值,更像是特意填充的,因此我们可以假设struct node的完整类型如下:

struct node {

int value;

int seq;

struct node *next;

};

当然了,成员seq在后续代码中并没有什么作用,最后关键部分用到的是成员value;

好了,因此我们可以得出链表在内存中的初始状态如下:


回到代码,从---> 0x00401188      4889547420     mov qword [rsp + rsi*2 + 0x20], rdx,可以猜测以%rsp + 0x20为基址的局部变量是个数组,并且数组元素类型为struct node*,即指向struct node的指针。因此可以判定函数内定义了一个struct node *nodes[6];数组。

这段代码是在给数组nodes的每个元素赋值,根据对应数组a的元素分别指向不同的nodeX。如果a[%esi]的值小于等于1则nodes[%esi]直接指向node1。否则指向对应的nodeX,其中X的值与a[%esi]的值相等

接下来的代码:

|     `---> 0x004011ab      488b5c2420     mov rbx, qword [rsp + local_20h] ; [0x20:8]=64 ; "@" 0x00000020
|           0x004011b0      488d442428     lea rax, qword [rsp + local_28h] ; 0x28 ; '('
|           0x004011b5      488d742450     lea rsi, qword [rsp + local_50h] ; 0x50 ; "@" ; 'P'
|           0x004011ba      4889d9         mov rcx, rbx
|           ; JMP XREF from 0x004011d0 (sym.phase_6)
|       .-> 0x004011bd      488b10         mov rdx, qword [rax]
|       |   0x004011c0      48895108       mov qword [rcx + 8], rdx
|       |   0x004011c4      4883c008       add rax, 8
|       |   0x004011c8      4839f0         cmp rax, rsi
|      ,==< 0x004011cb      7405           je 0x4011d2
|      ||   0x004011cd      4889d1         mov rcx, rdx
|      |`=< 0x004011d0      ebeb           jmp 0x4011bd
|      |    ; JMP XREF from 0x004011cb (sym.phase_6)
|      `--> 0x004011d2      48c742080000.  mov qword [rdx + 8], 0

循环遍历数组nodes,调整各自的next值。

|           0x004011da      bd05000000     mov ebp, 5
|           ; JMP XREF from 0x004011f5 (sym.phase_6)
|       .-> 0x004011df      488b4308       mov rax, qword [rbx + 8]    ; [0x8:8]=0
|       |   0x004011e3      8b00           mov eax, dword [rax]
|       |   0x004011e5      3903           cmp dword [rbx], eax        ; [0x13:4]=256
|      ,==< 0x004011e7      7d05           jge 0x4011ee
|      ||   0x004011e9      e84c020000     call sym.explode_bomb      ; long double expl(long double x);
|      ||   ; JMP XREF from 0x004011e7 (sym.phase_6)
|      `--> 0x004011ee      488b5b08       mov rbx, qword [rbx + 8]    ; [0x8:8]=0
|       |   0x004011f2      83ed01         sub ebp, 1
|       `=< 0x004011f5      75e8           jne 0x4011df

最后的这段代码最为关键,它告诉我们链表最终的内存状态,得出的结论是:从nodes[0]开始遍历链表,nodeX.i的值是从大到小的顺序排列的,即如下这样:


数组nodes元素的指向应该如下:


由此反推如下:


因此得到最终的输入应该是: 4 3 2 1 6 5。 Bingo!!!

还原C代码如下:

#include <stdio.h>#include <stdlib.h>static void read_six_numbers(const char *input, int *a){//%rdi     %rsi%rdx    %rcx   %r8   %r9    (%rsp)  *(%rsp)int result = sscanf(input, "%d %d %d %d %d %d", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]);if (result <= 5) {explode_bomb();}}struct node {int value;int seq;struct node *next;};struct node node6 = {0x000001bb, 6, NULL };struct node node5 = {0x000001dd, 5, &node6};struct node node4 = {0x000002b3, 4, &node5};struct node node3 = {0x0000039c, 3, &node4};struct node node2 = {0x000000a8, 2, &node3};struct node node1 = {0x0000014c, 1, &node2};void phase_6(const char *input){int a[6]; struct node *nodes[6];read_six_numbers(input, a);// %r13int *pa = a;// %r12dint i = 0;for ( ;; ) {if (*pa > 6) {explode_bomb();}++i;if (i == 6) break;int j = i;do {if (*pa == a[j]) {explode_bomb();}j++;} while (j <= 5);++pa;}int *begin = &a[0];int *end = &a[6];do {*begin = 7 - *begin;++begin;} while (begin != end);i = 0;do {if (a[i] <= 1) {nodes[i] = &node1;} else {int j = 1;struct node *pnode = &node1; do {pnode = pnode->next;++j;} while (j != a[i]);nodes[i] = pnode;}++i;} while (i != 6);struct node *head = nodes[0];// %rbxstruct node **begin_node = &nodes[1];// %raxstruct node **end_node = &nodes[6];// %rsistruct node *tmp;// %rdxfor ( ;; ) {// %rdxtmp = *begin_node;head->next = tmp;++begin_node;if (begin_node == end_node) break;head = tmp;}tmp->next = NULL;i = 5;head = nodes[0];do {tmp = head->next;if (head->value < tmp->value) {explode_bomb();}head = tmp;--i;} while (i != 0);printf("sucess\n");}#if 0int main(int argc, const char *argv[]){phase_6(argv[1]);return 0;}#endif


总结

这个二进制炸弹终于分析完了, 花时间最多的是最后一个阶段,首先要看懂汇编代码,其次借助gdb查看内存,最后要大胆假设。

不得不说,gdb不太适合逆向分析,今天发现的radare2工具非常厉害,要花时间去学习一下。

好了,over~~~

补充:完毕之后,网上找了其他人完成的文章,记录如下,可以参考参考。

《Defusing a binary bomb with gdb》

《Of Binary Bombs》(这个是用radare2的案列)

《We neutralize a bomb with Radare2》

《CMU Bomb Lab》

原创粉丝点击