编程语言
首页 > 编程语言> > 内核编程

内核编程

作者:互联网

内核编程

一. 驱动程序概述

1. 介绍
  1. 每一个进程都有一个4GB的进程空间,储存了进程需要的所有代码和数据,并分为用户空间和内核空间
  2. 不同进程的用户空间相互隔离,互不影响,共享内核空间,操作系统通过内核层代码给应用程序提供支持
  3. 用户空间代码不能直接访问内核空间,执行的命令也有限制,内核空间代码可以执行特权指令,用户层访问内核层也需要通过特定的接口
  4. 我们编写的驱动程序,并非是平时我们看到的一个能够运行的程序,而是加载到内核空间中,成为操作系统的一部分,作为应用程序与硬件的桥梁,为应用程序提供支持的一个模块
2. 分类
  1. 仅仅作为Windows操作系统内核的扩展,而并非是使得一个硬件工作起来,我们将它称之为内核程序
  2. 针对于某种硬件,使得其能够很好的工作起来
    • NT
    • WDM驱动模型(Windows Driver Model)
    • WDF驱动模型(Windows Driver Foudation)
1.首先,先从基础的东西说起,开发WINDOWS下的驱动程序,需要一个专门的开发包,如:开发JAVA程序,我们可能需要一个JDK,开发WINDOWS应用程序,我们需要WINDOWS的SDK,现在开发WINDOWS下的驱动程序,我们需要一个DDK/WDK。

  2.DDK(Driver Developer Kit)和WDK(Windows Driver Kit)的区别:

  这个要说说驱动相关的一些历史:

  1).95/98/ME下,驱动模型为:Vxd,相关资料可以看《编程高手箴言》的前几个章节,里面有很详细的介绍,虽然这个东西已经过时,但大概看看还是会增长见识的。

  2).2000/XP/2003下,Windows采用WDM驱动模型(Windows Driver Model),开发2000/XP/2003的驱动开发包为:DDK。

            WDM驱动无非是微软在NT式驱动之上进行了扩充,过滤驱动也不例外 。

  3).Vista及以后版本,采用了WDF驱动模型(Windows Driver Foudation),对应的开发包:WDK。

其实WDK可以看做是DDK的升级版本,现在一般的WDK是包含以前DDK相关的功能,现在XP下也可以用WDK开发驱动,WDK能编译出2000-2008的各种驱动。

  3.Vxd驱动文件扩展名为:.vxd。

     WDM和WDF驱动文件扩展名为:.sys。

        4、WDM 是 Win32设备驱动程序体系结构。

            Windows设备驱动程序,过去是WDM(Windows Driver Model)框架,编程复杂,初学者难以掌握其编程要领。为了解决这一问题,微软对WDM驱动程序的架构做了改进,形成了全新的WDF(Windows Driver Foundation)框架结构。它提供了面向对象和事件驱动的驱动程序开发框架,大大降低了开发难度。从现在开始,掌握Windows设备驱动程序的开发人员,由过去的“专业”人士,将变为“普通”大众。
 WDF驱动程序包括两个类型,一个是内核级的,称为KMDF(Kernel-Mode Driver Framework),为SYS文件;另一个是用户级的,称为UMDF(User-Mode Driver Framework),为DLL文件。

5、
ddk 和wdk
ddk是基于wdm驱动模型的,而wdk是基于WDF驱动模型的,wdm驱动模型和wdf驱动模型的最大的区别是:
      
1)wdf驱动框架对WDM进行了一次封装,WDF框架就好像C++中的基类一样,且这个基类中的model,IO model ,pnp和电源管理模型;且提供了一些与操作系统相关的处理函数,这些函数好像C++中的虚函数一样,WDF驱动中能够对这些函数进行override;特别是Pnp管理和电源管理!基本上都由WDF框架做了,而WDF的功能驱动几乎不要对它进行特殊的处理;

2)WDF驱动模型 与WDM驱动模型的另外一个主要区别是:

WDF 驱动采用队列进行IO处理,而WDM中将所有的IO操作都用默认的队列进行处理,如果要进行IRp同步,必须使用StartIO;

3)WDF是面向对象的,而WDM是面向过程的,WDF提供对象的封装,如将IRP封装成WDFREQUEST,对象提供方法和Event。

5)usb设备的读写;

当应用程序使用ReadFile或WriteFile进行读写时,首先将

UsbBuildInterruptOrBulkTransferRequest将构建urb请求,然后通过IoCallDriver发送给底层usb 总线驱动;

对于WDF来说,WdfUsbTargetPipeFormatRequestForRead 格式化读请求,然后使用WdfRequestSend  发送给底层Usb总线驱动;

对WDM和WDF的usb的读写都可以设置完成例程;
3. 安装SDK
  1. VS中工具:工具与功能查看SDK版本

  2. MSDN下载对应WDK版本

  3. 新建项目WDM(扩展版NT)

- 驱动程序

1. 最简单的驱动程序
#include <ntddk.h>

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);

	return STATUS_SUCCESS;
}
2. 简单驱动程序
  1. 包含ntddk.h(wdm.h)头文件
  2. DriverEntry入口函数
  3. DriverUnload驱动卸载函数(资源回收,清理)
  4. 返回值STATUS_SUCCESS(加载成功条件)
// 0. 由于驱动程序是和操作系统相关的,所以并不是一个驱动程序能够被
//	所有的系统加载,我们需要在项目属性中配置目标系统的版本。如果漏
//	掉了这一步,在加载驱动的瞬间,会蓝屏 !!!!!!

// 1. 需要包含提供内核结构体和基本函数的头文件,也可以使用 wdm.h
#include <ntddk.h>


// 提供驱动程序的卸载函数,在卸载驱动的时候会被自动的调用
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	KdPrint(("driver unloading...\n"));
	UNREFERENCED_PARAMETER(DriverObject);
}


// 2. 定义驱动程序的入口函数,该函数必须没有名称粉碎机制
NTSTATUS DriverEntry(
	PDRIVER_OBJECT DriverObject,			// 驱动对象,类似实例句柄,
	PUNICODE_STRING RegistryPath)			// 字符串指针,指向驱动在注册表的配置信息
{
	// 由于驱动程序的编写要求十分的严格,任何可能产生问题的语句都会被视
	//	为错误,所以我们对于为引用的对象应该使用以下的宏进行说明。
	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);


	// Windows 提供了函数 DbgBreakPoint 用于设置断点,当处于双机调试
	//	状态,该异常会被内核调试器 windbg 接收到,可以用于继续调试
	DbgBreakPoint();


	// 通过驱动对象中的一个字段设置所属驱动的卸载函数,如果没有提供这个
	//	函数驱动就无法被卸载,可以故意不提供卸载函数来防止被恶意卸载
	DriverObject->DriverUnload = DriverUnload;


	// 在驱动程序中不能直接使用输出函数,但是可以用调试信息,KdPrint 函
	//	数只会在当前驱动为 Debug 版本时被调用
    DbgPrint
	KdPrint(("driver loading...\n"));


	// 3. 想让当前的驱动程序加载成功,必须返回 STATUS_SUCCESS,如果返
	//	回了其它值,对应的驱动会加载失败
	return STATUS_SUCCESS;
}

显示 kdPrint reg文件
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter]

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter]
"DEFAULT"=dword:0000000f

重启生效
    
关闭安全警告 (将警告视为错误)
    1. C++
    2. 链接器->常规
3. 加载驱动对象

驱动编译完成之后,不同于exe文件,sys不能够随便的运行,只能通过windows提供的接口加载驱动程序,在windows内核程序的加载是通过服务来实现的

在这里插入图片描述

4. 驱动调试
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter]

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter]
"DEFAULT"=dword:0000000f

调试工具:

​ VirtualKD是一个开源的调式辅助软件,能帮助WinDbg与VmWare建立快速通讯http://virtualkd.sysprogs.org

VirtualKD分两部分,在target子文件夹中的所有文件需要拷贝到虚拟机中,并在虚拟机中运行vminstall.exe点击Install安装虚拟机部分,安装完成之后会在目标机器中添加一个新启动项,打开vmmon64配置调试器路径即可

- 蓝屏处理

可以使用windbg调式转储文件,分析崩溃原因

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdBcOYxZ-1616297958236)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210208164122498.png)]

windbg使用命令 !analyze -v,将分析dump文件,并显示程序崩溃处于的代码行。

二. 驱动对象

1. 驱动对象(程序)

​ 与容器类似,代表着你编写的这个驱动程序,就如同windows编程时的实例句柄一样,代表了整个应用程序一样

​ 当驱动加载时,对象管理器便会生成一个驱动对象,并调用DriverEntry,将驱动对象传入,用户可以在DriverEntry中为驱动对象中未初始化的各个字段赋值,为整体的驱动程序定下总基调,如:设定卸载驱动的函数,设定各个消息的处理函数

// 0. 由于驱动程序是和操作系统相关的,所以并不是一个驱动程序能够被
//	所有的系统加载,我们需要在项目属性中配置目标系统的版本。如果漏
//	掉了这一步,在加载驱动的瞬间,会蓝屏 !!!!!!

/*
struct _DRIVER_OBJECT
{
	驱动对象: 类似于 GUI 程序中的应用程序本身,不能直接和 R3 进行交互,需要依赖设备对象传递 IRP
	SHORT Type;                                      //0x0   // 表示结构体的类型,所以的驱动对象都为 4
	SHORT Size;                                      //0x2	 // 驱动对象的大小,所占用的字节数
	struct _DEVICE_OBJECT* DeviceObject;             //0x4	 // * 当前驱动对象下所有设备对象组合成的单向链表,每个驱动对象都会有一个或多个设备对象,所有的对象是链表的形式串联起来,这里指第一个设备对象,可以遍历所有设备对象
	ULONG Flags;                                     //0x8	 // 标志位
	VOID* DriverStart;                               //0xc	 // 当前对象在内存中的起始位置
	ULONG DriverSize;                                //0x10	 // 驱动对象所占用的总空间
	VOID* DriverSection;                             //0x14	 // * 指向 LDR 链表,可以用于遍历系统下的所有驱动对象
	struct _DRIVER_EXTENSION* DriverExtension;       //0x18	 // 指向扩展空间的指针
	struct _UNICODE_STRING DriverName;               //0x1c	 // * 驱动对象的名称,是一个字符串结构体,采用UNICODE编码字符串,一般形式为\Drivers\[驱动程序名称]
	struct _UNICODE_STRING* HardwareDatabase;        //0x24	 // 在注册表硬件配置表中的一个名称
	struct _FAST_IO_DISPATCH* FastIoDispatch;        //0x28	 // 特殊的派遣函数,在文件过滤驱动中会被使用
	LONG(*DriverInit)(struct _DRIVER_OBJECT* arg1, struct _UNICODE_STRING* arg2); 
													 //0x2c  // 驱动程序最先执行的代码
	VOID(*DriverStartIo)(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2);  
													 //0x30  // 用于驱动 IRP 的串行化处理
	VOID(*DriverUnload)(struct _DRIVER_OBJECT* arg1);                      
													 //0x34  // * 驱动程序的卸载函数地址,不提供就无法卸载
	LONG(*MajorFunction[28])(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2); 
													 //0x38  // * 消息派遣函数组成的列表(类似于消息处理函数)每个指针成员指向一个响应的处理IRP的派遣函数
};
*/


// 1. 需要包含提供内核结构体和基本函数的头文件,也可以使用 wdm.h
#include <ntddk.h>


// LDR 链表中的每一项都是这个结构体,保存了驱动的基本信息
typedef struct _LDR_DATA_TABLE_ENTRY
{
    struct _LIST_ENTRY InLoadOrderLinks;                                    //0x0
    struct _LIST_ENTRY InMemoryOrderLinks;                                  //0x8
    struct _LIST_ENTRY InInitializationOrderLinks;                          //0x10
    VOID* DllBase;                                                          //0x18
    VOID* EntryPoint;                                                       //0x1c
    ULONG SizeOfImage;                                                      //0x20
    struct _UNICODE_STRING FullDllName;                                     //0x24
    struct _UNICODE_STRING BaseDllName;                                     //0x2c
	// ... 后面还有一些字段,由于用不到,为了节省代码量,直接不考虑
} *PLDR_DATA_TABLE_ENTRY;

/* _LDR_DATA_TABLE_ENTRY结构体
//0x78 bytes (sizeof)
struct _LDR_DATA_TABLE_ENTRY
{
	struct _LIST_ENTRY InLoadOrderLinks;    双向链表                         //0x0
	struct _LIST_ENTRY InMemoryOrderLinks;                                  //0x8
	struct _LIST_ENTRY InInitializationOrderLinks;                          //0x10
	VOID* DllBase;                                                          //0x18
	VOID* EntryPoint;                                                       //0x1c
	ULONG SizeOfImage;                                                      //0x20
	struct _UNICODE_STRING FullDllName;                                     //0x24
	struct _UNICODE_STRING BaseDllName;                                     //0x2c
	ULONG Flags;                                                            //0x34
	USHORT LoadCount;                                                       //0x38
	USHORT TlsIndex;                                                        //0x3a
	union
	{
		struct _LIST_ENTRY HashLinks;                                       //0x3c
		struct
		{
			VOID* SectionPointer;                                           //0x3c
			ULONG CheckSum;                                                 //0x40
		};
	};
	union
	{
		ULONG TimeDateStamp;                                                //0x44
		VOID* LoadedImports;                                                //0x44
	};
	struct _ACTIVATION_CONTEXT* EntryPointActivationContext;                //0x48
	VOID* PatchInformation;                                                 //0x4c
	struct _LIST_ENTRY ForwarderLinks;                                      //0x50
	struct _LIST_ENTRY ServiceTagLinks;                                     //0x58
	struct _LIST_ENTRY StaticLinks;                                         //0x60
	VOID* ContextInformation;                                               //0x68
	ULONG OriginalBase;                                                     //0x6c
	union _LARGE_INTEGER LoadTime;                                          //0x70
};
*/

//0x8 bytes (sizeof)
struct _LIST_ENTRY
{
    struct _LIST_ENTRY* Flink;                                              //0x0
    struct _LIST_ENTRY* Blink;                                              //0x4
}; 


// 提供驱动程序的卸载函数,在卸载驱动的时候会被自动的调用
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	KdPrint(("driver unloading...\n"));
	UNREFERENCED_PARAMETER(DriverObject);
}


// 2. 定义驱动程序的入口函数,该函数必须没有名称粉碎机制
NTSTATUS DriverEntry(
	PDRIVER_OBJECT DriverObject,			// 驱动对象,类似实例句柄,
	PUNICODE_STRING RegistryPath)			// 字符串指针,指向驱动在注册表的配置信息
{
	// 由于驱动程序的编写要求十分的严格,任何可能产生问题的语句都会被视
	//	为错误,所以我们对于为引用的对象应该使用以下的宏进行说明。
	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);


	// Windows 提供了函数 DbgBreakPoint 用于设置断点,当处于双机调试
	//	状态,该异常会被内核调试器 windbg 接收到,可以用于继续调试
	DbgBreakPoint();


	// 通过驱动对象中的一个字段设置所属驱动的卸载函数,如果没有提供这个
	//	函数驱动就无法被卸载,可以故意不提供卸载函数来防止被恶意卸载
	DriverObject->DriverUnload = DriverUnload;


	// 在驱动程序中不能直接使用输出函数,但是可以用调试信息,KdPrint 函
	//	数只会在当前驱动为 Debug 版本时被调用
	KdPrint(("driver loading...\n"));


	// LDR 是一个双向链表,遍历结束的条件是,遍历到的是自己 
	PLDR_DATA_TABLE_ENTRY current = DriverObject->DriverSection;
	PLDR_DATA_TABLE_ENTRY item = DriverObject->DriverSection;

	// 创建一个索引,表示当前遍历到的是第几个
	int index = 1;
	do {
		// 输出元素的基本信息			%wZ  UNICODE_STRING字符串
		KdPrint(("%d: %wZ %wZ\n", index++, &item->BaseDllName, &item->FullDllName));

		// 获取遍历到的元素的下一个元素
		item = (PLDR_DATA_TABLE_ENTRY)item->InLoadOrderLinks.Flink;
	} while (current != item);

	// 摘链 断链 front前 back后
	current->InLoadOrderLinks.Flink->Blink = current->InLoadOrderLinks.Blink;
	current->InLoadOrderLinks.Blink->Flink = current->InLoadOrderLinks.Flink;

	// 3. 想让当前的驱动程序加载成功,必须返回 STATUS_SUCCESS,如果返
	//	回了其它值,对应的驱动会加载失败
	return STATUS_SUCCESS;
}
// 枚举驱动对象
void EnumDriver(PDRIVER_OBJECT pDriver)
{

	PLDR_DATA_TABLE_ENTRY pLdr = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;

	LIST_ENTRY* pTemp = &pLdr->InLoadOrderLinks;

	do
	{

		PLDR_DATA_TABLE_ENTRY pDriverInfo = (PLDR_DATA_TABLE_ENTRY)pTemp;
		KdPrint(("%wZ\n", &pDriverInfo->FullDllName));
		pTemp = pTemp->Blink;

	} while (pTemp != &pLdr->InLoadOrderLinks);

}

