实验目的与要求
1.更好地熟悉和掌握计算机中汇编语言和高级语言之间的关系。
2.增强学生对于调试器gdb、edb等调试器的使用和逆向工程等方面原理与技能的掌握。
3. 掌握使用gdb调试器和objdump来反汇编炸弹的可执行文件,并单步跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法“推断”出拆除炸弹所需的目标字符串。
4.完善测试,需要拆除尽可能多的炸弹
实验仪器设备/实验环境
1.Linux操作系统 — 64位Ubuntu
2. gdb调试器和objdump反汇编指令
3. 笔记本
实验内容及步骤
一个“binary bombs”(二进制炸弹,下文将简称为炸弹)是一个Linux可执行C程序,包含了7个阶段(phase1~phase6和一个隐藏阶段)。炸弹运行的每个阶段要求学生输入一个特定的字符串,若的输入符合程序预期的输入,该阶段的炸弹就被“拆除”,否则炸弹“爆炸”并打印输出 "BOOM!!!"字样。实验的目标是拆除尽可能多的炸弹层次。
每个炸弹阶段考察了机器级语言程序的一个不同方面,难度逐级递增:
- 阶段1:字符串比较
- 阶段2:for循环
- 阶段3:switch分支
- 阶段4:递归函数二分查找
- 阶段5:数组元素按序访问
- 阶段6:链表
- 隐藏阶段:只有在阶段4的拆解字符串后再附加一特定字符串后才会出现(作为最后一个阶段)
为了完成二进制炸弹拆除任务,需要使用gdb调试器和objdump来反汇编炸弹的可执行文件,并单步跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法“推断”出拆除炸弹所需的目标字符串。这可能需要在每一阶段的开始代码前和引爆炸弹的函数前设置断点,以便于调试。
拆弹密码的输入分文两种模式。
模式1:正常手动输入,每次程序运行到某一阶段会停下来要求用户输入数据。这种方式比较原始,不推荐使用。如果使用这种做法,在程序调试到后期时,每次为了进入后期的断点位置都需要在之前的每一个阶段进行手动输入,极其浪费时间。
模式2:采用输入重定向。首先将答案文本写至一个.txt文本中,每个阶段的拆弹密码占一行。
在调试程序时直接使用输入重定向指令,例如(假设密码已被写入到之前的拆弹密码文本文件solution.txt中):
./bomb < solution.txt
通过执行以上指令即可直接根据屏幕输出来判断程序正确地进行了几个阶段或者在第几个阶段出现了错误。如果密码全部正确,提示结果如下图所示:
实验设备与软件环境
1.Linux操作系统 —— 64位 Ubuntu 18.04
2. C编译环境(gcc)、make自动化编译工具
3. 虚拟机
实验注意事项
1.建议在linux下进行文本拆弹密码文本编辑。
2.建议使用gdb或IDA、edb、DDD之类的调试软件辅助进行。
3.建议实验过程中手绘图表辅助逆向工程分析。
实验技巧
你需要掌握:
1.使用 gdb 和反汇编以查看程序的运行过程。
2.明白具体的操作符的含义。
3.理解不同寄存器的常用方法。
4.一些汇编语句与实际命令的转换。
5.使用gdb和反汇编以查看程序的运行过程
本实验的执行文件为bomb(无后缀),先在命令行找到bomb文件的位置,再用gdb调试工具运行该文件,写法为gdb bomb。进入 gdb 调试后,使用disas phase_X反汇编相应关卡,或者直接使用layout regs进入图形化交互界面,注意自己添加断点(break 函数名),以及单步执行(si)
#####具体的操作符的含义
常量以符号$开头:$-42, $0x15213(一定要注意十进制还是十六进制)
寄存器以符号%开头:%esi,%rax(可能存的是值或者地址)
内存地址用括号括起来:如(%rbx),括号实际上是去寻址的意思
一些汇编语句与实际命令的转换,下面是一个例子
注:汇编指令的英文一般就指代其功能,如mov(move移动)。汇编的注释为;。
sub $0x8,%rsp ;rsp寄存器的内容减去8再保存到rsp中 mov $0x402400,%esi ;将常量$0x402400存到esi寄存器中,一般出现这类地址,是重点信息 callq 0x401338 <strings_not_equal> ;调用strings_not_equal函数。bomb文件中的函数名基本就指代其功能 test %eax,%eax ;test指令做&操作,一般和跳转一起使用 je 0x400ef7 <phase_1+23> ; callq 0x40143a <explode_bomb> ;je为若相等则跳转,je也等价于jz(若为零则跳转)。此处即若eax零则跳转到0x400ef7。 add $0x8,%rsp ;rsp寄存器内容加8再保存到rsp中 |
编程要求
根据提示,在汇编代码中找到本关密码,在执行文件中将密码输出验证。为了方便找出过关代码,反汇编找出源码:objdump -d ./bomb > bomb.s
如果提示没有执行权限,可以使用ll命令查看该文件夹下的文件权限,并用chmod修改
chmod 777 bomb来修改运行权限
之后再尝试运行即可,效果如下图。如果没有遇到Permission denied问题,则不需要这一步的特殊处理。
gdb bomb进入调试
进入调试后,先设置断点,如break phase_1即输入第一关密码后,在phase_1处暂停。再用run命令开始运行程序,run以后会出现一段话,此时输入第一关密
码,随便输一个,即进入断点处,此时disas即可查看第一关汇编代码,注意不必纠结函数,给出的地址很重要。
例子:
1.查看地址0x01存的内容
print 0x01
x/s 0x01 ;x为以16进制显示,s为以字符串显示
2.查看寄存器esi存的内容
print $esi
x/s $esi
3.建议将反汇编出来的代码放入一个文本中,好做笔记。
4.将汇编中出现的代码,与书中所学的if,while,for,switch的模板做笔记,提炼出程序框架。
实验过程与结果
阶段1:字符串比较
用gdb查看bomb,先用b main(breakpoint)在main函数前设置断点。再用r(run)执行,直到第一个断点处。再用n单步执行C语句。找到函数strings_not_equal,进入之后经过查看,发现密码应该在rsi中。利用x/s命令查看$rsi。得到密码:He is evil and fits easily into most overhead storage bins.
阶段2:for循环
进入断点步骤同上,然后单步到read_six_numbers,用x /6xw $rsp可知答案需要输入六个值才能通过
反汇编找出源码:objdump -d ./bomb > bomb.s,然后复制到本机中的edv中查看分析可知这是一个for循环,分别找到init,update,body,然后可得数值不能小于0个且初始值i=1开始进入循环且i<6,arr[i] != arr[i-1]+i,然后得到答案:2 3 5 8 12 17
阶段3:switch分支
进入断点步骤同上,经过分析汇编可知这是一个switch结构,执行到跳入switch需要两个数值和,则用x/s $rsi可以看到,
汇编中看到在进入switch前第二参数会和0x7进行比较且不能大于7,所以第一个参数的值需要小于7
由于知道了这个是switch结构,且知道最大左右七个值,所以从小于7的数值中随机选择一个放在第一个参数中,第二个参数随机,然后在gdb中单步到对应第二个参数的case之后就可以得到第二个参数的值,第一个参数输入哪个值就会跳转到哪个case
所以答案是:4 440(第一个参数随机0-7都有结果)
阶段4:二分查找递归函数
分析汇编可知会调用另外一个函数func4
方法一:看到汇编中的可以得到二个输入到func4的参数是第一个是6,然后通过单步到fun4下面那行rax输出结果为21
分析func4中的递归为
可得最后答案为6 21
阶段5:数组元素按序访
通过汇编先看需要输入多少个值
b *(0x8048fe8) 这里调用了<string_length>函数
b *(0x8048ff3) 这里将$0x6,-0x18(%ebp)比较,x/wx $ebp-0x18 得到-0x18(%ebp)=输入的个数,所以这里要求输入的字符个数为6
b *(0x8049046) 这里调用了<strings_not_equal>,可知使要比较输入字符串S与key字符串是否一致,这里输入字符串“rrrrrr”
x/s $ebx-0x2484 可以看到这里存放“flyers”,猜测与key有关
x/s $ebp-0x13 这里存放“dddddd”,这里本来应该是我们输入的“rrrrrr”,现在变成了dddddd,说明字符串发生了某种变换,比较对应关系
b *(0x80490x18),读取0x140(%ebx,%eax,1)= m a d u i e r s n f o t v b y l,而观察两两对应关系,只需要可以发现“flyers”在其中的顺序为”9,15,4,5,6,7”, 说明输入字符串中对应位的字符的最低4位的数值等于"9,f,e,5,6,7",即可通过这一关。查看ASCII值,得到ionefg
阶段6:链表
用ida分析汇编得知代码中一共分为三个部分
1、除重
2、连成链表在数组中
3、判断输入的值的排序是否为递增
第一部分代码分析:
从汇编中知道调用了一个read_six_numbers函数所以需要输入六个值,图中有解析
第二部分代码分析:
这部分重点在于可以得到最后的答案的值就在数组node1中,图中有解析
gdb中查看数组的值
第三部分代码分析:主要就是对数组的值进行排序,如果输入的值不是递增的则爆炸,图中有分析
do
{
if ( *(_DWORD *)v8 > **(_DWORD **)(v8 + 8) )
explode_bomb(a1, (const char *)6);
v8 = *(_QWORD *)(v8 + 8);
--v14;
}
while ( v14 );
result = __readfsqword(0x28u) ^ v23;
最后得到需要输入一个按照递增排序的数组,六个值
答案为:2 5 1 3 6 4