windows安全保护机制--DEP浅析

windows安全保护机制–DEP浅析

在温哥华Pwn2Own黑客大赛中,微软windows 7系统的ASLR技术和DEP技术都被选手成功绕过,但不可否认,ASLR和DEP确实是如微软IE小组经理所说,是“非常有效的保护机制”。

溢出攻击可谓是最常见的攻击手段之一,本质上溢出攻击都是利用了现代计算机体系结构上的设计缺陷,而重塑整个体系必然是不现实的,于是就有了各种各样的保护机制来弥补。在Windows XP SP2之前,利用代码(exp)会在分配的内存页执行,不需要检查内存保护常量(Memory Protection Constants)。例如,如果使用指定分配权限为PAGE_READWRITE 的VirtualAlloc ()函数分配了内存页, 则仍然可以从该内存页执行代码。从 windows XP SP2 和 windows Server 2003 SP1 开始, 如果 CPU 支持执行禁用 (XD) (针对Intel CPU)或不可执行 (NX)(针对AMD CPU) 位,则任何从被标记为PAGE_READWRITE(例)的内存页执行代码的行为,都将触发 STATUS_ACCESS_VIOLATION (0xC0000005) 访问冲突异常。而微软在windows xp sp2上开始提供的DEP,全称为数据执行保护,英文为Data Execution Prevention,则是一种更加有效的修补手段。

DEP根据其实现机制的不同分为硬件DEP和软件DEP,linux上的NX即为硬件DEP,而windows提供NX和SafeSEH(即为软件DEP)两种机制。一句话概括其功能:对于部分内存页不给予执行权限。

硬件DEP

硬件DEP的实现需要CPU的支持,其中AMD cpu提供了No-Execute Page-Protection(就是NX),而intel则是Execute Disable Bit(XD),可以在”我的电脑”->右键菜单”属性”->”高级”->”性能 - 设置”->”数据执行保护”或者是在C:\boot.ini来设置相关选项和查看cpu是否支持DEP,分为四种模式,一般计算机提供的是前两种:

  • optout模式,除了指定的程序以外其他的程序都使用
  • optin模式,只为windows的程序和服务开启

  • AlwaysOn模式,所有进程强制开启,仅支持64位(不建议开启,某些dll并不能开启)

  • AlwaysOff模式,所有进程强制关闭

硬件DEP的实现机制是操作系统通过设置内存页的NX/XD属性标记,来指明不能从该内存执行代码。为了实现这个功能,需要在内存的页面表(Page Table)中加入一个特殊的标识位(NX/XD)来标识是否允许在该页上执行指令。当该标识位设置为0里表示这个页面允许执行指令,设置为1时表示该页面不允许执行指令。在64位的操作系统中也就是页表项的第63bit,而在32位的操作系统中略显复杂,因为32位的pte并没有多余的bit可以存放这个标识位,所以要利用PAE进行扩展,在没有PAE的情况下32位的系统不支持硬件DEP

image-20190228225631962

那它在底层到底是如何实现的呢?我们可与通过VirtualProtect()这个api函数来深入探索,这个函数的具体功能就是改变指定的内存页的属性,该函数的调用链如下:

1
VirtualProtect() --> VirtualProtectEx() --> ZwProtectVirtualMemory()

接着通过sysenter进入内核,调用号为0x89,相应的调用是NtProtecVirtualMemory(),之后调用链如下

1
NtProtecVirtualMemory() --> MiProtectVirtualMemory() --> MiFlushTbAndCapture() --> KeInterlockedSwapPte()

注意,其中在MiProtectVirtualMemory()中,会计算出要改变其bit的内存页的PTE的地址,最后由KeInterlockedSwapPte()来改变指定PTE的属性。

来看看其汇编代码进行分析