2. 设备对象(窗口)

在这里插入图片描述

在这里插入图片描述

1. UNICODE_STRING对象
#include <ntddk.h>

// WINDOWS 中内存的分类(重点)
//	1. 分页文件: 能够被交换到页交换文件(磁盘)的内存[访问可能产生缺页异常]
//	2. 非分页文件: 只能存在于物理内存(内存条)中的内存[访问绝对不会产生异常]


// IRQL(中断请求级别)
//	Dispatch(DPC): 软件层面的最高优先级,同一时刻只会运行一个,不能访问分页内存
//	APC: 比Dispatch低的一个级别,可以访问分页内存
//	Passive: 最低的优先级,大多数代码所运行的级别


// 创建全局变量,用于接受申请到的非分页空间
PVOID Buffer = NULL;


// 驱动对象的卸载函数,如果不设置,就无法卸载
#pragma code_seg("PAGE")						// 表示当前的函数被放置在分页内存中,如果
												// 短时间内不会用到,可以放置到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	// 内核程序和普通应用程序不同,即使驱动程序被卸载了,使用的内存
	//	也不会被自动的释放,所以需要在卸载的时候释放掉所有的内存,否
	//	则会导致内存泄露,直至操作系统重新启动
	ExFreePoolWithTag(Buffer, 'nim.');

	UNREFERENCED_PARAMETER(DriverObject);
}


// 用于测试字符串结构体的函数
VOID StringTest()
{
	// DbgBreakPoint();

	// 内核中一般不再采用 C 语言风格的字符串,原因是可能会出现溢出问题
	//	下面的字符串结构体就是内核中所采用的表示字符串的方式,没有使用
	//	空字符结尾,而是通过其中的一些字段进行描述。
	//	typedef struct _UNICODE_STRING {
	//		USHORT Length;							// 字符串长度
	//		USHORT MaximumLength;					// 最大长度
	//		PWSTR  Buffer;							// 字符串缓存区
	//	} UNICODE_STRING, * PUNICODE_STRING;

	// 初始化的方法之一:在创建的时候直接使用常量进行初始化操作
	UNICODE_STRING String = RTL_CONSTANT_STRING(L"CONSTANT_STRING_1");

	// 初始化的方法之二:在创建完成之后,再手动的进行初始化
	RtlInitUnicodeString(&String, L"CONSTANT_STRING_2");

	// 如果想要对整个结构体内的字符串进行修改,需要手动的分配空间(常见写法)
	Buffer = ExAllocatePoolWithTag(NonPagedPool, 0x100, 'nim.');
	if (Buffer)
	{
		RtlFillMemory(Buffer, 0x100, 0xAA);
		RtlCopyMemory(Buffer, L"UNICODE_STRING_1", 34);
	}

	// 初始化的方法之三:使用自定义的空间关联到字符串结构体中
	String.Buffer = Buffer;						// 如果手动赋值,尽量都使用堆空间
	String.Length = 17 * 2;						// 当前字符串的字节数
	String.MaximumLength = 0x100;				// 能够存储的最大的字节数

	// 可以使用 KdPrint 配合 wZ 输出一个 UNICODE_STRING对象,注意取地址
	KdPrint(("%wZ\n", &String));
}


// 对于驱动程序,入口函数的名字是 DriverEntry,必须是没有名称粉碎的
#pragma code_seg("INIT")						// 表示当前的函数放置在 INIT 区段中,其中的
												// 所有内容都会在驱动初始化的时候能够使用,一
												// 旦 DriverEntry 执行结束,就会被释放掉
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	DriverObject->DriverUnload = DriverUnload;

	StringTest();

	return STATUS_SUCCESS;
}
2. MDL
#include <ntddk.h>

// 驱动对象的卸载函数,如果不设置,就无法卸载
#pragma code_seg("PAGE")						// 表示当前的函数被放置在分页内存中,如果
												// 短时间内不会用到,可以放置到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	UNREFERENCED_PARAMETER(DriverObject);
}


// 用于测试MDL结构体的函数
VOID MDLTest()
{
	DbgBreakPoint();

	// 创建一个常量指针,指向不可修改的常量字符串
	LPWSTR Buffer = L"ABCDEFGHIJKLMN";

	// 创建一个 MDL 用于描述指定的一个虚拟地址
	PMDL Mdl = IoAllocateMdl(Buffer, 30, FALSE, FALSE, NULL);

	// 对于分页内存,需要使用下面的这个函数进行锁住,暂时性的不允许交换
	// 对于非分页内存,应该使用 MmBuildMdlForNonPagedPool 函数进行操作
	MmProbeAndLockPages(Mdl, KernelMode, IoModifyAccess);

	// 设置 MDL 结构体中的 MdlFlags 字段,添加新的属性,能够允许新的映射可读写
	Mdl->MdlFlags |= MDL_MAPPED_TO_SYSTEM_VA;

    内存描述页表
	// 1. 需要进行重新映射的地址对应的 MDL 结构,必须是锁死在内存的
	// 2. 重新映射的虚拟内存位于什么空间,内核程序通通写 KernelMode
	// 3. 表示允许该数据被 CPU 缓存,加快读写的速度
	// 4. 请求将物理地址映射到哪一个新的虚拟地址上,如果是 NULL,表示随机分配
	// 5. 如果分配出现了问题,是否需要蓝屏
	// 5. 内存级别,使用默认的级别
	WCHAR* Buffer2 = MmMapLockedPagesSpecifyCache(Mdl, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);

	// 修改本身不可写的内容,并进行输出
	Buffer2[0] = L'0';
	KdPrint(("%wZ %wZ\n", &Buffer, &Buffer2));

	// 进行清理操作,和上面的操作是相反的
	MmUnmapLockedPages(Buffer2, Mdl); // 取消映射
	MmUnlockPages(Mdl);               // 取消锁定
    IoFreeMDL(Mdl);                   // 释放MDL

	// 重新的设置不可写的数组,会导致蓝屏
	Buffer[0] = L'A';
}


// 对于驱动程序,入口函数的名字是 DriverEntry,必须是没有名称粉碎的
#pragma code_seg("INIT")						// 表示当前的函数放置在 INIT 区段中,其中的
												// 所有内容都会在驱动初始化的时候能够使用,一
												// 旦 DriverEntry 执行结束,就会被释放掉
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	DriverObject->DriverUnload = DriverUnload;

	MDLTest();

	return STATUS_SUCCESS;
}
3. DeViceObject
#include <ntddk.h>

// 创建一个设备对象,接受的参数是 DriverEntry 传入的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
	// 1. 初始化设备对象的内部名称(只能被 R0 识别的名称)
	// 2. 创建设备对象,并挂在到指定的驱动对象中
	// 3. 由于内部名称只能在内核层使用,需要创建一个外部链接名

	// 创建该函数需要使用到的局部变量
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT DeviceObject = NULL;

	// 初始化设备对象的名称,要求格式必须是 \\Device\\xxx 的形式
	UNICODE_STRING DeviceName = { 0 };
	RtlInitUnicodeString(&DeviceName, L"\\Device\\.min");

	// 创建设备对象使用 IoCreateDevice,如果成功返回 STATUS_SUCCESS
	Status = IoCreateDevice(
		DriverObject,				// 所属的驱动对象,设备对象创建后会添加到其 DeviceObjict 成员中
		0,							// 设备的扩展空间大小,分配的空间会由 DeviceExtension 字段指向
		&DeviceName,				// 设备对象的名称,必须符合格式 \\Device\\DeviceName
		FILE_DEVICE_UNKNOWN,		// 设备对象的类型,特指硬件无关的虚拟设备对象
		0,							// 设备对象的属性
		FALSE,						// 是否启用独占模式,同一时刻能被打开几次
		&DeviceObject);				// 创建出的设备对象,由哪一个指针指向
    
NTSTATUS
IoCreateDeviceSecure(
IN PDRIVER_OBJECT DriverObject,
IN ULONG DeviceExtensionSize,
IN PUNICODE_STRING DeviceName OPTIONAL,
IN DEVICE_TYPE DeviceType,
IN ULONG DeviceCharacteristics,
IN BOOLEAN Exclusive,
IN PCUNICODE_STRING DefaultSDDLString, // 安全描述符定义语言
IN LPCGUID DeviceClassGuid,            // GUID
OUT PDEVICE_OBJECT *DeviceObject)

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		KdPrint(("设备对象创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设备对象的名称只能在内核中被直接的解析,为了 R3 能够识别并操作设备对象,需要
	//	创建与设备名称直接关联的符号链接名,必须写作: \\DosDevices\\xxx 或 \\??\\xxx
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		KdPrint(("符号链接创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	return Status;
}


// 用于处理所有非特殊操作的 IRP 请求
NTSTATUS DefaultDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	UNREFERENCED_PARAMETER(DeviceOBject);

	// 设置当前消息的处理状态,如果成功必须返回 STATUS_SUCCESS,它的返回值影响 R3 的 GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置当前的消息处理了多少个字节的数据,影响 ReadFile 中返回的实际读写字节数
	Irp->IoStatus.Information = 0;

	// 通知 IO 管理器当前的 IRP 已经处理成功,需要返回给 R3
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 当前函数的返回结构,输出是否成功
	return STATUS_SUCCESS;
}


// 处理 CreateFile 产生的消息,注意如果没有设置这个函数,CreateFile 必然失败
NTSTATUS CreateDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	KdPrint(("create device\n"));

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


// 处理 CloseFile 产生的消息
NTSTATUS CloseDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	KdPrint(("close device\n"));

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


// 处理 ReadFile 产生的消息
NTSTATUS ReadDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	KdPrint(("read device\n"));

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


// 处理 WriteFile 产生的消息
NTSTATUS WriteDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	KdPrint(("write device\n"));

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
#include <ntddk.h>
#include "header.h"


// 驱动对象的卸载函数,如果不设置,就无法卸载
#pragma code_seg("PAGE")						// 表示当前的函数被放置在分页内存中,如果
												// 短时间内不会用到,可以放置到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	// 如果设备对象创建成功,那么需要在卸载函数中删除设备对象和符号链接名称
	// 必须要先删除符号链接名,再删除设备对象,否则会出现不可描述的问题

	// 删除符号链接名
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	IoDeleteSymbolicLink(&SymLinkName);

	// 删除设备对象,如果有多个,需要遍历设备对象表
	IoDeleteDevice(DriverObject->DeviceObject);
}


// 对于驱动程序,入口函数的名字是 DriverEntry,必须是没有名称粉碎的
#pragma code_seg("INIT")						// 表示当前的函数放置在 INIT 区段中,其中的
												// 所有内容都会在驱动初始化的时候能够使用,一
												// 旦 DriverEntry 执行结束,就会被释放掉
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	DriverObject->DriverUnload = DriverUnload;

	// 为当前驱动下的所有设备对象,设置消息响应函数
	for (int i = 0; i < 28; ++i)
		DriverObject->MajorFunction[i] = DefaultDispath;

	// 对于一些特殊的函数,需要额外进行处理
	DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateDispath;				// CreateFile
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseDispath;				// CloseFile
	DriverObject->MajorFunction[IRP_MJ_READ] = ReadDispath;					// ReadFile
	DriverObject->MajorFunction[IRP_MJ_WRITE] = WriteDispath;				// WriteFile

	// 创建一个设备对象,如果失败则返回错误码
	NTSTATUS Status = CreateDevice(DriverObject);

	// 如果返回的不是 STATUS_SUCESS,驱动会安装失败,意味着 DriverUnload 不会调用
	return Status; 
}



//0xb8 bytes (sizeof)
struct _DEVICE_OBJECT
{
    SHORT Type;                                                             //0x0
    USHORT Size;                                                            //0x2
    LONG ReferenceCount;                                                    //0x4
    struct _DRIVER_OBJECT* DriverObject;                                    //0x8
    struct _DEVICE_OBJECT* NextDevice;                                      //0xc
    struct _DEVICE_OBJECT* AttachedDevice;                                  //0x10
    struct _IRP* CurrentIrp;                                                //0x14
    struct _IO_TIMER* Timer;                                                //0x18
    ULONG Flags;                                                            //0x1c
    ULONG Characteristics;                                                  //0x20
    struct _VPB* Vpb;                                                       //0x24
    VOID* DeviceExtension;                                                  //0x28
    ULONG DeviceType;                                                       //0x2c
    CHAR StackSize;                                                         //0x30
    union
    {
        struct _LIST_ENTRY ListEntry;                                       //0x34
        struct _WAIT_CONTEXT_BLOCK Wcb;                                     //0x34
    } Queue;                                                                //0x34
    ULONG AlignmentRequirement;                                             //0x5c
    struct _KDEVICE_QUEUE DeviceQueue;                                      //0x60
    struct _KDPC Dpc;                                                       //0x74
    ULONG ActiveThreadCount;                                                //0x94
    VOID* SecurityDescriptor;                                               //0x98
    struct _KEVENT DeviceLock;                                              //0x9c
    USHORT SectorSize;                                                      //0xac
    USHORT Spare1;                                                          //0xae
    struct _DEVOBJ_EXTENSION* DeviceObjectExtension;                        //0xb0
    VOID* Reserved;                                                         //0xb4
}; 
3. R3 Basic
#include <stdio.h>
#include <windows.h>

