Windows XP 驱动开发(三)

ʕ •ᴥ•ʔ ɔ:

1 0环与3环通信常规方式

在驱动中,如果想与3环的程序进行交互,就必须得有设备对象。

本文讲常规的通信方式,通常程序开发使用该方式,非常规方式在这里:0环与3环通信非常规方式 —— 0环InlineHook

1.1 设备对象

我们在开发窗口程序的时候,消息被封装成一个结构体:MSG,在内核开发时,消息被封装成另外一个结构体:IRP(I/O Request Package)。

在窗口程序中,能够接收消息的只能是窗口对象。在内核中,能够接收IRP消息的只能是设备对象。当上层应用程序需要访问底层输入输出设备时,发出I/O请求,系统会把这些请求转化为IRP数据,不同的IRP会启动I/O设备驱动中对应的派遣函数。

20.png

1
2
3
4
5
6
7
8
typedef struct tagMSG {
HWND hwnd; //该消息所属的窗口句柄
UINT message; //指定消息的类型
WPARAM wParam; //用于指定消息的附加信息,根据消息不同,代表不同意思
LPARAM lParam; //用于指定消息的附加信息,根据消息不同,代表不同意思
DWORD time; //该消息投递到消息列队当中的时间
POINT pt; //该消息投递到消息列队当时,鼠标的当前位置
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kd> dt _IRP
nt!_IRP
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 MdlAddress : Ptr32 _MDL
+0x008 Flags : Uint4B
+0x00c AssociatedIrp : __unnamed
+0x010 ThreadListEntry : _LIST_ENTRY
+0x018 IoStatus : _IO_STATUS_BLOCK
+0x020 RequestorMode : Char
+0x021 PendingReturned : UChar
+0x022 StackCount : Char
+0x023 CurrentLocation : Char
+0x024 Cancel : UChar
+0x025 CancelIrql : UChar
+0x026 ApcEnvironment : Char
+0x027 AllocationFlags : UChar
+0x028 UserIosb : Ptr32 _IO_STATUS_BLOCK
+0x02c UserEvent : Ptr32 _KEVENT
+0x030 Overlay : __unnamed
+0x038 CancelRoutine : Ptr32 void
+0x03c UserBuffer : Ptr32 Void
+0x040 Tail : __unnamed

1.2 创建设备对象

使用IoCreateDevice函数在0环来创建一个设备对象。

驱动程序原本的目的是用来控制硬件,但我们也可以用驱动做一些安全相关的事情,因为驱动运行在0环。为了控制驱动运行,我们需要在3环向驱动发数据,所以我们需要有一种方法来建立0环到3环的通信。本文介绍常规方式,也就是创建设备对象的方式。

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

NTSTATUS IoCreateDevice(
[in] PDRIVER_OBJECT DriverObject, //当前设备所属的驱动对象
[in] ULONG DeviceExtensionSize,
[in, optional] PUNICODE_STRING DeviceName, //设备对象的名称
[in] DEVICE_TYPE DeviceType,
[in] ULONG DeviceCharacteristics,
[in] BOOLEAN Exclusive,
[out] PDEVICE_OBJECT *DeviceObject //设备对象指针
);

1.3 0环与3环数据交互方式

设备对象的Flags(UINT32)成员决定0环和3环进行数据交互的方式,分以下3种:

  1. 缓冲区方式读写DO_BUFFERED_IO):操作系统将应用程序提供缓冲区的数据复制到内核模式下的地址中。实质上是:将3环一个缓冲区的内容复制到0环的缓冲区(数据量小时好用,但是交互慢)。
  2. 直接方式读写DO_DIRECT_IO):操作系统会将用户模式下的缓冲区锁住。然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。缺点就是要单独占用物理页面。0环和3环线性地址指向同一块物理页(数据量大时好用,但是物理页不允许换页,如写入文件)。
  3. 其他方式读写:在0环直接读3环的线性地址,不建议使用。在调用IoCreateDevice创建设备后对pDevObj->Flags即不设置DO_BUFFERED_IO也不设置DO_DIRECT_IO此时就是其他方式。

在使用其他方式读写设备时,派遣函数直接读写应用程序提供的缓冲区地址。在驱动程序中,直接操作应用程序的缓冲区地址是很危险的。只有驱动程序与应用程序运行在相同线程上下文的情况下,才能使用这种方式。

使用方式:

1
pDeviceObj->Flags |= DO_BUFFERED_IO;

1.4 创建符号链接

创建符号链接目的是:在3环不能直视使用设备名称进行数据交互,需要在0环将设备对象链接到一个符号上去,然后在3环通过这个符号进行数据交互。

特别说明:
1、设备名称的作用是给内核对象用的,如果要在Ring3访问,必须要有符号链接。其实就是一个别名,没有这个别名,在Ring3不可见。
2、内核模式下,符号链接是以\??\开头的,如C 盘就是\??\C:
3、而在用户模式下,则是以\\.\开头的,如C 盘就是\\.\C:

4、在3环使用符号链接的格式:\\.\符号名称,编程时通常需要使用转义符,写为\\\\.\\符号名称

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

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

1.5 IRP与派遣函数

Win32程序中,当一个事件发生,操作系统就会将这个事件封装成一个tagMSG结构的消息,然后将该消息发送给产生事件的窗口,然后窗口调用对应的消息回调函数进行处理。

同理,当3环调用API访问设备的时候,在0环会将某API事件封装成IRP结构,然后将IRP发给设备对象,设备对象调用派遣函数(回调函数)来进行处理。

21.png

IRP的类型:

  1. 当应用层通过CreateFile,ReadFile,WriteFile,CloseHandle等函数打开、从设备读取数据、向设备写入数据、关闭设备的时候,会使操作系统产生出IRP_MJ_CREATEIRP_MJ_READIRP_MJ_WRITEIRP_MJ_CLOSE等不同的IRP。

  2. 其他类型的IRP。

22.png

IRP列表如下:

名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
RP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或者丢弃输入缓冲区 FlushFileBuffers
FlushConsoleInputBuffer
PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown

wdm.h中:

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

派遣函数:

在Win32中,回调函数需要在窗口处理函数的参数中进行指定。在驱动程序中,回调函数需要在驱动对象里面进行指定(派遣)。

一个驱动可以创建许多设备,这些设备的回调函数都放在驱动对象的最后一个成员里面(MajorFunction)。函数是有顺序的

1
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

IRP_MJ_MAXIMUM_FUNCTION :派遣函数的最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kd> dt _DRIVER_OBJECT
ntdll!_DRIVER_OBJECT
+0x000 Type : Int2B //2B = 2Byte(SHORT)
+0x002 Size : Int2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 Flags : Uint4B
+0x00c DriverStart : Ptr32 Void //Ptr32:32位的指针类型
+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

注册派遣函数:(需要谁就注册谁),函数是有顺序的

1
2
3
4
5
6
7
8
9
10
11
12
13
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING reg_path)  
{
//设置派遣函数
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;
}

派遣函数的格式

Win32的窗口回调函数有自己的格式,同样的派遣函数也有自己的格式:

1
2
3
4
5
6
7
8
9
10
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;
}

1.6 IRP_MJ_DEVICE_CONTROL交互数据

应用层调用DeviceControl函数会产生此IRP。该函数可以与设备双向交互数据,最常用。

1
2
3
4
5
6
7
8
9
10
BOOL DeviceIoControl(
[in] HANDLE hDevice,
[in] DWORD dwIoControlCode,
[in, optional] LPVOID lpInBuffer,
[in] DWORD nInBufferSize,
[out, optional] LPVOID lpOutBuffer,
[in] DWORD nOutBufferSize,
[out, optional] LPDWORD lpBytesReturned,
[in, out, optional] LPOVERLAPPED lpOverlapped
);