1
2
3
4
5
6
7
8
9
10
nt!KeInterlockedSwapPte:
80541c08 53 push ebx
80541c09 56 push esi
80541c0a 8b5c2410 mov ebx,dword ptr [esp+10h] ;ebx为新的PTE的地址
80541c0e 8b74240c mov esi,dword ptr [esp+0Ch] ;esi为旧的PTE地址
80541c12 8b4b04 mov ecx,dword ptr [ebx+4] ;ecx为新PTE值高双字
80541c15 8b1b mov ebx,dword ptr [ebx] ;ebx为PTE值低双字
80541c17 8b5604 mov edx,dword ptr [esi+4] ;edx为旧PTE值高双字
80541c1a 8b06 mov eax,dword ptr[esi] ;eax为旧PTE值低双字
80541c1c 0fc70e cmpxchg8b qword ptr [esi];比较新旧是否相等,如果不想等用新的代替旧的

软件DEP

SafeSEH则是一种在软件层面实现的对SEH的保护机制,它需要操作系统和编译器的双重支持,在vs2013及以后的版本中会自动启用 /SafeSEH 链接选项来使用SafeSEH。也正是因为该项技术使得以往简单的覆盖异常处理句柄的漏洞利用几乎失效了

在加载PE文件时,SafeSEH将定位合法的SEH表的地址(如果该映像不支持SafeSEH的话则地址为0),然后是用共享内存中的一个随机数进行加密处理,程序中所有的异常处理函数的地址提取出来汇总放入SEH表,并将该表放入程序映像中,还会将将加密后的SEH函数表地址,IMAGE的开始地址,IMAGE的长度,合法SEH函数的个数,作为一条记录放入ntdll(ntdll模块是进行异常分发的模块)的加载模块数据内存中,每次调用异常处理函数时都会进行校验,只有二者一致才能够正常进行,该处理由RtlDispatchException() 开始,首先会经历两次检查,分别是:

  • 检查异常处理链是否在当前的栈中,不是则终止
  • 检查异常处理函数的指针是否指向栈,是则终止

通过两次检查后会调用RtlIsValidHandler() 来进行异常的有效性检查,08年的black hat给出了该函数的细节

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
BOOL RtlIsValidHandler( handler )
{
if (handler is in the loaded image) // 是否在loaded的空间内
{
if (image has set the IMAGE_DLLCHARACTERISTICS_NO_SEH flag) //是否设置了忽略异常
return FALSE;
if (image has a SafeSEH table) // 是否含有SEH表
if (handler found in the table) // 异常处理函数地址是否表中
return TRUE;
else
return FALSE;
if (image is a .NET assembly with the ILonl y flag set)
return FALSE;
}

if (handler is on non-executable page) // handler是否在不可执行页上
{
if (ExecuteDispatchEnable bit set in the process flags) //DEP是否开启
return TRUE;
else
raise ACCESS_VIOLATION;
}

if (handler is not in an image) // handler是否在未加载空间
{
if (ImageDispatchEnable bit set in the process flags) //设置的标志位是否允许
return TRUE;
else
return FALSE;
}
return TRUE; /s/ 允许执行异常处理函数
}

代码中的ExecuteDispatchEnable和ImageDispatchEnable位标志是内核KPROCESS结构的一部分,这两个位用来控制当异常处理函数在不可以执行内存或者不在异常模块的映像(IMAGE)内时,是否执行异常处理函数。这两个位的值可以在运行时修改,不过默认情况下如果进程的DEP被关闭,则这两个位置1,如果进程的DEP是开启状态,则这两个位被置0。

通过源码我们可以看出,RtlIsValidHandler() 函数只会在以下几种情况执行异常处理函数

  • 在进程的DEP是开启的情况下

    • 异常处理函数和进程映像的SafeSEH表匹配且没有NO_SEH标志。

    • 异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NET的ILonly标志。

  • 在进程的DEP关闭的情况下

    • 异常处理函数和进程映像的SafeSEH表匹配没有NO_SEH标志。

    • 异常处理函数在进程映像的可执行页,并且没有NO_SEH标志,没有SafeSEH表,没有.NET的ILonly标志。

    • 异常处理函数不在当前进程的映像里面,但是不在当前线程的堆栈上。

参考资料

看雪翻译小组 https://bbs.pediy.com/thread-226625.htm

safeSEH笔记http://pstgroup.blogspot.com/2007/08/tipssafeseh.html

DEP的保护原理http://netsecurity.51cto.com/art/201107/276497.htm