计算机系统bomb实验:隐藏关secret_phase详解

我们解完六个关卡后似乎程序已经运行完成了,说明隐藏关卡还需要一定的条件才能触发,那么我们首先就要先去找到触发隐藏关卡的条件。我们看到bomb.c文件,每段phase函数运行完成以后又会运行一个phase_defused()函数,这个函数我们在上述拆炸弹过程中都没有用到,自然它的嫌疑就很大,故我们先看看这个函数的具体内容:

Dump of assembler code for function phase_defused:
=> 0x0804923b <+0>:	sub    esp,0x8c									//开辟栈空间
   0x08049241 <+6>:	mov    eax,gs:0x14								
   //储存金丝雀值,进行栈保护
   0x08049247 <+12>:	mov    DWORD PTR [esp+0x7c],eax				
   //把金丝雀值放到0x7c(%esp)
   0x0804924b <+16>:	xor    eax,eax								//将%eax进行置零
   0x0804924d <+18>:	cmp    DWORD PTR ds:0x804c3cc,0x6
   //将0x804c3cc地址中的值与6进行比较,可以猜测这个地址储存的值是你通过的关的数量
   0x08049254 <+25>:	jne    0x80492c8 <phase_defused+141>		//如果不等于跳转到+141
   0x08049256 <+27>:	lea    eax,[esp+0x2c]						//参数的地址
   0x0804925a <+31>:	mov    DWORD PTR [esp+0x10],eax				//把参数地址保存
   0x0804925e <+35>:	lea    eax,[esp+0x28]						//第二个参数地址
   0x08049262 <+39>:	mov    DWORD PTR [esp+0xc],eax				//第二个参数地址保存
   0x08049266 <+43>:	lea    eax,[esp+0x24]						//第三个
   0x0804926a <+47>:	mov    DWORD PTR [esp+0x8],eax				//第三个
   //这我不自主的看看存放的值0x00000024	0x00000003	0xbffff264,这是这三个参数的值,前面两个数十进制分别是36 3,这里联想到前面一关也输入了这个,为了验证猜想可以看看地址
   0x0804926e <+51>:	mov    DWORD PTR [esp+0x4],0x804a3a9		
   //一个字符串地址,"%d %d %s"
   0x08049276 <+59>:	mov    DWORD PTR [esp],0x804c4d0
   //0x804c4d0 <input_strings+240>:	 "",这里对上述的假设验证,我们查看发现0x0804c4d0其实是字符串"36 3"的地址,也就是第四关输入的地址,那么我们只需要在后面添加一个输入即可,输入什么呢?接着看
   0x0804927d <+66>:	call   0x8048870 <__isoc99_sscanf@plt>
   //进行输入了
   0x08049282 <+71>:	cmp    eax,0x3
   //参数的个数与3进行比较
   0x08049285 <+74>:	jne    0x80492bc <phase_defused+129>
   //如果不等于跳转+129
   0x08049287 <+76>:	mov    DWORD PTR [esp+0x4],0x804a3b2
   //0x804a3b2:	 "DrEvil"
   0x0804928f <+84>:	lea    eax,[esp+0x2c]
   //调用函数前的参数的准备,进行传参,猜测应该是你输入的字符串,所以应该输入"DrEvil"
   //那么我们在第四关的时候就输入36 3 DrEvil,这时候完成其他六关会出现提示哦!
   0x08049293 <+88>:	mov    DWORD PTR [esp],eax
   //参数的地址拿到栈顶
   0x08049296 <+91>:	call   0x8048fa4 <strings_not_equal>
   //调用函数进行比较
   0x0804929b <+96>:	test   eax,eax
   //判断结果eax存放值的符号
   0x0804929d <+98>:	jne    0x80492bc <phase_defused+129>
   //如果不相等就跳转到+129,否则执行执行下方的secret_phase
   0x0804929f <+100>:	mov    DWORD PTR [esp],0x804a278
   //0x804a278:	 "Curses, you've found the secret phase!"
   0x080492a6 <+107>:	call   0x8048800 <puts@plt>
   0x080492ab <+112>:	mov    DWORD PTR [esp],0x804a2a0
   //0x804a2a0:	 "But finding it and solving it are quite different..."
   0x080492b2 <+119>:	call   0x8048800 <puts@plt>
   //打印以上两句话
   0x080492b7 <+124>:	call   0x8048e97 <secret_phase>
   //调用隐藏炸弹
   0x080492bc <+129>:	mov    DWORD PTR [esp],0x804a2d8
   //以第一个炸弹为例,这时候这个代表的是"Congratulations! You've defused the bomb!"
   0x080492c3 <+136>:	call   0x8048800 <puts@plt>
   //把上面的字符串进行输出
   0x080492c8 <+141>:	mov    eax,DWORD PTR [esp+0x7c]				//取出程序运行完之后的金丝雀值
   0x080492cc <+145>:	xor    eax,DWORD PTR gs:0x14				//对金丝雀值进行对比,检查是否发生异常
   0x080492d3 <+152>:	je     0x80492da <phase_defused+159>		//如果结果为0,表示正常
   0x080492d5 <+154>:	call   0x80487d0 <__stack_chk_fail@plt>		//否者报错
   0x080492da <+159>:	add    esp,0x8c
   0x080492e0 <+165>:	ret    
