Lab要求:
使用gdb获取程序内部信息,理解由反汇编器生成的汇编代码
实验机器:
VMware的虚拟机——Ubuntu-20.04
实验材料:
CSAPP的官网selfstudy-handout:
点击以进入CMU提供的下载渠道
一定记得看Writeup!
Phase_1
注意到phase_1函数带了一个参数进入,保存在%rdi 中,然后给了%esi 赋了一个值,随后进入了一个叫string_not_equal的函数,合理猜测这函数就是字面意思。
先在函数进入前,阻拦一下,感觉0x402400应该就是要比较的字符串的首地址
去看一眼:
看到结果基本确定正确了,验证一下也确实正确。
Phase_2
同样地,phase_2也带了一个参数进入,又使用了一个%rsi 作为参数二,进入了一个 read_six_number的函数,同样怀疑是否就是字面意思。
值得注意的是,无论在命令行中输入了什么,都会以字符串的形式先保存,而这也是为什么又调用了read_six_numbers,在read_six_lines 中调用了scanf将字符串转换为int,再往图片的下部看发现调用的格式控制字符串是"%d %d %d %d %d %d"。
再想,读成数字保存在哪里?由于输入了两个参数,参数二是用%rsp 赋值的,sp是stack pointer的简写,显然是一处内存地址。
先输入一个"1 2 3 4 5 6"作为测试
在函数的出口设一个断点
查看进入函数时用于赋值参数二的%rsp,发现我的输入果然是保存在栈中。
首先发现,输入的第一个数必须为 1,不然就 call exlplode_bomb.
随后进入了一个以rbx为搜索指针,以rbp为结尾信号的循环,
循环体是,将%rbx的上一个数乘以二,与%rbx指向的数作比较,即:
if(input[i] != input[i-1] * 2) explode_bomb();
鉴于第一个数为1,每个数是上一个的2倍,且有6个数,
所以答案是"1 2 4 8 16 32"。
Phase_3
和之前的方法类似直接查看scanf的格式控制字符串,又在%rdi中地址所在内存找到了自己的输入。
圈1:scanf的返回值要大于1,不然BOOM!
圈2:input[0]要<=7,不然BOOM!
圈3:这是一个 地址跳转 指令!
到这里应该不难看出,答案不止一个,我们可以不断的修改%rip,在跳转指令执行前修改%rax值,再执行以查看跳转目的地。
- Input[0] = 1,则:
input[1]必须等于 0x137 = 311(dec)
- Input[0] = 2,则:
input[1]必须等于 0x2c3 = 707(dec)
太无聊了,就不继续了,但反正 input[0]=0、1…7 都是可以有答案的。
以1为例:
但值得一提的是,负数不行,编译器使用了一个很聪明的ja指令。
Phase_4
总之和 Phase_3 很像,也是输入两个数字,就不展开了,仍然是0x8(%rsp)里存input[0],0xc(%rsp)里存input[1]。
圈1:input[0] <= 14,和上一个Phase一样,负数是禁止的,编译器使用了一个jbe对一个signed int作unsigned int比较,这是一种比较隐蔽的技巧以剔除负数。
圈2:进入 func4 时,带了三个参数,参数一:input[0]、参数二:0、参数三:14。
圈3:离开func4之后才检查input[1],说明func4确实只与input[0]有关,正如参数表示的那样。
圈4:func4的返回值必须是0,不然就jump to BOOM!
Phase_4 是肯定有其他答案,但func4的迭代我感觉挺难看出它的用意的,本着详解的原则我尝试把它翻译成了C语言:
所以只有3个答案,“1 0”、“3 0”、“7 0”
没有问题!
Phase_5
Phase_5汇编代码太长,我将程序分为了几个连续发生的阶段。
Stage 1
圈1:这段代码是canary,是一种栈保护机制,是编译器对你程序的附加部分。
圈2:因为很有可能string_length与phase_5的参数一致,就不需要额外加参数。而且,我的输入必须为6个字符。
Stage 2
代码的上一部分是对我的输入作出一些变化,但你会注意到变化遵循固定规定,他并不随机。
紧接着,程序就把 0x40245e作为参数二,我的输入作为参数一,进入了string_not_equal, 就考虑这是字面意思,那参数二应该就是目标对比物。
很快发现,目标对比物为"flyers",但注意,我的输入需要在经由上一步的变化后,变为"flyers"。
其实不需要知道程序的具体编码规则,只需要找出每个字母会变化成什么就OK了:
就以上述方式,5次就能确定编码规则:
% abcdef ghijkl mnopqr stuvwx yz
% aduier snfotv bylmad uiersn fo
上面是源,下面是密文
所以 “ionevw”->“flyers”
没得问题。
Phase_6
phase_6的解答也比较复杂,同样地,我把程序分了几个连续发生的阶段,在分别解释这几个阶段的内容:
Stage 1
和上几个阶段类似,read_six_numbers会从我的输入中,读取6个数字将我输入的数组按顺序向上储存,首地址是%rsp。
Stage 2
注意下 %r13里保存的是%rsp(in stage 1)
这是一个二层循环,长虚线是外层,短虚线是内层。
大概是:
for(int i=0;i!=6;i++){
if(input[i]>6) explode_bomb();
for(int j=i+1;j<=5;j++){
if(input[i]==input[j]) explode_bomb();
}
}
Stage 3
注意%r14中保存的是%rsp(in stage 1)
相当于:
for(int i=0;i<6;i++){
input[i]=7-input[i];
}
Stage 4
我觉得bomblab中最难的部分就是这里,我无数次不解这个0x6032d0是在干什么。
进入前:
注意看这是一个应该是作者设置的类——node,它是按1,2…的顺序依次连接。
进入后:
然后我就看到了,1、2…6的序号不变,但指针的指向已经变了,这段数据,就变成了以 node4 为首的类链表。
可以看到,链表的首节点已经保存于 0x20(%rsp)。
值得注意的是, 0x4011d2 处的指令是给尾结点赋NULL,虽然这个指令没执行,但效果还正常,这是因为我的尾结点就是node6,与原值一致,这是小意外。
Stage 5
可以发现,这个lab最烦人的点在于,对我的输入做了很多的变化,但只在最后做检查。
而 Stage 5 就是对 phase_6 的最终检查:
-
注意 %rbx中保存了链表首节点:
有意思的是,这几个node的value都不是它的序号。 -
圈1是说,对于每个 i,要有input[ i ]>input[ i+1 ]
所以,要使节点值递减,得调整一下:
注意到,即使链表变了,但序号对应的值不变:
0x39c > 0x2b3 > 0x1dd > 0x1bb > 0x14c > 0xa8
node3 > node4 > node5 > node6 > node1 > node2
考虑到数据会被倒转一次,所以最终的输入为 “4 3 2 1 6 5”
完结撒花!