实验二 拆炸弹
准备工作
首先反汇编
objdump -s -d bomb > bomb.txt
phase_1
开启gdb调试,并打断点到phase_1
>> gbd bomb
(gdb) break phase_1
(gdb) run
(gdb) disas
用disas 命令查看此函数汇编代码,对应在bomb.s中这一段:
08048b80 <phase_1>:
8048b80: 55 push %ebp
8048b81: 89 e5 mov %esp,%ebp
8048b83: 83 ec 08 sub $0x8,%esp
8048b86: c7 44 24 04 d8 99 04 movl $0x80499d8,0x4(%esp)
8048b8d: 08
8048b8e: 8b 45 08 mov 0x8(%ebp),%eax
8048b91: 89 04 24 mov %eax,(%esp)
8048b94: e8 7a 05 00 00 call 8049113 <strings_not_equal>
8048b99: 85 c0 test %eax,%eax
8048b9b: 74 05 je 8048ba2 <phase_1+0x22>
8048b9d: e8 38 0b 00 00 call 80496da <explode_bomb>
8048ba2: c9 leave
8048ba3: c3 ret
- 首先压栈ebp栈底指针,返回地址
- 然后将esp栈顶指针赋值给栈底指针,新的栈底
- 将esp栈顶指针向下移动8个字节
- 立即数$0x80499d8赋值给 %esp+4位置的对应的内存
- 将%ebp+8位置存的值赋给%eax,这个值就是input参数
- 将%eax赋值给%esp栈指向的这个位置
- 接下来调用比较string是否相同,则刚才%esp位置和%esp+4位置分别存的是input的字符串和需要比较的字符串地址,作为参数传入这个比较函数
- 当函数返回后,判断%eax这个值是否为0,为0说明就是不同,为1就说明是相同的,到8048ba2离开,ret返回,否则到8048b9d 触发炸弹爆炸。
所以查看$0x80499d8地址处的字符串,答案就是这个字符串
(gdb) x/s 0x80499d8
0x80499d8: "Why make trillions when we could make... billions?"
phase_2
开启gdb调试,并打断点到phase_2,记得输入前面阶段的答案
>> gbd bomb
(gdb) break phase_2
(gdb) run
(gdb) disas
用disas 命令查看此函数汇编代码,对应在bomb.s中这一段:
08048ba4 <phase_2>:
8048ba4: 55 push %ebp
8048ba5: 89 e5 mov %esp,%ebp
8048ba7: 83 ec 28 sub $0x28,%esp
8048baa: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%ebp)
8048bb1: 8d 45 e0 lea -0x20(%ebp),%eax
8048bb4: 89 44 24 04 mov %eax,0x4(%esp)
8048bb8: 8b 45 08 mov 0x8(%ebp),%eax
8048bbb: 89 04 24 mov %eax,(%esp)
8048bbe: e8 bd 04 00 00 call 8049080 <read_six_numbers>
8048bc3: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%ebp)
8048bca: eb 27 jmp 8048bf3 <phase_2+0x4f>
8048bcc: 8b 45 f8 mov -0x8(%ebp),%eax
8048bcf: 8b 54 85 e0 mov -0x20(%ebp,%eax,4),%edx
8048bd3: 8b 45 f8 mov -0x8(%ebp),%eax
8048bd6: 83 c0 03 add $0x3,%eax
8048bd9: 8b 44 85 e0 mov -0x20(%ebp,%eax,4),%eax
8048bdd: 39 c2 cmp %eax,%edx
8048bdf: 74 05 je 8048be6 <phase_2+0x42>
8048be1: e8 f4 0a 00 00 call 80496da <explode_bomb>
8048be6: 8b 45 f8 mov -0x8(%ebp),%eax
8048be9: 8b 44 85 e0 mov -0x20(%ebp,%eax,4),%eax
8048bed: 01 45 fc add %eax,-0x4(%ebp)
8048bf0: ff 45 f8 incl -0x8(%ebp)
8048bf3: 83 7d f8 02 cmpl $0x2,-0x8(%ebp)
8048bf7: 7e d3 jle 8048bcc <phase_2+0x28>
8048bf9: 83 7d fc 00 cmpl $0x0,-0x4(%ebp)
8048bfd: 75 05 jne 8048c04 <phase_2+0x60>
8048bff: e8 d6 0a 00 00 call 80496da <explode_bomb>
8048c04: c9 leave
8048c05: c3 ret
- 首先还是压栈栈底地址%ebp ,返回地址
- 赋值新的栈底
- esp 向下移动0x28=40个字节,即空间分配40个字节
- %ebp-4的位置存入0
- %eax = ebp-32
- 将地址ebp-32存入esp+4的内存位置
- 将内存位置ebp+8位置存的值写入内存中%esp指向的位置
- 调用函数read_six_numbers 读六个number
- 然后将ebp-8位置内存写入0
- 接下来跳转到8048bf3执行,注意这一开时候ebp-8位置内存是0,
- 然后ebp-8位置内存中中内容和0比较,当小于等于0时跳转到8048bcc执行,否则继续
- 然后内存中Mem(%ebp-8)的值赋值给%eax,计算%edx = Mem(4%eax+ebp-32):就是将ebp-32这个位置向上移4*%eax长度的位置中的值,%eax是Mem(%ebp-8)的值
- 同样内存中Mem(%ebp-8)的值赋值给%eax,计算%eax = Mem(4(%eax+3)+ebp-32),注意现在是eax+3的值在做计算:同理就是将ebp-32这个位置向上移4*(%eax+3)长度的位置中的值,%eax是Mem(%ebp-8)的值
- 比较edx与eax中的值,相同则跳过,否则就爆炸
- 跳过后,内存中Mem(%ebp-8)的值赋值给%eax,计算%eax = Mem(4%eax+ebp-32),然后Mem(%ebp-4)+=%eax
- 然后将Mem(%ebp-8)位置自增1
- 接下来又进入判断Mem(%ebp-8)与0x2大小比较,然后跳转执行,可以理解当循环3次后,继续就不跳转
- 不跳转继续执行是比较Mem(%ebp-4)的值是否为0,是0则爆炸,否则通过
由此可以看出Mem(%ebp-8)处存的是控制循环次数的变量i,初始i=0,i<=2。
Mem(%ebp-4)的值存的是初始为0,后面会连续累加每次计算Mem(4%eax+ebp-32)的值,这里eax就是i值。
在循环中,其实通过Mem(4%eax+ebp-32)和Mem(4(%eax+3)+ebp-32)就是比较每隔3个位置的值是否相同。
所以就是读入了6个值,然后六个值中间隔三个是相同的,如 1 2 3 1 2 3.
phase_3
开启gdb调试,并打断点到phase_3,记得输入前面阶段的答案
>> gbd bomb
(gdb) break phase_3
(gdb) run
(gdb) disas
用disas 命令查看此函数汇编代码,对应在bomb.s中这一段:
08048c06 <phase_3>:
8048c06: 55 push %ebp
8048c07: 89 e5 mov %esp,%ebp
8048c09: 83 ec 38 sub $0x38,%esp // 分配56字节空间,esp-56
8048c0c: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%ebp) // 将0存入Mem(%ebp-8)
8048c13: 8d 45 f0 lea -0x10(%ebp),%eax // eax = %ebp-16的地址值
8048c16: 89 44 24 10 mov %eax,0x10(%esp) // 将%ebp-16的地址值存入 Mem(%esp+16)
8048c1a: 8d 45 ef lea -0x11(%ebp),%eax // eax = %ebp-17的地址值
8048c1d: 89 44 24 0c mov %eax,0xc(%esp) // %ebp-17的地址值存入 Mem(%esp+12)
8048c21: 8d 45 f4 lea -0xc(%ebp),%eax // eax = %ebp-12的地址值
8048c24: 89 44 24 08 mov %eax,0x8(%esp) // %ebp-12的地址值 存入 Mem(%esp+8)
8048c28: c7 44 24 04 0b 9a 04 movl $0x8049a0b,0x4(%esp) // 立即数0x8049a0b存入 Mem(%esp+4)
8048c2f: 08
8048c30: 8b 45 08 mov 0x8(%ebp),%eax // eax = Mem(ebp+8),应该是取参数
8048c33: 89 04 24 mov %eax,(%esp) // Mem(ebp+8) -> 存入 Mem(esp)
8048c36: e8 2d fc ff ff call 8048868 <sscanf@plt> // 访问函数sscanf
8048c3b: 89 45 f8 mov %eax,-0x8(%ebp) // 将函数sscanf结果写入Mem(ebp-8)
8048c3e: 83 7d f8 02 cmpl $0x2,-0x8(%ebp) // 将函数sscanf(在Mem(ebp-8))与0x2比较
8048c42: 7f 05 jg 8048c49 <phase_3+0x43> // 大于2则跳过bomb,否则失败
8048c44: e8 91 0a 00 00 call 80496da <explode_bomb>
8048c49: 8b 45 f4 mov -0xc(%ebp),%eax // eax = Mem(ebp-12)
8048c4c: 89 45 dc mov %eax,-0x24(%ebp) // 将Mem(ebp-12) -> 存入 Mem(ebp-36)
8048c4f: 83 7d dc 07 cmpl $0x7,-0x24(%ebp) // 比较Mem(ebp-36)内容与 0x7
8048c53: 0f 87 c0 00 00 00 ja 8048d19 <phase_3+0x113> 大于则跳到 8048d19 去爆炸
8048c59: 8b 55 dc mov -0x24(%ebp),%edx // edx = Mem(ebp-36),即刚才要小于等7的判断值
8048c5c: 8b 04 95 14 9a 04 08 mov 0x8049a14(,%edx,4),%eax // eax = Mem(0x8049a14+edx*4)
8048c63: ff e0 jmp *%eax // 跳转到%eax中存的地方,猜测这里就是在switch选择跳转位置了
8048c65: c6 45 ff 79 movb $0x79,-0x1(%ebp) // Mem(ebp-1) = 0x79
8048c69: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048c6c: 3d 46 03 00 00 cmp $0x346,%eax // 比较 Mem(ebp-16) == 0x346
8048c71: 0f 84 ab 00 00 00 je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,否则bomb,所以需要跳转
8048c77: e8 5e 0a 00 00 call 80496da <explode_bomb>
8048c7c: e9 a1 00 00 00 jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048c81: c6 45 ff 69 movb $0x69,-0x1(%ebp) // Mem(ebp-1) = 0x69
8048c85: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048c88: 3d 6f 03 00 00 cmp $0x36f,%eax // 比较Mem(ebp-16) 与 0x36f
8048c8d: 0f 84 8f 00 00 00 je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048c93: e8 42 0a 00 00 call 80496da <explode_bomb>
8048c98: e9 85 00 00 00 jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048c9d: c6 45 ff 68 movb $0x68,-0x1(%ebp) // Mem(ebp-1) = 0x68
8048ca1: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048ca4: 3d 74 02 00 00 cmp $0x274,%eax // 比较Mem(ebp-16) 与 0x274
8048ca9: 74 77 je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048cab: e8 2a 0a 00 00 call 80496da <explode_bomb> //
8048cb0: eb 70 jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048cb2: c6 45 ff 6e movb $0x6e,-0x1(%ebp) // Mem(ebp-1) = 0x6e
8048cb6: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048cb9: 83 f8 46 cmp $0x46,%eax // 比较Mem(ebp-16) 与 0x46
8048cbc: 74 64 je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048cbe: e8 17 0a 00 00 call 80496da <explode_bomb> //
8048cc3: eb 5d jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048cc5: c6 45 ff 64 movb $0x64,-0x1(%ebp) // Mem(ebp-1) = 0x64
8048cc9: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048ccc: 3d 5b 01 00 00 cmp $0x15b,%eax // 比较Mem(ebp-16) 与 0x15b
8048cd1: 74 4f je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048cd3: e8 02 0a 00 00 call 80496da <explode_bomb> //
8048cd8: eb 48 jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048cda: c6 45 ff 6b movb $0x6b,-0x1(%ebp) // Mem(ebp-1) = 0x6b
8048cde: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048ce1: 3d 5c 03 00 00 cmp $0x35c,%eax // 比较Mem(ebp-16) 与 0x35c
8048ce6: 74 3a je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048ce8: e8 ed 09 00 00 call 80496da <explode_bomb> //
8048ced: eb 33 jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048cef: c6 45 ff 65 movb $0x65,-0x1(%ebp) // Mem(ebp-1) = 0x65
8048cf3: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048cf6: 3d 9c 02 00 00 cmp $0x29c,%eax // 比较Mem(ebp-16) 与 0x29c
8048cfb: 74 25 je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048cfd: e8 d8 09 00 00 call 80496da <explode_bomb> //
8048d02: eb 1e jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048d04: c6 45 ff 63 movb $0x63,-0x1(%ebp) // Mem(ebp-1) = 0x63
8048d08: 8b 45 f0 mov -0x10(%ebp),%eax // eax = Mem(ebp-16)
8048d0b: 3d eb 00 00 00 cmp $0xeb,%eax // 比较Mem(ebp-16) 与 0xeb
8048d10: 74 10 je 8048d22 <phase_3+0x11c> // 相同则跳转8048d22,不同bomb
8048d12: e8 c3 09 00 00 call 80496da <explode_bomb> //
8048d17: eb 09 jmp 8048d22 <phase_3+0x11c> // 跳转到8048d22
8048d19: c6 45 ff 63 movb $0x63,-0x1(%ebp) // Mem(ebp-1) = 0x63
8048d1d: e8 b8 09 00 00 call 80496da <explode_bomb> // 爆炸
8048d22: 0f b6 45 ef movzbl -0x11(%ebp),%eax // eax = Mem(ebp-17)
8048d26: 38 45 ff cmp %al,-0x1(%ebp) // 比较al 1字节与 ebp-1位置的值,
8048d29: 74 05 je 8048d30 <phase_3+0x12a> 相同则推出,否则bomb
8048d2b: e8 aa 09 00 00 call 80496da <explode_bomb>
8048d30: c9 leave
8048d31: c3 ret
具体分析见注释,然后可以得到如下分析:
-
首先分配了56字节的地址空间,初始化Mem(%ebp-8)为0
-
然后将三个地址%ebp-16、%ebp-17、%ebp-12和立即数0x8049a0b存入靠近esp的24个字节位置中,将Mem(ebp+8)内存中值(应该是phase_3的输入参数)存入Mem(esp),这一部分应该是存参数,作为sscanf函数的入参,所以参数有3个指针引用,一个立即数,一个普通参数,然后调用sscanf函数。
-
然后将sscanf函数的返回结果存入Mem(ebp-8)位置,将此返回结果与0x2比较,若大于2则跳到8048c49(即跳过了爆炸),否则爆炸,所以这个返回结果一定是要大于2的
-
大于2跳过爆炸后,将Mem(ebp-12)内容存入Mem(ebp-36),猜测Mem(ebp-12)内存中存的值应该是被sscanf函数改变过的,将此值,即Mem(ebp-36) 复制的Mem(ebp-12)的值与0x7比较,大于0x7则跳去8048d19,修改Mem(ebp-1) = 0x63,然后爆炸
-
否则小于等于7的话不跳过的话继续执行,将这个值,即Mem(ebp-36) 复制的Mem(ebp-12)的值存入%edx,然后作计算 Mem(0x8049a14+edx*4),相当于edx作索引,从0x8049a14地址后面取第i个int类型值。
-
然后将此int类型的值作为一个地址跳转过去,猜测这里应该就是switch操作,选择 跳转表 中的地址用于跳转,那么就查看一下地址0x8049a14后面的值,是否是跳转表内容,用命令
x/40x 0x8049a14
查看地址0x8049a14后40个字节的十六进制内容 -
(gdb) x/40x 0x8049a14 0x8049a14: 0x65 0x8c 0x04 0x08 0x81 0x8c 0x040x08 0x8049a1c: 0x9d 0x8c 0x04 0x08 0xb2 0x8c 0x040x08 0x8049a24: 0xc5 0x8c 0x04 0x08 0xda 0x8c 0x040x08 0x8049a2c: 0xef 0x8c 0x04 0x08 0x04 0x8d 0x040x08 0x8049a34: 0x25 0x64 0x00 0x73 0x61 0x69 0x6e0x74
-
则由查看结果,可以看到0x8049a14有8个地址和代码中地址能对应,依次是
-
0x08048c65 0x08048c81 0x08048c9d 0x08048cb2 0x08048cc5 0x08048cda 0x08048cef 0x08048d04
-
那么这些就对应switch选择后跳转的位置标签,选择值通过Mem(0x8049a14+edx*4)计算,关键就是%edx的索引,而%edx是Mem(ebp-36) 复制的Mem(ebp-12)的值,Mem(ebp-12)的值应该是被sscanf函数修改过的。
-
接下来就重复了8组代码,都是每个switch case中的处理,逻辑都相同,将Mem(ebp-1) 赋值,然后比较Mem(ebp-16)与一个数是否相同,不同bomb,相同跳转到8048d22最后处理,Mem(ebp-16)的值应该是被sscanf函数修改过的。
-
在8048d22处代码,比较Mem(ebp-17)与Mem(ebp-1) 值,相同则推出,Mem(ebp-1) 是在switch case处理中赋值的,Mem(ebp-17)是sscanf有修改的值。
-
最后串起来最初调用sscanf之前做的处理,有三个指针,即三个地址%ebp-16、%ebp-17、%ebp-12,后面也用到了,然后有一个立即数0x8049a0b,看看这个立即数地址存的什么
-
(gdb) x/s 0x8049a0b 0x8049a0b: "%d %c %d"
-
所以这个代表sscanf读入三个数, int char int 三个类型
-
所以推测整个函数功能就是,在sscanf函数中输入一个数(%ebp-12位置)作为选择switch分支,这个数在范围是0~7;第二个参数char(%ebp-17位置)与Mem(ebp-1)比较是否相同,Mem(ebp-1)是在每个case分支赋值的,故选择一个分支,对应相同的填入即可,第三个参数(%ebp-16位置)是在case分支内必须和其一个数相同。
-
这里选择第一个数来尝试,即输入0 ,跳转到case0x08048c65,Mem(ebp-1) = 0x79,那么输入的char也应该是0x79(代表y),比较 Mem(ebp-16) == 0x346,则输入的int应该是838
所以第三阶段的一个答案是
0 y 838
phase_4
开启gdb调试,并打断点到phase_4, 记得输入前面阶段的答案
>> gbd bomb
(gdb) break phase_4
(gdb) run
(gdb) disas
用disas 命令查看此函数汇编代码,对应在bomb.s中这一段:
08048d72 <phase_4>:
8048d72: 55 push %ebp // 压栈返回地址
8048d73: 89 e5 mov %esp,%ebp // 新的栈底
8048d75: 83 ec 28 sub $0x28,%esp // 分配40字节空间,esp
8048d78: 8d 45 f4 lea -0xc(%ebp),%eax // eax = ebp-12
8048d7b: 89 44 24 08 mov %eax,0x8(%esp) // 将地址ebp-12存入Mem(esp+8)
8048d7f: c7 44 24 04 34 9a 04 movl $0x8049a34,0x4(%esp) // 将地址0x8049a34存入Mem(esp+4)
8048d86: 08
8048d87: 8b 45 08 mov 0x8(%ebp),%eax // eax = Mem(ebp+8) 这个地址应该是提取phase_4的一个参数
8048d8a: 89 04 24 mov %eax,(%esp) // 将此参数存入Mem(esp)
8048d8d: e8 d6 fa ff ff call 8048868 <sscanf@plt> // 调用sscanf函数
8048d92: 89 45 fc mov %eax,-0x4(%ebp) // 将sscanf函数结果存入Mem(ebp-4)
8048d95: 83 7d fc 01 cmpl $0x1,-0x4(%ebp) // 比较这个结果和1
8048d99: 75 07 jne 8048da2 <phase_4+0x30> // 不同则跳到8048da2爆炸,否则继续
8048d9b: 8b 45 f4 mov -0xc(%ebp),%eax // eax = Mem(ebp-12) 也就是之前传入sscanf的指针指向的区域中的值
8048d9e: 85 c0 test %eax,%eax // 判断eax中的值是否为0
8048da0: 7f 05 jg 8048da7 <phase_4+0x35> // >0则跳过,否则爆炸
8048da2: e8 33 09 00 00 call 80496da <explode_bomb> // 爆炸
8048da7: 8b 45 f4 mov -0xc(%ebp),%eax // eax = Mem(ebp-12) 也就是之前传入sscanf的指针指向的区域中的值
8048daa: 89 04 24 mov %eax,(%esp) // 将此结果Mem(ebp-12)存入Mem(esp)
8048dad: e8 80 ff ff ff call 8048d32 <func4> // 调用函数func4
8048db2: 89 45 f8 mov %eax,-0x8(%ebp) // 将结果存入 Mem(ebp-8)
8048db5: 81 7d f8 62 02 00 00 cmpl $0x262,-0x8(%ebp) // 比较结果Mem(ebp-8)这个值和 0x262
8048dbc: 74 05 je 8048dc3 <phase_4+0x51> // 相等则离开,否则爆炸
8048dbe: e8 17 09 00 00 call 80496da <explode_bomb> //
8048dc3: c9 leave
8048dc4: c3 ret
见注释分析后,总结其功能如下:
-
在调用sscanf函数之前,做了一些准备工作,即其传入的参数有三个,地址ebp-12即一个指针,猜测是要将输入的值存在这,地址立即数0x8049a34即输入的格式, Mem(ebp+8)中的内容作为参数3。查看0x8049a34地址中存的内容
-
(gdb) x/s 0x8049a34 0x8049a34: "%d"
-
说明输入的是一个int值,然后存在地址ebp-12处的内存。
-
调用sscanf函数后,将返回值存在Mem(ebp-4),返回值不为1则继续执行
-
判断Mem(ebp-12)这个传入sscanf指针指向的内存中存的值是否大于0,大于继续执行
-
然后将Mem(ebp-12)这个值存入Mem(esp)作为func4函数的参数,然后调用func4,注意Mem(ebp-12)中的值其实就是sscanf中我们输入的值
-
func4函数执行完成后比对结果是否等于0x262 = 610,相等则离开
所以关键在于func4利用sscanf输入的值做了什么处理,最后一定要返回0x262才行,接下来查看func4函数中的代码,同样设置断点并查看
(gdb) break func4
(gdb) continue
Breakpoint 3, 0x08048d36 in func4 ()
(gdb) disas
展现的代码就是下面这一段:
08048d32 <func4>:
8048d32: 55 push %ebp \\ 保存返回地址
8048d33: 89 e5 mov %esp,%ebp \\ 新的栈底
8048d35: 53 push %ebx \\ 被调用者保存寄存器
8048d36: 83 ec 08 sub $0x8,%esp \\ 分配空间8字节
8048d39: 83 7d 08 01 cmpl $0x1,0x8(%ebp) \\ 比较输入的值Mem(ebp+8) 与 1
8048d3d: 7f 09 jg 8048d48 <func4+0x16> \\ 大于的话跳转到8048d48,否则继续执行
8048d3f: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%ebp) \\ 将Mem(ebp-8)处的值赋值为 1
8048d46: eb 21 jmp 8048d69 <func4+0x37> \\ 跳转到 8048d69继续执行
8048d48: 8b 45 08 mov 0x8(%ebp),%eax \\ eax = 输入的参数Mem(ebp+8)
8048d4b: 48 dec %eax \\ 输入的值-1
8048d4c: 89 04 24 mov %eax,(%esp) \\ 将此值放入Mem(%esp)作为下面调用func4的递归参数
8048d4f: e8 de ff ff ff call 8048d32 <func4>
8048d54: 89 c3 mov %eax,%ebx \\ ebx = 将func4返回值
8048d56: 8b 45 08 mov 0x8(%ebp),%eax \\ eax = 输入参数Mem(ebp+8)
8048d59: 83 e8 02 sub $0x2,%eax \\ eax = 输入参数Mem(ebp+8)-2
8048d5c: 89 04 24 mov %eax,(%esp) \\ 将此值放入Mem(%esp)作为下面调用func4的递归参数
8048d5f: e8 ce ff ff ff call 8048d32 <func4>
8048d64: 01 c3 add %eax,%ebx \\ 将func4的返回结果累加到ebx中
8048d66: 89 5d f8 mov %ebx,-0x8(%ebp) \\ 将ebx的值存入 Mem(ebp-8)
8048d69: 8b 45 f8 mov -0x8(%ebp),%eax \\ 将Mem(ebp-8)中的值作为结果
8048d6c: 83 c4 08 add $0x8,%esp \\ 将esp+=8 释放空间
8048d6f: 5b pop %ebx \\ 弹出ebp
8048d70: 5d pop %ebp \\ 弹栈出函数地址
8048d71: c3 ret
见代码注释,可以得到如下分析:
- 整个代码将输入参数x,做了两次func4递归, func4(x-1)和func4(x-2),然后将两次递归的结果累加返回,如果x==1的时候返回1.
- 所以这就是一个递归的裴波那契数列计算函数
- 则F(x) = F(x-1)+F(x-2), F(1)=1,F(2)=1,F(3)=2…F(14)=610=0x262
所以由上分析,需要输入一个参数使得裴波那契函数计算结果为610,即输入14就可以得到。
phase_5
开启gdb调试,并打断点到phase_5,记得输入前面阶段的答案
>> gbd bomb
(gdb) break phase_5
(gdb) run
(gdb) disas
用disas 命令查看此函数汇编代码,对应在bomb.s中这一段:
08048dc5 <phase_5>:
8048dc5: 55 push %ebp \\ 保存返回地址
8048dc6: 89 e5 mov %esp,%ebp \\ 新的栈底
8048dc8: 83 ec 18 sub $0x18,%esp \\ 分配24字节空间,esp
8048dcb: 8b 45 08 mov 0x8(%ebp),%eax \\ 将函数输入参数Mem(ebp+8)放入eax
8048dce: 89 04 24 mov %eax,(%esp) \\ 将此参数放入 Mem(ebp) ,即调用string_length的参数
8048dd1: e8 13 03 00 00 call 80490e9 <string_length>
8048dd6: 89 45 fc mov %eax,-0x4(%ebp) \\ 将string_length返回值存入Mem(ebp-4)位置
8048dd9: 83 7d fc 06 cmpl $0x6,-0x4(%ebp) \\ 比较string_length返回值 与 6
8048ddd: 74 05 je 8048de4 <phase_5+0x1f> \\ 相同则跳8048de4,否则爆炸,实际就是跳过爆炸代码
8048ddf: e8 f6 08 00 00 call 80496da <explode_bomb>
8048de4: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%ebp) \\ 初始化Mem(ebp-8) 为 0
8048deb: eb 20 jmp 8048e0d <phase_5+0x48> \\ 跳转到 8048e0d
8048ded: 8b 55 f8 mov -0x8(%ebp),%edx \\ edx = Mem(ebp-8)
8048df0: 8b 45 f8 mov -0x8(%ebp),%eax\\ eax = Mem(ebp-8)
8048df3: 03 45 08 add 0x8(%ebp),%eax \\ eax += (ebp+8),即循环的次数+输入参数指针 ptr+i
8048df6: 0f b6 00 movzbl (%eax),%eax \\ 将Mem(eax)的一个字节写入 eax,0补充高位
8048df9: 0f be c0 movsbl %al,%eax \\ 将al填入eax,符号位填充
8048dfc: 83 e0 0f and $0xf,%eax \\ 将eax &= 0xf(即0b1111) and运算
8048dff: 0f b6 80 c0 a5 04 08 movzbl 0x804a5c0(%eax),%eax \\ 将Mem(0x804a5c0+%eax) 值写入eax
8048e06: 88 44 15 f1 mov %al,-0xf(%ebp,%edx,1) \\ 将eax的值存入 Mem(ebp+edx-16) exd中存的是循环此数,即要存ebp-16到ebp-11的6个字节值
8048e0a: ff 45 f8 incl -0x8(%ebp) \\ 自增Mem(ebp-8),其实就是循环完成次数+1
8048e0d: 83 7d f8 05 cmpl $0x5,-0x8(%ebp) \\ 将 Mem(ebp-8) 与 5 比较,猜测这是循环次数的判断
8048e11: 7e da jle 8048ded <phase_5+0x28> \\ 小于等于5则跳到 8048ded继续执行
8048e13: c6 45 f7 00 movb $0x0,-0x9(%ebp) \\ 大于5后, 将Mem(ebp-9) = 0
8048e17: c7 44 24 04 37 9a 04 movl $0x8049a37,0x4(%esp) \\ 然后将立即数0x8049a37赋值到 Mem(esp+4)
8048e1e: 08
8048e1f: 8d 45 f1 lea -0xf(%ebp),%eax \\ eax = ebp-15
8048e22: 89 04 24 mov %eax,(%esp) \\ Mem(esp) = ebp-15的地址,其实相当于传了一个指针
8048e25: e8 e9 02 00 00 call 8049113 <strings_not_equal> 调用判断字符串不相等
8048e2a: 85 c0 test %eax,%eax \\ 判断结果
8048e2c: 74 05 je 8048e33 <phase_5+0x6e> \\ 若和0相同说明相等,就正确推出,若不等就爆炸
8048e2e: e8 a7 08 00 00 call 80496da <explode_bomb>
8048e33: c9 leave
8048e34: c3 ret
可见注释分析,总结如下:
-
将输入的字符串在string_length函数中得到长度,返回值即字符串长度一定是6
-
初始化Mem(ebp-8) 为 0,跳到8048e0d出判断此值是否小于等于5,是则跳入8048ded继续执行,所以这里其实是一个循环6次的循环,应该是把每个输入的字符作处理判断
-
循环内部:将循环次数i存入 %edx %eax。然后eax+Mem(ebp+8),即循环的次数+输入参数,将Mem(eax+Mem(ebp+8))的一个字节写入 eax,0补充高位,然后将al填入eax,符号位填充;将eax &= 0xf(即0b1111) ,其实这一段运算就是将Mem(eax+Mem(ebp+8))存的一个字节内容放入eax中,只留下了低4位bit的内容。
-
然后将刚刚一堆计算得到的eax值继续运算,Mem(0x804a5c0+%eax) 存的内容写一个字节到Mem(ebp+edx-16)位置。那么6次循环,exd中存的是循环此数,即要存ebp-16到ebp-11的6个字节值。所以查看一下0x804a5c0位置存了些什么
-
(gdb) x/s 0x804a5c0 0x804a5c0 <array.2511>: "isrveawhobpnutf", <incomplete sequence \371>
-
可以看到0x804a5c0处是一个数组,存了0x76727369 0x68776165 0x6e70626f 0x67667475 四个数。
-
然后就是循环次数自增i++,
-
跳出循环后会判断ebp-15的地址的字符串是否和地址0x8049a37处的字符串相同,不同则爆炸,相同则成功,所以查看0x8049a37处的字符串
-
(gdb) x/s 0x8049a37 0x8049a37: "saints"
-
所以理解其实就是从0x804a5c0处的字符串“isrveawhobpnutf”中通过下标取值,一次得到一个字符串,最后一共6个字符,和“saints”字符串要相等。那么输入6次的下标可以选择为
1,5,0,11,13,1
,然后回看循环内部如何处理输入得到索引下标呢, 索引字符串“isrveawhobpnutf”下标eax是 eax += (ebp+8),将输入参数地址ebp+8与循环次数eax累加,依次访问6个字符,然后每个字符的低4位作为索引。所以这里我直接选择0x31,0x35,0x30,0x3b,0x3d,0x31
作为结果,即他们的低4位是1,5,0,11,13,1
,对应ASCII为
150;=1
phase_6
开启gdb调试,并打断点到phase_6,记得输入前面阶段的答案
>> gbd bomb
(gdb) break phase_6
(gdb) run
(gdb) disas
用disas 命令查看此函数汇编代码,对应在bomb.s中这一段:
08048ec9 <phase_6>:
8048ec9: push %ebp \\ 保存返回地址
8048eca: mov %esp,%ebp \\ 新的栈底
8048ecc: sub $0x18,%esp \\ 分配24字节空间
8048ecf: movl $0x804a63c,-0x8(%ebp) \\ 将立即数0x804a63c 存入 Mem(ebp-8)
8048ed6: mov 0x8(%ebp),%eax \\ 将输入参数Mem(ebp+8) 赋值给 eax
8048ed9: mov %eax,(%esp) \\ 将Mem(ebp+8)参数放入 Mem(esp) 作为atoi的输入参数
8048edc: call 8048858 <atoi@plt> \\ 将字符串转换为int类型数据
8048ee1: mov %eax,%edx \\ 将转换的int数结果存入edx
8048ee3: mov -0x8(%ebp),%eax \\ eax = Mem(ebp-8) = 0x804a63c
8048ee6: mov %edx,(%eax) \\ 将转换的int数结果存入 Mem(0x804a63c)
8048ee8: mov -0x8(%ebp),%eax \\ eax = Mem(ebp-8) = 0x804a63c
8048eeb: mov %eax,(%esp) \\ 将0x804a63c 作为参数放在Mem(esp)
8048eee: call 8048e35 <fun6> \\ 调用fun6函数
8048ef3: mov %eax,-0x8(%ebp) \\ 将fun6返回结果放入 Mem(ebp-8) = 0x0x804a630
8048ef6: mov -0x8(%ebp),%eax \\ eax = Mem(ebp-8)
8048ef9: mov %eax,-0x4(%ebp) \\ 将fun6返回结果存入 Mem(ebp-4)
8048efc: movl $0x1,-0xc(%ebp) \\ 将1存入 Mem(ebp-12)
8048f03: jmp 8048f11 <phase_6+0x48> \\ 跳转到 8048f11
8048f05: mov -0x4(%ebp),%eax \\ eax = Mem(ebp-4)
8048f08: mov 0x8(%eax),%eax \\ eax = Mem(8 + Mem(ebp-4))
8048f0b: mov %eax,-0x4(%ebp) \\ 将更新的结果放回 Mem(ebp-4)
8048f0e: incl -0xc(%ebp) \\ Mem[ebp-12]++,即循环次数i++
8048f11: cmpl $0x7,-0xc(%ebp) \\ 比较 7 和 Mem(ebp-12)的值,Mem(ebp-12)的值初始化是1
8048f15: jle 8048f05 <phase_6+0x3c> \\ 小于等于则跳 8048f05继续执行,所以这里其实是循环8次
8048f17: mov -0x4(%ebp),%eax \\ eax = Mem(ebp-4)
8048f1a: mov (%eax),%edx \\ edx = Mem(Mem(ebp-4))
8048f1c: mov 0x804a63c,%eax \\ eax = Mem(0x804a63c)地址中存的值
8048f21: cmp %eax,%edx \\ 比较这两个值,相同就能通过
8048f23: je 8048f2a <phase_6+0x61> \\
8048f25: call 80496da <explode_bomb> \\
8048f2a: leave
8048f2b: ret
分析见注释,总结如下:
-
首先初始化栈空间,分配了24字节,将立即数0x804a63c 存入 Mem(ebp-8)
-
将输入参数作为atoi的输入参数,字符串转化为int数据类型,结果存到Mem(0x804a63c)这个立即数指向的位置
-
然后将0x804a63c 作为参数放在Mem(esp)调用fun6函数,fun6的调用结果放在了 Mem(ebp-8)和Mem(ebp-4)中
-
Mem(ebp-12)作为一个循环的计数器,初始化为1,<=7一直循环
-
循环内部每次对Mem(ebp-12)++,内部的工作为:Mem(ebp-4) = Mem(Mem(ebp-4)+8),这个有点像链表的next操作 p = p->next,实际就是向后操作了7次
-
然后edx= Mem(Mem(ebp-4))的结果和eax = Mem(0x804a63c)地址中存的值(即输入的值转化为int类型的结果)比较是否相同
-
所以直接查看%edx中的结果是什么即可。
-
0x08048f1c in phase_6 () (gdb) p/x $edx $2 = 0xf8
-
所以答案就是
0xf8 = 248
-
但其实解答过程并没有这么简单,之前0x804a63c这一片的地址查看,其实可以知道他们是10个节点串起来的指针,
-
(gdb) x/120b 0x804a5d0 0x804a5d0 <node9>: 0xf9 0x00 0x00 0x00 0x09 0x00 0x00 0x00 0x804a5d8 <node9+8>: 0x00 0x00 0x00 0x00 0xf8 0x00 0x00 0x00 0x804a5e0 <node8+4>: 0x08 0x00 0x00 0x00 0xd0 0xa5 0x04 0x08 0x804a5e8 <node7>: 0xb1 0x02 0x00 0x00 0x07 0x00 0x00 0x00 0x804a5f0 <node7+8>: 0xdc 0xa5 0x04 0x08 0x03 0x01 0x00 0x00 0x804a5f8 <node6+4>: 0x06 0x00 0x00 0x00 0xe8 0xa5 0x04 0x08 0x804a600 <node5>: 0x69 0x03 0x00 0x00 0x05 0x00 0x00 0x00 0x804a608 <node5+8>: 0xf4 0xa5 0x04 0x08 0xe7 0x01 0x00 0x00 0x804a610 <node4+4>: 0x04 0x00 0x00 0x00 0x00 0xa6 0x04 0x08 0x804a618 <node3>: 0x16 0x03 0x00 0x00 0x03 0x00 0x00 0x00 0x804a620 <node3+8>: 0x0c 0xa6 0x04 0x08 0x6d 0x00 0x00 0x00 0x804a628 <node2+4>: 0x02 0x00 0x00 0x00 0x18 0xa6 0x04 0x08 0x804a630 <node1>: 0xa9 0x03 0x00 0x00 0x01 0x00 0x00 0x00 0x804a638 <node1+8>: 0x24 0xa6 0x04 0x08 0x00 0x00 0x00 0x00 0x804a640 <node0+4>: 0x00 0x00 0x00 0x00 0x30 0xa6 0x04 0x08
-
每个节点12个字节,故猜测其结构是
-
struct node{ int val; \\node+0 node* next; \\ node+8 }
-
可以看到每个node有12个字节,高地址4字节存next指针,所以可以看到链表初始结构如下:
-
node0->next = 0x804a630 node1 , node0->val = 输入值 node1->next = 0x804a624 node2 , node1->val = 0x3a9 node2->next = 0x804a618 node3 , node2->val = 0x6d node3->next = 0x804a60c node4 , node3->val = 0x316 node4->next = 0x804a600 node5 , node4->val = 0x1e7 node5->next = 0x804a5f4 node6 , node5->val = 0x369 node6->next = 0x804a5e8 node7 , node6->val = 0x103 node7->next = 0x804a5dc node8 , node7->val = 0x2b1 node8->next = 0x804a5d0 node9 , node8->val = 0xf8 node9->next = 0x0000000 null , node9->val = 0xf9
-
而函数中循环7次,是从返回的结果开始循环,最后其实操作p = p->next 7次到一个节点,而链表结构是在fun4函数中有一堆更改操作,总之这里我们取巧直接去尝试每一个节点的值作为输入值比对,最后发现结果有两个:
-
0xf8=248 或者 0xf9=249
这里我们选取249作为结果
总结结果
最终选取的结果是
Why make trillions when we could make... billions?
1 2 3 1 2 3
0 y 838
14
150;=1
249
然后运行命令测试