int main()
{
	// 在驱动程序中创建了设备对象后,可以使用 CreateFile 连接到设备对象,并进行通信
	//	设备对象必须以 \\\\.\\ 开头,当前程序必须使用管理员权限打开
	HANDLE Device = CreateFile(L"\\\\.\\.min", GENERIC_ALL, NULL,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	// 判断句柄是否打开,输出错误信息
	if (Device == NULL || Device == INVALID_HANDLE_VALUE)
	{
		printf("设备对象打开失败,错误原因(%08X)\n", GetLastError());
		system("pause"); exit(0);
	}

	// 读写函数
	DWORD OperBytes = 0;
	ReadFile(Device, NULL, NULL, &OperBytes, NULL);
	WriteFile(Device, NULL, NULL, &OperBytes, NULL);

	// 在进行一系列操作后,需要关闭设备对象句柄,使用 CloseHandle
	CloseHandle(Device);
		
	system("pause");
	return 0;
}
4. do_bufferd_io
#include <ntddk.h>

// 创建一个设备对象,接受的参数是 DriverEntry 传入的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
	// 1. 初始化设备对象的内部名称(只能被 R0 识别的名称)
	// 2. 创建设备对象,并挂在到指定的驱动对象中
	// 3. 由于内部名称只能在内核层使用,需要创建一个外部链接名

	// 创建该函数需要使用到的局部变量
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT DeviceObject = NULL;

	// 初始化设备对象的名称,要求格式必须是 \\Device\\xxx 的形式
	UNICODE_STRING DeviceName = { 0 };
	RtlInitUnicodeString(&DeviceName, L"\\Device\\.min");

	// 创建设备对象使用 IoCreateDevice,如果成功返回 STATUS_SUCCESS
	Status = IoCreateDevice(
		DriverObject,				// 所属的驱动对象,设备对象创建后会添加到其 DeviceObjict 成员中
		0,							// 设备的扩展空间大小,分配的空间会由 DeviceExtension 字段指向
		&DeviceName,				// 设备对象的名称,必须符合格式 \\Device\\DeviceName
		FILE_DEVICE_UNKNOWN,		// 设备对象的类型,特指硬件无关的虚拟设备对象
		0,							// 设备对象的属性
		FALSE,						// 是否启用独占模式,同一时刻能被打开几次
		&DeviceObject);				// 创建出的设备对象,由哪一个指针指向

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		KdPrint(("设备对象创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设备对象的名称只能在内核中被直接的解析,为了 R3 能够识别并操作设备对象,需要
	//	创建与设备名称直接关联的符号链接名,必须写作: \\DosDevices\\xxx 或 \\??\\xxx
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		KdPrint(("符号链接创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设置设备对象的读写方式为缓冲区方式
	DeviceObject->Flags |= DO_BUFFERED_IO;

	return Status;
}


// 用于处理所有非特殊操作的 IRP 请求
NTSTATUS DefaultDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	UNREFERENCED_PARAMETER(DeviceOBject);

	// 设置当前消息的处理状态,如果成功必须返回 STATUS_SUCCESS,它的返回值影响 R3 的 GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置当前的消息处理了多少个字节的数据,影响 ReadFile 中返回的实际读写字节数
	Irp->IoStatus.Information = 0;

	// 通知 IO 管理器当前的 IRP 已经处理成功,需要返回给 R3
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 当前函数的返回结构,输出是否成功
	return STATUS_SUCCESS;
}

// 处理 ReadFile 产生的消息
NTSTATUS ReadDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	// 除了 IRP 结构体之外,IRP 栈保存了当前层 IRP 的附加参数
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

	// Irp 栈中保存了从用户层传递进来的一些信息,这里是 R3 想读取的长度
	KdPrint(("Length: %d\n", IrpStack->Parameters.Read.Length));

	// 当用户层调用 ReadFile 的时候,需要提供一个缓冲区用于接受内容,内存管理器会将
	//	Irp->AssociatedIrp.SystemBuffer 字段中的内容,拷贝到 R3 缓冲区中
	RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, "hello", 6);
	
	// 将实际的操作数量,返回给 R3,由 ReadFile 的第 4 个参数接受
	Irp->IoStatus.Information = 20;

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


// 处理 WriteFile 产生的消息
NTSTATUS WriteDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	// 除了 IRP 结构体之外,IRP 栈保存了当前层 IRP 的附加参数
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

	// Irp 栈中保存了从用户层传递进来的一些信息,这里是 R3 想写入的长度
	KdPrint(("Length: %d\n", IrpStack->Parameters.Write.Length));

	// 当用户层调用 ReadFile 的时候,需要提供一个缓冲区用于接受内容,内存管理器会将
	//	Irp->AssociatedIrp.SystemBuffer 字段中的内容,拷贝到 R3 缓冲区中
	KdPrint(("R3写入了: %s\n", Irp->AssociatedIrp.SystemBuffer));

	// 将实际的操作数量,返回给 R3,由 ReadFile 的第 4 个参数接受
	Irp->IoStatus.Information = 66;

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
#include <ntddk.h>
#include "header.h"


// 驱动对象的卸载函数,如果不设置,就无法卸载
#pragma code_seg("PAGE")						// 表示当前的函数被放置在分页内存中,如果
												// 短时间内不会用到,可以放置到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	// 如果设备对象创建成功,那么需要在卸载函数中删除设备对象和符号链接名称
	// 必须要先删除符号链接名,再删除设备对象,否则会出现不可描述的问题

	// 删除符号链接名
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	IoDeleteSymbolicLink(&SymLinkName);

	// 删除设备对象,如果有多个,需要遍历设备对象表
	IoDeleteDevice(DriverObject->DeviceObject);
}


// 对于驱动程序,入口函数的名字是 DriverEntry,必须是没有名称粉碎的
#pragma code_seg("INIT")						// 表示当前的函数放置在 INIT 区段中,其中的
												// 所有内容都会在驱动初始化的时候能够使用,一
												// 旦 DriverEntry 执行结束,就会被释放掉
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	DriverObject->DriverUnload = DriverUnload;

	// 为当前驱动下的所有设备对象,设置消息响应函数
	for (int i = 0; i < 28; ++i)
		DriverObject->MajorFunction[i] = DefaultDispath;

	// 对于一些特殊的函数,需要额外进行处理
	DriverObject->MajorFunction[IRP_MJ_READ] = ReadDispath;					// ReadFile
	DriverObject->MajorFunction[IRP_MJ_WRITE] = WriteDispath;				// WriteFile

	// 创建一个设备对象,如果失败则返回错误码
	NTSTATUS Status = CreateDevice(DriverObject);

	// 如果返回的不是 STATUS_SUCESS,驱动会安装失败,意味着 DriverUnload 不会调用
	return Status;
}
4. R3
#include <stdio.h>
#include <windows.h>

int main()
{
	DWORD Bytes = 0;

	// 可以使用 CreateFile 去打开一个设备对象,要求管理员权限
	HANDLE DeviceHandle = CreateFile(L"\\\\.\\.min", GENERIC_ALL,
		NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	// 向设备对象写入数据
	WriteFile(DeviceHandle, "123456 r3", 15, &Bytes, NULL);
	printf("WriteFile(15) > Bytes[%d]\n", Bytes);

	// 从设备对象读取内容
	CHAR Buffer[0x100] = { 0 };
	ReadFile(DeviceHandle, Buffer, 0x100, &Bytes, NULL);
	printf("ReadFile > %s Bytes[%d]\n", Buffer, Bytes);

	// 关闭句柄,会相应到 IRP 的 CLOSE 消息
	CloseHandle(DeviceHandle);

	system("pause");
	return 0;
}
5. do_direct_io
#include <ntddk.h>

// 创建一个设备对象,接受的参数是 DriverEntry 传入的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
	// 1. 初始化设备对象的内部名称(只能被 R0 识别的名称)
	// 2. 创建设备对象,并挂在到指定的驱动对象中
	// 3. 由于内部名称只能在内核层使用,需要创建一个外部链接名

	// 创建该函数需要使用到的局部变量
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT DeviceObject = NULL;

	// 初始化设备对象的名称,要求格式必须是 \\Device\\xxx 的形式
	UNICODE_STRING DeviceName = { 0 };
	RtlInitUnicodeString(&DeviceName, L"\\Device\\.min");

	// 创建设备对象使用 IoCreateDevice,如果成功返回 STATUS_SUCCESS
	Status = IoCreateDevice(
		DriverObject,				// 所属的驱动对象,设备对象创建后会添加到其 DeviceObjict 成员中
		0,							// 设备的扩展空间大小,分配的空间会由 DeviceExtension 字段指向
		&DeviceName,				// 设备对象的名称,必须符合格式 \\Device\\DeviceName
		FILE_DEVICE_UNKNOWN,		// 设备对象的类型,特指硬件无关的虚拟设备对象
		0,							// 设备对象的属性
		FALSE,						// 是否启用独占模式,同一时刻能被打开几次
		&DeviceObject);				// 创建出的设备对象,由哪一个指针指向

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		KdPrint(("设备对象创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设备对象的名称只能在内核中被直接的解析,为了 R3 能够识别并操作设备对象,需要
	//	创建与设备名称直接关联的符号链接名,必须写作: \\DosDevices\\xxx 或 \\??\\xxx
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		KdPrint(("符号链接创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设置设备对象的读写方式为缓冲区方式
	DeviceObject->Flags |= DO_DIRECT_IO;

	return Status;
}


// 用于处理所有非特殊操作的 IRP 请求
NTSTATUS DefaultDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	UNREFERENCED_PARAMETER(DeviceOBject);

	// 设置当前消息的处理状态,如果成功必须返回 STATUS_SUCCESS,它的返回值影响 R3 的 GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置当前的消息处理了多少个字节的数据,影响 ReadFile 中返回的实际读写字节数
	Irp->IoStatus.Information = 0;

	// 通知 IO 管理器当前的 IRP 已经处理成功,需要返回给 R3
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 当前函数的返回结构,输出是否成功
	return STATUS_SUCCESS;
}

// 处理 ReadFile 产生的消息
NTSTATUS ReadDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	// 除了 IRP 结构体之外,IRP 栈保存了当前层 IRP 的附加参数
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

	// Irp 栈中保存了从用户层传递进来的一些信息,这里是 R3 想读取的长度
	KdPrint(("Length: %d\n", IrpStack->Parameters.Read.Length));

	// 如果使用的是 DIRECT 方式,那么系统会给我们提供一个绑定到用户缓冲区的
	//	MDL,通过 MmGetSystemAddressForMdlSafe 进行映射
	PVOID Buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
	RtlCopyMemory(Buffer, "hello", 6);

	// 将实际的操作数量,返回给 R3,由 ReadFile 的第 4 个参数接受
	Irp->IoStatus.Information = 20;

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


// 处理 WriteFile 产生的消息
NTSTATUS WriteDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	// 除了 IRP 结构体之外,IRP 栈保存了当前层 IRP 的附加参数
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

	// Irp 栈中保存了从用户层传递进来的一些信息,这里是 R3 想写入的长度
	KdPrint(("Length: %d\n", IrpStack->Parameters.Write.Length));

	// 如果使用的是 DIRECT 方式,那么系统会给我们提供一个绑定到用户缓冲区的
	//	MDL,通过 MmGetSystemAddressForMdlSafe 进行映射
	PVOID Buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
	KdPrint(("R3写入了: %s\n", Buffer));

	// 将实际的操作数量,返回给 R3,由 ReadFile 的第 4 个参数接受
	Irp->IoStatus.Information = 66;

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
#include <ntddk.h>
#include "header.h"


// 驱动对象的卸载函数,如果不设置,就无法卸载
#pragma code_seg("PAGE")						// 表示当前的函数被放置在分页内存中,如果
												// 短时间内不会用到,可以放置到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	// 如果设备对象创建成功,那么需要在卸载函数中删除设备对象和符号链接名称
	// 必须要先删除符号链接名,再删除设备对象,否则会出现不可描述的问题

	// 删除符号链接名
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	IoDeleteSymbolicLink(&SymLinkName);

	// 删除设备对象,如果有多个,需要遍历设备对象表
	IoDeleteDevice(DriverObject->DeviceObject);
}


// 对于驱动程序,入口函数的名字是 DriverEntry,必须是没有名称粉碎的
#pragma code_seg("INIT")						// 表示当前的函数放置在 INIT 区段中,其中的
												// 所有内容都会在驱动初始化的时候能够使用,一
												// 旦 DriverEntry 执行结束,就会被释放掉
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	DriverObject->DriverUnload = DriverUnload;

	// 为当前驱动下的所有设备对象,设置消息响应函数
	for (int i = 0; i < 28; ++i)
		DriverObject->MajorFunction[i] = DefaultDispath;

	// 对于一些特殊的函数,需要额外进行处理
	DriverObject->MajorFunction[IRP_MJ_READ] = ReadDispath;					// ReadFile
	DriverObject->MajorFunction[IRP_MJ_WRITE] = WriteDispath;				// WriteFile

	// 创建一个设备对象,如果失败则返回错误码
	NTSTATUS Status = CreateDevice(DriverObject);

	// 如果返回的不是 STATUS_SUCESS,驱动会安装失败,意味着 DriverUnload 不会调用
	return Status;
}
6. neither
#include <ntddk.h>

// 创建一个设备对象,接受的参数是 DriverEntry 传入的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
	// 1. 初始化设备对象的内部名称(只能被 R0 识别的名称)
	// 2. 创建设备对象,并挂在到指定的驱动对象中
	// 3. 由于内部名称只能在内核层使用,需要创建一个外部链接名

	// 创建该函数需要使用到的局部变量
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT DeviceObject = NULL;

	// 初始化设备对象的名称,要求格式必须是 \\Device\\xxx 的形式
	UNICODE_STRING DeviceName = { 0 };
	RtlInitUnicodeString(&DeviceName, L"\\Device\\.min");

	// 创建设备对象使用 IoCreateDevice,如果成功返回 STATUS_SUCCESS
	Status = IoCreateDevice(
		DriverObject,				// 所属的驱动对象,设备对象创建后会添加到其 DeviceObjict 成员中
		0,							// 设备的扩展空间大小,分配的空间会由 DeviceExtension 字段指向
		&DeviceName,				// 设备对象的名称,必须符合格式 \\Device\\DeviceName
		FILE_DEVICE_UNKNOWN,		// 设备对象的类型,特指硬件无关的虚拟设备对象
		0,							// 设备对象的属性
		FALSE,						// 是否启用独占模式,同一时刻能被打开几次
		&DeviceObject);				// 创建出的设备对象,由哪一个指针指向

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		KdPrint(("设备对象创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设备对象的名称只能在内核中被直接的解析,为了 R3 能够识别并操作设备对象,需要
	//	创建与设备名称直接关联的符号链接名,必须写作: \\DosDevices\\xxx 或 \\??\\xxx
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);

	// 通过 NT_SUCCESS 判断函数的调用是否成功
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		KdPrint(("符号链接创建失败,检查原因,错误码(%08X)\n", Status));
		return Status;
	}

	// 设置设备对象的读写方式为缓冲区方式
	DeviceObject->Flags;

	return Status;
}


// 用于处理所有非特殊操作的 IRP 请求
NTSTATUS DefaultDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	UNREFERENCED_PARAMETER(DeviceOBject);

	// 设置当前消息的处理状态,如果成功必须返回 STATUS_SUCCESS,它的返回值影响 R3 的 GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置当前的消息处理了多少个字节的数据,影响 ReadFile 中返回的实际读写字节数
	Irp->IoStatus.Information = 0;

	// 通知 IO 管理器当前的 IRP 已经处理成功,需要返回给 R3
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 当前函数的返回结构,输出是否成功
	return STATUS_SUCCESS;
}

// 处理 ReadFile 产生的消息
NTSTATUS ReadDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	// 除了 IRP 结构体之外,IRP 栈保存了当前层 IRP 的附加参数
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

	// Irp 栈中保存了从用户层传递进来的一些信息,这里是 R3 想读取的长度
	KdPrint(("Length: %d\n", IrpStack->Parameters.Read.Length));

	// 如果使用两者都不的方式,那么内核代码会直接尝试操作用户数据,此时由于
	//	不能保证数据和进程是对应的,所以可能出现问题
	try {
		RtlCopyMemory(Irp->UserBuffer, "hello", 6);
	} except(EXCEPTION_EXECUTE_HANDLER) {
		KdPrint(("地址无法访问,请尝试其它方式\n"));
	}

	// 将实际的操作数量,返回给 R3,由 ReadFile 的第 4 个参数接受
	Irp->IoStatus.Information = 20;

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}


// 处理 WriteFile 产生的消息
NTSTATUS WriteDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	// 除了 IRP 结构体之外,IRP 栈保存了当前层 IRP 的附加参数
	PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);

	// Irp 栈中保存了从用户层传递进来的一些信息,这里是 R3 想写入的长度
	KdPrint(("Length: %d\n", IrpStack->Parameters.Write.Length));

	// 如果使用两者都不的方式,那么内核代码会直接尝试操作用户数据,此时由于
//	不能保证数据和进程是对应的,所以可能出现问题
	try {
		KdPrint(("R3: %s\n", Irp->UserBuffer));
	} except(EXCEPTION_EXECUTE_HANDLER) {
		KdPrint(("地址无法访问,请尝试其它方式\n"));
	}

	// 将实际的操作数量,返回给 R3,由 ReadFile 的第 4 个参数接受
	Irp->IoStatus.Information = 66;

	UNREFERENCED_PARAMETER(DeviceOBject);
	Irp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
