《深入理解计算机系统》CMU15213-BombLab学习笔记

前言

在皓哥的鼓励下,磕磕绊绊断断续续终于做完了BombLab,这个实验确实很有趣而且对我帮助很大,做完也非常的有成就感(HGNB),因此决定写一篇博客记录一下学习的过程

首先作几点说明
1、由于每个学生的Bomb是随机的,而我是从网上其他人的github下载的lab,所以有可能你的Bomb与我并不一样,导致每个阶段的答案可能是不一样的,但是方法应该还是可以参考的
2、这个实验对提升汇编语言的理解能力以及自己调试代码的能力真的有很大帮助,所以强烈建议你自己独立思考并完成这个实验,即使多花些时间我觉得也是值得的


常用gdb命令

常用的命令可以在CSAPP书上的3.10.2节查阅,也可以在课程的pdf上找到,下面是我在拆弹过程中用的较多的命令:(首先在终端输入gdb bomb启动gdb)

  • run: 运行程序。较为有用的方式是带参数启动,例如run solutions.txt,其中solutions.txt存有你已经完成的阶段的答案,这样可以避免在攻略后阶段时每次都要输入前面阶段的答案。另外如果程序已经启动,gdb会提示你是否要从头开始运行,可以用这个方法避免我们被炸死(虽然我们不会因此扣分,但紧张感还是要有的 )
  • b: 设置断点。常用形式是b functionNameb *0xffffffff,分别用来在函数入口处设置断点和在某个地址设置断点。这条命令执行后会提示breakpoint x at xxxxxxx,然后可以用delete x来删除该断点,disable/enable x来禁用和启用断点。直接输入delete可以一次性清除所有断点
  • continue: (在程序停下时)继续程序
  • disas: disas functionName查看某个函数的汇编代码
  • stepi/stepi n: 单步运行1步/n步(会进入函数)
  • nexti/nexti n: 单步运行1步/n步(不会进入函数)
  • info registers: 显示所有寄存器的值
  • print: 打印信息。print $rdx打印出rdx寄存器的值,print *(int*)0xffffffff打印出0xffffffff处的整数值
  • x: 检查信息。x /s 0xffffffff检查0xfffffff处的字节,x /20d 0xffffffff检查0xffffffff开始的20个4字节并用十进制输出

在开始拆弹前,我们可以先查看函数的源代码bomb.c
《深入理解计算机系统》CMU15213-BombLab学习笔记_第1张图片
可以看到这个程序一共有六个阶段,每一阶段会读取我们的输入并作为参数传递给当前阶段的函数,因此我们可以分别查看phase_1~phase_6的汇编代码来推测每一阶段的答案


Phase 1

函数phase_1的汇编代码如下:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第2张图片
可以看出,它将%esi置为0x402400然后调用了strings_not_equal函数,并比较结果,如果为0则返回,否则引爆。那么很自然的推测我们只需要输入这个炸弹相同的字符串即可,这里使用x /s命令有奇效:
phase_2

所以第一阶段直接输入这个句子即可


Phase 2

函数phase_2的汇编代码如下:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第3张图片
观察这个函数,我们会发现第九行调用了一个函数read_six_numbers,那么可以推测这一阶段需要输入6个数字,我们可以先随便输入6个数字试试(我输入了1 2 3 4 5 6)然后将程序运行到0x0000000000400f0a这一行,我们看到第14行拿rsp所保存的地址对应的数与1进行了比较,因此我们可以先查看一下这里放的是些什么:
phase_2_rsp
可以看到,从栈指针地址往上连续存放了我们输入的六个数的地址(这个发现会多次用到),因此(%rsp)对应的就是输入的第一个数,由此断定第一个数必须是1,否则就会跳转到引爆炸弹的函数。

确定了第一个数再继续看后面的部分,跳转到52行之后程序将rbx置为rsp+4的地址,rsp置为rsp+24的地址,正好是第二个数的起始地址到第六个数的地址的最后,可以猜测这里应该是在为循环做准备

