来简单实战几个字符串API

初始化

ASCII 字符和宽字符的版本分别是

1
2
RtlInitAnsiString
RtlInitUnicodeString

第一个参数都是对应的字符串结构体的指针,也就是说,在使用的时候需要先定义一个结构体变量再去使用这个 API 去初始化字符串变量。

1
2
3
4
5
6
7
LPSTR str2 = "123456789 hello";
ANSI_STRING astr;
RtlInitAnsiString(&astr, str2);

LPWSTR str = L"123456789 hello";
UNICODE_STRING ustr;
RtlInitUnicodeString(&ustr, str);

使用格式化字符串输出的 %Z%wZ 可以直接输出该字符串。

1
2
3
4
#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, ##__VA_ARGS__)

kprintf(("%Z\n"), astr);
kprintf(("%wZ\n"), ustr);

运行结果:

相互转换

宽字符可以变 ASCII 字符,ASCII 字符也可以变宽字符。前提是它们都是 ASCII 范围内可以在不改变内容的情况下转换,如果使用 Unicode 字符将 Unicode 字符串转为 ASCII 字符串则可能会出现乱码。

事实证明想多了,即使是 ASCII 字符串,也能存放 Unicode 字符串。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<fltKernel.h>
#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, ##__VA_ARGS__)
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
kprintf(("Bye!\n"));
}
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
reg_path;
NTSTATUS status = STATUS_SUCCESS;
kprintf(("Hello!\n"));
driver->DriverUnload = UnloadDriver;
ANSI_STRING astr;
UNICODE_STRING ustr;
LPWSTR str = L"123456789 hello哈哈";
RtlInitUnicodeString(&ustr, str);
RtlUnicodeStringToAnsiString(&astr, &ustr, TRUE);
kprintf(("%p,%p\n"), ustr.Buffer, astr.Buffer);
kprintf(("%Z\n"), astr);
return status;
}

运行结果:

这里的 API RtlUnicodeStringToAnsiString(&astr, &ustr, TRUE);,前两个参数,一个是目的字符串指针,一个是源字符串指针。第三个参数 TRUE 表示为目的字符串新分配内存,FALSE 表示不分配内存。

如果第三个参数设置为 FALSE 一定要注意,它会在 astr.buffer 中写入,因此一定要确保指向了一个正确的可写的内存,否则就会面临蓝屏。

RtlAnsiStringToUnicodeString 这个 API 同理可得。

注意,使用RtlUnicodeStringToAnsiString/RtlAnsiStringToUnicodeString函数时,需要在使用完后调用RtlFreeAnsiString/RtlFreeUnicodeString函数来释放所分配的缓冲区,否则会产生内存泄露。

数字与字符串之间的转换

在传统的 CHAR */WCHAR * 字符串中,我们只有 atoi,sprintf 等传统函数做数字与字符串之间的转换。 而内核就有很方便的 api 做转换,下面来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<fltKernel.h>
#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, ##__VA_ARGS__)
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
kprintf(("Bye!\n"));
}
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
reg_path;
NTSTATUS status = STATUS_SUCCESS;
kprintf(("Hello!\n"));
driver->DriverUnload = UnloadDriver;
UNICODE_STRING ustr,ustr2;
ULONG val,val2=0x12345678;
LPWSTR str = L"123456789";
RtlInitUnicodeString(&ustr, str);
RtlUnicodeStringToInteger(&ustr, 10, &val);
kprintf(("val=%d\n"), val);

WCHAR *S=(WCHAR*)ExAllocatePoolWithTag(PagedPool, 0x1000, 'str');
ustr2.Buffer=S;
ustr2.Length=0;
ustr2.MaximumLength=0x1000;
RtlIntegerToUnicodeString(val2, 16, &ustr2);
kprintf(("val=%wZ\n"), ustr2);
return status;
}

其中第二个参数 base 指示了字符串是什么进制。

可以发现,从数字转字符串还是需要废一点功夫的,需要手动给 UNICODE_STRING 结构体初始化分配内存。

但是也是显然的,该例程会存在内存泄漏,当驱动被释放的时候,分配的内存不会释放,因此需要养成良好的习惯,当不使用这个字符串的时候,及时释放分配的内存。

字符串拷贝与比较

例程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<fltKernel.h>
#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, ##__VA_ARGS__)
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
kprintf(("Bye!\n"));
}
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
reg_path;
NTSTATUS status = STATUS_SUCCESS;
kprintf(("Hello!\n"));
driver->DriverUnload = UnloadDriver;
UNICODE_STRING s1;
WCHAR* S = (WCHAR*)ExAllocatePoolWithTag(PagedPool, 0x1000, 'str');
s1.Buffer = S;
s1.Length = 0;
s1.MaximumLength = 0x1000;
RtlCopyUnicodeString(&s1, &driver->DriverName);
kprintf(("DriverName: %wZ\n"), s1);
auto compare=RtlCompareUnicodeString(&s1, &driver->DriverName, TRUE);
kprintf(("compare=%d\n"), compare);
return status;
}

RtlCopyUnicodeString 就是简单的字符串拷贝函数,不做过多解释。

RtlCompareUnicodeString 是字符串比较函数,与 strcmp 一样,字符串相等返回 0,不相等返回非 0,第三个参数指示英文单词是否大小写敏感,TRUE 则敏感,S 与 s 视为不同的字符。

其它

还有一系列的字符串操作函数也不一一展示了,它们都是 Rtl 为前缀命名的 API。

参考文献

  • [1]:MSDN