首页
社区
课程
招聘
[原创]内核基本操作,数据结构
发表于: 2023-2-7 08:15 6742

[原创]内核基本操作,数据结构

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),0x00000000
    • L'0',wchar_t(2Byte),0x0030
    • '0',char(1Byte),0x30
    • '\0',char(1Byte),0x00
    • "0",char*(2Byte),0x3000("0\0")
    • FALSE,BOOL(4Byte),0x00000000
    • false,bool(1Byte),0x00
    • NULL
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,
}
字符串
  1. char *
    • c语言中使用,以'\0'结尾的ASCII字符串,每个字符占1个字节
  2. BSTR
    • win32编程中使用,前4个字节表示字节长,后面以'\0'结尾,可以理解为是char*UNICODE_STRING的综合
    • BSTR 是一个指向 UNICODE 字符串的指针,且 BSTR 向前的4个字节中,使用DWORD保存着这个字符串的字节长度( 没有含字符串的结束符)。
  3. 宽字节字符串
    • L"Hello world,你好" 每个字符占2个字节
  4. 多字节字符串
    • "Hello world,你好" Hello world每个字符占1个字节,你好每个字符占2个字节
  5. _T("Hello world,你好")
    • 这个根据工程的设置,自适应变成宽字节或者多字节
  6. ANSI_STRING字符串
    • 多字节编码字符,不是'\0'结尾,是一个结构体类型
  7. 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)格式打印
  • %ZANSI_STRING字符串格式打印
  • %wZUNICODE_STRING字符串格式打印
  • %%打印一个%
  • %n,把前面打印的字符总数写入到变量里面去,现在已经被编译器禁用了,编译通过执行的时候会报错
  • %01000x%n把前面打印的字符总数10000写入到变量里面去,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. 什么是内核对象?
  • 像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象
  1. 如何管理内核对象?
  • 方式1:直接返回内核对象的地址,不可取
    • 内核对象是在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的过程完全一致。
    • 主要区别在于全局句柄表的表项指向的是对象体而不是对象头
  1. 多进程共享一个内核对象
  • 句柄表是进程私有的,句柄的索引值只在当前进程中有效
  • Closehandle()只是把引用计数减一,内核对象引用计数变0才会真正被销毁。
    • 线程和进程内核对象比较特殊,需要把线程关掉(进程则是关闭其中的所有线程))&&线程内核对象的引用计数为0,线程内核对象才会被关闭。
  1. 句柄是否可以被继承?

  2. 句柄表在哪?

  • 一个进程可打开多个对象,就会拥有多个句柄,所以每个进程都应该拥有一个句柄表,在进程控制块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
    • 设备对象名\\device\\harddiskvolume3是由内核驱动创建的,然后在根据设备对象名创建符号链接\\??\\c:(代表卷设备对象符号链接,也称为盘符)。

      设备对象的表示

  • 应用层:
    • 设备名: L"\\\\.\\xxxDrv"其中xxxDrv代表符号链接名,把设备对象当作一个特殊的文件打开,打开得到一个句柄。
  • 内核层:
    • 设备名:"\\device\\xxxDrv"
    • 符号链接名:"dosdevices\\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(重命名和删除,次功能号
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的长度
    非递归方法删除文件夹
  • 递归会很快把栈上的空间消耗掉
    思路:基于链栈,从堆上分配内存存放栈上的数据
    • 把文件和子文件夹遍历出来
    • 对子文件夹进行同样的遍历
      @todo

      磁盘恢复

      @todo

      注册表

      注册表的布局

  • 每一个根键都对应一个宏定义
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有多种类型:
    • REG_BINARY存放二进制的
    • REG_DWORD存放4个字节无符号整数
    • REG_EXPAND_SZ存放带环境变量的字符串,在应用层调用ExpandEnvironmentStrings函数把环境变量%systemroot%转换成具体的路径,系统变量就是为了灵活性,比如%systemroot%代表C:\Windows,如果系统安装在D:盘,则代表D:\Windows
    • REG_MULTI_SZ``多字符串类型
      REG_MULTI_SZ 多字符串类型
  1. 定义与构建
  • 字符串的定义:"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. 删除重命名
1
MoveFileEx(szTemp, NULL,MOVEFILE_DELAY_UNTIL_REBOOT);

  • 应用场景:
    • 1.更新程序:某个dll有漏洞,需要更新,从远程服务器下载dll,放在临时文件夹,从临时文件夹把dll文件拷贝到目标文件上,重命名覆盖原来的文件,有时候动态库dll被占用了,覆盖会失败,如果失败了则调用这个函数,提示更新重启,重启之后在完成覆盖(重启之后会在进程启动之前完成替换,就不会dll被占用造成覆盖失败情况了)
    • 2.卸载程序:这个程序正在运行或者这个程序某个dll文件被其他进程占用了,卸载程序的时候,被占用的dll删除不掉,卸载的时候提醒重启(也是调用这个函数),重启的时候就把文件删掉,早于其他进程启动。
  • 重启之后,系统是怎么知道要替换/删除的文件的呢?
    • 每调用一次MoveFileEx,都会把"szTemp, NULL"记录写到注册表这个位置HKEY_LOCAL_MACHINE\SYSTEMN\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations"
    • 两行为一对,把第一行文件覆盖到第二行的文件,如果第二行为,就把第一行删掉
  1. 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. 遍历
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文件:DEFAULTSAM ,SECURITYSOFTWAREUSERDIFFSYSTEM中(系统独占资源,无法直接复制)
  • 用户的配置信息存放在系统所在磁盘的users/user目录,包括ntuser.datntuser.inintuser.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
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;
}
重命名:未导出
  • 实现了的,只供微软自己使用,我们无法调用
    • XP:是删除再建
    • XP以上:未导出的重命名

      内核中的数据结构

  • 存储和管理程序中大量的数据,比如系统文件进程集合
  • 增删改查
  • LIST_ENTRY链表
    • 查找速度最慢Tn=O(n)
  • HASH
    • 查找速度最快Tn=O(1),但空间利用率并不高
  • TREE
    • 查找和管理数据非常高效O(log(n)),比如平衡二叉树,红黑树
  • LookAside结构
    • 管理内存,防止出现碎片化,比如频繁分配小块内存,会导致系统出现大量的内存碎片,最后导致系统明明有内存,但分配不出来
    • 如果系统频繁分配固定大小的内存,就可以使用LookAside解决内存碎片问题

      LIST_ENTRY链表

      定义

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表,O(1)
    • TREE树是O(log(n))

      HASH

  • 解决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表中)
  • SwapContext:
    • EPROCESS——>进程数据(用hash表保存进程切换的数据)
  • 优点:查找速度O(1)
  • 稀疏hash
    • 需要reload

      TREE

  • 只有平衡树,查找效率才高,比如如果是只有左/右结点,那就退化成链表了,查找的时间复杂度为O(n)
    • 平衡:|左右子树高度差| <= 1,(-1,0,1)
      使用方法
  • 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结构

  • 频繁的内存分配产生空洞碎片
  • 频繁分配固定大小内存
  • LookAside类别
    • PAGED _LOOKASIDE_LIST
    • NPAGED_LOOKASIDE_LIST

      使用方法

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);

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 4
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//