作者: 陆麟
转载请征得作者同意.
2004.10.22
“数据执行保护“的奥秘
lu0
Webmaster
of Inside Programming
http://lu0.126.com
2004-10-5
转载请注出处
Microsoft在WINDOWS XP
SP2中增加的”数据执行保护”是
令人感兴趣的安全措施之一. 本文研究的是如何进行”数
据执行保护”。
首先,在讨论数据执行保护前,我们看看是什么导致所谓”数
据执行保护”的概念得以实现。 最先出现能够使得”数
据执行保护”概念得到实现的是AMD64架
构的CPU。
AMD64是一个
架构。在这个架构下,支持64位操作数,虚拟地址空间也被扩展到了64位。AMD64为64位的操作增加一个名为Long
Mode的CPU运作模式。在Long
Mode下,划分了2种执行模式出来:一种被称为64-Bit Mode。一种被称为Compatibility
Mode。32BIT的CPU支
持的32位、16位保护模式和实模式则被称
为Legacy Mode. 从AMD64
Architecture Programming
Manual中有下表描述AMD64支持的各种模式。
既然操作模式有了加强,地址转换机制也需要顺路进行加强。分页
机制在Long Mode下有2种可能性。 1种是1个PAGE为4K,另外1种为1PAGE为2M。4K PAGE的地址转换由4级页表转换得来。而2M PAGE的地址转换由3级页表转换得来。
看看4K的
页是怎么回事情。
把地址划分为6组。Bit48-63是Bit 47的符号扩展,不在转换
中起作用。为什么要这么设计要问AMD。Bit39-47是Page
Map Level 4(PML4) Index,Bit30-38是Page Directory Pointer(PDP) Index,Bit21-29是Page Directory(PD)
Index,Bit12-20是Page
Table (PT) Index。转换过程与”数据执行保护”无关,本文中不予介绍。有兴趣的朋友可以自行参考AMD64
Architecture
Programmer Manual,但是各表的Entry却和”数据执行保护”大大相关。因此是一定要介绍的。下面
是各表的Entry格式。
Long
Mode 4K
PML4E (Level 4)
Long
Mode 4K
PDPE (Level 3)
Long
Mode 4K
PDE (Level 2)
Long
Mode 4K
PTE (Level 1)
2M的PAGE则是3级转换得出物理地址位置。
把地址划分为5组。Bit48-63是Bit 47的符号扩展,不在转换
中起作用。Bit39-47是Page Map
Level 4(PML4),Bit30-38是Page Directory Pointer(PDP),Bit21-29是Page Directory(PD)。省略了Page Table。
Long
Mode 2M
PML4E (Level 3)
Long
Mode 2M
PDPE (Level 1)
Long
Mode 2M
PDE (Level 1)
Page Map Level 4(PML4),Page
Directory Pointer(PDP) Index,Page
Directory(PD) Index, Page Table (PT) Index都
是以表的形式存在。每个表容纳一定数量的Entry。我们可以看到在各个表的Entry都会有1个NX Bit,位于Bit 63。其他的Bit我们暂不关心,这个NX bit则是这次”数据执行保护”的关键所在。
No
Execute
(NX) Bit.,在各Entry的中的Bit63决定当对应的项映射到虚拟内存中,对应的地址是否可以是代码并
且被执行。当NX位被设定为1,代码不可以从对应的虚拟地址执行。如果NT位被设定为0,代码可以从对应的地址执行。
NX是扩展功能,通常在某个特殊寄存器的某个Bit会有一个标志,来起用NX功能。这个Bit就是No-Execute
Enable (NXE) Bit。在Extended
Feature Enable Register(EFER)寄存器的Bit11。EFER寄存器是Model
Specific Register(MSR)的其中一个。EFER位于MSR的C0000080H处。可以通过特权指令RDMSR/WMSR来进行读写。当NXE为1时,NX功能就启动了。所谓”数据执行保护”也
就被启动出来。在启动NX功能前,必须通过CPUID指
令来确定NX功能确实存在。CPUID指令
的Function 80000001H用于检查CPU的Signature和AMD扩展功能。可用来检查NX功能是否存在。AMD扩展的CPUID指令可以在AMD64 Architecture
Programmer Manual Vol3中得到详尽参考。
当NXE,也就是EFER的Bit11被置为1前,NX Bit被认为是保留位。如果将NX Bit置1则会导致一个Page
Fault Exception。只
有将NXE置为1后,NX位才开始发挥作用。当NX发生作用时,如果每次执行指令时都去检查NX Bit,恐怕CPU的效率不会很好。因此,NX Bit只在指令TLB(TLB是虚拟地址和物理地址转换用的CACHE)加载的时候才会被检查。CPU在指令预取时,如果没有命中指令TLB,就需要尝试将虚拟地址和物理地址转换加载到TLB。此时,如果NX Bit被检查到为1,就引发一个Page
Fault exception。而
且无论是否是RING0,这项检查都无法逃过。
有一个小小的地方需要注意,由于每个表Entry都有NX位。因此不同表Entry的能控制的NX范围也不同,比如PTE中的NX只能控制对应的PAGE是否NX。而PDE的NX位则作用于PDE所指向的整个PT。当PDE的NX位为1,无论对应的PTE是否NX被置位,对应的页面就是NX,不可执行。
上面讲到的都是Long
Mode的NX。AMD在设计CPU时,同样将NX设计到了Legacy
Mode中。但是Legacy
Mode的情况有些特殊,32BIT的PTE,PDE需要兼容INTEL的IA32的PTE,PDE。而且IA32的PTE,PDE并没有什么空闲的地方可以插入NX位。有什么地方能动脑筋一下的呢?嘿嘿,从PENTIUM
PRO开始,INTEL定义了一个名为Physical
Address Extension(PAE)的扩展模式。在这个模式下,能够使用到36Bit的物理地址。能动脑筋的恐怕就是Physical
Address Extension(PAE)起用后的PDPE,PDE,PTE了。在PAE起用后,PDPE,PDE,PTE被扩展为64Bit. 由于PAE只用36BIT的地址空间,Bit63到可以占用一下。INTEL在PAE中规定了4K和2M这2种页面格式,那AMD看来也必须兼容4K和2M这2种格式的页面。AMD在设计Legacy
Mode的NX功能时,不象Long
Mode那么全面。并没有在所有的级
别都设定了NX Bit。
首先看看Legacy
Mode情况下的4K页面。
虚拟地址分成4部分。Bit30-31是Page
Directory Pointer(PDP),Bit21-29是Page
Directory Index,Bit 12-20是Page
Table(PT) Index。
Legacy
mode
PAE 4K PDPE
Legacy
mode
PAE 4K PDE
Legacy
mode
PAE 4K PTE
我们可以看到在Legacy
PAE Mode下,4K的页面模式,只有PTE和PDE能控制NX。而PDPE则无法控制NX。
再看看Legacy
Mode情况下的2M页面。
虚拟地址分成3部分。Bit30-31是Page
Directory Pointer(PDP),Bit21-29是Page
Directory Index。
Legacy
mode
PAE 2M PDPE
Legacy
mode
PAE 2M PDE
在Legacy
PAE Mode下,2M的页面模式,只有PDE能控制NX。而PDPE同样无法控制NX。
好了,硬件上的AMD64基本规格就是这样。自从AMD64推出这样的规格后。INTEL也开始走“兼容”路线。INTEL称为EM64T,NX规格的兼容是EM64T的一部分。
NX规格可以使得某片区域的RAM无法被执行。但是又不影响读写,这样的区域很符合HEAP和STACK的特征。通常HEAP和STACK是只能被读写,不被用来生成可执行代码的。这个很不错的特性被WINDOWS
XP SP2所利用。就是所谓的”数据执行保护”了。
但是”数据执行保护”有其局限性,因为”数据执行保护”归根结底是个CPU提供的硬件保护措施,如果CPU不支持NX特性,那么”数据执行保护”就无从谈起。目前大家手头的机器基本是没有机会享用这个”高
级”特性了。
那么在现实情况下,有了NX特性可以预防什么情况的发生呢?网络入侵中有一种常见入侵就是
堆栈溢出,有些病毒和蠕虫也利用堆栈溢出来获得操作机会。NX规格对这些情况十分适用。可以阻止病毒,蠕虫以及入侵的代码被
执行。通常情况下应用程序在STACK被破坏以后没有能力继续运行,但是OS却有了机会向应用发出一个ACCESS
VIOLATION的异常。一个编写
良好的应用通过SHE能获得出错地址,并且利用这点协助分析自己代码的问题所在。
在WINDOWS
XP SP2以及其他能支持NX功能的WINDOWS
OS下如何识别是因为”数据执行保护”引
发的异常呢?这是个问题。当”数据执行保护”引
发异常后,应用程序通过SEH捕捉到的异常是STATUS_ACCESS_VIOLATION,
异常信息的第一个参数是异常类型。当检查到这个值为8时,代表这个违例是由于试图在NX的区域执行代码所导致的。如果是在KERNEL MODE(比
如驱动程序中)触发”数据执行保护”异常,就会引发一个兰屏。BugCheck代码是0xfc。这就要求各个驱动厂商必须更仔细检查自己的代码稳定性。以前那些由于STACK
OVERFLOW引发的漏洞十分隐蔽,兰屏时很难区分真正BUG所在,现在这样的BUG就比较容易被抓到。骂不到Microsoft了。
”数据执行保护”对
一些应用提出的新的要求。比如可执行文件的壳程序。启动了”数据执行保护”后,正常情况下通过malloc/HeapAlloc获
得的RAM,则是不可执行的。意味着当壳解码真正执行内容时,如果是解码到malloc/HeapAlloc中获得的空间,那代码会无法执行起来。必须在执行目标代码前通过VirtualProtectEx设定页面的属性为PAGE_EXECUTE/PAGE_EXECUTE_WRITE_COPY/PAGE_EXECUTE_READ,
或者PAGE_EXECUTE_READWRITE。这对可执行文件的壳作者提出了新的挑战。如果
壳代码不顾一切,先将所有的的页面全部设定为PAGE_EXECUTE_READWRITE,那么
就彻底破坏了NX的原有保护功能。在壳代码所在的进程范围内无法阻止病毒蠕虫和恶意入侵。如果那个
壳装的是SERVERICE。那个SERVICE如
果存在缓冲区溢出漏洞则仍然会将系统暴露给攻击者。NX带来的好处将全部消失。那样的话一个糟糕的
应用加上糟糕的壳会是更糟糕的产品。
在WINDOWS
XP SP2以及其他能支持NX的WINDOWS
OS下,有时候由于NX的
存在导致的问题会严重影响系统。想象一下下面的状况:某些PROJECT受到限制,购买了其他厂商
的SDK,其中有一部分代码是以C文件的形
式存在,厂商在提供代码时考虑到代码的知识产权的问题,没有提供真正的源代码,而是在某一个C文件
里面写一个BYTE ARRAY,把代码的2进
制值写到了BYTE ARRAY里面。原先在没有NX的
情况下勉强是跑起来了。但是一旦起用了NX功能,应用就崩溃了,经过几次人事更替后,新来的员工根
本无法断定问题所在部件。真不是普通的糟糕。现在仍然有最后一招。Boot.ini为NX功能新增了2个开关。/NOEXECUTE和/EXECUTE。/NOEXECUTE用来起用NX功能。而/EXECUTE则关闭了NX功能。在产品一时无法定
位问题所在时,可以用这个救急。但是普通用户就要注意了。如果安装了某个应用后,您发现Boot.ini中
出现了/EXECUTE参数,说明您安装的那个软件是个很糟糕的软件,应该要注意一下。如果有替代
产品的话就用替代产品顶上。不要让糟糕的软件破坏系统整体安全性。