班级:
姓名:
学号:
一、bomblab相关介绍
1.简介
此次实验要求我们使用课程所学知识拆除“binary bombs”,增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。
一个“binary bombs”(二进制炸弹)是一个Linux可执行程序,包含了6个阶段。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信即解除了,否则炸弹“爆炸”打印输出 "BOOM!!!"。
实验的目标是拆除尽可能多的炸弹层次。 每个炸弹阶段考察了机器级程序语言的一个不同方面,难度逐级递增:
①阶段1:字符串比较
②阶段2:循环
③阶段3:条件/分支
④阶段4:递归调用和二分查找
⑤阶段5:指针
⑥阶段6:链表/指针/结构
⑦隐藏阶段:只有在第4阶段的解后加特定字符串才会出现
为完成二进制炸弹拆除任务,我们需要使用gdb调试器和objdump来反汇编炸弹的可执行文件并跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。比如在每一阶段的开始代码前和引爆炸弹的函数前设置断点。
实验语言:C;实验环境:Linux
2.实验步骤
2.1.获取bomb
①找到以tar文件的形式bomb187.tar,187是bomb的标识
②解压该tar文件(tar -xvf bomb187.tar)
得到一个目录./bomb187,其中包含如下文件:
README:标识该bomb和所有者(匹配自己的学号)。
bomb:bomb的可执行程序。
bomb.c:bomb程序的main函数。
2.2.拆除bomb
本实验的任务就是拆除炸弹。一定要在指定的虚拟机上完成作业,在其他的环境上运行有可能导致失败。
①运行./bomb可执行程序需要0或1个命令行参数(详见bomb.c源文件中的main()函数)。如果运行时不指定参数,则该程序打印出欢迎信息后,期望按行输入每一阶段用来拆除炸弹的字符串,根据当前输入的字符串决定是通过相应阶段还是炸弹爆炸导致任务失败。
②也可将拆除每一阶段炸弹的字符串按行组织在一个文本文件中,然后作为运行程序时的唯一一个命令行参数传给程序,程序读入文件中的每一行直到遇到EOF,再转到从stdin等待输入。这样对于已经拆除的炸弹,就不用每次都重新输入,只用放进文件里即可。
③要学会单步跟踪调试汇编代码以及学会设置断点。你还要学会如何检查寄存器和内存状态。很好的使用调试器是你在未来的职业生涯中赚到更多money的一项重要技能!
二、工具使用
①gdb调试工具
②objdump反汇编工具
三、准备工作
1.打开bomb.c文件
发现main函数依次调用了phase_1到phase_6六个函数,但函数的具体代码被隐藏。可以知道从命令行输入的内容必须和phase函数里面的一样,否则炸弹爆炸。
2.反汇编可执行文件bomb
objdump -d bomb > a.txt
这样就可以在bomb.asm里面看到整个文件的汇编代码。
(1)汇编代码及逐条分析
- 建立栈帧
②将地址参数到内存,其中一个是我们输入的字符串,还有一个是0x804a1c4地址存放的内容,存放到了esp+4处。
- 而调用函数之前,一般都是把这个函数要用的参数先存好,而 string_not_equal()函数作用是判断两个字符串是否相等,那么我们在此处用gdb分别查看两个参数值。
这个函数就是将输入的字符串和存好的字符串比较,如果相等则拆除成功,如果不相等就爆炸,BOOM!
可以看到,我在phase_1设置了一个断点,运行phase_1函数,在gdb里查看两个地址
存储的值
我输入的字符串是man! What can I Say?
可以看到寄存器%eax存的就是我输入的man,而地址0x08048b50存的就是答案
测试发现炸弹拆除
第二题
(1)汇编代码及逐条分析
①首先,我们发现函数调用了read_six_numbers,该函数是读取六个输入数字,我们研究一下汇编代码:
我们不难发现,在调用完sscanf函数之后,里吗将返回值与5比较,如果输入个数大于5则返回,小于等于5爆炸。
回到phase_2函数,我们发现储存的数据结构就是一个数组。
一开始把第一个数和0比较,不相等爆炸;第二个数和1比较,不相等爆炸;第三个数放在ebx寄存器里,接着进入循环。
在循环里,每次都要比较前两个数相加是否等于第三个数,不相等就爆炸。之后ebx寄存器指向第四个数,比较第二个和第三个数相加是否等于第四个数,不断循环到6。
到这里我们可以看出来答案一个斐波那契数列。开头是0,1。
需要注意的是,题目只要求前六个数是斐波那契数列即可。那么我们写7个,8个数也行,只要前六位正确,后面的数字可以不遵循规则。
第三题
(1)汇编代码及逐条分析
观察汇编代码,我们发现开辟栈帧后,传了两个参数进去,并且调用输入函数,判断返回值,大于1才不会爆炸。因此我们输入的数要大于1。
接着查看地址0x804a3e3,发现确实要输入两个数
继续往下看,我们发现要把输入的第一个数和7比较大小,如果超过7就跳转爆炸,所以第一个数要小于7
下面紧跟着一个跳转表,查看跳转表地址0x804a220,发现跳转表。根据第一个数找到对应跳转地址,发现会赋值,如果输入第二个数和赋值相等才不会爆炸
对应情况:0 562 ;1 949 ;2 341 ;3 323 ;4 167 ;5 91 ;6 534 ;7 655
第四题
(1)汇编代码及逐条分析
我们发现第四个函数同样保存了两个传参,并且调用地址0x84a3e3。那么跟第三题一样需要输入两个数。
果然后面如果调用sscanf返回值不是2就爆炸
接着调用函数func4,传入参数输入值1,0,e。即func4(x , 0 , e);
最后规定返回值必须为7,且第二个输入值要为7。
因此我们要找在func4返回7的输入值
我直接看汇编代码没有完全理解,因此就翻译成函数
用dev写出来
这下看懂了吧,func4是一个二分法递归函数
接着依次实验,发现输入为14结果为7。
第五题
(1)汇编代码及逐条分析
首先发现传了两个参数又调用了sscanf函数,那么可以认为要输入两个数字,果然后面判断输入了几个数,>1则跳转,≤1则爆炸。
接着进入一个循环,把x传入eax,并只保留低四位,若此时eax=15,则爆炸,!=15则不爆炸,edx自加一,用来记录循环的次数,防止越界超过15。
循环里把0x804a240+4*eax处的值保存在eax里,并且用ecx来累加每一次访问的值,若当前访问的值!=15,则继续循环。
直到当前访问的值=15,退出循环,再判断edx的值,进而判断是否访问了15次,最后再判断15个值累加的结果ecx是否等于我输入的第二个参数,等于则顺利结束函数,否则爆炸。
查看0x804a240地址所存放的数,发现对应数组。
发现循环每次都要累加1,在数组的值为15时跳出循环,且在结束循环后,必须要保证循环了15次,这样我们只能倒推。
得到5 累加115
第六题
我们可以发现第六题题目很长,但是可以分成几个函数功能段。
例如第一段初始化,要求输入六个数,这六个数不能超过6,而且两两不等。那么就是输入1~6。
第二阶段把输入的每一个数变成7-数本身即x=7-x
第三阶段根据我们的6个数的顺序,把每一个节点自己的地址存到一个新开辟的地方去
第四阶段根据我们新开辟的空间里面存放地址的顺序,来重新构建链表的节点顺序,实际上的操作就是更换每一个节点中,存放的下一个节点的地址,来重构链表顺序,注意末尾节点存放的下一个节点的地址为0。
第五阶段检查此时的链表是否权值从前往后,是按照递减的顺序进行的,只有这样才不会爆炸
进一步研究储存的数据结构,发现是链表储存,查看链表地址
根据地址,我们就得到了链表的权值递减标号排序,但是还要把每一位x转换成7-x
得到答案:2 4 6 5 1 3
第七题
本来以为做到这里就结束了,结果还有一个隐藏题目。
在phase函数里,写入命令finish,结束phase,则会立马进入phase_defused()
而第七题函数又会调用phase_defused(),于是查看
发现每次输入一条字符串,它每次的值就+1,好了,记录我当前输入了几条指令。
接着又发现一个地址0x804a3e9,0x804c4d0,0x804a3f2。
原来我们在第四题要输入两个数和一个给定字符串才会进入隐藏阶段
接着研究隐藏阶段函数
继续研究,发现隐藏阶段用二叉树储存,于是我们查看二叉树地址:
不断寻找,直到addr=0或val=x(我们输入的),并不断计算,函数最后的返回值放在eax里。
回到函数
test %eax,%eax
je 8048f33 <secret_phase+0x4b>
发现只允许func7返回0,其他值则会爆炸,那就好办了,现在只需要知道func7如何生成0,那就只有根节点可以得到0,那么根节点的value等于0x24,转换为10进制36。