本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正
内核基本操作,数据结构
内核的基本操作
UNICODE_STRING
为什么字符串很重要
- 大型工程中10%~20%的代码都与字符串操作有关
- 面试里面也有很多字符串相关问题
- 字符串涉及到指针操作
- 字符串是编程第二个重要关口
字符与字符串
字符
- char/sigined char/unsigned char是不同的类型,但int/sigined int是一样的
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
/// 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. `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
初始化
用字符串常量对其初始化
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或静态区的内存对其初始化
/// 定义并初始化一个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);
用从堆上分配一个内存对其初始化
/// 定义并初始化一个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;
- 拷贝
- 拼接
- 拆分
- 比较
- 编码转换
/// 把多字节字符串转换成宽字节字符串
/// @param TRUE/FALSE 转换的过程中内存的大小会发生变化,涉及内存分配和计算,TRUE表示交给系统去计算内存的大小和分配内存,FALSE则表示程序员自己去计算和分配内存
RtlAnsiStringToUnicodeString(&uStr1,&aStr1,TRUE/FALSE);
/// 如果前面设置为FALSE,系统帮忙计算和分配内存,用完之后一定要释放掉,否则会造成内存泄漏
/// 实际上很少会遇到字符串编码的转换,因为内核中用的都是UNICODE_STRING
/// DbgPrint在打印unicode中文的话,在debug view里面是看不到的,这种情况下就需要把unicode中文转化成多字节编码才能看到
RtlFreeUnicodeString(&uStr1);
安全函数
/// 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
个字符,而在上述的对字符串操作的函数
中,都没有溢出检测
的,存溢出风险
/// 可以发现动词放在后面的就是安全函数
//安全函数,溢出检测
#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))
实战
#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
类型的句柄来引用它们。
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
类型,指向本进程的句柄表!
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
创建/打开文件
/// @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 //对于设备和中间驱动程序,此参数必须为零。
);
/// 创建文件
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;
}
/// 创建目录
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
ZwWriteFile(
hDstFile,
NULL,
NULL,
NULL,
&io_status
buffer,
length,
&offset,
NULL //null表示同步
);
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
ZwReadFile(
hSrcFile
NULL,
NULL,
NULL,
&io_status,
buffer,
PAGE_SIZE
&offset
NULL //null表示同步
);
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
写到目标文件中去
/// 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;
}
移动
- 现实的移动文件是
重命名
/// 复制再删除
NTSTATUS ntMoveFile(const WCHAR * src, const WCHAR * dst)
{
NTSTATUS status = 0;
status = ntCopyFile(src, dst); //复制
if (NT_SUCCESS(status))
{
status = ntDeleteFile2(src); //删除
}
return status;
}
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
查询文件信息,大小,只读属性,文件访问时间,文件还是目录等
ZwQueryInformationFile(
handle, //打开文件的句柄
&iosb, //io操作的完成状态
&basiclnfo, //存放属性的结构体,把查询的属性放到这个结构体里面返回
sizeof(basicInfo),
File Basiclnformation
//枚举类型(FileBasiclnformation(基本属性)、FileStandardInformation(标准属性)、FileDispositionlnformation(删除文件的属性)、FilePositionlnformation(读取或者设置当前文件的读写指针)、FileRenamelnformation(重命名)),指定查询结构体的类型
);
ZwQueryFullAttributesFile
ZwQueryFullAttributesFile
查询文件信息,大小,只读属性,文件访问时间,文件还是目录等
ntStatus = ZwQueryFullAttributesFile(
&objAttr,
&info);
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(重命名和删除,
ZwSetlnformationFile(
handle, //打开文件的句柄
&iosb, //io操作的完成状态
&basicInfo, //存放属性的结构体,具体传入哪个结构体由下面参数FilexxxInformation决定
sizeof(basiclnfo),
FileBasiclnformation
//枚举类型(FileBasicInformation(基本属性)、FileStandardInformation(标准属性)、FileDispositionInformation(删除文件的属I性)、FilePositionInformation(读取或者设置当前文件的读写指针)、FileRenameInformation(重命名)),指定查询结构体的类型
);
/// 基本信息
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:
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:
/// 只在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;
}
/// 方式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
注册表
注册表的布局
- 每一个
根键
都对应一个宏定义
#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 多字符串类型
- 定义与构建
多
字符串的定义:"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()不安全注意防溢出
删除
与重命名
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
就得到了具体路径。
- 输入
- 遍历
/// 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)
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)
ZwCreateKey(
&hRegister, //key的句柄
KEY_ALL_ACCESS,
&objectAttributes, //key的路径,类似文件的创建
0,
NULL,
REG_OPTION_NON_VOLTILE, //回写到文件中去,永久生效,因为注册表是存放在文件中的,只在内存中创建出来,并没有写回到文件中去,如果不设置REG_OPTION_NON_VOLTILE,在系统重启之后,就丢失了
&ulResult
);
//创建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)
- 遍历,把每个
子键
都罗列出来
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)
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:系统有提供这类函数,只需要把参数传
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)
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
链表
定义
/// 普通结构体
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)))
使用方法
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的封装
/// 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
- 需要reload
TREE
树
- 只有
平衡树
,查找效率才高,比如如果是只有
左/右结点,那就退化
成链表了,查找的时间复杂度为O(n)- 平衡:|
左右子树高度差
| <= 1,(-1,0,1)
- 平衡:|
使用方法
- WRK里面提供了树的实现:
wrk-v1.2\base\ntos\rtl\AvlTable.c
- WDK也提供了树的实现:
/// 保护树的锁
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
使用方法
/// 定义一个分页内存
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);