开发环境

环境配置

1、配置使用的是VS2019

前提条件:

  • VS2019安装正确
  • 安装WDK

安装WDK之前一定要看清楚,对应当前VS2019安装的windows SDK版本

image-20241027113833143

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
次函数可能设置的时间会比较长,虚拟机内存越大,耗时越长
vmxPid:虚拟机进程ID vmware-vmx.exe的ID
MajorVersion:虚拟机系统的主版本
MinorVersion:虚拟机系统的次版本
BuildNumber :虚拟机系统的内部版本号
例如:WIN7 MajorVersion=6 MinorVersion=1 BuildNumber=7600 或 7601
例如:WIN10 MajorVersion=10 MinorVersion=0 BuildNumber=18326 代表1903
-----------------------------------------
Windows 11(21H2) 22000
Windows 10(21H1) 19043
Windows 10(20H2) 19042
Windows 10(2004) 19041
Windows 10(1909) 18363
Windows 10(1903) 18362
Windows 10(1809) 17763
Windows 10(1803) 17134
Windows 10(1709) 16299
Windows 10(1703) 15063
Windows 10(1607) 14393
Windows 10(1511) 10586
Windows 10 (1507) 10240

Windows 8.1(更新1) MajorVersion = 6 MinorVersion = 3 BuildNumber = 9600
Windows 8.1 MajorVersion = 6 MinorVersion = 3 BuildNumber = 9200
Windows 8 MajorVersion = 6 MinorVersion = 2 BuildNumber = 9200

根据对应的小版本号去寻找并且安装WDK,因为我系统是win10部操作系统版本号是1904,比如图中我安装的SDK小版本号是19041,并且我用的VS2019,这个版本号比较特殊,在官网寻找,发现需要用到win10 2004版本的WDK。

image-20241027113502683

image-20241027113618216

之后下载WDK进行安装,默认安装位置即可。

项目配置

新建项目选择Empty WDM Driver,新建完成后。配置属性页,如果配置所有平台出现,左侧栏少东西,那你就删掉ARM那几个平台

image-20241027115237585

image-20241027115336339

image-20241027115424021

image-20241027115548434

image-20241027115610127

image-20241027115802881

image-20241027115820775

