进制炸弹实验报告
登录putty服务器后,首先运行静态分析工具OBJDUMP 来获得程序的反汇编版本bomb.txt,
用 notepad++ 打开便可通过汇编代码来分析程序。
运行gdb bomb,开始进行动态调试。
首先找到explode_bomb的地址0x80492ab, 在爆炸函数的的入口设置断点,输入break
*0x80492ab 这样就可以避免调试过程中各种原因引起的爆炸。
首先来看 phase1 的代码:
08048f19
:
8048f19: 55 push %ebp
8048f1a: 89 e5 mov %esp,%ebp
8048f1c: 83 ec 08 sub $0x8,%esp
8048f1f: c7 44 24 04 ec 98 04 movl $0x80498ec,0x4(%esp)
8048f26: 08
8048f27: 8b 45 08 mov 0x8(%ebp),%eax
8048f2a: 89 04 24 mov %eax,(%esp)
8048f2d: e8 39 00 00 00 call 8048f6b
8048f32: 85 c0 test %eax,%eax
8048f34: 74 05 je 8048f3b
8048f36: e8 70 03 00 00 call 80492ab
8048f3b: c9 leave
8048f3c: 8d 74 26 00 lea 0x0(%esi),%esi
8048f40: c3 ret
注意 strings_not_equal 这个函数,从字面上可以猜想地理解为:把输入的字符串和内存
某处字符串相比较,不相等的时候函数返回值的为 1。再看接下来两句:
test %eax,%eax
je 8048f3b
Test 是把两个操作数进行与运算,而通常这两个操作数是一样的,此操作的意义就在于影
响标志位,当%eax 为 0 时,零标志位置 1,否则零标志为 0。而从 je 指令可以知道当%eax
为 0 时程序会跳过接下来一句对爆炸函数的引用,所以我们的目标就是要是 %eax 即
strings_not_equal 的返回值为 0,即要是输入的字符串与内存某处的存放的字符串相等。
于是现在的关键就是找出那个字符串是放在内存的哪个地方。其实非常明显,$0x80498ec 这
个地址在程序里实在太显眼,用 x/s 0x80498ec 命令一查,果然那里存放有一句话:"The
future will be better tomorrow."
输入进去之后出现如下提示开始第二个炸弹:
然后来看 phase2:
08048eb9 :
8048eb9: 55 push %ebp
8048eba: 89 e5 mov %esp,%ebp
8048ebc: 56 push %esi
8048ebd: 53 push %ebx
8048ebe: 83 ec 30 sub $0x30,%esp
8048ec1: 8d 45 e0 lea -0x20(%ebp),%eax
8048ec4: 89 44 24 04 mov %eax,0x4(%esp)
8048ec8: 8b 45 08 mov 0x8(%ebp),%eax
8048ecb: 89 04 24 mov %eax,(%esp)
8048ece: e8 1a 04 00 00 call 80492ed
8048ed3: 83 7d e0 01 cmpl $0x1,-0x20(%ebp)
8048ed7: 74 29 je 8048f02
8048ed9: e8 cd 03 00 00 call 80492ab
8048ede: 66 90 xchg %ax,%ax
8048ee0: eb 20 jmp 8048f02
8048ee2: 89 da mov %ebx,%edx
8048ee4: 83 c3 01 add $0x1,%ebx
8048ee7: 89 d8 mov %ebx,%eax
8048ee9: 0f af 44 96 fc imul -0x4(%esi,%edx,4),%eax
8048eee: 39 04 96 cmp %eax,(%esi,%edx,4)
8048ef1: 74 05 je 8048ef8
8048ef3: e8 b3 03 00 00 call 80492ab
8048ef8: 83 fb 06 cmp $0x6,%ebx
8048efb: 75 e5 jne 8048ee2
8048efd: 8d 76 00 lea 0x0(%esi),%esi
8048f00: eb 10 jmp 8048f12
8048f02: bb 01 00 00 00 mov $0x1,%ebx
8048f07: 8d 75 e0 lea -0x20(%ebp),%esi
8048f0a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048f10: eb d0 jmp 8048ee2
8048f12: 83 c4 30 add $0x30,%esp
8048f15: 5b pop %ebx
8048f16: 5e pop %esi
8048f17: 5d pop %ebp
8048f18: c3 ret
分析解答:
注意到 read_six_numbers 这个函数,同样顾名思义我先猜他为输入 6 个数字。暂且不看
read_six_numbers 函数的内容,先注意 8048ed3 这句,cmpl 显然是要引起我高度注意的,
因为比较的结果往往直接关系到爆不爆的问题。这条指令的内容很明确,就是看 -0x20(%ebp)
处的数字是否为 1,不是的话就会调用爆炸函数。于是我非常坚定地认为 1 便是我该输入的
第一个数字,而输入的数字是存放在 -0x20(%ebp) 开始的地址处的。
接着看下去:
8048ee9: 0f af 44 96 fc imul -0x4(%esi,%edx,4),%eax
8048eee: 39 04 96 cmp %eax,(%esi,%edx,4)
及
8048f07: 8d 75 e0 lea -0x20(%ebp),%esi
8048ee9 这句中的 -0x4(%esi,%edx,4) 这个存储器操作数稍作思考便可以知道其实就是
-0x20(%ebp),即是我们要输入的第一个数字,因为此时%eax 为 2。所以这句是将第一个数
字乘以 2 放入%eax。8048eee 中将 %eax 和 (%esi,%edx,4)进行比较,相等则继续运行,
很容易看出(%esi,%edx,4)即-0x1c(%ebp)是我们输入第 2 个数字的地方。
8048ef8 和 8048efb 两句告诉我们从%ebx 等于 1 到 5 进行以上操作,即后面一个数字由
前面一个数字乘以%eax 得到,而每次的乘数%eax 为%ebx 加 1,即乘数分别为 2,3,4,5,
6,所以可以确定这六个数字分别为 1,2,3,4,5,6 的阶乘,即 1 2 6 24 120 720。
于是输入,果然:That's number 2. Keep going!
8048e97: 90 nop
8048e98: eb 10 jmp 8048eaa
8048e9a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048ea0: e8 06 04 00 00 call 80492ab
8048ea5: b8 00 00 00 00 mov $0x0,%eax
8048eaa: 3b 45 f8 cmp -0x8(%ebp),%eax
8048ead: 8d 76 00 lea 0x0(%esi),%esi
8048eb0: 74 05 je 8048eb7
8048eb2: e8 f4 03 00 00 call 80492ab
8048eb7: c9 leave
8048eb8: c3 ret
分析解答:
Bomb3 的代码就比较长了,先抓住重点:jmp *0x8049920(,%eax,4)这句话容易让人联想到
switch 语句。再翻翻书,发现这段代码就是一个跳转表结构。从语句上便可以看出备选的
跳转地址存放在 0x8049920 开始的地址处,通过%eax 的值来选择。通过打印 0x8049920 处
的 16 进制数可以确认:
switch 转换表是这样的:
0x08049920
0x8048e62
0x8048e57
0x8048e6a
0x8048e72
0x8048e7a
0x8048e82
0x8048e8a
0x8048e92
可以看到这 8 个 16 进制数正好是程序中的 8 个地址(都用黑体标出),对应于%eax 为 0 到
7 时的跳转地址。由于
8048e10: c3 ret
相关函数 func4:
08048bd0
:
8048bd0: 55 push %ebp
8048bd1: 89 e5 mov %esp,%ebp
8048bd3: 83 ec 18 sub $0x18,%esp
8048bd6: 89 5d f8 mov %ebx,-0x8(%ebp)
8048bd9: 89 75 fc mov %esi,-0x4(%ebp)
8048bdc: 8b 75 08 mov 0x8(%ebp),%esi
8048bdf: b8 01 00 00 00 mov $0x1,%eax
8048be4: 83 fe 01 cmp $0x1,%esi
8048be7: 7e 1a jle 8048c03
8048be9: 8d 46 ff lea -0x1(%esi),%eax
8048bec: 89 04 24 mov %eax,(%esp)
8048bef: e8 dc ff ff ff call 8048bd0
8048bf4: 89 c3 mov %eax,%ebx
8048bf6: 8d 46 fe lea -0x2(%esi),%eax
8048bf9: 89 04 24 mov %eax,(%esp)
8048bfc: e8 cf ff ff ff call 8048bd0
8048c01: 01 d8 add %ebx,%eax
8048c03: 8b 5d f8 mov -0x8(%ebp),%ebx
8048c06: 8b 75 fc mov -0x4(%ebp),%esi
8048c09: 89 ec mov %ebp,%esp
8048c0b: 5d pop %ebp
8048c0c: c3 ret
分析解答:
首先要研究下 func4 的功能。
容易看出是一个递归函数。lea -0x1(%esi),%eax 是得到%esi-1 的值然后调用 func4,同
样 lea -0x2(%esi),%eax 是得到%esi-2 的值然后调用 func4,add %ebx,%eax 即是将 f
(%esi-1)的返回值(在%ebx 里面)与 f(%esi-2)的返回值(在%eax 里面)相加放在%eax
中作为 func4 的返回值,很明显这是一个斐波那契数列的函数。
细节研究可知:
func4(0) = 1,func4(1) = 1;
func4(2) = func4(1) + func4(0) = 2;
func4(3) = func4(2) + func4(1) = 3;
func4(4) = func4(3) + func4(2) = 5;
...
func4(9) = func4(8) + func4(7) = 55;
明白了 func4 的意思,我直插 phase4 的心脏:
cmp $0x37,%eax je 8048e0a
很清楚,当%eax 等于 0x37 的时候就可以过关了。%eax 就是 call 8048bd0 后的返
回值!调用的函数参数就是我们输入的数字。把斐波那契数列一排,0x37 对应的序号为 9,
输入,果然:So you got that one. Try this one.
第五个 bomb:
08048d6e
:
8048d6e: 55 push %ebp
8048d6f: 89 e5 mov %esp,%ebp
8048d71: 57 push %edi
8048d72: 56 push %esi
8048d73: 53 push %ebx
8048d74: 83 ec 0c sub $0xc,%esp
8048d77: 8b 75 08 mov 0x8(%ebp),%esi
8048d7a: 89 34 24 mov %esi,(%esp)
8048d7d: e8 ce 01 00 00 call 8048f50
8048d82: 83 f8 06 cmp $0x6,%eax
8048d85: 74 05 je 8048d8c
8048d87: e8 1f 05 00 00 call 80492ab
8048d8c: ba 00 00 00 00 mov $0x0,%edx
8048d91: b9 00 00 00 00 mov $0x0,%ecx
8048d96: bb 40 99 04 08 mov $0x8049940,%ebx
8048d9b: 0f be 04 16 movsbl (%esi,%edx,1),%eax
8048d9f: 83 e0 0f and $0xf,%eax
8048da2: 03 0c 83 add (%ebx,%eax,4),%ecx
8048da5: 83 c2 01 add $0x1,%edx
8048da8: 83 fa 06 cmp $0x6,%edx
8048dab: 75 ee jne 8048d9b
8048dad: 83 f9 1b cmp $0x1b,%ecx
8048db0: 74 05 je 8048db7
8048db2: e8 f4 04 00 00 call 80492ab
8048db7: 83 c4 0c add $0xc,%esp
8048dba: 5b pop %ebx
8048dbb: 5e pop %esi
8048dbc: 5f pop %edi
8048dbd: 5d pop %ebp
8048dbe: c3 ret
分析解答:
首先,call 8048f50 和 cmp $0x6,%eax 两句告诉我们要输入的是 6 个字
符。在指令 movsbl (%esi,%edx,1),%eax 中,%esi 为 6 个字符的起始地址,通过循环增
加%edx 的值来依次将这 6 个字符的 ASCII 码传给%eax 进行下一步操作。