15-213 bomb lab
Table of Contents
1. phase_1
tips: 首先要学会使用 gdb 才可以开始这个实验
查看关于 phase_1 函数的汇编代码:
可以看到,phase_1 函数只是调用了 strings_not_equal 函数就判断了炸弹是否爆炸,而对于 phase_1 函数,由源码可以知道会将读取的第一行传入作为第一个参数,所以到调用 strings_not_equal 时,函数一共有两个参数,而想要炸弹不爆炸,就必须保证传入的两个参数他们对于 string_not_equal 函数 返回值(保存在 %eax )必须为1,而第二个参数就存储在 %esi 中, 在函数调用时打断点,同时将 %esi 中保存的地址转化为 char * 类型打印出来,答案就出来了
Strings_not_equal 比较两个字符串,内容相同 返回0, 不同 返回 正值
查看汇编发现函数的比较逻辑是这样的:
- 先计算两个字符串的长度,相同,则继续到第二步,不同则返回正值
- 依次从字符串的第一个 char 开始比较,相同,则将依次比较下一个,直到比较到字符串结尾,或者不相同为止
2. phase_2
这个函数比较有意思 phase_2 函数首先通过 read_six_numbers 将 6 个数字读取到栈顶,而 read_six_numbers 的汇编代码比较有意思:
函数通过两个寄存器 %rdi 存储 phase_2 调用时栈顶指针的地址, %rsp 存储当前函数栈顶指针的地址,然后将 %rdi 的高地址的各个位置要么写进 %rsp 的高地址中存储, 要么写进其他参数寄存器,这样就可以在 0x40148a 这里函数调用时,动态的赋值这些地址的值,从而巧妙的将 6 个数字存储在了 phase_2 的栈帧中,这里读的其实就是我们输入的答案,这时候在查看 phase_2 函数,什么情况炸弹不会爆炸:
可以看到,一开始 %rbx 存储 0x4(%rsp) 的地址, 然后 %rbp 存储 0x18(%rsp) 的地址,中间刚好可以容纳 24 个字节,也就是读取的 6 个数,然后一个循环,一开始 %eax 为 1,从第一个数字开始比较,每次正确以后,%eax 中的数字翻倍, 一直比较到 当 %eax 的值为 %rbp 的地址的值时,此时也就比对 6 个数字全部正确,炸弹不爆炸,答案就是 1 2 4 8 16 32
3. phase_3
通过打断点调试可以知道,经由函数 __isoc99_sscanf@plt 调用以后,栈中 0xc(%rsp)、0x8(%rsp) 分别存储读取的第一个和第二个数字,同时汇编有:
第一个数字应该小于 7, 然后通过间接地址引用,断点调试出只有当数字为0 或者 1 时,计算出的跳转地址才是可行的
通过gdb 的调试命令 x /u *(0x402470)
以及 x /u *(0x402470)
, 得到当第一个数字为 0 时,下一步在 0x400f7c; 为 1 时,下一步在 0x400fb9 所以本题有两个答案: 0 207 以及 1 311
4. phase_4
有了前面三个题目的基础可以很直接得出以下已知条件
- 本题答案需要两个数字,第一个数必须小于等于 14, 第二个数字必须为 0,
- 对于调用的 func4 函数返回值必须为 0,
- Func4 有三个参数 第一个数字 0 14
查看 func4 的汇编,发现其实是一个递归函数,简单还原了这个函数的 c语言源代码:
int func4 (int a, int b, int c) {
int tmp = c - b;
int e = tmp >> 31;
tmp = e + tmp;
tmp /= 2;
int x = tmp + b;
if(x <= a) {
if(x == a) return 0;
else {
b = x + 1;
return 2 * func4(a, b, c) + 1;
}
}
else {
c = x - 1;
return 2 * func(a, b, c);
}
}
带入具体数据可以知道,当 b = 0, c = 14 时,a可以为 7, 3, 1
所以本题答案有三个
5. phase_5
这一个炸弹是一个很有意思的含有类似加密/解密的思想的题目,可以看到主要就是对输入的 6 个字符进行一定的操作,然后传入到 strings_not_equal 函数中,使用 gdb 调试出另一个参数时字符串 flyers,接下就是看怎么对输入的字符串进行操作,可以看到其实就是对每个字符一次进行一种算法操作,将输入的字符转换成 flyers,然后传入函数进行比较,答案就出来了。计算按照字符的 ascii 码计算,参照表的位置,可以通过内存打印出来: 401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx
由于参照表长度为 16,而一共有 26 个英文字母,所以答案显然是不唯一的
本题答案不唯一
6. phase_6
这应该是整个 lab 最难的题目了,代码巨长,但是,而且地址,值到处乱飞,看的时候真的很难受但好在分阶段,其实可以一点一点做出来的,很长的汇编其实对应起来也就只有几个循环加几个操作,耐心调试还是可以做出答案的,这里分段给出几个代码段的功能:
- 读取输入的 6 个数字,存放在栈顶的 24 个字节
- 循环判断每个数字都必须是 <= 6,且 >= 0
- 将原本位置上的参数 用 7 减去,并保存在栈上
- 通过数字的大小 存放链表中结点的值在栈的位置
- 判断栈中节点的值是按照从大到小的顺序排的
最终答案:4 3 2 1 6 5
7. secret_phase
这是一个隐藏关卡,其实就是一个彩蛋,查看汇编可以通过 在第 4 个输入的时候,在输入的答案末尾加上 DrEvil 进入
查看其实也是一个 递归 + 结构体 的过程,结果是要求 fun7 函数 返回 2
我们通过查看汇编中内存的引用可以看到一块 关于树的内存,其最后构成的树结构就是
36 8 50 6 22 45 107 1 7 20 35 40 47 99 1001
而 fun7 推算出来的情况就是
int fun7(struct node *p, int v) {
if(p == null) return -1;
else if(v < p->val) {
return 2 * fun7(p->left, v);
}
else if(v == p->val) {
return 0;
}
else {
return 2 * fun7(p->right, v) + 1;
}
}
要求返回 2,输入 20 即可
总结
做到后面,实在是觉得累,就没有贴 汇编截图了,其实做完,感觉对汇编还是没有那么有把握读懂是怎么回事?!不过对整个程序运行真的是有全新的认识,确实是很有帮助的,也加深我继续学下去的意志,虽然真的挺难的,但也是切切实实感觉到在进步。
一直下定决心要写这个 lab,总是各种各样的原因,已经拖了快半个学期了,好在现在终于是写完了,其实真的花的时间做 lab(不包括学 csapp 的时间,加起来我估计有一两个礼拜了),也只是做了两个下午,但是眼睛一闭,一睁,从中午的太阳到晚上的夜色,还是有一些让人觉得恍惚的。lab 个人觉得没有网上说的那么有趣,但是绕到寄存器和栈指针的各种转换的时候,真的有一种被卷入其中的感觉。名校的 lab,也是非常灵活,就是这样的 6 个 phase,就有 3 个的答案是不唯一的,还隐藏了一个彩蛋,真的能感受到课程的用心,反观自己的学校的课程设置和教学质量,还是有点失落的。