本文记录了完成bomblab的全流程,包括工具安装和解决思路。

准备工作

1
2
3
4
5
6
7
8
9
10
11
# 安装gdb
sudo apt update
sudo apt install gdb

# 为了同时看代码和调试,安装cgdb
wget https://gitee.com/lin-xi-269/csapplab/raw/master/lab2bomb/installCgdb.sh
bash installCgdb.sh

# pwndbg能同时列出寄存器的值、栈帧内容、代码中出现的内存地址指向的内容以及种种更方便的格式
# 不过我只有secret_phase用了它,前六个只用原始的cgdb让我明白了很多关于内存和寄存器的知识
# 顾只做推荐,未提供具体安装

课程官网上的gdb手册,提供了常用的gdb指令

phase_1

esi传入一个内存地址,调用strings_not_equal和输入字符串比较是否相同。理解esi中存的是内存地址,检查该地址的字符串是什么即可解决。

phase_2

phase_2 -> read_six_numbers,read_six_numbers里面起初看不太懂干什么,只知道返回值必须大于5,结合名字猜测是把六个数从字符串读出来并存起来?先立足整体,继续研究phase_2后续。

单步跟后续发现phase_2先判断栈顶指针指向的位置的数是否是1,然后循环判断栈中下一个位置的数是不是上一个位置的数的两倍。由此构造出答案。

回看read_six_numbers,它调用了sscanf,查阅(问ai)后得知这个函数本质是这样的:

1
sscanf(input_string, "%d %d %d %d %d %d", &a, &b, &c, &d, &e, &f);

根据x86_64传参规则,前六个参数放在寄存器,剩余的在栈上构造。%rdi保存了字符串地址,%rsi保存了模式串,abcd四个地址被构造成了phase_2栈区开辟的地址,解释了后来循环访问栈时栈中的内容来自哪里(从字符串中读入并被写入phase_2的栈区)。


立足整体,捋顺大体逻辑,不要在乎细枝末节很有效。不了解read_six_numbers的时候一堆lea看得人犯恶心,了解了之后发现只是传参而已很清晰了。

phase_3

可能是老师想让学生休息一下?搞这么简单。

开头就是上文拆解过的sscanf,这里是读两个数,判断返回值是否大于1。然后判断栈上第一个参数是否大于7,我随手设的1,直接跳转到函数快结尾的地方了。发现只是判断栈上第二个参数是否等于0x137,就把第二个参数设成这个值,通过了………

不过这题其实是个switch:

1
0x0000000000400f75 <+50>:    jmpq   *0x402470(,%rax,8)

通过访问switch的跳转表,跳转到对应位置,估计0~7里随便挑个数都会进入某个分支然后进行判断吧。

phase_4

也很简单,不过上文估计错了,老师大概还是希望大伙会认真看,因为这个想做出来也很简单。

开头使用sscanf读两个数放进栈里,栈顶的数要大于等于0,小于0xe;然后调用func4(x, 0, e),正如前文经验,立足整体先不管他。func4返回后判断返回值为0,最后判断栈中第二个数为0。我第一次用的值是1,2,发现确实能让func4返回0,只不过栈中第二个数为2不为0,改成1,0即可通过。

虽然光速过完了phase_4,还是看一下func4究竟在干什么吧。phase_4其实是一个递归的二分查找,范围在0到e就可以,查找成功返回0。

phase_5

读六个字符的字符串,循环访问每一个字符,通过&0xf,取每个字符的低4位,加0x4024b0,将这个地址的字符放入栈中。最后将放入栈中的字符串和位于0x40245e的字符串比较是否相等。

我们提供的每个字符低四位充当索引,所以根据答案字符串反推目标字符在0x4024b0的什么位置就行。我犯的错误是把字符自动当成了它的低四位,比如要在第15个位置(从0开始),那么低4位应该是f,我直接写了个f上去……f的ascii是0x66,低四位是0x6可不是0xf。0到9能奏效是因为它们的ascii是0x30~0x30。

phase_6

最艰难的一集!

指令偏移+0到+93,向栈中写入六个数,六个数处于[1, 6]且各不相同。

指令偏移+95到+121,栈内六个元素,每个数x = 7 - x。

指令偏移+123到+181,向栈中的一块区域存储指针,将栈中元素作为索引,按栈中元素顺序,存储内存中六个节点的地址。例如栈顶第一个元素为3,那么存储的第一个指针就是内存中第三个节点。

指令偏移+183到+222,将这六个节点连接起来,从这里可以推断出是个链表。

指令偏移+235到+257,从头顺序访问链表,比较每个节点的值是不是比下一个节点的值大。

需要查看内存中六个节点的值,按照降序排列生成答案。记得答案需要用7减过。

secret_phase

bomb.c中最后的注释提到“But isn’t something… missing? Perhaps something they overlooked?”原来是有隐藏关。

运行完phase_6后进入phase_defuse内部,发现它会在第三个炸弹的输入后再读进一个字符串,字符串对了则进入隐藏阶段。

隐藏阶段是一个递归,文字叙述太麻烦,把它的本质写成了c代码,仅供参考。

1
2
3
4
5
6
7
8
9
10
11
12
int ans;
if (target >= now) {
ans = 0;
if (target == now) return ans;
ans = fun7(向右,target);
ans = 2 * ans + 1;
return ans;
} else {
fun7(向左,target);
ans = ans * 2;
return ans;
}

构造最后返回值为2的序列就可以了。

后记

学会了如何阅读汇编代码,熟悉了gdb的使用,对内存结构有了更深的认识。

立足整体,不要纠结细枝末节,是读汇编最大的技巧和本节最大的收获。