需要注意的是参数2,控制码:dwIoControlCode。32位整数,由一个宏CTL_CODE将4个参数组成一个32位数字。

1
#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
  • FILE_DEVICE_UNKNOWN:设备类型
  • 0x800:自定义代码,0~0x7FF为保留,用户可用0x800~0xFFF
  • METHOD_BUFFERED:交互方式
  • FILE_ANY_ACCESS:权限

1.7 练习:0环与3环通信

0环代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <ntddk.h>
//#include <ntifs.h>

#define DEVICE_NAME L"\\Device\\A1v1n_Device_R0" //设备名称,0环用
#define SYMBOLICLINK_NAME L"\\??\\A1v1n_Device_R3" //符号链接的名称,3环用

NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);//派遣函数--CreateFile
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);//派遣函数--CloseHandle
NTSTATUS IrpDeviceIoControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);//派遣函数--DeviceIoControl

#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define OPER2 CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

// 卸载函数
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNICODE_STRING SymbolicLinkName;
//删除符号连接
RtlInitUnicodeString(&SymbolicLinkName,SYMBOLICLINK_NAME);
IoDeleteSymbolicLink(&SymbolicLinkName);
//删除设备
IoDeleteDevice(pDriver->DeviceObject);
DbgPrint("驱动程序停止运行了.\r\n");
}

// 入口函数,相当于main
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriver, IN PUNICODE_STRING reg_path)
{
NTSTATUS Ntstaus = 0;
PDEVICE_OBJECT pDeviceObj = NULL; //设备对象
UNICODE_STRING Unicode_Devicename = {0};//设备名称
UNICODE_STRING SymbolicLinkName = {0};

//创建设备名称
RtlInitUnicodeString(&Unicode_Devicename,DEVICE_NAME);

//创建设备对象
Ntstaus = IoCreateDevice(pDriver, 0 , &Unicode_Devicename, FILE_DEVICE_UNKNOWN, \
FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj);
if(Ntstaus != STATUS_SUCCESS)
{
DbgPrint("创建设备对象失败了.\r\n");
IoDeleteDevice(pDeviceObj);
return Ntstaus;
}

//设置数据交互方式
pDeviceObj->Flags |= DO_BUFFERED_IO;

//创建符号链接
RtlInitUnicodeString(&SymbolicLinkName,SYMBOLICLINK_NAME);
IoCreateSymbolicLink(&SymbolicLinkName,&Unicode_Devicename);

//注册派遣函数
pDriver->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc;
pDriver->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoControlProc;

// 设置一个卸载函数,便于退出
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
//处理自己的业务...
DbgPrint("应用层连接设备成功.\r\n");

//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据 没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);//将消息向下处理
return STATUS_SUCCESS;
}
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
//处理自己的业务...
DbgPrint("应用层设备断开连接.\r\n");

//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据 没有填0
IoCompleteRequest(pIrp, IO_NO_INCREMENT);//将消息向下处理
return STATUS_SUCCESS;
}
NTSTATUS IrpDeviceIoControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = 0;
PIO_STACK_LOCATION pIrpStack = NULL; //用于接收缓冲区的数据
ULONG uIoControlCode = 0; //控制码
PVOID pIoBuffer = NULL; //缓冲区地址
ULONG uInLength = 0; //从R3发送过来的数据长度
ULONG uOutLength = 0; //从R0发给R3的数据长度
ULONG uRead = 0; //从缓冲区读取的值
ULONG uWrite = 0x12345678; //写入缓冲区的值

// 从缓冲区获取数据
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

// 获取控制码
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 获取缓冲区地址(输入输出是同一个)
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
// Ring3 发送数据的长度
uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Ring0 发送数据的长度
uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

switch(uIoControlCode)
{
case OPER1:
{
DbgPrint("IrpDeviceControlProc -> OPER1...\n");
pIrp->IoStatus.Information = 0; // 返回给3环多少数据,没有填0
status = STATUS_SUCCESS;
break;
}
case OPER2:
{
DbgPrint("IrpDeviceControlProc -> OPER2 接收字节数: %d\n", uInLength);
DbgPrint("IrpDeviceControlProc -> OPER2 输出字节数: %d\n", uOutLength);
// 读取缓冲区
memcpy(&uRead,pIoBuffer,4);
DbgPrint("IrpDeviceControlProc -> OPER2 uRead: %x\n", uRead);
// 写入缓冲区
memcpy(pIoBuffer, &uWrite, 4);
// 设置状态
pIrp->IoStatus.Information = 2; // 返回给3环2字节
status = STATUS_SUCCESS;
break;
}
}

//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
IoCompleteRequest(pIrp, IO_NO_INCREMENT);//将消息向下处理
return STATUS_SUCCESS;
}

3环代码:

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
#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>
#include <winioctl.h>

#define SYMBOLICLINK_NAME L"\\\\.\\A1v1n_Device_R3"
#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define OPER2 CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IN_BUFFER_MAXLENGTH 4
#define OUT_BUFFER_MAXLENGTH 4

int _tmain(int argc, _TCHAR* argv[])
{
getchar();
// 获取设备句柄
HANDLE hDevice = CreateFileW(SYMBOLICLINK_NAME,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
getchar();
DWORD dwError = GetLastError();
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("获取设备句柄失败 %d.\n", dwError); // 如果返回1,请在驱动中指定 IRP_MJ_CREATE 处理函数
getchar();
return 1;
}
else
{
printf("获取设备句柄成功.\n");
}
// 测试通信
DWORD dwInBuffer = 0x11111111;
DWORD dwOutBuffer = 0;
DWORD dwOut;
DeviceIoControl(hDevice,OPER2,&dwInBuffer,IN_BUFFER_MAXLENGTH,&dwOutBuffer,OUT_BUFFER_MAXLENGTH,&dwOut,NULL);
printf("dwOutBuffer: %08X dwOut: %08X\n", dwOutBuffer, dwOut);
// 关闭设备
CloseHandle(hDevice);
getchar();
return 0;
}

23.png

24.png

1.8 练习:在3环实现驱动加载运行

一、手动加载驱动步骤

用GetFullPathNameW获取驱动的完整路径
用OpenSCManager打开服务控制管理器
用CreateServiceW创建服务
如果服务创建已存在,直接用OpenServiceW打开服务,否则用StartServiceW开启服务
二、卸载

用OpenSCManager打开服务控制管理器
用OpenServiceA打开服务
用ControlService停止驱动服务

⚠️注意:驱动项目名应和三环中定义的 DRIVER_NAME 一致。MathsDriver.sys程序需要放到3环程序所在目录。

3环代码:

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

// 编写一个简单的驱动,在驱动入口和卸载函数打印一些提示信息
// 这个驱动可以做加法,3环程序可以给他传两个4字节整数,驱动会返回相加的结果
#define DRIVER_NAME L"MathsDriver"
#define DRIVER_PATH L"MathsDriver.sys"
#define DRIVER_LINK L"\\\\.\\MathsDriverLnk"
#define OPERADD CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

