所以,决不是使用了GS编译选项就可以高枕无忧了。需要强调的是,GS编译选项并不是消除了程序的缓存溢出安全漏洞,而是试图在特定情况下,降低安全漏洞的危害程度。例如,即使GS编译选项可以防止恶意代码被远程执行,但是程序也会异常终止。如果该程序是一个重要的服务器进程,这就会是一个典型的DOS(Deny of Service)攻击。
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
… …
WORD DllCharacteristics;
… …
} IMAGE_OPTIONAL_HEADER,
*PIMAGE_OPTIONAL_HEADER;
DllCharacteristics
The DLL characteristics of the image. The following values are defined.
IMAGE_DLLCHARACTERISTICS_NX_COMPAT
0x0100 The image is compatible with data execution prevention (DEP).
下载Windows驱动程序开发包可以通过http://connect.microsoft.com/, 注册Microsoft Connect后选择Windows Logo Kit (WLK), Windows Driver Kit (WDK) and Windows Driver Framework (WDF)即可。具体的步骤这里就不详细叙述了。
安装好WDK后Prefast就已经直接在其开发环境下使用了。
2.3使用Prefast
在Visual Studio的团队版本中,使用Prefast,打开Project Properties --> Configuration Properties --> Code Analysis -->Enable Code Analysis For C/C++ on build。选择 Yes(/analyze)即可。具体可参见图1。
上面的介绍可以看出Prefast的使用操作并不难。成功应用Prefast的关键是要理解其输出的各类警告信息,并作出正确的评估和代码修改。文章前面我们提到过,Prefast检测的不仅仅是安全缺陷,但是安全缺陷类型是其检测的最为重要的部分。下面,我们就介绍一些和安全漏洞紧密相关的警告信息。以下给出的代码例子均来自于微软的文档Code Analysis for C/C++ Warnings【2】。
警告 C6001: using uninitialized memory <variable>,使用未初始化的变量。
例子:
#include "stdafx.h"
int f( bool b )
{
int i;
if ( b )
{
i = 0;
}
return i; // 当b为假时,变量i未初始化
}
大家也许会问,使用未初始化的变量会导致安全漏洞吗?这里我想特别强调的一点是,我们前面经常提到的安全漏洞是缓存溢出导致的,但是大家千万不要认为缓存溢出是导致安全漏洞的唯一原因。有各种各样的代码错误可以导致严重的安全漏洞,使用未初始化的变量也是其中的一种。关于未初始化的变量如何导致安全漏洞的详细信息超出了本文的范畴,有兴趣的读者可以参见微软SWI组的博客文章:MS08-014 : The Case of the Uninitialized Stack Variable Vulnerability【3】
修补:初始化变量。
警告C6029: possible buffer overrun in call to <function>: use of unchecked value,使用未验证的参数可能导致缓存溢出。
警告C6201: buffer overrun for <variable>, which is possibly stack allocated: index <name> is out of valid index range <min> to <max>,数组索引的越界可能导致缓存溢出。
例子:
void f()
{
int buff[25];
for (int i=0; i <= 25; i++) // i exceeds array bound
{
buff[i]=0; // initialize i
// code ...
}
}
修补:确保数组索引不越界。
警告C6202: buffer overrun for <variable>, which is possibly stack allocated, in call to <function>: length <size> exceeds buffer size <max>,使用缓存区时,给出的长度超出缓存区长度的最大值。
下面的表格【1,p246】里列出了两者之间的差异。采用何种方式应该根据具体情况而定。有时候也许只能采取其中一种方式:例如如果你的开发系统是Visual Studio 2003的话,就只能使用StrSafe。或者你的代码中有许多itoa的话,就考虑使用Safe CRT,因为StrSafe中没有提供简单的替代方式。有时候也许两者都可以。这种情况下,我个人是更喜欢采用StrSafe这种方式,因为它不依赖具体的动态库支持。如果是编写Win32上的程序的话,StrSafe的HRESULT的返回代码,也和Win32 API的代码类似,这样代码的整体风格可能会更加一致。
StrSafe
Safe CRT
发布方式
Web
Microsoft Visual Studio 2005
头文件
一个 (StrSafe.h)
多个 (不同的 C runtime 头文件)
是否提供链接库的版本
是
是
是否提供内嵌(Inline)版本
是
否
是否是业界标准
否
正在评估过程
Kernel Mode支持
是
否
返回类型
HRESULT (user mode) NTSTATUS (kernel mode)
随函数变化,errno_t
是否需要修改代码
是
是
主要针对
缓存溢出
缓存溢出,和其它安全方面的考虑
表3:StrSafe和Safe CRT对比
4.争论
在开发过程中,代码中全面禁用危险的API的编码实践,存在着一定的争议性。其中最具有代表性的观点可以参见Danny Kalev的Visual C++ 8.0 Hijacks the C++ Standard一文【4】。争论主要集中在以下几点。
#define MAX_CM_PATH 360
GetInstanceList(
IN LPCWSTR pszDevice, IN OUT LPWSTR *pBuffer, IN OUT PULONG pulLength)
{
WCHAR RegStr[MAX_CM_PATH], szInstance[MAX_DEVICE_ID_LEN];
...
// Validate that passed in pszDevice is an actual registry entry
// If lookup for the key fails, reject call and cleanup.
// ghEnumKey points to HKLM/System/CurrentControlSet/Enum
if (RegOpenKeyEx(ghEnumKey, pszDevice, 0,
KEY_ENUMERATE_SUB_KEYS, &hKey) != ERROR_SUCCESS) {
Status = CR_REGISTRY_ERROR;
goto Clean0;
}
...
ulLen = MAX_DEVICE_ID_LEN; // size in chars
...
// Query szInstance from registry
RegStatus = RegEnumKeyEx(hKey, ulIndex, szInstance, &ulLen, ...);
if (RegStatus == ERROR_SUCCESS) {
// Build lookup string given a valid registry root key and valid instance ID
wsprintf(RegStr, TEXT("%s//%s"), pszDevice, szInstance);}
使用ASLR非常简单。从Visual Studio 2005 SP1开始,增加了/dynamicbase链接选项。/dynamicbase选项可以通过Project Property -> Configuration Properties -> Linker -> Advanced -> Randomized Base Address,或直接修改linker的命令行编译选项即可。见下图。
图1:/dynamicbase链接选项配置
在Visual Studio 2008环境,用Win32 Console Application类型,编译链接演示程序。注意,如果使用Visual Studio 2005 SP1的话,需要将msvcr90.dll更改为msvcr80.dll。
如果程序没有使用ASLR功能的话,在Windows Vista下运行。输出的结果是:
Kernel32 loaded at 763F0000
Address of LoadLibrary = 7641361F
MSVCR90.dll loaded at 671F0000
Address of system function = 6721C88B
Address of function foo = 00401800
重启系统
Kernel32 loaded at 76320000
Address of LoadLibrary = 7634361F
MSVCR90.dll loaded at 6A340000
Address of system function = 6A36C88B
Address of function foo = 00401800
Kernel32 loaded at 763F0000
Address of LoadLibrary = 7641361F
MSVCR90.dll loaded at 671F0000
Address of system function = 6721C88B
Address of function foo = 003B1800
重启系统
Kernel32 loaded at 76320000
Address of LoadLibrary = 7634361F
MSVCR90.dll loaded at 697A0000
Address of system function = 697CC88B
Address of function foo = 00871800
ASLR安全特性在Windows Vista和其后的Windows版本(如Windows Server 2008)中实现。它可以防范基于Ret2libc方式的针对DEP的攻击。ASLR和DEP配合使用,能有效阻止攻击者在堆栈上运行恶意代码。建议开发人员使用/dynamicbase链接选项让开发的应用程序或动态链接库使用ASLR功能。