内核里操作字符串

字符串本质上就是一段内存,之所以和内存使用分开讲,是因为内核里的字符串太有花

样了,细数下来竟然有 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.内核里操作字符串的更多相关文章

  1. Win64 驱动内核编程-7.内核里操作进程

    在内核里操作进程 在内核里操作进程,相信是很多对 WINDOWS 内核编程感兴趣的朋友第一个学习的知识点.但在这里,我要让大家失望了,在内核里操作进程没什么特别的,就标准方法而言,还是调用那几个和进程 ...

  2. Win64 驱动内核编程-5.内核里操作文件

    内核里操作文件 RING0 操作文件和 RING3 操作文件在流程上没什么大的区别,也是"获得文件句柄->读/写/删/改->关闭文件句柄"的模式.当然了,只能用内核 A ...

  3. Win64 驱动内核编程-6.内核里操作注册表

    内核里操作注册表 RING0 操作注册表和 RING3 的区别也不大,同样是"获得句柄->执行操作->关闭句柄"的模式,同样也只能使用内核 API 不能使用 WIN32 ...

  4. Win64 驱动内核编程-3.内核里使用内存

    内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...

  5. Win64 驱动内核编程-8.内核里的其他常用

    内核里的其他常用 1.遍历链表.内核里有很多数据结构,但它们并不是孤立的,内核使用双向链表把它们像糖 葫芦一样给串了起来.所以遍历双向链表能获得很多重要的内核数据.举个简单的例子,驱 动对象 Driv ...

  6. 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作

    1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...

  7. Linux Shell编程(15)——操作字符串

    Bash已经支持了令人惊讶的字符串操作的数量.不幸地,这些工具缺乏统一的标准.一些是参数替换的子集,其它受到UNIX的expr命令的功能的影响.这导致不一致的命令语法和冗余的功能,但这些并没有引起混乱 ...

  8. WIN64内核编程-的基础知识

    WIN64内核编程基础班(作者:胡文亮)   https://www.dbgpro.com/x64driver 我们先从一份"简历"说起: 姓名:X86或80x86 性别:? 出生 ...

  9. Win64 驱动内核编程-2.基本框架(安装.通讯.HelloWorld)

    驱动安装,通讯,Hello World 开发驱动的简单流程是这样,开发驱动安装程序,开发驱动程序,然后安装程序(或者其他程序)通过通讯给驱动传命令,驱动接到之后进行解析并且执行,然后把执行结果返回. ...

随机推荐

  1. .net Core 上传文件详解

    .net core 和.net framework上传文件有很多需要注意的地方 .net framework 上传文件用httppostedfilebase .net core 上传文件用 IForm ...

  2. Ubuntu 18.04下Intel SGX应用程序程序开发——获得OCALL调用的返回值

    本文中,我们介绍在Enclave函数中调用不可信OCALL函数,并获得OCALL函数的返回值. 1. 复制SampleEnclave示例并建立自己的OcallRetSum项目 SampleEnclav ...

  3. Windows下用户手册

      (1)net user(查看系统用户) (2)net user 用户名(查看具体某个系统用户详细信息) (3)net user 用户名 密码 /add(在本地组成员创建新用户,此时为Users组) ...

  4. CVE-2019-12409-Apache Solr JMX服务远程代码执行

    漏洞分析 https://www.freebuf.com/vuls/218730.html 漏洞介绍 该漏洞源于默认配置文件solr.in.sh中的ENABLE_REMOTE_JMX_OPTS配置选项 ...

  5. List的主要实现类

    //ArrayList:List的主要实现类 /* * List中相对于Collection,新增加的方法 * void add(int index, Object ele):在指定的索引位置inde ...

  6. 2019HDU多校第七场 HDU6651 Final Exam

    一.题目 Final Exam 二.分析 题目说的比较绕,总之一定要记住,$n$个题目都可以做,至少作对$k$到,但是做题目的人不知道每道题对应的分数. 作为出题人,如果他是田忌,肯定不会去在做题目的 ...

  7. 一个名叫Sentinel-Rules-SDK的组件,使得Sentinel的流控&熔断规则的配置更加方便

    原文链接:一个名叫Sentinel-Rules-SDK的组件,使得Sentinel的流控&熔断规则的配置更加方便 1 Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越 ...

  8. gtk中构件添加背景图

    在gtk中我们总想要去给构件添加背景图,具体函数代码如下 void chang_background(GtkWidget *widget, int w, int h, const gcha r *pa ...

  9. DAOS 分布式异步对象存储|架构设计

    分布式异步对象存储 (DAOS) 是一个开源的对象存储系统,专为大规模分布式非易失性内存 (NVM, Non-Volatile Memory) 设计,利用了SCM(Storage-Class Memo ...

  10. PReact10.5.13源码理解之hook

    hook源码其实不多,但是实现的比较精巧:在diff/index.js中会有一些optison.diff这种钩子函数,hook中就用到了这些钩子函数.   在比如options._diff中将curr ...