BOOL LoadDriver(PCWSTR lpszDriverName, PCWSTR lpszDriverPath)
{
// 获取驱动完整路径
WCHAR szDriverFullPath[MAX_PATH] = { 0 };
GetFullPathNameW(lpszDriverPath,MAX_PATH,szDriverFullPath,NULL);
//printf("%ws\n", szDriverFullPath);
// 打开服务控制管理器
SC_HANDLE hServiceMgr = NULL; // SCM管理器句柄
hServiceMgr = OpenSCManagerW(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if (NULL == hServiceMgr)
{
printf("OpenSCManagerW 失败, %d\n", GetLastError());
return FALSE;
}
printf("打开服务控制管理器成功.\n");
// 创建驱动服务
SC_HANDLE hServiceDDK = NULL; // NT驱动程序服务句柄
hServiceDDK = CreateServiceW(
hServiceMgr,
lpszDriverName,
lpszDriverName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE,
szDriverFullPath,
NULL,
NULL,
NULL,
NULL,
NULL);
if (NULL == hServiceDDK)
{
DWORD dwErr = GetLastError();
if (dwErr != ERROR_IO_PENDING && dwErr != ERROR_SERVICE_EXISTS)
{
printf("创建驱动服务失败, %d\n", dwErr);
return FALSE;
}
}
printf("创建驱动服务成功.\n");
// 驱动服务已经创建,打开服务
hServiceDDK = OpenServiceW(hServiceMgr,lpszDriverName,SERVICE_ALL_ACCESS);
if (!StartService(hServiceDDK, NULL, NULL))
{
DWORD dwErr = GetLastError();
if (dwErr != ERROR_SERVICE_ALREADY_RUNNING)
{
printf("运行驱动服务失败, %d\n", dwErr);
return FALSE;
}
}
printf("运行驱动服务成功.\n");
if (hServiceDDK)
{
CloseServiceHandle(hServiceDDK);
}
if (hServiceMgr)
{
CloseServiceHandle(hServiceMgr);
}
return TRUE;
}

void RunMathDriver()
{
HANDLE hDevice = CreateFileW(DRIVER_LINK, GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("创建设备失败. %d\n", GetLastError());
return;
}
// 加法
DWORD n1 = 100, n2 = 50;
BYTE InBuffer[8];
DWORD OutBuffer;
memcpy(InBuffer,&n1,4);
memcpy(InBuffer+4,&n2,4);
DWORD dwOut;
DeviceIoControl(hDevice,OPERADD,InBuffer,8,&OutBuffer,4,&dwOut,NULL);
printf("%d + %d = %d\n", n1, n2, OutBuffer);
// 关闭设备
CloseHandle(hDevice);
}

void UnLoadDriver(PCWSTR lpszDriverName)
{
SC_HANDLE hServiceMgr = OpenSCManagerW(0,0,SC_MANAGER_ALL_ACCESS);
SC_HANDLE hServiceDDK = OpenServiceW(hServiceMgr,lpszDriverName,SERVICE_ALL_ACCESS);
SERVICE_STATUS SvrStatus;
ControlService(hServiceDDK,SERVICE_CONTROL_STOP,&SvrStatus);
DeleteService(hServiceDDK);
if (hServiceDDK)
{
CloseServiceHandle(hServiceDDK);
}
if (hServiceMgr)
{
CloseServiceHandle(hServiceMgr);
}
}

int _tmain(int argc, _TCHAR* argv[])
{
if (!LoadDriver(DRIVER_NAME, DRIVER_PATH))
{
printf("加载驱动失败.\n");
getchar();
return 1;
}
RunMathDriver();
UnLoadDriver(DRIVER_NAME);
getchar();
return 0;
}

0环驱动程序:

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

// 编写一个简单的驱动,在驱动入口和卸载函数打印一些提示信息
// 这个驱动可以做加法,3环程序可以给他传两个4字节整数,驱动会返回相加的结果
#define DEVICE_NAME L"\\Device\\MathsDriverDev"
#define DRIVER_LINK L"\\??\\MathsDriverLnk"
#define OPERADD CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

// 函数声明
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath);
VOID DriverUnload(PDRIVER_OBJECT pDriver);
NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);

// 入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath)
{
NTSTATUS status;
ULONG uIndex = 0;
PDEVICE_OBJECT pDeviceObj = NULL; // 设备对象指针
UNICODE_STRING DeviceName; // 设备名,0环用
UNICODE_STRING SymbolicLinkName; // 符号链接名,3环用

// 创建设备名称
RtlInitUnicodeString(&DeviceName,DEVICE_NAME);
// 创建设备
status = IoCreateDevice(pDriver,0,&DeviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&pDeviceObj);
if (status != STATUS_SUCCESS)
{
IoDeleteDevice(pDeviceObj);
DbgPrint("创建设备失败.\n");
return status;
}
DbgPrint("创建设备成功.\n");
// 设置交互数据的方式
pDeviceObj->Flags |= DO_BUFFERED_IO;
// 创建符号链接
RtlInitUnicodeString(&SymbolicLinkName, DRIVER_LINK);
IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
// 设置分发函数
pDriver->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc;
pDriver->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceControlProc;
// 设置卸载函数
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

// 卸载驱动
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNICODE_STRING SymbolicLinkName;
// 删除符号链接,删除设备
RtlInitUnicodeString(&SymbolicLinkName, DRIVER_LINK);
IoDeleteSymbolicLink(&SymbolicLinkName);
IoDeleteDevice(pDriver->DeviceObject);
DbgPrint("驱动卸载成功\n");
}

// 不设置这个函数,则Ring3调用CreateFile会返回1
// IRP_MJ_CREATE 处理函数
NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("应用层连接设备.\n");
// 返回状态如果不设置,Ring3返回值是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

// IRP_MJ_CLOSE 处理函数
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("应用层断开连接设备.\n");
// 返回状态如果不设置,Ring3返回值是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

// IRP_MJ_DEVICE_CONTROL 处理函数
NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
// DbgPrint("IrpDeviceControlProc.\n");
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
PIO_STACK_LOCATION pIrpStack;
ULONG uIoControlCode;
PVOID pIoBuffer;
ULONG uInLength;
ULONG uOutLength;
ULONG uRead1;
ULONG uRead2;
ULONG uWrite;

// 设置临时变量的值
uRead1 = uRead2 = 0;
uWrite = 0x12345678;
// 获取IRP数据
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// 获取控制码
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 获取缓冲区地址(输入输出是同一个)
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
// Ring3 发送数据的长度
uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Ring0 发送数据的长度
uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

switch (uIoControlCode)
{
case OPERADD:
{
// 读取缓冲区
memcpy(&uRead1,pIoBuffer,4);
memcpy(&uRead2,(PUCHAR)pIoBuffer+4,4);
uWrite = uRead1 + uRead2;
// 写入缓冲区
memcpy(pIoBuffer, &uWrite, 4);
// 设置状态
pIrp->IoStatus.Information = 4;
status = STATUS_SUCCESS;
break;
}
}

// 返回状态如果不设置,Ring3返回值是失败
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

1.9 练习:结束指定PID进程

编写一个3环程序,可以将任意一个进程的PID传递给0环的驱动程序,如果这个进程存在,驱动程序将该进程终结。另外,如果工具可以以一个单文件的形式存在,即不带sys文件,那么看起来会高级不少,然而我现在无法完成该需求,如果你知道怎么做,请在评论中留言,我会转达hambaga

这个项目基于1.8的练习,3环部分,只需将传递两个4字节加数改成传递一个4字节PID,并且修改一下宏定义中,驱动项目的名字即可。

3环代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include "stdafx.h"
#include <Windows.h>
#include <winioctl.h>
#include <tchar.h>
#include <stdlib.h> //system("Pause");

#define DRIVER_NAME L"MathsDriver"
#define DRIVER_PATH L"KillProc.sys"
#define DRIVER_LINK L"\\\\.\\MathsDriverLnk"
#define OPERADD CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

