bomb lab 初见bomb A “binary bomb” is a program provided to students as an object code file.  When run, it prompts the user to type in 6 different strings. If any of these is incorrect, the bomb “explodes,” printing an error message and logging the event on a grading server.  Students must “defuse” their own unique bomb by disassembling and reverse engineering the program to determine what the 6 strings should be. The lab teaches students to understand assembly language, and also forces them to learn how to use a debugger.  It’s also great fun.  A legendary lab among the CMU undergrads. 
二进制炸弹,每一个phase都是最喜欢的一集。 
概览: 每一个phase在bomb.c中结构都类似,那么我们便可以使用gdb对每一个phase进行反汇编。
1 2 3 input = read_line();             
输入传入phase_1,若成功返回则拆弹成功。
phase_1 1 2 3 4 5 6 7 8 9 10 Dump of assembler code for function phase_1:
根据函数名字,不难看出我们只需要得到0x402400处的字符串便能返回成功。 
 
直接获取0x402400处的值 :x/s 0x402400
1 2 (gdb) x/s 0x402400 0x402400 :       "Border relations with Canada have never been better." 
phase_1解除成功。
1 Phase 1  defused. How about the  next one ?
phase_2 反汇编:
1 2 3 4 5 6 Dump of assembler code for function phase_2:
第5行中,将栈顶指针传给rsi作为read_six_numbers的参数 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Dump of assembler code for function read_six_numbers:
对上述内容做简单梳理
栈空间 
指令 
注 
 
 
rsi+14 
lea    0x14(%rsi),%rax 
mov    %rax,0x8(%rsp) 
 
rsi+10 
lea    0x10(%rsi),%rax 
mov    %rax,(%rsp) 
 
rsi+c 
lea    0xc(%rsi),%r9 
 
rsi+8 
lea    0x8(%rsi),%r8 
 
rsi+4 
lea    0x4(%rsi),%rcx 
 
rsi 
mov    %rsi,%rdx 
 
rsp+8 
rsi+14 
 
rsp 
rsi+10 
 
输入的6个数字所在的位置就分别是:R[%rsp] R[%rsp+0x8] %rsi %rsi+0x4 %rsi+0x8 %rsi+0xc
返回phase_2函数后,利用栈顶指针调用就是: %rsp %rsp+0x4 %rsp+0x8 %rsp+0xc %rsp+0x10 %rsp+0x14
指令执行到0x401480,上图中加载的指令的栈空间
此时遇到一个内存地址0x4025c3 查看
1 2 (gdb) x/s 0x4025c3
看phase_2剩余部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21    0x0000000000400f0a <+14>:    cmpl   $0x1,(%rsp)
第二行,判断(%rsp)是否为1,不相等则引爆。 
循环规律,后一个数是前一个数的2倍,可得答案:1 2 4 8 16 32 
 