End of assembler dump.

我们反汇编看一看secret_phase的反汇编代码:

Dump of assembler code for function secret_phase:
=> 0x08048e97 <+0>:	push   ebx
   		//保存旧的ebp地址
   0x08048e98 <+1>:	sub    esp,0x18
       //开辟栈空间
   0x08048e9b <+4>:	call   0x80490dd <read_line>
       //读入一行数据,假设我们输入"100"
   0x08048ea0 <+9>:	mov    DWORD PTR [esp+0x8],0xa
       //输入参数10
   0x08048ea8 <+17>:	mov    DWORD PTR [esp+0x4],0x0
       //输入参数0
   0x08048eb0 <+25>:	mov    DWORD PTR [esp],eax
       //把eax中的值保存在栈顶,显然这个eax是我们输入字符串的地址
   0x08048eb3 <+28>:	call   0x80488e0 <strtol@plt>
       //经过这个函数eax保存的是0x64显然是100的16进制,那么推测上面函数是将一个字符串的数转化为整数
   0x08048eb8 <+33>:	mov    ebx,eax
       //用ebx保存100
   0x08048eba <+35>:	lea    eax,[eax-0x1]
       //eax-1
   0x08048ebd <+38>:	cmp    eax,0x3e8
       //eax即99和1000比较(后面观察到这个是二叉树的最大值-1)
   0x08048ec2 <+43>:	jbe    0x8048ec9 <secret_phase+50>
       //如果小于等于跳转+50,不会爆炸哦
   0x08048ec4 <+45>:	call   0x80490b6 <explode_bomb>
   0x08048ec9 <+50>:	mov    DWORD PTR [esp+0x4],ebx
       //把100移到esp+4,传参
   0x08048ecd <+54>:	mov    DWORD PTR [esp],0x804c088
       //gdb-peda$ x/s 0x804c088 0x804c088 <n1>:	 "$"    
       //gdb-peda$ x/xw 0x804c088 0x804c088 <n1>:	0x00000024
   0x08048ed4 <+61>:	call   0x8048e46 <fun7>
       //调用函数fun7
   0x08048ed9 <+66>:	cmp    eax,0x2
       //当返回值等于2时,bingo!
   0x08048edc <+69>:	je     0x8048ee3 <secret_phase+76>
   0x08048ede <+71>:	call   0x80490b6 <explode_bomb>
   0x08048ee3 <+76>:	mov    DWORD PTR [esp],0x804a1ac
   0x08048eea <+83>:	call   0x8048800 <puts@plt>
       //输出"Wow! You've defused the secret stage!"
   0x08048eef <+88>:	call   0x804923b <phase_defused>
   0x08048ef4 <+93>:	add    esp,0x18
       //esp恢复调用前的状态
   0x08048ef7 <+96>:	pop    ebx
       //恢复ebp
   0x08048ef8 <+97>:	ret    
       //跳转到调用函数时call的下一条语句
End of assembler dump.

fun7函数:显然地,这又是一个递归函数