BOOL LoadDriver(PCWSTR lpszDriverName, PCWSTR lpszDriverPath)
{
// 获取驱动完整路径
WCHAR szDriverFullPath[MAX_PATH] = { 0 };
GetFullPathNameW(lpszDriverPath,MAX_PATH,szDriverFullPath,NULL);
printf("%ws\n", szDriverFullPath);
// 打开服务控制管理器
SC_HANDLE hServiceMgr = NULL; // SCM管理器句柄
hServiceMgr = OpenSCManagerW(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if (NULL == hServiceMgr)
{
printf("OpenSCManagerW 失败, %d\n", GetLastError());
return FALSE;
}
printf("打开服务控制管理器成功.\n");
// 创建驱动服务
SC_HANDLE hServiceDDK = NULL; // NT驱动程序服务句柄
hServiceDDK = CreateServiceW(
hServiceMgr,
lpszDriverName,
lpszDriverName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE,
szDriverFullPath,
NULL,
NULL,
NULL,
NULL,
NULL);
if (NULL == hServiceDDK)
{
DWORD dwErr = GetLastError();
if (dwErr != ERROR_IO_PENDING && dwErr != ERROR_SERVICE_EXISTS)
{
printf("创建驱动服务失败, %d\n", dwErr);
return FALSE;
}
}
printf("创建驱动服务成功.\n");
// 驱动服务已经创建,打开服务
hServiceDDK = OpenServiceW(hServiceMgr,lpszDriverName,SERVICE_ALL_ACCESS);
if (!StartServiceW(hServiceDDK, NULL, NULL))
{
DWORD dwErr = GetLastError();
if (dwErr != ERROR_SERVICE_ALREADY_RUNNING)
{
printf("运行驱动服务失败, %d\n", dwErr);
return FALSE;
}
}
printf("运行驱动服务成功.\n");
if (hServiceDDK)
{
CloseServiceHandle(hServiceDDK);
}
if (hServiceMgr)
{
CloseServiceHandle(hServiceMgr);
}
return TRUE;
}

void RunMathDriver()
{
HANDLE hDevice = CreateFileW(DRIVER_LINK, GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if (hDevice == INVALID_HANDLE_VALUE)
{
printf("创建设备失败. %d\n", GetLastError());
return;
}
printf("创建设备成功.\n");
// 输入要干掉的进程PID
DWORD dwPID = 0;
printf("请输入要干掉的进程PID:");
scanf("%d",&dwPID);
DWORD dwInBuffer;
DWORD OutBuffer;
memcpy(&dwInBuffer,&dwPID,4);
DWORD dwOut;
DeviceIoControl(hDevice,OPERADD,&dwInBuffer,4,&OutBuffer,4,&dwOut,NULL);
switch(OutBuffer)
{
case 1:
{
printf("进程关闭成功.\n");
break;
}
case 0:
{
printf("进程关闭失败.\n");
break;
}
}

// 关闭设备
CloseHandle(hDevice);
}

void UnLoadDriver(PCWSTR lpszDriverName)
{
SC_HANDLE hServiceMgr = OpenSCManagerW(0,0,SC_MANAGER_ALL_ACCESS);
SC_HANDLE hServiceDDK = OpenServiceW(hServiceMgr,lpszDriverName,SERVICE_ALL_ACCESS);
SERVICE_STATUS SvrStatus;
ControlService(hServiceDDK,SERVICE_CONTROL_STOP,&SvrStatus);
DeleteService(hServiceDDK);
if (hServiceDDK)
{
CloseServiceHandle(hServiceDDK);
}
if (hServiceMgr)
{
CloseServiceHandle(hServiceMgr);
}
}

int _tmain(int argc, _TCHAR* argv[])
{
if (!LoadDriver(DRIVER_NAME, DRIVER_PATH))
{
printf("加载驱动失败.\n");
getchar();
return 1;
}
RunMathDriver();
UnLoadDriver(DRIVER_NAME);
system("Pause");
return 0;
}


0环驱动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
#include <ntddk.h>

#define DEVICE_NAME L"\\Device\\MathsDriverDev"
#define DRIVER_LINK L"\\??\\MathsDriverLnk"
#define OPERADD CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

ULONG GetKernelMoudleInfo(IN PDRIVER_OBJECT pDriver,OUT PVOID* DllBase,OUT PULONG SizeOfImage);
PVOID Find_FeatureCode(PVOID pShellCode,ULONG ulShellCode_Len,PVOID DllBase,ULONG ulSizeOfImage);
BOOLEAN KillProc(PDRIVER_OBJECT driver,ULONG uPID);

// 函数声明
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath);
VOID DriverUnload(PDRIVER_OBJECT pDriver);
NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);
NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp);
PDRIVER_OBJECT g_pDriver = NULL;

typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;//这个成员把系统所有加载(可能是停止没被卸载)已经读取到内存中,双链表
LIST_ENTRY InMemoryOrderLinks;//系统已经启动,没有被初始化,没有调用DriverEntry这个历程的时候,通过这个链表进程串接起来
LIST_ENTRY InInitializationOrderLinks;//已经调用DriverEntry这个函数的所有驱动程序
PVOID DllBase;
PVOID EntryPoint;//驱动的进入点 DriverEntry
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;

// 入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegPath)
{
ULONG Status;
ULONG uIndex = 0;
PDEVICE_OBJECT pDeviceObj = NULL; // 设备对象指针
UNICODE_STRING DeviceName; // 设备名,0环用
UNICODE_STRING SymbolicLinkName; // 符号链接名,3环用
g_pDriver = pDriver;

// 创建设备名称
RtlInitUnicodeString(&DeviceName,DEVICE_NAME);
// 创建设备
Status = IoCreateDevice(pDriver,0,&DeviceName,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&pDeviceObj);
if (Status != STATUS_SUCCESS)
{
IoDeleteDevice(pDeviceObj);
DbgPrint("创建设备失败.\n");
return Status;
}
DbgPrint("创建设备成功.\n");
// 设置交互数据的方式
pDeviceObj->Flags |= DO_BUFFERED_IO;
// 创建符号链接
RtlInitUnicodeString(&SymbolicLinkName, DRIVER_LINK);
IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
// 设置分发函数
pDriver->MajorFunction[IRP_MJ_CREATE] = IrpCreateProc;
pDriver->MajorFunction[IRP_MJ_CLOSE] = IrpCloseProc;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceControlProc;
// 设置卸载函数
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

// 卸载驱动
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNICODE_STRING SymbolicLinkName;
// 删除符号链接,删除设备
RtlInitUnicodeString(&SymbolicLinkName, DRIVER_LINK);
IoDeleteSymbolicLink(&SymbolicLinkName);
IoDeleteDevice(pDriver->DeviceObject);
DbgPrint("驱动卸载成功\n");
}

// 不设置这个函数,则Ring3调用CreateFile会返回1
// IRP_MJ_CREATE 处理函数
NTSTATUS IrpCreateProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("应用层连接设备.\n");
// 返回状态如果不设置,Ring3返回值是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

// IRP_MJ_CLOSE 处理函数
NTSTATUS IrpCloseProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("应用层断开连接设备.\n");
// 返回状态如果不设置,Ring3返回值是失败
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