#include <ntddk.h>
#include "header.h"


// 驱动对象的卸载函数,如果不设置,就无法卸载
#pragma code_seg("PAGE")						// 表示当前的函数被放置在分页内存中,如果
												// 短时间内不会用到,可以放置到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	// 如果设备对象创建成功,那么需要在卸载函数中删除设备对象和符号链接名称
	// 必须要先删除符号链接名,再删除设备对象,否则会出现不可描述的问题

	// 删除符号链接名
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	IoDeleteSymbolicLink(&SymLinkName);

	// 删除设备对象,如果有多个,需要遍历设备对象表
	IoDeleteDevice(DriverObject->DeviceObject);
}


// 对于驱动程序,入口函数的名字是 DriverEntry,必须是没有名称粉碎的
#pragma code_seg("INIT")						// 表示当前的函数放置在 INIT 区段中,其中的
												// 所有内容都会在驱动初始化的时候能够使用,一
												// 旦 DriverEntry 执行结束,就会被释放掉
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(RegistryPath);
	DriverObject->DriverUnload = DriverUnload;

	// 为当前驱动下的所有设备对象,设置消息响应函数
	for (int i = 0; i < 28; ++i)
		DriverObject->MajorFunction[i] = DefaultDispath;

	// 对于一些特殊的函数,需要额外进行处理
	DriverObject->MajorFunction[IRP_MJ_READ] = ReadDispath;					// ReadFile
	DriverObject->MajorFunction[IRP_MJ_WRITE] = WriteDispath;				// WriteFile

	// 创建一个设备对象,如果失败则返回错误码
	NTSTATUS Status = CreateDevice(DriverObject);

	// 如果返回的不是 STATUS_SUCESS,驱动会安装失败,意味着 DriverUnload 不会调用
	return Status;
}
7. control
#include <ntddk.h>

// 设备类型,IOCTL码,读写方式,权限
#define BufferWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define DirectWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x802, METHOD_IN_DIRECT, FILE_ANY_ACCESS )
#define NeitherWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS )


// 创建设备对象,接收的参数是所属的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
	DbgBreakPoint();

	// 创建需要用到的一些变量
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT DeviceObject = NULL;

	// 初始化设备对象的名称,名称必须是 \\Device\\xxx 的形式
	UNICODE_STRING DeviceName = { 0 };
	RtlInitUnicodeString(&DeviceName, L"\\Device\\mmm");

	// 创建设备对象的函数,如果成功,返回 STATUS_SUCCESS
	Status = IoCreateDevice(
		DriverObject,			// 所属驱动对象,创建后会被添加到它的 DeviceObject 链表中
		0,						// 设备的扩展空间大小,分配的空间会被 DeviceExtension 指向
		&DeviceName,			// 设备对象的名称
		FILE_DEVICE_UNKNOWN,	// 一般说明它是一个和硬件无关的虚拟设备
		0,						// 设备的属性信息
		TRUE,					// 是否独占 \ R3是否可访问
		&DeviceObject);			// 创建出的设备对象被保存到的地方

	// 通过 NT_SUCCESS 判断创建是否成功
	if (!NT_SUCCESS(Status))
	{
		KdPrint(("设备对象创建失败"));
		return Status;
	}

	// 设备对象的名称只能被内核程序解析,为了让R3应用识别
	//	需要设置符号链接名: \\DosDevice\\xxx 或 \\??\\xxx
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\mmms");
	Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);

	// 通过 NT_SUCCESS 判断创建是否成功
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		KdPrint(("设备对象创建失败"));
		return Status;
	}

	// 创建成功后,可以通过设置 Flags 指定读写的方式,如果没有指定
	//	读写方式,就会采用两者都不的形式,此时访问的直接是用户空间

	return Status;
}


// 用于实现默认的消息派遣函数
NTSTATUS DefaultDispatch(
	PDEVICE_OBJECT DeviceObject,		// 表示当前的消息是那个设备对象产生的
	PIRP Irp)							// IRP,对应的是三环程序的消息,保存了一些附加参数
{
	UNREFERENCED_PARAMETER(DeviceObject);

	// 设置消息的处理状态: 成功或失败 -> GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
	Irp->IoStatus.Information = 0;

	// 通知操作已经完成,完成后不提高当前的 IRQL
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 返回当前处理的整体结果是成功还是失败
	return STATUS_SUCCESS;
}


// 用于实现默认的消息派遣函数
NTSTATUS DeviceIoControlDispatch(
	PDEVICE_OBJECT DeviceObject,		// 表示当前的消息是那个设备对象产生的
	PIRP Irp)							// IRP,对应的是三环程序的消息,保存了一些附加参数
{
	UNREFERENCED_PARAMETER(DeviceObject);
	PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

	// 可以通过 irp 栈获取到 DeviceIoControl 的通知码
	switch (Stack->Parameters.DeviceIoControl.IoControlCode)
	{
	case BufferWay:
		Irp->AssociatedIrp.SystemBuffer;
		Irp->MdlAddress;
		break;
	case DirectWay:
		Irp->MdlAddress;
		break;
	case NeitherWay:
		Irp->UserBuffer;
		Stack->Parameters.DeviceIoControl.Type3InputBuffer;
		break;
	}

	// 设置消息的处理状态: 成功或失败 -> GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
	Irp->IoStatus.Information = 0;

	// 通知操作已经完成,完成后不提高当前的 IRQL
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 返回当前处理的整体结果是成功还是失败
	return STATUS_SUCCESS;
}
// DeviceType: 表示设备的类型,虚拟设备应该指定一个大于 0x8000
// Function: 一个自定义的消息,指明需要执行的是什么操作,应该大于 0x800 0x000-0x7ff为系统保留
// Method: 表示当前的消息以什么样的方式传递数据,直接IO,缓冲区IO等
// Access: 当前的消息具有什么样的权限,通常都是 FILE_ANY_ACCESS
#define BUFFER_METHOND CTL_CODE(0x8000, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define DIRECT_METHOND CTL_CODE(0x8000, 0x802, METHOD_IN_DIRECT, FILE_ANY_ACCESS)
#define NEITHER_METHOND CTL_CODE(0x8000, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS)


// 在进行数据传递时,需要用到的结构体
typedef struct _INFO 
{
	int number;
	char str[0x10];
} INFO, *PINFO;
#include <ntddk.h>
#include "code.h"
/*
	// 输出位置
	METHOD_IN_DIRECT irp->AssociatedIrp.SystemBuffer
	METHOD_OUT_DIRECT irp->AssociatedIrp.SystemBuffer
	METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
	METHOD_NEITHER irpStack->Parameters.DeviceIoControl.Type3InputBuffer

	// 输入位置
	METHOD_IN_DIRECT irp->MdlAddress
	METHOD_OUT_DIRECT irp->MdlAddress
	METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
	METHOD_NEITHER irp->UserBuffer
*/

缓冲区方式与IRP的关系如下:

在驱动层,依传输类型的不同,输入缓冲区的位置亦不同,见下表。
传输类型                               位置
METHOD_IN_DIRECT                irp->AssociatedIrp.SystemBuffer
METHOD_OUT_DIRECT             irp->AssociatedIrp.SystemBuffer
METHOD_BUFFERED                 irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER                   irpStack->Parameters.DeviceIoControl.Type3InputBuffer

在驱动层,依传输类型的不同,输出缓冲区的位置亦不同,见下表。
传输类型                              位置
METHOD_IN_DIRECT                irp->MdlAddress
METHOD_OUT_DIRECT             irp->MdlAddress
METHOD_BUFFERED                 irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER                    irp->UserBuffer



// 创建设备对象,接收的参数是所属的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
	DbgBreakPoint();

	// 创建需要用到的一些变量
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT DeviceObject = NULL;

	// 初始化设备对象的名称,名称必须是 \\Device\\xxx 的形式
	UNICODE_STRING DeviceName = { 0 };
	RtlInitUnicodeString(&DeviceName, L"\\Device\\.min");

	// 创建设备对象的函数,如果成功,返回 STATUS_SUCCESS
	Status = IoCreateDevice(
		DriverObject,			// 所属驱动对象,创建后会被添加到它的 DeviceObject 链表中
		0,						// 设备的扩展空间大小,分配的空间会被 DeviceExtension 指向
		&DeviceName,			// 设备对象的名称
		FILE_DEVICE_UNKNOWN,	// 一般说明它是一个和硬件无关的虚拟设备
		0,						// 设备的属性信息
		TRUE,					// 是否独占 \ R3是否可访问
		&DeviceObject);			// 创建出的设备对象被保存到的地方

	// 通过 NT_SUCCESS 判断创建是否成功
	if (!NT_SUCCESS(Status))
	{
		KdPrint(("设备对象创建失败"));
		return Status;
	}

	// 设备对象的名称只能被内核程序解析,为了让R3应用识别
	//	需要设置符号链接名: \\DosDevice\\xxx 或 \\??\\xxx
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\.min");
	Status = IoCreateSymbolicLink(&SymLinkName, &DeviceName);

	// 通过 NT_SUCCESS 判断创建是否成功
	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		KdPrint(("设备对象创建失败"));
		return Status;
	}

	// 创建成功后,可以通过设置 Flags 指定读写的方式,如果没有指定
	//	读写方式,就会采用两者都不的形式,此时访问的直接是用户空间

	return Status;
}


// 用于实现默认的消息派遣函数
NTSTATUS DefaultDispatch(
	PDEVICE_OBJECT DeviceObject,		// 表示当前的消息是那个设备对象产生的
	PIRP Irp)							// IRP,对应的是三环程序的消息,保存了一些附加参数
{
	UNREFERENCED_PARAMETER(DeviceObject);

	// 设置消息的处理状态: 成功或失败 -> GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
	Irp->IoStatus.Information = 0;

	// 通知操作已经完成,完成后不提高当前的 IRQL
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 返回当前处理的整体结果是成功还是失败
	return STATUS_SUCCESS;
}


// 用于实现默认的消息派遣函数
NTSTATUS DeviceIoControlDispatch(
	PDEVICE_OBJECT DeviceObject,		// 表示当前的消息是那个设备对象产生的
	PIRP Irp)							// IRP,对应的是三环程序的消息,保存了一些附加参数
{
	UNREFERENCED_PARAMETER(DeviceObject);
	PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);

	DbgBreakPoint();

	// 可以通过 irp 栈获取到 DeviceIoControl 的通知码
	switch (Stack->Parameters.DeviceIoControl.IoControlCode)
	{
	/*case BufferWay:
		Irp->AssociatedIrp.SystemBuffer;					// R3->R0
		Irp->MdlAddress;									// R0->R3
		break;
	case DirectWay:
		Irp->AssociatedIrp.SystemBuffer						// R0->R3
		Irp->MdlAddress;									// R3->R0
		break;
	case NeitherWay:
		Irp->UserBuffer;									// R0->R3
		Stack->Parameters.DeviceIoControl.Type3InputBuffer;	// R3->R0
		break;*/
	default:
		break;
	}

	// 设置消息的处理状态: 成功或失败 -> GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
	Irp->IoStatus.Information = 0;

	// 通知操作已经完成,完成后不提高当前的 IRQL
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 返回当前处理的整体结果是成功还是失败
	return STATUS_SUCCESS;
}
#include <ntddk.h>
#include "header.h"


// 驱动程序的卸载函数,会在卸载时调用,通常用于删除设备对象
#pragma code_seg("PAGE")		// 分页内存: 能过够被交换到页交换文件的内存
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	// 删除设备对象的时候,必须先删符号名,再删设备对象
	UNICODE_STRING SymLinkName = { 0 };
	RtlInitUnicodeString(&SymLinkName, L"\\??\\mmms");
	IoDeleteSymbolicLink(&SymLinkName);

	// 可以通过驱动对象的链表找到设备对象,进行删除
	IoDeleteDevice(DriverObject->DeviceObject);

	UNREFERENCED_PARAMETER(DriverObject);
}


// 驱动程序的入口函数,参数一表示当前的驱动对象,参数二是在注册表的路径
#pragma code_seg("INIT")		// 只有在初始化的时候存在,初始化完毕被卸载的内存
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);

	// 只有设置了卸载函数的驱动程序才能被卸载
	DriverObject->DriverUnload = DriverUnload;

	// 创建设备对象,只有设备对象才能进行通信
	CreateDevice(DriverObject);

	// 为所有的 IRP 消息设置默认的派遣函数
	for (int i = 0; i < 28; ++i)
		DriverObject->MajorFunction[i] = DefaultDispatch;

	// 创建设备对象之后,可以通过驱动对象设置消息的派遣函数(消息响应)
	//	消息是由设备对象产生的,但统一由驱动对象进行处理,对于下面的
	//	几个消息,如果不提供,就不能完成相应的函数调用
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControlDispatch;	// DeviceIoControl(自定义消息)

	// 只有当返回值为 STATUS_SUCCESS 驱动才会被加载到系统中
	return STATUS_SUCCESS;
}
7. R3 Basic
#include <stdio.h>
#include <windows.h>
#include "../07 device-io-control/code.h"



int main()
{
	// 可以使用 CreateFile 去打开一个设备对象,要求管理员权限
	HANDLE DeviceHandle = CreateFile(L"\\\\.\\.min", GENERIC_ALL,
		NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	DWORD Bytes = 0;
	INFO info = { 0x1234, "one" };
	BYTE buffer[0x100] = { 0 };

	// 1. 设备对象,需要向哪一个设备发出请求
	// 2. 需要传递给 R0 的缓冲区和大小
	// 3. 用于接受 R0 传回数据的缓冲区和大小
	DeviceIoControl(DeviceHandle, NEITHER_METHOND,
		&info, sizeof(info),		// 需要传递给 R0 的缓冲区和大小
		buffer, 0x100, &Bytes,  	// 用于接受 R0 传回的数据
		NULL);						// 重叠 IO 结构体

	// 关闭句柄,会相应到 IRP 的 CLOSE 消息
	CloseHandle(DeviceHandle);

	system("pause");
	return 0;
}

3. IRP(消息)

IRP(I/O Request Package)

在这里插入图片描述

//0x70 bytes (sizeof)
struct _IRP
{
    SHORT Type;                                                             //0x0
    USHORT Size;                                                            //0x2
    struct _MDL* MdlAddress;                                                //0x4
    ULONG Flags;                                                            //0x8
    union
    {
        struct _IRP* MasterIrp;                                             //0xc
        LONG IrpCount;                                                      //0xc
        VOID* SystemBuffer;                                                 //0xc
    } AssociatedIrp;                                                        //0xc
    struct _LIST_ENTRY ThreadListEntry;                                     //0x10
    struct _IO_STATUS_BLOCK IoStatus;                                       //0x18
    CHAR RequestorMode;                                                     //0x20
    UCHAR PendingReturned;                                                  //0x21
    CHAR StackCount;                                                        //0x22
    CHAR CurrentLocation;                                                   //0x23
    UCHAR Cancel;                                                           //0x24
    UCHAR CancelIrql;                                                       //0x25
    CHAR ApcEnvironment;                                                    //0x26
    UCHAR AllocationFlags;                                                  //0x27
    struct _IO_STATUS_BLOCK* UserIosb;                                      //0x28
    struct _KEVENT* UserEvent;                                              //0x2c
    union
    {
        struct
        {
            union
            {
                VOID (*UserApcRoutine)(VOID* arg1, struct _IO_STATUS_BLOCK* arg2, ULONG arg3); //0x30
                VOID* IssuingProcess;                                       //0x30
            };
            VOID* UserApcContext;                                           //0x34
        } AsynchronousParameters;                                           //0x30
        union _LARGE_INTEGER AllocationSize;                                //0x30
    } Overlay;                                                              //0x30
    VOID (*CancelRoutine)(struct _DEVICE_OBJECT* arg1, struct _IRP* arg2);  //0x38
    VOID* UserBuffer;                                                       //0x3c
    union
    {
        struct
        {
            union
            {
                struct _KDEVICE_QUEUE_ENTRY DeviceQueueEntry;               //0x40
                VOID* DriverContext[4];                                     //0x40
            };
            struct _ETHREAD* Thread;                                        //0x50
            CHAR* AuxiliaryBuffer;                                          //0x54
            struct _LIST_ENTRY ListEntry;                                   //0x58
            union
            {
                struct _IO_STACK_LOCATION* CurrentStackLocation;            //0x60
                ULONG PacketType;                                           //0x60
            };
            struct _FILE_OBJECT* OriginalFileObject;                        //0x64
        } Overlay;                                                          //0x40
        struct _KAPC Apc;                                                   //0x40
        VOID* CompletionKey;                                                //0x40
    } Tail;                                                                 //0x40
}; 

三. 驱动程序

四. 内核编程特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QlIToiUR-1616297958243)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210208215043528.png)]

