Unicode_String Ansi_String 内核字符串操作
2016-04-02 21:06
561 查看
typedef struct _UNICODE_STRING { USHORT Length;//字节数,不是字符数 一定要* sizeof(WCHAR) USHORT MaximumLength;//字节数,不是字符数 一定要* sizeof(WCHAR) PWSTR Buffer;//非零结尾,中间也可能含有零 } UNICODE_STRING, *PUNICODE_STRING;
//常用函数
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);
这些函数调用时系统会自动检测是否超过buffer 相对来说安全一些
#include <ntstrsafe.h> RtlUnicodeStringInit(&uStr1, str1); RtlUnicodeStringCopy(&uStr1, &uStr2); RtlUnicodeStringCat(&uStr1, &uStr2); #define NTSTRSAFE_UNICODE_STRING_MAX_CCH (0xffff / sizeof(wchar_t)) (32767个字符)
快速初始化一个UnicodeString字符串
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);
初始化方式:栈上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);
或者
UNICODE_STRING ustrTest = {0}; WCHAR szHello[512] = L”Hello, world!”; ustrTest.Buffer = szHello; ustrTest.Length = wcslen(L”hello,world!”)*sizeof(WCHAR); ustrTest.MaximumLength = sizeof(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);//这样释放就没有问题啦
下个这个演示了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常见问题
计算length的时候,少了*sizeof(WCHAR)
计算字符数的时候,少了/sizeof(WCHAR)
使用了wcscmp/wcscpy等函数操作
更长远的说,缓冲大小长度问题,就是驱动容易出错的问题
WCHAR wszPath[MAX_PATH];
MAX_PATH? MAX_PATH 在windows上是260 在linux是256
sizeof(wszPath)或者MAX_PATH*sizeof(WCHAR) 这才是字符长度
MAX_PATH*2? 也是可以的
使用Copy Append时目标的buffer不是自行分配的 必定失败 如下:
RtlInitUnicodeString(&uStr1, L"hello"); //直接将L"hello"字符串的指针赋给了uStr.Buffer; RtlInitUnicodeString(&uStr2, L"Goodbye"); DbgPrint("%ws\n", L"hello world"); DbgPrint("uStr1=%wZ\n", &uStr1); DbgPrint("uStr2=%wZ\n", &uStr2); RtlInitAnsiString(&aStr1, "Ansi to unicode"); DbgPrint("aStr1=%Z\n", &aStr1); RtlCopyUnicodeString(&uStr3, &uStr1); DbgPrint("uStr3=%wZ\n", &uStr3);//失败 RtlAppendUnicodeToString(&uStr1, L"world"); DbgPrint("uStr1=%wZ\n", &uStr1);//失败 RtlAppendUnicodeStringToString(&uStr1, &uStr2); DbgPrint("uStr1=%wZ\n", &uStr1);//失败
缓冲大小的判断
//处理应用层的read()函数
NTSTATUS DispatchRead ( IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; PVOID userBuffer = NULL; ULONG xferSize = 0; //获取IRP堆栈的当前位置 PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation( pIrp ); //获取传输的字节数和缓冲 xferSize = pIrpStack->Parameters.Read.Length; userBuffer = pIrp->AssociatedIrp.SystemBuffer; //从驱动中读数据 RtlCopyMemory( userBuffer, L"Hello, world", xferSize ); //填写IRP中的完成状态,结束IRP操作,不向下层发送 pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = xferSize; IoCompleteRequest( pIrp, IO_NO_INCREMENT ); return status; }
字符串本质上就是一段内存,之所以和内存使用分开讲,是因为内核里的字符串太有花
样了,细数下来竟然有 4 种字符串!这四种字符串,分别是:CHAR*、WCHAR*、ANSI_STRING、
UNICODE_STRING。当然,内核里使用频率最多的是 UNICODE_STRING,其次是 WCHAR*,再
次是 CHAR*,而 ANSI_STRING,则几乎没见过有什么内核函数使用。
但其实这四种字符串也不是完全独立的,ANSI_STRING可以看成是CHAR*的安全性扩展,
UNICODE_STRING 可以看成是 WCHAR*的安全性扩展。CHAR* , 可以 理解成 成 CHAR 数组, 本
质上来说 是一个地址, 它 的有效长度是多少?不知道 。 字符串 有多长?不清楚 , 遇到\0 就
当成是 字符串 的 结尾。综上所述,CHAR*的安全性是非常糟糕的,如果内核使用 CHAR*作为
主要字符串,那么会非常不稳定。WCHAR*和 和 CHAR* 类似 ,和 和 CHAR* 的唯一不同在于 一个
WCHAR 占 占 2 个字节,而一个 CHAR 只占 1 个字节。所以,WCHAR* 的安全性也是非常糟糕
的。微软为了安全性着想,推出了这两种字符串的扩展集,ANSI_STRING和UNICODE_STRING。
ANSI_STRING 和 UNICODE_STRING 都是结构体,定义如下:
typedef struct _ANSI_STRING { USHORT Length; USHORT MaximumLength; PCHAR Buffer; } ANSI_STRING, *PANSI_STRING; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWCHAR Buffer; } UNICODE_STRING, *PUNICODE_STRING;
可以看到,ANSI_STRING 和 UNICODE_STRING 的结构体大小是一样的,唯一不同在于第
三个成员,他们分别对应 CHAR*和 WCHAR*。它们的第一和第二个成员分别代表字符串的长
度和内存的有效长度。比如 BUFFER 的长度是 260 字节,而 BUFFER 只是一个单词”FUCK”,
则 ANSI_STRING 的 Length=4、MaximumLength=260;UNICODE_STRING 的 Length=8、
MaximumLength=260。另外需要注意的是,CHAR*和 和 WCHAR* 都是以 0 结尾的 (CHAR*以 以 1
个\0 结尾 ,WCHAR*以 以 2 个\0 结尾 ),但 但 ANSI_STRING 和 和 UNICODE_STRING 不一定以 以 0 结
尾。 因为 有了长度的说明,就不需要用特殊标识符来表示结尾了。 。 有些自以为是 的人直接把
ANSI_STRING 或 或 UNICODE_STRING 的 的 BUFFER 当作字符串用,是极端错误的。
在内核里,大部分的 C 语言字符串函数都是可以用的,无论是宽版的还是窄版的。比如
内核里可以照样使用 strcpy 和 wcscpy,但某些字符串函数签名可能要加上一个下划线。比
如 strlwr 要写成_strlwr。ANSI_STRING 和 UNICODE_STRING 有一套专门的字符串函数(在此
页面:http://msdn.microsoft.com/en-us/library/windows/hardware/ff563638(v=vs.85).aspx),如
果需要查询怎么使用,就用浏览器打开,搜索 AnsiString 或者 UnicodeString,并点击进去查
看说明即可。
下面的来直张帆的书
下面举几个关于 UNICODE_STRING 的例子(以下代码借用了张帆写的示例,特此声明感
谢)。1. 字符串初始化、2. 字符串拷贝 、3. 字符串比较 、4. 字符串变大写 、5. 字符串与整型相
互转化 、6. ANSI_STRING 字符串与 UNICODE_STRING 字符串相互转换。
//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); }
下面三个来自TA
//UNICODE_STRINGz 转换为 CHAR* //输入 UNICODE_STRING 的指针,输出窄字符串,BUFFER 需要已经分配好空间 VOID UnicodeToChar(PUNICODE_STRING dst, char *src) { ANSI_STRING string; RtlUnicodeStringToAnsiString(&string,dst, TRUE); strcpy(src,string.Buffer); RtlFreeAnsiString(&string); } //WCHAR*转换为 CHAR* //输入宽字符串首地址,输出窄字符串,BUFFER 需要已经分配好空间 VOID WcharToChar(PWCHAR src, PCHAR dst) { UNICODE_STRING uString; ANSI_STRING aString; RtlInitUnicodeString(&uString,src); RtlUnicodeStringToAnsiString(&aString,&uString,TRUE); strcpy(dst,aString.Buffer); RtlFreeAnsiString(&aString); } //CHAR*转 WCHAR* //输入窄字符串首地址,输出宽字符串,BUFFER 需要已经分配好空间 VOID CharToWchar(PCHAR src, PWCHAR dst) { UNICODE_STRING uString; ANSI_STRING aString; RtlInitAnsiString(&aString,src); RtlAnsiStringToUnicodeString(&uString,&aString,TRUE); wcscpy(dst,uString.Buffer); RtlFreeUnicodeString(&uString); }
CharToUnicode(CHAR*转 UNICODE_STRING)函数怎么写?
//待定
VOID CharToUnicode(PCHAR src,PUNICODE_STRING dst) { ANSI_STRING aString; RtlInitAnsiString(&aString,src); RtlAnsiStringToUnicodeString(dst,&aString,FALSE); }
下面还有用于安全开发的,转来的
一、前言
大量的系统安全问题是由于薄弱的缓冲处理以及由此产生的缓冲区溢出造成的,而薄弱的缓冲区处理常常与字符串操作相关。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.
三、内核模式安全字符串函数概述
下表概述了可以在内核驱动中使用的安全字符串函数,并指明了它们用来何种类型的c/c++运行库函数。
说明:
函数名含有Cb的是以字节数为单位,含有Cch的是以字符数为单位。
函数名 | 作用 | 取代 |
RtlStringCbCat RtlStringCbCatEx RtlStringCchCat RtlStringCchCatEx | 将源字符串连接到目的字符串的末尾 | strcat wcscat |
RtlStringCbCatN RtlStringCbCatNEx RtlStringCchCatN RtlStringCchCatNEx | 将源字符串指定数目的字符连接到目的字符串的末尾 | strncat wcsncat |
RtlStringCbCopy RtlStringCbCopyEx RtlStringCchCopy RtlStringCchCopyEx | 将源字符串拷贝到目的字符串 | strcpy wcscpy |
RtlStringCbCopyN RtlStringCbCopyNEx RtlStringCchCopyN RtlStringCchCopyNEx | 将源字符串指定数目的字符拷贝到目的字符串 | strncpy wcsncpy |
RtlStringCbLength RtlStringCchLength | 确定字符串的长度 | strlen wcslen |
RtlStringCbPrintf RtlStringCbPrintfEx RtlStringCchPrintf RtlStringCchPrintfEx | 格式化输出 | sprintf swprintf _snprintf _snwprintf |
RtlStringCbVPrintf RtlStringCbVPrintfEx RtlStringCchVPrintf RtlStringCchVPrintfEx | 可变格式化输出 | vsprintf vswprintf _vsnprintf _vsnwprintf |
相关文章推荐
- IOS中的编码规范
- # 20145210 《Java程序设计》第05周学习总结
- 第六周——进程描述和进程控制
- 基于SQL和PYTHON的数据库数据查询select语句
- Scrapy教程1--7
- xcode新建类设置前缀
- CA Loves Stick(BestCoder Round #78 (div.2))
- UVALive-6656-Watching the Kangaroo(二分)
- 计算机病毒实践总结一:简单静态分析
- Java - 多线程通信
- 深度探索C++对象模型(1)
- 第三周项目四(2)-换分币
- Leetcode 94. Binary Tree Inorder Traversal(python)
- 203. Remove Linked List Elements
- Ubuntu 14.04 LTS 安装 OpenCV2.4.12
- HTTP状态码大全
- HDU 1520 Anniversary party
- TimesTen的列压缩功能
- April Fools Day Contest 2016 D. Rosetta Problem
- POJ 1700 过河坐船最短时间问题