// IRP_MJ_DEVICE_CONTROL 处理函数
NTSTATUS IrpDeviceControlProc(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
// DbgPrint("IrpDeviceControlProc.\n");
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
PIO_STACK_LOCATION pIrpStack;
ULONG uIoControlCode;
PVOID pIoBuffer;
ULONG uInLength;
ULONG uOutLength;
ULONG uRead1;
ULONG uWrite1;
ULONG uWrite2;
BOOLEAN bRet = FALSE;

// 设置临时变量的值
uRead1 = 0;
uWrite1 = 1;
uWrite2 = 0;
// 获取IRP数据
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
// 获取控制码
uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;
// 获取缓冲区地址(输入输出是同一个)
pIoBuffer = pIrp->AssociatedIrp.SystemBuffer;
// Ring3 发送数据的长度
uInLength = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
// Ring0 发送数据的长度
uOutLength = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

switch (uIoControlCode)
{
case OPERADD:
{
// 读取缓冲区
memcpy(&uRead1,pIoBuffer,4);
//执行结束进程
bRet = KillProc(g_pDriver,uRead1);
if(bRet == TRUE)
{
// 写入缓冲区
memcpy(pIoBuffer, &uWrite1, 4);
}else
{
// 写入缓冲区
memcpy(pIoBuffer, &uWrite2, 4);
}

// 设置状态
pIrp->IoStatus.Information = 4;
status = STATUS_SUCCESS;
break;
}
}

// 返回状态如果不设置,Ring3返回值是失败
pIrp->IoStatus.Status = status;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
BOOLEAN KillProc(PDRIVER_OBJECT driver,ULONG uPID)
{
ULONG ulShellCode[] =
{
0x0124a164, 0x758b0000, 0x44703b08, 0x0db80775,
0xebc00000, 0xbe8d575a, 0x00000248, 0x200147f6,
0x868d1274, 0x00000174
};
ULONG ulRet = 0;
PVOID DllBase = NULL;
ULONG SizeOfImage = 0;
typedef NTSTATUS (*_PspTerminateProcess)(PEPROCESS pEprocess, NTSTATUS ExitCode);
_PspTerminateProcess PspTerminateProcess;
PEPROCESS pEprocess = NULL;

ulRet = GetKernelMoudleInfo(driver,&DllBase,&SizeOfImage);
if(!ulRet)
{
DbgPrint("驱动程序获取ntoskrnl.exe模块失败.\r\n");
return FALSE;
}
PspTerminateProcess = (_PspTerminateProcess)Find_FeatureCode((PVOID)ulShellCode,sizeof(ulShellCode),DllBase,SizeOfImage);
if(!PspTerminateProcess)
{
DbgPrint("PspTerminateProcess特征码搜索失败.\r\n");
return FALSE;
}

PsLookupProcessByProcessId((HANDLE)uPID,&pEprocess); // 记事本PID
PspTerminateProcess(pEprocess, 0);
DbgPrint("PID为%d的进程被 PspTerminateProcess 函数关闭了.\r\n",uPID);
return TRUE;
}
ULONG GetKernelMoudleInfo(IN PDRIVER_OBJECT pDriver,OUT PVOID* DllBase,OUT PULONG SizeOfImage)
{
PLDR_DATA_TABLE_ENTRY pLDR_Head = NULL;
PLDR_DATA_TABLE_ENTRY pLDR_Tail = NULL;
UNICODE_STRING UnicodeMoudleName = {0};

RtlInitUnicodeString(&UnicodeMoudleName,L"ntoskrnl.exe");

pLDR_Head = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;
pLDR_Tail = pLDR_Head;

do
{
if(RtlCompareUnicodeString(&pLDR_Tail->BaseDllName, &UnicodeMoudleName, TRUE) == 0)
{
*DllBase = pLDR_Tail->DllBase;
*SizeOfImage = pLDR_Tail->SizeOfImage;
return 1;
}
//pLDR_Blink = (PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pLDR_Flink,LDR_DATA_TABLE_ENTRY,InLoadOrderLinks);
pLDR_Tail = (PLDR_DATA_TABLE_ENTRY)pLDR_Tail->InLoadOrderLinks.Flink;
}
while(pLDR_Head != pLDR_Tail);
return 0;
}
PVOID Find_FeatureCode(PVOID pShellCode,ULONG ulShellCode_Len,PVOID DllBase,ULONG ulSizeOfImage)
{
ULONG ulDllLastByte = 0;
PVOID pNextByte = NULL;

pNextByte = DllBase;
ulDllLastByte = (ULONG)DllBase + ulSizeOfImage;
while((ULONG)pNextByte + ulShellCode_Len <= ulDllLastByte)
{
if(RtlCompareMemory(pNextByte,pShellCode,ulShellCode_Len) == ulShellCode_Len)
{
return (PVOID)((ULONG)pNextByte - 6);
}
pNextByte = (PVOID)((ULONG)pNextByte + 1);
}

return NULL;
}

2 驱动中的全局变量

在编写驱动中发现,使用全局变量或static变量,它们的地址是会变的,像局部变量一样,全局变量每次调用驱动函数都会重新初始化。

在驱动程序中应该尽量避免使用全局变量,因为全局变量会导致不同步的问题,解决办法之一就是可将全局变量存储在设备扩展中(使用指针指向设备扩展的内存块),将指针作为一个全局变量来使用。

设备扩展(DEVICE_EXTENSION)是与设备对象相关的另一种重要的数据结构。可以用它来保存与特定设备关联的信息。设备扩展其实只是一个未分页的池,由驱动开发者来定义它的大小和内容。并由I/O管理器自动把它分配给设备对象,即设备对象的PVOID DeviceExtension字段。由于此结构是驱动开发者自定义的,所以必须要让系统知道需要给此结构预留多少空间,因此要把设备扩展结构的大小作为参数传递给IoCreateDevice函数。I/O管理器的IoCreateDevice函数将为设备对象和设备扩展对象在非分页内存池内申请内存。

  • 非分页内存池:内存页不可以交换出内存。
  • 分页内存池:内存页可以交换出内存。

使用IoCreateDevice函数创建设备时,可以指定当前设备要使用的非分页内存的大小,由成员DeviceExtensionSize来指定大小。然后由对象的成员DeviceExtension指向这块内存。这块内存本来是用来存设备相关信息的,但是可以由开发者决定存啥,将其转为PVOID使用即可。

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
NTSTATUS IoCreateDevice(
[in] PDRIVER_OBJECT DriverObject,
[in] ULONG DeviceExtensionSize,
[in, optional] PUNICODE_STRING DeviceName,
[in] DEVICE_TYPE DeviceType,
[in] ULONG DeviceCharacteristics,
[in] BOOLEAN Exclusive,
[out] PDEVICE_OBJECT *DeviceObject
);

typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;

3 编写ShellCode

3.1 ShellCode定义

Shellcode是不依赖环境,放到任何地方都可以执行的机器码。

将一个函数模块注入到其他进程中,这个函数的硬编码就成为ShellCode,这种注入叫做代码注入/ShellCode注入

3.2 ShellCode编写原则

ShellCode编写原则有很多,下面列举一些必须要掌握的。

关于ShellCode注入应该注意(编写规则):
1、不要使用全局变量(当前程序的全局变量地址在其他进程不可用)
2、不要使用常量字符串(全局变量)
3、不能直接调用系统函数(不能使用IAT)
4、不能嵌套调用其他函数(全局变量)
5、注意编译器的一些设置

1、不要使用全局变量。

因为我们编写ShellCode时,使用的全局变量是自己的进程里面的全局变量,注入到别的进程里,这个地址就没用了。

2、不要使用常量字符串

因为字符串常量值也是全局变量,注入到别的进程里,根本没有这个字符串。

⚠️替代方法:要使用字符串,请使用字符数组。

1
char s[] = {'1','2',0};

3、不能直接调用系统函数(不能使用IAT)。

如在程序中使用MessageBox(0,0,0,0),调用系统函数的方式是间接调用(FF15),需要从IAT表里获取API地址,每个进程的IAT表位置不同,且对方的进程可能没有导入你需要调用的函数的DLL,那么你是不能调用这个系统函数的。

1
0040227B	FF15 44C14000 call dword ptr ds:[0x40C144]

这里的0x40C144就是全局变量的地址。

所以我们需要用到 LoadLibrary 和 GetProcAddress 这两个函数,来动态获取系统API的函数指针。

但是 LoadLibrary,GetProcAddress 本身就是系统函数,它们本身就依赖IAT表,咋办呢?

解决方案是这样的:通过FS:[0x30] 找到PEB,然后通过PEB里的LDR链表 [PEB+0x0C]找到 kernel32.dll 的地址,然后我们遍历它的 IAT表,找到 LoadLibrary 和 GetProcAddress 函数。

4、不能嵌套调用其他函数。
和前两点道理是一样的,本进程里的函数地址,拿到别的进程的虚拟地址空间是无效的。

1
2
3
int Num = Plus(2,3);

call 004027B0

这里的0x004027B0即为Plus的地址,是一个全局变量

5、注意编译器的一些设置。(《Rootkit:系统灰色地带的潜伏者 第2版》)

31.png

特别注意:

  • 运行时检查:可以设置成关闭,或者代码编译时选择release,这样就不会有堆栈检查_checksp函数。
  • 关闭缓冲区安全检查(GS选项):ShellCode中没有这些信息,方便免杀。

可以参考:

代码注入:(原理都是线程注入)
⚠️注意:类型定义不属于全局变量!!!

3.3 ShellCode示例

本例编写一个函数,该函数:

  1. 利用fs:[30]获取PEB起始地址。
  2. 根据PEB->InLoadOrderModuleList.Flink;获取PLDR_DATA_TABLE_ENTRY
  3. 根据Unicode结构BaseDllName和Unicode模块名称kernel32.dll进行对比获取模块加载基址。
  4. 根据kernel32.dll的基址获取导出表中的函数GetProcAddress的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>

// 内核Unicode字符串
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
} PEB_LDR_DATA,*PPEB_LDR_DATA;
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;
WORD LoadCount;
WORD TlsIndex;
//后面的成员暂时用不到,就不列举了
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

