64位内核开发第三讲,内核中字符串编程注意事项
windows内核下字符串操作详解
一丶内核字符串简介
1.1 简介
在Windows内核下 和 应用程序一样,驱动程序需要经常和字符串打交道。 其中包括了 ASCII
字符串丶宽字符串
,还有DDK定义的 ANSI_STRING STRING
UNICODE_STRING 等结构。下面就详细说明。
二丶 ASCII字符串和宽字符串简介
2.1 ASCII字符串和宽字符串类型和使用
在应用程序中,往往 使用两种字符串,一种是char 类型的字符串
,这种类型字符串一般都是一个 char 数组指针
,指针负责记录 ANSI的字符集
。每个 char
类型的变量都为一个字节
字符串是以 0 标志字符串
结尾。 如下:
char *pszName = "HelloWorld";
char pszName[] = "HelloWorld";
另一种是 wchar_t 类型
的宽字符串
它是负责描述 unicode
字符集的字符串,同第一种方式一样,唯一区别就是 wchar_t
类型的字符大小占用两个字节
,字符串结尾同样以0标志结尾
。
wchar_t
类型的字符串在使用的时候 我们必须 在字符串前边加 L
wchar_t *pszName = "HelloWorld";
wchar_t pszName[] = "HelloWorld";
在驱动程序开发中 DDK将 char 和 wchar_t 分别重定义为了 CHAR WCHAR 类型 并且分别为其提供了指针类型 PCHAR PWCHAR
所以在使用的时候我们可以进行如下使用
WCHAR *pszName = L"HelloWorld";
CHAR * pszName = L"HelloWorld";
PWCHAR pszName = L"HelloWorld";
PCHAR pszName = L"HelloWorld";
2.2 ASCII字符串和宽字符串的操作
操作ASCII字符串和宽字符串 都可以使用 C语言标准库中提供的函数
如针对ASCII字符串可以使用
strchr
strcoll
strlen
strncat
strncmp
strncpy
strcpy
strtok
strstr
等等,详情可以看一下 string.h
头文件中定义的函数 都是可以用的。
宽字符则如下(只列出常用的)
wcscat
wcschr
wcscmp
wcscpy
wcslen
wcsncat
wcsncmp
等等
其中标准中的 内存操作函数也是可以用的
memmove
memset
memcpy
三丶 ANSI_STRING STRING 与 UNICODE_STRING
3.1 简介与结构
在DDK编程中,它并不想让程序员使用 C语言字符串,主要原因就是标准的C字符串处理函数容易导致缓冲区溢出等错误。如果程序员在使用字符串的时候不进行校验那么就很容易出现错误导致蓝屏。
从而 DDK定义了结构来表示字符串。
其中 STRING
和 ANSI_STRING
是一个定义只不过名字不一样。
我们看下三个成员都是怎么定义的吧。
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
USHORT * Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;
typedef struct _STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} STRING;
typedef STRING *PSTRING;
typedef STRING ANSI_STRING;
其实它们本质都是一个结构体,总共三个成员。
成员1: 记录字符串的长度
成员2: 记录字符串的最大长度
成员3: 记录字符串指针。
请注意成员三记录的地址里面存储了字符串,但是并不是以0结尾的。字符串长度需要根据成员1来决定。
3.2 字符串的格式化输出
在内核中我们可以使用 DbgPrint
以及 KdPrint
来进行输出。可以参考 C 语言的 printf函数
如果要对 ANSI_STRING
进行输出的话那么例子如下:
ANSI_STRING ansiStr;
KdPrint(()"%Z \n",&ansiStr);
而针对 UNICODE_STRING
则为如下
UNICODE_STRING ustr;
KdPrint(("%wZ\n",&ustr));
如果是在调试状态下 KdPrint
是不会输出内容的。而 KdPrint
是个宏 所以我们括号要使用两个。
四丶字符串的初始化与销毁
4.1 ANSI_STRING 与 UNICODE_STRING 的初始化
Rtl函数初始化方法
针对这两个结构 要进行初始化 DDK也提供了函数来给我们使用
分别为如下:
RtlInitAnsiString
RtlInitEmptyAnsiString
RtlInitEmptyUnicodeString
RtlInitUnicodeString
RtlUnicodeStringInit
RtlUnicodeStringInit
需要包含 ntstrsafe.h
头文件。 它的实现在 Ntstrsafe.lib
库中。
这几个的参数都很简单,不懂得查询下API得使用即可。
UNICODE_STRING
用得比较多,那么列举一个例子。
NTSYSAPI
VOID
NTAPI
RtlInitUnicodeString(
__out PUNICODE_STRING DestinationString,
__in_z_opt __drv_aliasesMem PCWSTR SourceString
);
参数1: 是你要初始化的 UNICODE_STRING结构的地址,此函数负责为你填充这个结构体。所以需要传地址。
参数2: 初始化的宽字符
例子:
UNICODE_STRING ustr;
RtlInitUnicodeString(&ustr, L"abc");
内核DDK宏直接初始化
ANSI_STRING ansi = RTL_CONSTANT_STRING("HelloWorld");
UNICODE_STRING ustr = RTL_CONSTANT_STRING(L"HelloWorld");
RTL_CONSTANT_STRING
此宏可以进行在定义的时候直接初始化,初始化后 UN/AN 结构里面的Buffer指向的是常量字符串地址。
-
初始化的本质
ANSI/UNICODE
结构 里面都有一个pBuffer
指针 这个指针是来指向字符串的。但是我们要了解本质。 初始化还分为
常量初始化
栈内存初始化
堆内存初始化
. 其实本质是将pBuffer指针指向你初始化字符串的地址。 如果你字符串是一个常量字符串,那么自然指向的就是 常量区的地址, 如果你是用栈初始化的 那么自然指向你的栈内存,如果指向堆内存 那么自然指向的就是堆内存。
例子:
1.常量初始化
UNICODE_STRING ustr = RTL_CONSTANT_STRING(L"HelloWorld");
本质是如下:(伪代码)
UNICODE_STRING ustr;
ustr.length = wcslen(L"HelloWorld");
ustr.MaxLength = wcslen(L"HelloWorld")+1;
ustr.pBuffer = &L"HelloWorld";
如果你想修改 ustr只能重新修改指向了。
堆栈初始化
UNICODE_STRING ustr = {0};
WCHAR szBuffer[100] = L"HelloWorld";
RtlInitUnicodeString(&ustr, szBuffer);
看着代码很正常但是 ustr.pBuffer
的指向指向的是 szBuffer
。
如果你修改szBuffer
的内容那么ustr.pBuffer
的内容也会更改
原理(伪代码)
ustr.pBuffer = szBuffer;
3.堆内存初始化
同堆栈初始化一样,只不过 buffer是我们申请的堆内存了。一般我们也建议这样使用。
因为修改堆内存之后 ustr.pbuffer
才会更改
PWCHAR szBuffer = (PWCHAR)ExAllocatePoolWithTag(NonPagedPool, 'abcd');
if (szBuffer != NULL)
{
wcscpy(szBuffer, L"HelloWorld");
}
RtlInitUnicodeString(&ustr, szBuffer);
但是如果这样使用请一定注意要释放内存,否则就会内存泄漏。
五丶字符串操作常见函数
5.1 字符串复制
字符串复制函数针对 ANSI_STRING UNICODE_STRING 都分别提供了。
函数如下:
RtlCopyString
RtlCopyUnicodeString
RtlUnicodeStringCbCopyStringN
RtlUnicodeStringCbCopyN
RtlUnicodeStringCbCopyNEx
RtlUnicodeStringCbCopyStringNEx
RtlUnicodeStringCopyString
其中最长使用的就是前两个函数 用于分别 拷贝 ANSI_STRING
或者 UNICODE_STRING
下面的 cb类型的函数都是可以拷贝 宽字符到 UNICODE_STRING
中 下面的也会遇到 但主题说的是拷贝所以不在多说。如果想使用这类函数 一定要包含头文件 <ntstrsafe.h>
以及它对应的库文件。详情查询MSDN吧。
5.2 字符串比较
字符串比较同样提供一组函数
RtlCompareString()
RtlCompareUnicodeString()
RtlCompareUnicodeStrings()
RtlEqualString()
RtlEqualUnicodeString()
比较的时候是比较两个 结构的字符串是否相等
取一个举例子
LONG
NTAPI
RtlCompareUnicodeString(
__in PCUNICODE_STRING String1,
__in PCUNICODE_STRING String2,
__in BOOLEAN CaseInSensitive
);
参数1: 要比较的第一个字符串
参数2: 要比较的第二个字符串
参数3: 是否对大小写敏感
返回值: 比较结果 0 字符串相等 1 代表第一个字符串大于第二个字符串 小于0 代表第一个字符串小于第二个字符串
所以记住 返回0
是代表字符串相等
5.3 字符串转为大写
字符串的大小写转换也是很烦人的,索性 内核提供了 字符串转大写的函数
RtlUpcaseUnicodeChar()
RtlUpcaseUnicodeString()
RtlUpperString()
RtlUpperChar()
常用的就两个
RtlUpcaseUnicodeString
RtlUpperString 一个是将UNICODE_STRING
转为大写,另一个自然就是将 ANSI_STRING
转为大写了 而提供了 两外两个函数则是将 字符 转为 大写字符
例子:
NTSYSAPI NTSTATUS RtlUpcaseUnicodeString(
[in, out] PUNICODE_STRING DestinationString,
[in] PCUNICODE_STRING SourceString,
[in] BOOLEAN AllocateDestinationString
);
参数1: 目的字符串
参数2: 源字符串
参数3: 是否为目的字符串申请内存
返回值: 返回转换是否成功
要注意,UNICODE_STIRNG 转为大写多了一个参数,就是参数3.它的意思就是转换后要不要给参数一传递的UNICODE_STRING 申请空间。 如果要的话 那么你要注意使用
RtlFreeUnicodeString
来释放空间 而 ASCII版本没有这个参数,所以代表着你的参数一在传递的时候一定要自己预留出一个空间来。这样才能存放结果。
5.4 字符串与整形数字的相互转换
-
字符串转为整数
UNICODE_STRING
可以转换为整数。 那么自然整数也可以转化为 UNICODE_STRING
API如下:
NTSTATUS
NTAPI
RtlUnicodeStringToInteger (
__in PCUNICODE_STRING String,
__in_opt ULONG Base,
__out PULONG Value
);
参数1: 需要你进行转化的字符串
参数2: 转换的进制数,你要转化为多少进制,如 2 8 10 16
参数3: 转换后的结果数字
返回值: 指明是否转换成功
-
整数转为字符串
整数也可以转化为字符串
其中提供的函数有两个
NTSTATUS
NTAPI
RtlIntegerToUnicodeString (
__in ULONG Value,
__in_opt ULONG Base,
__inout PUNICODE_STRING String
);
NTSTATUS
NTAPI
RtlInt64ToUnicodeString (
__in ULONGLONG Value,
__in_opt ULONG Base,
__inout PUNICODE_STRING String
);
参数1: 你想转化的数值(注意用64的代表你要给一个64的值)
参数2: 转换的进制数(2 8 10 16)
参数3: 转换成功后存储结果的字符串(注意你要预留空间)
返回值: 是否成功
使用例子:
5.5 ANSI_STRING 与 UNICODE_STRING 字符串的互相转换
-
UNICODE_STRING
转化为ANSI_STRING
NTSYSAPI NTSTATUS RtlUnicodeStringToAnsiString(
[in, out] PANSI_STRING DestinationString,
[in] PCUNICODE_STRING SourceString,
[in] BOOLEAN AllocateDestinationString
);
-
ANSI_STRING
转化为UNICODE_STRING
NTSYSAPI NTSTATUS RtlAnsiStringToUnicodeString(
[in, out] PUNICODE_STRING DestinationString,
[in] PCANSI_STRING SourceString,
[in] BOOLEAN AllocateDestinationString
);
函数原理一样
参数1: 代表你需要被转化的字符串 目的字符串
参数2: 你要转化的源字符串
参数3: 是否为目的字符串申请内存
返回值: 是否转换成功
就上将源字符转化为目的字符串。 如果需要申请内存 请一定注意使用 RtlFreexxxString
5.6 字符串的拼接操作
字符串拼接 自然就是 cat的操作 DDK也为我们提供了函数
RtlStringCbCatA //
RtlStringCbCatNA
RtlUnicodeStringCat // 追加一个UNICODE_STRING
RtlUnicodeStringCatEx
RtlUnicodeStringCatString //追加一个 WCHAR*数据
RtlUnicodeStringCbCatN //按照大小追加
5.7 字符串的格式化-wsprintf..
RtlUnicodeStringPrintf
例子:
RtlUnicodeStringPrintf(&buffer,
L"OsrUsbFX2 RawPdo For Switch %d",
pDesc->SwitchNumber);
具体的话使用的时候在进行查询吧。
坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功. 想学习,有问题请加群.群号:725864912(收费)群名称: 逆向学习小分队 群里有大量学习资源. 以及定期直播答疑.有一个良好的学习氛围. 涉及到外挂反外挂病毒 司法取证加解密 驱动过保护 VT 等技术,期待你的进入。
详情请点击链接查看置顶博客 https://www.cnblogs.com/iBinary/p/7572603.html
本文来自博客园,作者:iBinary,未经允许禁止转载 转载前可联系本人.对于爬虫人员来说如果发现保留起诉权力.https://www.cnblogs.com/iBinary/p/10990678.html
欢迎大家关注我的微信公众号.不定期的更新文章.更新技术. 关注公众号后请大家养成 不白嫖的习惯.欢迎大家赞赏. 也希望在看完公众号文章之后 不忘 点击 收藏 转发 以及点击在看功能. QQ群: