缓冲区保护机制

为了更好地对缓冲区进行利用,十分有必要了解一下checksec所检查出的漏洞缓解措施都意味着什么

如图上所示,RELRO、Stack、NX、PIE四种保护机制,下方介绍时,括号里的是在Windows系统中的名称

以下主要来源于《从0到1CTFer成长之路》

一、NX(DEP)

NX即是No-execute,不可执行。原理是通过现代操作系统的内存保护单元机制(MPU)对程序内存按页的粒度进行权限设置,其基本规则为可写权限与可执行权限互斥。因此开启了NX的程序代表着堆栈上写入的代码数据将不可被执行,也就无法直接通过溢出写入shellcode而执行任意代码。

所有可以被修改写入的数据的内存都不可执行,所有可执行的代码数据都是不可修改的。这就是可执行权限与可写权限互斥

GCC编写程序默认开启NX,关闭方法是在编译是加入**-z execstack参数**

1
gcc -z execstack -o test test.c

二、Stack Canary

Stack Canary保护一种针对栈溢出攻击而设计的保护机制。当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行,因此Stack Canary会在函数开始执行前,在返回地址前写入一个字长的随机数据(可以称这个数据为canary,在Windows下是cookie),之后可以在函数返回前进行检验随机数据是否被更改,如果发生更改将直接终止程序进行保护。

GCC编写程序默认开启Stack Canary,关闭方法是在编译时加入**-fno-stack-protector参数**

1
gcc -fno-stack-protector -o test test.c

三、PIE

在介绍PIE前,要先介绍ASLR即Address Space Layout Randomization,地址空间分布随机化。ASLR是系统等级的保护机制,只有在开启了ASLR后PIE才能生效。而ASLR的目的是将程序的堆栈地址和动态链接库(或称为共享库)的加载基址进行一定的随机化,这些地址之间是不可读写执行的未映射内存。

关闭ASLR关闭方式是修改**/proc/sys/kernel/randomize_va_space**文件为0

PIE的目的则是让可执行程序ELF文件的基址进行随机化加载(负责代码段和数据段的随机化),从而使得攻击者难以知道程序的内存结构,与ASLR相互配合。

GCC编写程序开启方法是加入**-fpic -pie参数。较新版本的GCC默认开启PIE,可以设置-no-pie**关闭

1
gcc -fpic -pie -o test test.c

四、Full Relro

Full Relro 保护措施与Linux下的Lazy Binding机制有关,主要作用是设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,禁止.GOT.PLT表和其他一些相关内存的读写,从而阻止攻击者直接进行修改程序所要执行的函数

  • RELRO防御策略:
    • 为NO RELRO的时候,init.arrayfini.arraygot.plt均可读可写
    • 为PARTIAL RELRO的时候,init.arrayfini.array可读不可写,got.plt可读可写
    • 为FULL RELRO时,init.arrayfini.arraygot.plt均可读不可写。

GCC开启Full Relro 的方法是加入**-z relro参数**

1
gcc -z relro -o test test.c

五、Fortify

这部分转载自http://yunnigu.dropsec.xyz/2016/10/08/checksec及其包含的保护机制。

FORTIFY_SOURCE 机制对格式化字符串有两个限制

(1)包含 %n 的格式化字符串不能位于程序内存中的可写地址。

(2)当使用位置参数时,必须使用范围内的所有参数。

所以如果要使用 %7$x,你必须同时使用 1,2,3,4,5 和 6。

当进入到 gdb 中进行 checksec 检查文件会发现多了一个保护措施 Fortify,这是一种比较少见的保护措施,是为了防止缓冲区溢出攻击

举个例子可能简单明了一些:
一段简单的存在缓冲区溢出的C代码

1
2
3
4
5
6
void fun(char *s) {
char buf[0x100];
strcpy(buf, s);
/* Don't allow gcc to optimise away the buf */
asm volatile("" :: "m" (buf));
}

用包含参数-U_FORTIFY_SOURCE编译

1
2
3
4
5
6
7
8
9
10
11
12
13
08048450 <fun>:
push %ebp ;
mov %esp,%ebp

sub $0x118,%esp ; 将0x118存储到栈上
mov 0x8(%ebp),%eax ; 将目标参数载入eax
mov %eax,0x4(%esp) ; 保存目标参数
lea -0x108(%ebp),%eax ; 数组buf
mov %eax,(%esp) ; 保存
call 8048320 <strcpy@plt>

leave ;
ret

用包含参数-D_FORTIFY_SOURCE=2编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
08048470 <fun>:
push %ebp ;
mov %esp,%ebp

sub $0x118,%esp ;
movl $0x100,0x8(%esp) ; 把0x100当作目标参数保存
mov 0x8(%ebp),%eax ;
mov %eax,0x4(%esp) ;
lea -0x108(%ebp),%eax ;
mov %eax,(%esp) ;
call 8048370 <__strcpy_chk@plt>

leave ;
ret

可以看到 gcc 生成了一些附加代码,通过对数组大小的判断替换 strcpy,memcpy,memset 等函数名,达到防止缓冲区溢出的作用。

查看评论