Windows内核中的字符串操作
概述
在内核编程的过程中,有大量的代码涉及到对字符串的操作,比如需要暂存一部分的字符串、需要拷贝一个字符串到另一个字符串中,需要字符串追加,比较,判断是否含有子字符串等操作,在这些操作的过程中,需要时刻注意操作的字符串指针,避免溢出、为空等造成系统运行异常的错误。实际开发过程中微软提供了一套完整的字符串操作API,我们只需要借助这些API函数就可以满足大部分的字符串操作需求了。
由于在实际编程的过程中主要使用的是宽字符串,所以本文主要介绍的API是操作宽字符集的,窄字符集也有对应的操作。
定义和初始化
头文件
#include <ntifs.h>
#include <windef.h>
#include <ntstrsafe.h>
格式化输出表
符号 | 说明 | 类型 |
---|---|---|
%c, %lc | ANSI字符 | char |
%C, %wc | 宽字符 | wchar_t |
%d, %i | 十进制有符号整数 | int |
%D | 十进制_int64 | _int64 |
%L | 十六进制的LARGE_INTEGER | LARGE_INTEGER |
%s, %ls | NULL终止的ANSI字符串 | char* |
%S, %ws | NULL终止的宽字符串 | wchar_t* |
%Z | ANSI_STRING字符串 | STRING* |
%wZ | UNICODE_STRING字符串 | UNICODE_STRING* |
%u | 十进制的ULONG | ULONG |
%x | 小写字符十六进制的ULONG | ULONG |
%X | 大写字符十六进制的ULONG | ULONG |
%p | 指针Pointer 32/64位 | PVOID * |
基本数据结构
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;
#endif // MIDL_PASS
} UNICODE_STRING;
unicode string 结构体包含了三个成员变量,
Length 表示字符串所用的字节长度(不包含结束符),表示的是实际使用的字节数
MaximumLength 表示buffer的长度,表示申请的存放字符串的空间的大小(包含结束符)
Buffer 指向分配的字符串的指针,如果用常量字符串来初始化的话,这个指针指向常量字符串所在的内存,是不能修改的
初始化一个unicode string变量
UNICODE_STRING contemp = {0};
RtlInitUnicodeString(&contemp, L"HELLO WORLD!");
以上使用了一个常量字符串初始化了一个Unicode string类型的变量contemp,注意这个时候contemp指向的内容是常量字符串“HELLO WORLD!”的地址,是不能修改的,如果强行修改会导致宕机,并有如下提示:
将ansi转化为unicode
STRING strtemp = { 0 };
UNICODE_STRING temp = { 0 }; // 默认定义的变量是没有分配字符串内存的,buffer指针为空
RtlInitString(&strtemp, "strtemp hello world !");
// 用ansi字符串初始化unicodestring 这里的TRUE表示对temp自动创建缓存,使用完成后需要释放一下temp
RtlAnsiStringToUnicodeString(&temp, &strtemp, TRUE);
以上使用一个ansi的字符串结构变量,初始化一个unicode string的变量,初始化的过程也完成了buffer的内存申请,所以最终是需要释放的,由于是自己申请的内存,所以是可以对其内部数据进行修改的,下面的操作多基于temp
将字符串转化为大写
RtlUpcaseUnicodeString(&temp, &temp, FALSE); // 这里的temp是可以改变的
DbgPrint("MYTEST: ----%wZ-----", &temp);
从一个字符串复制到另一个字符串中
UNICODE_STRING copytest = { 0 };
copytest.Buffer = ExAllocatePool(NonPagedPool, 0X100);
copytest.MaximumLength = 0X100; // 不指定大小会导致下面的步骤无法填充内容
RtlCopyUnicodeString(©test, &temp);
DbgPrint("MYTEST: ----%wZ-----", ©test);
ExFreePool(copytest.Buffer);
或者
PWCHAR copytest2 = ExAllocatePool(NonPagedPool, 0x100);
RtlZeroMemory(copytest2, 0x100);
RtlStringCbCopyW(copytest2, 0x100, L"mytest copystr2 hello world");
DbgPrint("MYTEST: ----%ws-----", copytest2);
ExFreePool(copytest2);
注意: 格式化字符串输出的区别
比较字符串的大小
UNICODE_STRING comtemp1 = { 0 };
UNICODE_STRING comtemp2 = { 0 };
RtlInitUnicodeString(&comtemp1, L"hello world");
RtlInitUnicodeString(&comtemp2, L"HEllo world");
LONG ret = RtlCompareUnicodeString(&comtemp1, &comtemp2, FALSE); // 第三个参数表明是否忽略大小写差异,true 表明忽略大小写差异
DbgPrint("MYTEST: ret = %d", ret);
if (ret == 0)
{
DbgPrint("MYTEST: %wZ = %wZ", &comtemp1, &comtemp2);
}
else if (ret > 0)
{
DbgPrint("MYTEST: %wZ > %wZ", &comtemp1, &comtemp2);
}
else
{
DbgPrint("MYTEST: %wZ < %wZ", &comtemp1, &comtemp2);
}
追加字符串
- 在unicode string类型基础上直接进行追加
UNICODE_STRING appendtest = { 0 }; appendtest.Buffer = ExAllocatePool(NonPagedPool, 0X100); RtlZeroMemory(appendtest.Buffer, 0x100); RtlStringCbCopyW(appendtest.Buffer, 0x100, L"APPENDTEST---"); appendtest.MaximumLength = 0X100; appendtest.Length = sizeof(L"APPENDTEST---")-sizeof(WCHAR); DbgPrint("MYTEST: ----%wZ-----", &appendtest); // 开始在appendtest后追加 UNICODE_STRING appendtest2 = { 0 }; RtlInitUnicodeString(&appendtest2, L"hello world"); RtlAppendUnicodeStringToString(&appendtest, &appendtest2); DbgPrint("MYTEST: ----%wZ-----", &appendtest); ExFreePool(appendtest.Buffer);
- 在wchar *基础上进行追加
PWCHAR wcharappend = ExAllocatePool(NonPagedPool, 0X100); RtlZeroMemory(wcharappend, 0x100); RtlStringCbCopyW(wcharappend, 0x100, L"APPENDTEST2---"); DbgPrint("MYTEST: ----%ws-----", wcharappend); RtlStringCbCatW(wcharappend, 0x100, L"hello world!"); DbgPrint("MYTEST: ----%ws-----", wcharappend); ExFreePool(wcharappend);
判断是否含有子字符串
UNICODE_STRING srcstr = { 0 };
RtlInitUnicodeString(&srcstr, L"HELLO world ");
UNICODE_STRING tarstr = { 0 };
RtlInitUnicodeString(&tarstr, L"*WORLD*");// 注意1
if (FsRtlIsNameInExpression(&tarstr, &srcstr, TRUE, NULL)) // 注意2
{
DbgPrint("MYTEST: %wZ has substr %wZ---", &srcstr, &tarstr);
}
注意1: 需要查找的目标字符串,如果是断定位于开始或者结束的位置可以不需要前面或者后面的 * 号,比如只需要查找位于字符串开始位置的字符串的时候前面的 * 号就可以不要。
注意2: 这里在查找目标字符串的时候选定了不区分大小写,且最后一个参数设置为NULL,查看函数的定义可以知道:第四个参数用于不区分大小写匹配的大写字符表的可选指针。如果未提供此值,则使用默认的系统大写字符表进行匹配。所以当这个值设置为空的时候,目标字符串就需要自行转化为大写,系统会将源字符串进行大写转换进行查找。
如有错误,还请指正,谢谢