CSAPP 拆炸弹 中科大实验

实验二 拆炸弹

准备工作

首先反汇编

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

然后运行命令测试
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlwaysDayOne

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值