配置代码片段,方便快速开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>DriverMain</Title>
<Shortcut>DriverMain</Shortcut>
<Description>DriverMain</Description>
<Author>Microsoft Corporation</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Code Language="cpp"><![CDATA[
#include <ntifs.h>

VOID DriverUnload(PDRIVER_OBJECT pDriver)
{


}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver,PUNICODE_STRING pReg)
{
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>

新建一个文件,命名为DriverMain.snippet。然后添加进代码片段管理器

image-20241027114644286

编写第一个驱动程序

驱动的话用C开发比较好,因为用C++开发出来的驱动在某些平台会出现蓝屏。

image-20241027120036816

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <ntifs.h>

VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
DbgBreakPoint();
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrintEx(0,0,"第一个驱动测试程序");
DbgBreakPoint();
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

编译后得到.sys文件放入win7虚拟机测试。

image-20241027162537163

驱动管理程序进行注册,然后运行。

image-20241027162758270

image-20241027163048666

调试驱动程序

1、配置调试

驱动程序调试不如应用程序调试方便,主要因为驱动程序运行在操作系统内核中,直接调试内核程序难度大。

驱动程序调试需要使用特殊的调试器,如WinDbg,且调试过程通常涉及虚拟机和技术如断点和单步执行。

驱动程序调试时,无法在当前开发环境中直接调试,需要将驱动文件部署到新的操作系统环境中进行

原来我们是手动修改windbg软件的快捷图标信息来配置和虚拟机进行双机调试的,这次我们 VirtualKD来简化配置过程,提升效率。

具体压缩包目录是这样:

image-20241027164027781

其中target目录是分别针对32位和64位系统的,在对应位数的虚拟机,我们把对应位数的target目录复制到虚拟机内,然后在虚拟机内安装vminstall.exe,这样在启动时会多一个引导项名字是含有Redux。

image-20241027164347980

[!NOTE]

其中虚拟机含win7以上需要按F8,强制禁用驱动签名检测

安装完成后就重新启动。进入对应生成的引导项,启动系统。同时vmmon64.exe是主程序,双击打开。

image-20241027164608760

第一次可能需要配置windbg/KD debugger Path路径。

2、PDB文件

  • PDB文件是在我们编译工程的时候产生的,它是和对应的模块(exe或dll)一起生成出来的
  • 每个模块编译的时候都可以生成自己的PDB文件。比如.exe/.dll/.sys等等

image-20241027165105960

3、windbg如何找到pdb文件

对于虚拟机系统的PDB,我们可以通过微软的符号表服务器自动下载。

1
2
SRV*D:\Symbols\XP*http://msdl.microsoft.com/download/symbols
kd>.reload

但是对于我们自己写的驱动呢,却无法从微软的符号表服务器自动下载

1
2
SRV*D:\Symbols\XP*http://msdl.microsoft.com/download/symbols;D:\Projects\_01 HelloDriver\Driver
kd>.reload

内核编程基础

1、内核API的使用

在应用层编程我们可以使用WINDOWS提供的各种API函数,只要导入头文件<windows.h>就可以了,但是在内核编程的时候,我们不能像在Ring3那样直接使用。微软为内核程序提供了专用的API,只要在程序中包含相应的头文件就可以使用了,如:#include <ntddk.h> (假设你已经正确安装了WDK)

其中在Windows Vista 版本的 WDK 开始,使用Ntifs.h作为头文件。Ntifs.h 包含 Ntddk.h,而 Ntddk.h 又包含 Wdm.h

image-20241027165655203

在应用层编程的时候,我们通过MSDN来了解函数的详细信息,在内核编程的时候,要使用WDK自己的帮助文档

[!NOTE]

在老版本WDK中安装完还会自带帮助文档,但是新版的WDK中不会有文档,需要自己去微软官网查询

2、未导出函数的使用

WDK说明文档中只包含了内核模块导出的函数,对于未导出的函数,则不能直接使用。 如果要使用未导出的函数,只要自己定义一个函数指针,并且为函数指针提供正确的函数地址就可以使用了。

有两种办法都可以获取为导出的函数地址:

  • 特征码搜索

先确定,在哪个内核模块,通过遍历你想要找的这个内核函数,搜索特征码可以找到我们想要找的这个未导出的。

案例:

这里我们将会给出一个示例,再搜特征码的时候,一定要用固定不变的硬编码当作特征码,单一条件判断可能会出现重复的函数特征。

系统我用的是WIN7,这里我们拿PspTerminateAllProcessesInJob举例

我们通过pDriver拿到pDriver->DriverSection这里可以获取当前加载的所有驱动模块。

这里指向一个链表,而链表里的存储节点结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
};
};
struct _ACTIVATION_CONTEXT* EntryPointActivationContext;

PVOID PatchInformation;

} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;

之后通过遍历链表,对比BaseDllName

在10-10-12分页中需要拿到ntoskrnl.exe,如果是2-9-9-12分页中,则是ntkrnlpa.exe

当成功拿到需要的节点后,我们获得当前模块的基址pCurrentNode->DllBase

从当前基址开始遍历,获取内存和我们的特征码对比。

定义一个函数指针,创建PspTerminateAllProcessesInJob函数。

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <ntifs.h>
#include <ntstrsafe.h>
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;
};
};
struct _ACTIVATION_CONTEXT* EntryPointActivationContext;

PVOID PatchInformation;

} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;

typedef BOOLEAN PspTerminateAllProcessesInJob(
PEJOB Job,
NTSTATUS Status,
BOOLEAN IncCounter
);

VOID DriverUnload(PDRIVER_OBJECT pDriver)
{

}
//驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
PLDR_DATA_TABLE_ENTRY pListHead = NULL;
PLDR_DATA_TABLE_ENTRY pCurrentNode = NULL;
//kifastreadythread
/*ULONG code_sp1 = 0x8b55ff8b;
ULONG code_sp2 = 0x535151ec;
ULONG code_sp3 = 0x7c15ff57;*/

//PspTerminateAllProcessesInJob
ULONG code_sp1 = 0x8b55ff8b;
ULONG code_sp2 = 0xf8e483ec;
ULONG code_sp3 = 0x530cec83;
ULONG code_sp4 = 0x56085d8b;
ULONG code_sp5 = 0x24358b64;
PspTerminateAllProcessesInJob* ps = NULL;

