首先是一些准备工作

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仍然不是最新版本,需要到官网下载:

https://cgdb.github.io/

不知道为什么github clone之后,make总报错,所以我选择了从官网下载文件然后按照它的install说明文件编译安装。

在安装完后,在terminal中输入cgdb, 就可以打开cgdb了。

首先要大概看一下cgdb的中文手册,如果熟悉vim的话大概10分钟就能看完。(什么你不会用vim?那你还是去用gdbtui吧)

cgdb中文手册

当然你还需要一些必须的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是几天前做的了,现在回忆具体细节已经不太清楚,但是相信读者只要坚持,一定能解除炸弹的。

最新文章

  1. 用css3做一个正方体
  2. replace和replaceAll
  3. 为什么需要DTO(数据传输对象)
  4. VS为VC++添加UAC控制(VC程序默认管理员运行)
  5. 全代码实现ios-1
  6. iOS 基础 第四天(0809)
  7. 浅谈angular框架
  8. 对discuz的代码分析学习(三)mysql驱动
  9. Cygwin在线安装指南
  10. 【分布式系列之dubbo】dubbo管理工具dubbo-admin安装使用
  11. kali linux中的yum、rpm常见的问题
  12. php Header 函数使用
  13. vim基本命令总结
  14. Ubuntu释放磁盘空间的几种常用方法
  15. SSH免密远程登陆及详解
  16. T-SQL :TOP和OFFSET-FETCH筛选 (五)
  17. border绘制三角形
  18. 关于Android系统的启动流程
  19. Python中浅拷贝和深拷贝的区别总结与理解
  20. java中对JVM的深度解析、调优工具、垃圾回收

热门文章

  1. Volley源码分析
  2. Host key verification failed
  3. Ceph 的用户管理与认证
  4. Setup Python 开发环境和IPython的基本使用
  5. ras 加密及解密
  6. vs在微软官方tfs创建私有项目过程
  7. 【转】jstat命令查看jvm的GC情况 (以Linux为例)
  8. HTTP请求的python实现(urlopen、headers处理、 Cookie处理、设置Timeout超时、 重定向、Proxy的设置)
  9. ERROR】Unable to open underlying table which is differently defined or of non-MyISAM type or ...
  10. 常见IE6兼容问题总结