//线程处理函数,回调函数
DWORD WINAPI ShellCode(LPVOID lpThreadParameter);

typedef HMODULE (WINAPI *PLOADLIBRARYA)(LPCSTR);
typedef DWORD (WINAPI *PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI *PMESSAGEBOXA)(HWND, LPCSTR,LPCSTR,UINT);
#define TOUPPER(x) ((((x)>='a')&&((x)<='z'))?((x)-32):(x))
#define TOLOWER(x) ((((x)>='A')&&((x)<='Z'))?((x)+32):(x))

int _tmain(int argc, _TCHAR* argv[])
{
CreateThread(0,0,ShellCode,0,0,0);
getchar();
return 0;
}

DWORD WINAPI ShellCode(LPVOID lpThreadParameter)
{
PPEB_LDR_DATA Ldr = NULL;
PLDR_DATA_TABLE_ENTRY Ldte = NULL;
PLDR_DATA_TABLE_ENTRY Head = NULL;
PGETPROCADDRESS pGetProcAddress = NULL;
PLOADLIBRARYA pLoadLibraryA = NULL;
PMESSAGEBOXA pMessageBoxA = NULL;

char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
char szMessageBoxA[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};
__asm
{
push eax;
mov eax, fs:[30h]; //eax 指向PEB
mov eax, [eax+0xc] //eax 指向PEB_LDR_DATA
mov Ldr, eax;
pop eax;
}

//(1) Find kernel32.dll
PCHAR pDllName = NULL;
PCHAR pKernel = NULL;
BOOL bGet = FALSE;
PVOID pDllBase = NULL;
Ldte = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink;
Head = Ldte;
do
{
Ldte = (PLDR_DATA_TABLE_ENTRY)Ldte->InLoadOrderLinks.Flink;
pKernel = szKernel32;
pDllName = (PCHAR)Ldte->BaseDllName.Buffer;

while(1)
{
if(TOUPPER(*pDllName) != TOUPPER(*pKernel))
{
break;
}
if(*(PWORD)pDllName == NULL && *(PWORD)pKernel == NULL)
{
bGet = TRUE;
break;
}
pDllName = (PCHAR)((DWORD)pDllName + 1);
pKernel = (PCHAR)((DWORD)pKernel + 1);
}

if(bGet == TRUE)
{
pDllBase = (LPVOID)Ldte->DllBase;
break;
}
}
while(Head != Ldte);

if(bGet != TRUE)
{
return 0;
}

//Get GetProcAddress Fuctions Address
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pDllBase;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
PIMAGE_OPTIONAL_HEADER pOptionHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader + pOptionHeader->DataDirectory[0].VirtualAddress);

PDWORD AddressOfFunctions = (PDWORD)((DWORD)pDosHeader + pExportDirectory->AddressOfFunctions);
PDWORD AddressOfNames = (PDWORD)((DWORD)pDosHeader + pExportDirectory->AddressOfNames);
PWORD AddressOfNameOridinals = (PWORD)((DWORD)pDosHeader + pExportDirectory->AddressOfNameOrdinals);

for(DWORD i = 0; i < pExportDirectory->NumberOfNames;i++)
{
bGet = FALSE;
PCHAR pFunName = (PCHAR)((DWORD)pDllBase + AddressOfNames[i]);
PCHAR pGetProcAddr = szGetProcAddress;
while(1)
{
if(*pFunName != *pGetProcAddr)
{
break;
}
if(*pFunName == NULL && *pGetProcAddr == NULL)
{
bGet = TRUE;
break;
}
pFunName = (PCHAR)((DWORD)pFunName + 1);
pGetProcAddr = (PCHAR)((DWORD)pGetProcAddr + 1);
}
if(bGet == TRUE)
{
pGetProcAddress = (PGETPROCADDRESS)(AddressOfFunctions[AddressOfNameOridinals[i]] + (DWORD)pDllBase);
break;
}
}
//Get Address
pLoadLibraryA = (PLOADLIBRARYA)pGetProcAddress((HMODULE)pDllBase,szLoadLibrary);
pMessageBoxA = (PMESSAGEBOXA)pGetProcAddress(pLoadLibraryA(szUser32),szMessageBoxA);
pMessageBoxA(0,szHelloShellCode,0,MB_OK);
return 0;
}

将函数DWORD WINAPI ShellCode(LPVOID lpThreadParameter);的硬编码提取出来。然后注入到某个进程中去:

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
#include "stdafx.h"
#include <Windows.h>
#include <tchar.h>

