CSAPP-3-bomblab
3.bomb lab
参考:
- 深入理解计算机系统-bomblab详解_独小雪的博客-CSDN博客
- Lab2 CSAPP: BombLab环境+思路+全注释 - 知乎 (zhihu.com)
- 手把手教你拆解 CSAPP 的 炸弹实验室 BombLab - 知乎 (zhihu.com)
3.1基本指令
- 必须反汇编对炸弹进行拆除
objdump -d bomb > bomb.s
生成bomb的汇编文件objdump -d bomb > bomb.t
生成bomb的符号表gdb bomb
gdb调试bomb程序(gdb) b 0x0000
设置断点(gdb) x/wx 0x0000
指令x:查看内存指令、w:四字节 x:16进制显示- 所有的字符串都是通过%rdi寄存器传入的,在 bomb.s中搜索 phase_1,可以得到如下的代码:
1 | 400e32: e8 67 06 00 00 callq 40149e <read_line> # 读取命令行的字符串,%rax是 readline 的返回值也就是输入的string |
3.2 phase1
在bomb.s搜素 phase_1 ,可以得到如下的反汇编代码:
1 | 0000000000400ee0 <phase_1>: |
题目分析:
- 函数调用了
strings_not_equal
这个函数,主要是判断输入的字符串与对比的字符串是否不相等:相等返回0,不相等则会返回非0。即 如果%rdi 与 地址(0x402400)存的字符串相等就不会bomb
汇编代码分析:
sub $0x8,%rsp
%rsp是栈指针寄存器,存储栈的顶部位置, 这一行将栈顶减去8位,为函数的局部变量分配空间。mov $0x402400,%esi
将 地址(0x402400)的字符串,放入到 %esi 中。callq 401338 <strings_not_equal>
调用地址(401338)的函数 用于判断两个字符串是否不相等test %eax,%eax
对%eax寄存器中的内容 进行与运算。test指令执行按位与的操作,类似于and指令,但是test 不会保存结果,只会更新标志寄存器的值。- 因此test指令通常用作检查某个寄存器或者内存位置的位模式,而不需要保存结果。
- test操作,如果结果为0,则将零标志位(ZF),设置为1 通常与(je、jne、j1、jg)配合使用
je 400ef7 <phase_1+0x17>
与运算设置了0 标志位 ,那么就跳转400ef7add $0x8,%rsp
释放之前的空间。- je指令 对应于标志寄存器中的零标志(ZF),如果上一条指令产生了零标志,那么程序会跳转到指定地址。
callq 40143a <explode_bomb>
否则执行地址(40143a)的函数 爆炸
题解:
gdb bomb
x/s 0x402400
- 在中断中输入:Border relations with Canada have never been better.
3.2 phase_2
phase_2汇编代码:
1 | 0000000000400efc <phase_2>: |
read_six_numbers汇编代码:
1 | 000000000040145c <read_six_numbers>:#从我们输入的字符串中读取6个数字 第一个参数是%rdi |
思路:
callq 40145c <read_six_numbers>
调用函数“先读入六个数”,同 phase1一样,%rdi原封不动的传入了这个函数,那么%rsi存入的是什么呢?sub $0x28,%rsp
和mov %rsp,%rsi
这两行代码可以看出,先将%rsp中的栈指针栈顶 减去 0x28,然后存入%rsi中,所以%rsi中存入的是地址。结合函数可以得%rsi 在源码是一个指针,指向一个数组的开头 - 在read six numbers中 0x4(%rsi) 。。有几个地址还因为寄存器放不下 放入到了栈中,sscanf的第一个参数是read six numbers的第一个参数,输入的字符串 第二个参数是存储在0x4025c3中的字符串格式
x/s 0x4025c3
查看 字符串存储的格式
- 回到phase_2函数
- 判断第一个元素是否是1,是则跳转0x400f30
- 0x400f30 是%rsp增加四字节(数组的第二个元素),放入%rbx中,然后把最后一个数字的下一个字节放入到%rbp——–0x18 = 24 = 4 * 6 用于判断循环终止,然后回到0x400f17
- 0x400f17
mov -0x4(%rbx),%eax
取得%rbx上一个数 放入%eax ,然后相加使其翻倍,最后将翻倍的数与当前的数进行比较 ,循环比较剩下五个数。 - 其实是以1为首项,2为公比的等比数列。
- 所以答案输入1 2 4 8 16 32就好
3.3 phase_3
phase_3 coding
1 | 0000000000400f43 <phase_3>: |
思路:
- 考察的是switch语句的汇编表示,也调用了sscanf
x/s 0x4025cf
结果是两个数,第一个放在0xc(%rsp)存入rcx 和 第二个数放在0x8(rsp)存入 rdx
- 对于0x8(rsp)只在下面出现过
- *解引用 也就是0x402470这个地址在存储的 值与8乘rax的值加在一起组成的地址 rax既是前一个输入的参数
x/wx 0x402470
得到 指令x:查看内存指令、w:四字节 x:16进制显示,跳转到0x400f7c,也就是下一行,对应switch的第一个case
- 对于0xc:
- 这七个case 处理不同的逻辑,即我们传入两个数,第一个数用来判断要去哪个case, 第二个数用来和这个case存放的书进行比较,如果相等则通过,不相等则bomb
- 答案不唯一 对应的case 输入对应的值就好 0 207
3.4 phase_4
phase_4 coding
1 | 000000000040100c <phase_4>: |
func4 coding
1 | 0000000000400fce <func4>: |
思路
- 同上sscanf 输入标准化是“
%d %d
” 第一个数放在0x8(%rsp),第二个数放在0xc(%rsp) - 要求第一个数不大于14
- 要求第二个数等于0
- 使用 0x8,调用了func4 函数,func函数的第一个参数是我们输入的第一个数 0x8(%rsp),第二个 第三个参数是常数,然后判断func的返回值 是否等于0,不能等0则bomb
- 然后就分析 func函数传入0和 14实现了什么 那么 ecx = 7 只需要让rdi也等于7 即第一参数等于7即可
sar %eax
等价于SAR EAX 1
- 输入 7 0
3.5 phase_5
phase_5 coding
1 | 0000000000401062 <phase_5>: |
思路:
gdb x/s 0x4024b0
得到如下字符串gdb x/s 0x40245e
得到如下字符串:cmp $0x6,%eax
和je 4010d2 <phase_5+0x70>
规定了要传入6个字符 否则会爆炸- 即我们输入的6个字符 低四位rdx 在0x4024b0[rdx] 得到一个字符串要与 flyers相等就行
- 所以在字符串1中找出rdx即可 分别是9(1001)、15(1111)、14(1110)、5(0101)、6(0110)、7(0111)
- 查找ascii码数的低四位即可是上述即可 9?NUVW
3.6 phase_6
phase_6 coding
1 | 00000000004010f4 <phase_6>: |
思路
- 实际上是链表的应用
- 将第一个大循环转换成c语言代码可以得
1 | int sixNum[6] = {输入的6个数} |
x/wx 0x6032d0
- node 变量 链表!!!
x/24wx 0x6032d0
24字节索引 值 1 332[0x14c] 2 168[0xa8] 3 924[0x39c] 4 691[0x2b3] 5 477[0x1dd] 6 443[0x1bb]
单向链表:
1 | //单链表 |
- 那么前面的嵌套循环作用:根据输入的索引,找到对应的node值,并且把这个值放入到一个数组中
1 | Node* addresses[6] = {}; |
- 所以题目要我们输入的是链表的索引顺序,这个顺序是降序排列的,并且每个值都要用 7- 。所以降序排列顺序为
3 4 5 6 1 2
答案为4 3 2 1 6 5
3.7 secret_phase
secret_phase coding:
1 | 0000000000401242 <secret_phase>: |
func_7
1 | 0000000000401204 <fun7>: |
满足6个数后phase_defused代码即0x4015e1之后:
1 | 4015e1: 4c 8d 44 24 10 lea 0x`10(%rsp),%r8 |
搜索
secret_phase
,发现在phase_defused
调用,然后搜索phase_defused
,发现每个phase
结束后都会调用。但不确定是哪个phase在
phase_defused
看什么情况下才能触发secret_phase
,当操作数不等于6时,直接跳0x40163f,直接返回,跳过了secret_phase当输入的字符串数量等于6时才判断是否调用secret函数。——-即必须要解答6个bomb后才能调用secret_phase
查看0x4015e1后的代码,调用了sscanf函数 对输入的字符串进行模板对比。
- 在b 0x4015e1 设置断点,查看 地址0x402619 即sscanf的输入格式
%d %d %s
- 然后查看0x603870传入sscanf是什么数
x/s 0x603870
为7 0
,恰好是phase_4
输入的答案,因此可以推断,phase_4
应该输入特定的答案从而触发secret_phase
。 - 再往下看,最后那个s%的字符串是是什么呢?可以看到将``0x402622地址的字符串传入sstrings_not_equal
判断与输入的是否相等 ,所以查看
0x402622存放了什么。---
DrEvil`
- 在b 0x4015e1 设置断点,查看 地址0x402619 即sscanf的输入格式
secret_phase解析:
将
phase_4
答案后面加上DrEvil
b secret_phase
打上断点,然后运行r ans.txt
,layout asm
看到调用了strtol
将输入的字符串转换成long
,然后比较eax
和0x3e8[1000]
调用 func 传入的参数是(%edi(0x6030f0),%esi(用户输入)),只有当func 7的返回值为2时才能避免爆炸。
0x6030f0
这个数就推断是个地址,x/130xw
查看这个地址,每个节点占据32位 分别是 val值(前四字节)、填充(4字节)、左子节点地址(8字节)、右子节点(8字节)。因此可以得出这个是一个二叉树结构写成二叉树的形式如下:每个节点占据32位 分别是 val值(前四字节)、填充(4字节)、左子节点地址(8字节)、右子节点(8字节)。下面是16进制的表达
分析func7的汇编代码,可以写成如下:
1
2
3
4
5
6
7
8
9
10int fun7(TreeNode* tree, int num) {
if (tree == NULL) return -1;
if (tree->val < num) {
return 2*fun7(tree->right, num) +1;
}else if(tree->val == num) {
return 0;
}else{
return 2*fun7(tree->left, num);
}
}所以可以看出,想要返回2,输入的值必须在数中,否则会返回负数。并且左边递归会无法返回2,只会把原来的值加倍,因此需要原来右子树递归+1,获取1,然后利用2*fun7() 获得2.
从根节点出发0x24,那么若向右走,
则执行 2*fun7(tree->right, num) + 1
想要return 2,需要fun7() ==0.5,显然不可能,那么从左走 2*fun7(tree->left, num)
,到达节点0x8 第二层,此时,前面一次选择已经有2的倍数了,所以只需要能够获得1即可,那么怎么获得1呢?—->向右走2*fun7(tree->right, num) + 1
使得num 等于 右边子树的一个fun7()==0
,所以就可以获得1,最终得到2*(fun7(tree->right,num) + 1)
.所以num = 0x16 或0x14
总结:
答案:
- Border relations with Canada have never been better.
- 1 2 4 8 16 32
- 0 207
- 7 0 DrEvil
- 9?NUVW
- 4 3 2 1 6 5
- 20 或 22