UNICODE_STRING moudleName1 = { 0 };
RtlInitUnicodeString(&moudleName1, L"ntoskrnl.exe");

DbgPrintEx(77, 0, "驱动加载\n");

pListHead = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;

pCurrentNode = pListHead->InLoadOrderLinks.Flink;
SIZE_T i = 0;
while (pListHead != pCurrentNode)
{
//DbgPrintEx(77, 0, "index:%d--%S\r\n", i++, pCurrentNode->BaseDllName.Buffer);
if (RtlCompareUnicodeString(&moudleName1, &pCurrentNode->BaseDllName, TRUE) == 0)
{
DbgPrintEx(77, 0, "已经找到--%S\r\n", pCurrentNode->BaseDllName.Buffer);
PVOID moudleBase = pCurrentNode->DllBase;
PVOID sizeOfImage = pCurrentNode->SizeOfImage;
for (SIZE_T t = moudleBase;t <((ULONG)moudleBase + (ULONG)sizeOfImage);t++)
{

ULONG code1 = *(PULONG)t;
ULONG code2 = *((PULONG)(t + 4 * 1));
ULONG code3 = *((PULONG)(t + 4 * 2));
ULONG code4 = *((PULONG)(t + 4 * 3));
ULONG code5 = *((PULONG)(t + 4 * 4));
//这里匹配到特征码
if (code1 == code_sp1 && code2 == code_sp2 && code3 == code_sp3 && code4 == code_sp4 && code5 == code_sp5)
{
DbgBreakPoint();
DbgPrint("Target Function address is:%x\n", t);
ps = (PspTerminateAllProcessesInJob*)t;
ps();
return STATUS_SUCCESS;

}
}
}
pCurrentNode = (PLDR_DATA_TABLE_ENTRY)pCurrentNode->InLoadOrderLinks.Flink;
}
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

  • 解析内核PDB文件

PDB调试文件,就相当于内核里的一份完整的说明书。里边不但包含了。导出函数的相关说明也包含了未导出函数的相关说明,我们可以自己解析它的PDB文件,就像windbg解析它的PDB文件的一样,我们也可以这种方式可以更加灵活的得到。

3、基本数据类型

在内核编程的时候,强烈建议大家遵守WDK的编码习惯,不要这样写:unsigned long length;

习惯使用WDK自己的类型

WDK 的写法 等同于
ULONG unsigned long
UCHAR unsigned char
UINT unsigned int
VOID void
PULONG unsigned long*
PUCHAR unsigned char*
PUNIT unsigned int*
PVOID void*

[!NOTE]

1.建议使用WDK定义的数据类型以确保代码可移植性。

2.使用ULONG等类型定义以适应不同编译平台的宽度要求。

4、返回值

大部分内核函数的返回值都是NTSTATUS类型,内核API函数通常返回NTSTATUS,用于指示成功或失败

分别表示一下常见几种情况:

返回值 含义
STATUS_SUCCESS 0x00000000 成功
STATUS_INVALID_PARAMETER 0xC000000D 参数无效
STATUS_BUFFER_OVERFLOW 0x80000005 缓冲区长度不够

当你调用的内核函数,如果返回的结果不是STATUS_SUCCESS,就说明函数执行中遇到了问题,具体是什么问题,可以在ntstatus.h文件中查看。

5、内核中的异常处理

在内核中,一个小小的错误就可能导致蓝屏,比如:读写一个无效的内存地址。为了让自己的内核程序更加健壮,强烈建议大家在编写内核程序时,使用异常处

Windows提供了结构化异常处理机制,一般的编译器都是支持的

1
2
3
4
5
6
__try{
//可能出错的代码
}
__except(filter_value) {
//出错时要执行的代码
}

出现异常时,可根据filter_value的值来决定程序该如果执行,当filter_value的值为

宏名称 实际值 含义
EXCEPTION_EXECUTE_HANDLER 1 代码进入except块
EXCEPTION_CONTINUE_SEARCH 0 不处理异常,由上一层调用函数处理
EXCEPTION_CONTINUE_EXECUTION -1 回去继续执行错误处的代码

6、常用的内核内存函数

C语言 内核中
malloc ExAllocatePool
memset RtlFillMemory
memcpy RtlMoveMemory
free ExFreePool

7、内核字符串种类

CHAR(char)相当于ANSI_STRING,WCHAR相当于UNICODE_STRING

ANSI_STRING字符串的定义如下

1
2
3
4
5
6
typedef struct _STRING
{
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
}STRING;

UNICODE_STRING字符串的定义如下

1
2
3
4
5
6
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaxmumLength;
PWSTR Buffer;
} UNICODE_STRING;

内核中避免使用传统字符串以防止蓝屏

推荐使用升级版的字符串结构体,如ANSI_STRINGUNICODE_STRING

8、内核字符串常用函数

字符串常用的功能无非就是:创建、复制、比较以及转换等

ANSI_STRING字符串 UNICODE_STRING字符串 含义
RtlInitAnsiString RtlInitUnicodeString 创建
RtlCopyString RtlCopyUnicodeString 复制
RtlCompareString RtlCompareUnicoodeString 比较
RtlAnsiStringToUnicodeString RtlUnicodeStringToAnsiString 转换

内核空间与内核模块

1、内核空间

image-20241027200605755

内核模块定义全局变量:在不同进程中查看

在2g中所有模块共享一块内存

现在我设置一个全局变量在驱动里,然后注册运行通过dbgview查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <ntifs.h>

ULONG a = 0x12345678;
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
DbgBreakPoint();
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
DbgPrint("%x\n", &a);

pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

image-20241103213633527

现在我们随便找一个进程,通过.process来选择进程查看,我们发现在dbgview的内存中存在那个全局变量的值

image-20241103213714154

证实了在4GB的内存空间中高2G是共享的,高2g就是内核空间。

2、内核模块

image-20241103210521664

  • 硬件种类繁多,不可能做一个兼容所有硬件的内核,所以,微软提供规定的接口格式,让硬件驱动人员安装规定的格式编写“驱动程序” 。
  • 这些驱动程序每一个都是一个模块,称为“内核模块”,都可以加载到内核中,都遵守PE结构。但本质上讲,任意一个.sys文件与内核文件没有区别。

3、DRIVER_OBJECT

每个内核模块都有一个对应的结构体,来描述这个模块在内核中的:位置、大小、名称等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _DRIVER_OBJECT
nt!_DRIVER_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void
+0x010 DriverSize : Uint4B
+0x014 DriverSection : Ptr32 Void
+0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING
+0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
+0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH
+0x02c DriverInit : Ptr32 long
+0x030 DriverStartIo : Ptr32 void
+0x034 DriverUnload : Ptr32 void
+0x038 MajorFunction : [28] Ptr32 long

4、打印DRIVER_OBJECT地址

1
DbgPrint("DRIVER_OBJECT对象地址:%x \r\n",driver);

image-20241103214325045

通过DRIVER_OBJECT找到当前模块信息:

1
2
3
DbgPrint("驱动名称:%ws \r\n",driver->DriverName.Buffer);
DbgPrint("模块基址:%x \r\n",driver->DriverStart);
DbgPrint("模块大小:%x \r\n",driver->DriverSize);

image-20241103214347788

5、遍历内核模块

  1. dt _DRIVER_OBJECT (地址)
  2. dt _LDR_DATA_TABLE_ENTRY (DriverSection)地址
  3. dt _LDR_DATA_TABLE_ENTRY (InLoadOrderLinks.Flink)地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
