
2.6.10 W^X
多种操作系统,包括OpenBSD、Windows、Linux和OS X,在内核强制减少权限,使得进程地址空间中的任何部分都不能同时既可写又可执行。这一策略被称为W xor X,或更为简洁的W ^ X,并通过使用多种CPU的不执行(No eXecute,NX)位来支持这种功能。
NX位使内存页可以标记为数据,以禁用这些页面上的代码的执行。该位在AMD的CPU上名为NX,在Intel CPU上名为XD(eXecute Disable,执行禁用),而在ARM版本6和更高版本的CPU上名为XN(eXecute Never,从不执行)。大部分最新的英特尔CPU和目前所有的AMD CPU都支持此功能。
W^X要求,不是程序本身的一部分的任何代码,都不可以执行。这可以防止执行栈、堆或数据段上的shellcode。W^X还可以防止故意在数据页执行代码。例如,一个即时(Just-In-Time,JIT)编译器通常从外部数据(如字节码)构造汇编代码,然后执行它。要在这样的环境中工作,JIT编译器必须符合这些限制,例如,确保包含可执行指令的页面被适当地标记。
数据执行保护。数据执行保护(Data Execution Prevention,DEP)是W^X策略在微软Visual Studio中的实现。DEP使用NX技术,以防止存储在数据段中的指令的执行。这个功能自XP Service Pack2以来一直在Windows中可用。DEP假设:不是程序本身的一部分的任何代码都不可执行。因此,它不妥善地处理在“禁止”页面中试图执行的代码。例如,JIT编译器通常从外部数据(如字节码)构造汇编代码,然后执行它,这种操作只能被DEP阻止。此外,DEP经常可以揭露隐藏在软件中的错误。
如果你的应用程序是针对Windows XP Service Pack3开发的,那么你应该调用SetProcessDEPPolicy()来强制执行DEP/NX。如果不清楚应用程序是否将运行在一个低级别的平台,包括对SetProcessDEPPolicy()的支持,请尽早在启动代码中调用以下代码。
01 BOOL __cdecl EnableNX(void) { 02 HMODULE hK = GetModuleHandleW(L"KERNEL32.DLL"); 03 BOOL (WINAPI *pfnSetDEP)(DWORD); 04 05 *(FARPROC *) &pfnSetDEP = 06 GetProcAddress(hK, "SetProcessDEPPolicy"); 07 if (pfnSetDEP) 08 return (*pfnSetDEP)(PROCESS_DEP_ENABLE); 09 return(FALSE); 10 }
如果你的应用程序包含自修改代码或执行JIT编译,那么DEP可能导致应用程序失败。为了缓解这个问题,你还是应该选择DEP(见下面的链接器开关)并把将用于JIT编译的任何数据标记如下。
01 PVOID pBuff = VirtualAlloc(NULL,4096,MEM_COMMIT,PAGE_READWRITE); 02 if (pBuff) { 03 // 复制可执行的汇编(ASM )代码至缓冲区 04 memcpy_s(pBuff, 4096); 05 06 // 缓冲区已就绪,因此把它标记为可执行并执行写保护 07 DWORD dwOldProtect = 0; 08 if (!VirtualProtect(pBuff,4096,PAGE_EXECUTE_READ,&dwOldProtect) 09 ) { 10 // 出错 11 } else { 12 // 调用pBuff 13 } 14 VirtualFree(pBuff,0,MEM_RELEASE); 15 }
DEP / NX对Windows的性能没有影响。要启用DEP,你应该把你的代码用/NXCOMPAT链接或调用SetProcessDEPPolicy()并在具有DEP功能的CPU上测试你的应用程序,然后记录并修复任何因使用DEP而导致的故障。在Vista或更高版本的Windows中,使用/NXCOMPAT类似于调用SetProcessDEPPolicy()。不过,Windows XP的装载器不能识别/NXCOMPAT链接选项。因此,使用SetProcessDEPPolicy()一般是首选的。
ASLR和DEP在Windows平台上提供不同的保护。因此,你应该对所有的二进制文件同时启用(/DYNAMICBASE和/NXCOMPAT)两种机制。