1. 数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8L670hKG-1616297958244)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210208215105975.png)]

2. 函数内核属性

在这里插入图片描述

3. 概念
4. 内核API
  1. IO 管理器 创建设备

  2. Ex 执行体 内存分配

  3. RTL RunTimeLibrary运行时库函数 strcpy

  4. Ke kernel内核相关

  5. Zw 系统服务表 操作系统导出函数 0环

  6. NT Natives 原生函数 3环到0环过度函数 用户的最顶层 内核的最底层

分页内存一般指页交换文件可以放在内存中也可以放在磁盘中,非分页内存只能放在内存中

用户层内存属性 私有的private IMAGE映像 MAPPING映射

5. 函数返回值

在这里插入图片描述

6. IRQL

中断请求级别

在这里插入图片描述

7. UNICODE_STRING
  1. RtlInitUnicodeString 初始化字符串
  2. RtlFreeUnicodeString 销毁字符串
  3. RtlCopyUnicodeString 拷贝字符串
  4. RtlAppendUnicodeStringToString 追加字符串
  5. RtlCompareUnicodeString 比较字符串
  6. RtlUnicodeStringToInteger 字符串转数字
  7. RtlIntegerToUnicodeString 数字转字符串
  8. Kdprint 输出调试信息
8. 内存操作

在这里插入图片描述

在这里插入图片描述

RtlZeroMemory (缓冲区,大小)清空内存为0

RtlFillMemory(缓冲区,大小,填充内容)

RtlCopyMemory(目的缓冲区,源缓冲区,大小)

ExFreePool(缓冲区)

9. MDL(内存描述符表)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0L6wOR0c-1616297958248)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210211144742260.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ByAyjSg-1616297958248)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210211144951689.png)]

Dispatch不能产生异常 ,因为异常也是DPC级别的

五. 设备对象

1. 创建设备对象

2. IRP的处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v5ln0wKh-1616297958250)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210212175138738.png)]

1. IRP栈

IRP结构体是 I/O 信息的一部分,还有一个IRP栈对应的一个 IO_STACK_LOCATION结构体

获取当前IRP栈 PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(In PIRP Irp)

有两个重要成员,分别是MajorFuction和MinorFuction分别记录了IRP的主类型(什么IRP)和子类型(子消息)

还有一个重要的联合体成员,根据不同的IRP传递不同的消息

2. 处理方式
  1. 填写不同的回调函数,类似MFC
  2. 填写相同的回调函数,类似SDK,再判断
// 用于处理所有非特殊操作的 IRP 请求
NTSTATUS DefaultDispath(
	PDEVICE_OBJECT DeviceOBject,		// 表示当前的消息是哪一个设备对象接受的
	PIRP Irp)							// IRP,接收到的消息是什么,类似 R3 的 MSG
{
	UNREFERENCED_PARAMETER(DeviceOBject);

	// 设置当前消息的处理状态,如果成功必须返回 STATUS_SUCCESS,它的返回值影响 R3 的 GetLastError
	Irp->IoStatus.Status = STATUS_SUCCESS;

	// 设置当前的消息处理了多少个字节的数据,影响 ReadFile 中返回的实际读写字节数
	Irp->IoStatus.Information = 0;

	// 通知 IO 管理器当前的 IRP 已经处理成功,需要返回给 R3
	IoCompleteRequest(Irp, IO_NO_INCREMENT);

	// 当前函数的返回结构,输出是否成功
	return STATUS_SUCCESS;
}

3. 设备对象通讯方式

效率和安全的区分,

对于小不讲究效率 安全的用buffer

对于大型的讲究效率的用直接direct

对于 用user 同步不需要考虑安全 异步需要考虑

4. 控制码通讯

五. 文件操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3Cs6dfO-1616297958252)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210215145258439.png)]

最后一个带名称的内核对象,需要初始化为一个对象属性结构体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xUv4b7hH-1616297958252)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210215150145746.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzTzzEvn-1616297958253)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210215150255196.png)]

NT函数会检查地址是否可以访问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWpe3vSR-1616297958253)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210215161422230.png)]

在这里插入图片描述

#include <ntifs.h>
#include <ntddk.h>


// 使用指定的方式创建或打开一个指定的文件或目录
HANDLE CreateFile(LPCWSTR pFilePath, ACCESS_MASK Access, BOOLEAN IsFile)
{
	HANDLE FileHandle = NULL;
	IO_STATUS_BLOCK IoStatusBlock = { 0 };

	// 初始化文件或目录的名字保存到 UNICODE_STRING 中
	UNICODE_STRING FilePath = { 0 };
	RtlInitUnicodeString(&FilePath, pFilePath);

	// 操作一个具名对象的时候,通常都需要提供结构体,描述文件的名字
	OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
	InitializeObjectAttributes(
		&ObjectAttributes,				// 需要初始化的对象属性结构体
		&FilePath,						// 当前的对象描述的是哪一个名称
		OBJ_CASE_INSENSITIVE,			// 表示对名称不区分大小写
		NULL, NULL);					// 对象根目录和安全属性字段


	// 根据用户传入的布尔值设置 CreateOption 的值是目录\文件
	ULONG CreateOption = IsFile ? FILE_NON_DIRECTORY_FILE
		: FILE_DIRECTORY_FILE;


	// 内核层操作文件的函数是 ZwCreateFile,如果失败返回失败原因
	NTSTATUS Status = ZwCreateFile(
		&FileHandle,					// 成功调用函数后用于保存句柄的变量
		Access,							// 以什么样的权限去操作文件 GENERIC_ALL
		&ObjectAttributes,				// 主要用于保存文件的路径
		&IoStatusBlock,					// 保存的是当前函数的执行结果
		0,								// 如果是创建新的文件,那么文件默认的大小是多少
		FILE_ATTRIBUTE_NORMAL,			// 创建或打开的文件应该具有什么样的属性
		FILE_SHARE_VALID_FLAGS,			// 文件允许的共享方式,和 R3 保持一致
		FILE_OPEN_IF,					// 打开\创建文件的方式 FILE_OPEN_IF 有就开
		CreateOption,					// 当前属性我们只需要关注操作的是文件还是目录
		NULL, 0);						// 描述扩展区域的大小,还有指向此区域的指针


	// 判断打开路径的操作是否成功,如果失败返回-1,否则返回句柄
	if (NT_SUCCESS(Status))
		return FileHandle;
	else
		return (HANDLE)-1;
}


// 向指定文件内写入指定大小的指定数据
NTSTATUS WriteFile(HANDLE FileHandle, PVOID Buffer, ULONG Length, ULONG pOffset)
{
	IO_STATUS_BLOCK IoStatusBlock = { 0 };
	LARGE_INTEGER Offset = { pOffset, 0 };

	// 向指定的文件内写入指定长度的数据
	NTSTATUS Status = ZwWriteFile(
		FileHandle,					// 需要操作的到底是哪个文件
		NULL, NULL, NULL,			// 异步 IO 使用的三个参数,几乎不用
		&IoStatusBlock,				// 保存了操作的结果,例如字节数和是否成功
		Buffer, Length,				// 需要写入的数据以及写入的长度
		&Offset, 0);				// 需要操作的文件指针的位置

	// 无论是否操作成功,都直接返回操作结果
	return Status;
}


// 从指定文件读取指定大小的指定数据
NTSTATUS ReadFile(HANDLE FileHandle, PVOID Buffer, ULONG Length, ULONG pOffset)
{
	IO_STATUS_BLOCK IoStatusBlock = { 0 };
	LARGE_INTEGER Offset = { pOffset, 0 };

	// 从指定文件读取指定长度的数据
	NTSTATUS Status = ZwReadFile(
		FileHandle,					// 需要操作的到底是哪个文件
		NULL, NULL, NULL,			// 异步 IO 使用的三个参数,几乎不用
		&IoStatusBlock,				// 保存了操作的结果,例如字节数和是否成功
		Buffer, Length,				// 需要读取的数据以及读取的长度
		&Offset, 0);				// 需要操作的文件指针的位置

	// 无论是否操作成功,都直接返回操作结果
	return Status;
}


// 获取指定文件的基本信息,该函数主要获取文件的大小
LONGLONG GetFileSize(HANDLE FileHandle)
{
	IO_STATUS_BLOCK IoStatusBlock = { 0 };
	FILE_STANDARD_INFORMATION FileInfo = { 0 };

	// 用于查询文件信息的函数,可以查询非常多的信息
	NTSTATUS Status = ZwQueryInformationFile(
		FileHandle,					// 需要查询哪个文件的信息
		&IoStatusBlock,				// 查询成功了还是失败了
		&FileInfo,					// 查询到的信息保存到哪里,结构体不是固定的
		sizeof(FileInfo),			// 提供的缓冲区的大小
		FileStandardInformation);	// 需要查询的是哪种类型的信息

	// 如果成功返回查询的结果,否则返回 0
	if (NT_SUCCESS(Status))
		return  FileInfo.EndOfFile.QuadPart;
	else
		return 0;
}


// 删除文件,不需要指定文件句柄
NTSTATUS DeleteFile(LPCWSTR pFilePath)
{
	// 初始化文件或目录的名字保存到 UNICODE_STRING 中
	UNICODE_STRING FilePath = { 0 };
	RtlInitUnicodeString(&FilePath, pFilePath);

	// 操作一个具名对象的时候,通常都需要提供结构体,描述文件的名字
	OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
	InitializeObjectAttributes(
		&ObjectAttributes,				// 需要初始化的对象属性结构体
		&FilePath,						// 当前的对象描述的是哪一个名称
		OBJ_CASE_INSENSITIVE,			// 表示对名称不区分大小写
		NULL, NULL);					// 对象根目录和安全属性字段

	// 返回删除的结果
	return ZwDeleteFile(&ObjectAttributes);
}


// 遍历指定目录下的所有文件
VOID ListDirectoryFile(LPCWSTR DirPath)
{
	// 获取指定目录的句柄,用于后续的遍历
	HANDLE DirHandle = CreateFile(DirPath, GENERIC_ALL, FALSE);

	// 创建一些用于支持遍历的结构体和变量
	IO_STATUS_BLOCK IoStatusBlock = { 0 };
	ULONG InfoSize = sizeof(FILE_FULL_DIR_INFORMATION) + 260 * 2;
	PFILE_FULL_DIR_INFORMATION FileInfo = (PFILE_FULL_DIR_INFORMATION)
		ExAllocatePoolWithTag(NonPagedPool, InfoSize, 'elif');

	// 遍历指定目录下的第一个文件,并返回遍历到的结果
	NTSTATUS Status = ZwQueryDirectoryFile(
		DirHandle,							// 需要遍历的是哪一个[目录]
		NULL, NULL, NULL,					// 基本不会用到的三个异步IO参数
		&IoStatusBlock,						// 保存 IO 操作的结果
		FileInfo, InfoSize,					// 保存数据的结构体以及其大小
		FileFullDirectoryInformation,		// 需要查询到的是什么信息
		TRUE,								// 是否仅返回一项内容
		NULL,								// 如果想要查询指定文件的信息,就需要传入
		TRUE);								// 是否是从第一个文件开始遍历

	// 如果第一个文件的信息遍历成功,就尝试继续遍历其他文件的信息
	if (NT_SUCCESS(Status))
	{
		do {
			// 输出遍历到的文件的信息,并清除结构体内容,否则下次遍历可能保存之前的信息
			KdPrint(("%S\n", FileInfo->FileName));
			RtlZeroMemory(FileInfo, InfoSize);

			// 遍历指定目录下的第一个文件,并返回遍历到的结果
			ZwQueryDirectoryFile(
				DirHandle,							// 需要遍历的是哪一个[目录]
				NULL, NULL, NULL,					// 基本不会用到的三个异步IO参数
				&IoStatusBlock,						// 保存 IO 操作的结果
				FileInfo, InfoSize,					// 保存数据的结构体以及其大小
				FileFullDirectoryInformation,		// 需要查询到的是什么信息
				TRUE,								// 是否仅返回一项内容
				NULL,								// 如果想要查询指定文件的信息,就需要传入
				FALSE);								// 是否是从第一个文件开始遍历

			// 结束条件的判断是 IoStatusBlock 而不是函数的返回值
		} while (IoStatusBlock.Status != STATUS_NO_MORE_FILES);
	}

	// 释放空间并关闭句柄
	ExFreePoolWithTag(FileInfo, 'elif');
	ZwClose(DirHandle);
}
#include "header.h"


// 驱动的卸载函数,会在祛痘被卸载的时候调用
#pragma code_seg("PAGE")		// 分页内存,能够被交换到页交换文件中
VOID DriverUnload(PDRIVER_OBJECT DriverOBject)
{
	UNREFERENCED_PARAMETER(DriverOBject);
}


// 驱动程序的入口函数,参数一表示的是当前的驱动对象,参数二是位于注册表的路径
#pragma code_seg("INIT")		// 只有在初始化的时候存在,初始化完毕内存就被释放
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverOBject, PUNICODE_STRING RegistryPath)
{
	DbgBreakPoint();
	UNREFERENCED_PARAMETER(RegistryPath);

	// 只有设置了卸载函数才能够停止驱动程序
	DriverOBject->DriverUnload = DriverUnload;

	/*
		// 如果文件存在,则以只读的方式打开指定的文件,否则就创建它,路径是 \\??\\...
		HANDLE FileHandle = CreateFile(L"\\??\\D:\\data.txt", GENERIC_WRITE, TRUE);
		// 向文件内写入字符串: "hello world"
		WriteFile(FileHandle, "hello world", 12, 0);
		// 文件打开以后必须使用 ZwClose 进行关闭,否则会产生文件占用
		ZwClose(FileHandle);

		FileHandle = CreateFile(L"\\??\\D:\\data.txt", GENERIC_READ, TRUE);
		// 向文件内写入字符串: "hello world"
		CHAR Buffer[0x10] = { 0 };
		ReadFile(FileHandle, Buffer, 0x10, 0);

		// 查询文件的大小
		LONGLONG FileSize = GetFileSize(FileHandle);
		KdPrint(("FileSize: %lld\n", FileSize));

		// 文件打开以后必须使用 ZwClose 进行关闭,否则会产生文件占用
		ZwClose(FileHandle);

		DeleteFile(L"\\??\\D:\\data.txt");
	*/

	ListDirectoryFile(L"\\??\\C:\\");

	return STATUS_SUCCESS;
}

六. 进程与线程

1. 进程

#include <ntifs.h>
#include <ntddk.h>

// 函数有导出,但是没有声明,需要自己提供
NTKERNELAPI UCHAR* PsGetProcessImageFileName(__in PEPROCESS Process);

