[内核编程] 内核编程之安全字符串
很多系统的安全问题是由于不好的缓冲处理而导致的缓冲区溢出而引起的。不好的缓冲区处理经常与字符串操作有关。标准的字符串处理函数是由C/C++语言运行时库提供的(例如:strcat、strcpy、sprintf等),这些函数都没有防止写操作会超过缓冲区实际长度。
两个新的字符串处理函数集,被称为安全字符串函数(safe string function),为不好的缓冲区处理代码提供了额外的处理。这些安全字符串函数存在于Windows Driver Kit(WDK)、微软针对Windows XP SP1以及之后版本操作系统的Driver Development Kit(DDK)以及Windows SDK之中。这些函数于原有的由Windows提供的内置于C/C++中的字符串函数相对应或类似。
其中一个安全字符串函数集是提供给内核模式代码使用的。这些函数的原型在Ntstrsafe.h头文件中定义。这个头文件和相关联的库由WDK提供。
另一个安全字符串函数集是提供给用户模式下的应用程序使用的。原型在Strsafe.h头文件中。这个头文件和相关联的库由Windows SDK提供。
其中内核模式下的安全字符串函数集由下列两个子集构成:
-
处理Unicode 和 ANSI 字符的安全字符串函数。
处理两个字节的Unicode字符的安全字符串函数一般以-W为后缀,而处理一个字节的ANSI字符的安全字符串函数一般以-A为后缀。例如,RtlStringCbCatN,这个函数用于连接两个字符串并且限制附加字符串的长度,针对字符类型由两个调用:RtlStringCbCatNW和RtlStringCbCatN。
-
处理UNICODE_STRING结构的安全字符串函数。
每个这样的函数会接收一个UNICODE_STRING结构作为一个输入或者输出参数,或者两者都有。例如:RtlStringCbCopyUnicodeString接收UNICODE_STRING结构作为输入参数,RtlUnicodeStringCopyString接收UNICODE_STRING结构作为一个输出参数,RtlUnicodeStringCopy接收UNICODE_STRING作为一个输入和输出参数。
内核模式下的安全字符串函数提供以下性能:
- 每个安全字符串函数需要目标缓冲区的大小作为输入参数。因此,这个函数可以保证在写的过程中不会操作缓冲区的结尾。
- Unicode和ANSI字符串函数将NULL字符作为所有输出字符串的结束标志,即使该操作会缩短期望的结果。
- 所有的安全字符串函数都会返回一个NTSTATUS值,其中只有一个值表示成功(STATUS_SUCCESS)。
- 大多安全字符串函数可同时支持以字节来计数和以字符来计数。例如,RtlStringCbCat连接两个以字节计数的字符串,RtlStringCchCat连接两个以字符计数的字符串。
- 大多安全字符串函数都提供扩展版,以-Ex为后缀的版本提供了额外的功能。例如,RtlStringCbCatEx扩充了RltStringCbCat的功能。
内核模式下安全字符串的摘要信息
下表是内核模式驱动下的安全字符串函数摘要信息,它还表明了安全字符串函数所代替的C/C++运行时库函数。如果一个函数名中包含Cb,则这个函数所处理的字符串是以字节计数的,若一个函数名中包含Cch,则这个函数所处理的字符串是以字符计数的。
Functions |
Purpose |
Replaces |
RtlStringCbCat |
连接两个字符串。 |
strcat |
RtlStringCbCatN |
连接两个以字节来计数的字符串,并限制附加字符串的大小。 |
strncat |
RtlStringCbCopy |
将一个字符串复制到缓冲区中。 |
strcpy |
RtlStringCbCopyN |
将一个字符串复制到缓冲区中,并限制被复制字符串的大小。 |
strncpy |
RtlStringCbLength |
计算字符串的长度。 |
strlen |
RtlStringCbPrintf |
创建一个格式化文本字符串,这基于一个格式字符串和多个额外的函数参数。 |
print |
RtlStringCbVPrintf |
创建一个格式化文本字符串,这基于一个格式字符串和一个额外的函数参数。 |
vsprintf |
RtlUnicodeStringInit |
初始化或验证一个UNICODE_STRING结构。 |
None |
导入内核模式安全字符串函数
内核模式安全字符串函数由以下两个部分支持:
- 作为内链代码,这被包含在入头文件Ntstrsafe.h中。
- 在一个你的代码可以被链接到的库中。
如果你的代码只是运行在Windows XP 以及其后版本的操作系统中的话,你应该使用内核模式安全字符串函数的内联版本。如果你的代码需要运行在Windows XP之前的操作系统上,那么你必须使用安全字符串的库版本。
使用内核模式安全字符串函数的内联版本
包含如下头文件:
#include <ntstrsafe.h>
使用内核模式安全字符函数的库版本
-
在包含新的头文件之前定义NTSTRSAFE_LIB,如下所示:
#define NTSTRSAFE_LIB
#include <ntstrsafe.h> - 在你工程的源文件中,为$(DDK_LIB_PATH)\ntstrsafe.lib.添加一个TARGETLIBS入口
只要你在源文件中包含了Ntstrsafe.h头文件,在C/C++运行时库函数中已经被Ntstrsafe.h代替的函数将不能使用。如果你使用了这些被废除的函数,编译器就会给出错误提示你要使用安全字符串函数。
类似的,若一个头文件中所提供的代码引用了一个或多个已经被废除的字符串,而这个头文件又在Ntstrsafe.h之后才包含进来的话,这些函数将会导致编译器给出错误信息。因此,Ntstrsafe.h应该在所有其他头文件都包含完毕之后再包含进来,也就是说放在Include的最后一行。
如果你想同时使用C/C++运行时库函数和安全字符串函数,那么就在包含Ntstrsafe.h头文件之前加入下面这行代码:
#define NTSTRSAFE_NO_DEPRECATE
你可以只提供以字节计数的或只以字符计数的安全字符串函数。
只提供以字节计数的函数
在包含Ntstrsafe.h头文件之前加入下面这行代码:
#define NTSTRSAFE_NO_CCH_FUNCTIONS
只支持以字符计数的函数
在包含Ntstrsafe.h头文件之前加入下面这行代码:
#define NTSTRSAFE_NO_CB_FUNCTIONS
你可以定义NTSTRSAFE_NO_CB_FUNCTIONS 或NTSTRSAFE_NO_CCH_FUNCTIONS其中一个,但不能都定义。
你可以不支持UNICODE_STRING结构的函数
不支持UNICODE_STRING结构的函数
在包含Ntstrsafe.h头文件之前加入下面这行代码:
#define NTSTRSAFE_NO_UNICODE_STRING_FUNCTIONS
任意一个ANSI或Unicode字符串的最大字符数存储在NTSTRSAFE_MAX_CCH中。UNICODE_STRING结构的最大字符数保存在NTSTRSAFE_UNICODE_MAX_CCH中。这些常量在被定义在Ntstrsafe.h中。
若你想重新指定NTSTRSAFE_MAX_CCH和NTSTRSAFE_UNICODE_STRING_MAX_CCH的值,注意这个值,必须小于原有的默认值。可以在包含Ntstrsafe.h之前加入下面的代码:
#define NTSTRSAFE_MAX_CCH <new-value>
#define NTSTRSAFE_UNICODE_STRING_MAX_CCH <new-value>
在Ntstrsafe.h中的指令将会验证你新设置的值不能大于原有的默认值。