又一个计算系统的实验,这次实验的主要目标是熟悉GDB调试工具,以及一些汇编的语法。
实验环境:32位操作系统(Fedora 13)。
实验内容:本实验设计为一个黑客拆解二进制炸弹的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bomb和主函数所在的源程序bomb.c,不提供每个关卡的源代码。程序运行中有6个关卡(6个phase),每个关卡需要用户输入正确的字符串或数字才能通关,否则会引爆炸弹!
要求同学运用GDB调试工具和objdump反汇编工具,通过分析汇编代码,找到在每个phase程序段中,引导程序跳转到“explode_bomb”程序段的地方,并分析其成功跳转的条件,以此为突破口寻找应该在命令行输入何种字符串来通关。
接下来是各个关卡的解答
1. phase_1
phase_1的代码如图1。
图1
分析:0x8048b3e是bomb引导函数,其转移所需条件是%eax为0,通过阅读strings_not_equal的代码可以知道,若两个字符串相等,则%eax设置为0,否则设置为1;那么,bomb函数的转移条件就变成两个字符串不相等,而两个字符串分别为0x80497c0和%eax所存储的字符串首地址。%eax是用户输入的字符串,那么答案就是地址0x804907c0所存储的字符串。
图2
输出字符串可知答案为“Public speaking is very easy.”。
2. phase_2phase_2汇编代码如下图3所示。
图3
分析:图中红框表示两个炸弹的位置,而黄色方框表示引爆炸弹的转移条件,先分析第一个炸弹,转移条件为M (%ebp-0x18) 的不等于1,那么就需要知道 (%ebp-0x18) 表示什么,第一次出现%ebp-0x18是在地址0x8048b56,此时的%ebp-0x18是作为一个参数传入给函数read_six_numbers,显然,这是一个读取六个数字的函数,那么阅读这个函数的代码,代码如图4所示。从图中可以看出,%ebp的值表示是一个数组的地址,而这个函数就是为这个数组写入值得。从图中可以看到还有一个炸弹,但这个炸弹的意思是如果输入数字不足6个,就引爆炸弹,所以这道题需要输入6个数。而数组的首地址是(%ebp-0x18),那么回到调用函数phase_2,那么调用函数的第一个炸弹的拆除条件就明了了,就是数组的第一个数必须是1。接下来是第二个炸弹,经过阅读和简单的举例,可以得到第二个炸弹之前的那段代码是一个循环,而且是一个迭代,假设数组为a,那么这个循环的意思是a[1]=2*a[0],a[2]=3*a[1],a[3]=4*a[2],以此类推,直到求出a[5]为止,那么这个炸弹也就拆除了。
这道题的答案就是1、2、6、24、120、720
图4
3. phase_3
phase_3的汇编代码如下:
08048b98
8048b98: 55 push %ebp
8048b99: 89 e5 mov %esp,%ebp
8048b9b: 83 ec 14 sub $0x14,%esp
8048b9e: 53 push %ebx
8048b9f: 8b 55 08 mov 0x8(%ebp),%edx
8048ba2: 83 c4 f4 add $0xfffffff4,%esp
8048ba5:8d 45 fc lea -0x4(%ebp),%eax
8048ba8:50 push %eax
8048ba9:8d 45 fb lea -0x5(%ebp),%eax
8048bac:50 push %eax
8048bad:8d 45 f4 lea -0xc(%ebp),%eax
8048bb0:50 push %eax
8048bb1:68 de 97 04 08 push $0x80497de
8048bb6:52 push %edx
8048bb7:e8 a4 fc ff ff call 8048860
8048bbc: 83 c4 20 add $0x20,%esp
8048bbf: 83 f8 02 cmp $0x2,%eax
8048bc2: 7f 05 jg 8048bc9
8048bc4:e8 33 09 00 00 call 80494fc
8048bc9:83 7d f4 07 cmpl $0x7,-0xc(%ebp)
8048bcd:0f 87 b5 00 00 00 ja 8048c88
8048bd3:8b 45 f4 mov -0xc(%ebp),%eax
8048bd6:ff 24 85 e8 97 04 08 jmp *0x80497e8(,%eax,4)
8048bdd: 8d 76 00 lea 0x0(%esi),%esi
8048be0: b3 71 mov $0x71,%bl
8048be2: 81 7d fc 09 03 00 00 cmpl $0x309,-0x4(%ebp)
8048be9: 0f 84 a0 00 00 00 je 8048c8f
8048bef:e8 08 09 00 00 call 80494fc
8048bf4: e9 96 00 00 00 jmp 8048c8f
8048bf9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048c00: b3 62 mov $0x62,%bl
8048c02: 81 7d fc d6 00 00 00 cmpl $0xd6,-0x4(%ebp)
8048c09: 0f 84 80 00 00 00 je 8048c8f
8048c0f:e8 e8 08 00 00 call 80494fc
8048c14: eb 79 jmp 8048c8f
8048c16: b3 62 mov $0x62,%bl
8048c18: 81 7d fc f3 02 00 00 cmpl $0x2f3,-0x4(%ebp)
8048c1f: 74 6e je 8048c8f
8048c21:e8 d6 08 00 00 call 80494fc
8048c26: eb 67 jmp 8048c8f
8048c28: b3 6b mov $0x6b,%bl
8048c2a: 81 7d fc fb 00 00 00 cmpl $0xfb,-0x4(%ebp)
8048c31: 74 5c je 8048c8f
8048c33:e8 c4 08 00 00 call 80494fc
8048c38: eb 55 jmp 8048c8f
8048c3a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048c40: b3 6f mov $0x6f,%bl
8048c42: 81 7d fc a0 00 00 00 cmpl $0xa0,-0x4(%ebp)
8048c49: 74 44 je 8048c8f
8048c4b:e8 ac 08 00 00 call 80494fc
8048c50: eb 3d jmp 8048c8f
8048c52: b3 74 mov $0x74,%bl
8048c54: 81 7d fc ca 01 00 00 cmpl $0x1ca,-0x4(%ebp)
8048c5b: 74 32 je 8048c8f
8048c5d:e8 9a 08 00 00 call 80494fc
8048c62: eb 2b jmp 8048c8f
8048c64: b3 76 mov $0x76,%bl
8048c66: 81 7d fc 0c 03 00 00 cmpl $0x30c,-0x4(%ebp)
8048c6d: 74 20 je 8048c8f
8048c6f:e8 88 08 00 00 call 80494fc
8048c74: eb 19 jmp 8048c8f
8048c76: b3 62 mov $0x62,%bl
8048c78: 81 7d fc 0c 02 00 00 cmpl $0x20c,-0x4(%ebp)
8048c7f: 74 0e je 8048c8f
8048c81:e8 76 08 00 00 call 80494fc
8048c86: eb 07 jmp 8048c8f
8048c88: b3 78 mov $0x78,%bl
8048c8a:e8 6d 08 00 00 call 80494fc
8048c8f: 3a 5d fb cmp -0x5(%ebp),%bl
8048c92: 74 05 je 8048c99
8048c94:e8 63 08 00 00 call 80494fc
8048c99: 8b 5d e8 mov -0x18(%ebp),%ebx
8048c9c: 89 ec mov %ebp,%esp
8048c9e: 5d pop %ebp
8048c9f: c3 ret
简要分析如下:
黄色部分:为输入函数做准备,通过gdb调试分析可知,地址-0x4(%ebp)、-0x5(%ebp)、-0xc(%ebp)为三个要输入的参数,具体调试过程见图5。黄色部分中地址0x80497de存储的是字符串,其值为“%d %c %d”,说明中间的数是char,而前后两数是int。
图5
把断点设在0x8048bbf,run程序,在第三关输入5 a 160的测试数据,然后检查寄存器ebp的值,从汇编代码可以知道,ebp存储的是一个地址,而-0x4(%ebp)为输入的第三个数,-0x5(%ebp)为输入的第二个数,-0xc(%ebp)为输入的第一个数。
红色部分:为炸弹的位置,第一个炸弹比较简单,意思是输入数据少于3个就引爆炸弹。后面的炸弹是针对不同的输入做不同的处理。
绿色部分:switch头部的判断,当第一个输入大于7时,跳到倒数第二个炸弹位置,所以倒数第二个炸弹可以拆除。
其它部分:现在任务就简单了,通过都代码就可以得出答案了。最后一个炸弹是判断第二个参数的,所有答案如下表所示。
第一个参数 |
0 |
1 |
2 |
4 |
5 |
6 |
7 |
第二个参数 |
q |
b |
b |
k |
o |
v |
b |
第三个参数 |
777 |
214 |
755 |
251 |
160 |
458 |
524 |
phase_4代码如下,这里直接阅读代码即可读懂,所以没有调试。
08048ce0
8048ce0: 55 push %ebp
8048ce1: 89 e5 mov %esp,%ebp
8048ce3: 83 ec 18 sub $0x18,%esp
8048ce6: 8b 55 08 mov 0x8(%ebp),%edx
8048ce9: 83 c4 fc add $0xfffffffc,%esp
8048cec: 8d 45 fc lea -0x4(%ebp),%eax
8048cef: 50 push %eax
8048cf0: 68 08 98 04 08 push $0x8049808
8048cf5: 52 push %edx
8048cf6: e8 65 fb ff ff call 8048860
8048cfb: 83 c4 10 add $0x10,%esp
8048cfe: 83 f8 01 cmp $0x1,%eax
8048d01: 75 06 jne 8048d09
8048d03: 83 7d fc 00 cmpl $0x0,-0x4(%ebp)
8048d07: 7f 05 jg 8048d0e
8048d09:e8 ee 07 00 00 call 80494fc
8048d0e: 83 c4 f4 add $0xfffffff4,%esp
8048d11: 8b 45 fc mov -0x4(%ebp),%eax
8048d14: 50 push %eax
8048d15: e8 86 ff ff ff call 8048ca0
8048d1a: 83 c4 10 add $0x10,%esp
8048d1d: 83 f8 37 cmp $0x37,%eax
8048d20: 74 05 je 8048d27
8048d22:e8 d5 07 00 00 call 80494fc
8048d27: 89 ec mov %ebp,%esp
8048d29: 5d pop %ebp
8048d2a: c3 ret
8048d2b: 90 nop
具体分析:
红色部分:炸弹位置,按照第三关的分析方法,第一个炸弹作用是判断输入的数据是否大于0,所以输入的数据只有一个,且其值必须大于0。第二个炸弹判断经过函数func4处理后,得到的结果是否等于55。
接下来主要是看func4的作用。
08048ca0
8048ca0: 55 push %ebp
8048ca1: 89 e5 mov %esp,%ebp
8048ca3: 83 ec 10 sub $0x10,%esp
8048ca6: 56 push %esi
8048ca7: 53 push %ebx
8048ca8: 8b 5d 08 mov 0x8(%ebp),%ebx
8048cab: 83 fb 01 cmp $0x1,%ebx
8048cae: 7e 20 jle 8048cd0
8048cb0: 83 c4 f4 add $0xfffffff4,%esp
8048cb3: 8d 43 ff lea -0x1(%ebx),%eax
8048cb6: 50 push %eax
8048cb7: e8 e4 ff ff ff call 8048ca0
8048cbc: 89 c6 mov %eax,%esi
8048cbe: 83 c4 f4 add $0xfffffff4,%esp
8048cc1: 8d 43 fe lea -0x2(%ebx),%eax
8048cc4: 50 push %eax
8048cc5: e8 d6 ff ff ff call 8048ca0
8048cca: 01 f0 add %esi,%eax
8048ccc: eb 07 jmp 8048cd5
8048cce: 89 f6 mov %esi,%esi
8048cd0: b8 01 00 00 00 mov $0x1,%eax
8048cd5: 8d 65 e8 lea -0x18(%ebp),%esp
8048cd8: 5b pop %ebx
8048cd9: 5e pop %esi
8048cda: 89 ec mov %ebp,%esp
8048cdc: 5d pop %ebp
8048cdd: c3 ret
8048cde: 89 f6 mov %esi,%esi
func4用来处理输入的数,该函数的功能是求斐波那契数。地址8048cab的语句判断当数小于等于1时,返回1。其它情况的处理是func4(n-1)+func4(n-2)。就是这样,没什么好说的。
那么答案就是:9。
5. phase_5phase_5的代码如下:
08048d2c
8048d2c: 55 push %ebp
8048d2d: 89 e5 mov %esp,%ebp
8048d2f: 83 ec 10 sub $0x10,%esp
8048d32: 56 push %esi
8048d33: 53 push %ebx
8048d34: 8b 5d 08 mov 0x8(%ebp),%ebx
8048d37: 83 c4 f4 add $0xfffffff4,%esp
8048d3a: 53 push %ebx
8048d3b: e8 d8 02 00 00 call 8049018
8048d40: 83 c4 10 add $0x10,%esp
8048d43: 83 f8 06 cmp $0x6,%eax
8048d46: 74 05 je 8048d4d
8048d48:e8 af 07 00 00 call 80494fc
8048d4d:31 d2 xor %edx,%edx
8048d4f:8d 4d f8 lea -0x8(%ebp),%ecx
8048d52:be 20 b2 04 08 mov $0x804b220,%esi
8048d57:8a 04 1a mov (%edx,%ebx,1),%al
8048d5a:24 0f and $0xf,%al
8048d5c:0f be c0 movsbl %al,%eax
8048d5f:8a 04 30 mov (%eax,%esi,1),%al
8048d62:88 04 0a mov %al,(%edx,%ecx,1)
8048d65:42 inc %edx
8048d66:83 fa 05 cmp $0x5,%edx
8048d69:7e ec jle 8048d57
8048d6b:c6 45 fe 00 movb $0x0,-0x2(%ebp)
8048d6f: 83 c4 f8 add $0xfffffff8,%esp
8048d72: 68 0b 98 04 08 push $0x804980b
8048d77: 8d 45 f8 lea -0x8(%ebp),%eax
8048d7a: 50 push %eax
8048d7b: e8 b0 02 00 00 call 8049030
8048d80: 83 c4 10 add $0x10,%esp
8048d83: 85 c0 test %eax,%eax
8048d85: 74 05 je 8048d8c
8048d87:e8 70 07 00 00 call 80494fc
8048d8c: 8d 65 e8 lea -0x18(%ebp),%esp
8048d8f: 5b pop %ebx
8048d90: 5e pop %esi
8048d91: 89 ec mov %ebp,%esp
8048d93: 5d pop %ebp
8048d94: c3 ret
8048d95: 8d 76 00 lea 0x0(%esi),%esi
具体分析:
红色部分:两个炸弹,从第一个炸弹前面调用了函数string_length可以知道,输入数据是一个字符串,且其长度为6。第二个炸弹之前有一个判断字符串是否相等的函数,地址0x8048d6f
的语句是一个字符串常量,通过gdb调试输出其值,为“giants”。那么初步判断输入字符串经过某些处理后和giants进行相等比较,若相等,%eax设置为0,否则,设置为1。看起来和第一道题很相似。那么问题主要在中间的黄色部分呢。
黄色部分:%edx是一个计数器,初始化为0,%ecx是待构造的字符串,%esi是字符串常量,其值如图6所示。%ebx是输入的字符串。
图6
接下来是一个循环,该循环的作用是依次取出%ebx的字符,截取其后四位得到值x,x再符号扩展为32位为y,然后在%esi存储的字符串常量中找到对应位置y的字符。最后的判断只要构造出的字符串为giants,则可拆除炸弹。那么答案也就显而易见了。这道题答案不定。这里只给出其中一个:opekmq
6. phase_608048d98
8048d98: 55 push %ebp
8048d99: 89 e5 mov %esp,%ebp
8048d9b: 83 ec 4c sub $0x4c,%esp
8048d9e: 57 push %edi
8048d9f: 56 push %esi
8048da0: 53 push %ebx
8048da1: 8b 55 08 mov 0x8(%ebp),%edx
8048da4: c7 45 cc 6c b2 04 08 movl $0x804b26c,-0x34(%ebp)
8048dab: 83 c4 f8 add $0xfffffff8,%esp
8048dae: 8d 45 e8 lea -0x18(%ebp),%eax
8048db1: 50 push %eax
8048db2: 52 push %edx
8048db3: e8 20 02 00 00 call 8048fd8
8048db8: 31 ff xor %edi,%edi
8048dba: 83 c4 10 add $0x10,%esp
8048dbd: 8d 76 00 lea 0x0(%esi),%esi
8048dc0:8d 45 e8 lea -0x18(%ebp),%eax
8048dc3:8b 04 b8 mov (%eax,%edi,4),%eax
8048dc6:48 dec %eax
8048dc7:83 f8 05 cmp $0x5,%eax
8048dca:76 05 jbe 8048dd1
8048dcc:e8 2b 07 00 00 call 80494fc
8048dd1:8d 5f 01 lea 0x1(%edi),%ebx
8048dd4:83 fb 05 cmp $0x5,%ebx
8048dd7:7f 23 jg 8048dfc
8048dd9:8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax
8048de0:89 45 c8 mov %eax,-0x38(%ebp)
8048de3:8d 75 e8 lea -0x18(%ebp),%esi
8048de6:8b 55 c8 mov -0x38(%ebp),%edx
8048de9:8b 04 32 mov (%edx,%esi,1),%eax
8048dec:3b 04 9e cmp (%esi,%ebx,4),%eax
8048def:75 05 jne 8048df6
8048df1:e8 06 07 00 00 call 80494fc
8048df6:43 inc %ebx
8048df7:83 fb 05 cmp $0x5,%ebx
8048dfa:7e ea jle 8048de6
8048dfc:47 inc %edi
8048dfd:83 ff 05 cmp $0x5,%edi
8048e00:7e be jle 8048dc0
8048e02: 31 ff xor %edi,%edi
8048e04: 8d 4d e8 lea -0x18(%ebp),%ecx
8048e07: 8d 45 d0 lea -0x30(%ebp),%eax
8048e0a: 89 45 c4 mov %eax,-0x3c(%ebp)
8048e0d: 8d 76 00 lea 0x0(%esi),%esi
8048e10:8b 75 cc mov -0x34(%ebp),%esi
8048e13:bb 01 00 00 00 mov $0x1,%ebx
8048e18:8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax
8048e1f:89 c2 mov %eax,%edx
8048e21:3b 1c 08 cmp (%eax,%ecx,1),%ebx
8048e24:7d 12 jge 8048e38
8048e26:8b 04 0a mov (%edx,%ecx,1),%eax
8048e29:8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048e30:8b 76 08 mov 0x8(%esi),%esi
8048e33:43 inc %ebx
8048e34:39 c3 cmp %eax,%ebx
8048e36:7c f8 jl 8048e30
8048e38:8b 55 c4 mov -0x3c(%ebp),%edx
8048e3b:89 34 ba mov %esi,(%edx,%edi,4)
8048e3e:47 inc %edi
8048e3f:83 ff 05 cmp $0x5,%edi
8048e42:7e cc jle 8048e10
8048e44: 8b 75 d0 mov -0x30(%ebp),%esi
8048e47: 89 75 cc mov %esi,-0x34(%ebp)
8048e4a: bf 01 00 00 00 mov $0x1,%edi
8048e4f: 8d 55 d0 lea -0x30(%ebp),%edx
8048e52:8b 04 ba mov (%edx,%edi,4),%eax
8048e55:89 46 08 mov %eax,0x8(%esi)
8048e58:89 c6 mov %eax,%esi
8048e5a:47 inc %edi
8048e5b:83 ff 05 cmp $0x5,%edi
8048e5e:7e f2 jle 8048e52
8048e60: c7 46 08 00 00 00 00 movl $0x0,0x8(%esi)
8048e67: 8b 75 cc mov -0x34(%ebp),%esi
8048e6a: 31 ff xor %edi,%edi
8048e6c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048e70:8b 56 08 mov 0x8(%esi),%edx
8048e73:8b 06 mov (%esi),%eax
8048e75:3b 02 cmp (%edx),%eax
8048e77:7d 05 jge 8048e7e
8048e79:e8 7e 06 00 00 call 80494fc
8048e7e:8b 76 08 mov 0x8(%esi),%esi
8048e81:47 inc %edi
8048e82:83 ff 04 cmp $0x4,%edi
8048e85:7e e9 jle 8048e70
8048e87: 8d 65 a8 lea -0x58(%ebp),%esp
8048e8a: 5b pop %ebx
8048e8b: 5e pop %esi
8048e8c: 5f pop %edi
8048e8d: 89 ec mov %ebp,%esp
8048e8f: 5d pop %ebp
8048e90: c3 ret
8048e91: 8d 76 00 lea 0x0(%esi),%esi
具体分析:
这段代码需要输入6个数字。这里以2 1 3 4 5 6为测试数据。
红色部分:炸弹的位置,共有3个。
绿色部分:为一个双层循环。输入数据通过单步运行输出各个寄存器的值可知,这个双层循环的作用为:对输入的6个数字判断是否在1~6之间,且互不相等。若其中有大于6或有两个数相等则引爆炸弹。图7为单步调试过程部分截图。
图7
黄色部分:为第二个循环,这个循环有点奇怪,它的作用是为地址%ebp-0x34之后的连续六个地址空间分配六个奇怪的地址。这个地址分别为0x804b260、0x804b26c、0x804b254、0x804b248、0x804b23c、0x8048230。对应图8。观察可知,这些地址大小都相差0xc,而且这个循环结束之后,0x804b26c这个最大的值位于六个连续地址的第二位,其它都是按递减顺序来的,再次分布调试代码可知,这些值得分配是按输入数据的大小来分的,它们的值对应如下表。(看不懂先看后面)
输入的数据 |
分配的地址 |
2 |
0x804b260 |
1 |
0x804b26c |
3 |
0x804b254 |
4 |
0x804b248 |
5 |
0x804b23c |
6 |
0x8048230 |
图8 phase_6 部分调试过程
灰色部分:第三个循环,这个循环的作用如下:
地址 |
对应的值 |
0xbffff1d4 |
0x804b260 |
0x804b274 |
0x804b254 |
0x804b268 |
0x804b26c |
0x804b25c |
0x804b248 |
0x804b250 |
0x804b23c |
0x804b244 |
0x804b230 |
简单来说,就是在第二个循环那些奇怪的值加上0x8之后对应的地址存接下来的值。可以想到类似于链表之类的东西。(看不懂先看后面)
蓝色部分:第四个循环,这个循环看完之后我瞬间明白之前两个循环的作用了。这个循环分别比较了0x804b260和0x804b26c等地址对所存值的大小,如果后者大于等于前者,就引爆炸弹。所以,这里我把所有奇怪地址对应的值都输出了。如下表。
地址 |
值 |
0x804b26c |
253 |
0x804b260 |
725 |
0x804b254 |
301 |
0x804b248 |
997 |
0x804b23c |
212 |
0x804b230 |
432 |
结合前面黄色部分的分析,可以知道这道题其实就是给右表这些值排个序,使他们按降序排列。
那么答案也就显而易见了:4 2 6 3 1 5
7. secrect_phase一开始看main函数的时候就发现了一个隐藏关卡。不过不知道怎么启动。既然前六关做完离ddl还有很多时间,这里就来试一下。
main函数中每个关卡之后都有一行 call 804952c
0804952c
804952c: 55 push %ebp
804952d: 89 e5 mov %esp,%ebp
804952f: 83 ec 64 sub $0x64,%esp
8049532: 53 push %ebx
8049533: 83 3d 80 b4 04 08 06 cmpl $0x6,0x804b480 //这个直接数的值是空
804953a: 75 63 jne 804959f
804953c: 8d 5d b0 lea -0x50(%ebp),%ebx
804953f: 53 push %ebx
8049540: 8d 45 ac lea -0x54(%ebp),%eax
8049543: 50 push %eax
8049544: 68 03 9d 04 08 push $0x8049d03 //“%d %s”,输入一个数和一个字符串
8049549: 68 70 b7 04 08 push $0x804b770 //空值
804954e: e8 0d f3 ff ff call 8048860
8049553: 83 c4 10 add $0x10,%esp
8049556: 83 f8 02 cmp $0x2,%eax //输入的数据的个数(一个字符串算一个)
8049559: 75 37 jne 8049592
804955b: 83 c4 f8 add $0xfffffff8,%esp
804955e: 68 09 9d 04 08 push $0x8049d09 //“austinpowers”
8049563: 53 push %ebx
8049564: e8 c7 fa ff ff call 8049030
8049569: 83 c4 10 add $0x10,%esp
804956c: 85 c0 test %eax,%eax
804956e: 75 22 jne 8049592
8049570: 83 c4 f4 add $0xfffffff4,%esp
8049573: 68 20 9d 04 08 push $0x8049d20 //“Curses, you've found the secret phase!\n”
8049578: e8 93 f2 ff ff call 8048810
804957d: 83 c4 f4 add $0xfffffff4,%esp
8049580: 68 60 9d 04 08 push $0x8049d60 //“But finding it and solving it are quite different...\n”
8049585: e8 86 f2 ff ff call 8048810
804958a: 83 c4 20 add $0x20,%esp
804958d:e8 56 f9 ff ff call 8048ee8
8049592: 83 c4 f4 add $0xfffffff4,%esp
8049595: 68 a0 9d 04 08 push $0x8049da0 //“Congratulations! You've defused the bomb!\n”
804959a: e8 71 f2 ff ff call 8048810
804959f: 8b 5d 98 mov -0x68(%ebp),%ebx
80495a2: 89 ec mov %ebp,%esp
80495a4: 5d pop %ebp
80495a5: c3 ret
80495a6: 90 nop
80495a7: 90 nop
80495a8: 90 nop
80495a9: 90 nop
80495aa: 90 nop
80495ab: 90 nop
80495ac: 90 nop
80495ad: 90 nop
80495ae: 90 nop
80495af: 90 nop
红色部分为隐藏关卡位置。
具体分析:通过阅读代码可以发现这个函数中有很多的直接数出现,经过前面的几个关卡的经验,这里直接调试输出这些直接数的值。如图9。
图 9
将图中内容对应着代码一起看。就可以读懂这段代码了。(如代码中的注释,读懂后写的),根据注释可以知道,这段代码的引发需要两个数据,一个int和一个string。
看一下前面关卡的输入就可以知道,只有第四关的输入小于2个,其它关卡的输入都大于2。那么在第四关输入9的时候顺便输入“austinpowers”肯定可以引发隐藏关卡。如图10。
图10 与想象中不同,以为第4关就可以引发隐藏关卡
接下来继续看secret_phase的代码(输入第一个测试数据“1234”,引爆第一颗炸弹后再输入第二个测试数据“900”)
08048ee8
8048ee8: 55 push %ebp
8048ee9: 89 e5 mov %esp,%ebp
8048eeb: 83 ec 14 sub $0x14,%esp //(%esp-$0x14)存储的是字符串“But finding it and solving it are quite different...\n”的首地址
8048eee: 53 push %ebx //经过调试,ebx寄存器存储的是“austinpowers”的首地址。
8048eef: e8 08 03 00 00 call 80491fc
8048ef4: 6a 00 push $0x0
8048ef6: 6a 0a push $0xa
8048ef8: 6a 00 push $0x0
8048efa: 50 push %eax //eax寄存器存储的是“1234”的首地址
8048efb: e8 f0 f8 ff ff call 80487f0 <__strtol_internal@plt> //这个函数的作用不太明了,暂时看下面
8048f00: 83 c4 10 add $0x10,%esp
8048f03: 89 c3 mov %eax,%ebx //把%eax的值移到%ebx,此时%ebx=”1234”
8048f05: 8d 43 ff lea -0x1(%ebx),%eax //此时%eax的值为”1233”
8048f08: 3d e8 03 00 00 cmp $0x3e8,%eax //把%eax的值和1000比较
8048f0d: 76 05 jbe 8048f14
8048f0f:e8 e8 05 00 00 call 80494fc
8048f14: 83 c4 f8 add $0xfffffff8,%esp
8048f17: 53 push %ebx //%ebx存储的是测试数据“900”
8048f18: 68 20 b3 04 08 push $0x804b320
8048f1d: e8 72 ff ff ff call 8048e94
8048f22: 83 c4 10 add $0x10,%esp
8048f25: 83 f8 07 cmp $0x7,%eax //%eax存的是fun7的返回值,只要这个值和0x7想等,就可以跳过第二个炸弹
8048f28: 74 05 je 8048f2f
8048f2a:e8 cd 05 00 00 call 80494fc
8048f2f: 83 c4 f4 add $0xfffffff4,%esp
8048f32: 68 20 98 04 08 push $0x8049820
8048f37: e8 d4 f8 ff ff call 8048810
8048f3c: e8 eb 05 00 00 call 804952c
8048f41: 8b 5d e8 mov -0x18(%ebp),%ebx
8048f44: 89 ec mov %ebp,%esp
8048f46: 5d pop %ebp
8048f47: c3 ret
8048f48: 90 nop
8048f49: 90 nop
8048f4a: 90 nop
8048f4b: 90 nop
8048f4c: 90 nop
8048f4d: 90 nop
8048f4e: 90 nop
8048f4f: 90 nop
具体分析:
红色部分:炸弹位置,第一个炸弹的引爆条件是输入的字符串大于1001。第二个炸弹的引爆条件是fun7返回值和0x7不相等。那么这个隐藏关卡的关键就只剩下fun7的作用了。
下面是fun7函数的代码
08048e94
8048e94: 55 push %ebp
8048e95: 89 e5 mov %esp,%ebp
8048e97: 83 ec 08 sub $0x8,%esp
8048e9a: 8b 55 08 mov 0x8(%ebp),%edx //存的是36的地址“0x804b320”,这个地址在相同的计算机中是不会变的。
8048e9d: 8b 45 0c mov 0xc(%ebp),%eax //%eax存的是900
8048ea0: 85 d2 test %edx,%edx //测试edx的值是否为0,为0的时候修改标志位ZF=1,否则ZF=0。
8048ea2: 75 0c jne 8048eb0
8048ea4: b8 ff ff ff ff mov $0xffffffff,%eax //把eax赋值为-1
8048ea9: eb 37 jmp 8048ee2
8048eab: 90 nop
8048eac: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048eb0: 3b 02 cmp (%edx),%eax //比较%edx存储的地址里面的值和%eax的值
8048eb2: 7d 11 jge 8048ec5
8048eb4: 83 c4 f8 add $0xfffffff8,%esp
8048eb7: 50 push %eax
8048eb8: 8b 42 04 mov 0x4(%edx),%eax //%edx存储的值加4后得到一个地址,取这个地址里面的值x,把x赋给%eax
8048ebb: 50 push %eax
8048ebc: e8 d3 ff ff ff call 8048e94
8048ec1: 01 c0 add %eax,%eax //%eax的值乘2
8048ec3: eb 1d jmp 8048ee2
8048ec5: 3b 02 cmp (%edx),%eax //比较M(%edx)的值和%eax的值的大小
8048ec7: 74 17 je 8048ee0
8048ec9: 83 c4 f8 add $0xfffffff8,%esp
8048ecc: 50 push %eax
8048ecd: 8b 42 08 mov 0x8(%edx),%eax //%edx存储的值加8后得到一个地址,取这个地址里面的值x,把x赋给%eax
8048ed0: 50 push %eax
8048ed1: e8 be ff ff ff call 8048e94
8048ed6: 01 c0 add %eax,%eax //%eax的值乘2
8048ed8: 40 inc %eax //%eax的值加1
8048ed9: eb 07 jmp 8048ee2
8048edb: 90 nop
8048edc: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048ee0: 31 c0 xor %eax,%eax //将%eax置为0
8048ee2: 89 ec mov %ebp,%esp
8048ee4: 5d pop %ebp
8048ee5: c3 ret
8048ee6: 89 f6 mov %esi,%esi
具体分析:这个函数是一个递归函数,通过对代码的阅读,可以发现这个函数的关键点是地址“0x804b320”的值,以及它加n*0x4和n*0x8的值。下表表示的就是这些地址和值的对应关系。
地址%edx |
%edx为地址里面的值 |
0x4(%edx) |
0x8(%edx) |
0x804b320 |
36 |
0x804b314 |
0x804b308 |
|
|
|
|
0x804b314 |
8 |
0x804b214 |
0x804b2fc |
0x804b308 |
50 |
0x804b2f0 |
0x804b2d8 |
|
|
|
|
0x804b214 |
0 |
0 |
0 |
0x804b2fc |
22 |
0x0804b290 |
0x804b2a8 |
0x804b2f0 |
45 |
0x804b2cc |
0x804b284 |
0x804b2d8 |
107 |
0x804b2b4 |
0x804b278 |
|
|
|
|
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0x804b290 |
20 |
0 |
0 |
0x804b2a8 |
35 |
0 |
0 |
0x804b2cc |
40 |
0 |
0 |
0x804b284 |
47 |
0 |
0 |
0x804b2b4 |
99 |
0 |
0 |
0x804b278 |
1001 |
0 |
0 |
通过写这个表的过程,可以发现这其实是一棵二叉树。下面以二叉树的形式来表示这些数据。
这颗二叉树我只画出不是0的部分。
结合代码和注释可以得出这个函数的作用:
设输入的数为x,x与36比较,若小于36,则继续与36的左子树比较;若大于36,则与36的右子树比较;若等于36,则%eax的值置为0。若跳到某一个子树为0时,把%eax的值置为-1。当函数返回时,若是从右子树返回,则%eax=2*%eax+1,若从左子树返回,则%eax=2*%eax。
那么为了让%eax返回0x7,那么输入的数x需要满足的首要条件是:x∈{36,8,50,22,45,107,20,35,40,47,99,1001}。因为如果x等于其它数的话,那么fun7返回的值一定为负数。所以,只需简单代入这些值计算一下就可以知道答案了。
答案为1001。
继续查看代码发现还有一个奇怪的关卡invalid_phase,但没找到程序入口,而且根据这个关卡名称也可以判断这是个没用的关卡。
最后是一张测试截图
总结:这次实验花的时间挺长的(接近一周的时间),主要考察GDB调试和汇编代码阅读能力,在这两方面我都需要加强。