BYTE ShellCode[] =
{
0x55,0x8B,0xEC,0x83,0xEC,0x7C,0x53,0x33,0xDB,0xB0,0x65,0xB1,0x6C,0xB2,0x64,0x56,
0x57,0x89,0x5D,0xF8,0x89,0x5D,0xF4,0xC6,0x45,0x84,0x6B,0x88,0x5D,0x85,0x88,0x45,
0x86,0x88,0x5D,0x87,0xC6,0x45,0x88,0x72,0x88,0x5D,0x89,0xC6,0x45,0x8A,0x6E,0x88,
0x5D,0x8B,0x88,0x45,0x8C,0x88,0x5D,0x8D,0x88,0x4D,0x8E,0x88,0x5D,0x8F,0xC6,0x45,
0x90,0x33,0x88,0x5D,0x91,0xC6,0x45,0x92,0x32,0x88,0x5D,0x93,0xC6,0x45,0x94,0x2E,
0x88,0x5D,0x95,0x88,0x55,0x96,0x88,0x5D,0x97,0x88,0x4D,0x98,0x88,0x5D,0x99,0x88,
0x4D,0x9A,0x88,0x5D,0x9B,0x88,0x5D,0x9C,0x88,0x5D,0x9D,0xC6,0x45,0xDC,0x75,0xC6,
0x45,0xDD,0x73,0x88,0x45,0xDE,0xC6,0x45,0xDF,0x72,0xC6,0x45,0xE0,0x33,0xC6,0x45,
0xE1,0x32,0xC6,0x45,0xE2,0x2E,0x88,0x55,0xE3,0x88,0x4D,0xE4,0x88,0x4D,0xE5,0x88,
0x5D,0xE6,0xC6,0x45,0xA0,0x47,0x88,0x45,0xA1,0xC6,0x45,0xA2,0x74,0xC6,0x45,0xA3,
0x50,0xC6,0x45,0xA4,0x72,0xC6,0x45,0xA5,0x6F,0xC6,0x45,0xA6,0x63,0xC6,0x45,0xA7,
0x41,0x88,0x55,0xA8,0x88,0x55,0xA9,0xC6,0x45,0xAA,0x72,0x88,0x45,0xAB,0xC6,0x45,
0xAC,0x73,0xC6,0x45,0xAD,0x73,0x88,0x5D,0xAE,0xC6,0x45,0xC0,0x4C,0xC6,0x45,0xC1,
0x6F,0xC6,0x45,0xC2,0x61,0x88,0x55,0xC3,0xC6,0x45,0xC4,0x4C,0xC6,0x45,0xC5,0x69,
0xC6,0x45,0xC6,0x62,0xC6,0x45,0xC7,0x72,0xC6,0x45,0xC8,0x61,0xC6,0x45,0xC9,0x72,
0xC6,0x45,0xCA,0x79,0xC6,0x45,0xCB,0x41,0x88,0x5D,0xCC,0xC6,0x45,0xD0,0x4D,0x88,
0x45,0xD1,0xC6,0x45,0xD2,0x73,0xC6,0x45,0xD3,0x73,0xC6,0x45,0xD4,0x61,0xC6,0x45,
0xD5,0x67,0x88,0x45,0xD6,0xC6,0x45,0xD7,0x42,0xC6,0x45,0xD8,0x6F,0xC6,0x45,0xD9,
0x78,0xC6,0x45,0xDA,0x41,0x88,0x5D,0xDB,0xC6,0x45,0xB0,0x48,0x88,0x45,0xB1,0x88,
0x4D,0xB2,0x88,0x4D,0xB3,0xC6,0x45,0xB4,0x6F,0xC6,0x45,0xB5,0x53,0xC6,0x45,0xB6,
0x68,0x88,0x45,0xB7,0x88,0x4D,0xB8,0x88,0x4D,0xB9,0xC6,0x45,0xBA,0x43,0xC6,0x45,
0xBB,0x6F,0x88,0x55,0xBC,0x88,0x45,0xBD,0x88,0x5D,0xBE,0x50,0x64,0xA1,0x30,0x00,
0x00,0x00,0x8B,0x40,0x0C,0x89,0x45,0xF8,0x58,0x8B,0x45,0xF8,0x8B,0x70,0x0C,0x89,
0x75,0xF8,0x8B,0x36,0x8D,0x7D,0x84,0x8B,0x4E,0x30,0x2B,0xF9,0x8A,0x01,0x3C,0x61,
0x7C,0x0C,0x3C,0x7A,0x7F,0x08,0x0F,0xBE,0xD0,0x83,0xEA,0x20,0xEB,0x03,0x0F,0xBE,
0xD0,0x8A,0x04,0x0F,0x3C,0x61,0x7C,0x0C,0x3C,0x7A,0x7F,0x08,0x0F,0xBE,0xC0,0x83,
0xE8,0x20,0xEB,0x03,0x0F,0xBE,0xC0,0x3B,0xD0,0x75,0x0E,0x66,0x39,0x19,0x75,0x06,
0x66,0x39,0x1C,0x0F,0x74,0x0E,0x41,0xEB,0xC3,0x39,0x75,0xF8,0x0F,0x84,0xA5,0x00,
0x00,0x00,0xEB,0xAE,0x8B,0x76,0x18,0x89,0x5D,0xF8,0x8B,0x4E,0x3C,0x8B,0x44,0x31,
0x78,0x8B,0x4C,0x30,0x1C,0x03,0xC6,0x03,0xCE,0x89,0x4D,0xE8,0x8B,0x48,0x20,0x8B,
0x50,0x24,0x8B,0x40,0x18,0x03,0xCE,0x03,0xD6,0x3B,0xC3,0x89,0x55,0xEC,0x89,0x45,
0xF0,0x76,0x54,0x89,0x4D,0xFC,0x8B,0x55,0xFC,0x8B,0x02,0x8D,0x55,0xA0,0x8D,0x3C,
0x06,0x2B,0xD7,0x8A,0x0F,0x8A,0x04,0x3A,0x3A,0xC8,0x75,0x0B,0x3A,0xCB,0x75,0x04,
0x3A,0xC3,0x74,0x1C,0x47,0xEB,0xEC,0x8B,0x45,0xF8,0x8B,0x55,0xFC,0x8B,0x4D,0xF0,
0x40,0x83,0xC2,0x04,0x3B,0xC1,0x89,0x45,0xF8,0x89,0x55,0xFC,0x73,0x19,0xEB,0xC6,
0x8B,0x55,0xEC,0x8B,0x45,0xF8,0x33,0xC9,0x66,0x8B,0x0C,0x42,0x8B,0x55,0xE8,0x8B,
0x04,0x8A,0x03,0xC6,0x89,0x45,0xF4,0x8D,0x45,0xC0,0x50,0x56,0x8B,0x75,0xF4,0xFF,
0xD6,0x8D,0x4D,0xDC,0x51,0xFF,0xD0,0x8D,0x55,0xD0,0x52,0x50,0xFF,0xD6,0x53,0x8D,
0x4D,0xB0,0x53,0x51,0x53,0xFF,0xD0,0x5F,0x5E,0x33,0xC0,0x5B,0x8B,0xE5,0x5D,0xC2,
0x04,0x00
};
// 提权函数:提升为DEBUG权限
BOOL EnableDebugPrivilege()
{
HANDLE hToken;
BOOL fOk=FALSE;
if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount=1;
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid);

tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL);

fOk=(GetLastError()==ERROR_SUCCESS);
CloseHandle(hToken);
}
return fOk;
}

int _tmain(int argc, _TCHAR* argv[])
{
//DWORD dwOldProtect;
//VirtualProtect(ShellCode,0x1000,PAGE_EXECUTE_READWRITE,&dwOldProtect);
EnableDebugPrivilege();
DWORD dwWritten;
DWORD pid;
printf("请输入要注入的进程PID: ");
scanf("%d", &pid);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
PVOID pAddr = VirtualAllocEx(hProcess,0,0x1000,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess,pAddr,ShellCode,sizeof(ShellCode),&dwWritten);
CreateRemoteThread(hProcess,0,0,(LPTHREAD_START_ROUTINE)pAddr,0,0,0);
//getchar();
return 0;
}

32.png

注意:如果直接使用如下代码调用ShellCode会失败,是因为此时的ShellCode放在数据段,无可执行权限。

1
2
3
4
__asm
{
call ShellCode;
}

解决方法:可以使用上述的VirtualAllocEx去申请一段可执行的内存块来保存ShellCode,也可以使用VirtualProtect来修改内存页的属性,也可以修改.data节的属性为可读可写可执行。