VOID ListCurrentProcessAndThread()
{
   // 提供一个 EPROCESS 的指针,用于接受查询到的内容
   PETHREAD Thread = NULL;
   PEPROCESS Process = NULL;

   // 提供一个用于遍历的范围,以 4 为递增值,暴力遍历所有的进程,由于
   //	进程和线程被放置在了同一个位置,所以两者的 id 是处于同意序列的
   for (ULONG id = 4; id <= 5000; id += 4)
   {
   	// 尝试使用 pid 找到相应的 EOROCESS 结构体,如果找到就输出信息
   	if (NT_SUCCESS(PsLookupProcessByProcessId(ULongToHandle(id), &Process)))
   	{
   		// 通过 windows 提供的内置函数获取名称
   		KdPrint(("[%d][P]: %s\n", id, PsGetProcessImageFileName(Process)));

   		// 如果操作使指针引用计数 +1 了,那么就需要 -1
   		ObDereferenceObject(Process);
   	}

   	// 如果不是进程,还有可能是线程,再进行一次判断
   	else if (NT_SUCCESS(PsLookupThreadByThreadId(ULongToHandle(id), &Thread)))
   	{
   		// 通过函数函数获取当前线程的所属进程
   		PEPROCESS Process2 = IoThreadToProcess(Thread);
   		KdPrint(("[%d][T]:%d\n", id, PsGetProcessId(Process2)));

   		// 操作完毕以后,需要手动的减少引用计数
   		ObDereferenceObject(Thread);
   	}
   }
}


// 线程回调函数,这个函数是一个永不返回的函数
VOID WorkerThread(PVOID StartContext)
{
   UNREFERENCED_PARAMETER(StartContext);

   // 编写循环,输出内容
   for (int i = 0; i < 10; ++i)
   	KdPrint(("%d\n", i));

   // 任何一个系统线程,都必须使用下面的函数自我销毁
   PsTerminateSystemThread(STATUS_SUCCESS);
}


// 创建线程
VOID CreateThread()
{
   // 创建线程句柄用于保存线程
   HANDLE Thread = NULL;

   // 创建一个位于系统的线程
   NTSTATUS Status = PsCreateSystemThread(&Thread, 0, NULL,
   	NULL, NULL, WorkerThread, NULL);

   // 如果线程内核独享创建成功了,就尝试等待线程
   if (NT_SUCCESS(Status))
   {
   	PVOID ThreadObject = NULL;
   	Status = ObReferenceObjectByHandle(Thread, GENERIC_ALL, NULL, KernelMode, &ThreadObject, NULL);

   	if (NT_SUCCESS(Status))
   	{
   		KeWaitForSingleObject(ThreadObject, Executive, KernelMode, FALSE, 0);
   		KdPrint(("thread over....\n"));
   		ZwClose(Thread);
   		ObDereferenceObject(ThreadObject);
   	}
   }
}
#include "header.h"


// 驱动程序的卸载函数,会在卸载时调用,通常用于删除设备对象
#pragma code_seg("PAGE")		// 分页内存: 能过够被交换到页交换文件的内存
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	UNREFERENCED_PARAMETER(DriverObject);
}


// 驱动程序的入口函数,参数一表示当前的驱动对象,参数二是在注册表的路径
#pragma code_seg("INIT")		// 只有在初始化的时候存在,初始化完毕被卸载的内存
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DbgBreakPoint();

	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);

	// 只有设置了卸载函数的驱动程序才能被卸载
	DriverObject->DriverUnload = DriverUnload;

	ListCurrentProcessAndThread();

	CreateThread();

	// 只有当返回值为 STATUS_SUCCESS 驱动才会被加载到系统中
	return STATUS_SUCCESS;
}

2. 线程

SDT表 系统描述符表

定时器需要硬件支持 运行在DPC上,IO是每隔一秒,DPC每隔指定

操作系统提供的函数 返回值都是NTSTATUS

3. 进程挂靠

#include <ntifs.h>


// 驱动程序的卸载函数,会在卸载时调用,通常用于删除设备对象
#pragma code_seg("PAGE")		// 分页内存: 能过够被交换到页交换文件的内存
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	UNREFERENCED_PARAMETER(DriverObject);
}


// 驱动程序的入口函数,参数一表示当前的驱动对象,参数二是在注册表的路径
#pragma code_seg("INIT")		// 只有在初始化的时候存在,初始化完毕被卸载的内存
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DbgBreakPoint();

	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);

	// 只有设置了卸载函数的驱动程序才能被卸载
	DriverObject->DriverUnload = DriverUnload;

	// 进程挂靠可以让一个进程访问到另一个进程的内存空间
	PEPROCESS Process = NULL;
	PsLookupProcessByProcessId(ULongToHandle(4068), &Process);
	
	KAPC_STATE ApcState = { 0 };
	KeStackAttachProcess(Process, &ApcState);

	// 在挂靠之后就可以访问其它进程的内存空间
	CHAR* Buffer = (CHAR*)0x002CFD74;
	Buffer[0] = Buffer[1] = Buffer[2] = '0';

	KeUnstackDetachProcess(&ApcState);

	// 只有当返回值为 STATUS_SUCCESS 驱动才会被加载到系统中
	return STATUS_SUCCESS;
}


#include <stdio.h>
#include <windows.h>

int main()
{
	char buffer[] = "hello 15pb";

	printf("[%d]: %p %s\n", GetCurrentProcessId(), buffer, buffer);
	system("pause");

	printf("[%d]: %p %s\n", GetCurrentProcessId(), buffer, buffer);
	system("pause");

	return 0;
}

在这里插入图片描述

外挂修改 内存dump 内存校验保护

4. 同步操作

内核层同步方式

  1. 原子操作
  2. 事件
  3. 互斥体
  4. 信号量
  5. 自旋锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ROOAMCOF-1616297958263)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210218183918667.png)]

汇编层面 lock指令

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
快速互斥体只能一次打开 多次蓝屏在这里插入图片描述

在这里插入图片描述

5. 注册表操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ng8dnbyX-1616297958266)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210218184700923.png)]

在这里插入图片描述

使用封装过的RTL函数

6. 链表操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LjjKwi4h-1616297958268)(E:/%E8%BD%AF%E4%BB%B6%E4%B8%8B%E8%BD%BD/Typora/%E5%9B%BE%E7%89%87/image-20210218184846813.png)]

在这里插入图片描述

7. 异常处理

七. 内核对象

1. 内核对象

在这里插入图片描述

2. 内核对象结构

在这里插入图片描述

任何一个对象结构体减去0x18就是header

在这里插入图片描述

3. 获取内核对象

在这里插入图片描述

4. Object_Hook

在这里插入图片描述

#include <ntifs.h>

// 要 HOOK 的函数保存的位置
typedef struct _OBJECT_TYPE_INITIALIZER {
	USHORT Length;
	UCHAR ObjectTypeFlags;
	UCHAR CaseInsensitive;
	UCHAR UnnamedObjectsOnly;
	UCHAR  UseDefaultObject;
	UCHAR  SecurityRequired;
	UCHAR MaintainHandleCount;
	UCHAR MaintainTypeList;
	UCHAR SupportsObjectCallbacks;
	UCHAR CacheAligned;
	ULONG ObjectTypeCode;
	BOOLEAN InvalidAttributes;
	GENERIC_MAPPING GenericMapping;
	BOOLEAN   ValidAccessMask;
	BOOLEAN   RetainAccess;
	POOL_TYPE PoolType;
	BOOLEAN DefaultPagedPoolCharge;
	BOOLEAN DefaultNonPagedPoolCharge;
	PVOID DumpProcedure;
	ULONG OpenProcedure;
	PVOID CloseProcedure;
	PVOID DeleteProcedure;
	ULONG ParseProcedure;
	ULONG SecurityProcedure;
	ULONG QueryNameProcedure;
	UCHAR OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, * POBJECT_TYPE_INITIALIZER;

// ObGetObjectType 的返回值
typedef struct _OBJECT_TYPE {
	LIST_ENTRY TypeList;
	UNICODE_STRING Name;
	PVOID DefaultObject;
	ULONG Index;
	ULONG TotalNumberOfObjects;
	ULONG TotalNumberOfHandles;
	ULONG HighWaterNumberOfObjects;
	ULONG HighWaterNumberOfHandles;
	OBJECT_TYPE_INITIALIZER TypeInfo;
	ULONG TypeLock;
	ULONG Key;
	LIST_ENTRY   CallbackList;
} OBJECT_TYPE, * POBJECT_TYPE;

// 需要 HOOK 的函数的原型
typedef NTSTATUS(*PParseProcedure)(
	IN PVOID ParseObject,
	IN PVOID ObjectType,
	IN OUT PACCESS_STATE AccessState,
	IN KPROCESSOR_MODE AccessMode,
	IN ULONG Attributes,
	IN OUT PUNICODE_STRING CompleteName,
	IN OUT PUNICODE_STRING RemainingName,
	IN OUT PVOID Context OPTIONAL,
	IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
	OUT PVOID* Object);

// ObGetObjectType 的函数原型
typedef POBJECT_TYPE(*PObGetObjectType)(PVOID Object);

// 使用指定的方式创建或打开一个文件或目录
HANDLE CreateFile(LPCWSTR pFilePath, ACCESS_MASK Access, ULONG CreateDisposition, BOOLEAN IsFile)
{
	HANDLE FileHandle = NULL;
	IO_STATUS_BLOCK IoStatusBlock = { 0 };

	// 初始化文件或目录对应的路径字符串
	UNICODE_STRING FilePath = { 0 };
	RtlInitUnicodeString(&FilePath, pFilePath);

	// 操作文件的时候,通常都需要提供这个结构体,描述文件的名称
	OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
	InitializeObjectAttributes(
		&ObjectAttributes,			// 需要初始化的结构体
		&FilePath,					// 文件或目录的路径
		OBJ_CASE_INSENSITIVE,		// 名称不需要区分大小写
		NULL, NULL);				// 对象根目录路径

	// 根据用户传入的布尔值设置 CreateOptions 的值为目录\非目录
	ULONG CreateOptions = IsFile ? FILE_NON_DIRECTORY_FILE
		: FILE_DIRECTORY_FILE;

	// 内核层操作文件的函数,如果失败,返回原因
	NTSTATUS Status = ZwCreateFile(
		&FileHandle,				// 成功调用函数后保存句柄的变量
		Access,						// 以什么样的权限操作文件 GENERIC_ALL
		&ObjectAttributes,			// 主要描述的是需要操作的路径
		&IoStatusBlock,				// 保存当前函数的执行结果
		0,							// 如果是覆盖或创建文件,表示默认的文件大小
		FILE_ATTRIBUTE_NORMAL,		// 创建出的文件具有什么样的属性
		0,							// 文件的允许的共享方式(读 | 写 | 删除)
		CreateDisposition,			// 创建的方式,FILE_CREATE 或 FILE_OPEN_IF
		CreateOptions,				// 可以通过这个字段设置操作的是文件还是目录
		0, 0);						// 描述扩展区域的大小以及指向它的指针

	// 判断文件的操作是否成功,返回相应的值
	if (NT_SUCCESS(Status))
		return FileHandle;
	else
		return (HANDLE)-1;
}

// 获取到内置的函数
PObGetObjectType GetObjectTypeAddress()
{
	UNICODE_STRING pslookup = RTL_CONSTANT_STRING(L"ObGetObjectType");
	return (PObGetObjectType)MmGetSystemRoutineAddress(&pslookup);
}


// 保存 HOOK 的地址以及 HOOK 前的函数
PULONG HookAddress = NULL;
PParseProcedure HookFunction = NULL;


NTSTATUS MyParseProcedure(
	IN PVOID ParseObject,
	IN PVOID ObjectType,
	IN OUT PACCESS_STATE AccessState,
	IN KPROCESSOR_MODE AccessMode,
	IN ULONG Attributes,
	IN OUT PUNICODE_STRING CompleteName,
	IN OUT PUNICODE_STRING RemainingName,
	IN OUT PVOID Context OPTIONAL,
	IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
	OUT PVOID* Object)
{
	KdPrint(("hook success\n"));

	return HookFunction(ParseObject, ObjectType, AccessState, AccessMode,
		Attributes, CompleteName, RemainingName, Context, SecurityQos, Object);
}

// 开启对象函数的 HOOK,主要 HOOK 文件对象用于解析名称的函数
VOID OnObjectHook()
{
	// 1. 随意的打开一个文件
	HANDLE File = CreateFile(L"\\??\\D:\\data.txt", GENERIC_ALL, FILE_CREATE, TRUE);

	// 2. 获取到它的结构体指针(对象指针)
	PVOID ObjectBuffer = NULL;
	ObReferenceObjectByHandle(File, GENERIC_ALL, NULL, KernelMode, &ObjectBuffer, NULL);

	// 3. 获取到函数,用于取得对象的类型结构
	PObGetObjectType ObGetObjectType = GetObjectTypeAddress();
	POBJECT_TYPE Type = ObGetObjectType(ObjectBuffer);

	// 4. 保存 HOOK 的函数地址和函数,方便后续进行还原
	HookAddress = &Type->TypeInfo.ParseProcedure;
	HookFunction = (PParseProcedure)Type->TypeInfo.ParseProcedure;

	// 5. 使用自己的函数替代默认的函数
	Type->TypeInfo.ParseProcedure = (ULONG)MyParseProcedure;
}

VOID OffObjectHook()
{
	*HookAddress = (ULONG)HookFunction;
}
#include "header.h"


// 驱动程序的卸载函数,会在卸载时调用,通常用于删除设备对象
#pragma code_seg("PAGE")		// 分页内存: 能过够被交换到页交换文件的内存
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	OffObjectHook();
	UNREFERENCED_PARAMETER(DriverObject);
}


// 驱动程序的入口函数,参数一表示当前的驱动对象,参数二是在注册表的路径
#pragma code_seg("INIT")		// 只有在初始化的时候存在,初始化完毕被卸载的内存
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	DbgBreakPoint();

	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);

	// 只有设置了卸载函数的驱动程序才能被卸载
	DriverObject->DriverUnload = DriverUnload;

	OnObjectHook();

	// 只有当返回值为 STATUS_SUCCESS 驱动才会被加载到系统中
	return STATUS_SUCCESS;
}

八. 系统调用

SSDT:主要处理 Kernel32.dll中的系统调用

​ OpenProcess,ReadFile

ShadowSSDT:主要处理user32.dll,GDI32.dll中的调用函数

​ PostMessage,FindWindow

ShadowSSDT并未导出,可用ida在win32k.sys中的导出表中搜索,结构与SSDT相似,且只在GUI环境下由有值,所以需要调用KeAttachStackProcess来切换到GUI线程里看到

SSDT表介绍

ntdll.dll模块中的函数有些以nt或zw开头的函数为了完成相应的功能需要进入内核,调用内核中以nt开头的函数来完成相应的功能。ntdll.dll里的函数在进入内核层之前首先将系统服务号传入eax寄存器中,然后调用KiSystemService函数进入内核层。进入内核后会根据eax值索引ssdt表里的函数进行执行相应地址的函数。

SSDT的每一项是一个系统服务函数的地址,可以通过HOOK这些函数完成特定的功能。

32位系统上SSDT是导出的,64位是不会导出的。

通过PCHunter查看win7 x64系统的SSDT表:
在这里插入图片描述

如何获得SSDT表的地址和每一个项对应的服务名称呢?

注意:内核文件有多个,操作系统会根据当前cpu和分页方式选择不同的内核文件。

ntoskrnl.exe - 单处理器,不支持PAE分页模式;

ntkrnlpa.exe - 单处理器,支持PAE分页模式;

ntkrnlmp.exe - 多处理器,不支持PAE分页模式;

ntkrpamp.exe - 多处理器,支持PAE分页模式。

32位系统

32系统上ntdll.dll使用mov eax,xxx传入索引值,可以通过遍历ntdll.dll查看每一个函数对应的服务号,从而找到服务函数名和服务编号的关系。另外,内核中32位的SSDT的起始地址是直接在ntoskrnl.exe中通过KeServiceDescriptorTable符号导出,不需要使用工具来获得,可以直接在驱动程序中引用该符号的地址。注意:在代码实现上应当引入头文件#include <ntimage.h>之后使用语句

extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;

来获得KeServiceDescriptorTable的地址。

32位系统中KeServiceDescriptorTable结构如下图所示

