这个 bomb 是 Couresa 上面的一门课 The Hardware/Software Interface 中第四章的一个实验。同时也是 CSAPP 里面的一个作业。花了1天时间把这个做了。期间主要是用到了 gdb,objdump 的一些知识,当然还有一些汇编的基础知识,比如说在 64 位系统下,参数通过 rdi
, rsi
, rdx
, rcx
, r8
, r9 传递,返回值在 rax中。其他的利用 gdb 差不多就可以完成了[这里只讲前五个关卡,不包括后面的附加关卡和隐藏]。
首先我们不知道任何有关 bomb 的输入,所以直接 gdb 运行即可,随便输入看看程序需要什么[下面所有红色的斜体字表示命令]。
一: 运行 gdb bomb。然后在 gdb 的命令行里面执行 b phase_1。然后运行程序,会发现程序停在那,等你输入,这个时候随便输入一些字符即可。然后发现程序执行到了 phase_1 处,利用 gdb 的命令 disas 反汇编指令查看 phase_1 函数的汇编语句,如下所示
发现调用了一个叫做 string_not_equal 的函数,用 stepi 执行到第三行,然后根据函数返回结果(函数返回结果在 rax 中,eax 是 rax 的低 32 位)。判断是否 explode_bomb。那么我们利用 stepi 指令运行到 callq 0x40123d
二: 在等待输入的时候,继续随便输入一些字符(我们只是用这些字符来调试的,从而得到正确的答案)。
=> 0x0000000000400e8c <+0>: mov %rbx,-0x20(%rsp) 0x0000000000400e91 <+5>: mov %rbp,-0x18(%rsp) 0x0000000000400e96 <+10>: mov %r12,-0x10(%rsp) 0x0000000000400e9b <+15>: mov %r13,-0x8(%rsp) 0x0000000000400ea0 <+20>: sub $0x48,%rsp 0x0000000000400ea4 <+24>: mov %rsp,%rsi 0x0000000000400ea7 <+27>: callq 0x4017430x0000000000400eac <+32>: mov %rsp,%rbp 0x0000000000400eaf <+35>: lea 0xc(%rsp),%r13 0x0000000000400eb4 <+40>: mov $0x0,%r12d 0x0000000000400eba <+46>: mov %rbp,%rbx 0x0000000000400ebd <+49>: mov 0xc(%rbp),%eax 0x0000000000400ec0 <+52>: cmp %eax,0x0(%rbp) 0x0000000000400ec3 <+55>: je 0x400eca 0x0000000000400ec5 <+57>: callq 0x40163d 0x0000000000400eca <+62>: add (%rbx),%r12d 0x0000000000400ecd <+65>: add $0x4,%rbp 0x0000000000400ed1 <+69>: cmp %r13,%rbp 0x0000000000400ed4 <+72>: jne 0x400eba 0x0000000000400ed6 <+74>: test %r12d,%r12d 0x0000000000400ed9 <+77>: jne 0x400ee0 0x0000000000400edb <+79>: callq 0x40163d 0x0000000000400ee0 <+84>: mov 0x28(%rsp),%rbx 0x0000000000400ee5 <+89>: mov 0x30(%rsp),%rbp 0x0000000000400eea <+94>: mov 0x38(%rsp),%r12 0x0000000000400eef <+99>: mov 0x40(%rsp),%r13 0x0000000000400ef4 <+104>: add $0x48,%rsp 0x0000000000400ef8 <+108>: retq
在上面的额汇编代码中,我们看到首先,是会调用一个叫做 read_six_numbers 的函数,也就是说需要读入的是6个数字。然后接下来我们发现12行中把 0xc($rbp) 所对应的内存中的数据赋值给 %eax, 然后用 %eax 和 0x0($rbp) 做比较,如果不相等就爆炸,也就是说我们输入的6个数字中第1个数字和第4个数字必须相等. 我用的是数字 4. 从第11行到第19行,是一个循环,表示输入的这 6 个数的前4个都要相等表示第1个数和第4个数相等,第2个数和第5个数相等,第3个数和第6个数相等(相差3个位置)。谢谢网友@zxd 指出。在这里我用的是4个4,然后测试 $12d 是否为0, 这个 $12d 是前4个数字的和,不等于0就行了,否则就会爆炸了。接下来到了第3关
三: 继续输入无关字符,我们停在 phase_3 处,得到如下汇编代码
=> 0x0000000000400ef9 <+0>: sub $0x18,%rsp 0x0000000000400efd <+4>: lea 0x8(%rsp),%rcx 0x0000000000400f02 <+9>: lea 0xc(%rsp),%rdx 0x0000000000400f07 <+14>: mov $0x401ebe,%esi 0x0000000000400f0c <+19>: mov $0x0,%eax 0x0000000000400f11 <+24>: callq 0x400ab0 <__isoc99_sscanf@plt> 0x0000000000400f16 <+29>: cmp $0x1,%eax 0x0000000000400f19 <+32>: jg 0x400f200x0000000000400f1b <+34>: callq 0x40163d 0x0000000000400f20 <+39>: cmpl $0x7,0xc(%rsp) 0x0000000000400f25 <+44>: ja 0x400f63 0x0000000000400f27 <+46>: mov 0xc(%rsp),%eax 0x0000000000400f2b <+50>: jmpq *0x401b60(,%rax,8) 0x0000000000400f32 <+57>: mov $0x217,%eax 0x0000000000400f37 <+62>: jmp 0x400f74 0x0000000000400f39 <+64>: mov $0xd6,%eax 0x0000000000400f3e <+69>: jmp 0x400f74 0x0000000000400f40 <+71>: mov $0x153,%eax 0x0000000000400f45 <+76>: jmp 0x400f74 0x0000000000400f47 <+78>: mov $0x77,%eax 0x0000000000400f4c <+83>: jmp 0x400f74 0x0000000000400f4e <+85>: mov $0x160,%eax ---Type to continue, or q to quit--- 0x0000000000400f53 <+90>: jmp 0x400f74 0x0000000000400f55 <+92>: mov $0x397,%eax 0x0000000000400f5a <+97>: jmp 0x400f74 0x0000000000400f5c <+99>: mov $0x19c,%eax 0x0000000000400f61 <+104>: jmp 0x400f74 0x0000000000400f63 <+106>: callq 0x40163d 0x0000000000400f68 <+111>: mov $0x0,%eax 0x0000000000400f6d <+116>: jmp 0x400f74 0x0000000000400f6f <+118>: mov $0x39e,%eax 0x0000000000400f74 <+123>: cmp 0x8(%rsp),%eax 0x0000000000400f78 <+127>: je 0x400f7f 0x0000000000400f7a <+129>: callq 0x40163d 0x0000000000400f7f <+134>: add $0x18,%rsp 0x0000000000400f83 <+138>: retq
我们看到第6行调用 sscanf,然后第7行对 sscanf 的返回结果做判断,也就是说我们必须输入至少两个数字(或字符串),否则就爆炸了。然后跳到第10行,用我们输入的的第一个数字和7比较,不能大于7,否则就爆炸了。接下来需要知道13行中的代码表示是一个 switch 语句。其中 *0x401b60 表示 jump table 的地址,后面的 rax 表示第几个,8表示数据类型。由于我一开始输入的数字是 2,然后跳转到相应的位置(我们可以用 print *0x401b60 来查看 jump table 的起始位置,其中 gdb 的 print 命令用来输出值, x 命令用来显示相应位置的的内存内容,通俗的说 print 可以看成一个值,x 看成一个指针。)跳到第16行。然后把 $eax 和 第二个输入的数值做对比($eax 是在前面第 16 行进行的赋值,0xd6),所以我们的第二个参数设置位 0xd6(214) 就行了.然后到了第四关
四:来到第四关,我们得到如下汇编代码
=> 0x0000000000400fc1 <+0>: sub $0x18,%rsp 0x0000000000400fc5 <+4>: lea 0xc(%rsp),%rdx 0x0000000000400fca <+9>: mov $0x401ec1,%esi 0x0000000000400fcf <+14>: mov $0x0,%eax 0x0000000000400fd4 <+19>: callq 0x400ab0 <__isoc99_sscanf@plt> 0x0000000000400fd9 <+24>: cmp $0x1,%eax 0x0000000000400fdc <+27>: jne 0x400fe50x0000000000400fde <+29>: cmpl $0x0,0xc(%rsp) 0x0000000000400fe3 <+34>: jg 0x400fea 0x0000000000400fe5 <+36>: callq 0x40163d 0x0000000000400fea <+41>: mov 0xc(%rsp),%edi 0x0000000000400fee <+45>: callq 0x400f84 0x0000000000400ff3 <+50>: cmp $0x37,%eax 0x0000000000400ff6 <+53>: je 0x400ffd 0x0000000000400ff8 <+55>: callq 0x40163d 0x0000000000400ffd <+60>: add $0x18,%rsp 0x0000000000401001 <+64>: retq
首先看到 sscanf 函数,然后判断 eax 是否等于1,也就说说这里有且只有一个输入,然后在第8行把这个参数和0比较,必须大于0,否则爆炸。然后把这个输入作为参数调用 func4 。下面得到的是 func4 的汇编代码
=> 0x0000000000400f84 <+0>: mov %rbx,-0x10(%rsp) 0x0000000000400f89 <+5>: mov %rbp,-0x8(%rsp) 0x0000000000400f8e <+10>: sub $0x18,%rsp 0x0000000000400f92 <+14>: mov %edi,%ebx 0x0000000000400f94 <+16>: mov $0x1,%eax 0x0000000000400f99 <+21>: cmp $0x1,%edi 0x0000000000400f9c <+24>: jle 0x400fb20x0000000000400f9e <+26>: lea -0x1(%rbx),%edi 0x0000000000400fa1 <+29>: callq 0x400f84 0x0000000000400fa6 <+34>: mov %eax,%ebp 0x0000000000400fa8 <+36>: lea -0x2(%rbx),%edi 0x0000000000400fab <+39>: callq 0x400f84 0x0000000000400fb0 <+44>: add %ebp,%eax 0x0000000000400fb2 <+46>: mov 0x8(%rsp),%rbx 0x0000000000400fb7 <+51>: mov 0x10(%rsp),%rbp 0x0000000000400fbc <+56>: add $0x18,%rsp 0x0000000000400fc0 <+60>: retq End of assembler dump.
这份代码一开始的时候还是有点绕的,这个函数是一个递归函数。带回我们就可以看到这个函数的原函数了。
首先我们看到,如果这个函数的参数小于等于1的话,那么直接返回(第7,8行的比较和跳转),设置的返回值是1(第6行,记着我们的返回值存在 $rax 中,$eax 是 $rax 的低位)。如果大于1的话,那么就调用两次改函数(调用自己),第一次的参数是 $rdi-1(这里的 $rdi 是函数传入的参数), 第二次的参数是 $rdi-2,其中第一个在第9行设置成 $rdi-1, 第二个函数在第12行,这里的 $rbx 是保存的 $rdi,然后把两个函数的结果相加得到改函数的返回结果,也就是变成了如下的原函数
int func4(int x) { if(x<=1) return 1; return func4(x-1)+ func4(x-2); }
接下来我们用这个原函数来计算相应的值,我们需要得到的结果等于 0x37.这个是在第四关的第13行。得到的是 9.到此我们第四关完成了,接下来是第无关
五:第无关来了,得到如下的汇编代码
=> 0x0000000000401002 <+0>: sub $0x18,%rsp 0x0000000000401006 <+4>: lea 0x8(%rsp),%rcx 0x000000000040100b <+9>: lea 0xc(%rsp),%rdx 0x0000000000401010 <+14>: mov $0x401ebe,%esi 0x0000000000401015 <+19>: mov $0x0,%eax 0x000000000040101a <+24>: callq 0x400ab0 <__isoc99_sscanf@plt> 0x000000000040101f <+29>: cmp $0x1,%eax 0x0000000000401022 <+32>: jg 0x4010290x0000000000401024 <+34>: callq 0x40163d 0x0000000000401029 <+39>: mov 0xc(%rsp),%eax 0x000000000040102d <+43>: and $0xf,%eax 0x0000000000401030 <+46>: mov %eax,0xc(%rsp) 0x0000000000401034 <+50>: cmp $0xf,%eax 0x0000000000401037 <+53>: je 0x401065 0x0000000000401039 <+55>: mov $0x0,%ecx 0x000000000040103e <+60>: mov $0x0,%edx 0x0000000000401043 <+65>: add $0x1,%edx 0x0000000000401046 <+68>: cltq 0x0000000000401048 <+70>: mov 0x401ba0(,%rax,4),%eax 0x000000000040104f <+77>: add %eax,%ecx 0x0000000000401051 <+79>: cmp $0xf,%eax 0x0000000000401054 <+82>: jne 0x401043 0x0000000000401056 <+84>: mov %eax,0xc(%rsp) 0x000000000040105a <+88>: cmp $0xc,%edx 0x000000000040105d <+91>: jne 0x401065 0x000000000040105f <+93>: cmp 0x8(%rsp),%ecx 0x0000000000401063 <+97>: je 0x40106a 0x0000000000401065 <+99>: callq 0x40163d 0x000000000040106a <+104>: add $0x18,%rsp 0x000000000040106e <+108>: retq End of assembler dump.
同样我们看到 sscanf,然后判断返回值,必须大于1个参数,然后把输入的第一个参数与上 0xf。也就是把这个参数调整到 [1,15] 这个范围内,接下来17-22行一个循环,我们可以还原成一个函数,如下
int a[] = {a, 2, e, 7, 8, c, f, b, 0, 4, 1, d, 3, 9, 6, 5};//16进制 ecx = 0 edx = 1; eax = a[eax]; ecx += eax; while(eax != f) { ++edx; eax = a[eax]; ecx += eax; }
然后把 edx 和7比较,也就是说 我们必须让 edx =7.然后把 ecx 和设置的值做比较(也就是说我们输入的第二个参数),我们可以用反推出来的函数计算结果。最后就行了。最后就完全完成了。至此无关完全完成。 Oh,yeah!
您可能也喜欢: | ||||
![]() 用 GDB 调式 Nginx |
![]() VMware 安装RedHat9时光盘无法挂载 |
![]() 几个简单的数学题 |
![]() 另类UX让你输入强口令 |
![]() 网络流sap算法 |
无觅 |