Dump of assembler code for function fun7:
=> 0x08048e46 <+0>:	push   ebx
   		//放入ebx
   0x08048e47 <+1>:	sub    esp,0x18
       //为栈帧开辟空间
   0x08048e4a <+4>:	mov    edx,DWORD PTR [esp+0x20]
       //读入形式参数edx = 0x804c088
   0x08048e4e <+8>:	mov    ecx,DWORD PTR [esp+0x24]
       //读入形式参数ecx = 0x64
       
       //这里推测输入的两个参数一个数地址一个是数值,又结合递归联想到他可能和树结构有关系,假设函数		   //为fun7(root, val)
       
   0x08048e52 <+12>:	test   edx,edx
       //判断edx的符号
   0x08048e54 <+14>:	je     0x8048e8d <fun7+71>
       //等于0,跳转至+71,其实可以理解为节点为NULL
   0x08048e56 <+16>:	mov    ebx,DWORD PTR [edx]
       //ebx = 0x00000024,节点的数值
   0x08048e58 <+18>:	cmp    ebx,ecx
       //把节点的数值和输入的形参数值比较
   0x08048e5a <+20>:	jle    0x8048e6f <fun7+41>
       //如果ebx<=ecx。跳转至+41
       //如果ebx里面的值更大
   0x08048e5c <+22>:	mov    DWORD PTR [esp+0x4],ecx
       //把ecx保存在esp+0x4
   0x08048e60 <+26>:	mov    eax,DWORD PTR [edx+0x4]
       //传递参数,这里传入的是左节点,edx +0x4是左节点 +0x8是右节点
   0x08048e63 <+29>:	mov    DWORD PTR [esp],eax
       //eax放到栈顶,方便调用
   0x08048e66 <+32>:	call   0x8048e46 <fun7>
       //调用函数fun7
   0x08048e6b <+37>:	add    eax,eax
       //eax = 2*eax,return 2*fun7(val1,val2),val1 = root->left,val2 = val
   0x08048e6d <+39>:	jmp    0x8048e92 <fun7+76>
       //跳出递归
   0x08048e6f <+41>:	mov    eax,0x0
       //将eax置0
   0x08048e74 <+46>:	cmp    ebx,ecx
   0x08048e76 <+48>:	je     0x8048e92 <fun7+76>
       //如果ebx(0x24) == ecx(0x64),(显然不等于),跳转至+76,就是直接返回了,return 0
   0x08048e78 <+50>:	mov    DWORD PTR [esp+0x4],ecx
   0x08048e7c <+54>:	mov    eax,DWORD PTR [edx+0x8]
       //为什么加8,摆明了是指向其右节点
   0x08048e7f <+57>:	mov    DWORD PTR [esp],eax
       //以上三步就是参数的传递
   0x08048e82 <+60>:	call   0x8048e46 <fun7>
   0x08048e87 <+65>:	lea    eax,[eax+eax*1+0x1]
       //递归的最后一步结果的线性组合,return 2*fun7(val1,val2)+1,val1 = root->right,val2 = val
   0x08048e8b <+69>:	jmp    0x8048e92 <fun7+76>
       //结束递归
   0x08048e8d <+71>:	mov    eax,0xffffffff
       //将0xffffffff传入eax
   0x08048e92 <+76>:	add    esp,0x18
   0x08048e95 <+79>:	pop    ebx
   0x08048e96 <+80>:	ret  
       //恢复调用前的状态
End of assembler dump.

我们写出递归函数:

int fun7(Node *root, int val){
    if(root == NULL)
        return -1;
    if(root->value == val)
         return 0;       
    else if(root->value > val)
    	return 2*fun7(root->left,val);
    else if(root->value < val)
        return 2*fun7(root->right,val)+1;
}

可以通过以下指令查看一个结点的信息:

p/x *0x804c088@3

通过这个可以查看一个节点,其中0x804c088是一个结点的地址指针,可以自行修改。

gdb-peda$ p/x *0x804c088@3
$5 = {0x24, 0x804c094, 0x804c0a0}
gdb-peda$ p/x *0x804c088@3
$6 = {0x24, 0x804c094, 0x804c0a0}
gdb-peda$ p/x *0x804c094@3
$8 = {0x8, 0x804c0c4, 0x804c0ac}
gdb-peda$ p/x *0x804c0c4@3
$9 = {0x6, 0x804c0e8, 0x804c10c}
gdb-peda$ p/x *0x804c0ac@3
$10 = {0x16, 0x804c118, 0x804c100}
gdb-peda$ p/x *0x804c0e8@3
$11 = {0x1, 0x0, 0x0}
gdb-peda$ p/x *0x804c10c@3
$12 = {0x7, 0x0, 0x0}
gdb-peda$ p/x *0x804c118@3
$13 = {0x14, 0x0, 0x0}
gdb-peda$ p/x *0x804c100@3
$14 = {0x23, 0x0, 0x0}
gdb-peda$ p/x *0x804c0ac@3
$15 = {0x16, 0x804c118, 0x804c100}
gdb-peda$ p/x *0x804c0a0@3
$16 = {0x32, 0x804c0b8, 0x804c0d0}
gdb-peda$ p/x *0x804c0b8@3
$17 = {0x2d, 0x804c0dc, 0x804c124}
gdb-peda$ p/x *0x804c0d0@3
$18 = {0x6b, 0x804c0f4, 0x804c130}
gdb-peda$ p/x *0x804c0dc@3
$19 = {0x28, 0x0, 0x0}
gdb-peda$ p/x *0x804c124@3
$20 = {0x2f, 0x0, 0x0}
gdb-peda$ p/x *0x804c0f4@3
$21 = {0x63, 0x0, 0x0}
gdb-peda$ p/x *0x804c130@3
$22 = {0x3e9, 0x0, 0x0}

通过上述的结点的信息,可以大致画出以下的树结构图(16进制):

​ 24

​ 8 23

​ 6 16 2d 6b

​ 1 7 14 23 28 2f 63 3e9
(显示有问题,意会一下,它是一颗满二叉树,可以自己画一画)

限制条件:

   0x08048ebd <+38>:	cmp    eax,0x3e8
       //eax即99和1000比较(后面观察到这个是二叉树的最大值-1)
   0x08048ec2 <+43>:	jbe    0x8048ec9 <secret_phase+50>

在每一个节点都有两种选择,然后利用递归的性质我们可以得到以下的展开图:

在这里插入图片描述

那么显然有:

  1. 根->左->右 :0x16
  2. 根->左->右->左 :0x14
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

call me Patrick

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值