typedef struct _UNICODE_STRING {
USHORT Length;//字节数,不是字符数,有效的字节数。
USHORT MaximumLength;//字节数,告诉系统函数最多有多少内存可用
#ifdef MIDL_PASS
[size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
_Field_size_bytes_part_opt_(MaximumLength, Length) PWCH Buffer;//指针,非零结尾,中间可能含有零,PWSTR等价WCHAR*,所有wcscpy/wcscmp等操作就不可靠了。
#endif // MIDL_PASS
} UNICODE_STRING;
因为UNICODE_STRING uStr={0};这样定义个buffer是个NULL指针,所以没有内存,不能直接用,必须初始化。
初始化操作
用字符串常量初始化
DECLARE_CONST_UNICODE_STRING(ustrTest,L”Hello, world!”);
//这个等同于
UNICODE_STRING ustrTest = {0};
WCHAR *szHello = L”Hello, world!”;
RtlInitUnicodeString(&ustrTest, szHello);
//Length:wcslen(szHello)*sizeof(WCHAR)
//MaximumLength:(wcslen(szHello)+1)*sizeof(WCHAR);
因为UNICODE_STRING uStr={0};//无内存初始化
WCHAR *szHello=L"Hello,world!";
RTLInitUnicodeString(&ustrTest,szHello);//长度就被设置成了wcslen(szHello)*size(WCHAR);最大长度包含结尾也就是多两字节。
等价于宏 DECLARE_CONST_UNICODE_STRING(ustrTest,"Hello,world");
另外此时没有拷贝字符串,只是将buffer指过去。
用栈上buffer初始化
UNICODE_STRING ustrTest = {0};
WCHAR szHello[512] = L”Hello, world!”;
//Length:wcslen(szHello)*sizeof(WCHAR)
//MaximumLength:(wcslen(szHello)+1)*sizeof(WCHAR);
RtlInitUnicodeString(&ustrTest, szHello);
堆上buffer
UNICODE_STRING ustrTest = {0};
ULONG ulLength = (wcslen(L"Hello world") + 1)*sizeof(WCHAR);
ustrTest.Buffer = ExAllocatePoolWithTag(PagedPool, MAX_PATH*sizeof(WCHAR), 'POCU');
if (ustrTest.Buffer == NULL)
{
return;
}
RtlZeroMemory(ustrTest.Buffer, MAX_PATH*sizeof(WCHAR));
wcscpy(ustrTest.Buffer, L"Hello, world");//必须使用内存拷贝类函数
ustrTest.Length = ulLength;
ustrTest.MaximumLength = MAX_PATH*sizeof(WCHAR);
DbgPrint("%wZ\n", &ustrTest);
ExFreePool(ustrTest.Buffer);//这样释放就没有问题
//常用函数
RtlInitUnicodeString(&uStr1, str1);//注意 这个函数并不是将str1拷贝到uStr1.buffer 而是将buffer指向str1 所以如果这个buffer是程序员为它分配的 那么不能释放 也不能为它分配 一是BSOD 二是内存泄露
RtlCopyUnicodeString(&uStr1, &uStr2);//需要注意这里copy uStr1的buffer必须自己分配 如果后面跟着的是常量
RtlAppendUnicodeToString(&uStr1, str1);//注意 uStr1的buffer必须自己分配
RtlAppendUnicodeStringToString(&uStr1, &uStr2);//注意 uStr1的buffer必须自己分配
RtlCompareUnicodeString(&uStr1, &uStr2, TRUE/FALSE);//比较 TRUE 指定大小写
RtlAnsiStringToUnicodeString(&uStr1, &aStr1, TRUE/FALSE);// TRUE 指定buffer系统分配 如果是TRUE 则用后需要调用RtlFreeUnicodeString
RtlFreeUnicodeString(&uStr1);
这些函数调用时系统会自动溢出检测
#include <ntstrsafe.h>
RtlUnicodeStringInit(&uStr1, str1);
RtlUnicodeStringCopy(&uStr1, &uStr2);
RtlUnicodeStringCat(&uStr1, &uStr2);
#define NTSTRSAFE_UNICODE_STRING_MAX_CCH (0xffff / sizeof(wchar_t)) (32767个字符)
一个UnicodeString的一个蓝屏
UNICODE_STRING ustrTest = {0};
ustrTest.Buffer = ExAllocatePoolWithTag(PagedPool,
(wcslen(L"Nice to meet u") + 1)*sizeof(WCHAR), 'POCU');
if (ustrTest.Buffer == NULL)
{
return;
}
RtlZeroMemory(ustrTest.Buffer, (wcslen(L"Hello, world") + 1)*sizeof(WCHAR));
RtlInitUnicodeString(&ustrTest, L”Hello, world”);//这个时候userTest.buffer指向了hello wolrd静态常量区的地址 导致了内存泄露 在下面调用ExFree的时候还会蓝屏
DbgPrint("%wZ\n", & ustrTest);
ExFreePool(ustrTest.Buffer); //蓝屏 释放常量区内存
UNICODE_STRING常见的问题
UNICODE_STRING中length代表的是字节数,所以
计算length的时候,少了*sizeof(WCHAR),计算长度是字节数不是字符数
计算字符数的时候,少了/sizeof(WCHAR)
使用了wcscmp/wcscpy等函数操作,这个不是0结尾
更长远的说,缓冲大小长度问题,就是驱动容易出错的问题
MAX_PATH:260这里是字符数,所以一般初始化存放路径之类的要乘2
WCHAR wszPath[MAX_PATH];
MAX_PATH? MAX_PATH 在windows上是260 在linux是256,盘符,0结尾,:,斜线各占一个。
sizeof(wszPath)或者MAX_PATH*sizeof(WCHAR) 这才是字符长度
常用函数
//1.字符串初始化
VOID StringInitTest()
{
//(1)用 RtlInitAnsiString 初始化字符串
ANSI_STRING AnsiString1;
CHAR * string1= "hello";
//初始化 ANSI_STRING 字符串
RtlInitAnsiString(&AnsiString1,string1);
KdPrint(("AnsiString1:%Z\n",&AnsiString1));//打印 hello
string1[0]='H';
string1[1]='E';
string1[2]='L';
string1[3]='L';
string1[4]='O';
//改变 string1,AnsiString1 同样会导致变化
KdPrint(("AnsiString1:%Z\n",&AnsiString1));//打印 HELLO
//(2)程序员自己初始化字符串
#define BUFFER_SIZE 1024
UNICODE_STRING UnicodeString1 = {0};
//设置缓冲区大小
UnicodeString1.MaximumLength = BUFFER_SIZE;
//分配内存
UnicodeString1.Buffer = (PWSTR)ExAllocatePool(PagedPool,BUFFER_SIZE);
WCHAR* wideString = L"hello";
//设置字符长度,因为是宽字符,所以是字符长度的 2 倍
UnicodeString1.Length = 2*wcslen(wideString);
//保证缓冲区足够大,否则程序终止
ASSERT(UnicodeString1.MaximumLength>=UnicodeString1.Length);
//内存拷贝,
RtlCopyMemory(UnicodeString1.Buffer,wideString,UnicodeString1.Length);
//设置字符长度
UnicodeString1.Length = 2*wcslen(wideString);
KdPrint(("UnicodeString:%wZ\n",&UnicodeString1));
//清理内存
ExFreePool(UnicodeString1.Buffer);
UnicodeString1.Buffer = NULL;
UnicodeString1.Length = UnicodeString1.MaximumLength = 0;
}
//2.字符串拷贝
VOID StringCopyTest()
{
//初始化 UnicodeString1
UNICODE_STRING UnicodeString1;
RtlInitUnicodeString(&UnicodeString1,L"Hello World");
//初始化 UnicodeString2
UNICODE_STRING UnicodeString2={0};
UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool,BUFFER_SIZE);
UnicodeString2.MaximumLength = BUFFER_SIZE;
//将初始化 UnicodeString2 拷贝到 UnicodeString1
RtlCopyUnicodeString(&UnicodeString2,&UnicodeString1);
//分别显示 UnicodeString1 和 UnicodeString2
KdPrint(("UnicodeString1:%wZ\n",&UnicodeString1));
KdPrint(("UnicodeString2:%wZ\n",&UnicodeString2));
//销毁 UnicodeString2
//注意!!UnicodeString1 不用销毁
RtlFreeUnicodeString(&UnicodeString2);
}
//3.字符串比较
VOID StringCompareTest()
{
//初始化 UnicodeString1
UNICODE_STRING UnicodeString1;
RtlInitUnicodeString(&UnicodeString1,L"Hello World");
//初始化 UnicodeString2
UNICODE_STRING UnicodeString2;
RtlInitUnicodeString(&UnicodeString1,L"Hello");
if (RtlEqualUnicodeString(&UnicodeString1,&UnicodeString2,TRUE))
KdPrint(("UnicodeString1 and UnicodeString2 are equal\n"));
else
KdPrint(("UnicodeString1 and UnicodeString2 are NOT equal\n"));
}
//4.字符串变大写
VOID StringToUpperTest()
{
//初始化 UnicodeString1
UNICODE_STRING UnicodeString1;
UNICODE_STRING UnicodeString2;
RtlInitUnicodeString(&UnicodeString1,L"Hello World");
//变化前
DbgPrint("UnicodeString1:%wZ\n",&UnicodeString1);
//变大写
RtlUpcaseUnicodeString(&UnicodeString2,&UnicodeString1,TRUE);
//变化后
DbgPrint("UnicodeString2:%wZ\n",&UnicodeString2);
//销毁 UnicodeString2(UnicodeString1 不用销毁)
RtlFreeUnicodeString(&UnicodeString2);
}
//5.字符串与整型相互转化
VOID StringToIntegerTest()
{
//(1)字符串转换成数字
//初始化 UnicodeString1
UNICODE_STRING UnicodeString1;
RtlInitUnicodeString(&UnicodeString1,L"-100");
ULONG lNumber;
NTSTATUS nStatus = RtlUnicodeStringToInteger(&UnicodeString1,10,&lNumber);
if ( NT_SUCCESS(nStatus))
{
KdPrint(("Conver to integer succussfully!\n"));
KdPrint(("Result:%d\n",lNumber));
}
else
{
KdPrint(("Conver to integer unsuccessfully!\n"));
}
//(2)数字转换成字符串
//初始化 UnicodeString2
UNICODE_STRING UnicodeString2={0};
UnicodeString2.Buffer = (PWSTR)ExAllocatePool(PagedPool,BUFFER_SIZE);
UnicodeString2.MaximumLength = BUFFER_SIZE;
nStatus = RtlIntegerToUnicodeString(200,10,&UnicodeString2);
if ( NT_SUCCESS(nStatus))
{
KdPrint(("Conver to string succussfully!\n"));
KdPrint(("Result:%wZ\n",&UnicodeString2));
}
else
{
KdPrint(("Conver to string unsuccessfully!\n"));
}
//销毁 UnicodeString2
//注意!!UnicodeString1 不用销毁
RtlFreeUnicodeString(&UnicodeString2);
}
//6. ANSI_STRING 字符串与 UNICODE_STRING 字符串相互转换
VOID StringConverTest()
{
//(1)将 UNICODE_STRING 字符串转换成 ANSI_STRING 字符串
//初始化 UnicodeString1
UNICODE_STRING UnicodeString1;
RtlInitUnicodeString(&UnicodeString1,L"Hello World");
ANSI_STRING AnsiString1;
NTSTATUS nStatus = RtlUnicodeStringToAnsiString(&AnsiString1,&UnicodeString1,TRUE);
if ( NT_SUCCESS(nStatus))
{
KdPrint(("Conver succussfully!\n"));
KdPrint(("Result:%Z\n",&AnsiString1));
}
else
{
KdPrint(("Conver unsuccessfully!\n"));
}
//销毁 AnsiString1
RtlFreeAnsiString(&AnsiString1);
//(2)将 ANSI_STRING 字符串转换成 UNICODE_STRING 字符串
//初始化 AnsiString2
ANSI_STRING AnsiString2;
RtlInitString(&AnsiString2,"Hello World");
UNICODE_STRING UnicodeString2;
nStatus = RtlAnsiStringToUnicodeString(&UnicodeString2,&AnsiString2,TRUE);
if ( NT_SUCCESS(nStatus))
{
KdPrint(("Conver succussfully!\n"));
KdPrint(("Result:%wZ\n",&UnicodeString2));
}
else
{
KdPrint(("Conver unsuccessfully!\n"));
}
//销毁 UnicodeString2
RtlFreeUnicodeString(&UnicodeString2);
}
关于安全开发
大量的系统安全问题是由于薄弱的缓冲处理以及由此产生的缓冲区溢出造成的,而薄弱的缓冲区处理常常与字符串操作相关。c/c++语言运行库提供的标准字符串操作函数(strcpy, strcat, sprintf等)不能阻止在超出字符串尾端的写入。
基于Windows XP SP1以及随后的操作系统的Windows DDK版本提供了安全字符串函数(safe string functions)。这类函数被设计的目的是用来取代相同功能的c/c++标准函数和其它微软提供的库函数。这类函数具有以下特征:
每个函数以目标缓冲区所占的字节大小作为其一个输入参数,因此可以保证在写入时不会超出缓冲区末端。每个函数的输出字符串均以NULL结尾(null-terminate),即使该函数可能会对正确的结果进行截断。所有函数均有返回值,类型为NTSTATUS,只有返回STATUS_SUCCESS时,操作结果才正确。每个函数均有两种类型的版本,按字节或者按字符数。例如,RtlStringCbCatW和RtlStringCchCatW。每个函数均有支持双字节的unicode字符(以W作为后缀)和单字节的ANSI字符(以A作为后缀)的版本。例如:RtlStringCbCatW和RtlStringCbCatA。大部分函数有提供扩展版本的函数(以Ex作为后缀),例如,RtlStringCbCatW和RtlStringCbCatExW。
二、如何在内核驱动代码中引入安全字符串函数
有两种方式可以引入安全字符串函数:
l 以内联的方式引入,包含在ntstrsafe.h中
l 在链接时以库的方式引入
其中,如果代码需要在系统为Windows XP及以后版本运行时,可以使用内联的方式;如果代码需要运行在早于Windows XP时,则必须使用链接库的方式。
以内联方式引入
只需包含头文件即可
#include <ntstrsafe.h>
以链接库的方式
在包含头文件之前先定义宏#define NTSTRSAFE_LIB
#include <ntstrsafe.h>
在项目的sources文件中,添加一TARGETLIBS条目如下: $(DDK_LIB_PATH)\ntstrsafe.lib.在默认情况下,当引入了安全字符串函数后,那些被取代的c/c++运行库函数将变得无效,编译是会报错,提示需要使用安全字符串函数。
如果还希望继续使用c/c++运行库函数,即在使用安全字符串函数的时候,c/c++运行库函数还可以继续使用,则需要在包含ntstrsafe.h之前先定义宏NTSTRSAFE_NO_DEPRECATE
#define NTSTRSAFE_NO_DEPRECATE
The maximum number of characters that any ANSI or Unicode string can contain is STRSAFE_MAX_CCH. This constant is defined in ntstrsafe.h.
字符串最长长度为STRSAFE_MAX_CCH,该宏在ntstrsafe.h中定义。另外,如果一个字符串需要被转换成UNICODE_STRING结构,则该字符串长度不能超过65535.
函数名含有Cb的是以字节数为单位,含有Cch的是以字符数为单位。
另外:比较记得注意MaximumLength
参考资料
https://blog.csdn.net/zhuhuibeishadiao/article/details/51045446
https://www.cnblogs.com/qintangtao/archive/2013/04/15/3023092.html