计算机系统基础实验2——bomb
实验介绍使用课程知识拆除一个“Binary Bombs”来增强对程序的机器级表示、汇编语言、调试器和逆向工程等理解。一个“Binary Bombs”(二进制炸弹,简称炸弹)是一个Linux可执行C程序,包含phase1~phase6共6个阶段。炸弹运行各阶段要求输入一个字符串,若输入符合程序预期,该阶段炸弹被“拆除”,否则“爆炸” 。实验目标是你需要拆除尽可能多的炸弹。实验步骤:第一步:反汇编将自
实验介绍
- 使用课程知识拆除一个“Binary Bombs”来增强对程序的机器级表示、汇编语言、调试器和逆向工程等理解。
- 一个“Binary Bombs”(二进制炸弹,简称炸弹)是一个Linux可执行C程序,包含phase1~phase6共6个阶段。
- 炸弹运行各阶段要求输入一个字符串,若输入符合程序预期,该阶段炸弹被“拆除”,否则“爆炸” 。
- 实验目标是你需要拆除尽可能多的炸弹。
实验步骤:
第一步:反汇编
将自己的炸弹文件复制到share目录下
解压炸弹文件,并 cd
到解压出的子目录
对bomb
进行反汇编并将汇编代码输出到asm.txt
中:objdump –d bomb > asm.txt
查看生成的asm.txt
文件,结合bomb.c
中的代码框架和汇编代码,找出:函数之间的调用关系、传递的参数;
第二步:查看汇编源代码asm.txt文件
第三步:执行 gdb bomb
,找到拆弹字符串。
一句话:通过阅读执行判断操作的汇编代码,得出正确输入。
实验内容:
<phase_1> :字符串比较
08048b33 <phase_1>: #字符串比较
8048b33: 83 ec 14 sub $0x14,%esp
8048b36: 68 2c a1 04 08 push 0x804a12c #(gdb) x/s 0x804812c,便找到输入的字符串;
8048b3b:ff 74 24 1c pushl 0x1c(%esp)
8048b3f: e8 7c 04 00 00 call 8048fc0 <strings_not_equal>
8048b44: 83 c4 10 add $0x10,%esp
8048b47: 85 c0 test %eax,%eax #判断两个字符串是否相等。
8048b49: 74 05 je 8048b50 <phase_1+0x1d>
8048b4b:e8 ad 06 00 00 call 80491fd <explode_bomb>
8048b50: 83 c4 0c add $0xc,%esp
8048b53: c3 ret
gdb调试,找到判断时所对照的正确字符串,便是正确输入。
gdb bomb
:进入bomb文件的调试界面;
x/s 0x804812c
:找到该位置中存放的正确字符串。
找到的字符串为:When I get angry, Mr. Bigglesworth gets upset.
<phase_2>:循环
//例1:
08048b54 <phase_2>: #循环
8048b54: 56 push %esi
8048b55: 53 push %ebx #没有修正ebp,还在main函数底部
8048b56: 83 ec 2c sub $0x2c,%esp
8048b59: 65 a1 14 00 00 00 mov %gs:0x14,%eax #gs段寄存器,保存段内地址
8048b5f: 89 44 24 24 mov %eax,0x24(%esp) #检测栈帧有没有被破坏
8048b63: 31 c0 xor %eax,%eax
8048b65: 8d 44 24 0c lea 0xc(%esp),%eax
8048b69: 50 push %eax
8048b6a: ff 74 24 3c pushl 0x3c(%esp) #修正到main函数栈帧中取参数,相当于ebp+8
8048b6e: e8 c7 06 00 00 call 804923a <read_six_numbers> #从input分离出6个数字
8048b73: 83 c4 10 add $0x10,%esp
8048b76: 83 7c 24 04 01 cmpl $0x1,0x4(%esp) #判断第一个数是否为1
8048b7b:74 05 je 8048b82 <phase_2+0x2e>
8048b7d:e8 7b 06 00 00 call 80491fd <explode_bomb>
8048b82: 8d 5c 24 04 lea 0x4(%esp),%ebx #现在ebx存的是数1所在位置
8048b86: 8d 74 24 18 lea 0x18(%esp),%esi #设置esi为为ebx循环的终止位置
//循环部分
8048b8a: 8b 03 mov (%ebx),%eax #把ebx中存的数赋给eax
8048b8c: 01 c0 add %eax,%eax #eax+=eax;
8048b8e: 39 43 04 cmp %eax,0x4(%ebx) #看ebx+4位置上的数是否为eax;
8048b91: 74 05 je 8048b98 <phase_2+0x44>
8048b93: e8 65 06 00 00 call 80491fd <explode_bomb>
8048b98: 83 c3 04 add $0x4,%ebx #找下一个位置上的数,ebx+=4;
8048b9b:39 f3 cmp %esi,%ebx #判断ebx是否已经到达终止位置,是否要继续循环
8048b9d:75 eb jne 8048b8a <phase_2+0x36> #继续的话,跳转到8048b8a;
8048b9f: 8b 44 24 1c mov 0x1c(%esp),%eax
8048ba3: 65 33 05 14 00 00 00 xor %gs:0x14,%eax #无影响,不管
8048baa: 74 05 je 8048bb1 <phase_2+0x5d>
8048bac: e8 df fb ff ff call 8048790 <__stack_chk_fail@plt>
8048bb1:83 c4 24 add $0x24,%esp
8048bb4:5b pop %ebx
8048bb5:5e pop %esi
8048bb6:c3 ret
由 call 804923a <read_six_numbers>
可知,输入的字符串包括 6 个数。
由 8048b9d
上的指令jne 8048b8a <phase_2+0x36>
,又跳转回了上面的位置,可以知道这段是循环过程:
ebx
从第一个数所在的位置开始循环,esi
存放最后一个数所在的位置。
每次将 ebx
中的数赋值为到 eax
上,eax*=2
,判断下一位置上的数是否和 eax
中的值相等。也就是说,后面一个数的位置一定是前面一个数的2倍。
而由 cmpl $0x1,0x4(%esp)
可知第一个数为1,那么最终的答案便为1 2 4 8 16 32
。
//例2:
08048b8c <phase2>:
8048b8c: 55 push %ebp
8048b8d:89 e5 mov %esp,%ebp
8048b8f: 56 push %esi
8048b90: 53 push %ebx
8048b91: 83 ec 28 sub $0x28,%esp
8048b94: 8d 45 e0 lea -0x20(%ebp),%eax
8048b97: 50 push %eax
8048b98: ff 75 08 pushl 0x8(%ebp)
8048b9b:e8 ca 05 00 00 call 804916a <readsixnumbers>
8048ba0: 83 c4 10 add $0x10,%esp
8048ba3: 83 7d e0 00 cmpl $0x0,-0x20(%ebp) #判断第一个值是否为0
8048ba7: 75 06 jne 8048baf <phase2+0x23>
8048ba9: 83 7d e4 01 cmpl $0x1,-0x1c(%ebp) #判断第二个值是否为1
8048bad: 74 05 je 8048bb4 <phase2+0x28>
8048baf: e8 8e 05 00 00 call 8049142 <explodebomb>
8048bb4:8d 5d e0 lea -0x20(%ebp),%ebx #将0的地址放入ebx
8048bb7:8d 75 f0 lea -0x10(%ebp),%esi #设置ebx的循环终止地址
8048bba: eb 07 jmp 8048bc3 <phase2+0x37> #跳过第一次循环的初始值判断部分,直接进行操作
//循环部分
8048bbc: 83 c3 04 add $0x4,%ebx #ebx+=4,地址更新
8048bbf: 39 f3 cmp %esi,%ebx #判断是否循环到终止位置了
8048bc1: 74 11 je 8048bd4 <phase2+0x48> #如果是,退出循环
8048bc3: 8b 43 04 mov 0x4(%ebx),%eax #把下一位置的值放到eax中
8048bc6: 03 03 add (%ebx),%eax #把当前位置的值累加到eax
8048bc8: 39 43 08 cmp %eax,0x8(%ebx) #判断下下位置是否是eax,即其前面两位置之和
8048bcb: 74 ef je 8048bbc <phase2+0x30> #如果是,继续循环;否则爆炸。
8048bcd: e8 70 05 00 00 call 8049142 <explodebomb>
8048bd2:eb e8 jmp 8048bbc <phase2+0x30>
8048bd4:8d 65 f8 lea -0x8(%ebp),%esp
8048bd7:5b pop %ebx
8048bd8:5e pop %esi
8048bd9:5d pop %ebp
8048bda: c3 ret
<phase_3>:跳转
08048bb7 <phase_3>: #跳转
8048bb7:83 ec 1c sub $0x1c,%esp
8048bba: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048bc0: 89 44 24 0c mov %eax,0xc(%esp)
8048bc4: 31 c0 xor %eax,%eax
8048bc6: 8d 44 24 08 lea 0x8(%esp),%eax
8048bca: 50 push %eax
8048bcb: 8d 44 24 08 lea 0x8(%esp),%eax
8048bcf: 50 push %eax
8048bd0:68 8d a3 04 08 push $0x804a38d #(gdb) x/s 0x804a38d,得到 "%d %d",可知输入的是数字,数字;
8048bd5:ff 74 24 2c pushl 0x2c(%esp)
8048bd9:e8 32 fc ff ff call 8048810 <__isoc99_sscanf@plt>
8048bde: 83 c4 10 add $0x10,%esp
8048be1: 83 f8 01 cmp $0x1,%eax #判断输入数字的个数,一定要大于1;
8048be4: 7f 05 jg 8048beb <phase_3+0x34>
8048be6: e8 12 06 00 00 call 80491fd <explode_bomb>
8048beb: 83 7c 24 04 07 cmpl $0x7,0x4(%esp) #判断输入的第一个值是否大于7,因为只有0~7八个分支;
8048bf0: 77 3c ja 8048c2e <phase_3+0x77>
8048bf2: 8b 44 24 04 mov 0x4(%esp),%eax
8048bf6: ff 24 85 8c a1 04 08 jmp *0x804a18c(,%eax,4) //#(gdb) p/x *0x804a18c,得到下一条指令的地址0x8048c3a;
# 因为一共8个跳转分支,所以需要得到其跳转位置。(gdb) x/8x 0x804a18c,得到其8个分支的位置
# 跳转表:
8048bfd: b8 e7 03 00 00 mov $0x3e7,%eax #第1个,如果输入1的话,跳到此处;
8048c02: eb 3b jmp 8048c3f <phase_3+0x88>
8048c04: b8 83 03 00 00 mov $0x383,%eax #第2个
8048c09: eb 34 jmp 8048c3f <phase_3+0x88>
8048c0b: b8 9a 03 00 00 mov $0x39a,%eax #第3个
8048c10: eb 2d jmp 8048c3f <phase_3+0x88>
8048c12: b8 f0 01 00 00 mov $0x1f0,%eax #第4个
8048c17: eb 26 jmp 8048c3f <phase_3+0x88>
8048c19: b8 31 01 00 00 mov $0x131,%eax #第5个
8048c1e: eb 1f jmp 8048c3f <phase_3+0x88>
8048c20: b8 d2 03 00 00 mov $0x3d2,%eax #第6个
8048c25: eb 18 jmp 8048c3f <phase_3+0x88>
8048c27: b8 6b 01 00 00 mov $0x16b,%eax #第7个
8048c2c: eb 11 jmp 8048c3f <phase_3+0x88>
8048c2e: e8 ca 05 00 00 call 80491fd <explode_bomb>
8048c33: b8 00 00 00 00 mov $0x0,%eax
8048c38: eb 05 jmp 8048c3f <phase_3+0x88>
8048c3a: b8 ad 01 00 00 mov $0x1ad,%eax #第0个 如果跳转到此处,eax赋值为(1ad)_16,即429;
8048c3f: 3b 44 24 08 cmp 0x8(%esp),%eax #判断第二个数是否为eax。
8048c43: 74 05 je 8048c4a <phase_3+0x93>
8048c45: e8 b3 05 00 00 call 80491fd <explode_bomb>
8048c4a: 8b 44 24 0c mov 0xc(%esp),%eax
8048c4e: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048c55: 74 05 je 8048c5c <phase_3+0xa5>
8048c57: e8 34 fb ff ff call 8048790 <__stack_chk_fail@plt>
8048c5c: 83 c4 1c add $0x1c,%esp
8048c5f: c3 ret
给出了一个地址 push $0x804a38d
,那么gdb调试 #(gdb) x/s 0x804a38d
,得到 "%d %d"
,可知输入的是数字,数字
,两个数。
由 cmpl $0x7,0x4(%esp)
可以知道,输入的第一个数最大不超过7,说明只有0~7八个分支。
通过gdb调试指令:x/8x 0x804a18c
,便能找到所有分支的地址。
可见,第0个分支的地址为 0x8048c3a
,跳转到该位置,eax
赋值为(1ad)_16
,即十进制的 429
;所以最终的答案可以为:0 429
。
同理,可以找到第1分支,第2分支…上的数,答案不唯一。
<phase_4>:递归调用
08048c60 <func4>:
8048c60: 57 push %edi
8048c61: 56 push %esi
8048c62: 53 push %ebx
8048c63: 8b 5c 24 10 mov 0x10(%esp),%ebx #x = 7
8048c67: 8b 7c 24 14 mov 0x14(%esp),%edi #y = d2
8048c6b: 85 db test %ebx,%ebx
8048c6d: 7e 2b jle 8048c9a <func4+0x3a> #如果x<=0的话,返回0
8048c6f: 89 f8 mov %edi,%eax
8048c71: 83 fb 01 cmp $0x1,%ebx #否则,若x=1,返回y
8048c74: 74 29 je 8048c9f <func4+0x3f>
8048c76: 83 ec 08 sub $0x8,%esp
8048c79: 57 push %edi
8048c7a: 8d 43 ff lea -0x1(%ebx),%eax
8048c7d: 50 push %eax
8048c7e: e8 dd ff ff ff call 8048c60 <func4> #以x-1作为形参继续调用func(x-1,y);
8048c83: 83 c4 08 add $0x8,%esp
8048c86: 8d 34 07 lea (%edi,%eax,1),%esi #esi = y + sub_8048C60(x - 1, y);
8048c89: 57 push %edi
8048c8a: 83 eb 02 sub $0x2,%ebx
8048c8d: 53 push %ebx
8048c8e: e8 cd ff ff ff call 8048c60 <func4> #以x-2作为形参继续调用func(x-2,y);
8048c93: 83 c4 10 add $0x10,%esp
8048c96: 01 f0 add %esi,%eax #否则 x!=1, 返回 esi + func(x-2,y),即y + func(x-1,y) + func(x-2,y)。
8048c98: eb 05 jmp 8048c9f <func4+0x3f>
8048c9a: b8 00 00 00 00 mov $0x0,%eax
8048c9f: 5b pop %ebx
8048ca0: 5e pop %esi
8048ca1: 5f pop %edi
8048ca2: c3 ret
08048ca3 <phase_4>:
8048ca3: 83 ec 1c sub $0x1c,%esp
8048ca6: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8048cac: 89 44 24 0c mov %eax,0xc(%esp)
8048cb0: 31 c0 xor %eax,%eax
8048cb2: 8d 44 24 04 lea 0x4(%esp),%eax
8048cb6: 50 push %eax
8048cb7: 8d 44 24 0c lea 0xc(%esp),%eax
8048cbb: 50 push %eax
8048cbc: 68 8d a3 04 08 push $0x804a38d
8048cc1: ff 74 24 2c pushl 0x2c(%esp)
8048cc5: e8 46 fb ff ff call 8048810 <__isoc99_sscanf@plt>
8048cca: 83 c4 10 add $0x10,%esp
8048ccd: 83 f8 02 cmp $0x2,%eax #判断,如果输入数的个数小于等于2的话,爆炸
8048cd0: 75 0c jne 8048cde <phase_4+0x3b>
8048cd2: 8b 44 24 04 mov 0x4(%esp),%eax
8048cd6: 83 e8 02 sub $0x2,%eax
8048cd9: 83 f8 02 cmp $0x2,%eax
8048cdc: 76 05 jbe 8048ce3 <phase_4+0x40> #判断,第二个数是否大于等于2,小于4
8048cde: e8 1a 05 00 00 call 80491fd <explode_bomb>
8048ce3: 83 ec 08 sub $0x8,%esp
8048ce6: ff 74 24 0c pushl 0xc(%esp)
8048cea: 6a 07 push $0x7
8048cec: e8 6f ff ff ff call 8048c60 <func4 > #func(7,d2)
8048cf1: 83 c4 10 add $0x10,%esp
8048cf4: 3b 44 24 08 cmp 0x8(%esp),%eax #判断第一个数是否为函数返回值
8048cf8: 74 05 je 8048cff <phase_4+0x5c>
8048cfa: e8 fe 04 00 00 call 80491fd <explode_bomb>
8048cff: 8b 44 24 0c mov 0xc(%esp),%eax
8048d03: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
8048d0a: 74 05 je 8048d11 <phase_4+0x6e>
8048d0c: e8 7f fa ff ff call 8048790 <__stack_chk_fail@plt>
8048d11: 83 c4 1c add $0x1c,%esp
8048d14: c3 ret
根据func4函数的汇编代码,还原出其C++程序为:
int __cdecl sub_8048C60(int a1, int a2)
{
int result; // eax
int v3; // esi
if ( a1 <= 0 )
return 0;
result = a2;
if ( a1 != 1 )
{
v3 = a2 + sub_8048C60(a1 - 1, a2);
result = v3 + sub_8048C60(a1 - 2, a2);
}
return result;
}
最初状态,a1为定值7,a2为第二个输入的数。
所以,如果第二个输入2的话,函数递归过程为:func(7,2)调用func(6,2)和func(5,2),func(6,2)再调用func(5,2)和func(4,2),func(5,2)再调用func(4,2)和func(3,2)…
所以,为了计算func(7,2),则需依次计算出func(0,2), func(1,2), func(2,2), func(3,2), func(4,2), func(5,2), func(6,2).
结果依次为:2 4 8 14 24 40,那么最终答案为2 + 24 + 40 = 66。
所以,一种答案可为66 2
。
更多推荐
所有评论(0)