
二进制炸弹bomblab-隐藏关-详细解析
二进制炸弹bomblab-隐藏关-详细解析,本文并非速通指南,偏向于如何一步步思考与解题,也许会分析得有些拖沓。且本文为系列文章,一篇只分析一关的破解思路,前文提到的知识点后面不会赘述,分析过的类似的汇编代码段也不会再分析,除非是没见过的结构,所以越往后分析得越少。
观前提示:本文并非速通指南,偏向于如何一步步思考与解题,也许会分析得有些拖沓。且本文为系列文章,每一篇会分析一关的破解思路,前文提到的知识点后面不会赘述,分析过的类似的汇编代码段也不会再分析,除非是没见过的结构,所以越往后分析得越少。
本文共2400余字。
前言
如果这些解析对你有帮助,那么我会非常荣幸和开心!
如果我的解析存在错误,非常抱歉误导了你!请在评论区提出!我也是一个正在不断学习的学生。
谢谢你!
破解前的分析
当我们来到这里,已经解完了6个关卡。在第一篇文章我们有说过,main函数里一共调用了6个关卡函数。炸弹提示语好像也告诉我们拆除了炸弹:
但是回到我们拥有的main函数C语言代码,可以看到设下炸弹的人留下了这么一段话:
“But isn’t something… missing? Perhaps something they overlooked? Mua ha ha ha ha! ”
但是是不是有什么东西… 缺失了? 也许是他们忽略了什么!*得意地怪笑
可见,炸弹还没有完全破解。
回顾一下,我们一路走来几乎是把所有关卡函数和它的子函数都分析了个遍。所以,仅剩的phase_defused函数就是最后的疑点。
正式破解
隐藏关卡触发方法
扫视一下phase_defused函数果然可以发现它有可能会触发隐藏关:
0x08049249 <+115>: call 0x8048e92 <secret_phase>
那就来分析一下这个函数:
0x080491d6 <+0>: sub $0x6c,%esp
0x080491d9 <+3>: mov %gs:0x14,%eax
0x080491df <+9>: mov %eax,0x5c(%esp)
0x080491e3 <+13>: xor %eax,%eax
;栈保护操作,之前的文章讲过
0x080491e5 <+15>: cmpl $0x6,0x804c3cc
0x080491ec <+22>: jne 0x8049261 <phase_defused+139>
;………………略去中间代码……………………
0x08049261 <+139>: mov 0x5c(%esp),%eax
0x08049265 <+143>: xor %gs:0x14,%eax
0x0804926c <+150>: je 0x8049273 <phase_defused+157>
0x0804926e <+152>: call 0x8048790 <__stack_chk_fail@plt>
0x08049273 <+157>: add $0x6c,%esp
0x08049276 <+160>: ret
可以发现:
只有输入的字符串个数等于6才会触发隐藏关,也就是要先破解完第六关才会往下走,否则会跳过隐藏函数。我上面的截图是第一关调用的phase_defused函数,所以是1。经过调试,来到第六次调用,于是成功往下运行,不跳过:
;略去入栈操作
0x08049200 <+42>: push $0x804a1c9
0x08049205 <+47>: push $0x804c4d0
0x0804920a <+52>: call 0x8048810 <__isoc99_sscanf@plt>
0x0804920f <+57>: add $0x20,%esp
0x08049212 <+60>: cmp $0x3,%eax
0x08049215 <+63>: jne 0x8049251 <phase_defused+123>
发现是把第四关的通关答案再次拿出来解析,不过这次按"%d %d %s"解析,而不是之前第四关里的 "%d %d "。如果参数个数不等于3,又会导致跳过。
假设我们没有跳转,程序接着往下走:
0x08049217 <+65>: sub $0x8,%esp
0x0804921a <+68>: push $0x804a1d2
0x0804921f <+73>: lea 0x18(%esp),%eax
0x08049223 <+77>: push %eax
0x08049224 <+78>: call 0x8048f86 <strings_not_equal>
0x08049229 <+83>: add $0x10,%esp
0x0804922c <+86>: test %eax,%eax
0x0804922e <+88>: jne 0x8049251 <phase_defused+123>
这个逻辑我在第一篇文章的后半部分提过,不在此赘述。在这里就是比较我们第四关输入的通关字符串的第三个字符串参数是否和0x804a1d2处的字符串相等,不相等又会导致跳过。
查看0x804a1d2处的字符串:
假设我们没有跳转,程序接着往下走,就即将到隐藏关了:
0x08049230 <+90>: sub $0xc,%esp
0x08049233 <+93>: push $0x804a098
;入栈操作
0x08049238 <+98>: call 0x80487c0 <puts@plt>
0x0804923d <+103>: movl $0x804a0c0,(%esp)
0x08049244 <+110>: call 0x80487c0 <puts@plt>
;在进入隐藏关之前,调用puts函数,输出2句字符串
0x08049249 <+115>: call 0x8048e92 <secret_phase>
输出的2句字符串:
该死的!你找到了隐藏关卡!
但是找到它和解决它是完全不同的……
看到了语气有趣的困难预告。
到这里已经知道触发隐藏关的条件了:
- 先要破解完6个普通关卡
- 第四关的通关字符串要额外加一个字符串参数 “DrEvil”
分析隐藏关卡
0x08048e92 <+0>: push %ebx
0x08048e93 <+1>: sub $0x8,%esp
0x08048e96 <+4>: call 0x80490dd <read_line>
0x08048e9b <+9>: sub $0x4,%esp
0x08048e9e <+12>: push $0xa
0x08048ea0 <+14>: push $0x0
0x08048ea2 <+16>: push %eax
0x08048ea3 <+17>: call 0x8048880 <strtol@plt>
strtol
(String to Long)函数是C语言标准库中的一个函数,用于将字符串转换为长整型数(long int
类型),并返回该数值。它的函数原型在头文件 <stdlib.h>
中声明:
long int strtol(const char *nptr, char **endptr, int base);
nptr
:要转换的字符串的指针。endptr
:一个指向字符指针的指针,用于存储转换停止的位置。如果不想获取这个信息,可以传入NULL
。base
:进制数,指定待转换字符串的进制。
在这里就是把我们的输入转成10(0xa)进制。
0x08048ea8 <+22>: mov %eax,%ebx
0x08048eaa <+24>: lea -0x1(%eax),%eax
0x08048ead <+27>: add $0x10,%esp
0x08048eb0 <+30>: cmp $0x3e8,%eax
0x08048eb5 <+35>: jbe 0x8048ebc <secret_phase+42>
0x08048eb7 <+37>: call 0x804907d <explode_bomb>
;输入的数字大于0x3e8 (即1000) 就炸
0x08048ebc <+42>: sub $0x8,%esp
0x08048ebf <+45>: push %ebx
0x08048ec0 <+46>: push $0x804c088
;把ebx和0x804c088处的数据作为参数传入fun7 ebx是我的输入 另一个是题目定好的变量n1
0x08048ec5 <+51>: call 0x8048e41 <fun7>
这里发现了一个变量名n1,回顾一路解题的过程中,我们一共遇到过2次这种情况:
- 一次是第五关有个叫arrary的变量,这个告诉我们要用数组这个数据结构来理解内存里的数据。
- 一次是第六关有个叫node1的变量,这个告诉我们要用链表这个数据结构来理解内存里的数据。
那说明n1既不是数组也不是链表,那是什么呢?我们来试试查看从他开始的接下来的变量名,但是,内存空间该地址该怎么变换呢?地址该+4还是+8还是别的特殊位数?目前我们不知道,就先不碰运气枚举查看了,先往下看。
从fun7出来后:
0x08048eca <+56>: add $0x10,%esp
0x08048ecd <+59>: cmp $0x5,%eax
0x08048ed0 <+62>: je 0x8048ed7 <secret_phase+69>
0x08048ed2 <+64>: call 0x804907d <explode_bomb>
;fun7返回值不等于5就爆炸
0x08048ed7 <+69>: sub $0xc,%esp
0x08048eda <+72>: push $0x8049fbc
0x08048edf <+77>: call 0x80487c0 <puts@plt>
0x08048ee4 <+82>: call 0x80491d6 <phase_defused>
;fun7返回值等于5就打印提示语 通关
0x08048ee9 <+87>: add $0x18,%esp
0x08048eec <+90>: pop %ebx
0x08048eed <+91>: ret
小结一下,目前得到的信息就是隐藏关的通关字符串是一个数字,且不能超过1000。数据结构未知。
分析这一关的数据结构
0x08048e5b <+26>: pushl 0x4(%edx)
0x08048e77 <+54>: pushl 0x8(%edx)
从这两句可以看出,那个新的数据结构能够按+4和+8两种方式跳转到下面的存储单元。
知道这个就可以查看余下的其他的变量了:
发现这个数据结构的命名是层数+在当前层的序号,每个节点存了自己本身的值还有2个指向下层节点的指针。而且一共有四层,每个节点有2个分支。所以这一关的数据结构是二叉树,更确切可以发现是满二叉搜索树:
分析fun7代码
扫一眼就发现又是递归。
//按汇编代码直译:
int fun7(node *p,int input){
int result;
if(p == NULL){
result = -1;
}else{
if( p->val > input){
return fun7(p->left,input) * 2 ;
}
result = 0;
if(p->val != input){
result = fun7(p->right,input) * 2 + 1 ;
}
}
return result;
}
//按汇编代码意译:
int fun7(node *p,int input){
if(p == NULL){
return -1 ;
}
if( input == p->val ){
return 0 ;
}
if( input < p->val ){
return fun7( p->left, input ) * 2 ;
}
if( input > p->val ){
return fun7( p->right, input ) * 2 + 1 ;
}
}
之前分析fun7最后应该要返回5,不然就会触发炸弹。那就意味着:
- 输入的值不能等于36这个根节点,不然直接返回0。
- 如果过程中来到了叶子节点,并且此时它与输入值不相等,就会导致返回值为-1,就不可能通过(-1)*2 +1or +0 最后达成返回5的结果,所以输入值不能是遍历到达了叶子结点发现找不到,回溯到上一层去另外一个节点,即过程中不可以发生回溯。所以最后一定是找到了,所以输入值是树上的节点。
- 所以5应该是递去到结点等于输入值,然后返回0, 然后一次次归回0x2+1, 1x2=2, 2x2 +1 =5。
上面是从递去终点开始分析的,现在回过来结合fun7代码按正常顺序:
- 输入值 > 根 去右孩子
- 输入值 < 当前节点的值 去左孩子
- 输入值 > 当前节点的值 去右孩子
- 输入值 == 当前节点的值 返回
在刚刚画出的二叉树上走一遍即可发现输入值应该是47。
后记
写到这里,这个系列共7篇文章终于完结了。
一路写下来发现有些地方可以讲解得更连贯,但是博客讲解还是有点受限,也许以后会录制视频版?
感谢你看到这里!
更多推荐
所有评论(0)