接着程序跳转到27行,将eax置为-0x4(rbx),也就是第一个数,下一个指令将eax的值翻倍,再下面一条指令将eax的值与rbx对应的数进行了比较,如果不相等则引爆炸弹

到这里已经大概可以猜到,这六个数应该是以1为首项,2为公比的等比数列,所以输入1 2 4 8 16 32,第二阶段完成


Phase 3

函数phase_3的汇编代码如下:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第4张图片
第三阶段的函数看起来有点长,我们可以慢慢分析一下。首先看到第24行调用了sscanf来读取输入,然后判断eax是否大于1,如果不是则引爆,我们可以根据这个确定应该输入多少个字符。先输入1个数字然后运行到第29行查看一下:
phase_3_eax
可以看到输入一个数的时候eax的值为1,不满足要求,那么我们可以推测这一阶段应该是要输入两个数字

再看后面,首先比较了0x8(%rsp)与7的大小,如果超过7会直接引爆,然后程序跳转到了一个不知名的地方,并且以输入的第一个数*8为索引的偏移量,而且查看下面的代码我们会发现有很多行对eax的赋值语句以及一个跳转语句,结合上面所有的线索可以联想到这一大段应该是一个switch语句,会根据不同的分支给eax赋不同的值,最后都会走到拿它跟0xc(%rsp)比较。

根据上一题的经验,这个地方的值应该是我们输入的第二个数。所以这一阶段,我们只需要任选某一个分支,找到这个分支对应的值就行了。比如输入1之后,我们逐步单步运行,会发现程序走到了0x0000000000400fb9 <+118>: mov $0x137,%eax这一行,那么第二个数就应该输入0x137也就是311,当然用别的分支也是可以的


Phase 4

函数phase_4的汇编代码如下:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第5张图片
这一阶段我们还是先来分析输入是什么格式,根据上一阶段的经验,这里我们从第29行就能看出,输入应该也是两个字符,而且如果第一个数大于14就会直接引爆。

然后后面调用了一个叫func4的函数,接着测试eax是否为0,如果不为0则引爆。所以我们的目标就是要让func4得到的结果是0,这里可以试着查看func4的汇编代码,并分析怎样的输入可以得到0。不过我并没有看懂它的逻辑,所以我是直接通过一个个尝试发现,输入为7、3、0的时候返回结果为0。最后还有个比较0xc(%rsp)是否等于0的语句,所以第二个输入为0即可

于是这一阶段也解决了,感兴趣的朋友可以试着分析一下func4的映射关系究竟是什么样的


Phase 5

函数phase_5的汇编代码如下:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第6张图片
这一阶段的函数也比较长,我们还是一步步分析。首先从29行以及调用了string_length可以知道,我们需要输入的字符长度应该是6。接下来的41到70行进行了循环,把我们输入的字符串作为索引,从0x4024b0这个地址取了一些字符存放到了rsp+16rsp+22的位置,然后又调用了strings_not_equal比较两个字符串,所以这里我们先看看0x4024b0放的是啥:
0x4024b0
可以看到是Dr.Devil的一句垃圾话
然后再看看另一个字符串是什么:
flyers
所以我们需要用上面那个很长的字符串拼出下面这个单词,很容易可以知道9对应f,567对应ers。但是l和y都不能用数字得到,所以我是将小写字母一个个输入看看会得到什么来求出ly对应的源,这里我的答案是9on567,应该也是不止一种答案


Phase 6

终于到了最后一个阶段,而phase_6的代码也没有让我们失望:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第7张图片《深入理解计算机系统》CMU15213-BombLab学习笔记_第8张图片
——长度甚至超出了一页。这一阶段也是最为困难和花时间的一个阶段,我们可以根据各种jmp的循环,将函数大致分成几个部分,然后逐个分析

第一部分

首先很明显,第18行告诉我们输入应该是6个数字。根据前面的经验,我们输入的6个数字应该位于rsp ~ rsp+24这块区域内