1 That's  number 2 .  Keep going!
phase_3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 Dump of assembler code for function phase_3:   
查看0x4025cf的内容
1 2 (gdb) x /s 0x4025cf 0x4025cf :       "%d %d" 
受第二题启发,我们可以知道输入就在rsp+8和rsp+c
第10行说明第一个输入不能大于7
第十六行假设第一个输入为1,查看0x402478,
(gdb) x/x 0x402478
0x402478:       0xb9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 0xb9 =311 1  311 ``` assembly0x000000000040100c  <+0 >:     sub     $0 x18,%rsp0x0000000000401010  <+4 >:     lea     0xc (%rsp),%rcx  #第二个输入存在0xc (%rsp)0x0000000000401015  <+9 >:     lea     0x8 (%rsp),%rdx  #第一个输入存在0x8 (%rsp)0x000000000040101a  <+14 >:    mov     $0 x4025cf,%esi0x000000000040101f  <+19 >:    mov     $0 x0,%eax0x0000000000401024  <+24 >:    call    0x400bf0  <__isoc99_sscanf@plt>0x0000000000401029  <+29 >:    cmp     $0 x2,%eax0x000000000040102c  <+32 >:    jne     0x401035  <phase_4+41 > #输入数量不为2 直接爆0x000000000040102e  <+34 >:    cmpl   $0 xe,0x8 (%rsp)0x0000000000401033  <+39 >:    jbe     0x40103a  <phase_4+46 > #若第一个参数>14 ,爆0x0000000000401035  <+41 >:    call    0x40143a  <explode_bomb>0x000000000040103a  <+46 >:    mov     $0 xe,%edx #%edx=0xe 0x000000000040103f  <+51 >:    mov     $0 x0,%esi #esi =0x0 0x0000000000401044  <+56 >:    mov     0x8 (%rsp),%edi #%edi=0x8 (%rsp)0x0000000000401048  <+60 >:    call    0x400fce  <func4>0x000000000040104d  <+65 >:    test    %eax,%eax0x000000000040104f  <+67 >:    jne     0x401058  <phase_4+76 > #func4返回值为0 才不爆0x0000000000401051  <+69 >:    cmpl   $0 x0,0xc (%rsp) #第二个输入为0 才不爆0x0000000000401056  <+74 >:    je      0x40105d  <phase_4+81 >0x0000000000401058  <+76 >:    call    0x40143a  <explode_bomb>0x000000000040105d  <+81 >:    add     $0 x18,%rsp0x0000000000401061  <+85 >:    ret 
 
反汇编func4 int func4 ( int edi, int esi, int edx ) 返回值放在 eax
注释为初始情况下第一次的情况 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Dump of assembler code for function func4:
C语言代码实现func4
1 2 3 4 5 6 7 8 9 10 11 12 int  func4  ( int  edi, int  esi, int  edx )  31 )) >> 1 ;  if (edi < ecx) return   2  * func4(edi, esi, edx - 1 ); else  if  (edi > ecx)return   2  * func4(edi, esi + 1 , edx) + 1 ; else return   0 ;
发现x=7直接满足条件 
答案7 0 
phase_5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 Dump of assembler code for function phase_5:
0x4024b0处:maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you? 
+41到+74的代码使用gpt转换后
1 2 3 4 5 6 7 8 9 10 11 12 13 void  phase_5 (const  char * rbx)  {int  rax = 0 ;int  rsp_value;while  (rax != 6 ) {char  ecx = rbx[rax];char *)(&rsp_value) = ecx;int  rdx = rsp_value;0xf ;int  edx = *(char *)(0x4024b0  + rdx);char *)(&rsp_value + rax + 0x10 ) = edx;1 ;
退出循环时,%rsp+10处以此存储着6个字符
继续看+76后的代码
查看0x40245e 
字符串:flyers
调用strings_not_equal函数,判断栈上的六个字符是否与这个字符串的相等。
我们能够知道,这六个字符是通过我们输入的字符的ascii码的低4位作为索引,查找maduiersnfotvbyl 这个i部分 的字符。
在maduiersnfotvbyl 中,f为第9位,l第15位,y第14位,e第5位,r第6位,s第7位。
即我们输入的字符,其ascii码的低4位依次是1001,1111,1110,0101,0110,0111。
即答案为ionuvw
phase_6 1 2 3 4 5 6 7 8 9 0x00000000004010f4 <+0>:     push   %r14
调用read_six_numbers函数,参考phase_2 
rsp处存放6个数字 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x000000000040110b <+23>:    mov    %rsp,%r14
这一部分对输入的要求
1 2 3 4 5 6 7 8 9 0x0000000000401153 <+95>:    lea    0x18(%rsp),%rsi
这一part简单的c语言转换
1 2 3 rsi=0x18 (%rsp)for (rax=%r14,rax<rsi,rax++)7 -n[rax];} 
可以看到这一part主要是对输入进行了简单的变换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0x000000000040116f <+123>:   mov    $0x0,%esi
+163: ecx=num[rsi]
+166:如果num[0]=1跳转
+143:查看0x6032d0内存空间
1 2 3 4 5 6 7 (gdb) x/24 x 0x6032d0 0x6032d0  <node1>:       0 x0000014c      0x00000001       0 x006032e0       0x00000000 0x6032e0  <node2>:       0 x000000a8       0x00000002       0 x006032f0       0x00000000 0x6032f0  <node3>:       0 x0000039c      0x00000003       0x00603300       0x00000000 0x603300  <node4>:       0 x000002b3       0x00000004       0x00603310       0x00000000 0x603310  <node5>:       0 x000001dd      0x00000005       0x00603320       0x00000000 0x603320  <node6>:       0 x000001bb      0x00000006       0x00000000       0x00000000 
不难发现这是一个 链表结构  第一列是数值,第二列序号,第三列是下一个节点的地址
+148~+161: node[2]=node[1]->next
+171:num[rsi]>1时,eax=1,edx=0x6032d0,然后跳转
+130:rdx=rdx+0x8,eax++,eax!=ecx则一直循环
当ecx=eax时,rsp=node[num[i]]
 
至此 我们可以发现,这一部分的操作是将指针移动到node[num[i]]处 ,即栈空间的依次存放着node[num[0]]~node[num[6]]。
此时栈空间 的情况如下表所示
栈地址 
内容 
 
 
rsp+0x48 
node[num[5]] 
 
rsp+0x40 
node[num[4]] 
 
rsp+0x38 
node[num[3]] 
 
rsp+0x30 
node[num[2]] 
 
rsp+0x28 
node[num[1]] 
 
rsp+0x20 
node[num[0]] 
 
rsp+0x14 
num[5] 
 
rsp+0x10 
num[4] 
 
rsp+0xc 
num[3] 
 
rsp+0x8 
num[2] 
 
rsp+0x4 
num[1] 
 
rsp 
num[0] 
 
1 2 3 4 5 6 7 8 9 10 11 0x00000000004011ab <+183>:   mov    0x20(%rsp),%rbx #rax=node[num[0]]
这一部分将链表按栈内节点的位置顺序重新排列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0x00000000004011d2 <+222>:   movq   $0x0,0x8(%rdx)
rbx指向node[num[0]],eax指向node[num[1]],如果rbx<eax,爆炸,即node[i]对的值应该在栈中递减根据node的值排序:node[3]>node[4]>node[5]>node[6]>node[1]>node[2],即对应num[0]=3,num[1]=4,num[2]=5,num[3]=6,num[4]=1,num[5]=2 
但前一部分中,需要对输入的进行变换(n=7-num)才能得到num,所以最终答案是:4 3 2 1 6 5  
 
1 Congratulations! You've defused the bomb!
至此bomb lab的6个phase全部完成。
One more thing… 在bomb.c的最后有一段注释
查看phase_defused的汇编 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 4015c4:	48 83 ec 78          	sub    $0x78,%rsp
发现在 phase_defused中会调用secret_phase
1 2 (gdb) x/s 0x402619
1 2 (gdb) x/s 0x402622"DrEvil" 
不难猜测应该是在某个两个数字的后面加上一个字符串DrEvil
经尝试发现在第四个答案后添加指定字符串可进入隐藏关
1 2 Curses, you've found the  secret phase!it  and  solving it  are quite different...
这里的secret_phase便是暗雷 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 0000000000401242 <secret_phase>:
在第13 14行后,调用了fun7函数,其中有两个参数,esi和edi,esi是输入,edi是一个内存地址 
第16行,返回值与2比较,相等则成功,即目标:使fun7返回2. 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0000000000401204 <fun7>:
查看0x6030f0处的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 (gdb) x/500x 0x06030f0
不难发现这片内存空间中存着的数据是树结构的定义,其中
第一行表示其数值 
第二行表示左子树的地址 
第三行表示右子树的地址 
 
那么其树结构可以用下图表示 注:图中为16进制
而fun7函数的c语言版如下所示
1 2 3 4 5 6 7 8 9 10 int  fun7 (Tree* rdi, int  esi)  {if  (!rdi)   return  -1 ;  if  (rdi->val == esi)    return  0 ;       else  if  (rdi->val < esi)        return  2  * fun7(rdi -> right, esi) + 1 ; else return  2  * fun7(rdi -> left, esi);  
反推,
想要得到2,那么一定是从左子树返回1 
想要得到1,那么一定从右子树返回值返回0 
返回值为0,节点值就是输入的值 
 
2=2*fun7(8,16)
fun7(8,16)=fun7(16,16)+1=1
那么,隐藏关的答案便呼之欲出,0x16=22
1 Wow! You've defused the secret stage!
总结 
耗时3天,总计20h,做完只觉茅塞顿开,csapp这本书相见恨晚。 
各类数据结构在机器级的实现方式令我瞋目结舌。 
phase_5的逻辑设计惊艳到了我,其通过输入字符的ascii码来定位字符,结构巧妙令人赞叹不已。phase_5和secret_phase的用时最长,其复杂的循环或递归结构逻辑复杂,需要先将其转化为c语言。