文章目录
- Lab 总结博客链接
- 实验前提引子
- 实验需要指令及准备
- Phase 1
- Phase 2
- Phase 3
- Phase 4
- Phase 5
- Phase 6
- Phase Secret(彩蛋Phase)
Lab 总结博客链接
CSAPP Lab入门系统安装摸索+Lab 博客链接
实验前提引子
这部分是我在大一寒假的时候写的 那个时候只写了
Phase 1 - 5
没有做出来Phase 6
现在大二开学没几周 过来重新把Phase 6
给完成了 算是弥补原来没有写出来的遗憾吧 那前面的Phase 1 - Phase 5
的讲解 我就不再重新编辑了 那个时候感觉自己写的还是蛮好的 后面的Phase 6
才是二次编辑讲解的maybe
原来有些地方的小理解有点问题 那就烦请Forgive Me
一下了呜
补Phase 6
特此过来说明一下^^
各位继续往下看吧
这个引子是为了给一些这个实验还没有入门的hxd看的
因为这个实验直接拿给一个phase都还没过的人 干什么都是不知道的
这个实验入门还是挺简单的 所以大家可以对于前两个phase 可以看一下我是怎么解题的 大概流程或者操作熟悉一下
对于后面拆炸弹你可能就自己是懂得怎么解决了
过掉两个phase是不会影响这个Lab对你能力的提升的
所以对于前两个phase我会很详细的 把我怎么做的思路一步步都讲出来
对于后面3、4、5、6 四个phase我可能会稍微没有那么仔细的分析了
需要什么指令 还有需要还有什么其他的 我都会在下面详细的给出来的
实验需要指令及准备
我们首先把 Bomb-Lab 给解压了
得到Bomb的文件夹 打开里面有三个文件 除了 bomb.asm反汇编文件
然后我们需要什么呢 首先要安装Gdb调试器 对于Ubuntu而言
打开终端 直接输入sudo apt-get install gdb 然后截图是下面的
然后就进入 bomb文件夹
我们可以先看看read文件 bomb.c文件可以不看 因为你看了也没有什么用qwq 看了你也不晓得在说什么(有彩蛋的)
到最后我们都是通过gdb调试器来运行bomb文件 来进行调试
下面先给出gdb调试器的一些指令 是我们后续需要用到的
这里先给出来 不需要全部记下来 后面我会用到的 再讲用法
这些指令我都是复制过来的
基本上都是要用到的
r 运行程序b <*0x某某某> 在某个地址设置断点,具体哪里,可以看反汇编的代码,可以根据那个直接复制粘贴设断点的d 删除所有断点d <断点号> 删除指定断点info b 查看所有断点信息continue 从断点处继续执行display <$寄存器> 跟踪寄存器,碰到断点停下时会显示出所有跟踪的寄存器的当前值,非常好用的一个命令,注意的是gdb中表示寄存器的话前面用的不是百分符号%,而是美元符号$x/参数 <地址> 访问地址的内存,其实就是间接访问,也是很好用的指令,关于参数,s是输出为字符串,d为输出为十进制,x为输出为十六进制,b、w、l、q控制输出字节,默认是w,四字节,s字符串不受这个控制除外。info r 查看所有寄存器的值print (可加强制转换符号)<数字> 跟C语言的基本性质一样的,理解即可
然后我们提前生成Bomb.c的反汇编文件
为什么要生成 因为后面是需要用的 到后面需要怎么用的时候再讲
我们只需要生成一次 在终端输入objdump -d bomb > bomb.asm
就能看到 在bomb文件夹多出来了一个这样子的文件
ok 我们进入我们的紧张刺激的拆炸弹phase环节哈哈哈哈哈 (finally gogogo!)
Phase 1
我们先通过gdb 载入bomb文件 终端输入gdb bomb 然后就会出现这样子的状态
我们可以先运行一下这个bomb软件 直接输入r即可
然后就出现下面炸弹 have 6 phases with which to blow ourselves up
我们可以试一下 输入自己想输入的 让不让我们通过
比如 dontletmebomb
可是事不如我们所预期的 bomb了 那我们如何躲开炸弹爆炸通过呢
我们只能查看我们的反汇编语言来寻找蛛丝马迹了
这个时候反汇编我们之前生成的bomb.asm就有用武之处了
那我们打开bomb.asm
建议复制一份到windows 因为在ubuntu是只读的
然后不好打备注 打开之后我们先来到Phase_1段落处
我们这个时候来仔细分析分析
第一行代码 把栈指针减少了8 是为了给局部变量提供空间
与最后一行addq $0x8 相对应 函数都是会回收栈指针的
第二行代码向寄存器 esi给了0x402400的数据
第三行就是调用函数 strings_not_equal
看函数名字 我们应该能判断出来 这个函数是看我们输入的字符串和这个程序的其中的哪个字符串是否相同
然后我们再往后看看第四行代码 就是test eax寄存器是否有数
根据我们之前理解到的 rax寄存器一般里面存放的是函数的返回值
eax寄存器不过就是rax的低32位表示而已
哦? 这就有意思了 我们不难分析出 当rax寄存器当值等于0的时候
即可满足第五行代码的跳转指令
跳转指令跳转到400ef7处 然后是跳过了 400ef2处的指令
我们仔细看一下400ef2的指令 调用函数 explode_bomb
xdm 看函数名懂得都懂 爆炸指令 则我们可以得到下面的信息
如果当rax寄存器值不等于0的时候
我们不会跳转 就会来到400ef2处 就会调用爆炸函数 我们就会起飞
唯一的方法就是 我们输入的字符串和其中存储的一个字符串相等
相等的话 string_not_equal 返回值就会是0
不相等可能会返回1
(在c语言中 0有false的意思 1也有true的意思 即满足相等 函数返回false)
说了那么多 我们仔细看一下这两行代码
这里是为什么会向寄存器放入这个 0x402400呢
因为显然 程序是不肯定把提前准备好的字符串藏到其他很深的地方的
那不然我们怎么拆炸弹啊哈哈哈哈哈
我们就可以调查这个地方
有两个办法 因为是字符串
首先是 第一个是直接通过gdb调试看
print (char*)0x402400
我们一个一个试一下
哦哟 不错哦~ 出现了一串字符串~
这个可能是正确答案 我们先记下来
Border relations with Canada have never been better.
我先把下面一个方法介绍一下 再去核对一下这个答案是否是正确的
第二种是通过设置断点在349行代码处 此时已经经过了
mov 0x402400, $esi
这行指令了
我们先输入b *0x400ee9
设置断点
然后我们先重新重新运行程序 输入r
然后随便输入一串字符串abcde
当程序运行到断点处自动停止 此时我们就可以看一下esi寄存器里面的数据了然后通过指令 x/s $esi
看寄存器中存放数据 这里的s就是字符串哈
两种办法介绍完了 我们现在就去重新运行验证一下 字符串正确与否
ok 终于拿下了~ 拿下(破音) 有了phase的经验
后面的拆炸弹相比也有一定经验了
如果觉得自己经过phase_1理解了拆弹流程了
真的想自己拆弹的话 建议phase_2就可以开始自己拆了 不用再跟着实验记录
如果觉得自己还没有拆弹的感觉 怕被炸飞的
可以继续跟着实验记录走一波
反正我是看完人家的phase_1之后的 phase_2就是全自己推了
ok 那我们来到phase_2继续当拆弹专家:)
拆弹密码1
Border relations with Canada have never been better.
Phase 2
老规矩 分析一下吧
357 358行 存放寄存器状态
359行提供局部变量空间
360行把我们的栈指针状态存入到rsi寄存器中
然后继续往下看到 callq read_six_numbers 看函数名字是读取六个数字 我们这边可以先试一下输入六个数字
不错的昂~ 炸弹成功炸掉 扣了HP-1 可惜我们的HP为99999999
而且还是god mode 说明这个phase不仅仅只是输入6个数字那么简单
要想仔细分析一下 我们就需要去函数 read_six_numbers代码区一探究竟
发现为40145c
有个快捷键 ctrl+f 我们可以搜索关于这个的文字
啪的一下很快嗷 我们就到了这个代码区
我们可以逐行的再来分析一下这个函数
首先栈指针减少24 从中我们可以不难得出 24刚好是6个int整形数字所需要的字节数
然后后面到814行之前应该是把6个整形数字的地址放到各个寄存器中
然后我们注意814行— 817行之间
首先816行有个调用函数 scanf 我们可以稍微去看一下这个函数
emmm 这个函数又跳转到其他位置
然后我之后再400bf0这里设置了一个断点
然后info r看了一下 寄存器rip 跳转地址看了记得是0x600000(可能有误)
但是反正也不需要去搞懂
这个应该就是跟c语言 scanf函数一样的
然后我们返回814行代码 有没有想到c语言我们调用scanf函数
我们需要首先给出 我们输入格式 比如%d %d %d %d
联系到第一题 我们可以调查一下 我们不难联想到 这个向esi转入的数字
可能就是 我们输入格式
说干就干
在终端输入
print (char*)0x4025c3
不错的昂 这里也分析出了确实是6个数字
这里还有个小细节也能分析出来至少是大于5个数字的
就是817行 rax装的是scanf函数的返回值
如果小于等于5 也会引发炸弹爆炸
对于输入数字的问题 我们就解决了
再返回phase_2
首先可以判断 当rsp间接引用的内存地址必须要等于1
通过我的两次尝试再加上一定的理性判断 可以判断出
(%rsp)装的肯定是我们输入6个数的第一个数
那么 0x4(%rsp)就是第二个数 0x8(%rsp)是第三个数
我们输入的数第一个就必须是1 跳转到400f30
接下来的分析我就快速分析一下了 rbx装2号数 rbp装6号数
跳转 400f17 下面就类似数学小学数学问题了
我用c语言来概括一下下面的代码在干什么
int i;
int a[6];
a[0] = 1;
for(i=1;i<6;i++)
{a[i] = a[i-1]*2;
}
还是不难分析的 最后就能成功得到6个数字为 1 2 4 8 16 32
我们重新开始实验 测试一下是不是这个数字
ok 拆弹专家level is becoming higher了嗷
我这边实际写博客的时间远比我解题的时间多qwq
但是接下来的四个phase 我不会再像这个样子 一点一点分析了
但是也会记录下来我的一些想法 关键步骤
拆弹密码2
1 2 4 8 16 32
Phase 3
0000000000400f43 <phase_3>://给局部变量腾出空间 把两个数据内存地址移向两个寄存器400f43: 48 83 ec 18 sub $0x18,%rsp400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx//调查输入格式because 400f5b调用了scanf函数
//print (char*)0x4025cf
//得到%d %d 即为输入两个数400f51: be cf 25 40 00 mov $0x4025cf,%esi 400f56: b8 00 00 00 00 mov $0x0,%eax//调用scanf函数400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>//也可从这里印证 返回值需要大于1400f60: 83 f8 01 cmp $0x1,%eax//跳转400f6a400f63: 7f 05 jg 400f6a <phase_3+0x27>400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>//因为只有两个数 我们从开头发现有个0x8(%rsp) 还有个0xc(%rsp)内存调用进入了寄存器
//所以我们不难推测出 这个0x8(%rsp)可能是第一个数
//通过两次断点尝试 我们肯定出这个是第一个数
//不碰炸弹条件需要小于等于7 400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)//跳转400fad 如果大了即跳转 跳转地址为调用引爆炸弹指令400f6f: 77 3c ja 400fad <phase_3+0x6a>//这里我还是花了点时间分析的
//下面很多重复的移动
//这里不妨可以联系到类似跳转表
//因为我知道400f75也是跳转到一个地址 我计算出来在整个反汇编文件上限都没有
//那么我想到书里面有个跳转表 是通过自己减去一部分的地址 比如0~7的
//可能计算地址为100~107 通过减去100而到相对于的地方
//这里可以通过观察得到 刚好有7个跳转重复的 我们可得到应该第一个数范围应该是0~7 但最搞笑的是 应该是0~7 可是当我代入测试数据的时候
//不知道为什么 当第一个数为1的时候 是不存在的 我认为可能是设置数据没设计好的问题
//这里存在Bug400f71: 8b 44 24 08 mov 0x8(%rsp),%eax//第一个数400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8)400f7c: b8 cf 00 00 00 mov $0xcf,%eax//0 0xcf = 207400f81: eb 3b jmp 400fbe <phase_3+0x7b>400f83: b8 c3 02 00 00 mov $0x2c3,%eax//2 0x2c3 = 707 400f88: eb 34 jmp 400fbe <phase_3+0x7b>400f8a: b8 00 01 00 00 mov $0x100,%eax//3 0x100 = 256400f8f: eb 2d jmp 400fbe <phase_3+0x7b>400f91: b8 85 01 00 00 mov $0x185,%eax//4 0x185 = 389400f96: eb 26 jmp 400fbe <phase_3+0x7b>400f98: b8 ce 00 00 00 mov $0xce,%eax//5 0xce = 206400f9d: eb 1f jmp 400fbe <phase_3+0x7b>400f9f: b8 aa 02 00 00 mov $0x2aa,%eax//6 0x2aa = 682400fa4: eb 18 jmp 400fbe <phase_3+0x7b>400fa6: b8 47 01 00 00 mov $0x147,%eax//7 0x147 = 327400fab: eb 11 jmp 400fbe <phase_3+0x7b>400fad: e8 88 04 00 00 callq 40143a <explode_bomb>400fb2: b8 00 00 00 00 mov $0x0,%eax400 fb7: eb 05 jmp 400fbe <phase_3+0x7b>400fb9: b8 37 01 00 00 mov $0x137,%eax//跳转后要求 之前移入eax的数等于我们输入的第二个数
//上面已经把所有第一个数对应第二个数 标注了出来
//这道题的结果应该是有7种 有一种数据没设计进去 随意输入一种即可通过400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax400fc2: 74 05 je 400fc9 <phase_3+0x86>400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>400fc9: 48 83 c4 18 add $0x18,%rsp400fcd: c3 retq
拆弹密码3
0 207; 2 707; 3 256 ;4 389; 5 206; 6 682; 7 327
Phase 4
000000000040100c <phase_4>:40100c: 48 83 ec 18 sub $0x18,%rsp401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx// %d %d 输入格式40101a: be cf 25 40 00 mov $0x4025cf,%esi 40101f: b8 00 00 00 00 mov $0x0,%eax401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>401029: 83 f8 02 cmp $0x2,%eax40102c: 75 07 jne 401035 <phase_4+0x29>//第一个数小于等于1440102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp)401033: 76 05 jbe 40103a <phase_4+0x2e>401035: e8 00 04 00 00 callq 40143a <explode_bomb>//edx 1440103a: ba 0e 00 00 00 mov $0xe,%edx//esi 040103f: be 00 00 00 00 mov $0x0,%esi//edi 第一个数 esi 0 edx 14 401044: 8b 7c 24 08 mov 0x8(%rsp),%edi401048: e8 81 ff ff ff callq 400fce <func4>40104d: 85 c0 test %eax,%eax//edi 7 esi 0 edx 14 eax 0 ecx 740104f: 75 07 jne 401058 <phase_4+0x4c>//第二个数为0401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)401056: 74 05 je 40105d <phase_4+0x51>401058: e8 dd 03 00 00 callq 40143a <explode_bomb>40105d: 48 83 c4 18 add $0x18,%rsp401061: c3 retq 0000000000400fce <func4>://edi 第一个数 esi 0 edx 14 eax 14 ecx 14
//edi 第一个数 esi 0 edx 14 eax 7 ecx 0
//edi 第一个数 esi 0 edx 14 eax 7 ecx 7
//edi 0~7 esi 0 edx 14 eax 7 ecx 7400fce: 48 83 ec 08 sub $0x8,%rsp400fd2: 89 d0 mov %edx,%eax400fd4: 29 f0 sub %esi,%eax400fd6: 89 c1 mov %eax,%ecx400fd8: c1 e9 1f shr $0x1f,%ecx400fdb: 01 c8 add %ecx,%eax400fdd: d1 f8 sar %eax400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx//这里限制了edi 小于等于rcx400fe2: 39 f9 cmp %edi,%ecx400fe4: 7e 0c jle 400ff2 <func4+0x24>400fe6: 8d 51 ff lea -0x1(%rcx),%edx400fe9: e8 e0 ff ff ff callq 400fce <func4>400fee: 01 c0 add %eax,%eax400ff0: eb 15 jmp 401007 <func4+0x39>//edi 0~7 esi 0 edx 14 eax 0 ecx 7400ff2: b8 00 00 00 00 mov $0x0,%eax//这里限制了edi 大于等于rcx
//得到即edi只能等于rcx 7
//edi为第一个数 即第一个数为7400ff7: 39 f9 cmp %edi,%ecx400ff9: 7d 0c jge 401007 <func4+0x39>400ffb: 8d 71 01 lea 0x1(%rcx),%esi400ffe: e8 cb ff ff ff callq 400fce <func4>401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax401007: 48 83 c4 08 add $0x8,%rsp40100b: c3 retq
拆弹密码4
7 0
Phase 5
我的Phase4 和我的Phase5都是
刚解出来 我就跑过来写解答了
相对于Phase4 我认为Phase5的难度是上升了一个台阶
因为我分析起来 Phase4可能就花了不到20分钟
我自认为还是没有什么难度的
而Phase5相比来说 我花了更多时间去看
相关寄存器的数据和其他的东西 去尽量搞懂每行代码在干什么
正好趁我手感火热 赶快就把博客写了
这部分我就不能仅仅把那个备注给写了 还是觉得有必要写的详细一点
一步步来吧
0000000000401062 <phase_5>://储存rbx寄存器状态 减少栈指针为局部变量提供空间401062: 53 push %rbx401063: 48 83 ec 20 sub $0x20,%rsp//没有搞懂fs:0x28是啥 但反正设置断点得到fs:0x28为0401067: 48 89 fb mov %rdi,%rbx40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax401071: 00 00 401073: 48 89 44 24 18 mov %rax,0x18(%rsp)//异或设置rax为0401078: 31 c0 xor %eax,%eax//调用函数 检测字符串长度 与下面一条指令 则说明字符串需要长度为640107a: e8 9c 02 00 00 callq 40131b <string_length>40107f: 83 f8 06 cmp $0x6,%eax401082: 74 4e je 4010d2 <phase_5+0x70>401084: e8 b1 03 00 00 callq 40143a <explode_bomb>401089: eb 47 jmp 4010d2 <phase_5+0x70>//rbx位置存放着第一个数字位置//下面四条指令的作用就是 rdx取的是字符的低四位40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx40108f: 88 0c 24 mov %cl,(%rsp)401092: 48 8b 14 24 mov (%rsp),%rdx401096: 83 e2 0f and $0xf,%edx//这个时候我不知道这条指令是干嘛的//直接调用指令 x/s 0x4024b0 出现一串字符串//maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you//这个指令是干什么呢 也就是把这个语句中的一个字符 转移到rdx中//字符定位就在地址 0x4024b0再加上我们之前输入的某个字符的低四位401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx//这个语句又是把maduiersnfotvbylSo其中一个字符转移到 一个地址4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1)//最刚开始rax一直为0 现在加+14010a4: 48 83 c0 01 add $0x1,%rax//与6相比较 如果不等于6的话 循坏又到0x40108b位置处//这里的意思就像c语言中 for(i=0;i<6;i++) //我们先看看如果rax为6之后又是怎么样了4010a8: 48 83 f8 06 cmp $0x6,%rax4010ac: 75 dd jne 40108b <phase_5+0x29>//我一看到这里 我们把这四条指令一起看//第四条指令调用 判断字符串是否相等 而第二条又是向寄存器存放了一个不晓得什么的东西//这个时候我大胆估计 这个esi装的就是我们需要匹配的字符串//print (char*)0x40245e 得到字符串为flyers//诶这里的 我们需要匹配的flyers字符串也刚好是6个字符//那和我们输入的6个字符又有什么联系呢4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp)4010b3: be 5e 24 40 00 mov $0x40245e,%esi4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>//当字符串相等后 返回值为0 跳转到4010d94010c2: 85 c0 test %eax,%eax4010c4: 74 13 je 4010d9 <phase_5+0x77>4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)4010d0: eb 07 jmp 4010d9 <phase_5+0x77>//eax置04010d2: b8 00 00 00 00 mov $0x0,%eax4010d7: eb b2 jmp 40108b <phase_5+0x29>//这里不知道在干什么 但我们看到4010e9位置调用 stack_chk_fail函数//这里应该是检查堆栈是否溢出或者是也有什么地方不相同//反正重心应该也不会在这里4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax4010e5: 00 00 4010e7: 74 05 je 4010ee <phase_5+0x8c>4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>//这里调用函数结束 炸弹也就成功拆除了4010ee: 48 83 c4 20 add $0x20,%rsp4010f2: 5b pop %rbx4010f3: c3 retq
ok 这里就是连接着上面代码段 已经分析出了
maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you
这个代码了
然后我们来仔细分析一下
那个时候我是这样揣测的
因为要循坏六次 然后六次都把这个字符串的一个字符送到一个地址
后面还要进行匹配 那么 我们送到那个地址的字母
按照顺序 就应该是
f l y e r s
但是哪个字符送出去 决定权在于我们六个输入的字符的后四位
ok 我们把大概思路理清了 我们就在字符串定位即可
第一个字符 f位置在字符串地址开始 9号位 即确定后四位为9
我们的一个ASCII的字符是有一个字节长 也就是8位
后四位确定了 但是前四位没有确定 最开始的位置是符号位0
那么有三个位置不确定的 ASCII字符大小最小是32 32为空格
按照这种逻辑 我们可以列出来多个组合方式
就拿第一个举例子
32+16+9 = 57 ; 64+32+16+9 = 121 ; 32+9 = 41; 64+32+9 = 105;
64+9 = 73等等等等… 然后我们在ASCII表上找到对应的符号即可…
我这里就贴一个我满足条件的一个字符串
9?>567
最终拆弹密码可能有上百种组合
我这列出来我的一种
9?>567
去尝试一下呢 OK 拿下~
Phase 6
我大概是在6点钟左右开始动工 在晚上8点20左右就最终完成了Phase 6
现在我的感受只有两个 第一个是确确实实 大一一年的磨练与学习 能力真的提高了不知道多少 真真实实感受到了自己的进步与蜕变 第二个是 老美真的是不得不佩服 人家老师的题目的构思 以及实验设计的精巧
这个实验确实比较的困难 做出来可能真的首先需要耐心 第二个就是对于汇编语言真的掌握的比较熟练 还有就是对于GDB
调试比较熟练 做这个Phase会用到GDB
调试 好了 废话不多说了 今天本来打算晚上看虚拟内存的 然后完成Malloc Lab
但是过来做Phase 6
了 吃了饭回来就没时间了 那只能明天来弄咯
Phase 6
的话 细节很多 我们一部分一部分来剖析吧 比较是二次编辑 还是想写好一点 那下面走起了
第一部分
主要是来判断是否为6个数字 如果小于6个数字 Bome 大于6个数字也 Bome 必须是刚好6个 比较容易看出来 就不介绍了
00000000004010f4 <phase_6>:4010f4: 41 56 push %r144010f6: 41 55 push %r134010f8: 41 54 push %r124010fa: 55 push %rbp4010fb: 53 push %rbx4010fc: 48 83 ec 50 sub $0x50,%rsp // rsp 开了 80字节的栈401100: 49 89 e5 mov %rsp,%r13 401103: 48 89 e6 mov %rsp,%rsi401106: e8 51 03 00 00 callq 40145c <read_six_numbers>40110b: 49 89 e6 mov %rsp,%r14 // r14 = rsp40110e: 41 bc 00 00 00 00 mov $0x0,%r12d401114: 4c 89 ed mov %r13,%rbp401117: 41 8b 45 00 mov 0x0(%r13),%eax40111b: 83 e8 01 sub $0x1,%eax40111e: 83 f8 05 cmp $0x5,%eax401121: 76 05 jbe 401128 <phase_6+0x34> // eax -= 1; eax >= 5跳转 // 则这里是判定是否大于等于6 小于6 bomb401123: e8 12 03 00 00 callq 40143a <explode_bomb>401128: 41 83 c4 01 add $0x1,%r12d40112c: 41 83 fc 06 cmp $0x6,%r12d401130: 74 21 je 401153 <phase_6+0x5f> // eax += 1; eax == 6跳转 跳转至 401153401132: 44 89 e3 mov %r12d,%ebx401135: 48 63 c3 movslq %ebx,%rax401138: 8b 04 84 mov (%rsp,%rax,4),%eax40113b: 39 45 00 cmp %eax,0x0(%rbp)40113e: 75 05 jne 401145 <phase_6+0x51>401140: e8 f5 02 00 00 callq 40143a <explode_bomb>401145: 83 c3 01 add $0x1,%ebx401148: 83 fb 05 cmp $0x5,%ebx40114b: 7e e8 jle 401135 <phase_6+0x41>40114d: 49 83 c5 04 add $0x4,%r13401151: eb c1 jmp 401114 <phase_6+0x20>
第二部分开始进入正题了 这里我要做一下解说了
首先就是 刚开始的循环目的 就是把 6个数字 假如为num1、num2、num3、num4、num5、num6
不特定指的话 就用numx
来做 6个输入进来的数字numx = 0x7 - numx
我们把部分切的细一点
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi // rsi = rsp + 24 401158: 4c 89 f0 mov %r14,%rax // r14 = rsp rax = r14 = rsp40115b: b9 07 00 00 00 mov $0x7,%ecx // ecx = 0x7401160: 89 ca mov %ecx,%edx // edx = ecx = 0x7401162: 2b 10 sub (%rax),%edx // edx = 0x7 - numx401164: 89 10 mov %edx,(%rax) // numx = 0x7 - numx401166: 48 83 c0 04 add $0x4,%rax // rsp + 4 ++num;40116a: 48 39 f0 cmp %rsi,%rax // rsi = rsp + 24 循环6次 numx = 0x7 - numx40116d: 75 f1 jne 401160 <phase_6+0x6c>40116f: be 00 00 00 00 mov $0x0,%esi // esi = 0 ecx = 0x7401174: eb 21 jmp 401197 <phase_6+0xa3> // jmp 401197
第三部分的话 就有点难了 因为这部分需要先理解
4011ab
到最后在干什么了 看解析之前 建议认认真真的仔细分析完401176-4011a9
在干什么内容再看 如果没理解的话 我们就暂且认为是在把某个结构体的地址放到rsp + 32 - rsp + 72 每个地址8字节 刚好6个
的位置 所以我们先看看第四部分的代码 第三部分的话 后面会给的
在看下面的代码之前 可能大家已经注意到了一个神秘数字
0x6032d0
我先说一下我怎么注意到的 因为我是逐行逐行分析的 先分析完第三部分 分析的模模糊糊后 就看最后一部分 就发现我们的数据访问了这个地址 则我们先gdb
调试看一下
此时xdm 有没有什么感觉了 如果此时你已经感觉呼之欲出了 就不用再往下看了 赶快去继续完成你的
phase
如果还是模模糊糊的 那我下面就要介绍一下了 我看到这里的时候 就发现后面的数字 就是最后8字节数字每次都加了16字节 有点像指针数组
指针数组是通过前面的node1、2、3...
看出来 访问的是一个结构体
此时不妨打破沙锅问到底 我们再访问6304480看看 即node1
的指针 看看能不能有数据
此时结果已经很明显了 这个指针指向的是下一个节点 node2 我们如果访问
6304496
得到的会是node3
先出现 则我们可以推测出 前面的332、168、924
是节点数据1 2 3
是节点编号 最后8字节是next
指针
在完成上面的推测后 我们再来看看第四部分的代码 注释很详细的写出了 链表修改的全过程 这部分的工作就是根据我们第三部分的向栈中放结构体地址的顺序 而修改链表的顺序 仔细看看注释 最后再给出第三部分
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx // rbx = 第一个我们认为的node地址 4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax // rax = rsp + 40 指向第二个node的地址4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi // rsi = 指向结束位置 rsp + 80堆栈4011ba: 48 89 d9 mov %rbx,%rcx // rcx = 第一个node地址4011bd: 48 8b 10 mov (%rax),%rdx // rdx = 第n(2,3,4,5,6)个node地址(会循环5次)4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) // 指针指向第n(2,3,4,5,6)node 则该链表的next地址修改4011c4: 48 83 c0 08 add $0x8,%rax // rax += 8 则rax移向第n+1(4011c8: 48 39 f0 cmp %rsi,%rax // 当rsi = rsp + 80(当已经遍历完了2,3,4,5,6)node 后 即停止循环 进入验收// 结束则意味着链表已经按照我们输入的方式已经排序好了 没有遍历完4011cb: 74 05 je 4011d2 <phase_6+0xde> // rax = rsp + 0x50 = rsi4011cd: 48 89 d1 mov %rdx,%rcx // 移动向第n个节点(2,3,4,5) 4011d0: eb eb jmp 4011bd <phase_6+0xc9> // 循环4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) // 末尾链表next 为 NULL 则设置为0x04011d9: 00 4011da: bd 05 00 00 00 mov $0x5,%ebp 4011df: 48 8b 43 08 mov 0x8(%rbx),%rax // 0x8(%rbx) 为链表第二项的地址4011e3: 8b 00 mov (%rax),%eax //链表第二项头部数据4011e5: 39 03 cmp %eax,(%rbx) //链表第一项与第二项的数据比较4011e7: 7d 05 jge 4011ee <phase_6+0xfa> //第一项数据大于或等于第二项则成功!4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb>4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx4011f2: 83 ed 01 sub $0x1,%ebp4011f5: 75 e8 jne 4011df <phase_6+0xeb>4011f7: 48 83 c4 50 add $0x50,%rsp4011fb: 5b pop %rbx4011fc: 5d pop %rbp4011fd: 41 5c pop %r124011ff: 41 5d pop %r13401201: 41 5e pop %r14
第四部分已经介绍完了 第三部分相对来说 就没有那么难了 因为最后这部分的链表修改 就根据这部分的代码来修改
下面的注释写的很清楚了 大家看注释应该就能看懂了
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx40117a: 83 c0 01 add $0x1,%eax //如果为3 则会循环到node1->node2->node3 最后rdx会为node3结构体的地址40117d: 39 c8 cmp %ecx,%eax40117f: 75 f5 jne 401176 <phase_6+0x82>401181: eb 05 jmp 401188 <phase_6+0x94>401183: ba d0 32 60 00 mov $0x6032d0,%edx // edx = 0x006032d0 当ecx = 1时才会来到这里401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) // edx = rsp + 2*rsi + 32 核心代码****** 写链表地址顺序的40118d: 48 83 c6 04 add $0x4,%rsi // rsi += 4401191: 48 83 fe 18 cmp $0x18,%rsi // 循环6次401195: 74 14 je 4011ab <phase_6+0xb7>401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx // ecx = rsp + rsi 则为遍历1 2 3 4 5 6号输入数字40119a: 83 f9 01 cmp $0x1,%ecx // origin: ecx = numx = (0x7 - (origin)numx)40119d: 7e e4 jle 401183 <phase_6+0x8f> // 如果numx <= 1的话 就跳转 jle // 透露一点的话 就是你认为链表位置排名第五的话 你输入2 得到 ecx = 5 最后放入栈的则是node5的地址40119f: b8 01 00 00 00 mov $0x1,%eax //eax 后面做循环4011a4: ba d0 32 60 00 mov $0x6032d0,%edx4011a9: eb cb jmp 401176 <phase_6+0x82> //跳入第四部分做最后的处理
最后我们再来总结一下干了什么 最后再来得到我们的答案
1、numx = 0x7 - numx
2、我们需要链表逆序(因为第四部分要求 链表第一项数据 > 第二项数据)
3、我们根据gdb
调试 看地址0x6032d0
得逆序顺序应该是3 4 5 6 1 2
又因为这个顺序 是经过了numx = 0x7 - numx
则原输入数据应该是4 3 2 1 6 5
好了 到了验收时间 经过上面的分析 得出来了结果4 3 2 1 6 5
看一下对不对
yeah! we finally make it!
我们还可以gdb
看看链表中的指针
这个是在jge 0x4011e7
处断的点 我们可以看出来是这样得
Phase Secret(彩蛋Phase)
其实如果仔细点 细心点的hxd
就可以发现 这个Lab
是有很多细节的 比如你在输入的时候 点了ctrl+c
的时候 会出现下面的输出
当时不小心点到了 就发现这个 真的很有趣 尽管很多地方我都夸赞了老美老师做这些Lab
的用心与沉浸 确确实实也带给我们很多很愉快的过程 这些Lab
解决的过程 可能在之后很多年回想起来 还是会觉得特别有趣 ^^
这里先加一点预告吧 因为这篇博客篇幅太长了 所以我把Secret Phase
单独作为一篇博客来写吧 因为在我把大一没有完成的Phase 6
完成的时候 长吐了一口气 认为已经完成Lab
的时候 忽然想看一下Bomb.c
里面是怎么样的时候 我忽然看到了一段注释 那个时候 我就知道 Bomb Lab
的历程还有最后一段路没有走 解决Secret Phase
的博客链接我会在下面放着 对这个隐藏彩蛋Phase
感兴趣的hxd
就可以来看看 那各位下篇博客再见啦!
CSAPP Lab2实验记录 ---- Bomb Lab(Secret Phase彩蛋解析)
前提预警 彩蛋Phase难度跟Phase 6差不了多少