实验报告
实 验(三)
题 目 Binary Bomb
二进制炸弹
专 业 计算机学院
学 号
班 级
学 生
指 导 教 师
实 验 地 点
实 验 日 期 2022-03-31
计算学部
目 录
第1章 实验基本信息................................................................... - 3 -
1.1 实验目的............................................................................... - 3 -
1.2 实验环境与工具................................................................... - 3 -
1.2.1 硬件环境....................................................................... - 3 -
1.2.2 软件环境....................................................................... - 3 -
1.2.3 开发工具....................................................................... - 3 -
1.3 实验预习............................................................................... - 3 -
第2章 实验环境建立................................................................... - 5 -
2.1 Ubuntu下CodeBlocks反汇编(10分)........................ - 5 -
2.2 Ubuntu下EDB运行环境建立(10分).......................... - 5 -
第3章 各阶段炸弹破解与分析................................................... - 7 -
3.1 阶段1的破解与分析........................................................... - 7 -
3.2 阶段2的破解与分析........................................................... - 9 -
3.3 阶段3的破解与分析......................................................... - 12 -
3.4 阶段4的破解与分析......................................................... - 15 -
3.5 阶段5的破解与分析......................................................... - 18 -
3.6 阶段6的破解与分析......................................................... - 22 -
3.7 阶段7的破解与分析(隐藏阶段)....................................... - 28 -
第4章 总结................................................................................. - 32 -
4.1 请总结本次实验的收获..................................................... - 32 -
4.2 请给出对本次实验内容的建议......................................... - 32 -
参考文献....................................................................................... - 34 -
第1章 实验基本信息
熟练掌机器级表示、汇编语言、调试器和逆向工程等的理解
1.2 实验环境与工具
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Windows11 64位;VirtualBox;Ubuntu 20.04 LTS 64位
1.2.3 开发工具
Visual Studio 2019 64位;CodeBlocks 64位;vi/vim/gedit+gcc
1.3 实验预习
上实验课前,认真预习实验指导书(PPT或PDF)
了解实验的目的、实验环境与软硬件工具、实验操作步骤,复习与实验有关的理论知识。
请写出C语言下包含字符串比较、循环、分支(含switch)、函数调用、递归、指针、结构、链表等的例子程序sample.c。
生成执行程序sample.out。
用gcc –S或CodeBlocks或GDB或OBJDUMP等,反汇编,比较。
列出每一部分的C语言对应的汇编语言。
修改编译选项-O (缺省2)、O0、O1、O3、Og、-m32/m64。再次查看生成的汇编语言与原来的区别。
注意O1之后缺省无栈帧,RBP为普通寄存器。用 -fno-omit-frame-pointer加上栈指针。
GDB命令详解 –tui模式 ^XA切换 layout改变等等
第2章 实验环境建立
2.1 Ubuntu下CodeBlocks反汇编(10分)
CodeBlocks运行hello.c。反汇编查看printf函数的实现。
要求:C、ASM、内存(显示hello等内容)、堆栈(call printf前)、寄存器同时在一个窗口。
图2-1 Ubuntu下CodeBlocks反汇编截图
2.2 Ubuntu下EDB运行环境建立(10分)
用EDB调试hello.c的执行文件,截图,要求同2.1
图2-2 Ubuntu下EDB截图
第3章 各阶段炸弹破解与分析
每阶段30分,密码10分,分析20分,总分不超过80分
3.1 阶段1的破解与分析
密码如下:Brownie, you are doing a heck of a job.
破解过程:
- 先运行可执行文件bomb,随意输入一些东西,发现程序被引爆,通关密码错误。后利用objdump –d bomb > asm.txt指令生成asm.txt文件。
- 在main函数下发现了它调用了phase_1到phase_6以及phase_defused函数,由此可猜测phase_1到phase_6即为所要找的关卡。在asm.txt文件中简单查看phase_1。
- 用gdb查看bomb,先用b main(breakpoint)在main函数前设置断点。再用r(run)执行,直到第一个断点处。再用n单步执行C语句。找到input=read_line();然后输入测试的字符串。利用si进入汇编和C函数内部(即加入phase_1函数内)。经过查看,发现密码应该在0x403150中。利用x/s命令查看0x403150。得到密码:Brownie, you are doing a heck of a job.
3.2 阶段2的破解与分析
密码如下: 1 2 4 8 16 32… ()
破解过程:
- 用gdb查看bomb,先用b main(breakpoint)在main函数前设置断点。再用r(run)执行,直到第一个断点处。再用n单步执行C语句。在通过第一个测试后(Phase 1 defused. How about the next one?),找到下一个input=read_line();然后输入测试的数字。利用si进入汇编和C函数内部(即加入phase_2函数内)。
2. 在gdb中查看phase_2函数,看见callq 0x4018ea <read_six_numbers>猜测这题需要输入6个数字。在asm.txt中查看read_six_numbers,分析cmp $0x5,%eax得知,这题确实需要至少输入6个数字,否则会直接爆炸。
3. 根据13的cmpl $0x1,(%rsp)和17的jne 0x401424<phase_2+26>得出,第一个数必须为1,否则直接爆炸。
4. 假设第一个数为1,%ebx会被赋值为$0x1,向下分析。程序会跳转到19的add $0x1,%ebx,cmp $0x5,%ebx,jg 0x40144c<phase_2+66>。可知这是一个循环,%ebx会从$0x1一直增大到$0x5,若在循环中炸弹没有爆炸,则可成功从66-71处retq(退出)。
5. 41的cmp $0x5,%ebx,jg 0x40144c<phase_2+66>到59的cmp %eax,(%rsp,%rdx,4)是该phase_2函数最重要的部分,它通过循环的方式验证了剩下的5个数。
6. 因为%ebx 初始值赋为1,又在每轮循环开始时定义%rdx储存的值等于%ebx且%rax=%ebx=%rbx-0x1,所以%rdx、%ebx在每轮的值为1-5递增,%rax在每轮的初始值为0-4递增。循环继续的条件为:
%eax储存的值等于[%rsp+%rdx*4]地址的值,这需要:
([%rsp+%rax*4] 地址指向的值 * 2)== [%rsp+%rdx*4]地址指向的值
又因为%rax和%rdx在每轮存储的值不因输入值而改变,且%rdx-%rax=1,所以:
若[%rsp+%rax*4]是第n个参数,[%rsp+%rdx*4]则代表第n+1个参数且第n+1个参数的值为第n个参数的两倍:。
%rdx eq %ebx | %rax=%rbx-0x1 | %eax eq ([%rsp+%rax*4]的值*2) | [%rsp+%rdx*4]的值 | |
第一次 | 1 | 0 | 2 | 2 |
第二次 | 2 | 1 | 4 | 4 |
第三次 | 3 | 2 | 8 | 8 |
第四次 | 4 | 3 | 16 | 16 |
第五次 | 5 | 4 | 32 | 32 |
- 所以经过上表的计算,在第一、二、三、四、五次循环中,分别验证了2,4,8,16,32。所以,第二次的密码为1,2,4,8,16,32。
3.3 阶段3的破解与分析
密码如下:
0 -99
(或0 4294967197、
1 -835、
1 4294966461、
2 -20、
2 4294967276、
3 -539、
3 4294966757、
4 0、
5 -539、
5 4294966757)
破解过程:
首先在gdb中查看phase_3,并从头开始分析。
分析phase_3函数的执行过程。第一个重点在于
-
- cmp $0x1,%eax
- jle 40148a <phase_3+0x38> è callq 4018c6 <explode_bomb>
由此可知,初始%eax中应存储着输入参数的数量,数量需大于1,否则程序直接爆炸。若参数数量大于1,则进入下一步:
- mov 0xc(%rsp),%eax
- cmp $0x7,%eax
- ja 401501 <phase_3+0xaf> è callq 4018c6 <explode_bomb>
这三句根本上是将%eax赋值等于[%rsp+0xc]地址指向的值,并和0x7进行大小比较。可知[%rsp+0xc]地址指向的值应为参数①,且参数①应小于7,否则程序直接爆炸。若参数①小于7,则进入下一步:
- mov %eax,%eax
- jmpq *0x4031a0(,%rax,8)
这两句主要在于跳转,%rax存储的值等于%eax的值,所以参数①的值会决定该语句会跳转到哪里执行,且最后都会收敛至cmpl $0x5,0xc(%rsp),该语句决定参数①不能大于5,所以无需考虑参数①为6或以上的情况。根据整理梳理,得出下表:
参数①取值 | 跳转到 | 该过程中计算出的%eax值 |
0 | <phase_3+126> | ffffff9d |
1 | <phase_3+63> | fffffcbd |
2 | <phase_3+133> | ffffffec |
3 | <phase_3+140> | fffffde5 |
4 | <phase_3+147> | 0 |
5 | <phase_3+154> | fffffde5 |
- cmpl $0x5,0xc(%rsp)
- jg 4014c6 <phase_3+0x74>
- cmp %eax,0x8(%rsp)
- je 4014cb <phase_3+0x79>
- callq 4018c6 <explode_bomb>
以上语句说明了,%eax的值应该等于0x8(%rsp),否则会直接爆炸。由此可知0x8(%rsp)应该为输入的参数②,参数①与参数②有配对关系,且不唯一。由计算可知,有如下几种情况:
参数①取值 | %eax | 参数②取值 |
0 | ffffff9d | -99或4294967197 |
1 | fffffcbd | -835或4294966461 |
2 | ffffffec | -20或4294967276 |
3 | fffffde5 | -539或4294966757 |
4 | 0 | 0 |
5 | fffffde5 | -539或4294966757 |
以上参数②的多种取值一部分原因是参数①的多种取值,另一部分原因是溢出导致的。
- add $0x18,%rsp
- retq
若输入参数满足了上述情况,则可成功退出。
3.4 阶段4的破解与分析
密码如下:10 37
破解过程:
本阶段涉及到对phase_4的逐句分析和对其调用的func4函数的递归分析。
首先在phase_4中值得关注的点是:
- cmp $0x2,%eax
- jne 40156e <phase_4+0x2f> è callq 4018c6 <explode_bomb>
由此可知,%eax是输入参数的个数,表明输入参数个数应为两个,否则直接爆炸。假设输入了两个参数,接着往下看:
- mov 0xc(%rsp),%eax
- js 40156e <phase_4+0x2f>
- cmp $0xe,%eax
- jle 401573 <phase_4+0x34>
- callq 4018c6 <explode_bomb>
值得注意的是,% eax被赋值为0xc(%rsp),也即[%rsp+0xc]指向的值,也即参数①。 这告诉我们,参数①要小于等于14且大于等于0。继续阅读:
- mov $0x0,%esi
- mov 0xc(%rsp),%edi
- callq 40150d <func4>
这里对%edx、%esi、%edi存储的数据进行了预先处理,然后进入func4函数的调用。值得注意的是,%edi被赋值为0xc(%rsp),也即[%rsp+0xc]指向的值,也即参数①。
下面我们查看func4的内容:
000000000040150d <func4>:
//这一段经过计算,发现是对%ebx的值进行0-14的二分,最后达到%ebx和%edi取值相等的判断条件
//例如:若%edi的值为10,%ebx会取7 => 11 => 9 => 10,最后%ebx和%edi取值相等,然后退出程序
40150d: 53 push %rbx
40150e: 89 d0 mov %edx,%eax
401510: 29 f0 sub %esi,%eax
401512: 89 c3 mov %eax,%ebx
401514: c1 eb 1f shr $0x1f,%ebx
401517: 01 c3 add %eax,%ebx
401519: d1 fb sar %ebx
40151b: 01 f3 add %esi,%ebx
//这一段是%ebx和%edi的比较和选择跳转语句
40151d: 39 fb cmp %edi,%ebx
40151f: 7f 06 jg 401527 <func4+0x1a>
401521: 7c 10 jl 401533 <func4+0x26>
//这一段是根据%edi,%ebx的大小关系所选择的跳转关系。
//当%edi,%ebx相等时,走下列语句,最后retq
401523: 89 d8 mov %ebx,%eax
401525: 5b pop %rbx
401526: c3 retq
//当%edi < %ebx时,走下列语句,递归调用func4,最后跳转到401523 <func4+0x16>
401527: 8d 53 ff lea -0x1(%rbx),%edx
40152a: e8 de ff ff ff callq 40150d <func4>
40152f: 01 c3 add %eax,%ebx
401531: eb f0 jmp 401523 <func4+0x16>
//当%edi < %ebx时,走下列语句,递归调用func4,最后跳转到401523 <func4+0x16>
401533: 8d 73 01 lea 0x1(%rbx),%esi
401536: e8 d2 ff ff ff callq 40150d <func4>
40153b: 01 c3 add %eax,%ebx
40153d: eb e4 jmp 401523 <func4+0x16>
若用流程图来看执行过程中%ebx的变化过程:
- cmp $0x25,%eax
- jne 401592 <phase_4+0x53> è <explode_bomb>
由上述代码可知,只有当func4函数计算最后得出的%eax的值等于0x25,也即十进制里的37时,才能解出谜题。于是经过计算,得出下表:
%edi 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
%eax 11 11 13 10 19 15 21 7 35 27 37 18 43 31 45
所以可知%edi应取10(也即参数①应取10),这样%eax计算得37(0x25),才可以避免爆炸。
- cmpl $0x25,0x8(%rsp)
- je 401597 <phase_4+0x58>
- callq 4018c6 <explode_bomb>
又由以上语句可知,[%rsp+0x8]指向的地址值应为参数②,而参数②应该等于0x25(也即37),这样才能避免爆炸,最后phase_4安全解出。
3.5 阶段5的破解与分析
密码如下:50j7on 或 %p*g_^ 或 ……(等等)
(使字符串中的各字符对应地址所取值相加等于63,该题有非常多种可行的答案。不区分大小写,因为一一对应)
破解过程:
本阶段涉及到对phase_5的逐句分析和对其调用的string_length函数的递归分析。
首先我们从string_length入手,看看这个函数有什么作用。先顾名思义,猜测它可以判断输入的字符串长度,阅读以下语句猜想得到验证:
- cmpb $0x0,(%rdi)
- je 4017e3 <string_length+0x13> è retq
è cmp $0x6,%eax
jne 4015cf <phase_5+0x33> è callq 4018c6 <explode_bomb>
- add $0x1,%rdi
- add $0x1,%eax
- jmp 4017d5 <string_length+0x5>
以上语句说明了输入的字符串长度必须为6,否则会在phase_4中触发炸弹。
假设输入了长度为6的字符串,让我们继续阅读:
- mov $0x0,%eax
- cmp $0x5,%eax
- jg 4015d6 <phase_5+0x3a>
- movslq %eax,%rdx
- movzbl (%rbx,%rdx,1),%edx
- and $0xf,%edx
- add 0x4031e0(,%rdx,4),%ecx
- add $0x1,%eax
- jmp 4015b4 <phase_5+0x18>
至此,我们发现了一个循环结构。%eax从0è5增大,意味着有六轮循环,且循环的轮数无关输入的字符串。假设循环结束,我们往下看跳转语句:
- cmp $0x3f,%ecx
- jne 4015dd <phase_5+0x41> ècallq 4018c6 <explode_bomb>
- pop %rbx
- retq
可以发现输入字符串应该只与%ecx有关,且字符串应该构造成可以使%ecx和0x3f(也即十进制中的63)相等的数。
了解上面的信息后,仔细研究循环中与%ecx有关的语句:
- add 0x4031e0(,%rdx,4),%ecx
现在可以知道%rdx应该与输入字符串有关,向上推导,%rdx等于%edx的值。于是关注到 %edx等于【[%rbx+%rdx]指向的值和0xf按位相与】语句,其中%rdx与%edx相等(0è5),可知[%rbx+%rdx]指向的值应该随着循环从指向字符串的第一个符号直到指向字符串的最后一个符号。所以经过六轮循环后:
指向的值
(为字符串的第n个符号和0xf按位相与的值,0<=
<=15)
于是在edb中查看0x4031e0地址后16*4的值(见下图),并计算输入符号与对应地址所取值的对应关系(由ASCII码和计算可得,见下表)。
(不区分大小写,因为一一对应)
构造答案中的字符串的方法是:使字符串中的各字符对应地址所取值相加等于63,该题有非常多种可行的答案。
3.6 阶段6的破解与分析
密码如下:5 2 3 4 6 1
破解过程:
本阶段涉及到对phase_6的逐句分析。
首先关注到以下代码:
- lea 0x30(%rsp),%rsi
- callq 4018ea <read_six_numbers>
- mov $0x0,%ebp
- jmp 401626 <phase_6+0x42>
可知该阶段要求我们输入6个数字,若输入符合该要求程序会跳转到401626 <phase_6+0x42>继续执行:
- cmp $0x5,%ebp
- jg 401643 <phase_6+0x5f>
- movslq %ebp,%rax
- mov 0x30(%rsp,%rax,4),%eax
- sub $0x1,%eax
- cmp $0x5,%eax
- ja 4015fd <phase_6+0x19> è callq 4018c6 <explode_bomb>
发现在此会比较0x5,%eax大小,根据上述的语句,可知这里应该是先通过
mov 0x30(%rsp,%rax,4),%eax语句将参数①的值赋给%eax,减一后与5比较大小,若大于5会直接爆炸,所以可知至少是参数①应该是<=6且>0的。假设参数①<=6,继续阅读:
- lea 0x1(%rbp),%r12d
- mov %r12d,%ebx
- jmp 401607 <phase_6+0x23>
cmp $0x5,%ebx - jg 401623 <phase_6+0x3f>
发现上述语句的最终作用是将%ebx赋值为%rbp+1,然后与0x5比较。在此时,%ebx值应该为1,是小于0x5的,所以不会跳转。继续阅读:
- movslq %ebp,%rax
- movslq %ebx,%rdx
- mov 0x30(%rsp,%rdx,4),%edi
- cmp %edi,0x30(%rsp,%rax,4)
- jne 401604 <phase_6+0x20>
- callq 4018c6 <explode_bomb>
此时程序保证了%rdx为%rax è5递增,%rax则为%ebp(此时值为0),最后相当于cmp 0x30(%rsp,%rdx,4),0x30(%rsp,%rax,4),若相等则爆炸。意味着这个循环是为了保证参数①和(参数②è⑤)不相等。
假设参数①和(参数②è⑤)不相等,继续阅读代码,我们会发现这应该是一个双重循环不仅%rdx是从%rax è5的递增,当每轮%rdx==5退出循环后,%rax也会+1,再次进入%rdx从%rax è5的循环。简单表示如下:
for(rdx:0->5){
for(rax:rdx->5){
if(0x30(%rsp,%rax,4)== 0x30(%rsp,%rdx,4)){
explode_bomb;
}
}
}
以上逻辑表示,参数①è⑥两两都不相等,又因为参数①è⑥的值均>0且<=6。所以得出第一阶段结论:参数①è⑥为1至6的且不重复的数字。
继续分析,下面将用一个流程图来说明下一段代码的执行流程:
上述流程主要进行以下操作:
将参数n赋值为(0x4052d0+0x8*)地址所指的值(为参数n的值):
整理一下,可知:
沿着142继续阅读,分析下一个循环:
这段程序,首先使%rbx=%rdx=[%rsp],然后是通过循环,使(%rbx+8*(n-1))取得(0x4052d0+0x8*)地址所指的值,其中n从1一直增大到6。
这段程序是为了比较(%rdx+8*n)和(%rdx+8*(n+1))的大小关系,其中n从0增加到4。与前面程序相互映照,可得出这段程序的真实作用是为了对比(0x4052d0+0x8*)和(0x4052d0+0x8*)大小关系,且前者必须小于后者,否则程序会直接爆炸。回顾上面的表格,可得出密码应该为:5 2 3 4 6 1
3.7 阶段7的破解与分析(隐藏阶段)
密码如下:1(或8、6)
破解过程:
首先隐藏阶段意味着我们必须通过寻找来开启它,否则在前六阶段成功解决后程序会直接退出。于是,我们先回顾main函数,看看其中有什么玄机:
发现除了可以显而易见的phase_1到phase_6,隐藏关卡至少没有在main中直接体现出来。所以这时候我们想到没有查看过的phase_defused函数,于是在asm.txt中查看:
在其中我们直接发现secret_phase函数,并且在查看0x403258和0x403280时发现了如下语句:
于是可以肯定这就是隐藏关卡的入口,接下来解决如何开启这个关卡:
阅读上面的代码,它的执行流程是先观察是不是前六关都完成了,若是则可以转入尝试读入第四关中输入的字符,这里有一个判断:若是2个字符,则可知拆弹者并未发现这个隐藏关卡,而能直接顺利退出。若是3个字符,转入下列语句:
在这里取出第四关中输入的第三个字符,并与0x403382中存的字符串(也即:DrEvil)对比。若一致,则可跳转secret_phase,也即正式开启了隐藏关卡。
接下来,我们继续阅读secret_phase函数,用流程图的形式可能会更清楚的说明其执行流程:
大体上secret_phase的流程是非常清晰的,不过限制了输入的字符不能大于0x3e9,对内存地址的访问则又和phase_6类似,于是我们查看一下0x4050f0中存放了什么:
虽然暂时不知道这有什么用,但是这个疑问会在对fun7的分析中得到解答。接下来关注递归函数fun7:
该函数主要的难点是递归调用,这个和func4类似,这时我们整理上面提到的内存给出的信息:
观察这个表格发现[rdi+8]的值<rdi<[rdi+10],由此可知,上述语句是通过递归在这些数中寻找(esi的值=eax的值)的情况。所以经过分析,esi应该取0x 08、0x 06、0x 1其中之一。
由此可计算和对递归的推导,通过跳转,密码最终可取0x 8、0x 6、0x 1其中之一(也即:8、或6、或1)。
第4章 总结
4.1 请总结本次实验的收获
通过本次实验学会了如何使用gdb和edb,能够自主的分析出汇编代码的意思,读懂汇编代码。更了解程序运行的内部逻辑,对linux的使用更加熟练自如。
4.2 请给出对本次实验内容的建议
建议可以多鼓励进行edb的使用,还可以讲一下gdb更多指令的使用,虽然自己探索也很有趣但是帮助总是不嫌多的。
注:本章为酌情加分项。
参考文献
为完成本次实验你翻阅的书籍与网站等
[1] http://blog.csdn.net/xiaoguaihai/article/details/8705992
[2] C汇编Linux手册 http://docs.huihoo.com/c/linux-c-programming
[3] CMU的实验参考 http://csapp.cs.cmu.edu/3e/labs.html
[4] 网站与论坛 http://cn.ubuntu.com/ http://forum.ubuntu.org.cn/
[5] 《深入理解计算机系统》Randal E. Bryant David R.O`Hallaron