1. 增强学生对于程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。
2. 掌握使用gdb调试器和objdump来反汇编炸弹的可执行文件,并单步跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法“推断”出拆除炸弹所需的目标字符串。
3. 需要拆除尽可能多的炸弹。
一个“binary bombs”(二进制炸弹,下文将简称为炸弹)是一个Linux可执行C程序,包含了7个阶段(phase1~phase6和一个隐藏阶段)。炸弹运行的每个阶段要求学生输入一个特定的字符串,若的输入符合程序预期的输入,该阶段的炸弹就被“拆除”,否则炸弹“爆炸”并打印输出 "BOOM!!!"字样。实验的目标是拆除尽可能多的炸弹层次。
每个炸弹阶段考察了机器级语言程序的一个不同方面,难度逐级递增:
n阶段1:字符串比较
n阶段2:for循环
n阶段3:switch分支
n阶段4:递归函数
n阶段5:数组元素按序访问
n阶段6:链表
n隐藏阶段:只有在阶段4的拆解字符串后再附加一特定字符串后才会出现(作为最后一个阶段)
为了完成二进制炸弹拆除任务,需要使用gdb调试器和objdump来反汇编炸弹的可执行文件,并单步跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法“推断”出拆除炸弹所需的目标字符串。这可能需要在每一阶段的开始代码前和引爆炸弹的函数前设置断点,以便于调试
1.Linux操作系统—32位debian
2. gdb调试器和objdump反汇编指令
3. 笔记本
可以看出第二关一开始就需要跳到
这是感觉$rsi的值有点怪怪的,查看了一下才得知密码格式是“%d %d %d %d %d %d”
得知格式后返回第二关的函数
cmpl $0x0,(%rsp)
jne 124f
callq 180d
得出(%rsp)需要等于0,不相等则跳转爆炸
cmpl $0x1,0x4(%rsp)
je 1254
callq 180d
得出0x4(%rsp)一定要等于1,都否则就无法跳转,按照程序顺序将会爆炸
add $0x4,%rbx
cmp %rbp,%rbx
je 1277
mov 0x4(%rbx),%eax //%eax=1
add (%rbx),%eax //%eax=1+1=2
cmp %eax,0x8(%rbx)
je 125d
由此可以推断出第四个数是前两个数之和,因此答案为:0 1 1 2 3 5
阅读 phase_3
分配栈针:利用%rax,也就是0x8+%rsp 和利用%rcx,也就是0x4+%rsp,传递sccanf函数用的第三和第四个参数
断点查询%rsi的值,答案格式是“%d %d”
返回phase_3
cmp $0x1,%eax
jle 12d9
callq 180d
由此可知:%eax>1
cmpl $0x7,(%rsp)
ja 1311
callq 180d
由此可知:%rsp<7
接着往下读,看到
lea 0x15a0(%rip),%rdx
查询了一下发现有刚好是两个整数“0 662”
浅试一下,发现成功了!
再继续往下读
把0x29f、39b、348、1ea、b9、86、8a转换成十进制分别是:671、923、840、490、185、134、138,Switch满足条件(cmpl $0x7,(%rsp))【(%rsp)≤7】
因此,我尝试了一下答案,正确,所以感觉这个逻辑也没错
首先还是一样都是判断输入是否合法
通过画图得知,rdx存储了我们读入的第一个参数,rcx存储了我们读入的第二个参数,在这里查询一下%rsi的值,可以推断出答案格式“%d %d”
接着往下读.
cmpl $0xe,(%rsp)
jbe 13be
mov $0xe,%edx (此行为:13be)
mov $0x0,%esi
mov (%rsp),%edi
算出这三个参数分别为:14、0、14,然后带着函数跳转到fun4函数
/ 注意一下的是 a in %edi, b in %esi, c in %rdx
int func4(int a, int b, int c){
// 获取l 和 r 的中位数
int mid = b +(c - b)/2;
// 进行递归调用
if(mid > a) return 2*fun(a,b,(mid-1)) result=0;
if(mid < a) {
result=2 * func4(a,mid+1,c)+1;
Return result;
}
}
我就带入算了一下,返回结果值为23,预测小于23的都可以
cmp $0x6,%eax
jne 13dc
由此可以得出%eax=6
因此第一个数≤23,第二个数为6,答案就可以正确,我输了23,6
但是我输入24,6也可以,到25,6就不行了,我也不知道哪里出了问题
首先感觉应该是个递归问题
callq 16e4
$0x6,%eax
1472
因此初步判断字符串长度为6
lea 0x1467(%rip),%rcx
查询 %rcx 的值
发现他是一个较长的字符串“maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?”
由lea 0x1(%rsp),%rdi,lea 0x1413(%rip),%rsi,则可查看rsi的值
观察两两对应关系,只需要发现“flyers”在其中的顺序为“9 15 14 5 6 7”,说明输入字符串中对应位的字符的最低4位的数值等于“9 f e 5 6 7”,即可通过这一关,查看ASCII值,得到“Y_^UVW”“ionefg”都可以,答案正确
以上均为对调用者保存寄存器的保存过程
这里的rsi就是栈帧指针,然后调用 read_six_numbers
得到六个参数的分布
a1=%rsi;a2=%rsi+4,a3=%rsi+8;a4=%rsi+12;a5=%rsi+16;a6=%rsi+20
凭感觉直接查询有注释的$rsi,得到答案格式“%d %d %d %d %d %d”,跳出函数继续读phase_6
发现如果输入的参数>6则会直接爆炸,继续
mov %edx,%eax
sub (%r12),%eax
分析后一个循环可知,输入的6个数,分别被7减,然后保留在原处
lea 0x202cc9(%rip),%rdx # 204210
%rdx的初始值为204210
mov 0x8(%rbx),%rax
mov (%rax),%eax
cmp %eax,(%rbx)
jge 158f
由此可知,链表值从大到小排列
我直接查了一下节点204210的值
从大到小依次排列为:6 3 2 4 1 5
减去7后的数值为:1 4 5 3 6 2
输入答案,正确
搜索隐藏关发需要从phase_defused进入
设置断点跳转到19f9,查询一下参数格式为"%d %d %s"
<+115>语句对两个字符串进行了比较。而其中一个字符串就是sscanf输入地址参数所指向的字符串。使用x/s命令查看,知道可以通过在第4关输入"23 6 DrEvil"作为通关密钥的同时,开启隐藏关卡。
成功进入隐藏阶段!
将断点断到secret_phase,分析secret_phase,由read_line可知,输入的是一个字符串,并且strtol函数是将一个字符串转化为十进制长整数赋给%rax作为返回值,不知道
cmp $0x5,%eax
je 1647
再由func7之后,可以知道返回值是0x5
读secret_phase阶段
根据C库函数strtol的定义,我得知这是一个转换字符串为数字的字符串。所以,我们需要输入一个数字。根据*1628,该数字需要低于0x3e8。在进入到func7函数后,若返回值为5,则跳转到*1647打印出拆弹成功的字符串。因此,关键在于func7函数。注意,初次调用func7时,我们的参数分别是输入的数字(%esi),以及0x202af8(%rip)
进入func7函数
这一部分的代码比较短,也比较好读,下面调用了两次fun7说明这也是一个递归程序,而且观察得到%rsi的值在整个递归的过程中没有变化过,起到的只是一个比较的作用。
一开始还检测了一下%rdi是否为0,后面设置递归参数的时候用mov 0x8(%rdi),%rdi,自身加上一个偏移量的间接寻址代替自身,基本可以确定%rdi是一个指针,%rdi+0x8和%rdi+0x10同样也是一个指针,看到这里基本已经猜出这个数据结构就是二叉树了
转换成C
int func7(int a, int b){
// 获取l 和 r 的中位数
int mid = b +(c - b)/2;
// 进行递归调用
if(a > b) return 2*fun7((*(a+8),b) result=0;
if(a != b) {
result=2*fun7((*(a+16),b)+1;
Return result;
}
}
算出答案为“47”
总结所有答案汇总:
And they have no disregard for human life.
0 1 1 2 3 5
0 662
6 6 DrEvil
ionefg
1 4 5 3 6 2
47
这一次的bomb实验,包含了计算机系统中第三章汇编语言的几乎所有知识点。通过本次的练习,我的汇编语言能力获得了很好的锻炼,对于一些重要知识点(如跳转表,循环)的知识点,掌握的更加牢靠。而本实验中包含的许多有趣实用的汇编语言技巧(如一些精巧的中间变量的使用、灵活的jump跳转指令的运用)使我更加注意编程技巧的学习。