目录
实验内容
给定一个二进制文件bomb,需要输入特定的几段字符或者数字才可通过,需要利用调试工具和反汇编找出这些特定的输入,各个阶段信息如下:
Each bomb phase tests a different aspect of machine language programs:
Phase 1: string comparison
Phase 2: loops
Phase 3: conditionals/switches
Phase 4: recursive calls and the stack discipline
Phase 5: pointers
Phase 6: linked lists/pointers/structs
Phases get progressively harder. There is also a "secret phase" that
only appears if students append a certain string to the solution to
Phase 4.
注:DataLab和BombLab写的是旧版。
反汇编
objdump -d bomb > bomb.txt
利用objdump -d可以将指定可执行文件反汇编,并可以反汇编结果输出到指定文件中。
进入bomb.txt后可以找到main函数,其中包含了phase_1 ~ phase_6共6个阶段,这些phase即为需要拆除的炸弹。
phase_1
main函数中关于phase_1的代码如下,在进入phase_1前的read_line函数,用于读取一串字符。
8048a52: e8 a5 07 00 00 call 80491fc <read_line>
8048a57: 83 c4 f4 add $0xfffffff4,%esp
8048a5a: 50 push %eax
8048a5b: e8 c0 00 00 00 call 8048b20 <phase_1>
phase_1中调用了strings_not_equal函数,同时根据该函数的返回值%eax判断是否进入explode_bomb函数引爆炸弹。
08048b20 <phase_1>:
......
8048b23: 83 ec 08 sub $0x8,%esp
8048b26: 8b 45 08 mov 0x8(%ebp),%eax
8048b29: 83 c4 f8 add $0xfffffff8,%esp
8048b2c: 68 c0 97 04 08 push $0x80497c0
8048b31: 50 push %eax
8048b32: e8 f9 04 00 00 call 8049030 <strings_not_equal>
8048b37: 83 c4 10 add $0x10,%esp
8048b3a: 85 c0 test %eax,%eax
8048b3c: 74 05 je 8048b43 <phase_1+0x23>
8048b3e: e8 b9 09 00 00 call 80494fc <explode_bomb>
......
string_not_equal函数中:
08049030 <strings_not_equal>:
......
8049039: 8b 75 08 mov 0x8(%ebp),%esi
804903c: 8b 7d 0c mov 0xc(%ebp),%edi
804903f: 83 c4 f4 add $0xfffffff4,%esp
8049042: 56 push %esi
//字符串1的长度存储在%ebx
8049043: e8 d0 ff ff ff call 8049018 <string_length>
8049048: 89 c3 mov %eax,%ebx
804904a: 83 c4 f4 add $0xfffffff4,%esp
804904d: 57 push %edi
//返回的字符串2的长度存于%eax,与%ebx进行比较
804904e: e8 c5 ff ff ff call 8049018 <string_length>
8049053: 39 c3 cmp %eax,%ebx
8049055: 74 09 je 8049060 <strings_not_equal+0x30>
//不匹配,返回1并退出函数
8049057: b8 01 00 00 00 mov $0x1,%eax
804905c: eb 21 jmp 804907f <strings_not_equal+0x4f>
//判断字符串是否为空
804905e: 89 f6 mov %esi,%esi
8049060: 89 f2 mov %esi,%edx
8049062: 89 f9 mov %edi,%ecx
8049064: 80 3a 00 cmpb $0x0,(%edx)
8049067: 74 14 je 804907d <strings_not_equal+0x4d>
//逐个判断字符是否相等,两字符分别为(%edx)和(%ecx)
8049069: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8049070: 8a 02 mov (%edx),%al
8049072: 3a 01 cmp (%ecx),%al
8049074: 75 e1 jne 8049057 <strings_not_equal+0x27>
//因为字符只占一个字节,故地址自增1
8049076: 42 inc %edx
8049077: 41 inc %ecx
8049078: 80 3a 00 cmpb $0x0,(%edx)
804907b: 75 f3 jne 8049070 <strings_not_equal+0x40>
//匹配,返回0
804907d: 31 c0 xor %eax,%eax
804907f: 8d 65 e8 lea -0x18(%ebp),%esp
......
由上可知,在比较长度时,两个字符串的长度分别在%ebx和%eax。在逐个比较字符时两字符串的首指针分别在%edx和%ecx,为了知道字符具体是多少,下面需要进行gdb断点调试:
输入的字符串为abcde,%ebx对应的长度为5,而%eax的长度为29,即为目标串的长度,下面需要重新调试,输入长度29的串才能进入逐个判断字符阶段:
打印%ecx和%edx处地址的字符,容易发现%ecx为目标串的指针,选择打印%ecx地址下的29个字符即可得到phase_1的结果为:
Public speaking is very easy.
phase_2
08048b48 <phase_2>:
......
//读取六个数字
8048b5b: e8 78 04 00 00 call 8048fd8 <read_six_numbers>
8048b60: 83 c4 10 add $0x10,%esp
//比较第一个数字
8048b63: 83 7d e8 01 cmpl $0x1,-0x18(%ebp)
8048b67: 74 05 je 8048b6e <phase_2+0x26>
8048b69: e8 8e 09 00 00 call 80494fc <explode_bomb>
//%eax = %ebx + 1;
//%eax = [i] * %eax;
//if (%eax == [i + 1]) ...
8048b6e: bb 01 00 00 00 mov $0x1,%ebx
8048b73: 8d 75 e8 lea -0x18(%ebp),%esi
8048b76: 8d 43 01 lea 0x1(%ebx),%eax
8048b79: 0f af 44 9e fc imul -0x4(%esi,%ebx,4),%eax
8048b7e: 39 04 9e cmp %eax,(%esi,%ebx,4)
8048b81: 74 05 je 8048b88 <phase_2+0x40>
8048b83: e8 74 09 00 00 call 80494fc <explode_bomb>
//%ebx++;
8048b88: 43 inc %ebx
//继续循环
8048b89: 83 fb 05 cmp $0x5,%ebx
8048b8c: 7e e8 jle 8048b76 <phase_2+0x2e>
......
直接根据代码逻辑可知,需要六个数字,且[1] = 1,[i] = [i - 1] * i,易有答案为
1 2 6 24 120 720
phase_3
main函数中phase_3前为read_line读取的字符串,输入的字符串首地址入栈。
08048b98 <phase_3>:
......
//为sscanf函数作参数准备(指针做参数)
8048b9f: 8b 55 08 mov 0x8(%ebp),%edx
8048ba2: 83 c4 f4 add $0xfffffff4,%esp
8048ba5: 8d 45 fc lea -0x4(%ebp),%eax //&n2
8048ba8: 50 push %eax
8048ba9: 8d 45 fb lea -0x5(%ebp),%eax //&c
8048bac: 50 push %eax
8048bad: 8d 45 f4 lea -0xc(%ebp),%eax //&n1
8048bb0: 50 push %eax
8048bb1: 68 de 97 04 08 push $0x80497de //"%d, %c, %d"
8048bb6: 52 push %edx //%edx即为我们输入的字符串的首地址
//sscanf(%edx, "%d, %c, %d", &n1, &c, &n2)
8048bb7: e8 a4 fc ff ff call 8048860 <sscanf@plt>
//sscanf返回了读取的数据个数,返回值是否大于两个
8048bbc: 83 c4 20 add $0x20,%esp
8048bbf: 83 f8 02 cmp $0x2,%eax
8048bc2: 7f 05 jg 8048bc9 <phase_3+0x31>
8048bc4: e8 33 09 00 00 call 80494fc <explode_bomb>
//n1大于7,则爆炸
8048bc9: 83 7d f4 07 cmpl $0x7,-0xc(%ebp)
8048bcd: 0f 87 b5 00 00 00 ja 8048c88 <phase_3+0xf0>
8048bd3: 8b 45 f4 mov -0xc(%ebp),%eax
//*(0x80497e8) = 0x8048be0,根据n1的值进行跳转
8048bd6: ff 24 85 e8 97 04 08 jmp *0x80497e8(,%eax,4)
//n1 = 0, c = 0x71, n2 = 0x309
8048bdd: 8d 76 00 lea 0x0(%esi),%esi
8048be0: b3 71 mov $0x71,%bl
//n2需要等于0x309
8048be2: 81 7d fc 09 03 00 00 cmpl $0x309,-0x4(%ebp)
8048be9: 0f 84 a0 00 00 00 je 8048c8f <phase_3+0xf7>
8048bef: e8 08 09 00 00 call 80494fc <explode_bomb>
8048bf4: e9 96 00 00 00 jmp 8048c8f <phase_3+0xf7>
......
8048c88: b3 78 mov $0x78,%bl
8048c8a: e8 6d 08 00 00 call 80494fc <explode_bomb>
//c需要等于%bl
8048c8f: 3a 5d fb cmp -0x5(%ebp),%bl
8048c92: 74 05 je 8048c99 <phase_3+0x101>
8048c94: e8 63 08 00 00 call 80494fc <explode_bomb>
8048c99: 8b 5d e8 mov -0x18(%ebp),%ebx
......
phase_3中主要是用sscanf函数从输入的字符串中读取数据,从参数上看可以知道共有三个数据,而读取格式为$0x80497de,转换为字符串即为"%d, %c, %d"。随后根据n1的值进入switch语句,以n1 == 0为例:首先将%bl置为0x71,然后判断n2 == 0x309。接着进入另一段代码,判断c == %bl。故可以得到一个合法的输入为:
0 q 777
类似的,n1 = 1, 2, ...时也可以同样得到若干组数据。
phase_4
phase_4同样利用read_line读取了字符串:
08048ce0 <phase_4>:
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 //%edx为input的首地址
8048ce9: 83 c4 fc add $0xfffffffc,%esp
8048cec: 8d 45 fc lea -0x4(%ebp),%eax //&n
8048cef: 50 push %eax
8048cf0: 68 08 98 04 08 push $0x8049808 //"%d"
8048cf5: 52 push %edx
//sscanf(%edx, "%d", &n)
8048cf6: e8 65 fb ff ff call 8048860 <sscanf@plt>
8048cfb: 83 c4 10 add $0x10,%esp
8048cfe: 83 f8 01 cmp $0x1,%eax //返回值为1
8048d01: 75 06 jne 8048d09 <phase_4+0x29>
8048d03: 83 7d fc 00 cmpl $0x0,-0x4(%ebp) //n > 0
8048d07: 7f 05 jg 8048d0e <phase_4+0x2e>
8048d09: e8 ee 07 00 00 call 80494fc <explode_bomb>
//调用了func4(n)
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 <func4>
8048d1a: 83 c4 10 add $0x10,%esp
//fuc4(n)的返回值需要为0x37
8048d1d: 83 f8 37 cmp $0x37,%eax
8048d20: 74 05 je 8048d27 <phase_4+0x47>
8048d22: e8 d5 07 00 00 call 80494fc <explode_bomb>
8048d27: 89 ec mov %ebp,%esp
8048d29: 5d pop %ebp
8048d2a: c3 ret
8048d2b: 90 nop
可以知道输入字符串被读取为数字n,n需要满足n > 0且func4(&n) == 0x37,进入func4查看:
08048ca0 <func4>:
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 //%ebx = n
//n小于等于1退出函数并返回1
8048cab: 83 fb 01 cmp $0x1,%ebx
8048cae: 7e 20 jle 8048cd0 <func4+0x30>
8048cb0: 83 c4 f4 add $0xfffffff4,%esp
8048cb3: 8d 43 ff lea -0x1(%ebx),%eax //%eax == n - 1
8048cb6: 50 push %eax
8048cb7: e8 e4 ff ff ff call 8048ca0 <func4> //调用了func4(n - 1)
8048cbc: 89 c6 mov %eax,%esi //func(n - 1)的返回值保存于%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 <func4> //调用了func4(n - 2)
//本函数的返回值设为func(n - 1) + func4(n - 2)
8048cca: 01 f0 add %esi,%eax
8048ccc: eb 07 jmp 8048cd5 <func4+0x35>
8048cce: 89 f6 mov %esi,%esi
8048cd0: b8 01 00 00 00 mov $0x1,%eax //返回1
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的函数可以知道,func4(n) = func4(n - 1) + func4(n - 2),且func(1) = func(0) = 1,即斐波那契数列,故容易知道func(9) == 55 == 0x37,答案为:
9
phase_5
phase_5同样输入一字符串:现有一长度为16的字符串S。依次取input的每个字符的低4位,根据低4位的值,取S中对应的字符,构造一个新串,要求新串和目标串相等。
08048d2c <phase_5>:
......
8048d34: 8b 5d 08 mov 0x8(%ebp),%ebx //%ebx = input首地址
8048d37: 83 c4 f4 add $0xfffffff4,%esp
8048d3a: 53 push %ebx
8048d3b: e8 d8 02 00 00 call 8049018 <string_length>
8048d40: 83 c4 10 add $0x10,%esp
8048d43: 83 f8 06 cmp $0x6,%eax //长度为6
8048d46: 74 05 je 8048d4d <phase_5+0x21>
8048d48: e8 af 07 00 00 call 80494fc <explode_bomb>
8048d4d: 31 d2 xor %edx,%edx //%edx初始化为0
8048d4f: 8d 4d f8 lea -0x8(%ebp),%ecx //地址存于%ecx
8048d52: be 20 b2 04 08 mov $0x804b220,%esi //地址0x804b220存于%esi
//%al = input字符串的第%edx个字符
8048d57: 8a 04 1a mov (%edx,%ebx,1),%al
//只取字符的低4位,即半个字节
8048d5a: 24 0f and $0xf,%al
//记%al = c,取地址0x804b220偏移c后的字符
//数组首地址为0x804b220,大小为16
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 //循环共执行6次
8048d69: 7e ec jle 8048d57 <phase_5+0x2b>
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
//phase_1用到的比较字符串的函数
8048d7b: e8 b0 02 00 00 call 8049030 <strings_not_equal>
8048d80: 83 c4 10 add $0x10,%esp
8048d83: 85 c0 test %eax,%eax
8048d85: 74 05 je 8048d8c <phase_5+0x60>
8048d87: e8 70 07 00 00 call 80494fc <explode_bomb>
......
从代码中容易知道,input的长度需要为6,S的首地址为0x804b220,target的首地址为0x804980b,利用gdb打印出各个值:
"giants"分别出现在S的第15、0、5、11、13、1位置,故input的6个字符的低4位,即半个字节,分别为f、0、5、b、d、1,容易构造出一个字符串:
oPekma
phase_6
phase_6的输入为一个字符串,由字符串构造6个数字,主要分为两个部分:
第一个部分是判断6个数字是否都处于[1, 6]范围内,且没有重复数字,涉及一个双层循环。
8048da1: 8b 55 08 mov 0x8(%ebp),%edx //%edx = *input
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 //%ebp - 0x18开始存6个数字
8048db1: 50 push %eax
8048db2: 52 push %edx
8048db3: e8 20 02 00 00 call 8048fd8 <read_six_numbers>
//下面是一个双层循环
//判断是否所有无符号数字都小于等于6,且各不相等
8048db8: 31 ff xor %edi,%edi //%edi = i = 0
8048dba: 83 c4 10 add $0x10,%esp
8048dbd: 8d 76 00 lea 0x0(%esi),%esi
8048dc0: 8d 45 e8 lea -0x18(%ebp),%eax //%eax = [0]的地址
8048dc3: 8b 04 b8 mov (%eax,%edi,4),%eax //%eax = [i]的值
8048dc6: 48 dec %eax //[i]若为0,减1后变无符号数也会爆炸
8048dc7: 83 f8 05 cmp $0x5,%eax
8048dca: 76 05 jbe 8048dd1 <phase_6+0x39> //0 < [i] <= 6
8048dcc: e8 2b 07 00 00 call 80494fc <explode_bomb>
8048dd1: 8d 5f 01 lea 0x1(%edi),%ebx //%ebx = j = i + 1
8048dd4: 83 fb 05 cmp $0x5,%ebx
8048dd7: 7f 23 jg 8048dfc <phase_6+0x64> //j > 5则跳转
8048dd9: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax //i * 4
8048de0: 89 45 c8 mov %eax,-0x38(%ebp) //-0x38(%ebp) = i * 4
8048de3: 8d 75 e8 lea -0x18(%ebp),%esi //%esi置为6个数字首地址
8048de6: 8b 55 c8 mov -0x38(%ebp),%edx //%edx = i * 4
8048de9: 8b 04 32 mov (%edx,%esi,1),%eax //%eax = [i]
8048dec: 3b 04 9e cmp (%esi,%ebx,4),%eax //[i + j] =? [i]
8048def: 75 05 jne 8048df6 <phase_6+0x5e> //不相等则跳转
8048df1: e8 06 07 00 00 call 80494fc <explode_bomb>
8048df6: 43 inc %ebx //++j
8048df7: 83 fb 05 cmp $0x5,%ebx
8048dfa: 7e ea jle 8048de6 <phase_6+0x4e> //内层循环
8048dfc: 47 inc %edi //++i
8048dfd: 83 ff 05 cmp $0x5,%edi
8048e00: 7e be jle 8048dc0 <phase_6+0x28> //外层循环
第二部分涉及到节点(结构体)的构造,程序中从0x804b26c开始定义了一个节点数组。通过汇编码可以知道:每个结构体的大小是12字节,其中高地址存储指针,剩余部分存储一个64位数字,再通过gdb打印可以看到每个节点都预先存储了数据:
//开辟一片内存存放节点,从0x804b26c开始
//每个结构体12个字节,高4字节放地址
8048e02: 31 ff xor %edi,%edi //%edi = i = 0
8048e04: 8d 4d e8 lea -0x18(%ebp),%ecx //6个数字的位置
8048e07: 8d 45 d0 lea -0x30(%ebp),%eax
8048e0a: 89 45 c4 mov %eax,-0x3c(%ebp)
8048e0d: 8d 76 00 lea 0x0(%esi),%esi
//%esi = -0x34(%ebp) = 0x804b26c
8048e10: 8b 75 cc mov -0x34(%ebp),%esi
8048e13: bb 01 00 00 00 mov $0x1,%ebx //%ebx = j = 1
8048e18: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax //%eax = 4i
8048e1f: 89 c2 mov %eax,%edx //%edx = 4i
8048e21: 3b 1c 08 cmp (%eax,%ecx,1),%ebx //[i] 与 j比较
8048e24: 7d 12 jge 8048e38 <phase_6+0xa0> //[i] <= j则跳转
8048e26: 8b 04 0a mov (%edx,%ecx,1),%eax //%eax = [i]
8048e29: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048e30: 8b 76 08 mov 0x8(%esi),%esi //高地址的是指针,剩下8字节是数据
8048e33: 43 inc %ebx //++j
8048e34: 39 c3 cmp %eax,%ebx //直到[i] == j
8048e36: 7c f8 jl 8048e30 <phase_6+0x98>
//若[i] == j
//那么就在%edx + 4 * i的位置存放第j个节点的地址
8048e38: 8b 55 c4 mov -0x3c(%ebp),%edx //%edx = %ebp - 0x30
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 <phase_6+0x78> //继续读取其他数字
从(%ebp - 0x30)开始有一个指针数组C,如果[i] == j,那么就把节点数组中第j个节点的地址存在C[i]。例如如果input = {2, 6, 4, 1, 3, 2},设节点数组首地址为m,那么C = {*m[1], *m[5], *m[3], *m[0], *m[2], *m[1]}。
第三部分的作用是,按照C的顺序将节点改造为新的链表,并根据链表访问节点,访问时的数据单调递减:
//构造链表的循环
8048e44: 8b 75 d0 mov -0x30(%ebp),%esi //[0]对应的节点j所在的位置
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 //[1]对应的节点k的位置
8048e55: 89 46 08 mov %eax,0x8(%esi) //j.next = k
8048e58: 89 c6 mov %eax,%esi
8048e5a: 47 inc %edi
8048e5b: 83 ff 05 cmp $0x5,%edi
8048e5e: 7e f2 jle 8048e52 <phase_6+0xba> //循环下去
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 <phase_6+0xe6>
8048e79: e8 7e 06 00 00 call 80494fc <explode_bomb>
8048e7e: 8b 76 08 mov 0x8(%esi),%esi
8048e81: 47 inc %edi
8048e82: 83 ff 04 cmp $0x4,%edi
8048e85: 7e e9 jle 8048e70 <phase_6+0xd8>
8048e87: 8d 65 a8 lea -0x58(%ebp),%esp
根据gdb打印的每个节点的数据分别为253、725、301、997、212、432,故输入的6个数字为:
4 2 6 3 1 5
phase_6涉及到三个数组,n保存输入的数据,m为节点数组,C为节点地址数组。
secret_phase
在phase_defused中有如下一段代码,结合gdb调试可以知道,0x804b770这个地址将分为串s和数字n,s == "austinpowers"才可进入secret_phase。
0804952c <phase_defused>:
......
8049533: 83 3d 80 b4 04 08 06 cmpl $0x6,0x804b480 //这里记录着已经解决的阶段数
804953a: 75 63 jne 804959f <phase_defused+0x73>
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 //sscanf参数1
8049549: 68 70 b7 04 08 push $0x804b770 //sscanf参数2
804954e: e8 0d f3 ff ff call 8048860 <sscanf@plt>
8049553: 83 c4 10 add $0x10,%esp
8049556: 83 f8 02 cmp $0x2,%eax
8049559: 75 37 jne 8049592 <phase_defused+0x66>
804955b: 83 c4 f8 add $0xfffffff8,%esp
804955e: 68 09 9d 04 08 push $0x8049d09 //目标串
8049563: 53 push %ebx //sscanf读入的串
8049564: e8 c7 fa ff ff call 8049030 <strings_not_equal>
8049569: 83 c4 10 add $0x10,%esp
804956c: 85 c0 test %eax,%eax
804956e: 75 22 jne 8049592 <phase_defused+0x66>
8049570: 83 c4 f4 add $0xfffffff4,%esp
8049573: 68 20 9d 04 08 push $0x8049d20
8049578: e8 93 f2 ff ff call 8048810 <printf@plt>
804957d: 83 c4 f4 add $0xfffffff4,%esp
8049580: 68 60 9d 04 08 push $0x8049d60
8049585: e8 86 f2 ff ff call 8048810 <printf@plt>
804958a: 83 c4 20 add $0x20,%esp
804958d: e8 56 f9 ff ff call 8048ee8 <secret_phase>
在README中已有提示,在phase_4后添加这一串即可。或者也可以在read_line中得到验证,0x804b770就是phase_4的input的首地址。
下面是secret_phase,其中strtol_internal(input, NULL, 10, 0) == strtol(input, NULL, 10),即把input转换为以10为基的数并返回n,随后调用了fun7(m, n),m为地址0x804b320。
08048ee8 <secret_phase>:
8048ee8: 55 push %ebp
8048ee9: 89 e5 mov %esp,%ebp
8048eeb: 83 ec 14 sub $0x14,%esp
8048eee: 53 push %ebx
8048eef: e8 08 03 00 00 call 80491fc <read_line>
8048ef4: 6a 00 push $0x0
8048ef6: 6a 0a push $0xa
8048ef8: 6a 00 push $0x0
8048efa: 50 push %eax
//strtol_internal(input, NULL, 10, 0)
8048efb: e8 f0 f8 ff ff call 80487f0 <__strtol_internal@plt>
8048f00: 83 c4 10 add $0x10,%esp
8048f03: 89 c3 mov %eax,%ebx
8048f05: 8d 43 ff lea -0x1(%ebx),%eax
8048f08: 3d e8 03 00 00 cmp $0x3e8,%eax //n <= 1001
8048f0d: 76 05 jbe 8048f14 <secret_phase+0x2c>
8048f0f: e8 e8 05 00 00 call 80494fc <explode_bomb>
8048f14: 83 c4 f8 add $0xfffffff8,%esp
8048f17: 53 push %ebx //n
8048f18: 68 20 b3 04 08 push $0x804b320 //0x804b320
8048f1d: e8 72 ff ff ff call 8048e94 <fun7>
//要求返回7
8048f22: 83 c4 10 add $0x10,%esp
8048f25: 83 f8 07 cmp $0x7,%eax
8048f28: 74 05 je 8048f2f <secret_phase+0x47>
8048f2a: e8 cd 05 00 00 call 80494fc <explode_bomb>
8048f2f: 83 c4 f4 add $0xfffffff4,%esp
8048f32: 68 20 98 04 08 push $0x8049820
8048f37: e8 d4 f8 ff ff call 8048810 <printf@plt>
8048f3c: e8 eb 05 00 00 call 804952c <phase_defused>
fun7(node, n)是一个递归函数,可以看到传入的node地址指向一个结构体{data, left, right}包括数据和左右指针,根据data与n的大小关系进入left和right进行查找。
- 如果当前节点为NULL,返回-1
- 如果当前节点找到,返回0
- 如果在left中找到,返回fun7(left, n) * 2
- 如果在right中找到,返回fun7(right, n) * 2 + 1
//fun7(node, n)
08048e94 <fun7>:
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 //node.data
8048e9d: 8b 45 0c mov 0xc(%ebp),%eax //n
8048ea0: 85 d2 test %edx,%edx
8048ea2: 75 0c jne 8048eb0 <fun7+0x1c>
8048ea4: b8 ff ff ff ff mov $0xffffffff,%eax
8048ea9: eb 37 jmp 8048ee2 <fun7+0x4e> //node == NULL,返回-1
8048eab: 90 nop
8048eac: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048eb0: 3b 02 cmp (%edx),%eax
8048eb2: 7d 11 jge 8048ec5 <fun7+0x31> //n >= node.data
//n < m 递归
8048eb4: 83 c4 f8 add $0xfffffff8,%esp
8048eb7: 50 push %eax
8048eb8: 8b 42 04 mov 0x4(%edx),%eax //新地址为0x4(%edx) = left
8048ebb: 50 push %eax
8048ebc: e8 d3 ff ff ff call 8048e94 <fun7> //fun7(node.left, n)
8048ec1: 01 c0 add %eax,%eax //返回2 * %eax
8048ec3: eb 1d jmp 8048ee2 <fun7+0x4e>
8048ec5: 3b 02 cmp (%edx),%eax // n == m
8048ec7: 74 17 je 8048ee0 <fun7+0x4c> //返回0
//n > m 递归
8048ec9: 83 c4 f8 add $0xfffffff8,%esp
8048ecc: 50 push %eax
8048ecd: 8b 42 08 mov 0x8(%edx),%eax //新地址为0x8(%edx) = right
8048ed0: 50 push %eax
8048ed1: e8 be ff ff ff call 8048e94 <fun7> //fun7(node.right, n)
8048ed6: 01 c0 add %eax,%eax
8048ed8: 40 inc %eax //返回2 * %eax + 1
8048ed9: eb 07 jmp 8048ee2 <fun7+0x4e>
8048edb: 90 nop
8048edc: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048ee0: 31 c0 xor %eax,%eax //置返回值为0
8048ee2: 89 ec mov %ebp,%esp
8048ee4: 5d pop %ebp
8048ee5: c3 ret
8048ee6: 89 f6 mov %esi,%esi
用gdb把二叉树各个节点打印出来,观察输入的n是哪个即可:
十六进制下的树为:
根据返回值的规则:
- 7 = 2 * 3 + 1
- 3 = 2 * 1 + 1
- 1 =0 * 1 + 1
故是最右侧找不到,例如
1001
结果
其中phase_3,phase_5,phase_secret的结果不唯一。
Public speaking is very easy.
1 2 6 24 120 720
0 q 777
9 austinpowers
oPekma
4 2 6 3 1 5
1001