-
-
[原创]内核基本操作,数据结构
-
发表于: 2023-2-7 08:15 6742
-
本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正
内核基本操作,数据结构
内核的基本操作
UNICODE_STRING
为什么字符串很重要
- 大型工程中10%~20%的代码都与字符串操作有关
- 面试里面也有很多字符串相关问题
- 字符串涉及到指针操作
- 字符串是编程第二个重要关口
字符与字符串
字符
- char/sigined char/unsigned char是不同的类型,但int/sigined int是一样的
1 2 3 4 5 | std::out<<std::is_same<char,char>::value<<std::endl; / / TRUE std::out<<std::is_same<char,sigined char>::value<<std::endl; / / FALSE std::out<<std::is_same<char,sunigined char>::value<<std::endl; / / FALSE std::out<<std::is_same< int , int >::value<<std::endl; / / TRUE std::out<<std::is_same< int ,sigined int >::value<<std::endl; / / TRUE |
- 字符编码(Character encoding)也称字集码,是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成摩斯电码和ASCII。其中,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示这个整数。通常会额外使用一个扩充的比特,以便于以1个字节的方式存储。
ASSCII
:美国(国家)信息交换标准(代)码,一种使用7个或8个二进制位进行编码的方案,最多可以给256个字符(包括字母、数字、标点符号、控制字符及其他符号)分配(或指定)数值。GB2312
:也是ANSI
编码里的一种,对ANSI编码最初始的ASCII编码进行扩充,为了满足国内在计算机中使用汉字的需要,中国国家标准总局发布了一系列的汉字字符集国家标准编码,统称为GB码,或国标码。GBK
:即汉字内码扩展规范,K为扩展的汉语拼音中“扩”字的声母。英文全称Chinese Internal Code Specification。GBK编码标准兼容GB2312,共收录汉字21003个、符号883个,并提供1894个造字码位,简、繁体字融于一库。unicode
:世界上存在着多种编码方式,在ANSI编码下,同一个编码值,在不同的编码体系里代表着不同的字。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码,可能最终显示的是中文,也可能显示的是日文。在ANSI编码体系下,要想打开一个文本文件,不但要知道它的编码方式,还要安装有对应编码表,否则就可能无法读取或出现乱码。为什么电子邮件和网页都经常会出现乱码,就是因为信息的提供者可能是日文的ANSI编码体系和信息的读取者可能是中文的编码体系,他们对同一个二进制编码值进行显示,采用了不同的编码,导致乱码。这个问题促使了unicode码的诞生。- 如果有一种编码,将世界上所有的符号都纳入其中,无论是英文、日文、还是中文等,大家都使用这个编码表,就不会出现编码不匹配现象。每个符号对应一个唯一的编码,乱码问题就不存在了。这就是Unicode编码。
UTF-8
:为了提高Unicode的编码效率,于是就出现了UTF-8编码。UTF-8可以根据不同的符号自动选择编码的长短。比如英文字母可以只用1个字节就够了。Base64
:有的电子邮件系统(比如国外信箱)不支持非英文字母(比如汉字)传输,这是历史原因造成的(认为只有美国会使用电子邮件?)。因为一个英文字母使用ASCII编码来存储,占存储器的1个字节(8位),实际上只用了7位2进制来存储,第一位并没有使用,设置为0,所以,这样的系统认为凡是第一位是1的字节都是错误的。而有的编码方案(比如GB2312)不但使用多个字节编码一个字符,并且第一位经常是1,于是邮件系统就把1换成0,这样收到邮件的人就会发现邮件乱码。
- 0、L'0'、'0'、
'\0'
、"0"、FALSE、false、NULL区别0
,int(4Byte),0x00000000L'0'
,wchar_t(2Byte),0x0030'0'
,char(1Byte),0x30'\0'
,char(1Byte),0x00"0"
,char*(2Byte),0x3000("0\0"
)FALSE
,BOOL(4Byte),0x00000000false
,bool(1Byte),0x00NULL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | / / / C #define NULL(viod*)0 / / / C + + 98 ,C + + 不允许直接使用void * 隐式的转化为其他类型,如果NULL被定义为((viod * ) 0 ),当编译char * p = NULL;就会报错。 #define NULL 0 / / / 如果NULL 被定义为 0 ,C + + 中的函数重载就会出问题 void func( int ); / / 因为NULL是 0 ,实际上是调用这个函数,不符合预期,这是是C + + 98 遗留的问题 void func(char * ); / / 当把NULL传给func,期待是调用这个函数 / / / C + + 11 ,引入了nullptr类型,不是整数类型,能够隐式的转换成任何指针,所以用空指针推荐使用nullptr。 / / / NULL的发明人东尼.霍尔(Toby Hoare)图灵奖得主,把NULL引用称为十亿美元的错误 / / / 有不使用NULL的语言,Rust就是,一个数据可能有值可能没有值,需要把它放到Option里面,这样编译器在处理Option的时候会强制去判断它是否有值,如果没有值,就需要程序员去处理没有值的情况,否则编译无法通过。 enum Option <T>{ / / 标识一个值无效或者缺失 Some(T), / / T是泛型,可以包含任何数据 None , } |
字符串
char *
- 在
c语言
中使用,以'\0'
结尾的ASCII
字符串,每个字符占1
个字节
- 在
BSTR
- 在
win32
编程中使用,前4个字节表示字节长,后面以'\0'结尾,可以理解为是char*
和UNICODE_STRING
的综合 - BSTR 是一个指向
UNICODE
字符串的指针,且 BSTR 向前的4个字节中,使用DWORD保存着这个字符串的字节长度
( 没有含字符串的结束符)。
- 在
宽字节
字符串- L"Hello world,你好" 每个字符占
2
个字节
- L"Hello world,你好" 每个字符占
多字节
字符串- "Hello world,你好"
Hello world
每个字符占1个字节,你好
每个字符占2个字节
- "Hello world,你好"
_T("Hello world,你好")
- 这个
宏
根据工程的设置,自适应变成宽字节
或者多字节
- 这个
ANSI_STRING
字符串多字节
编码字符,不是'\0'
结尾,是一个结构体类型
UNICODE_STRING
字符串unicode
编码字符,不是'\0'
结尾,是一个结构体类型,内核统一使用的字符串格式
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 29 30 31 32 33 34 35 | / / / 1. `char * ` char * str = "Hello world,你好" ; / / 每个字符占` 1 `个字节 / / / 2. `BSTR` BSTR bstr = ::SysAllocString(L "Hello,你好" ); / / 长度是 16 ,::SysAllocString(L"")申请一个 BSTR 字符串,并初始化为一个字符串。申请后如果修改可使用::SysReAllocString(bstr, L””)重新分配内容和空间。使用完毕后用SysFreeString(bstr)用来释放内存,否则会内存泄漏。 / / / 3. `宽字节`字符串 wchar_t * szHello = "Hello world,你好" ; / / 每个字母占用的字节数是一样的,每个字符占` 2 `个字节 / / / 4. `多字节`字符串 char * str = "Hello world,你好" ; / / 英文字符占一个字节,而中文字符占 2 个字节 / / / 5. `_T( "hello world,你好" ) ` MessageBox(NULL, _T( "hell dll" ), _T( "mydll" ), MB_OK); / / _T这个宏,需要包含tchar.h头文件,表示字符串类型随着项目不同而发生变化, unicode 或者多字节 / / / 7. `UNICODE_STRING`字符串, unicode 编码字符 typedef struct UNICODE_STRING{ USHORT Length; / / / < 字节数,不是字符数,而是数据的字节数,字节数 = 字符数 * sizeof(WCHAR) USHORT MaximumLength; / / / < 字节数,告诉系统函数最多有多少内存可用 PWSTR Buffer ; / / / < 是 unicode 编码,只是一个指针,一旦定义之后并没有内存,需要正确初始化之后,让 Buffer 含有真正的数据,才拥有内存. * * 非零结尾,中间也可能含有 0 * * ,PWSTR类型等价于WCHAR }UNICODE_STRING, * PUNICODE_STRING; / / / 6. `ANSI_STRING`字符串,多字节编码字符 typedef struct _STRING{ USHORT Length; / / / < 同上 USHORT MaximumLength; / / / < 同上 PCHAR Buffer ; / / / < 同上,这里ANSI_STRIN. buffer 是多字节编码 }ANSI_STRING, * PANSI_STRING; / / / Buffer 不是以零结尾,中间也可能含有零,wcscpy / wcscmp等操作其中的 Buffer 不可靠, / / / (如果中间UNICODE_STRING. Buffer 或者_STRING. Buffer 中间也可能含有零,则会提前截断,如果中间不含有零,则会溢出) / / / PCHAR Buffer 与字符数组WCHAR Buffer [MAX_PATH]对比 typedef struct XXX{ USHORT Length; ... WCHAR Buffer [MAX_PATH]; / / / < 字符数组,一旦定义就拥有内存 }XXX, * PXXX |
DbgPrint
- 打印格式:%
[flags]
[width]
[.precision]
[{h|l|ll|w|I|I32|I64|}]
type %c
以char(2Byte)字符格式打印%wc
以wchar_t(2Byte)字符格式打印%d
以int(4Byte)格式打印%hd
以short(2Byte)格式打印%ld
以long(4Byte)格式打印%I64d
以_int64
(8Byte)格式打印%lld
以long long或者_int64
(8Byte)格式打印%s
以多字节
字符串格式打印%ws
以宽字节
字符串格式打印%u
以unsigned格式打印%#x
以16进制格式打印#
表示带前缀0x
%02x
以16进制格式打印02
表示不足两位补零%o
以8进制格式打印%#o
以8进制格式打印#
表示带前缀0
%02o
以8进制格式打印02
表示不足两位补零%p
以指针格式打印%f
以float(4Btye)格式打印%.2f
以float(4Btye)格式打印,.2
表示保留小数点后两位%lf
以double(2Byte)格式打印%Z
以ANSI_STRING
字符串格式打印%wZ
以UNICODE_STRING
字符串格式打印%%
打印一个%
%n
,把前面打印的字符总数写入到变量里面去,现在已经被编译器禁用
了,编译
能通过
但执行
的时候会报错
。%01000x%n
把前面打印的字符总数1000
个0
写入到变量里面去,0
表示用0填充,%1000x
表示以16进制格式重复打印1000个字符,x可以替换为c(以char格式打印,还是一个字节)%I
IRP主功能号
和次功能号
代码常用对字符串处理的API
初始化
用字符串常量对其初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | UNICODE_STRING uStr = { 0 }; / / 直接定义, Buffer 没有内存( Buffer = NULL),必须正确初始化,让 Buffer 含有真正的数据。 / / / 在栈上的局部变量未初始化,调试的时候内存显示的是 "烫烫烫" / / / 在堆上的局部变量未初始化,调试的时候内存显示的是 "屯屯屯" WCHAR * szHello = L "Hello,world!" ; / / / < 全局变量WCHAR * szHello,指向L "Hello,world!" 宽字节字符串常量,L "Hello,world!" 存放在静态区的.rdata / / / 初始化的过程: / / / buffer 是浅拷贝(只把常量字符串的地址拷贝到 buffer 中), buffer 直接指向L "Hello,world!" / / / Length:wcslen(szHello) * sizeof(WCHAR);不含 '\0' / / / MaximumLength:(wcslen(szHello) + 1 ) * sizeof(WCHAR);,含 '\0' RtllnitUnicodeString(&ustrTest,szHello); / / / 上面两句等价于:DECLARE_CONST_UNICODE_STRING(ustrTest,L"Hello,world!); / / / RtllnitUnicodeString(&ustrTest,szHello);等价于 / / / ustrTest. Buffer = szHello; / / / ustrTest.Length = wcslen(szHello) * sizeof(WCHAR); / / / ustrTest.MaximumLength = sizeof(szHello); / / / buffer 直接指向静态常量L "Hello,world!" ,常量区内存不可被修改,下面的操作必然会出错,造成蓝屏。 RtlCopyUnicodeString(&ustrTest,&uStr2); RtlAppendUnicodeToString(&ustrTest,Str1); RtlAppendUnicodeStringToString(&ustrTest,&uStr2); RtlAnsiStringToUnicodeString(&ustrTest,&aStr1,FALSE); |
用栈上的buffer或静态区的内存对其初始化
1 2 3 4 5 6 7 8 9 | / / / 定义并初始化一个UNICODE_STRING字符串 UNICODE_STRING ustrTest = { 0 }; / / / 如果把szHello定义在函数内部就是在栈上 / / / 如果把szHello定义在全局变量就是在静态区.data WCHAR szHello[ 512 ] = L "Hello,world!" / / / 用栈上的 buffer 或静态区的内存对其初始化, ustrTest. Buffer = szHello; ustrTest.Length = wcslen(szHello) * sizeof(WCHAR); ustrTest.MaximumLength = sizeof(szHello); |
用从堆上分配一个内存对其初始化
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | / / / 定义并初始化一个UNICODE_STRING字符串 UNICODE_STRING ustrTest = { 0 }; WCHAR * szHello = L "Hello,world!" ; / / / < 全局变量WCHAR * szHello,指向L "Hello,world!" 宽字节字符串常量,L "Hello,world!" 存放在静态区的.data / / / 设置有效长度 ULONG ulLength = wcslen(szHello) * sizeof(WCHAR); / / / @brief PVOID p = ExAllocatePoolWithTag(Pool_Type, Size, Tag); 在调用ExAllocatePoolWithTag的时候,系统会在要求的内存大小的基础上再额外多分配 4 个字节的标签.这个标签占用了开始的 4 个字节,位于返回指针所指向地址的前面.这样,当调试时这个标签可以帮助你识别有问题的内存块. / / / @param MAX_PATH * sizeof(WCHAR)表示待分配的内存的大小(是字节数) / / / 在驱动中UNICODE_STRING一般用来表示设备对象名,符号链接,文件路径,所以 * * 字符数 * * 不会超过MAX_PATH)MAX_PATH宏是 260 ,表示在windows系统中一个文件的路径最大个 * * 字符数 * * 。(除去一个盘符,一个 ':' ,一个 '\',一个' \ 0 ',所以实际上文件路径剩下可修改字符数的是 256 ) / / / 为 Buffer 分配一个堆上的内存 ustrTest. Buffer = ExAllocatePooMWithTag(PagedPool,MAX_PATH * sizeof(WCHAR), 'POCU' ); / / tag反过来写,低位优先,调试看到是的 'UCOP' ,即unicode_string operation / * * 问题代码 uPath. Buffer = ExAllocatePooMWithTag(PagedPool, MAX_PATH, / / 这里是字节数,而不是字符数, 260Byte 只能表示 130 个宽字节字符,当文件路径超过 130 个字符的时候就会出问题 'POCU' ); * / / / / 如果分配失败,则 return if (ustrTest. Buffer = = NULL) { return ; } / / / 分配成功,则对 Buffer 的指向的堆上内存初始化为 0 RtlZeroMemory(ustrTest. Buffer , MAX PATH * sizeof(WCHAR)); / / / 把L "Hello,world" 拷贝到 Buffer 指向的内存中去,是深拷贝 memcpy(ustrTest. Buffer ,szHello,ulLength); / / memcpy并不安全,会访问越界,破坏了dest后面的数据,并且可能我们还不知道。而memcpy_s就会弹出一个对话框提醒我们。 / / / 设置有效长度 ustrTest.Length = ulLength; / / / 设置最大长度 ustrTest.MaximumLength MAX_PATH * sizeof(WCHAR); DbgPrint( "%wZ\n" ,&ustrTest); / / / 通过上面这样初始化之后, Buffer 指向的堆上的内存,但如果用RtllnitunicodeString初始化 Buffer 指向了静态区的常量字符串 / / / 下面释放 Buffer 所指向的内存,不是堆上内存,内存泄漏了,同时释放的是静态区内存,系统自我保护蓝屏了。 / / / RtllnitunicodeString(ustrTest,L "Hello,world" ); / / 是浅拷贝(只把字符串str1的地址拷贝到uStr1. buffer 中), buffer 直接指向字符串str1 / / / 堆上分配的内存需要手动把它释放掉 ExFreePool(ustrTest. Buffer ); / / / 如果下面还需要使用ustrTest,一定要把它设为NEULL / / / 如果执行完ExFreePool(ustrTest. Buffer );函数就返回了,就不需要设置为NULL / / / ustrTest. Buffer = NULL; |
- 拷贝
- 拼接
- 拆分
- 比较
- 编码转换
1 2 3 4 5 6 7 | / / / 把多字节字符串转换成宽字节字符串 / / / @param TRUE / FALSE 转换的过程中内存的大小会发生变化,涉及内存分配和计算,TRUE表示交给系统去计算内存的大小和分配内存,FALSE则表示程序员自己去计算和分配内存 RtlAnsiStringToUnicodeString(&uStr1,&aStr1,TRUE / FALSE); / / / 如果前面设置为FALSE,系统帮忙计算和分配内存,用完之后一定要释放掉,否则会造成内存泄漏 / / / 实际上很少会遇到字符串编码的转换,因为内核中用的都是UNICODE_STRING / / / DbgPrint在打印 unicode 中文的话,在debug view里面是看不到的,这种情况下就需要把 unicode 中文转化成多字节编码才能看到 RtlFreeUnicodeString(&uStr1); |
安全函数
1 2 3 4 5 6 | / / / unicode 编码字符 typedef struct UNICODE_STRING{ USHORT Length; / / / < 字节数,不是字符数,而是有效数据的字节数 USHORT MaximumLength; / / / < 字节数,告诉系统函数最多有多少内存可用 PWSTR Buffer ; / / / < 只是一个指针,一旦定义之后并没有内存,需要正确初始化之后,让 Buffer 含有真正的数据,才拥有内存. * * 非零结尾,中间也可能含有 0 * * ,PWSTR等价于WCHAR }UNICODE_STRING, * PUNICODE_STRING; |
在UNICODE_STRNG的类型定义中可以发现,是存在UNICODE_STRING能存储最大字符长度是65536(USHORT取值范围是0-(2^16-1)
),能表示65536/2 -1 = 32,767
个字符,而在上述的对字符串操作的函数
中,都没有溢出检测
的,存溢出风险
1 2 3 4 5 6 7 8 9 | / / / 可以发现动词放在后面的就是安全函数 / / 安全函数,溢出检测 #include <ntstrsafe.h> / / / str1,&uStr2超过了 32767 ,或者uStr1 + uStr2超过了 32767 ,函数就会返回失败 RtlUnicodeStringInit(&uStr1,str1); RtlUnicodeStringCopy(&uStr1,&uStr2); RtlUnicodeStringCat(&uStr1,&uStr2); / / / 不能超过 32767 个字符 #define NTSTRSAFE UNICODE_STRING_MAX_CCH(Oxffff / sizeof((wchar t)) |
实战
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | #include <ntddk.h> #include <ntstrsafe.h> VOID OperUnicodeStr(VOID); VOID DriverUnload(PDRIVER_OBJECT pDriverObject); / / / 没有涉及R3通信,所以在DriverEntry里面就不创建设备对象和符号链接了,也没有注册其他分发函数. NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath) { UNREFERENCED_PARAMETER(pRegPath); DbgPrint( "Driver loaded\n" ); pDriverObject - >DriverUnload = DriverUnload; OperUnicodeStr(); / / 驱动启动的就会执行 return STATUS_SUCCESS; } / / / 对字符串的操作 VOID OperUnicodeStr(VOID) { UNICODE_STRING uStr1 = { 0 }; UNICODE_STRING uStr2 = { 0 }; UNICODE_STRING uStr3 = { 0 }; / / / < 直接定义, Buffer 没有内存( Buffer = NULL),必须正确初始化,让 Buffer 含有真正的数据. UNICODE_STRING uStr4 = { 0 }; / / / < UNICODE_STRING字符串, unicode 编码字符 ANSI_STRING aStr1 = { 0 }; / / / < ANSI_STRING字符串,多字节编码字符 WCHAR szHello[ 512 ] = L "Hello" ; / / / < 局部变量WCHAR * szHello在栈上,里面存放L "Hello,world!" 宽字节字符串. WCHAR szWorld[ 256 ] = L "World" ; WCHAR szCopiedStr[ 1024 ] = L""; UNICODE_STRING uHello = { 0 }; UNICODE_STRING uWorld = { 0 }; UNICODE_STRING uCopyiedStr = { 0 }; / / / 用字符串常量对其初始化 / / / 1.Length = wcslen(szHello) * sizeof(WCHAR);不含 '\0' / / / 2.MaximumLength = (wcslen(szHello) + 1 ) * sizeof(WCHAR);,含 '\0' / / / 3. 直接将L "hello" 字符串的指针赋给了uStr. Buffer ; buffer 是浅拷贝(只把常量字符串的地址拷贝到 buffer 中), buffer 直接指向L "hello" RtlInitUnicodeString(&uStr1, L "hello" ); RtlInitUnicodeString(&uStr2, L "Goodbye" ); DbgPrint( "%ws\n" , L "hello world" ); DbgPrint( "uStr1=%wZ\n" , &uStr1); DbgPrint( "uStr2=%wZ\n" , &uStr2); / * * * @brief RtlInitAnsiString 例程初始化 ANSI 字符的计数字符串; * @param[out] DestinationString 指向要初始化的 ANSI_STRING 结构的指针; * @param[ in , optional] SourceString 指向以 null 结尾的字符串的指针.此字符串用于初始化 DestinationString指向的计数字符串; * @ return void * @see https: / / docs.microsoft.com / zh - cn / windows - hardware / drivers / ddi / wdm / nf - wdm - rtlinitansistring * @author microsoft * / RtlInitAnsiString(&aStr1, "Ansi to unicode" ); DbgPrint( "aStr1=%Z\n" , &aStr1); / * * * @brief RtlCopyUnicodeString 将源字符串复制到目标字符串;深拷贝(copy值,不是地址),很明显名字中有copy字样; * @param[ in , out] DestinationString 指向目标字符串缓冲区的指针。 此参数指向 UNICODE_STRING 结构; * @param[ in , optional] SourceString 指向源字符串缓冲区的指针。 此参数指向 UNICODE_STRING 结构。 * @ return void * @see https: / / docs.microsoft.com / zh - cn / windows - hardware / drivers / ddi / wdm / nf - wdm - rtlcopyunicodestring * @author microsoft * / RtlCopyUnicodeString(&uStr3, &uStr1); DbgPrint( "uStr3=%wZ\n" , &uStr3); / / 失败:只是定义了uStr3, Buffer 没有内存( Buffer = NULL),必须正确初始化,让 Buffer 含有真正的数据. / / / 拼接,简单以 '\0' 结尾的C语言中的宽字节字符串拼接到UNICODE_STRING上 RtlAppendUnicodeToString(&uStr1, L "world" ); DbgPrint( "uStr1=%wZ\n" , &uStr1); / / 失败:RtlInitUnicodeString(&uStr1, L "hello" );后,uStr1指向的是常量字符串,往常量字符串拷贝会失败 / / / 拼接,UNICODE_STRING拼接到UNICODE_STRING上 RtlAppendUnicodeStringToString(&uStr1, &uStr2); DbgPrint( "uStr1=%wZ\n" , &uStr1); / / 失败:同上 / * * * @brief RtlCompareUnicodeString 比较两个 Unicode 字符串。 * @param[ in ] String1 指向第一个字符串的指针。 * @param[ in ] String2 指向第二个字符串的指针。 * @param[ in ] CaseInSensitive 如果 为 TRUE,则执行比较时应忽略大小写。; * @ return LONG 0 表示相等,负数表示uStr1 < uStr1,正数则表示uStr1 > uStr1 * @see https: / / docs.microsoft.com / zh - cn / windows - hardware / drivers / wdf / driverentry - for - kmdf - drivers * @author microsoft * / if (RtlCompareUnicodeString(&uStr1, &uStr2, TRUE) = = 0 ) { DbgPrint( "%wZ == %wZ\n" , &uStr1, &uStr2); } else { DbgPrint( "%wZ != %wZ\n" , &uStr1, &uStr2); } / * * * @brief RtlAnsiStringToUnicodeString 将给定的 ANSI 源字符串转换为 Unicode 字符串; * @param[ in , out] DestinationString 指向 用于UNICODE_STRING Unicode 字符串的字符串的指针。 如果 AllocateDestinationString 为 TRUE,例程将分配一个新缓冲区来保存字符串数据,并更新 DestinationString 的 Buffer 成员以指向新缓冲区。 否则,例程使用当前指定的缓冲区来保存字符串。 * @param[ in ] SourceString 指向要转换为 Unicode 的 ANSI 字符串的指针。 * @param[ in ] AllocateDestinationString 指定此例程是否应为目标字符串分配缓冲区空间。 如果这样做,调用方必须通过调用 RtlFreeUnicodeString 来解除分配缓冲区。 * @ return int 如果转换成功, RtlAnsiStringToUnicodeString 将 返回STATUS_SUCCESS。 如果失败,例程不会分配任何内存。; * @see https: / / docs.microsoft.com / zh - cn / windows - hardware / drivers / ddi / wdm / nf - wdm - rtlansistringtounicodestring * @author microsoft * / RtlAnsiStringToUnicodeString(&uStr3, &aStr1, TRUE); / / TRUE: memory allocation for uStr1 and should be freed by RtlFreeUnicodeString DbgPrint( "uStr3=%wZ\n" , &uStr3); / / 成功 RtlFreeUnicodeString(&uStr3); / / RtlAnsiStringToUnicodeString(&uStr3, &aStr1, FALSE); / / DbgPrint( "uStr3=%wZ\n" , &uStr3); / / 成功 / / RtlFreeUnicodeString(&uStr3); / / / 用栈上的 buffer 或静态区的内存对其初始化 RtlInitUnicodeString(&uHello, szHello); / / uHello.MaximumLength = sizeof(szHello); DbgPrint( "uHello=%wZ\n" , &uHello); DbgPrint( "uWorld.MaximumLength=%hd\n" , &uHello.MaximumLength); RtlInitUnicodeString(&uWorld, szWorld); DbgPrint( "uWorld=%wZ\n" , &uWorld); RtlInitUnicodeString(&uCopyiedStr, szCopiedStr); / / uCopyiedStr.MaximumLength = sizeof(szCopiedStr); DbgPrint( "uCopyiedStr=%wZ\n" , &uCopyiedStr); RtlAppendUnicodeStringToString(&uHello, &uWorld); DbgPrint( "uHello=%wZ\n" , &uHello); RtlAppendUnicodeToString(&uHello, szWorld); DbgPrint( "uHello=%wZ\n" , &uHello); RtlCopyUnicodeString(&uCopyiedStr, &uHello); DbgPrint( "uCopyiedStr=%wZ\n" , &uCopyiedStr); / / / 用从堆上分配一个内存对其初始化 uStr4. Buffer = ExAllocatePoolWithTag(PagedPool, (wcslen(L "Nice to meet u" ) + 1 ) * sizeof(WCHAR), 'POCU' ); if (uStr4. Buffer = = NULL) { return ; } RtlZeroMemory(uStr4. Buffer , (wcslen(L "Nice to meet u" ) + 1 ) * sizeof(WCHAR)); uStr4.Length = uStr4.MaximumLength = (wcslen(L "Nice to meet u" ) + 1 ) * sizeof(WCHAR); / / 不能调用RtlIniUnicodeString()来初始化,下面释放的时候会引起蓝屏 RtlCopyMemory(uStr4. Buffer , L "Nice to meet u" , (wcslen(L "Nice to meet u" ) + 1 ) * sizeof(WCHAR)); DbgPrint( "%wZ\n" , &uStr4); ExFreePool(uStr4. Buffer ); int ret = 0 ; / / ret = RtlUnicodeStringInit(&uHello, L "safe_hello" ); / / 直接蓝屏了,初始化如果指向一个字符串常量,应该是下面RtlUnicodeStringCopy的对字符串常量进行拷贝的时候就会出问题 ret = RtlUnicodeStringInit(&uHello, szHello); DbgPrint( "safeinit ret=%d,uHello=%wZ\n" , &ret,&uHello); ret = RtlUnicodeStringCopy(&uHello, &uStr1); DbgPrint( "safecopy ret=%d,uHello=%wZ\n" ,&ret,&uHello); ret = RtlUnicodeStringCat(&uHello, &uWorld); DbgPrint( "safecat ret=%d,uHello=%wZ\n" ,&ret,&uHello); } VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { DbgPrint( "Driver unloaded!\n" ); } |
全局句柄表
- 什么是
内核对象
?
- 像进程、线程、文件、互斥体、事件等在内核都有一个对应的
结构体
,这些结构体由内核负责管理。我们管这样的对象叫做内核对象
。
- 如何
管理
内核对象?
- 方式1:直接返回内核对象的地址,
不可取
- 内核对象是在R0创建的,即地址在0x80000000以上,如果R3修改这个内核对象的地址,一旦指向了无效的内核内存地址就会
蓝屏
。
- 内核对象是在R0创建的,即地址在0x80000000以上,如果R3修改这个内核对象的地址,一旦指向了无效的内核内存地址就会
- 方式2:使用句柄
- 句柄存在的
目的
是:为了避免在应用层直接修改
内核对象。
- 句柄存在的
- 句柄是什么?
- windows定义了很多内核对象:
进程对象
、线程对象
、互斥量对象
、信号量对象
、事件对象
、文件对象
等等。在调用相应的函数创建
这些对象后,将获得一个句柄,我们都可以通过HANDLE
类型的句柄来引用它们。
1 2 3 4 | HANDLE g_hMutex = ::CreateMutex(NULL,FALSE, "XYZ" ); HANDLE g_hMutex = ::OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ" ); HANDLE g_hEvent = ::CreateEvent(NULL,TRUE,FALSE, NULL); HANDLE g_hThread = ::CreateThread(NULL, 0 ,Proc, NULL,O, NULL); |
- 自己创建的内核对象都在句柄表里面
- 在windows系统中,主要分为两种句柄表:
- 1、单个进程的句柄表
- 2、系统全局句柄表
PspCidTable
- 两者没有关系。
- 前者主要用于进程打开的各种对象,而后者用于分配全局进程PID。
- 比如,任务管理器
关闭
某个进程,如果其要关闭一个进程,首先根据进程PID
打开其进程并获取访问这个进程的句柄
,这时,PID对应在PspHandleTable
中的索引
,而获得的句柄对应任务管理器的句柄表中的索引,仅仅在任务管理器的进程空间
中有效,一个全局、一个局部。而解析句柄和PID的过程完全一致。 - 主要区别在于全局句柄表的表项指向的是
对象体
而不是对象头
。
- 多进程
共享
一个内核对象
- 句柄表是进程
私有
的,句柄的索引值只在当前进程中有效 Closehandle()
只是把引用计数减一,内核对象引用计数变0才会真正被销毁。- 线程和进程内核对象比较特殊,需要把
线程关掉
(进程则是关闭其中的所有线程))&&线程内核对象的引用计数为0
,线程内核对象才会被关闭。
- 线程和进程内核对象比较特殊,需要把
句柄是否可以被
继承?
句柄表在哪?
- 一个进程可打开
多个
对象,就会拥有多个
句柄,所以每个进程都应该拥有一个句柄表,在进程控制块EPROCESS
中有个指针ObjectTable
是_HANDLE_TABLE
类型,指向本进程的句柄表!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | typedef struct _HANDLE_TABLE { ULONG_PTR TableCode; / / 指针指向句柄表的存储结构。TableCode的低两位被用作标志位,用于表示当前句柄表的级数, 0 , 1 , 2 分别表示一级表,二级表,三级表。 / / 一级表实际上是一个_HANDLE_TABLE_ENTRY 数组,每个_HANDLE_TABLE_ENTRY 8 个字节,而一级表是一个page的大小,所以一级表可以容纳 4K / 8 = 2 ^ 9 个_HANDLE_TABLE_ENTRY struct _EPROCESS * QuotaProcess; / / 所属进程的指针 HANDLE UniqueProcessId; / / 这个进程的 ProcessID EX_PUSH_LOCK HandleTableLock[HANDLE_TABLE_LOCKS]; / / 句柄表所,仅在句柄表扩展时使用。 LIST_ENTRY HandleTableList; / / 所有的 HandleTAble 在内核中形成一个 List ,这是 Entry EX_PUSH_LOCK hangleConventionEvent / / 若在访问句柄表时发生了竞争则在此锁上等待。 PHANDLE_TRACE_DEBUG_INFO DebugInfo; / / 用来追踪用的调试信息 LONG extrainfoPag; ULONG FirstFree; / / 空闲链表表头句柄索引。 ULONG LastFree; / / 最近被释放的句柄索引。 ULONG NextHandleNeedingPool; / / 下一次句柄表扩展的起始句柄索引。 LONG HandleCount; / / 正在使用的句柄表项数量。 union{ ULONG Flags; / / 标志域 BOOLEAN StrictFIFO: 1 ; / / 是否使用FIFO风格的重用。 }; } HANDLE_TABLE, * PHANDLE_TABLE; |
文件
文件的表示
- 应用层:
"c\\doc\\hi.txt"
- 内核:
L"\\??\\c:\\hi.txt"
-->"\\device\\harddiskvolume3\\hi.txt
- 应用层:
- 设备名:
L"\\\\.\\xxxDrv"
其中xxxDrv
代表符号链接名,把设备对象
当作一个特殊的文件
打开,打开得到一个句柄。
- 设备名:
- 内核层:
- 对文件操作
流程
:打开
文件获得handle -> 基于handle读写删除查询
->关闭
- 创建文件/文件夹
- 读/写
- 拷贝
- 移动
- 删除
- 属性访问与设置
实战
内核层
- 对文件操作
流程
:打开
文件获得handle -> 基于handle读写删除查询
->关闭
- 每个API都有对应的
Irp
,但复制、粘贴、移动没有对应的Irp
,因为这三个动作本质是读和写
ZwCreateFile
ZwCreateFile
创建/打开文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | / / / @brief DriverEntry NtCreateFile 例程创建一个新文件或打开一个现有文件。 ZwCreateFile( _Out_ PHANDLE FileHandle, / / 指向接收文件句柄的 HANDLE 变量的指针。 _In_ ACCESS_MASK DesiredAccess, / / 指定一个ACCESS_MASK值,该值确定对对象的请求访问权限。 _In_ POBJECT_ATTRIBUTES ObjectAttributes, / / 要打开的文件路径 _Out_ PIO_STATUS_BLOCK IoStatusBlock, / / 操作的结果,指向IO_STATUS_BLOCK结构的指针,该结构接收最终完成状态和有关所请求操作的其他信息 _In_opt_ PLARGE_INTEGER AllocationSize, / / 指向LARGE_INTEGER的指针,该LARGE_INTEGER包含创建或覆盖的文件的初始分配大小(以字节为单位)。如果 "分配大小" 为 NULL,则未指定分配大小。如果未创建或覆盖任何文件,则忽略分配大小。 _In_ ULONG FileAttributes, / / 指定一个或多个FILE_ATTRIBUTE_XXX 标志,这些标志表示在创建或覆盖文件时要设置的文件属性,调用方通常指定FILE_ATTRIBUTE_NORMAL,从而设置默认属性。 _In_ ULONG ShareAccess, / / 创建 / 打开这个文件的共享访问的类型,指定为零或以下标志的任意组合。共享读|共享写|共享删除,如果设为 0 ,则进程以独占的方式打开这个文件,其他进程没办法再打开它,也就没办法删除它,但还是有其他办法强删的 _In_ ULONG CreateDisposition, / / 指定在文件存在或不存在时要执行的操作。FILE_OPEN_IF,存在则打开这个文件,不存在则创建这个文件。 360 在处理文件创建拦截的时候,曾经在FILE_OPEN_IF出现过漏洞,流氓软件生成仿造系统软件的图标诱导用户点击,给网站导流获取收益。因为当时 360 考虑到大部分文件都是以FILE_OPEN、FILE_OPEN_IF方式打开的,如果监控会拖慢系统性能,但后来还是把这个标志加入到监控中来了。 _In_ ULONG CreateOptions, / / 指定驱动程序创建或打开文件时要应用的选项。同步操作的标志、普通文件标志、文件夹标志 _In_reads_bytes_opt_(EaLength) PVOID EaBuffer, / / 对于设备和中间驱动程序,此参数必须是 NULL 指针。 _In_ ULONG EaLength / / 对于设备和中间驱动程序,此参数必须为零。 ); |
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 29 30 31 32 33 34 35 36 37 38 39 | / / / 创建文件 NTSTATUS ntCreateFile(WCHAR * szFileName) / / L "\\??\\c:\\doc\\1.txt" { OBJECT_ATTRIBUTES objAttrib = { 0 }; UNICODE_STRING uFileName = { 0 }; IO_STATUS_BLOCK io_status = { 0 }; HANDLE hFile = NULL; NTSTATUS status = 0 ; RtlInitUnicodeString(&uFileName, szFileName); InitializeObjectAttributes( &objAttrib, & uFileName, / / OBJ_CASE_INSENSITIVE 文件大小写不敏感 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, / / OBJ_KERNEL_HANDLE表示内核句柄,放到全局句柄表里(非进程自己的句柄表),应用层无法访问 NULL, NULL ); status = ZwCreateFile( &hFile, GENERIC_WRITE, &objAttrib, &io_status, NULL, FILE_ATTRIBUTE_NORMAL, / / 文件 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, / / 文件 NULL, 0 ); if (NT_SUCCESS(status)) { ZwClose(hFile); } return status; } |
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 29 30 31 32 33 34 35 | / / / 创建目录 NTSTATUS ntCreateDirectory(WCHAR * szDirName) / / L "\\??\\c:\\doc\\" { OBJECT_ATTRIBUTES objAttrib = { 0 }; UNICODE_STRING uDirName = { 0 }; IO_STATUS_BLOCK io_status = { 0 }; HANDLE hFile = NULL; NTSTATUS status = 0 ; RtlInitUnicodeString(&uDirName, szDirName); InitializeObjectAttributes(&objAttrib, &uDirName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hFile, GENERIC_READ | GENERIC_WRITE, &objAttrib, &io_status, NULL, FILE_ATTRIBUTE_DIRECTORY, / / 文件夹 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, / / 文件夹 NULL, 0 ); if (NT_SUCCESS(status)) { ZwClose(hFile); } return status; } |
ZwWriteFile
ZwWriteFile
写文件,是相对于应用层来说,数据流向:r3->R0
1 2 3 4 5 6 7 8 9 10 11 | ZwWriteFile( hDstFile, NULL, NULL, NULL, &io_status buffer , length, &offset, NULL / / null表示同步 ); |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | NTSTATUS ntWriteFile(WCHAR * szFileName) { OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iostatus = { 0 }; HANDLE hfile = NULL; UNICODE_STRING uFile = { 0 }; LARGE_INTEGER number = { 0 }; PUCHAR pBuffer = NULL; NTSTATUS ntStatus = STATUS_SUCCESS; RtlInitUnicodeString( &uFile, szFileName); InitializeObjectAttributes(&objectAttributes, &uFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL ); / / 创建文件 ntStatus = ZwCreateFile( &hfile, GENERIC_WRITE, &objectAttributes, &iostatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } pBuffer = (PUCHAR)ExAllocatePoolWithTag(PagedPool, 1024 , 'ELIF' ); if (pBuffer = = NULL) { ZwClose(hfile); return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(pBuffer, 1024 ); RtlCopyMemory(pBuffer, L "Hello, world" , wcslen(L "Hello, world" ) * sizeof(WCHAR)); / / 写文件 ntStatus = ZwWriteFile(hfile,NULL,NULL,NULL,&iostatus,pBuffer, 1024 ,NULL,NULL); ZwClose(hfile); ExFreePool(pBuffer); return ntStatus; } |
ZwReadFile
ZwReadFile
读文件,是相对于应用层来说,数据流向:r0->R3
1 2 3 4 5 6 7 8 9 10 11 | ZwReadFile( hSrcFile NULL, NULL, NULL, &io_status, buffer , PAGE_SIZE &offset NULL / / null表示同步 ); |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | NTSTATUS ntReadFile(WCHAR * szFile) { OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iostatus = { 0 }; HANDLE hfile = NULL; UNICODE_STRING uFile = { 0 }; FILE_STANDARD_INFORMATION fsi = { 0 }; PUCHAR pBuffer = NULL; NTSTATUS ntStatus = 0 ; RtlInitUnicodeString( &uFile, szFile); InitializeObjectAttributes(&objectAttributes, &uFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL ); ntStatus = ZwCreateFile( &hfile, GENERIC_READ, &objectAttributes, &iostatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } ntStatus = ZwQueryInformationFile(hfile, &iostatus, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(ntStatus)) { ZwClose(hfile); return ntStatus; } pBuffer = (PUCHAR)ExAllocatePoolWithTag(PagedPool, ( LONG )fsi.EndOfFile.QuadPart, 'ELIF' ); / / 一次性读有问题,应该循环读,比如文件很大,不可能为其分配这么大的内存 if (pBuffer = = NULL) { ZwClose(hfile); return STATUS_INSUFFICIENT_RESOURCES; } ntStatus = ZwReadFile( hfile, NULL, NULL, NULL, &iostatus, pBuffer, ( LONG )fsi.EndOfFile.QuadPart, NULL,NULL); ZwClose(hfile); ExFreePool(pBuffer); return ntStatus; } |
拷贝
- 拷贝其实是用
ZwReadFile
把源文件读出来,用ZwCreateFile
写到目标文件中去
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | / / / copy NTSTATUS ntCopyFile(const WCHAR * src, const WCHAR * dst) { HANDLE hSrcFile = NULL; HANDLE hDstFile = NULL; UNICODE_STRING uSrc = { 0 }; UNICODE_STRING uDst = { 0 }; OBJECT_ATTRIBUTES objSrcAttrib = { 0 }; OBJECT_ATTRIBUTES objDstAttrib = { 0 }; NTSTATUS status = 0 ; ULONG uReadSize = 0 ; ULONG uWriteSize = 0 ; ULONG length = 0 ; PVOID buffer = NULL; LARGE_INTEGER offset = { 0 }; IO_STATUS_BLOCK io_status = { 0 }; RtlInitUnicodeString(&uSrc, src); RtlInitUnicodeString(&uDst, dst); InitializeObjectAttributes(&objSrcAttrib, &uSrc, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); InitializeObjectAttributes(&objDstAttrib, &uDst, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile( &hSrcFile, FILE_READ_DATA | FILE_READ_ATTRIBUTES, &objSrcAttrib, &io_status, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(status)) { return status; } status = ZwCreateFile( &hDstFile, GENERIC_WRITE, &objDstAttrib, &io_status, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(status)) { ZwClose(hSrcFile); return status; } buffer = ExAllocatePoolWithTag(PagedPool, 1024 , 'ELIF' ); if ( buffer = = NULL) { ZwClose(hSrcFile); ZwClose(hDstFile); return STATUS_INSUFFICIENT_RESOURCES; } while ( 1 ) { status = ZwReadFile ( hSrcFile,NULL,NULL,NULL, &io_status, buffer , PAGE_SIZE,&offset, NULL); if (!NT_SUCCESS(status)) { if (status = = STATUS_END_OF_FILE) / / 读完 { status = STATUS_SUCCESS; } break ; } length = (ULONG)io_status.Information; status = ZwWriteFile( hDstFile,NULL,NULL,NULL, &io_status, buffer ,length,&offset, NULL); if (!NT_SUCCESS(status)) break ; offset.QuadPart + = length; } ExFreePool( buffer ); ZwClose(hSrcFile); ZwClose(hDstFile); return status; } |
移动
- 现实的移动文件是
重命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | / / / 复制再删除 NTSTATUS ntMoveFile(const WCHAR * src, const WCHAR * dst) { NTSTATUS status = 0 ; status = ntCopyFile(src, dst); / / 复制 if (NT_SUCCESS(status)) { status = ntDeleteFile2(src); / / 删除 } return status; } |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | NTSTATUS ntMoveFile2(const WCHAR * src, const WCHAR * reNamePath) { UNICODE_STRING srcFileName; OBJECT_ATTRIBUTES object ; NTSTATUS status; HANDLE hFile; IO_STATUS_BLOCK io_status = { 0 }; PFILE_RENAME_INFORMATION pri = NULL; PFILE_OBJECT fileObject; RtlInitUnicodeString(&srcFileName, src); InitializeObjectAttributes( & object , &srcFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); / / 打开文件,存在打开,不存在返回错误 status = ZwCreateFile(&hFile, GENERIC_ALL, & object , &io_status, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(status)) { DbgPrint( "ZwCreateFile error" ); return status; } / / 这里分配的长度应该是PFILE_RENAME_INFORMATION结构体加上要赋值FileName的长度 pri = (PFILE_RENAME_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, sizeof(FILE_RENAME_INFORMATION) + (wcslen(reNamePath)) * sizeof(WCHAR), 'TSET' ); if (!pri) { ZwClose(hFile); return STATUS_INSUFFICIENT_RESOURCES; } memset(pri, 0 , sizeof(FILE_RENAME_INFORMATION) + (wcslen(reNamePath) ) * sizeof(WCHAR)); pri - >FileNameLength = (wcslen(reNamePath) ) * sizeof(WCHAR); RtlCopyMemory(pri - >FileName, reNamePath, pri - >FileNameLength); status = ZwSetInformationFile(hFile, &io_status, pri, sizeof(FILE_RENAME_INFORMATION) + (wcslen(reNamePath)) * sizeof(WCHAR), FileRenameInformation); / / 长度错误可能返回 0xC0000033 STATUS_OBJECT_NAME_INVALID / / if (!NT_SUCCESS(status)) { ZwClose(hFile); return status; } ZwClose(hFile); return status; } |
ZwQuerylnformationFile
ZwQuerylnformationFile
查询文件信息,大小,只读属性,文件访问时间,文件还是目录等
1 2 3 4 5 6 7 8 | ZwQueryInformationFile( handle, / / 打开文件的句柄 &iosb, / / io操作的完成状态 &basiclnfo, / / 存放属性的结构体,把查询的属性放到这个结构体里面返回 sizeof(basicInfo), File Basiclnformation / / 枚举类型(FileBasiclnformation(基本属性)、FileStandardInformation(标准属性)、FileDispositionlnformation(删除文件的属性)、FilePositionlnformation(读取或者设置当前文件的读写指针)、FileRenamelnformation(重命名)),指定查询结构体的类型 ); |
ZwQueryFullAttributesFile
ZwQueryFullAttributesFile
查询文件信息,大小,只读属性,文件访问时间,文件还是目录等
1 2 3 | ntStatus = ZwQueryFullAttributesFile( &objAttr, &info); |
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 29 30 31 32 33 34 35 36 | ULONG ntGetFileAttributes(const WCHAR * filename) { ULONG dwRtn = 0 ; NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; OBJECT_ATTRIBUTES objAttr = { 0 }; UNICODE_STRING uName = { 0 }; FILE_NETWORK_OPEN_INFORMATION info = { 0 }; if (filename = = NULL) { return ntStatus; } RtlInitUnicodeString(&uName, filename); RtlZeroMemory(&info, sizeof(FILE_NETWORK_OPEN_INFORMATION)); InitializeObjectAttributes( &objAttr, &uName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL ); ntStatus = ZwQueryFullAttributesFile( &objAttr, &info); if (NT_SUCCESS(ntStatus)) { dwRtn = info.FileAttributes; } if (dwRtn & FILE_ATTRIBUTE_DIRECTORY) { DbgPrint( "%S is a directory\n" , filename); } return dwRtn; } |
ZwSetinformationFile
ZwSetinformationFile
设置文件信息(设置文件完整信息,查询文件信息,大小,只读属性,文件访问时间等)- 对应的IRP:
irp_mj_set_information
- 在这里还有两个重要的Irp(重命名和删除,
次功能号
)
- 在这里还有两个重要的Irp(重命名和删除,
1 2 3 4 5 6 7 8 | ZwSetlnformationFile( handle, / / 打开文件的句柄 &iosb, / / io操作的完成状态 &basicInfo, / / 存放属性的结构体,具体传入哪个结构体由下面参数FilexxxInformation决定 sizeof(basiclnfo), FileBasiclnformation / / 枚举类型(FileBasicInformation(基本属性)、FileStandardInformation(标准属性)、FileDispositionInformation(删除文件的属I性)、FilePositionInformation(读取或者设置当前文件的读写指针)、FileRenameInformation(重命名)),指定查询结构体的类型 ); |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | / / / 基本信息 typedef struct _FILE_BASIC_INFORMATION{ LARGE_INTEGER CreationTime; / / / < 创建时间 LARGE_INTEGER LastAccessTime; / / / < 最后访问时间 LARGE_INTEGER LastWriteTime; / / / < 最后写入时间 LARGE_INTEGER ChangeTime; / / / < 修改时间 ULONG FileAttributes; / / / < 文件的属性 }FILE_BASIC_INFORMATION, * PFILE_BASIC_INFORMATION; / / / 标准信息 typedef struct _FILE_STANDARD_INFORMATION{ LARGE_INTEGER AllocationSize; / / / < 文件占磁盘空间的大小 bit - * 8 - >Byte - * 512 - >sector - * 8 - >lcluster( 4k ,簇,文件系统基本单位),即一个文件即使真实大小只有 1B ,文件占磁盘空间的大小是 4K 。( 4K - 1 )的空间就是碎片,这些隐藏碎片还可以用来存一些其他东西。 / / / 但win10中ntfs系统做了些优化,每个文件和文件夹都是以记录的形式存放在MFT表(两份,一份是备份)里面,如果文件小,文件的数据就直接存放在记录里面,不用占额外的磁盘空间了,如果文件大,就需要分配额外的簇来存放。 LARGE_INTEGER EndOfFile; / / / < 文件真实的大小 LONG NumberOfLinks; / / / < 软链接 BOOLEAN DeletePending; / / / < 删除延迟标志 BOOLEAN Directory; / / / < 是否为目录 }FILE_STANDARD_INFORMATION, * PFILE_STANDARD_INFORMATION; / / / 当前文件读写指针的位置 typedef struct _FILE_POSITION_INFORMATIONO{} LARGE INTEGER CurrentByteOffset; / / / 当前字节的偏移,读 / 写指针距离文件头部的偏移量 }FILE_POSITION_INFORMATION; * PFILE_POSITION_INFORMATION; / / / 当使用ZwSetinformationFile重命名文件的时候,目标名放在这个结构体里面的FileName[ 1 ]上 typedef struct _FILE_RENAME_INFORMATION{ BOOLEAN ReplacelfExists; / / / < 如果原来存在与改名后的文件相同的文件,是否将其覆盖 HANDLE RootDirectory; ULONG FileNameLength; / / / < 指定FileName[]的长度 WCHAR FileName[ 1 ]; / / / < 重命名之后的文件名,只有一个字符,是个变长数组, 长度由FileNameLength指定 / / 定义缓存的方式 1 :UNICODE_STRING 字符指针,在别的地方为其分配内存 / / 定义缓存的方式 2 :WCHAR Buffer [MAX_PATH]; 字符数组,长度是固定的 / / 方式 3 : FileName[ 1 ]; }FILE_RENAME_INFORMATION, FILE_RENAME_INFORMATION; / / / 几乎包含全部属性 typedef struct _FILE_NETWORK_OPEN_INFORMATION{ LARGE INTEGER CreationTime LARGEINTEGER LastAccessTime; LARGE INTEGER LastWriteTime; LARGE INTEGER AllocationSize: LARGE INTEGER ChangeTime; ALARGE INTEGER EndOfFile; ULONG FileAttributes; }FILE_NETWORK_OPEN_INFORMATION, * PFILE_NETWORK_OPENXINFORMATION; / / / 删除文件 如果文件只读,独占,或者是正在运行.exe都会导致删除失败 typedef struct _FLE_DISPOSITION_INFORMATION{ BOOLEAN DeleteFile; }FILE_DISPOSITION_INFORMATION, * PFILE_DISPOSITION_INFORMATION: |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | NTSTATUS ntSetFileAttribute (WCHAR * szFileName) { OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iostatus = { 0 }; HANDLE hfile = NULL; UNICODE_STRING uFile = { 0 }; FILE_STANDARD_INFORMATION fsi = { 0 }; FILE_POSITION_INFORMATION fpi = { 0 }; NTSTATUS ntStatus = 0 ; RtlInitUnicodeString( &uFile, szFileName); InitializeObjectAttributes(&objectAttributes, &uFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL ); ntStatus = ZwCreateFile( &hfile, GENERIC_READ, &objectAttributes, &iostatus, NULL, FILE_ATTRIBUTE_NORMAL, 0 , FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } ntStatus = ZwQueryInformationFile(hfile, &iostatus, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(ntStatus)) { ZwClose(hfile); return ntStatus; } fpi.CurrentByteOffset.QuadPart = 100i64 ; ntStatus = ZwSetInformationFile(hfile, &iostatus, &fpi, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation); ZwClose(hfile); return ntStatus; } |
ZwDeleteFile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | / / / 方式 1 : / / / 只在XP及其以后版本才支持,Windows2000是没有的 NTSTATUS ntDeleteFile1(const WCHAR * filename) { NTSTATUS ntStatus = 0 ; OBJECT_ATTRIBUTES objAttr = { 0 }; UNICODE_STRING uName = { 0 }; RtlInitUnicodeString(&uName, filename); InitializeObjectAttributes( &objAttr, &uName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL ); ntStatus = ZwDeleteFile(&objAttr); return ntStatus; } |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | / / / 方式 2 :删除只读属性的文件 NTSTATUS ntDeleteFile2(const WCHAR * fileName) { OBJECT_ATTRIBUTES objAttributes = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; HANDLE handle = NULL; FILE_DISPOSITION_INFORMATION disInfo = { 0 }; UNICODE_STRING uFileName = { 0 }; NTSTATUS status = 0 ; RtlInitUnicodeString(&uFileName, fileName); InitializeObjectAttributes(&objAttributes, &uFileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL); status = ZwCreateFile( &handle, SYNCHRONIZE | FILE_WRITE_DATA | DELETE, / / 以DELETE权限打开文件 &objAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_DELETE_ON_CLOSE, NULL, 0 ); if (!NT_SUCCESS(status)) { if (status = = STATUS_ACCESS_DENIED) / / 访问失败,权限不够,因为文件只读无法删除 { status = ZwCreateFile( &handle, SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, / / 把文件属性读出来,然后修改,抹除文件的只读属性 &objAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if (NT_SUCCESS(status)) { FILE_BASIC_INFORMATION basicInfo = { 0 }; status = ZwQueryInformationFile(handle, &iosb, &basicInfo, sizeof(basicInfo), FileBasicInformation); / / 把文件的基本属性读出来 if (!NT_SUCCESS(status)) { DbgPrint( "ZwQueryInformationFile(%wZ) failed(%x)\n" , &uFileName, status); } basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; / / 改成normal status = ZwSetInformationFile(handle, &iosb, &basicInfo, sizeof(basicInfo), FileBasicInformation); / / 把修改后的属性在写回去 if (!NT_SUCCESS(status)) { DbgPrint( "ZwSetInformationFile(%wZ) failed(%x)\n" , &uFileName, status); } ZwClose(handle); status = ZwCreateFile( &handle, SYNCHRONIZE | FILE_WRITE_DATA | DELETE, / / 再以DELETE方式打开就可能会成功了 &objAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_DELETE_ON_CLOSE, NULL, 0 ); } } if (!NT_SUCCESS(status)) { DbgPrint( "ZwCreateFile(%wZ) failed(%x)\n" , &uFileName, status); return status; } } disInfo.DeleteFile = TRUE; / / 设为TRUE status = ZwSetInformationFile(handle, &iosb, &disInfo, sizeof(disInfo), FileDispositionInformation); / / 把文件删掉 if (!NT_SUCCESS(status)) { DbgPrint( "ZwSetInformationFile(%wZ) failed(%x)\n" , &uFileName, status); } ZwClose(handle); return status; } |
ZwClose
ZwClose
关闭文件ZwQueryDirectoryFile
ZwQueryDirectoryFile
遍历文件夹,把子文件和普通文件夹遍历出来./
当前目录..
父目录
- 就需要调整
buffer
的大小,使其指数增长
(64B->128B->256B)去测试buffer的长度非递归方法删除文件夹
- 递归会很快把栈上的空间
消耗掉
思路:基于链栈,从堆上分配内存存放栈上的数据 - 每一个
根键
都对应一个宏定义
1 2 3 4 5 | #define HKEY_LOCAL_MACHINE //在驱动中访问注册表只有两条路径:"\\Registry\\Machine\\software"和"\\REGISTRY\\User" #define HKEY_USERS //HKEY_CURRENT_CONFIG、HKEY_CLASSES_ROOT、HKEY_CURRENT_USER都是来源于HKEY_LOCAL_MACHINE和HKEY_USERS #define HKEY_CURRENT_CONFIG #define HKEY_CLASSES_ROOT #define HKEY_CURRENT_USER |
注册表
布局和文件
的布局类似的(左边是key
(类似文件夹),右边是valuekey
(类似文件)),树形结构,B+树
- valuekey有多种类型:
- 定义与构建
多
字符串的定义:"abc\0efg\0hij\0\0"
( str1,str2,str3本身不含'\0'了,也可理解为str1str2str3\0,str1,str2,str3为'\0'结尾)多
字符串的构建:sprintf(buf,"%s%c%s%c%c","1.1.1.1",0,"2.2.2.2",0,0); //sprintf()不安全注意防溢出
删除
与重命名
1 | MoveFileEx(szTemp, NULL,MOVEFILE_DELAY_UNTIL_REBOOT); |
- 应用场景:
- 1.
更新程序
:某个dll有漏洞
,需要更新,从远程服务器下载dll,放在临时文件夹,从临时文件夹把dll文件拷贝到目标文件上,重命名
覆盖原来的文件,有时候动态库dll被占用
了,覆盖会失败
,如果失败了则调用这个函数,提示更新重启
,重启之后在完成覆盖(重启之后会在进程启动之前
完成替换
,就不会dll被占用造成覆盖失败情况了) - 2.
卸载程序
:这个程序正在运行
或者这个程序某个dll文件被其他进程占用
了,卸载程序的时候,被占用的dll删除不掉
,卸载的时候提醒重启
(也是调用这个函数),重启的时候就把文件删掉,早于其他进程启动。
- 1.
- 重启之后,系统是怎么知道要替换/删除的文件的呢?
- 每调用一次MoveFileEx,都会把"szTemp, NULL"记录写到注册表这个位置
HKEY_LOCAL_MACHINE\SYSTEMN\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations"
- 两行为
一对
,把第一行文件覆盖到
第二行的文件,如果第二行为空
,就把第一行删掉
了
- 每调用一次MoveFileEx,都会把"szTemp, NULL"记录写到注册表这个位置
- UNC
- UNC (Universal Naming Convention)通用命名规则
- 安全软件不能只拦截
本地路径
,如果攻击者传入一条UNC
路径,就拦截不到了。需要转换
,把UNC转换成具体
的路径,完善匹配规则 \\servername\sharename\directory\filename
- 输入
"\\\\192.168.0.1\\mydocs\\hi.txt"
就可访问"D:\\Docs\hi.txt"
- 原理是:访问
"\\\\192.168.0.1\\Share\\hi.txt"
会在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Shares
下找到REG_MULTI_SZ
的valuekeyShare
,把Path=E:\Share
读取出来,替换"\\\\192.168.0.1\\mydocs\\hi.txt"
中的mydocs
就得到了具体路径。
- 输入
- 遍历
1 2 3 4 5 6 7 8 9 10 11 | / / / ascii编码 char * buf = "123\0456\0789\0\0" ; for (char * p = buf; * p! = '\0' ;p = p + strlen(p) + 1 ){ printf( "%s\n" ,p); } / / / unicode 编码 char * buf = L "123\0456\0789\0\0" ; for (char * p = buf; * p! = L '\0' ;p = p + wcslen(p) + 1 ){ printf( "%s\n" ,p); } |
注册表的HIVE存储
- 注册表可以看成Win的一个
系统信息
数据库,记录了系统中所有硬件
/驱动
和应用程序
的数据。 - 该数据文件在
缺省配置
情况下是存放在%systemroot%\SYSTEM32\CONFIG
(如果系统安装在c盘,%systemroot%则代表C:\Windows
)目录下的6个HIVE文件:DEFAULT
、SAM
,SECURITY
、SOFTWARE
、USERDIFF
和SYSTEM
中(系统独占资源
,无法直接复制) - 用户的配置信息存放在系统所在磁盘的
users/user
目录,包括ntuser.dat
,ntuser.ini
和ntuser.dat.log
。其中每个文件的路径都由注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist
下的键值指出。 - 一个
HIVE
文件由多个巢箱(BIN
组成,HIVE文件的首部有一个文件头
(基本块
base block),用于描述这个HIVE文件的一些全局信息
。一个BIN由多个巢室(CELL
)组成, CELL可以分为具体的5
种,用于存储不同的注册表数据以容纳一个键
、一个值
、一个安全描述符
、一列子键
或者一列键值
。reghive注册表解析:https://www.52pojie.cn/thread-41492-1-1.html sudami
注册表操作
key
打开
key(ZwOpenKey)
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 | NTSTATUS ntOpenKey(WCHAR * szKey) { UNICODE_STRING RegUnicodeString = { 0 }; HANDLE hRegister = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; NTSTATUS ntStatus = STATUS_SUCCESS; RtlInitUnicodeString( &RegUnicodeString, szKey); InitializeObjectAttributes(&objectAttributes, &RegUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwOpenKey( &hRegister, KEY_ALL_ACCESS, &objectAttributes); if (NT_SUCCESS(ntStatus)) { return ntStatus; } ZwClose(hRegister); return ntStatus; } |
创建
key(ZwCreateKey)
1 2 3 4 5 6 7 8 9 | ZwCreateKey( &hRegister, / / key的句柄 KEY_ALL_ACCESS, &objectAttributes, / / key的路径,类似文件的创建 0 , NULL, REG_OPTION_NON_VOLTILE, / / 回写到文件中去,永久生效,因为注册表是存放在文件中的,只在内存中创建出来,并没有写回到文件中去,如果不设置REG_OPTION_NON_VOLTILE,在系统重启之后,就丢失了 &ulResult ); |
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | / / 创建KEY和SUBKEY示例 NTSTATUS ntCreateKey(WCHAR * szKey) { UNICODE_STRING uRegKey = { 0 }; HANDLE hRegister = NULL; ULONG ulResult = 0 ; OBJECT_ATTRIBUTES objectAttributes = { 0 }; UNICODE_STRING subRegKey = { 0 }; HANDLE hSubRegister = NULL; OBJECT_ATTRIBUTES subObjectAttributes = { 0 }; NTSTATUS ntStatus = STATUS_SUCCESS; RtlInitUnicodeString( &uRegKey, szKey); InitializeObjectAttributes(&objectAttributes, &uRegKey, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwCreateKey( &hRegister, KEY_ALL_ACCESS, &objectAttributes, 0 , NULL, REG_OPTION_NON_VOLATILE, &ulResult); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } / / 开始创建SUBKEY RtlInitUnicodeString( &subRegKey, L "KernelDriver" ); InitializeObjectAttributes(&subObjectAttributes, &subRegKey, OBJ_CASE_INSENSITIVE, hRegister, NULL ); ntStatus = ZwCreateKey( &hSubRegister, KEY_ALL_ACCESS, &subObjectAttributes, 0 , NULL, REG_OPTION_NON_VOLATILE, &ulResult); if (!NT_SUCCESS(ntStatus)) { ZwClose(hRegister); return ntStatus; } ZwClose(hRegister); ZwClose(hSubRegister); return ntStatus; } |
查询key(ZwQueryKey)
枚举
key(ZwEnumerateKey)
- 遍历,把每个
子键
都罗列出来
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | NTSTATUS ntEnumerateSubKey(WCHAR * szKey) { UNICODE_STRING RegUnicodeString = { 0 }; HANDLE hRegister = NULL; ULONG ulSize = 0 ; OBJECT_ATTRIBUTES objectAttributes = { 0 }; NTSTATUS ntStatus = STATUS_SUCCESS; UNICODE_STRING uniKeyName = { 0 }; PKEY_FULL_INFORMATION pfi = NULL; ULONG i = 0 ; PKEY_BASIC_INFORMATION pbi = NULL; RtlInitUnicodeString(&RegUnicodeString,szKey); InitializeObjectAttributes(&objectAttributes, &RegUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwOpenKey( &hRegister, KEY_ALL_ACCESS, &objectAttributes); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } / / 第一次调用ZwQueryKey为了获取KEY_FULL_INFORMATION数据的长度 ntStatus = ZwQueryKey(hRegister, KeyFullInformation, NULL, 0 , &ulSize); if (STATUS_BUFFER_OVERFLOW ! = ntStatus && STATUS_BUFFER_TOO_SMALL ! = ntStatus) { return ntStatus; } pfi = (PKEY_FULL_INFORMATION) ExAllocatePoolWithTag(PagedPool,ulSize, 'SGER' ); if (pfi = = NULL) { ZwClose(hRegister); return STATUS_INSUFFICIENT_RESOURCES; } / / 第二次调用ZwQueryKey为了获取KEY_FULL_INFORMATION数据的数据 ntStatus = ZwQueryKey(hRegister, KeyFullInformation, pfi, ulSize, &ulSize); if (!NT_SUCCESS(ntStatus)) { ExFreePool(pfi); ZwClose(hRegister); return ntStatus; } for (i = 0 ;i<pfi - >SubKeys;i + + ) { / / 第一次调用ZwEnumerateKey为了获取KEY_BASIC_INFORMATION数据的长度 ntStatus = ZwEnumerateKey(hRegister, i, KeyBasicInformation, NULL, 0 , &ulSize); if (STATUS_BUFFER_OVERFLOW ! = ntStatus && STATUS_BUFFER_TOO_SMALL ! = ntStatus) { ZwClose(hRegister); ExFreePool(pfi); return ntStatus; } pbi = (PKEY_BASIC_INFORMATION) ExAllocatePoolWithTag(PagedPool,ulSize, 'SGER' ); if (pbi = = NULL) { ZwClose(hRegister); return STATUS_INSUFFICIENT_RESOURCES; } / / 第二次调用ZwEnumerateKey为了获取KEY_BASIC_INFORMATION数据的数据 ntStatus = ZwEnumerateKey(hRegister, i, KeyBasicInformation, pbi, ulSize, &ulSize); if (!NT_SUCCESS(ntStatus)) { ZwClose(hRegister); ExFreePool(pfi); ExFreePool(pbi); return ntStatus; } uniKeyName.Length = uniKeyName.MaximumLength = (USHORT)pbi - >NameLength; uniKeyName. Buffer = pbi - >Name; DbgPrint( "The %d sub item name is:%wZ\n" ,i,&uniKeyName); ExFreePool(pbi); } ExFreePool(pfi); ZwClose(hRegister); return ntStatus; } |
删除
key(ZwDeleteKey)
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 29 | NTSTATUS ntDeleteKey(WCHAR * szKey) { UNICODE_STRING RegUnicodeString = { 0 }; HANDLE hRegister = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; NTSTATUS ntStatus = STATUS_SUCCESS; RtlInitUnicodeString(&RegUnicodeString,szKey); InitializeObjectAttributes(&objectAttributes, &RegUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwOpenKey( &hRegister, KEY_ALL_ACCESS, &objectAttributes); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } ntStatus = ZwDeleteKey(hRegister); ZwClose(hRegister); return ntStatus; } |
valuekey
设置创建
valuekey(ZwSetValueKey)
查询
valuekey(ZwQueryValueKey)
- 把
valuekey
的值查询出来枚举
valuekey (ZwEnumerateValueKey) - 遍历,把每个
valuekey
都罗列出来 - 面对内存
无法确定
的编程思路:- 思路1:系统有提供这类函数,只需要把参数传
NULL
就返回buffer的实际
大小,比如:ZwEnumerateValueKey
- 思路2:函数
不支持
把参数传NULL就返回buffer的实际大小,就需要调整buffer的大小,使其指数增长
(64B->128B->256B)去测试buffer的长度,比如ZwQueryDirectoryFile
- 思路1:系统有提供这类函数,只需要把参数传
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | NTSTATUS ntEnumerateSubValueKey(WCHAR * szKey) { UNICODE_STRING RegUnicodeString = { 0 }; HANDLE hRegister = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; ULONG ulSize = 0 ; UNICODE_STRING uniKeyName = { 0 }; ULONG i = 0 ; NTSTATUS ntStatus = 0 ; PKEY_VALUE_BASIC_INFORMATION pvbi = NULL; PKEY_FULL_INFORMATION pfi = NULL; RtlInitUnicodeString( &RegUnicodeString,szKey); InitializeObjectAttributes(&objectAttributes, &RegUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwOpenKey( &hRegister, KEY_ALL_ACCESS, &objectAttributes); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } ntStatus = ZwQueryKey(hRegister, KeyFullInformation, NULL, 0 , &ulSize); if (STATUS_BUFFER_OVERFLOW ! = ntStatus && STATUS_BUFFER_TOO_SMALL ! = ntStatus) { ZwClose(hRegister); return ntStatus; } pfi = (PKEY_FULL_INFORMATION) ExAllocatePoolWithTag(PagedPool,ulSize, 'SGER' ); if (pfi = = NULL) { ZwClose(hRegister); return STATUS_INSUFFICIENT_RESOURCES; } ntStatus = ZwQueryKey(hRegister, KeyFullInformation, pfi, ulSize, &ulSize); if (!NT_SUCCESS(ntStatus)) { ZwClose(hRegister); ExFreePool(pfi); return ntStatus; } for (i = 0 ;i<pfi - >Values;i + + ) { ntStatus = ZwEnumerateValueKey(hRegister, i, KeyValueBasicInformation, NULL, 0 , &ulSize); if (STATUS_BUFFER_OVERFLOW ! = ntStatus && STATUS_BUFFER_TOO_SMALL ! = ntStatus) { ZwClose(hRegister); ExFreePool(pfi); return ntStatus; } pvbi = (PKEY_VALUE_BASIC_INFORMATION) ExAllocatePoolWithTag(PagedPool,ulSize, 'SGER' ); if (pvbi = = NULL) { ZwClose(hRegister); ExFreePool(pfi); return ntStatus; } ntStatus = ZwEnumerateValueKey(hRegister, i, KeyValueBasicInformation, pvbi, ulSize, &ulSize); if (!NT_SUCCESS(ntStatus)) { ZwClose(hRegister); ExFreePool(pfi); ExFreePool(pvbi); return ntStatus; } uniKeyName.Length = uniKeyName.MaximumLength = (USHORT)pvbi - >NameLength; uniKeyName. Buffer = pvbi - >Name; DbgPrint( "The %d sub value name:%wZ\n" ,i,&uniKeyName); if (pvbi - > Type = = REG_SZ) { DbgPrint( "type:REG_SZ\n" ); } else if (pvbi - > Type = = REG_MULTI_SZ) { DbgPrint( "type:REG_MULTI_SZ\n" ); } else if (pvbi - > Type = = REG_DWORD) { DbgPrint( "type:REG_DWORD\n" ); } else if (pvbi - > Type = = REG_BINARY) { DbgPrint( "type:REG_BINARY\n" ); } ExFreePool(pvbi); } ExFreePool(pfi); ZwClose(hRegister); return ntStatus; } |
删除
valuekey(ZwDeleteValueKey)
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 29 30 | NTSTATUS ntDeleteValueKey(WCHAR * szKey) { UNICODE_STRING RegUnicodeString = { 0 }; HANDLE hRegister = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; UNICODE_STRING ValueName = { 0 }; ULONG ulSize = 0 ; NTSTATUS ntStatus = STATUS_SUCCESS; RtlInitUnicodeString( &RegUnicodeString,szKey); InitializeObjectAttributes(&objectAttributes, &RegUnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); ntStatus = ZwOpenKey( &hRegister, KEY_ALL_ACCESS, &objectAttributes); if (!NT_SUCCESS(ntStatus)) { return ntStatus; } RtlInitUnicodeString( &ValueName, L "csico1" ); ntStatus = ZwDeleteValueKey(hRegister, &ValueName); ZwClose(hRegister); return ntStatus; } |
重命名
:未导出
- 实现了的,
只供
微软自己使用,我们无法调用 - 存储和管理程序中大量的数据,比如
系统文件
,进程集合
- 增删改查
LIST_ENTRY
链表- 查找速度
最慢
Tn=O(n)
- 查找速度
HASH
表- 查找速度
最快
Tn=O(1),但空间利用率并不高
- 查找速度
TREE
树- 查找和管理数据非常高效
O(log(n))
,比如平衡二叉树,红黑树
- 查找和管理数据非常高效
LookAside
结构
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 29 30 31 32 33 34 35 36 37 38 39 | / / / 普通结构体 typedef struct _MYDATA { int value; WCHAR NameBuffer[MAX_PATH]; }MYDATA, * PMYDATA; / / / 双向指针 typedef struct _LIST_ENTRY { struct_LIST ENTRY * Flink; struct_LIST_ENTRY * Blink; }LIST_ENTRY, * PLIST_ENTRY; / / / 往普通结构体中插入双向指针,就演变成了一个双向链表的节点了 typedef struct _MYDATA_LIST_ENTRY { int value; LIST_ENTRY Entry; / / / < 插入位置可以按照需求调整,可以放在后面方便拷贝节点前面的数据 WCHAR NameBuffer[MAX_PATH]; }MYDATALIST_ENTRY, * PMYDATALIST_ENTRY; / / / 通过Entry遍历链表,由于指针指向的不是MYDATALIST_ENTRY的首地址,而是指向MYDATALIST_ENTRY.Entry,所以需要计算出MYDATALIST_ENTRY的首地址,通过MYDATALIST_ENTRY的首地址访问节点内的其他成员 / / / 宏CONTAINING_RECORD是通过MYDATA_LLIST_ENTRY - Entry得到offset,通过pList - offset就得到MYDATALIST_ENTRY的首地址了 PMYDATA_LIST_ENTRY dataEntry = CONTAINING_RECORD(pList,MYDATA_LLIST_ENTRY,Entry); / / / 通过MYDATALIST_ENTRY的首地址访问节点内的其他成员 dataEntry - >value = 1 ; / / / 求一个结构体成员对于结构体首地址的偏移 / / / 以 0 地址为参照,m的地址就是m相对于 0 的偏移,先把 0 地址转化成s类型的结构体指针(以s的地址看作是 0 地址),取m成员的地址,把地址转换成无符号整数 #define offsetof(s,m) (size_t)&(s *)0)->m) /// 如果这样写(*0).m就是访问0地址的内存了 / / / 0 地址(null地址)会不会导致非法访问导致系统奔溃呢? / / / 并没有对内存指针进行解引用(没有访问内存中的数据) / / / 从而下面这个宏就容易理解了 #define CONTAINING RECORD(address, type, field) ((type *)(\ (PCHAR)(address) - \ (ULONG_PTRH(&itype * ) 0 ) - >field))) |
使用方法
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | LIST_ENTRY listHead; / / / < 头指针 PMYDATA_LISTL_ENTRY tmpEntry = NULL; / / / 结点,须自己初始化值 ... / / / 需要为其分配内存才能使用,这里省略了 InitializeListHead(&listHead); / / / 初始化 InsertleadList( &listHead, &tmpEntry - >Entry); / / / 结点头插入 InsertTailList( &listHead,&tmpEntry - >Entry); / / / 结点尾插入 PLIST_ENTRY listEntry = RemoveHeadList(&listHead); / / / 从链表头部移除,删除头节点,并没有释放节点在堆上的内存,返回删除的节点地址, PLIST_ENTRY listEntry = RemoveTailList( &listHead); / / / 从链表尾部移除,删除头节点前一个节点,PLIST_ENTRY是个双向循环链表 / / / 删除节点,这个函数删除就会释放节点在堆上的内存 RemoveEntryList(&dataEntry - >Entry); IsListEmpty(&listHead); / / / 判断是否为空链表 / / / 头插和从链表头部移除,尾插和从链表尾部移除可以实现一个链栈 / / / 头插和从链表尾部移除,尾插和从链表头部移除可以实现一个队列 / / / do - while 遍历 pList = pListHead; do{ pList = pList - >Flink; dataEntry = CONTAINING RECORD(pList,MYDATA LIST_ENTRY,Entry); DbgPrint( "%S\n" , dataEntry - >NameBuffer); } while (pListl! = pListHead); / / / while 遍历删除 while (!IsListEmpty(&listHead)) { listEntry = RemoveHeadList(&listHead); / / / 从链表头部移除,并没有释放节点在堆上的内存,返回删除的节点地址, dataEntry = CONTAINING_RECORD(IistEntrye,SBPROCESS_ENTRY,Entry); DbgPrint( "%ws\n" ,dataEntry - >NameBuffer); / / / RemoveEntryList(listEntry); / / / 删除接点,这个函数删除就会释放节点在堆上的内存 ExFreePool(dataEntry); / / / 释放节点在堆上的内存 } / / / for 遍历 PLIST_ENTRY plistHead = &listHead; / / / < 头指针 PLIST_ENTRY plist = NULL; / / / < 遍历用的指针 PMYDATA_LIST_ENTRY dataEntry = NULL; / / / < / / / 双向循环链表,向前遍历,用RemoveEntryList(&dataEntry - >Entry);删除节点之前,主要并保存删除节点的地址,因为删除节点后,会释放节点在堆上的内存,pList指针指向的地址失效了,无法通过pList = pList - >Flink改变循环条件 For(pList = plistHead - >Flink;pList ! = plistHead;pList = pList - >Flink) dataEntry = CONTAINING_RECORD(pList,MYDATA_LIST_ENTRY, Entry); DbgPrint( "%S\n" , dataEntry - >NameBuffer); } |
LIST_ENTRY应用
- 内核非递归删除文件夹
缺点
:查找速度较慢
O(N)- 解决hash冲突
- 在
WDK
好像没有实现Hash的封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | / / / hash 函数,传入key之后,返回一个key在 hash 表中对应的位置,一般是取模运算 unsigned int Hash (DWORD key,unsigned int tableSize) / / / 通过key找到 hash 表中的某个结点 PTWOWAY Find(DWORD key,PHASHTABLE pHashTable) / / / 插入 void Insert(DWORD key,PELEMENE pData,PHASHTABLE pHashTable); / / / 删除 void Remove(DWORD key,PHASHTABLE pHashTable); / / / 销毁 void DestroyTable(PHASHTABLE pHashTable); / / / 打印 ULONG DumpTable(PHASHTABLE plHashTable); |
使用方法
HASH
表的应用
FileMon
存储:- fileobject→文件名(文件名保存至hash表中)
SwapContex
t:EPROCESS
——>进程数据(用hash表保存进程切换的数据)
- 优点:查找速度O(1)
- 稀疏hash
- 只有
平衡树
,查找效率才高,比如如果是只有
左/右结点,那就退化
成链表了,查找的时间复杂度为O(n) - WRK里面提供了树的实现:
wrk-v1.2\base\ntos\rtl\AvlTable.c
- WDK也提供了树的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | / / / 保护树的锁 RTL_AVL_TABLE g_FileNameTable; / / / 初始化 RtLInitiaIizeGenericTableAvl(&g_ FileNameTable, CompareNameFunc, AllocateFunc, FreeFunc, NULL); / / / 加锁 ExAcquireFastMutex(&g_FileName TableMutex); / / / 插入结点 RtlInsertElementGenericTableAvl(&gFileNameTable,&Key,sizeof(FILE_NAME_ENTRY),NULL); / / / 释放锁 ExReleaseFastMutex(&g_FileNameTableMutex); |
LookAside
结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | / / / 定义一个分页内存 PAGED_LOOKASIDE_LIST g_pageList; / / / 初始化,在DriverEntry调用,以连续的内存页分成一个大的缓存池 ExInitializePagedLookasideList(&g_pageList, NULL, NULL, 0 , sizeof(MYDATA), / / 指定固定大小 'HSAH"' , / / tag 0 ); / / / 分配内存 MYDATA * pMyData = (MYDATA * )ExAllocateFromPagedLookasideList(&g pageList); / / / 把内存释放到g_pageList中去 ExFreeToPaged LookasideList(&g_pageList,pMyData); / / / 在DriverUnload,调用把内存释放掉 ExDeletePagedLookasideList(&g_pageList); |
赞赏记录
参与人
雪币
留言
时间
治愈ckt
为你点赞~
2023-11-9 23:15
pysafe
为你点赞~
2023-5-6 10:38
704088
为你点赞~
2023-3-31 00:37
zhczf
为你点赞~
2023-2-7 08:20
赞赏
他的文章
- [原创]java和smali汇编 2679
- [原创]Android逆向前期准备(下) 4239
- [原创]native层逆向分析(上篇) 13789
- [原创]Java层逆向分析方法和技巧 7244
看原图
赞赏
雪币:
留言: