Windows安全机制概述
微软提高内存安全性的改动:
- 使用GS编译技术,在返回地址之前加入了Security Cookie,在函数返回的时候先检查Security Cookie是否被覆盖,从而把针对操作系统的栈溢出漏洞变得非常困难
- 增加了对S.E.H的安全校验机制,能够有效地挫败绝大多数改写S.E.H而劫持进程的攻击
- 堆中加入了Heap Cookie,Safe Unlinking等一系列的安全机制,为原本就困难重重的堆溢出增加了更多的限制
- DEP(Data Execution Protection 数据集执行保护)将数据部分标识为不可执行,阻止了栈,堆和数据节中攻击代码的执行。
- ASLR(Address space layout randomization 加载地址随机)技术通过堆系统关键地址的随机化,是的经典堆栈溢出手段失效
- SEHOP(Structured Exception Handler Overflow Protection S.E.H覆盖保护)作为对安全S.E.H的补充,SEHOP将保护提升到系统级别,使得S.E.H的保护机制更为有效。
栈中的守护天使:GS
GS安全编译选项中的保护原理
针对缓冲区溢出时覆盖返回函数地址这一特征,微软在编译程序中使用了GS安全编译选项
GS编译选项为每个函数增加了一些额外的数据和操作,用以检测栈中的溢出
- 在所有函数调用发生时,向栈帧压入一个额外的随机DWORD,这个随机数被称为”canary“,但如果使用IDA反汇编的话,IDA会将这个随机数标注为”Security Cookie“。
- Security Cookie位于EBP之前(低地址),系统还将在.data的内存区域存放一个Security Cookie的副本。
- 在栈溢出的时候首先被淹没的是Security Cookie,之后才是EBP和返回地址
- 在函数返回之前,系统将执行一个额外的安全检测操作,被称作Security check
- 在Security Check的过程中,系统将比较栈帧中原先存放的Security Cookie和.data区中副本的值,如果两者不吻合,说明栈帧中的Security Cookie被破坏,即栈中发生溢出。
- 检测到栈中发生溢出,程序将进入异常处理流程,函数不会被正常返回,ret指令也不会被执行。
但是额外的数据和操作带来的是性能的下降。为了将性能的影响降到最小,编译器在编译程序的时候并不是对所有的函数都应用GS,以下情况不会应用GS。
- 函数不包含缓冲区
- 函数被定义为具有变量参数列表
- 函数使用无保护的关键字标记
- 函数在第一个语句中包含内嵌汇编代码
- 缓冲区不是八字节类型且大小不超过四字节
#pragma_strict_gs_check(on)
添加这个会为下边的函数强制启用GS
除了返回地址前添加Security Cookie外,在VS2005以及后续版本还是用变量重排技术,在编译时根据局部便令的类型对变量在栈帧中的位置进行调整,将字符串变量移动到栈帧的高地址。这样可以防止字符串溢出的时候破坏其他变量。同时还会将指针参数和字符串参数复制到内存中的低地址,防止函数参数被破坏。
Security Cookie产生的细节
- 系统以.data节的第一个双字作为Cookie的种子,或称原始Cookie(所有函数的Cookie都用这个DWORD生成)
- 在程序每次运行时Cookie的种子都不同,因此种子有很强的随机性
- 在栈帧初始化以后系统用EBP异或种子,作为当前函数的Cookie,以此作为不同函数之间的差别,并增加种子的随机性
- 在函数返回前,用EBP还原出Cookie的种子
- 修改栈帧中的函数返回地址被GS机制有效遏制
- 基于改写函数指针的攻击,如对C++虚函数的攻击,GS机制仍然很难防御
- 针对异常处理机制的攻击,GS很难防御
- GS是堆栈帧的保护机制,因此很难防御堆溢出攻击
利用未被保护的内存突破GS
详见第10章实验
覆盖虚函数突破GS
详见第10章实验
攻击异常处理突破GS
详见第10章实验
同时替换栈中和.data中的Cookie突破GS
详见第10章实验
SafeSEH
SafeSEH对异常处理的保护原理
在程序调用异常处理函数前,对要调用的处理函数进行一系列的有效性校验,当发现处理函数不可靠时将终止异常处理函数的调用。SafeSEH实现需要操作系统与编译器的双重支持,二者缺一都会降低SafeSEH的保护能力。
编译器在SafeSEH机制中所作的工作。通过琼瑶那个/SafeSEH连接选项可以让编译好的程序具有SafeSEH功能。这一连接选项在VS2003及后续版本中时默认启用的。启用该选项后,编译器在编译程序的时候将程序所有的异常处理函数地址提取出来,编入一张安全S.E.H表,并将这张表放到函数的映像里。当程序调用异常处理函数的时候会将函数地址与安全S.E.H表进行匹配,检查调用的异常处理函数是否位于安全S.E.H表中。
SafeSEH保护措施:
- 检查异常处理链是否位于当前程序的栈中,如果不在当前的栈中,程序将终止异常处理函数的调用。
- 检查异常处理函数指针是否指向当前程序的栈中。如果指向当前栈中,程序将终止异常处理函数的调用。
- 在前两项检查通过后,程序调用一个全新的函数RtlIsValidHandler(),来对异常处理函数的有效性进行验证。
RtlIsValidHandler:
首先,该函数判断异常处理函数地址是不是在加载模块的内存空间,如果属于加载模块的内存空间,校验函数将依次进行如下校验。
- 判断程序是否设置了IMAGE_DLLCHARCTERISTICS_NO_SEH。如果设置了这个标识,这个程序内的异常会被忽略,所以当这个标志被设置时,函数直接返回校验失败。
- 检查程序是否包含安全S.E.H表。如果程序包含安全S.E.H表,则将当前的异常处理函数地址与该表进行匹配,匹配成功返回校验成功,匹配失败返回校验失败。
- 判断程序是否有ILonly标识,如果设置了这个标识,说明该程序只包含.NET编译人中间语言,函数直接返回失败。
- 判断异常处理函数地址是否位于不可执行页(non-executable-page)上,当异常处理函数地址位于不可执行页上时,校验函数检查DEP是否开启,如果系统未开启DEP则返回校验成功,开启了则返回校验失败。
如果异常处理函数的地址没有包含在加载模块的内存空间,校验函数直接进行DEP相关检测,函数依次进行如下校验。
- 判断异常处理函数地址是否位于不可执行页(non-executable-page)上,当异常处理函数地址位于不可执行页上时,校验函数检查DEP是否开启,如果系统未开启DEP则返回校验成功,开启了则返回校验失败。
- 判断系统是否允许跳转到加载模块的内存空间外执行,如果允许返回校验成功,否则返回校验失败。
RtlIsValidHandler函数允许异常处理函数执行的情况:
- 异常处理函数位于加载模块内存之外,DEP关闭
- 异常处理函数位于加载模块内存范围之内,相应模块没有启用SafeSEH(安全SEH表为空),同时相应模块不是纯IL。
- 异常处理函数位于加载模块内存范围之内,相应模块启用SafeSEH,异常处理函数包含在安全SEH表中。
第二种情况中,我们可以利用未启用SEH的模块中的指令作为跳板,跳转到shellcode中。
如果S.E.H中的异常处理函数指针指向堆区,即使安全校验发现了SEH已经不可信,仍然会调用其已被修改过的异常处理函数,因此只要将shellcode不知道堆区就可以执行了。
从堆中绕过SafeSEH
详见第11章实验。
利用未启用SafeSEH的模块绕过SafeSEH
详见第11章实验。
利用加载模块之外的地址绕过SafeSEH
详见第11章实验。
数据与程序的分水岭:DEP
DEP机制的保护原理
DEP的基本原理是将数据所在的内存页标识为不可知性。当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,这时候cpu就会抛出异常,而不是去执行恶意指令。
DEP主要的作用是组织数据页(如默认的堆页,各种堆栈页以及内存池页)执行代码。根据实现的机制不同可以分为:软件DEP,硬件DEP。
软件DEP其实就是我们之前介绍的SafeSEH,它的目的时阻止利用S.E.H的的攻击,这种机制和硬件无关。
硬件DEP才是真正意义上的DEP,需要CPU的支持。AMD称之为No-Exeucute Page-Protection(NX),Intel称之为Execute Disable Bit(XD)。
操作系统通过设置内存页的NX/XD属性,来指明不能从该内存页执行代码。为了这个功能,需要在内存的页面表中加入一个标识位来标识是否允许在当前页面上执行指令。设置0表示允许。
连接选项中有一个和DEP密切相关的,/NXCOMPAT,这是vs2005及后续版本引入的一个连接选项,默认情况下时开启的。
采用/NXCOMPAT选项编译到程序会在文件的PE头中设置IMAGE_DLLCHARACTERTICS_NX_COMPAT标识,该标识通过结构体IMAGE_OPTIONAL_HEADER中的DllCharacteristics变量进行体现,当Dllcharactertics被设置为0x0100表示该程序采用了/NXCOMPAT编译。
用户的操作系统中的DEP一般工作在Optin状态,此时DEP只保护系统的核心进程,对于普通的程序是没有保护的。经过/NXCOMPAT编译的程序在windows vista及后续版本的操作系统上会自动启用DEP保护。
攻击未启用DEP的程序
DEP保护的对象是进程级的,当某个进程的加载模块只要有一个模块不支持DEP,这个进程就不能贸然开启DEP,否则可能发生异常。
利用Ret2Libc挑战DEP
在DEP保护下溢出失败的根本原因是DEP检测到程序跳转到非可执行页面下的指令了,如果跳转到系统函数则不会拦截。
Ret2Libc是Return-to-libc缩写。我们只需要不停的在代码区找到shellcode的替代指令,就能完成的想要的功能但是这也遇到的问题很多。以下是改进后的三种方法。
- 通过跳转到ZwSetInformationProcess函数将DEP关闭
- 通过跳转到VirtualProtect函数来讲shellcode所在的内存页设置为可执行状态
- 通过跳转到VirtualAlloc开辟一段有执行权限的内存空间,随后将shellcode复制到其中。
实战之利用ZwSetInformationProcess
详细见12章实验
实战之利用VirtualProtect
详细见12章实验
实战之利用VirtualAlloc
详细见12章实验
利用可执行内存挑战DEP
详细见12章实验
利用.NET挑战DEP
暂时没做
利用 Java applet挑战DEP
暂时没做
在内存中躲猫猫 ASLR
内存中随机化保护机制
ASLR(Address Space Layout Randomization)技术就是通过加载程序的时候不再使用固定的基址加载(这里应该指的是PE加载器的加载过程 也就是虚拟内存而不是物理内存)从而干扰shellcode定位的一种保护机制。
与SafeSEH类似的ASLR也需要程序和操作系统的双重支持,其中程序的支持不是必须的。
支持ASLR的程序在它的PE头会设置IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE标识来说明其支持ASLR。微软从vs2005 sp1开始加入了/dynamicbase连接选项帮我们来完成这个任务。
ASLR在windows vista后的操作系统才真正发挥作用。它包含了映像随机化,堆栈随机化,PEB与TEB随机化。
映像随机化
影响随机化是指PE文件映射到虚拟内存时,对其加载的虚拟地址进行随机化处理,这个地址是在系统启动后确定的,系统重启后这个地址变化。我们以IE为例
用户可以通过设置注册表中HKEY_LOCAL_MACHINES\SYSTEM\CurrentControlSet\Control\Session Manager Memory Mangement\MoveImage的键值来设定映像随机化的工作模式。
- 设置为0的时候禁用
- 设置为-1的时候强制对可随机化的映像进行处理,无论是否设置IMAGE_DLL_CHARACTERISTICS_BASE标识
- 设置为其他值的时候为正常工作模式,只对具有随机化处理标识的映像进行处理
堆栈随机化
这项措施时在程序运行时随机的选择堆栈的基址。与映像基址随机化不同的时堆栈不是在系统启动的时候确定的,而是在打开程序的时候确定的,也就是说同一个程序任意两次运行时的堆栈基址是不同的,进而各变量在内存中的位置也就是不确定的。
#include<stdio.h>
#include<stdlib.h>
int main()
{
char *heap = (char *)malloc(100);
char statk[100];
printf("address of heap:%0.4x\naddress of stack:%0.4x\n",heap,statk);
getchar();
return 0;
}
PEB与TEB随机化
PEB与TEB随机化在windwos xp sp2中就引入了。微软在xp sp2之后不再使用固定的PEB基址0x7FFDF000和TEB基址0x7FFDE000,而是使用具有一定随机性的基址,这就增加了攻击PEB中的函数中的函数指针的难度。
ASLR中的映像随机化,虽然其加载基址变化了但是其入口地址与基址的低位两个字节没有变化。变化的是高位字节也就是前两个字节。
ASLR中的堆栈随机化。这项措施将每个线程的堆栈地址都进行了随机化处理使得程序每次运行时变量的地址都不相同,但是由于jmp esp跳板指令开始使用后溢出时很少跳到shellcode中执行;另外在浏览器供给方面很流行的heap spray等技术,也不需要精确的跳转,只需要跳转到一个大概的位置。
PEB和TEB的随机化还是可以通过一些方法在程序运行时获取到。
攻击未启用ASLR的模块
使用flash暂时找不到低版本
利用部分覆盖进行定位内存地址
详细见第13章实验
利用Heap spray技术定位内存地址
暂时没写
利用Java applet heap spray技术定位内存地址
暂时没写
为.NET控件禁用ASLR
暂时没写
S.E.H的终极防护:SEHOP
SEHOP的原理
SEHOP(Structured Exception Handing Overwrite Protection),这是一种比SafeSEH更为严厉的保护机制。
SEHOP在Windows Server 2008默认启用,而在windows vista和windows7中SEHOP是默认关闭的。
各S.E.H函数是以单链表的形式存放在栈中的
链表末端是程序的默认异常处理。
SEHOP的核心任务是检查SEH链的完整性,在程序转入异常处理前SEHOP会检查SEH链上最后一个异常处理函数是否为系统固定的终极异常处理函数。如果没找到,就不会执行当前的异常处理函数。
SEHOP检查是在SafeSEH的RtlIsValidHandler函数校验之前进行的,也就是利用攻击加载模块之外的地址,堆地址,和魏琼用SafeSEH模块的方法都行不通了,必须要考虑其他的出路。理论上有三种方法:
- 不去攻击SEH
- 利用未启用SEHOP的模块
- 仿造SEH链
攻击返回地址
如果程序启用了SEH却没有启用GS,就可以攻击函数返回地址。
攻击虚函数
这个过程不设计异常处理
利用未启用SEHOP的模块
这个由于我找的win7不能安装vmtools,所以直接拿vista做的 详细请见实验
伪造SEH链表
依旧使用vista进行演示。详细请见实验。
重重保护下的堆
堆的保护机制原理
- PEB random:微软再windows xp sp2 之后不再使用固定的PEB基址0x7FFDF000,而是使用具有一定随机化的PEB基址。
- Safe Unlink:微软改写了操作双向李娜表的代码,在写在free list中的堆块时更加小心。
- 在XP SP2之前的链表拆卸操作类似以下代码:
- int remove(ListNode* Node)
{
node->blink->flink=node->flink;
node->flink->blink=node->blink;
} - 修改后增加了对堆块前向指针和后向指针的完整性:
- int sage_remove(ListNode* Node)
{
if((node->blink->flink==node)&&(node->flink->blink=node))
{
node->blink->flink=node->flink;
node->flink->blink=node->blink;
return 1;
}
else
{
进入异常
return 0;
}
} - heap cookie:和栈中的security cookie类似,微软在堆中也引入了cookie,用于检测堆溢出的发生。cookie被布置在堆首部分原堆块segment table的位置,占1字节
- 元数据加密:微软在windows vista及后续版本的操作系统中开始使用该安全措施。块首的一些重要数据在保存时会与一个4字节的随机数进行异或运算,在使用数据的时候再进行一次异或运算。
攻击堆中的变量
堆中的各项保护措施是对堆块而言的,对于堆中存储的内容是不保护的。
利用chunk重设大小攻击堆
详细见实验
利用Lookaside表进行堆溢出
详细见实验
文章评论