大家好,我叫范翔。我是微软C++上海团队的一名软件开发工程师。
今天我想向大家介绍一下两个和安全性相关的链接器选项:/DYNAMICBASE和/NXCOMPAT。
这两个选项是在VS2005中引入的,目的是提高本机代码的安全性。
您可以在IDE中设置它们:
这两个选项在IDE中有三个可选值:On,Off和Default。
如果你是用VS2008的向导生成的C++工程,那么它们会被设置为“On”。
当VS2008从不支持这两个选项的老版本工程进行升级时 ,会将这两个选项设置为“Off”。
如果您选择“Default”,链接器的默认值是“Off”。
经过多年的推广,我们决定在VS2010中将“Default”对应的值改为“On”,以增强C++应用程序的安全性。
下面是对这两个选项的详细介绍:
1. DYNAMICBASE
/DYNAMICBASE使用 Windows Vista 的地址空间布局随机化 ASLR(Address Space Layout Randomization) 功能,指定是否生成可在加载时随机重新设定基址的可执行文件映像。
该选项隐含了“/FIXED:NO”,以便在可执行文件中生成重定位节。 具体请参见/FIXED中的说明。
默认情况下,如果某个组件需要 Windows Vista (/SUBSYSTEM 6.0和更高版本),则会自动打开 /DYNAMICBASE。
/DYNAMICBASE:NO可以用于禁用随机重新设定基址。
这篇文件对ASLR进行了介绍: http://technet.microsoft.com/zh-cn/magazine/cc162458.aspx
ASLR仅被Windows Vista和之后的操作系统支持,它会被之前的操作系统忽略。
ASLR对于应用程序来说是透明的。当开启ASLR的时候,唯一的区别是,操作系统将会无条件的对可执行文件重新设定基址。如果未开启ASLR,只有当发生地址冲突的时候,操作系统才会重新设定基址。
2. NXCOMPAT
/NXCOMPAT用于指定可执行文件与 Windows 数据执行保护功能DEP(Data Execution Prevention)兼容
请注意,该选项只适用于32位的可执行文件。非32位的桌面版Windows(比如x64和IA64)总是会为64位的应用程序开启DEP。
下面是对DEP的详尽介绍:
http://support.microsoft.com/kb/875352
默认情况下,如果某个组件需要 Windows Vista (/SUBSYSTEM 6.0和更高版本),则会自动打开 /NXCOMPAT。
/NXCOMPAT:NO可以用于指定应用程序和DEP不兼容。然而,管理员仍然可以强制为所有的应用程序开启DEP。因此,您总是应当测试您的程序和DEP的兼容性。
Windows Vista SP1、Windows XP SP3和Windows Server 2008增加了一个新的API SetProcessDEPPolicy。它允许开发人员在运行时而不是链接的时候设置程序的DEP策略。具体的信息请参考下面的链接:
http://blogs.msdn.com/michael_howard/archive/2008/01/29/new-nx-apis-added-to-windows-vista-sp1-windows-xp-sp3-and-windows-server-2008.aspx
下面列举了常见的和DEP不兼容的例子(更详细的信息请参见http://msdn.microsoft.com/en-us/library/aa366553.aspx)
a. 在堆上或者栈上动态生成代码
如果您的程序必须运行内存中的代码,存放代码的内存必须拥有正确的属性。
分配的内存必须被标记为PAGE_EXECUTE、PAGE_EXECUTE_READ、PAGE_EXECUTE_READWRITE或者PAGE_EXECUTE_WRITECOPY。使用new、malloc和HeapAlloc函数分配的内存是不能运行代码的。应用程序需要调用VirtualAlloc函数分配可以运行代码的内存。
那外一种方法是在使用HeapCreate创建堆的时候传入HEAP_CREATE_ENABLE_EXECUTE。这样之后用HeapAlloc分配的内存就可以运行代码了。
b. 在数据段中执行代码
您应当将代码放置在代码段
如果没有开启DEP,安全漏洞的破坏性会更大。所以您应当总是使您的程序和DEP兼容并开启DEP。
下面的例子展示了和DEP不兼容的代码。它同时展示了两种和DEP兼容的在堆上运行代码的方法。
在数据段或者栈上运行代码总是非常危险的,会被恶意用户利用产生安全漏洞。您应当将代码放在代码段或者堆上。
#include "windows.h"
#include <cstdio>
typedef void (*funType)();
unsigned char gCode[] = {0xC3}; // x86上的ret指令的机器码
const size_t gCodeSize = sizeof(gCode);
// 下面的代码和DEP不兼容
void RunCodeOnHeap()
{
unsigned char *code = new unsigned char[gCodeSize];
memcpy(code, gCode, gCodeSize);
funType fun = reinterpret_cast<funType>(code);
fun();
delete []code;
}
// 下面的代码和DEP兼容
void RunCodeOnHeapCompatible1()
{
unsigned char *code = (unsigned char *)::VirtualAlloc(NULL, gCodeSize, MEM_COMMIT, PAGE_READWRITE);
memcpy(code, gCode, gCodeSize);
DWORD flOldProtect;
::VirtualProtect(code, gCodeSize, PAGE_EXECUTE_READ, &flOldProtect);
funType fun = reinterpret_cast<funType>(code);
fun();
::VirtualFree(code, 0, MEM_RELEASE);
}
void RunCodeOnHeapCompatible2()
{
HANDLE hheap = ::HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
unsigned char *code = (unsigned char *)::HeapAlloc(hheap, 0, gCodeSize);
memcpy(code, gCode, gCodeSize);
funType fun = reinterpret_cast<funType>(code);
fun();
::HeapFree(hheap, 0, code);
::HeapDestroy(hheap);
}
INT DEPExceptionFilter(LPEXCEPTION_POINTERS lpInfo)
{
// 详细信息请参见 http://technet.microsoft.com/en-us/library/bb457155.aspx
if (lpInfo->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION &&
lpInfo->ExceptionRecord->ExceptionInformation[0] == 8) {
return EXCEPTION_EXECUTE_HANDLER;
}
return EXCEPTION_CONTINUE_SEARCH;
}
int main()
{
__try
{
RunCodeOnHeap();
printf("RunCodeOnHeap: OK\n");
}
__except (DEPExceptionFilter(GetExceptionInformation()))
{
printf("RunCodeOnHeap: Fail due to DEP\n");
}
__try
{
RunCodeOnHeapCompatible1();
printf("RunCodeOnHeapCompatible1: OK\n");
}
__except (DEPExceptionFilter(GetExceptionInformation()))
{
printf("RunCodeOnHeapCompatible1: Fail due to DEP\n");
}
__try
{
RunCodeOnHeapCompatible2();
printf("RunCodeOnHeapCompatible2: OK\n");
}
__except (DEPExceptionFilter(GetExceptionInformation()))
{
printf("RunCodeOnHeapCompatible2: Fail due to DEP\n");
}
}
输出:
cl test.cpp /link /nxcompat:no
RunCodeOnHeap: OK
RunCodeOnHeapCompatible1: OK
RunCodeOnHeapCompatible2: OK
cl test.cpp /link /nxcompat
RunCodeOnHeap: Fail due to DEP
RunCodeOnHeapCompatible1: OK
RunCodeOnHeapCompatible2: OK
综上,在VS2010以前, “cl test.cpp”等价于“cl test.cpp /link /nxcompat:no /dynamicbase:no”。在VS2010中,它将等价于“cl test.cpp /link /nxcompat /dynamicbase”。
如果您对于这两个选项的默认值的改动有任何意见或者建议,欢迎反馈。谢谢!