UNICODE_STRING、全局句柄表、文件、注册表、LIST_ENTRY、HASH、TREE、LookAside

本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正

内核基本操作,数据结构

内核的基本操作

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),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
/// 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. `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
初始化
用字符串常量对其初始化
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. 什么是内核对象?
  • 像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象
  1. 如何管理内核对象?
  • 方式1:直接返回内核对象的地址,不可取
    • 内核对象是在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的过程完全一致。
    • 主要区别在于全局句柄表的表项指向的是对象体而不是对象头
  1. 多进程共享一个内核对象
  • 句柄表是进程私有的,句柄的索引值只在当前进程中有效
  • Closehandle()只是把引用计数减一,内核对象引用计数变0才会真正被销毁。
    • 线程和进程内核对象比较特殊,需要把线程关掉(进程则是关闭其中的所有线程))&&线程内核对象的引用计数为0,线程内核对象才会被关闭。
  1. 句柄是否可以被继承?

  2. 句柄表在哪?

  • 一个进程可打开多个对象,就会拥有多个句柄,所以每个进程都应该拥有一个句柄表,在进程控制块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(重命名和删除,次功能号
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 多字符串类型
  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. 删除重命名
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. 遍历
/// 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)
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
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表中)
  • SwapContext:
    • 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); 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值