#pragma pack(1)
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
    PULONG ServiceTableBase;//SSDT的起始地址
    PULONG ServiceCounterTableBase;//
    ULONG  NumberOfService;//SSDT表中服务函数的总数
    PUCHAR ParamTableBase;//服务函数的参数个数数组的起始地址,数组的每一个成员占1字节,记录的值是对应函数的参数个数*4
} SSDTEntry, *PSSDTEntry;
#pragma pack()

ServiceTableBase的内容是SSDT表的起始地址,然后从ServiceTableBase开始是一个长度为NumberOfService的指针数组,每一项是4个字节,是SSDT表中每一个服务的函数地址。

在内核调试器windbg中使用dd KeServiceDescriptorTable命令查看KeServiceDescriptorTable数据,就可以看到SSDTEntry结构的每一项数据。

在这里插入图片描述

根据服务表号能够决定调用哪张表,服务表号如果为0则加0,SSDT

否则加0x10的位置,ShadowsSSDT

ntoskerl.exe 核心 组件模块

近跳保存eip

远跳保存cs和eip

1. KiFastCallEntry

; 将寄存器(段寄存器和栈)从 R3 切换到 R0 

    mov     ecx,23h                             ; 通过 ecx 设置 ds 和 es 段选择子为 0x23        
    push    30h
    pop     fs                                  ; 将 fs 段选择子切换为 0x30,此时指向 KPCR
    mov     ds,cx
    mov     es,cx
    mov     ecx,dword ptr fs:[40h]              ; 获取 TSS 中的 R0 ESP 并进行设置 
    mov     esp,dword ptr [ecx+4]


; 构建 _KTRAP_FRAME 保存 R3 的寄存器状态

    push    23h
    push    edx 
    pushfd 
    push    2
    add     edx,8
    popfd
    or      byte ptr [esp+1],2
    push    1Bh
    push    dword ptr ds:[0FFDF0304h]
    push    0
    push    ebp
    push    ebx
    push    esi
    push    edi
    mov     ebx,dword ptr fs:[1Ch]              ; 指向 KPCR(自己) 的指针
    push    3Bh
    mov     esi,dword ptr [ebx+124h]            ; 保存 CurrentThread,即当前线程的 KTHREAD
    push    dword ptr [ebx]
    mov     dword ptr [ebx],0FFFFFFFFh
    mov     ebp,dword ptr [esi+28h]             ; ebp 指向 KTRAP_FRAME 结构体首地址
    push    1
    sub     esp,48h
    sub     ebp,29Ch
    mov     byte ptr [esi+13Ah],1               ; 设置 CurrentThread.PreviousMode 为 1
    cmp     ebp,esp
    jne     nt!KiFastCallEntry2+0x49 (83e760bb)
    and     dword ptr [ebp+2Ch],0
    test    byte ptr [esi+3],0DFh
    mov     dword ptr [esi+128h],ebp
    jne     nt!Dr_FastCallDrSave (83e75f70)
    mov     ebx,dword ptr [ebp+60h]
    mov     edi,dword ptr [ebp+68h]
    mov     dword ptr [ebp+0Ch],edx             ; 设置参数指向用户层传递的函数参数
    mov     dword ptr [ebp+8],0BADB0D00h
    mov     dword ptr [ebp],ebx
    mov     dword ptr [ebp+4],edi


; 开启中断标志位
    sti


; 通过调用号判定当前是 SSDT 还是 Shadow SSDT 调用

    mov     edi,eax                             ; 获取函数的调用号,调用号的组成是
    shr     edi,8                               ; 19(未使用)+1(服务表号)+12(索引)
    and     edi,10h                             
    mov     ecx,edi                             ; 判断当前使用的是 ShadowSSDT 表
    add     edi,dword ptr [esi+0BCh]            ; 还是 SSDT 表,并找到对应的结构
                                                ; 找到 VOID* ServiceTable

    mov     ebx,eax
    and     eax,0FFFh                           ; 获取到调用号中的(真实)索引部分

    cmp     eax,dword ptr [edi+8]               
    jae     nt!KiBBTUnexpectedRange (83e75ea2)  ; 如果超出了有效的调用号范围就报错

    cmp     ecx,10h                             
    jne     nt!KiFastCallEntry+0xce (83e7618e)  ; 如果当前为 SSDT 表的调用则跳转
    
    mov     ecx,dword ptr [esi+88h]             ; 如果是 Shadow SSDT 则进行其他操作
    xor     esi,esi
    or      esi,dword ptr [ecx+0F70h]
    je      nt!KiFastCallEntry+0xce (83e7618e)
    push    edx
    push    eax
    call    dword ptr [nt!KeGdiFlushUserBatch (83fa294c)]
    pop     eax
    pop     edx


; 如果当前是 SSDT 函数调用执行的操作

nt!KiFastCallEntry+0xce:
    inc     dword ptr fs:[6B0h]                     ; 产生系统调用的次数 KPCR->KeSystemCalls 加一
    mov     esi,edx                                 ; esi 指向用户传递的参数在栈中的位置
    xor     ecx,ecx

    mov     edx,dword ptr [edi+0Ch]                 ; 找到 SSDT 中的参数表并保存到 edx 中
    mov     edi,dword ptr [edi+00h]                 ; 找到 SSDT 中的函数表并保存到 edi 中

    mov     cl,byte ptr [eax+edx]                   ; ecx 保存当前函数所需要的字节个数
    mov     edx,dword ptr [edi+eax*4]               ; edx 保存当前函数的真实地址

    sub     esp,ecx                                 ; 在栈中开辟空间用于保存所有的参数

    shr     ecx,2                                   ; 字节数 / 4 保存了参数的个数
    mov     edi,esp                                 ; esi=用户空间参数  edi=系统空间参数

    cmp     esi,dword ptr [nt!MmUserProbeAddress (83fa271c)]
    jae     nt!KiSystemCallExit2+0xa5 (83e763e5)    ; 判断用户参数是否在有效范围(0x7FFF0000)内

    rep movs dword ptr es:[edi],dword ptr [esi]     ; 将参数从用户空间拷贝到系统空间

    test    byte ptr [ebp+6Ch],1                    
    je      nt!KiFastCallEntry+0x115 (83e761d5)     ; 获取 CPL 和 1 进行与运算,如果是 R3 则跳转

    mov     ecx,dword ptr fs:[124h]                 ; 如果是 R0 的调用则执行其他操作
    mov     edi,dword ptr [esp]
    mov     dword ptr [ecx+13Ch],ebx
    mov     dword ptr [ecx+12Ch],edi


nt!KiFastCallEntry+0x115:

    mov     ebx,edx                                 ; 获取真实的函数地址
    test    byte ptr [nt!PerfGlobalGroupMask+0x8 (83f6f908)],40h
    setne   byte ptr [ebp+12h]
    jne     nt!KiServiceExit2+0x17b (83e76574)
    call    ebx                                     ; 调用对应的 SSDT 函数

2. KPCR

//0x3748 bytes (sizeof)
struct _KPCR
{
    union
    {
        struct _NT_TIB NtTib;                                               //0x0
        struct
        {
            struct _EXCEPTION_REGISTRATION_RECORD* Used_ExceptionList;      //0x0
            VOID* Used_StackBase;                                           //0x4
            VOID* Spare2;                                                   //0x8
            VOID* TssCopy;                                                  //0xc
            ULONG ContextSwitches;                                          //0x10
            ULONG SetMemberCopy;                                            //0x14
            VOID* Used_Self;                                                //0x18
        };
    };
    struct _KPCR* SelfPcr;                                                  //0x1c
    struct _KPRCB* Prcb;                                                    //0x20
    UCHAR Irql;                                                             //0x24
    ULONG IRR;                                                              //0x28
    ULONG IrrActive;                                                        //0x2c
    ULONG IDR;                                                              //0x30
    VOID* KdVersionBlock;                                                   //0x34
    struct _KIDTENTRY* IDT;                                                 //0x38
    struct _KGDTENTRY* GDT;                                                 //0x3c
    struct _KTSS* TSS;                                                      //0x40
    USHORT MajorVersion;                                                    //0x44
    USHORT MinorVersion;                                                    //0x46
    ULONG SetMember;                                                        //0x48
    ULONG StallScaleFactor;                                                 //0x4c
    UCHAR SpareUnused;                                                      //0x50
    UCHAR Number;                                                           //0x51
    UCHAR Spare0;                                                           //0x52
    UCHAR SecondLevelCacheAssociativity;                                    //0x53
    ULONG VdmAlert;                                                         //0x54
    ULONG KernelReserved[14];                                               //0x58
    ULONG SecondLevelCacheSize;                                             //0x90
    ULONG HalReserved[16];                                                  //0x94
    ULONG InterruptMode;                                                    //0xd4
    UCHAR Spare1;                                                           //0xd8
    ULONG KernelReserved2[17];                                              //0xdc
    struct _KPRCB PrcbData;                                                 //0x120
}; 

3. SysEntryHook

#include <ntddk.h>

// 原始函数
ULONG g_OldKiFastCallEntry = 0;
// 要保护的进程PID
ULONG g_pid = 0;

// 1. 获取原始KiFastCallEntry函数
//			1.1 ecx 保存寄存器号
//			1.2 rdmsr 调用指令
//			1.3 eax 返回保存原始函数

// 2. 将我们过滤函数替换到msr 0x176号寄存器中 
//     2.1 ecx 保存寄存器号,eax 写入过滤函数
//	   2.2 wrmsr 调用指令

// 3. 卸载钩子
//     3.1 ecx 保存寄存器号,eax 写入原始函数
//	   2.2 wrmsr 调用指令


// 过滤函数
void _declspec(naked) MyKiFastCallEntry()
{
	// 过滤 ZwOpenProcess,调用号eax == 0x0BE
	// edx保存用户栈
	//  [edx + 0x00] : 返回地址1
	//	[edx + 0x04] : 返回地址2
	//	[edx + 0x08] : 参数1    ProcessHandle
	//	[edx + 0x0c] : 参数2    DesiredAccess
	//	[edx + 0x10] : 参数3    ObjectAttributes
	//	[edx + 0x14] : 参数4    ClientId

	/*
		https://github.com/j00ru/windows-syscalls/
	*/

	_asm
	{
		pushad;					// 保存寄存器
		cmp eax, 0x0BE;			// 是否是ZwOpenProcess 函数
		jne CallEnd;			// 结束过滤
		mov eax, [edx + 0x14];	// 获取第四个参数 ClientId
		mov eax, [eax];			// ClientId->ProcessId
		cmp eax, g_pid;			// 判断是否要保存的进程
		jne CallEnd;			
		mov[edx + 0x0c], 0;		// 将权限改为0,无法访问

	CallEnd:
		popad;						//恢复寄存器
		jmp g_OldKiFastCallEntry;	//调用原始函数
	}

}

// 安装钩子
void InstallHook()
{
	// 获取原始函数
	_asm
	{
		// 从 MSR 0x176 的位置读取原有的 KiFastCallEntry 函数进行保存
		mov ecx, 0x176;
		rdmsr;							//将msr176寄存器的内容保存eax
		mov g_OldKiFastCallEntry, eax; //保存原始函数地址
	}

	// 设置钩子
	_asm
	{
		mov ecx, 0x176;
		mov eax, MyKiFastCallEntry; // 过滤函数
		wrmsr;						//写入到msr0x176寄存器
	}

}

// 卸载钩子
void UnInstallHook()
{
	// 设置钩子
	_asm
	{
		mov ecx, 0x176;
		mov eax, g_OldKiFastCallEntry;  // 过滤函数
		wrmsr;							//写入到msr0x176寄存器
	}
}

// 卸载函数
void UnLoadDriver(PDRIVER_OBJECT pDriver)
{
	// 卸载钩子
	UnInstallHook();
	pDriver;
	return;
}


// 入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
	pDriver->DriverUnload = UnLoadDriver;
	pPath;
	// 要保护进程
	g_pid = 2684;

	// 安装钩子
	InstallHook();

	return STATUS_SUCCESS;
}

九. SSDT Hook

ssdt hook

系统服务描述表

管理系统函数ntdll

#include <ntddk.h>

// SSDT hook 保护一下计算器

#pragma pack(1)
typedef struct _ServiceDesriptorEntry
{
	ULONG* ServiceTableBase;        // 服务表基址
	ULONG* ServiceCounterTableBase; // 计数表基址
	ULONG NumberOfServices;         // 表中项的个数
	UCHAR* ParamTableBase;          // 参数表基址
}SSDTEntry, * PSSDTEntry;
#pragma pack()
// 函数原型
typedef NTSTATUS(NTAPI* FnZwOpenProcess)(PHANDLE,
	ACCESS_MASK,
	POBJECT_ATTRIBUTES,
	PCLIENT_ID);


// 导入SSDT,直接声明就能使用
NTSYSAPI SSDTEntry KeServiceDescriptorTable;

// 要保护的进程pid
ULONG g_pid = 0;
// 保存原始函数地址
FnZwOpenProcess g_OldZwOpenProcess;

// 过滤函数
NTSTATUS NTAPI MyZwOpenProcess(
	__out PHANDLE  ProcessHandle,
	__in ACCESS_MASK  DesiredAccess,
	__in POBJECT_ATTRIBUTES  ObjectAttributes,
	__in_opt PCLIENT_ID  ClientId
)
{
	// 判断是否是我们的函数
	if ((ULONG)ClientId->UniqueProcess == g_pid)
		DesiredAccess = 0;

	// 调用原始函数
	return g_OldZwOpenProcess(ProcessHandle,
		DesiredAccess,
		ObjectAttributes,
		ClientId);
}


// 关闭页保护
void OffPageProcted()
{
	_asm
	{
		mov eax, cr0;
		and eax, ~0x10000;
		mov cr0, eax;
	}

}

// 开页保护
void OnPageProcted()
{
	_asm
	{
		mov eax, cr0;
		or eax, 0x10000;
		mov cr0, eax;
	}
}


// SSDT hook函数
void InstallHook()
{
	//1.保存原始函数地址
	g_OldZwOpenProcess =
		(FnZwOpenProcess)KeServiceDescriptorTable.ServiceTableBase[0xBE];
	
	// 关闭页保护
	OffPageProcted();

	// 2. 替换我们的过滤函数
	InterlockedExchange(
		&KeServiceDescriptorTable.ServiceTableBase[0xBE],
		MyZwOpenProcess);

	// 开页保护
	OnPageProcted();
}

// SSDT unhook函数
void UnInstallHook()
{
	// 关闭页保护
	OffPageProcted();

	// 2. 替换我们的过滤函数
	InterlockedExchange(
		&KeServiceDescriptorTable.ServiceTableBase[0xBE],
		g_OldZwOpenProcess);

	// 开页保护
	OnPageProcted();

}


// 卸载函数
void UnLoadDriver(PDRIVER_OBJECT pDriver)
{
	// 卸载钩子
	UnInstallHook();
	pDriver;
	return;
}


// 入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
	pDriver->DriverUnload = UnLoadDriver;
	pPath;
	// 要保护进程
	g_pid = 2720;

	// 安装钩子
	InstallHook();

	return STATUS_SUCCESS;
}

十. 内核重载

sub esp,ecx

shr ecx,2

#include <ntifs.h>
#include <ntimage.h>

PDRIVER_OBJECT g_pDriver = NULL;