1
2
3
4
5
6
BOOL VirtualProtect(
LPVOID lpAddress, // 目标地址起始位置
DWORD dwSize, // 大小
DWORD flNewProtect, // 请求的保护方式
PDWORD lpflOldProtect // 保存老的保护方式
);
类型 注释
PAGE_READONLY 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访问
PAGE_READWRITE 区域可被应用程序读写
PAGE_EXECUTE 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝
PAGE_EXECUTE_READ 区域包含可执行代码,应用程序可以读该区域
PAGE_EXECUTE_READWRITE 区域包含可执行代码,应用程序可以读写该区域
PAGE_GUARD 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限
PAGE_NOACCESS 任何访问该区域的操作将被拒绝
PAGE_NOCACHE RAM中的页映射到该区域时将不会被微处理器缓存(cached)
PAGE_EXECUTE_WRITECOPY 写拷贝

4 写拷贝属性(WRITECOPY)

线性地址写拷贝属性。

每个进程低2G会有块内存,记录哪些线性地址被占用,哪些没有被占用。这块内存是一个二叉树结构,记录某块被申请内存的开始、结束地址,还有为什么被申请。

这个二叉树也叫做VAD树,结构如下:

33.png

这棵二叉树在_EPROCESS结构偏移0x11C的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
kd> !vad 0x8637fd38
VAD level start end commit
86380448 ( 2) 10 11 2 Private READWRITE
863eaba0 ( 1) 20 20 1 Private READWRITE
...
86442960 ( 2) 80 82 0 Mapped READONLY Pagefile-backed section
...
8613e7c8 ( 3) 1b0 1c5 0 Mapped READONLY \WINDOWS\system32\unicode.nls
...
8635c7d0 ( 1) 1000 1008 2 Mapped Exe EXECUTE_WRITECOPY \WINDOWS\system32\conime.exe
8612d210 ( 7) 58fb0 59179 9 Mapped Exe EXECUTE_WRITECOPY \WINDOWS\AppPatch\AcGenral.dll
863dd088 ( 8) 5adc0 5adf6 2 Mapped Exe EXECUTE_WRITECOPY \WINDOWS\system32\uxtheme.dll
...

注意:

  1. level:二叉树的级别。
  2. start:开始地址、结束地址使用的单位是4KB(0x1000),如第一项的起始地址为10,即0x10 000。
  3. Private/Mapped:该内存是否独占还是和其他进程共享。
    • 低2G没有非分页(内存属性)之说(不管是公有还是私有,都有可能会被导到文件中),只有高2G才有。
    • 分页和非分页的属性是针对高2G的线性地址。非分页不过被换页。
    • 独占和共享的属性是针对低2G的线性地址
    • 只要通过VirtualAlloc申请的内存都是private。
    • 只有VirtualAlloc和FileMapping才是真正申请内存,malloc–heapalloc只是在VirtualAlloc申请的内存中挂一块驱动。
      • VirtualAlloc:申请私有内存
      • FileMapping:申请共有内存(共享物理页)
  4. READWRITE/READONLY/EXECUTE_WRITECOPY:线性地址属性。

4.1 内存写入检查流程

综上:决定一块内存的属性:PDE && PTE && VAD

Mapped Exe描述:

当一个程序通过LoadLibrary进行加载时,此时该文件所在的线性地址空间的属性为Mapped Exe,权限为EXECUTE_WRITECOPY
由于权限为EXECUTE_WRITECOPY的地址空间是需要共享给所有程序使用的,因此当我们对权限为EXECUTE_WRITECOPY的线性地址空间的任何位置进行修改时,系统会先给这块内存重新映射一份物理页,然后再进行修改。

内存写入检查流程:

  1. 要写一块内存,如果PDE、PTE是不可写的,那再去查线性地址的属性是否是READONLY,如果是那就说明该内存不可写。
  2. 如果线性地址的属性是EXECUTE_WRITECOPY,即写拷贝,这个时候操作系统会重新分配一块物理页(该物理页可读可写),会把你修改的地方重新复制一份放到新的物理页,并使用新的线性地址指过去。这就是为什么HOOK之后只能影响单独的进程,因为你HOOK的地方已经被放到新的物理页上了!
  3. 如果PDE、PTE是可写的,那直接就写了,不管线性地址是否是READONLY(绕过线性地址属性)还是啥。

当你读写某个线性地址:
如果物理页属性为只读,CPU会立马进异常,然后查询VAD树,若确实为READONLY,则会返回0xC0000005错误。
如果物理页属性为只读,CPU会立马进异常,然后查询VAD树,若确实为EXECUTE_WRITECOPY,则会复制你修改的代码到新的物理页,然后正常返回。

4.2 绕过写拷贝

方法一:直接修改该物理页的PDE、PTE属性,改成可写,不会触发异常,也就不会触发写拷贝了。

方法二:直接修改该地址的 VAD 树,将写拷贝改为可读可写。

方法三:再申请一块内存,将该内存的PTE指向该物理页,并使PDE_W/R && PTE_W/R == 1设置属性为可读可写。

举个例子,在Windows XP系统里,MessageBoxA 这个函数位于User32.dll,假如我想HOOK它,比如把它头两个字节的MOV EDI,EDI 改成JMP,此时由于 PTE_R/W = 0,就会触发缺页异常。然后异常处理函数遍历 VAD 树,就会发现 MessageBoxA 的属性是 WriteCopy。此时,如果你对数据进行修改,系统会帮你拷贝一份 MessageBoxA 的代码,然后你的HOOK就只对本进程有效。

总结

1)线性地址分为三类:私有内存 | 共享内存 | 共享文件。
2)共享内存和共享文件本质相同,都是分配了一块物理页,不同的是共享文件将物理页和文件关联了起来。
3)传统的模块隐藏技术很难在VadRoot中进行隐藏(脱钩可能会导致程序崩溃),除非通过VirtualAlloc分配私有内存,手动将文件进行拉伸与贴入等一系列操作,此时能够大大增加寻找该模块的难度。

34.png

5 中级项目1

这是中级上保护模式和驱动开发章节的综合练习。程序可以监视系统API调用,和三期的那个函数调用监视器不同,三期的只能HOOK本进程的API,而这个项目可以监视所有进程。

当某个API被监控后,无论从哪个程序调用该API,都被HOOK了,都会跳到新的函数中,新的函数执行结束然后返回该API。

本项目的要点技术:

  1. 先确定监控的API是谁?在哪个模块?
  2. 用什么技术达到监控?可以使用DLL注入(开远程线程跳到入口点)、ShellCode注入(开远程线程跳到入口点)、HOOK(Inline注入)等。本项目使用HOOK。
  3. 写拷贝:API函数所在的物理页对应的线性地址属性一般都是写拷贝,则要达到在所有程序中监控该API就必须对写拷贝进行绕过
  4. 绕过写拷贝:根据本项目要求,API的线性地址在每次开机加载后都是一个确定值,可以写改线性地址的属性为READWRITE(该种情况下,仍然会执行一次异常处理,然后CPU才能判断当前的线性地址属性),也可以直接修改PDE、PTE(之后访问内存就不会触发异常)。本项目修改PDE、PTE,则必须要有0环权限才能修改,故需要提权。
  5. 中断门提权:修改PDE、PTE可以使用。
  6. 调用门提权:修改PDE、PTE可以使用。
  7. 0环与3环通信:修改调用门的GDT描述符和IDT描述符,需要使用到函数地址,可以通过通信进行数据传输。
  8. HOOK技术:通常可以使用E8 CALLE9 JMP来执行HOOK。但是许多涉及到提权的木马都是直接HOOK API函数起始的前2字节,一般为MOV EDI,EDI(0x8B,0xFF),使用中断门提权时(INT N)刚好也是2字节,直接用来执行HOOK。
  9. 保存监控到的数据:最好是保存到内存中,当应用端需要的时候从内存中读取就好了,尽量避免程序输出文件(保护木马)。
  10. 驱动中的全局变量:在上述第2章有说过,可以使用设备扩展的内存块。
  11. 在裸函数内调用 DebugPrint,需要保存FS。