Win64 驱动内核编程-4.内核里操作字符串
内核里操作字符串
字符串本质上就是一段内存,之所以和内存使用分开讲,是因为内核里的字符串太有花
样了,细数下来竟然有 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 都是结构体,定义如下:
可以看到,ANSI_STRING 和 UNICODE_STRING 的结构体大小是一样的,唯一不同在于第三个成员,他们分别对应 CHAR*和 WCHAR*。它们的第一和第二个成员分别代表字符串的长度和内存的有效长度。比如 BUFFER 的长度是 260 字节,而 BUFFER 只是一个单词”WERT”,则 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,并点击进去查看说明即可。关于 CHAR*和 WCHAR*的字符串操作就不讲了,不懂的请看任意的 C 语言教程。下面举几个关于 UNICODE_STRING 的例子(以下代码借用了张帆写的示例,特此声明感谢)。1. 字符串初始化、2. 字符串拷贝 、3. 字符串比较 、4. 字符串变大写 、5. 字符串与整型相互转化 、6. ANSI_STRING 字符串与 UNICODE_STRING 字符串相互转换。
//1.字符串初始化
VOID StringInitTest()
{
//(1)用 RtlInitAnsiString 初始化字符串
ANSI_STRING AnsiString1;
CHAR string1[] = "hello";//此处注意,原版的这里面写的是CHAR *,这样的话会导致改值失败。
//初始化 ANSI_STRING 字符串
RtlInitAnsiString(&AnsiString1, string1);
DbgPrint("AnsiString1:%Z\n", &AnsiString1);//打印 hello
string1[0] = 'H';
string1[1] = 'E';
string1[2] = 'L';
string1[3] = 'L';
string1[4] = 'O';
//改变 string1,AnsiString1 同样会导致变化
DbgPrint("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);
DbgPrint("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
DbgPrint("UnicodeString1:%wZ\n", &UnicodeString1);
DbgPrint("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))
DbgPrint("UnicodeString1 and UnicodeString2 are equal\n");
else
DbgPrint("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))
{
DbgPrint("Conver to integer succussfully!\n");
DbgPrint("Result:%d\n", lNumber);
}
else
{
DbgPrint("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))
{
DbgPrint("Conver to string succussfully!\n");
DbgPrint("Result:%wZ\n", &UnicodeString2);
}
else
{
DbgPrint("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))
{
DbgPrint("Conver succussfully!\n");
DbgPrint("Result:%Z\n", &AnsiString1);
}
else
{
DbgPrint("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);
}
测试结果:
以上示例是我在很多书本的示例中精心挑选出来的,简单明了,当然能保证能测试成功。不过如果在实战环境下,因为操作字符串而导致蓝屏的还是非常多见的。根本原因只有两个:1.缓冲区长度溢出;2.操作的指针无效。所以大家以后在做项目时,遇到需要操作字符串的场景还是要格外当心。
最后再加上看的资料的那个作者自己写的三个函数,留着笔记:
//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);
}
Win64 驱动内核编程-4.内核里操作字符串的更多相关文章
- Win64 驱动内核编程-7.内核里操作进程
在内核里操作进程 在内核里操作进程,相信是很多对 WINDOWS 内核编程感兴趣的朋友第一个学习的知识点.但在这里,我要让大家失望了,在内核里操作进程没什么特别的,就标准方法而言,还是调用那几个和进程 ...
- Win64 驱动内核编程-5.内核里操作文件
内核里操作文件 RING0 操作文件和 RING3 操作文件在流程上没什么大的区别,也是"获得文件句柄->读/写/删/改->关闭文件句柄"的模式.当然了,只能用内核 A ...
- Win64 驱动内核编程-6.内核里操作注册表
内核里操作注册表 RING0 操作注册表和 RING3 的区别也不大,同样是"获得句柄->执行操作->关闭句柄"的模式,同样也只能使用内核 API 不能使用 WIN32 ...
- Win64 驱动内核编程-3.内核里使用内存
内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...
- Win64 驱动内核编程-8.内核里的其他常用
内核里的其他常用 1.遍历链表.内核里有很多数据结构,但它们并不是孤立的,内核使用双向链表把它们像糖 葫芦一样给串了起来.所以遍历双向链表能获得很多重要的内核数据.举个简单的例子,驱 动对象 Driv ...
- 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作
1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...
- Linux Shell编程(15)——操作字符串
Bash已经支持了令人惊讶的字符串操作的数量.不幸地,这些工具缺乏统一的标准.一些是参数替换的子集,其它受到UNIX的expr命令的功能的影响.这导致不一致的命令语法和冗余的功能,但这些并没有引起混乱 ...
- WIN64内核编程-的基础知识
WIN64内核编程基础班(作者:胡文亮) https://www.dbgpro.com/x64driver 我们先从一份"简历"说起: 姓名:X86或80x86 性别:? 出生 ...
- Win64 驱动内核编程-2.基本框架(安装.通讯.HelloWorld)
驱动安装,通讯,Hello World 开发驱动的简单流程是这样,开发驱动安装程序,开发驱动程序,然后安装程序(或者其他程序)通过通讯给驱动传命令,驱动接到之后进行解析并且执行,然后把执行结果返回. ...
随机推荐
- Spring的IOC常用注解(含源码)
一.容器中注入组件 1,包扫描 + 组件标注注解 源码:Demo01_ComponentScan a)组件标注 @Controller @Service @Repository @Component ...
- redis过期key监听事件
目录 redis安装 docker拉取 启动 redis 配置 命令监听 问题 程序监听 具体监听类 效果 总结 redis常用语缓存操作,但是redis功能不仅仅于此.今天我们来看看redis的ke ...
- webpack核心模块tapable用法解析
前不久写了一篇webpack基本原理和AST用法的文章,本来想接着写webpack plugin的原理的,但是发现webpack plugin高度依赖tapable这个库,不清楚tapable而直接去 ...
- Springboot项目启动后自动创建多表关联的数据库与表的方案
文/朱季谦 在一些项目开发当中,存在这样一种需求,即开发完成的项目,在第一次部署启动时,需能自行构建系统需要的数据库及其对应的数据库表. 若要解决这类需求,其实现在已有不少开源框架都能实现自动生成数据 ...
- 使用 DD 命令制作 USB 启动盘
Windows 下有很多很好用的 USB 启动盘制作工具,比如 Rufus,但是 MacOS 下这个类型的工具就少了很多,这里记录下在 MacOS 中用 DD 命令制作 Linux USB 启动盘的操 ...
- 使用命令行编译Qt程序
code[class*="language-"], pre[class*="language-"] { color: rgba(51, 51, 51, 1); ...
- Web安全(更新中)
sql注入 创建一个数据库 create database admin1; 查询数据库 查看所有数据库 show databases; 使用该数据库 use admin1; 创建一个表 创建一个 ...
- 亲测有效JS中9种数组去重方法
码文不易,转载请带上本文链接,感谢~ https://www.cnblogs.com/echoyya/p/14555831.html 目录 码文不易,转载请带上本文链接,感谢~ https://www ...
- 利用Vue实现一个简单的购物车功能
开始学习Vue的小白,dalao多多指正 想要实现下面这个界面,包含总价计算.数量增加和移除功能 话不多说代码如下 <!DOCTYPE html> <html> <hea ...
- 一文搞懂MySQL体系架构!!
写在前面 很多小伙伴工作很长时间了,对于MySQL的掌握程度却仅仅停留在表面的CRUD,对于MySQL深层次的原理和技术知识了解的少之又少,随着工作年限的不断增长,职场竞争力却是不断降低的.很多时候, ...