Linux下的保护机制

  1. 1. 前言
  2. 2. canary
  3. 3. NX:no-execute
  4. 4. PIE:position-independent executables
  5. 5. RELRO:read only relocation
  6. 6. Fortity
  7. 7. 总结
  8. 8. 参考资料

前言

在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NXPIE等机制,例如存在NX的话就不能直接执行栈上的code,存在PIE的话各个系统调用的地址就是随机化的。Linux中的保护机制包括canaryNX(no-execute)PIE(position-independent executables)RELRO(read only relocation)FORTIFY等等,暂时没写关于内核安全防护的内容,以后再补充。检查可执行文件属性可使用checksec

canary

栈保护。栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址从而控制程序流的执行。当启用栈保护后,函数开始执行的时候会先往栈里插入一个特殊的value值,当函数返回的时候会验证value值是否变化,如果发生变化就停止程序运行。攻击者在覆盖返回地址的时候往往也会将value值给覆盖掉,导致栈保护检查失败而终止程序的执行。这个特殊的value值我们称之为canary

编译时控制是否开启栈保护及程度的指令:

1
2
3
4
gcc -o test test.c  // 默认情况下,不开启Canary保护
gcc -fno-stack-protector -o test test.c //禁用栈保护
gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有char数组的函数插入保护代码
gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码

NX:no-execute

堆栈不可执行。将数据所在内存页标识为不可执行,当程序执行shellcode中的指令时,CPU就会抛出异常,而不是去执行恶意指令。Linux中的NX与Windows下的DEP工作原理类似,DEP工作原理如下:

1、DEP工作原理图

控制是否开启NX的指令如下:

1
2
3
gcc -o test test.c // 默认情况下,开启NX保护
gcc -z execstack -o test test.c // 禁用NX保护
gcc -z noexecstack -o test test.c // 开启NX保护

注:在Windows下,类似的概念为DEP:Data Execution Prevention(数据执行保护)

PIE:position-independent executables

位置独立的可执行区域。NXPIE常常同时工作,这样使得在利用缓冲溢出和移动操作系统中存在的其他内存崩溃缺陷时采用ROP技术变得难得多。Windows中的类似机制叫做ASLR:address space layout randomization(内存地址随机化机制),有以下三种情况:

1
2
3
0 - 表示关闭进程地址空间随机化
1 - 表示将mmap的基址,stack和vdso页面随机化
2 - 表示在1的基础上增加栈(heap)的随机化。

Linux下关闭PIE的命令如下:

1
sudo -s echo 0 > /proc/sys/kernel/randomize_va_space

编译时的控制指令如下:

1
2
3
gcc -o test test.c					// 默认情况下,不开启PIE
gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1
gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2

RELRO:read only relocation

只读重定位。在Linux系统中,数据可以写的存储区就会是攻击的目标,尤其是存储函数指针的区域。 所以从安全防护的角度来说,尽量减少可写的存储区域对安全会有极大的好处。RELRO让加载器将重定位表中加载时解析的符号标记为只读或在程序启动时就解析并绑定所有动态符号,这减少了GOT覆写攻击的面积。

RELRO可分为:

1
2
Partial RELRO(部分RELRO) //开启Partial RELRO后GOT表是可写的
Full RELRO(完整RELRO) //开启FULL RELRO后GOT表是只读的,不可写

编译时的控制指令如下:

1
2
3
4
gcc -o test test.c // 默认情况下,是Partial RELRO
gcc -z norelro -o test test.c // 关闭,即No RELRO
gcc -z lazy -o test test.c // 部分开启,即Partial RELRO
gcc -z now -o test test.c // 全部开启

Fortity

一种非常轻微的检查,用于检查是否存在缓冲区溢出的错误。适用情形是程序采用大量的字符串或者内存操作函数,如memcpymemsetstpcpystrcpystrncpystrcatstrncatsprintfsnprintfvsnprintfgets以及宽字符的变体。开启fortity检查后会替换strcpy等危险函数,由于开销较大,所以默认不开启。

1
2
gcc -D_FORTIFY_SOURCE=1 //仅在编译时进行检查 (特别像某些头文件 #include <string.h>)
gcc -D_FORTIFY_SOURCE=2 //程序执行时也会有检查 (如果检查到缓冲区溢出,就终止程序)

2、未启用fortity

3、启用fortity

上述两图分别为不启用和启用fortity的截图。对比发现,启用fortity后,程序在执行strcpy函数时,运行了__strcpt_chk函数,这个函数用来检查是否溢出。检查通过后,这个函数会调用strcpy函数。

总结

这些安全保护措施极大程度上杜绝了恶意程序的攻击,但大部分情况下有一定缺陷、或需要耗费大量资源。这些保护机制仍需要程序员在操作内存时注意程序的安全问题,如需要严格检查不可信的输入。关于如何绕过这些防护机制,待续。。。

参考资料

GCC安全保护机制

linux程序的常用保护机制