kd> dt _LDR_DATA_TABLE_ENTRY 0x87ba7430 
nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x83f82850 - 0x87a114c8 ] //双向链表
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xffffffff - 0xffffffff ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x1c - 0x0 ]
+0x018 DllBase : 0x8d600000 Void
+0x01c EntryPoint : 0x8d604000 Void
+0x020 SizeOfImage : 0x6000
+0x024 FullDllName : _UNICODE_STRING "\??\C:\Users\Administrator\Desktop\MyDriver1.sys"
+0x02c BaseDllName : _UNICODE_STRING "MyDriver1.sys"
+0x034 Flags : 0x49104000
+0x038 LoadCount : 1
+0x03a TlsIndex : 0x6d
+0x03c HashLinks : _LIST_ENTRY [ 0x0 - 0xabfc ]
+0x03c SectionPointer : (null)
+0x040 CheckSum : 0xabfc
+0x044 TimeDateStamp : 0x490057
+0x044 LoadedImports : 0x00490057 Void
+0x048 EntryPointActivationContext : (null)
+0x04c PatchInformation : 0x86344c99 Void
+0x050 ForwarderLinks : _LIST_ENTRY [ 0x0 - 0x6000 ]
+0x058 ServiceTagLinks : _LIST_ENTRY [ 0x67277d47 - 0x79004d ]
+0x060 StaticLinks : _LIST_ENTRY [ 0x720044 - 0x760069 ]
+0x068 ContextInformation : 0x00720065 Void
+0x06c OriginalBase : 0x2e0031
+0x070 LoadTime : _LARGE_INTEGER 0x00000073`00790073

0环与3环通信(常规方式)

1、设备对象

我们在开发窗口程序的时候,消息被封装成一个结构体:MSG

在内核开发时,消息被封装成另外一个结构体:IRP(I/O Request Package)

在窗口程序中,能够接收消息的只能是窗口对象。在内核中,能够接收IRP消息的只能是设备对象

image-20241103211509184

2、创建设备对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//创建设备名称
UNICODE_STRING Devicename;
RtlInitUnicodeString(&Devicename,L"\\Device\\MyDevice");

//创建设备
IoCreateDevice(
pDriver, //当前设备所属的驱动对象
0,
&Devicename, //设备对象的名称
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&pDeviceObj //设备对象指针
);

3、设置交互数据的方式

1
pDeviceObj->Flags |= DO_BUFFERED_IO;

缓冲区方式读写(DO_BUFFERED_IO):操作系统将应用程序提供缓冲区的数据复制到内核模式下的地址中。

直接方式读写(DO_DIRECT_IO):操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。缺点就是要单独占用物理页面。

其他方式读写(在调用IoCreateDevice创建设备后对pDevObj->Flags即不设置DO_BUFFERED_IO也不设置DO_DIRECT_IO此时就是其他方式):在使用其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址。在驱动程序中,直接操作应用程序的缓冲区地址是很危险的。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。

4、创建符号链接

1
2
3
4
5
//创建符号链接名称
RtlInitUnicodeString(&SymbolicLinkName,L"\\??\\MyTestDriver");

//创建符号链接
IoCreateSymbolicLink(&SymbolicLinkName,&Devicename);

[!NOTE]

1、设备名称的作用是给内核对象用的,如果要在Ring3访问,必须要有符号链接其实就是一个别名,没有这个别名,在Ring3不可见。

2、内核模式下,符号链接是以“??\”开头的,如C 盘就是“??\C:”

3、而在用户模式下,则是以“\.\”开头的,如C 盘就是“\.\C:“

5、IRP与派遣函数

image-20241103211936136

在3环会将事件封装澄一个MSG对象,窗口对象接收,然后触发对应的回调函数。

同样在0环也是会将事件封装澄一个IRP对象,设备对象接收,然后触发对应的派遣函数。

6、IRP的类型

IRP类型 来源 作用
IRP_MJ_CREATE CreateFile 打开设备
IRP_MJ_READ ReadFile 设备读取数据
IRP_MJ_WRITE WriteFile 设备写入数据
IRP_MJ_CLOSE CloseHandle 关闭设备

其他类型:

IRP类型 来源
IRP_MJ_DEVICE_CONTROL DeviceControl函数会产生此IRP
IRP_MJ_POWER 在操作系统处理电源消息时,产生次IRP
IRP_MJ_SHUTDOWN 关闭系统前会产生此IRP

7、派遣函数注册位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _DRIVER_OBJECT
nt!_DRIVER_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void
+0x010 DriverSize : Uint4B
+0x014 DriverSection : Ptr32 Void
+0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION
+0x01c DriverName : _UNICODE_STRING
+0x024 HardwareDatabase : Ptr32 _UNICODE_STRING
+0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH
+0x02c DriverInit : Ptr32 long
+0x030 DriverStartIo : Ptr32 void
+0x034 DriverUnload : Ptr32 void //卸载函数
+0x038 MajorFunction : [28] Ptr32 long //派遣函数

8、注册派遣函数

派遣函数种类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define IRP_MJ_CREATE                   0x00
#define IRP_MJ_CREATE_NAMED_PIPE 0x01
#define IRP_MJ_CLOSE 0x02
#define IRP_MJ_READ 0x03
#define IRP_MJ_WRITE 0x04
#define IRP_MJ_QUERY_INFORMATION 0x05
#define IRP_MJ_SET_INFORMATION 0x06
#define IRP_MJ_QUERY_EA 0x07
#define IRP_MJ_SET_EA 0x08
#define IRP_MJ_FLUSH_BUFFERS 0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b
#define IRP_MJ_DIRECTORY_CONTROL 0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d
#define IRP_MJ_DEVICE_CONTROL 0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f
#define IRP_MJ_SHUTDOWN 0x10
#define IRP_MJ_LOCK_CONTROL 0x11
#define IRP_MJ_CLEANUP 0x12
#define IRP_MJ_CREATE_MAILSLOT 0x13
#define IRP_MJ_QUERY_SECURITY 0x14
#define IRP_MJ_SET_SECURITY 0x15
#define IRP_MJ_POWER 0x16
#define IRP_MJ_SYSTEM_CONTROL 0x17
#define IRP_MJ_DEVICE_CHANGE 0x18
#define IRP_MJ_QUERY_QUOTA 0x19
#define IRP_MJ_SET_QUOTA 0x1a
#define IRP_MJ_PNP 0x1b
#define IRP_MJ_PNP_POWER IRP_MJ_PNP // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION 0x1b

注册派遣函数格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NTSTATUS DriverEntry( 。。。。)  
{
//设置卸载函数
pDriverObject->DriverUnload = 卸载函数;

//设置派遣函数
pDriverObject->MajorFunction[IRP_MJ_CREATE] = 派遣函数1;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = 派遣函数2;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = 派遣函数3;
pDriverObject->MajorFunction[IRP_MJ_READ] = 派遣函数4;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = 派遣函数5;
pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = 派遣函数6;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 派遣函数7;
pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = 派遣函数8;
pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = 派遣函数9;
}

IRP_MJ_MAXIMUM_FUNCTION 派遣函数的最大值

9、派遣函数的格式

1
2
3
4
5
6
7
8
9
10
11
12
//派遣函数的格式:

NTSTATUS MyDispatchFunction(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
//处理自己的业务...

//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据 没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

10、通过IRP_MJ_DEVICE_CONTROL交互数据

应用层调用DeviceControl函数会产生此IRP.

代码:

Demo代码

驱动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <ntifs.h>

#define Deivce_Name L"\\Device\\hackflame1"
#define Symbol_Name L"\\??\\hackflame1"

NTSTATUS dispatchCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
DbgBreakPoint();
DbgPrint("dispatchCreate执行成功\n");

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS dispatchClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
DbgBreakPoint();
DbgPrint("dispatchClose执行成功\n");

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

NTSTATUS dispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
DbgBreakPoint();
DbgPrint("dispatchDeviceControl执行成功\n");

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


VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNICODE_STRING symName;
RtlInitUnicodeString(&symName, Symbol_Name);
IoDeleteSymbolicLink(&symName);
if (pDriver->DeviceObject) IoDeleteDevice(pDriver->DeviceObject);
DbgPrintEx(77, 0, "驱动卸载完成");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{

UNICODE_STRING deviceName = { 0 };
UNICODE_STRING symName = { 0 };

RtlInitUnicodeString(&deviceName, Deivce_Name);
RtlInitUnicodeString(&symName, Symbol_Name);

PDEVICE_OBJECT pDevice = NULL; //这里是设备类型 PDEVICE_OBJECT 不是 PDRIVER_OBJECT

NTSTATUS status = IoCreateDevice(pDriver, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);

if (!NT_SUCCESS(status))
{
KdPrintEx((77, 0, "[db]:%x\r\n", status));
return status;
}

status = IoCreateSymbolicLink(&symName, &deviceName);

if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDriverObj);
KdPrintEx((77, 0, "[db]:%x\r\n", status));
return status;
}

pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
pDevice->Flags |= DO_BUFFERED_IO;

pDriver->MajorFunction[IRP_MJ_CREATE] = dispatchCreate;
pDriver->MajorFunction[IRP_MJ_CLOSE] = dispatchClose;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatchDeviceControl;

pDriver->DriverUnload = DriverUnload;

return status;
}


3环代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include<windows.h>
#include<winioctl.h>
int main()
{
HANDLE hfile = CreateFileA("\\\\.\\hackflame1", GENERIC_READ | GENERIC_WRITE,0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0);
DWORD status = GetLastError();
if (!hfile)
{
printf("status:%d\n", status);
system("pause");
return 0;
}
printf("status:%d\n", status);
CloseHandle(hfile);
}

驱动通信案例代码

驱动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <ntifs.h>

#define OPER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define Deivce_Name L"\\Device\\hackflame1"
#define Symbol_Name L"\\??\\hackflame1"

NTSTATUS dispatchCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
DbgPrint("dispatchCreate执行成功\n");

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS dispatchClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
DbgPrint("dispatchClose执行成功\n");

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

NTSTATUS dispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
DbgBreakPoint();
DbgPrint("dispatchDeviceControl执行成功\n");

ULONG uRead, uWrite;
NTSTATUS retStatus = NULL;
//拿到当前设备的堆栈
PIO_STACK_LOCATION pCurrentStack = IoGetCurrentIrpStackLocation(Irp);
//获取操作码
ULONG code = pCurrentStack->Parameters.DeviceIoControl.IoControlCode;

//获取缓冲区
PVOID pIoBuffer = Irp->AssociatedIrp.SystemBuffer;

//判断操作码是否是对应的
switch (code)
{
case OPER:
//从3环向0环写
memcpy(&uRead,pIoBuffer,4);
DbgPrint("uRead: %x \n", uRead);

//从0环向3环写
uWrite = 0x123456;
memcpy(pIoBuffer, &uWrite, 4);
Irp->IoStatus.Information = 4;
retStatus = STATUS_SUCCESS;
break;
default:
break;
}

Irp->IoStatus.Status = retStatus == STATUS_SUCCESS ? retStatus : STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}


VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNICODE_STRING symName;
RtlInitUnicodeString(&symName, Symbol_Name);
IoDeleteSymbolicLink(&symName);
if (pDriver->DeviceObject) IoDeleteDevice(pDriver->DeviceObject);
DbgPrintEx(77, 0, "驱动卸载完成");

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{

UNICODE_STRING deviceName = { 0 };
UNICODE_STRING symName = { 0 };

RtlInitUnicodeString(&deviceName, Deivce_Name);
RtlInitUnicodeString(&symName, Symbol_Name);

PDEVICE_OBJECT pDevice = NULL; //这里是设备类型 PDEVICE_OBJECT 不是 PDRIVER_OBJECT

NTSTATUS status = IoCreateDevice(pDriver, 0, &deviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);

if (!NT_SUCCESS(status))
{
KdPrintEx((77, 0, "[db]:%x\r\n", status));
return status;
}

status = IoCreateSymbolicLink(&symName, &deviceName);

if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDevice);
KdPrintEx((77, 0, "[db]:%x\r\n", status));
return status;
}

pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
pDevice->Flags |= DO_BUFFERED_IO;

pDriver->MajorFunction[IRP_MJ_CREATE] = dispatchCreate;
pDriver->MajorFunction[IRP_MJ_CLOSE] = dispatchClose;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatchDeviceControl;

pDriver->DriverUnload = DriverUnload;

return status;
}

三环代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// demoR3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include<windows.h>
#include<winioctl.h>

#define OPER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
HANDLE g_hDevice;
int main()
{
HANDLE g_hDevice = CreateFileA("\\\\.\\hackflame1", GENERIC_READ | GENERIC_WRITE,0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0);
DWORD code = GetLastError();
if (code != 0)
{
printf("status:%d\n", code);
system("pause");
return 0;
}
LPVOID inBuffer , outBuffer;
inBuffer = (LPVOID)0x223456;
DWORD size = 4;
DWORD dwLen;
//向0环写入数据
/*
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_reads_bytes_opt_(nInBufferSize) LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_writes_bytes_to_opt_(nOutBufferSize,*lpBytesReturned) LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped
*/
DeviceIoControl(g_hDevice, OPER,&inBuffer,0x10,&outBuffer,0x10,&dwLen,NULL);
printf("outBuffer:%x \n", outBuffer);
CloseHandle(g_hDevice);
return 0;
}

本站由 RuntimeBroker 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本"页面"访问 次 | 👀总访问 次 | 总访客