介绍
最近有机会接触了可执行文件(例如elf,pe文件)的安全缓解措施的检测方法,这里做一些记录。
NX标志位
栈上的数据没有执行权限
防止攻击手段:栈溢出 + 跳到栈上执行shellcode
NX位是某些CPU(包括x86)的内存管理单元(MMU)的一个特性。它允许将每个内存页标记为代码执行的“允许”或“不允许”。MMU受内核控制;内核代码决定哪些页获得执行权限,哪些页没有。因此,堆栈空间是否受到执行保护取决于操作系统。这还取决于许多其他因素:
- 尽管内核可以为页面属性应用默认值,但应用程序可以请求更改。例如,在Linux系统上,mprotect()系统调用可用于启用或禁用在调用进程控制下的任何页面上的执行。特别是,任何包含JIT编译器的应用程序(例如,Web浏览器,用于高效的Javascript支持)都必须使用mprotect(),因为它必须生成代码(即,将字节写入某些内存),然后执行所述代码。
- 单线程应用程序在内核知道的专用区域中获取其堆栈;特别是,在首次使用时会自动分配堆栈页。在多线程应用程序中,情况会发生变化:每个线程都有自己的堆栈,从内核的角度来看,这是堆分配的。根据操作系统的不同,内核可能会或不会对线程堆栈提供特殊支持。
- GCC特别支持称为nested functions的C语言扩展。由于这些函数的语义,嵌套函数的编译代码必须在堆栈上动态生成一些可执行代码;这称为trampoline,要让trampoline真正工作,堆栈(或者至少是trampoline所在的页面)必须标记为可执行。
在ELF文件格式(Linux上的可执行文件和DLL的格式)中,有一个字段允许指定堆栈是否应该是可执行的。当GCC编译一些包含嵌套函数的代码时,它会在生成的二进制文件中设置这个标志。
我在最近的Linux上做了一些测试(ubuntu13.10,64位x86,NX位激活)。可以从特殊文件/proc/是进程ID。这些测试显示了以下内容:
- 默认情况下,主堆栈不可执行。为堆栈页设置NX位。
- 如果代码包含嵌套函数,则可执行文件将被标记为“executable stack”标志,实际上,主堆栈现在是可执行的。
- 线程堆栈是以与主堆栈相同的“可执行状态”创建的。
- 如果“普通”可执行文件(其主堆栈不可执行)动态加载包含嵌套函数代码的DLL(使用dlopen()),则主堆栈将自动切换到可执行状态。
- 如果一个普通的多线程可执行文件(其主堆栈和所有线程堆栈都不可执行)动态加载一个包含嵌套函数代码的DLL,则主堆栈和所有线程堆栈将整体升级为可执行状态。请注意,这意味着在这个Linux系统上,”其他代码段“有可能(可能是内核,我没有检查)知道所有当前线程堆栈,并且可以动态更改它们的映射权限。
至少在Linux的一些最新版本中,堆栈(主线程和线程)通常会被标记为不可执行(即NX位集)。但是,如果应用程序的可执行代码或应用程序加载的DLL中某处的某些可执行代码包含嵌套函数或以其他方式宣布需要可执行堆栈,则应用程序中的所有堆栈都将标记为可执行(即未设置NX位)。换句话说,应用程序的单个可加载插件或扩展可能会停用应用程序所有线程的NX堆栈保护,只需使用GCC的一个很少使用但合法且有文档记录的特性即可。
不过,没有必要惊慌失措,因为NX位提供的保护并不是那么好。这将使一些攻击对于最不称职的攻击者来说更加尴尬;但是优秀的攻击者不会受到阻碍。而且,所有关于NX的讨论都是关于一旦缓冲区溢出或释放后使用发生时,就试图控制损坏,这可以说是反应有点晚了。
来自于:
https://security.stackexchange.com/questions/47807/nx-bit-does-it-protect-the-stack
https://fedoraproject.org/wiki/Toolchain/Watermark/PIEDSO
PIE
代码部分地址无关
防止攻击手段:构造ROP链攻击
要使用gcc编译出位置无关可执行程序,必须使用-pie和-fPIE选项。其中,-fPIE选项用于编译器,使用这个选项之后,从.c或.cpp编译出来的.o文件将是位置无关的目标文件。而-pie选项则用于链接器,使用这个选项之后,链接器能够把-fPIE选项下编译出来的.o文件链接成位置无关可执行程序。
位置无关可执行文件(PIE)是加固包构建过程的输出。每次执行应用程序时,PIE二进制文件及其所有依赖项都会加载到虚拟内存中的随机位置。这使得面向返回编程(Return-Oriented Programming,ROP)攻击更难可靠地执行。
面向返回编程(英語:Return-Oriented Programming,缩写:ROP)是计算机安全漏洞利用技术,该技术允许攻击者在安全防御的情况下执行代码,如不可执行的内存和代码签名。
ROP是一种高级的堆栈溢出攻击。这类攻击往往利用操作堆栈调用时的程序漏洞,通常是缓冲区溢出。在缓冲区溢出中,在将数据存入内存前未能正确检查适当范围的函数会收到多于正常承受范围的数据,如果数据将写入栈,多余的数据会溢出为函数变量分配的空间并覆盖替换返回地址(return address)。在原本用以重定向控制流并返回给调用者的地址被覆盖替换后,控制流将改写到新分配的地址。
标准的缓冲区溢出攻击,攻击者只需要写出针对堆栈部分的代码(有效载荷)。直到1990年代后期,主流操作系统没有为该类攻击作出任何防范,微软直到2004年才提供了缓冲区溢出保护。操作系统最终使用数据执行保护技术来修补这个漏洞,该技术标记内存数据不可执行。 启用数据执行保护,机器将拒绝执行任何内存中user级别可写区域的代码。该技术的硬件支持不久用以加强该防范
https://zh.wikipedia.org/wiki/%E8%BF%94%E5%9B%9E%E5%AF%BC%E5%90%91%E7%BC%96%E7%A8%8B
http://richardustc.github.io/2013-05-21-2013-05-21-pie.html
https://access.redhat.com/blogs/766093/posts/1975793
Canary
在函数开始时就随机产生一个值,将这个值CANARY放到栈上紧挨ebp的上一个位置,当攻击者想通过缓冲区溢出覆盖ebp或者ebp下方的返回地址时,一定会覆盖掉CANARY的值;当程序结束时,程序会检查CANARY这个值和之前的是否一致,如果不一致,则不会往下运行,从而避免了缓冲区溢出攻击。
防止攻击手段:所有单纯的栈溢出
Foritfy Source
FORTIFY-SOURCE宏提供了轻量级支持,用于在对内存和字符串执行操作的各种函数中检测缓冲区溢出。不是所有类型的缓冲区溢出都可以用这个宏检测到,但是它确实为一些可能是缓冲区溢出缺陷来源的函数提供了额外的验证级别。它同时保护C和C++代码。加强源代码的工作原理是计算从源代码复制到目标代码的字节数。如果攻击者试图复制更多字节以使缓冲区溢出,程序的执行将停止。
GNU_RELRO段
此段表示在重定位完成后应设为只读的内存区域。此段通常出现在动态链接库中,它包含.ctors、.dtors、.dynamic、.got节。
http://keserix.blogspot.com/2013/09/runtime-protection-with-gcc.html