然后通过观察,我们可以发现,32行到93行应该是一个循环,这个循环让r13d每次加4从而用eax遍历输入的6个数。而且对每个数字减1之后和5进行了比较,如果大于5就会引爆,所以这6个数都要小于等于6。r12d作为索引每次加一,到6跳出循环

接下来从62行到87行可以看出应该是更深层次的一个循环,这里用ebx对当前元素后面的元素进行了遍历,并且跟当前数进行了比较,如果相等则引爆炸弹,也就是说每个数后面的数都不等于这个数

所以这几行总结一下就是:输入的6个数≤6且互不相等,即它们是1到6的一个排列

接着100到121行又是一个循环,这几行比较容易,可以看出是用7-x替换了x。到这里可以看作是第一个部分,也就是对输入的限制和处理

第二部分

第二部分大体上还是一个循环,依然是用ecx去遍历了栈上的6个数,rsi每次增加4,如果到了24则跳出循环,这个循环做的事情就是在rsp+32开始每8个字节存一个地址。我们可以重点关注一下这行指令:

   0x0000000000401176 <+130>:	mov    0x8(%rdx),%rdx

它取了rdx+8这块内存,又赋给了rdx,是不是觉得很像链表的node = node->next?实际上我们可以查看一下这块内存来验证一下:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第9张图片
注意到,0x6032d8存放的数值0xe0 0x32 0x60正好就是第三行的地址0x6032e0,因此这块内存其实就是一个链表的结构!前4个字节是一个整数,下面4个字节可能是id,最后8个字节是下一个节点的地址。有了这个发现之后,我们剩下需要做的就是分析代码存放地址的规律

从163 166行可以知道,这段程序先将当前值v与1进行比较,如果是1就直接将起始地址0x6032d0放到rsp+32开始偏移量为索引*8的地址去(143,148行。索引表示的是当前在处理第几个数字),否则就找链表的下一个,一直到第v个节点(130~137行)。

总结一下:假设当前的6个数分别为x1, x2, x3, x4, x5, x6,那么rsp+32开始的这段空间分别存放第x1个节点的地址,第x2个节点的地址……第x6个节点的地址。到这里可以看作第二个部分,也就是节点地址的存放

第三部分

最后一部分就是从183行开始到程序的结束,也就是我们需要满足的条件了。不难看出raxrsi被用作了循环的起始和终止条件,183到212行是一个循环,用rax来遍历后面5个地址:

  1. rcx初始为rsp+32所指的节点,rax初始为rsp+40
  2. rdx=rax所指的节点
  3. rcx的next=rdx
  4. rax+=8
  5. rcx=rdx

总结一下就是:按照6个地址的顺序依次给链表排了序,也就是说rsp+32所指向的节点将成为头节点,它的下一个节点是rsp+40所指向的节点,以此类推

230到257行是最后一个循环,依次将后一个节点的数值跟当前节点的数值进行了比较,如果比当前节点大就引爆炸弹,因此我们构造好的链表应该是降序排列。根据之前检查的节点的值,它们的大小顺序应该是:3(0x39c)->4(0x2b3)->5(0x1dd)->6(0x1bb)->1(0x14c)->2(0xa8),反推我们的x1~x6分别为:3,4,5,6,1,2,所以我们最初的输入应该是7减去它们,也就是4,3,2,1,6,5。至此,最后一阶段也成功解决!


总结

这次实验累积的经验如下(不一定正确,欢迎探讨)

  1. 看汇编代码时,可以根据循环、函数调用等等把整体分成一个个小的部分再去分析
  2. 有的地方看不懂可以先不去探究细节,把某些代码看作一个整体再去思考
  3. 没有头绪时多试试各种输入
  4. 多使用printx命令查看内存的状态

附上最终答案和运行截图:
《深入理解计算机系统》CMU15213-BombLab学习笔记_第10张图片

你可能感兴趣的:(CSAPP读书笔记)