终于要开始做大名鼎鼎的BombLab了!
首先是一些准备工作
lab下载地址:http://csapp.cs.cmu.edu/3e/labs.html
第二个的Bomblab的 self-study handout就是
在做这个lab前,首先要确定使用的调试工具。我试过gdbtui(难用,显示有问题)和ddd(难用+丑),最后选择了cgdb。
cgdb最新版本增加了显示汇编代码的功能,和bomblab搭配的很棒。
目前(2017/11/21),apt-get源下载到的cgdb仍然不是最新版本,需要到官网下载:
不知道为什么github clone之后,make总报错,所以我选择了从官网下载文件然后按照它的install说明文件编译安装。
在安装完后,在terminal中输入cgdb, 就可以打开cgdb了。
首先要大概看一下cgdb的中文手册,如果熟悉vim的话大概10分钟就能看完。(什么你不会用vim?那你还是去用gdbtui吧)
当然你还需要一些必须的x86-64汇编知识(CSAPP第三章) 以及 一份gdb简易使用指南
http://csapp.cs.cmu.edu/3e/docs/gdbnotes-x86-64.pdf
Phase1
在最新版本的cgdb中,在cgdb模式下输入
:set disasm
可以显示反汇编代码(程序必须处于运行状态),如图:
此处我使用ctrl + W实现了左右分屏
注意到程序需要从stdin或文件中读取输入,所以我们需要给程序指定输入,避免程序卡死在读取输入的system call上。
(gdb)run <in
in文件是之前已经解出的炸弹内容;没有解出来的就随便写好了(反正我们的炸弹不会连接到服务器然后爆炸扣分,所以炸多少次都可以)
设置断点,逐条语句运行到phase1()函数,然后使用
(gdb) stepi
单条指令调试工具,进入phase1()函数,查看该函数反汇编代码。
(用gdb的同学可以用disas命令,具体的看上面的gdb指南)。
从第四行往后的内容很容易理解,大概就是调用一个判断两个字符串是否一样的函数,如果返回0(函数名为not equal,所以返回0 就是两个字符串相同),即%eax寄存器内值为0的话就通过,否则引爆炸弹。
我们很容易猜到这个函数要接受两个参数(废话,不然怎么比较两个字符串),其中一个参数是调用phase_1()的参数input,也就是用户输入,另一个参数是奇怪的东西,输出来看看:
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."
好了,Phase1 我们就做完了。
Phase2
相比Phase1, Phase2可以说难了很多。
首先在phase_2()处打上断点:
(gdb) break phase_2
Breakpoint 1 at 0x400efc
然后运行,si逐步调试:
(gdb) run <in
(gdb) si
可以看到这里调用了一个名为read_six_numbers的函数,并且传入了两个参数:一个参数是input的地址(也就是我们输入的字符串),另一个参数是开辟的栈空间的地址。
进入这个函数,发现这个函数里面就可能会引爆炸弹:
具体做的大概看一下,就是传了好多参数,调用sscanf,这些参数还都是地址。
那么这个函数的作用我们就能猜到了:从输入的字符串中读取6个数字出来,并且存储到之前开辟的栈空间内。
而这个函数内引爆炸弹的触发条件,应该就是input是否由6个数字组成(经过测试也的确如此,在把输入改成6个数字后,就能顺利通过read_six_numbers函数)
提示:这里如果陷入了一个共享库函数(sscanf之类),可以用finish命令快速退出
那我们就先把in里phase2的密码暂时改成1 2 3 4 5 6.百度得知sscanf的参数顺序,然后我们依次用print命令输出调用sscanf()的参数是啥,可以发现最后这6个数依次被存储在了
%rsp, %rsp + 4, %rsp + 8, %rsp + 12, %rsp + 16, %rsp + 20
然后我们回到phase_2,按照流程走一遍(很简单,就在纸上写一下几个寄存器值的变化),题目答案就出来了。
Phase3
这一关可以说是很弱智了。
首先还是在phase_3处打上断点,然后stepi逐条指令执行:
(gdb) break phase_3
Breakpoint 1 at 0x400f43
(gdb) run <in
Starting program: /home/fanese/Documents/CSAPP/bomb/bomb <in
反汇编得到的phase_3代码如图所示
看到了熟悉的sscanf()函数。我们可以看到这里用到了
%rdi, %rsi, %rdx, %rcx
四个寄存器。根据x86-64 寄存器传参规则,第一个寄存器存储的就是调用phase_3时的那个input参数,可以不管;第二个参数是关键,指定了格式化字符串读取的格式,这里是一个地址。
所以我们把这个第二个参数输出来看一下:
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"
可以得知这次的输入是两个整数。
好了,我们退出去,修改in文件,把第三行改为1 2, 继续调试。
第八行显示把%eax 和1比较。%eax 存储的是sscanf函数的返回值,百度得知是正确读取到的参数个数,所以为2.
在sscanf执行完毕后,我们根据x86-64寄存器传参规则,可以确定第一个数被存储在%rsp + 8位置, 第二个数被存储在%rsp + 12位置。当然可以在gdb中使用如下命令验证:
(gdb) x/wd $rsp+8
0x7fffffffddc8: 1
继续执行,第11行可以看到将第一个参数与7进行比较。因为这里我们假设第一个参数为1,所以会跳转。
不停的逐指令执行,到第32行,可以看到将第二个参数与0x137(311)比较,如果不等于就引爆炸弹,所以我们退出去,将第二个参数改成311.
然后炸弹就被拆掉了。
Phase4
这个Phase4有一点小难度。
首先还是和之前一样的设置断点,然后查看反汇编源代码:
(gdb) break phase_4
(gdb) run <in
可以看到开始的部分和Phase3是差不多的,所以退出去把in文件的第四行改成1 2两个数。
注意,为了叙述方便,从此处开始,将两个参数称作x y。
接着逐条指令调试,在第10行,有一个x 与 14的比较。因为第12行就会引爆炸弹,所以第11行的条件跳转指令必须执行,也就是 x <= 14.
因为我们假设的x为1, 继续逐步调试。
接下来可以看到,程序设置了几个寄存器的值,并调用了一个名为fun4的函数。根据x86-64寄存器调用规则,可以确定函数的调用情况为:
fun4(x, 0, 4)
传入的是三个int类型变量,所以不用担心这个函数去修改内存,所以我们先看这个函数之后的部分。
如果已经进入了这个函数,可以使用
(gdb) disassemble phase_4
在gdb窗口调出phase_4 函数的反汇编代码。
根据fun4之后的反汇编代码,很容易看出只有当fun4()返回0 且y == 0,才能解除炸弹。
接下来我们深入函数:
注意到只有%rdi 存储了我们的x,而在2 - 9行的反汇编代码都没有出现%rdi, 所以放心的逐步调试,直到第10行,比较%edi和 %ecx的值。
(gdb) print/d $ecx
$17 = 7
查看%ecx寄存器的值,并把这个值作为x, 输入后发现炸弹已经被解除。
Phase5
Phase5就是真的有点难了。
还是和之前一样,设置断点,运行,然后逐指令调试:
经过百度,第四行的内容是和stack-protecter有关的,暂时不管。
可以看到第八行调用了对字符串求长度的函数,之后如果长度不是6就引爆炸弹。所以退出去将in的第五行改为“abcdef”,继续调试。
然后都是一些简单的mov操作,可以先在纸上记录一下。
到了13行,我们需要看一下这句话是什么意思。
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
大概意思是取内存的这个位置的一个字节,然后零拓展到32位,存储到%ecx寄存器里去。
根据之前的操作,%rbx存放的应该是我们input的起始位置,验证一下:
(gdb) x/s $rbx
0x6038c0 <input_strings+320>: "abcdef"
此时的%rax寄存器值为0,那么这条指令也就是把我们输入的第一个字符(a)的ASCII码存储到了%ecx寄存器中。
14-15行把%ecx寄存器的值移到了%ecx寄存器中。(cl是%ecx寄存器的低8位访问方式)
第16行:
0x0000000000401096 <+52>: and $0xf,%edx
把%edx寄存器的高28位清0,只保留低4位。
考虑到%edx里存储的是一个字符的ASCII码,我们得到的是这个ASCII码的低4位。
第17行
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
这条指令的意思是,从(0x4024b0 + %rdx)这个位置取一个字节,进行零拓展之后,得到的值放到%edx里面去。
妈耶,我们怎么知道这是个啥东西?
别急,输出来看一下:
(gdb) x/s 0x4024b0
0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
原来是一个字符串。那么我们做的就是:以输入字符的低4位作为索引,去访问这个字符串的一个字符,并存储到%edx里。
第18行
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
把这个得到的值存储到了内存中,起始位置为%rsp + 16
第19-20行:
0x00000000004010a4 <+66>: add $0x1,%rax
20│ 0x00000000004010a8 <+70>: cmp $0x6,%rax
就是说把上述的步骤重复6次:存储到%rsp + 16 %rsp + 17,......%rsp + 21 的位置。
那么我们就重复执行,看看直到%rax 为6(循环结束),之后bomb会干什么。
可以用watch变量的方式观察%rax的变化:
(gdb) watch $rax
Watchpoint 2: $rax
Old value = 5
New value = 6
然后用
(gdb) delete 2
删除这个watchpoint.
第22行,在%rsp + 22位置插入了一个byte,值为0,其实就是给字符串加上了‘\0’
现在我们可以输出看一下这个得到的字符串是什么了:
(gdb) x/s $rsp+16
0x7fffffffddc0: "aduier"
接下来就进入easy模式了:可以看到调用了strings_not_equal函数,待比较的字符串地址放在了%esi里,那么我们就输出位于%esi的字符串,看看到底是什么:
(gdb) x/s $esi
0x40245e: "flyers"
之后的指令就是说,根据我们输入生成的字符串需要和“flyers"比较。如果相同就可以。
那么我们依次去那个字符串里找'f' 'l' 'y'等字符,然后去ASCII表查询哪个字符的低4位满足条件。
那么Phase5 我们就完成了.
Phase6
这一关的确很有难度!不愧是BOSS关!
建议想尝试的读者空出大于2小时的时间,一口气把这一关做完(作者花了2小时),所获得的成就感是无与伦比的。
好了,废话不多说,我们开始吧。
首先我们看到了我们的老朋友read_six_numbers,所以退出去把in第6行改成1 2 3 4 5 6,再进入程序进行调试。
下面把我们输入的6个数称为a1, a2, a3,a4, a5, a6
在掉用完这个函数后,我们输入的6个数被依次存储在
%rsp, %rsp + 4, %rsp + 8, %rsp + 12, %rsp + 16, %rsp + 20
接着我们逐指令执行,发现之后的代码依次保证了如下的条件,如果不满足就引爆炸弹:
a1 <= 6; a1 != a2; a1 != a3; a1 != a4; a1 != a5; a1 != a6
然后我们会发现第二次执行和之前差不多,只是条件改成了
a2 <= 6; a2 != a3; a2 != a4; a2 != a5; a2 != a6 ;
所以这一段代码的意思是所有输入的数不能相同,并且都小于等于6.
接下来的代码比较难看懂了。。主要是这一句:
mov 0x8(%rdx),%rdx
仔细想想CSAPP讲过的结构体对齐,就知道这是一个链表
Node* ptr = &A;
ptr = ptr -> next;
的汇编形式代码。
那么首先我们要把这个链表每个节点的值和地址都写在纸上。
下面一长串指令其实就是根据我们输入数的值创建对应的链表节点,每个值和一个节点的地址、值对应。也就是说,我们给根据输入的值得到了一个链表。
再接下来的代码实际上把得到的链表进行了排序——使得第i个链表对应我们的第i个输入。
最后是一个判断:假如经过排序的链表的值是严格单调递增的,就解除了炸弹。
那么,我们根据链表每个节点的val域的值,就得到了这个链表的顺序,以及输入的值。
因为最后一个phase是几天前做的了,现在回忆具体细节已经不太清楚,但是相信读者只要坚持,一定能解除炸弹的。
终于要开始做大名鼎鼎的BombLab了!的更多相关文章
- 用C# BigInteger实现的BigDecimal类,终于可以直接做四则运算了。
https://code.google.com/p/dotnet-big-decimal/ 这是个BigDecimal类的开源项目,支持Operators +, - and *. 俺给改了改,加上了除 ...
- 终于等到你---订餐系统之负载均衡(nginx+memcached+ftp上传图片+iis)
又见毕业 对面工商大学的毕业生叕在拍毕业照了,一个个脸上都挂满了笑容,也许是满意自己四年的修行,也许是期待步入繁华的社会... 恰逢其时的连绵细雨与满天柳絮,似乎也是在映衬他们心中那些离别的忧伤,与对 ...
- 做个流量站-聚茶吧, 汇聚"茶"的地方
犹豫了好久,终于下定决心,做一回个人站长了,虽然没啥经验,但毕竟也是IT科班出身了,准备用一年的事件摸索一下内容站和SEO,看看能否积累点经验,赚点小钱. 推酷-靠爬虫起家的内容站 做内容站,站长们都 ...
- Windows+Nginx+IIS做图片分布式存储详细步骤
最近几天,一直在学习nginx在windows平台下的使用,为了寻找几种大量图片分布式存储而且有相对简单的存储方案 nginx是一种,还找到一种MongoDB GridFS 这两种方案我还是比较中意的 ...
- [DForm]我也来做自定义Winform之另类标题栏重绘
据说得有楔子 按照惯例,先来几张样例图(注:为了展示窗口阴影效果,截图范围向外扩展了些,各位凭想象吧). 还要来个序 其实,很多年没写过Winform了,前端时间在 ...
- BJOI做题记录
BJOI做题记录 终于想起还要做一下历年省选题了2333 然而咕了的还是比做了的多2333 LOJ #2178. 「BJOI2017」机动训练 咕了. LOJ #2179. 「BJOI2017」树的难 ...
- 为什么推荐你用 Kotlin语言?
谷歌大牛说:为什么 Kotlin 比你们用的那些垃圾语言都好 原标题:谷歌大牛说:为什么 Kotlin 比你们用的那些垃圾语言都好 编译:伯乐在线/黄小非 [伯乐在线/程序员的那些事 导读]:5月18 ...
- 【BZOJ-1146】网络管理Network DFS序 + 带修主席树
1146: [CTSC2008]网络管理Network Time Limit: 50 Sec Memory Limit: 162 MBSubmit: 3495 Solved: 1032[Submi ...
- Hello, cnblogs !
来博客园的第一天. 大学毕业三年多了,感觉自己碌碌无为,不知道自己究竟想做什么,就这样浑浑噩噩过了三年多. 记得大三那年,为了准备大四的毕业设计,自学了好几个月的Java EE.Java基础.Java ...
随机推荐
- 阶段3 3.SpringMVC·_04.SpringMVC返回值类型及响应数据类型_4 响应之返回值是ModelAndView类型
ModelAndView是SpringMvc提供的一个对象 ModelAndView底层源码用也是ModelMap.ModelMap实现过Model的接口 ModelAndView可以直接new出来. ...
- C++函数传值问题
在做题出现个神奇的事情,C++的传值跟其他OOP语言不一样.首先做个测试,看看下面输出结果是什么? void F(int a,int b,int c){ cout<<a<<b& ...
- js判断字符串是否为JSON格式
不能简单地使用来判断字符串是否是JSON格式: function isJSON(str) { if (typeof str == 'string') { try { JSON.parse(str); ...
- app测试自动化操作方法之二
3.进行APP的滑动操作 方法一:#获取窗口大小def get_size(): size=dr.get_window_size() return size print(get_size())#向上滑动 ...
- MATLAB实现OTSU
目录 1.OTSU算法原理简述: 2.MATLAB实现代码 @ 1.OTSU算法原理简述: 最大类间方差是由日本学者大津(Nobuyuki Otsu)于1979年提出,是一种自适应的阈值确定方法.算法 ...
- CentOS 7 分区
必须的分区 boot分区: 作用:引导分区,包含了系统启动的必要内核文件,即使根分区顺坏也能正常引导启动 一般这些文件所占空间在200M以下, 分区建议:分区的时候可选100M-500M之间,如果空间 ...
- linux环境jdk+tomcat搭建
一.什么是Linux? 和Windows操作系统软件一样,Linux也是一个操作系统软件.但是和Windows不同的是,Linux是一套开放源代码程序的.并可以自由传播的类Unix操作系统软件(Uni ...
- Flume下载安装
下载 可以apache官网下载flume的安装包 下载时注意,flume具有两个版本,0.9.x和1.x,两个版本并不兼容,我们用最新的1.x版本,也叫flume-ng版本. 安装 解压到指定目录即可 ...
- shell - python 函数式编程 -- 经典例子 + 让数据自增 + while + > /dev/null 2>&1 & crontab
1.shell #!/bin/bash anynowtime="date +'%Y-%m-%d %H:%M:%S'" NOW="echo [\`$anynowtime\` ...
- PTA(Basic Level)1087.有多少不同的值
当自然数 n 依次取 1.2.3.--.N 时,算式 ⌊n/2⌋+⌊n/3⌋+⌊n/5⌋ 有多少个不同的值?(注:⌊x⌋ 为取整函数,表示不超过 x 的最大自然数,即 x 的整数部分.) 输入格式: ...