写在前面
哇哦,终于到了最后一个阶段了,估计会比较难!let's go!!!
分析
先找到调用函数phase_6的地方如下:
反汇编函数phase_6如下:
(gdb) disassemble /m phase_6
Dump 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
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
0x0000000000401123 <+47>: callq 0x40143a
0x0000000000401128 <+52>: add $0x1,%r12d
0x000000000040112c <+56>: cmp $0x6,%r12d
0x0000000000401130 <+60>: je 0x401153
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
0x0000000000401140 <+76>: callq 0x40143a
0x0000000000401145 <+81>: add $0x1,%ebx
0x0000000000401148 <+84>: cmp $0x5,%ebx
0x000000000040114b <+87>: jle 0x401135
0x000000000040114d <+89>: add $0x4,%r13
0x0000000000401151 <+93>: jmp 0x401114
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
0x000000000040116f <+123>: mov $0x0,%esi
0x0000000000401174 <+128>: jmp 0x401197
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx
0x000000000040117a <+134>: add $0x1,%eax
0x000000000040117d <+137>: cmp %ecx,%eax
0x000000000040117f <+139>: jne 0x401176
0x0000000000401181 <+141>: jmp 0x401188
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
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
0x000000000040119a <+166>: cmp $0x1,%ecx
0x000000000040119d <+169>: jle 0x401183
0x000000000040119f <+171>: mov $0x1,%eax
0x00000000004011a4 <+176>: mov $0x6032d0,%edx
0x00000000004011a9 <+181>: jmp 0x401176
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
0x00000000004011cd <+217>: mov %rdx,%rcx
0x00000000004011d0 <+220>: jmp 0x4011bd
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
0x00000000004011e9 <+245>: callq 0x40143a
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp
0x00000000004011f5 <+257>: jne 0x4011df
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>: retq
End 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
#include
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);
// %r13
int *pa = a;
// %r12d
int 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]; // %rbx
struct node **begin_node = &nodes[1]; // %rax
struct node **end_node = &nodes[6]; // %rsi
struct node *tmp; // %rdx
for ( ;; ) {
// %rdx
tmp = *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 0
int 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》