#pragma pack(1)
typedef struct _ServiceDesriptorEntry
{
	ULONG *ServiceTableBase;        // 服务表基址
	ULONG *ServiceCounterTableBase; // 计数表基址
	ULONG NumberOfServices;         // 表中项的个数
	UCHAR *ParamTableBase;          // 参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()

// 导入SSDT
NTSYSAPI SSDTEntry KeServiceDescriptorTable;

PSSDTEntry g_pNewSSDT;//新的SSDT
ULONG g_JmpPoint;
PUCHAR pHookPoint;

// 打开文件
HANDLE KernelCreateFile(
	IN PUNICODE_STRING pstrFile, // 文件路径符号链接
	IN BOOLEAN         bIsDir)   // 是否为文件夹
{
	HANDLE          hFile = NULL;
	NTSTATUS        Status = STATUS_UNSUCCESSFUL;
	IO_STATUS_BLOCK StatusBlock = { 0 };
	ULONG           ulShareAccess =
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
	ULONG           ulCreateOpt =
		FILE_SYNCHRONOUS_IO_NONALERT;
	// 1. 初始化OBJECT_ATTRIBUTES的内容
	OBJECT_ATTRIBUTES objAttrib = { 0 };
	ULONG             ulAttributes =
		OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
	InitializeObjectAttributes(
		&objAttrib,    // 返回初始化完毕的结构体
		pstrFile,      // 文件对象名称
		ulAttributes,  // 对象属性
		NULL, NULL);   // 一般为NULL
	// 2. 创建文件对象
	ulCreateOpt |= bIsDir ?
		FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE;
	Status = ZwCreateFile(
		&hFile,                // 返回文件句柄
		GENERIC_ALL,           // 文件操作描述
		&objAttrib,            // OBJECT_ATTRIBUTES
		&StatusBlock,          // 接受函数的操作结果
		0,                     // 初始文件大小
		FILE_ATTRIBUTE_NORMAL, // 新建文件的属性
		ulShareAccess,         // 文件共享方式
		FILE_OPEN_IF,          // 文件存在则打开不存在则创建
		ulCreateOpt,           // 打开操作的附加标志位
		NULL,                  // 扩展属性区
		0);                   // 扩展属性区长度
	if (!NT_SUCCESS(Status))
		return (HANDLE)-1;
	return hFile;
}

// 获取文件大小
ULONG64 KernelGetFileSize(IN HANDLE hfile)
{
	// 查询文件状态
	IO_STATUS_BLOCK           StatusBlock = { 0 };
	FILE_STANDARD_INFORMATION fsi = { 0 };
	NTSTATUS Status = STATUS_UNSUCCESSFUL;
	Status = ZwQueryInformationFile(
		hfile,        // 文件句柄
		&StatusBlock, // 接受函数的操作结果
		&fsi,         // 根据最后一个参数的类型输出相关信息
		sizeof(FILE_STANDARD_INFORMATION),
		FileStandardInformation);
	if (!NT_SUCCESS(Status))
		return 0;
	return fsi.EndOfFile.QuadPart;
}
// 读取文件
ULONG64 KernelReadFile(
	IN  HANDLE         hfile,    // 文件句柄
	IN  PLARGE_INTEGER Offset,   // 从哪里开始读取
	IN  ULONG          ulLength, // 读取多少字节
	OUT PVOID          pBuffer)  // 保存数据的缓存
{
	// 1. 读取文件
	IO_STATUS_BLOCK StatusBlock = { 0 };
	NTSTATUS        Status = STATUS_UNSUCCESSFUL;
	Status = ZwReadFile(
		hfile,        // 文件句柄
		NULL,         // 信号状态(一般为NULL)
		NULL, NULL,   // 保留
		&StatusBlock, // 接受函数的操作结果
		pBuffer,      // 保存读取数据的缓存
		ulLength,     // 想要读取的长度
		Offset,       // 读取的起始偏移
		NULL);        // 一般为NULL
	if (!NT_SUCCESS(Status))  return 0;
	// 2. 返回实际读取的长度
	return StatusBlock.Information;
}
typedef struct _LDR_DATA_TABLE_ENTRY {
	LIST_ENTRY InLoadOrderLinks;    //双向链表
	LIST_ENTRY InMemoryOrderLinks;
	LIST_ENTRY InInitializationOrderLinks;
	PVOID DllBase;
	PVOID EntryPoint;
	ULONG SizeOfImage;
	UNICODE_STRING FullDllName;
	UNICODE_STRING BaseDllName;
	ULONG Flags;
	USHORT LoadCount;
	USHORT TlsIndex;
	union {
		LIST_ENTRY HashLinks;
		struct {
			PVOID SectionPointer;
			ULONG CheckSum;
		};
	};
	union {
		struct {
			ULONG TimeDateStamp;
		};
		struct {
			PVOID LoadedImports;
		};
	};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

// 搜索内存特征
void * SearchMemory(char * buf, int BufLenth, char * Mem, int MaxLenth)
{
	int MemIndex = 0;
	int BufIndex = 0;
	for (MemIndex = 0; MemIndex < MaxLenth; MemIndex++)
	{
		BufIndex = 0;
		if (Mem[MemIndex] == buf[BufIndex] || buf[BufIndex] == '?')
		{
			int MemIndexTemp = MemIndex;
			do
			{
				MemIndexTemp++;
				BufIndex++;
			} while ((Mem[MemIndexTemp] == buf[BufIndex] || buf[BufIndex] == '?') && BufIndex < BufLenth);
			if (BufIndex == BufLenth)
			{
				return Mem + MemIndex;
			}

		}
	}
	return 0;
}

// 关闭页保护
void OffProtected()
{
	__asm { //关闭内存保护
		cli;
		push eax;
		mov eax, cr0;
		and eax, ~0x10000;
		mov cr0, eax;
		pop eax;
	}
}

// 开启页保护
void OnProtected()
{
	__asm { //恢复内存保护
		push eax;
		mov eax, cr0;
		or eax, 0x10000;
		mov cr0, eax;
		pop eax;
		sti;
	}

}

// 通过名称获取模块地址
ULONG32 MyGetModuleHandle(PUNICODE_STRING pModuleName)
{
	PLDR_DATA_TABLE_ENTRY pLdr =
		(PLDR_DATA_TABLE_ENTRY)g_pDriver->DriverSection;
	LIST_ENTRY *pTemp = &pLdr->InLoadOrderLinks;
	do
	{
		PLDR_DATA_TABLE_ENTRY pDriverInfo =
			(PLDR_DATA_TABLE_ENTRY)pTemp;
		if (RtlCompareUnicodeString(pModuleName, &pDriverInfo->BaseDllName, FALSE) == 0)
		{
			return pDriverInfo->DllBase;
		}
		pTemp = pTemp->Blink;
	} while (pTemp != &pLdr->InLoadOrderLinks);
	return 0;
}

//windows根据不同的环境,会加载不同的内核文件
//单核,开了PAE  
//单核,没开PAE
//多核,开了PAE
//多核,没开PAE

// 读取内核模块到内存中
void ReadKernelToBuf(PWCHAR pPath, PUCHAR* pBuf)
{
	//-----------------------------------------
	UNICODE_STRING pKernelPath;  //内核文件路径
	HANDLE hFile = 0;            //内核文件句柄
	LARGE_INTEGER Offset = { 0 };//读取的偏移值
	//-----------------------------------------
	//1 打开文件
	RtlInitUnicodeString(
		&pKernelPath,
		pPath);
	hFile = KernelCreateFile(&pKernelPath, FALSE);
	//2 获取文件大小
	ULONG64 ulFileSize = KernelGetFileSize(hFile);
	*pBuf = ExAllocatePool(NonPagedPool, ulFileSize);
	RtlZeroMemory(*pBuf, ulFileSize);
	//3 读取文件到内存
	KernelReadFile(hFile, &Offset, ulFileSize, *pBuf);
}

// 展开内核PE文件
void ZKKernel(PUCHAR * pZkBUf, PUCHAR buf)
{
	//1 获得DOS头,继而获得NT头,再获得扩展头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buf;
	PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buf);
	ULONG uZkSize = pNt->OptionalHeader.SizeOfImage;

	//2 申请空间
	*pZkBUf = ExAllocatePool(NonPagedPool, uZkSize);
	RtlZeroMemory(*pZkBUf, uZkSize);
	//3 开始展开
	//3.1 先拷贝头部
	memcpy(*pZkBUf, buf, pNt->OptionalHeader.SizeOfHeaders);
	//3.2再拷贝区段
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
	for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
	{
		memcpy(
			*pZkBUf + pSection[i].VirtualAddress,//本区段内存中的起始位置
			buf + pSection[i].PointerToRawData,  //本区段在文件中的位置
			pSection[i].Misc.VirtualSize         //本区段的大小
		);
	}
}

// 修复新内核重定位
void FixReloc(PUCHAR ZkBuf, PUCHAR OldBase)
{
	typedef struct _TYPE {
		USHORT Offset : 12;
		USHORT Type : 4;
	}TYPE, *PTYPE;

	//1 获得DOS头,继而获得NT头,再获得扩展头
	PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)ZkBuf;
	PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + ZkBuf);
	//2 获得重定位表
	PIMAGE_DATA_DIRECTORY pRelocDir = (pNt->OptionalHeader.DataDirectory + 5);
	PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)
		(pRelocDir->VirtualAddress + ZkBuf);

	//2.5 得到一个老内核与默认基址间的一个差值
	ULONG uOffset = (ULONG)OldBase - pNt->OptionalHeader.ImageBase;

	//3 开始修复重定位
	while (pReloc->SizeOfBlock != 0)
	{
		ULONG uCount = (pReloc->SizeOfBlock - 8) / 2;//本0x1000内,有多少需要重定位的地方
		ULONG uBaseRva = pReloc->VirtualAddress;     //本0x1000的起始位置
		PTYPE pType = (PTYPE)(pReloc + 1);
		for (int i = 0; i < uCount; i++)
		{
			if (pType->Type == 3)
			{
				PULONG pRelocPoint = (uBaseRva + pType->Offset + ZkBuf);
				//重定位后的地址 - 新基址 = 没重定位的地址 - 默认基址
				//所以:重定位后的地址 = 新基址 - 默认基址 + 没重定位的地址
				*pRelocPoint = uOffset + *pRelocPoint;
			}
			pType++;
		}
		pReloc = (PIMAGE_BASE_RELOCATION)((ULONG)pReloc + pReloc->SizeOfBlock);
	}

}

// 修复旧SSDT表
void FixSSDT(PUCHAR pZKBuf, PUCHAR OldBase)
{

	//新内核某位置1 - 新内核基址 = 老内核某位置1 - 老内核基址;
	//新内核某位置1 = 新内核基址 - 老内核基址 + 老内核某位置1;

	LONG Offset = (ULONG)pZKBuf - (ULONG)OldBase;
	//1 得到新内核中的SSDT
	g_pNewSSDT = (PSSDTEntry)((LONG)&KeServiceDescriptorTable + Offset);

	//2 填充系统服务个数
	g_pNewSSDT->NumberOfServices = KeServiceDescriptorTable.NumberOfServices;

	//3 填充SSDT表
	g_pNewSSDT->ServiceTableBase = (ULONG *)((PUCHAR)KeServiceDescriptorTable.ServiceTableBase + Offset);
	//让所有的SSDT中保存的函数地址,都指向新内核
	for (int i = 0; i < g_pNewSSDT->NumberOfServices; i++)
	{
		g_pNewSSDT->ServiceTableBase[i] = g_pNewSSDT->ServiceTableBase[i] + Offset;
	}
	//4 填充参数表
	g_pNewSSDT->ParamTableBase = (PULONG)((PUCHAR)KeServiceDescriptorTable.ParamTableBase + Offset);
	memcpy(g_pNewSSDT->ParamTableBase,
		KeServiceDescriptorTable.ParamTableBase,
		g_pNewSSDT->NumberOfServices
	);
}
// 获取KiFastCallEntry函数
ULONG GetKiFastCallEntry()
{
	ULONG uAddress = 0;
	_asm
	{
		push eax;
		push ecx;
		mov ecx, 0x176;
		rdmsr;
		mov uAddress, eax;
		pop ecx;
		pop eax;
	}
	return uAddress;
}

// hook中过滤函数
ULONG   FilterFun(ULONG SSdtBase, PULONG OldFun, ULONG Id)
{
	//如果相等,说明调用的是SSDT中的函数
	if (SSdtBase == (ULONG)KeServiceDescriptorTable.ServiceTableBase)
	{
		//使用思路:
		//假如进程是OD,并且函数调用是190号,就走新内核中的函数,这样通过hookOpenProcess就无法拦住OD了。
		return g_pNewSSDT->ServiceTableBase[Id];
	}
	return OldFun;

}

// inline Hook的回调函数
_declspec(naked)void MyHookFun()
{
	//eax 里面是调用号,edx里面是老函数地址,edi里面是SSDT基址
	_asm {
		pushad;
		pushfd;
		push eax;		//调用号
		push edx;		//原始函数地址
		push edi;		//SSDT基址

		call FilterFun;	// 自己的过滤函数,获取最真实函数地址  由于前面压入3个参数和pushfd,pushad ebx刚好在栈中的【esp+0x18]
		mov dword ptr ds : [esp + 0x18], eax; // 【esp+0x18】 ebx的值,替换服务函数地址
		popfd;
		popad;			  // 恢复通用寄存器,ebx被替换成新的函数地址了
						  // 执行原始hook的5个字节
		sub     esp, ecx;
		shr     ecx, 2;
		jmp     g_JmpPoint;	// 跳转回原来函数地址
	}
}

// hook 目标的5个字节
UCHAR Old_Code[5] = { 0 };

// hookKiFastCallEntry函数
void OnHookKiFastCallEntry()
{
	// KiFastCallEntry中特征值
	char buf[] = { 0x2b, 0xe1, 0xc1, 0xe9, 0x02 };
	// 获取KiFastCallEntry函数地址
	ULONG KiFastCallEntryAdd = GetKiFastCallEntry();
	// 找到hook点
	pHookPoint = SearchMemory(buf, 5, (char*)KiFastCallEntryAdd, 0x200);
	// 绕过前5个字节,应为被hook替换了
	g_JmpPoint = (ULONG)(pHookPoint + 5);
	// 备份旧的5个字节
	memcpy(Old_Code, pHookPoint, 5);
	// 关闭页保护
	OffProtected();
	// jmp xxxxxx
	// 写入跳转目标地址   目标地址-指令所在-5
	pHookPoint[0] = 0xE9;
	*(ULONG*)(&pHookPoint[1]) = (ULONG)MyHookFun - (ULONG)pHookPoint - 5;
	// 开启页保护
	OnProtected();
}


//ntoskrnl - 单处理器,不支持PAE
//ntkrnlpa - 单处理器,支持PAE

//ntkrnlmp - 多处理器,不支持PAE
//ntkrpamp - 多处理器,支持PAE
// 在windows vista 开始后 所有的内核默认安装多核处理器方式(ntkrnlmp 或者 ntkrpamp)
// 然后拷贝到System32目录下,更改和单核处理名称一样



// 内核重载 开始
void KernelReload()
{
	PUCHAR pBuf = NULL;
	PUCHAR pZKBuf = NULL;
	UNICODE_STRING KernelName;

	//1 首先把内核文件读取到内存里 (默认开启PAE)
	ReadKernelToBuf(L"\\??\\C:\\Windows\\System32\\ntkrnlpa.exe", &pBuf);

	//2 把读到内存中的内核给展开成0x1000对齐
	ZKKernel(&pZKBuf, pBuf);
	ExFreePool(pBuf);

	//3 修复新内核的重定位 ,虽然开启PAE(ntkrnlpa.exe),但是显示的名称 ntoskrnl.exe
	RtlInitUnicodeString(&KernelName, L"ntoskrnl.exe");
	ULONG32 uBase = MyGetModuleHandle(&KernelName);

	FixReloc(pZKBuf, (PUCHAR)uBase);

	//4 修复新的SSDT表
	FixSSDT(pZKBuf, (PUCHAR)uBase);

	//5 Hook掉KiFastCallEntry,在自己的Hook函数中判断应该走新内核还是老内核
	OnHookKiFastCallEntry();

}


// 卸载内核钩子
void UnHook()
{
	OffProtected();
	memcpy(pHookPoint, Old_Code, 5);
	OnProtected();
}

// 驱动卸载
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
	pDriver;
	UnHook();
	KdPrint(("Leave"));
}
// 驱动加载
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
	UNREFERENCED_PARAMETER(pPath);
	DbgBreakPoint();
	g_pDriver = pDriver;
	// 重载内核
	KernelReload();
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

标签:Status,IRP,函数,SUCCESS,对象,编程,Irp,内核
来源: https://blog.csdn.net/datouyu0824/article/details/115046842