# 简单操作系统驱动及安全

# 一。配置和环境

  • 系统 windows XP

  • 安装 Windows SDK & DDK

  • 下载 DriverMonitor.exe(将文件加载到内核)

  • 下载 DirverView.exe(观察驱动运行结果)

# 二. Windows XP 平台下的驱动框架进行说明。

# 1. Windows XP 驱动框架。

Windows XP 驱动不同于普通的 C 应用态程序。C 应用态程序的入口是 main 函数,但是驱动中没有 main 函数,Windows XP 驱动入口函数称为 “DriverEntry” 的函数。在驱动加载后,第一个运行的就是 DriverEntry 函数,因此,所有需要初始化的工作都是在 DriverEntry 函数当中完成的。接下来,在初始化完成之后,DriverEntry(通过调用其他的功能函数)将控制权交给功能函数以完成必要的驱动功能。最后,当驱动工作完成之后,必须退出以释放内核空间,这是通过调用 “驱动退出函数” 实现的。与 Windows XP 驱动一定有一个固定的、称为 “DriverEntry” 的入口函数不同,“驱动退出函数” 的名称是不定的,具体名称是什么,由 “DriverEntry” 中的 “driver->DriverUnload” 语句指定函数名。具体到本实验的例子(参见如下 Encryption.c 源码),有语句 “driver->DriverUnload = DriverUnload;”,因此在本例中,驱动的退出函数名为 “DriverUnload”。

综上,Windows XP 下的驱动框架部分需要掌握以下几点:

  • 首先,驱动的入口函数称为 “DriverEntry”,在 “DriverEntry” 中完成驱动的所有初始化工作;

  • 之后,“DriverEntry”(通过调用其他功能函数)将控制权交给其他功能函数以完成驱动所需的功能。这个过程和用户态 C 语言的子函数调用是类似的;

  • 最后,在 “DriverEntry” 使用 “driver->DriverUnload = XXXX;” 语句指定驱动退出函数。上例中 XXXX 就是驱动退出函数的函数名,由用户进行编写后指定。当驱动程序退出时,会自动调用驱动退出函数以完成必要的终止和回收工作。

# 2. Windows 驱动编译。

驱动的编译可以采用 DDK 环境,也可以采用 VC 环境。本次实验采用 DDK 环境进行编译(感兴趣的同学可以搜索如何以 VC6.0 环境编译驱动。两者编译的结果是一样的,没有差别)。利用 DDK 环境编译驱动的具体过程如下:

首先在源程序的相同目录下创建两个文本文件 makefile 和 Sources。这两个文本文件描述了 DDK 驱动程序的源文件、用到的 lib 文件、inlcude 路径名以及编译输出的目录和文件名等信息,具体示例如下。

img

表 1 当中,第 1 行说明驱动的名称为 Encrytion(驱动名根据需要修改);第 2 行指明此驱动的类型为 NT 型驱动;第 3 行设置编译输出目录;第 4 行指定编译原文件名为 Encryption.c(驱动源文件根据需要修改)。

Makefile 文件内容如下:

img

编写完这两个脚本之后,在 Windows 的开始菜单中选择 “Windows XP Checked Build Environment” 编译环境,启动运行,进入到 cmd 命令界面。实验时选择 Checked 版本,而不是 Free 版本,两者的区别类似于 Win32 程序开发的 Debug 版本和 Release 版本。

最后,在进入命令行方式的 cmd 窗口之后,用 cd 命令进入需要编译的目录,然后输入 “build” 命令,DDK 的编译环境就会自动调用编译器进行编译。如果编译出错,对应修改源文件即可;编译成功之后,会在 obj 目录中生成 Encryption.sys 文件。一旦成功生成了.sys 文件,即可使用附带的 instdrv.exe 将驱动 Encryption.sys 加载到内核运行。

综上,Windows XP 驱动编译需要掌握如下几点:

  • Windows XP 驱动可以采用 DDK 环境编译,也可以采用 VC 环境编译。两者的编译结果是一样的。

  • 采用 DDK 环境编译时,需要在驱动程序所在的目录下创建 makefile 和 Sources 文件,具体的写法参见表 1。

  • l 驱动编译的结果是.sys 文件。

# 3. Windows 驱动安装与运行。

Windows 驱动编译生成.sys 文件。.sys 文件是不能直接运行的,必须要插入到内核当中。本实验当中,采用 DriverMonitor.exe 工具将其插入(当然选用其他工具也可以)。其步骤如下:

  • 首先下载 DriverMonitor.exe 文件并安装到本机。

  • 运行 DriverMonitor.exe。点击”Edit“,添加文件。

  • 打开编译得到的.sys 文件。

# 4. 驱动信息查看。

以上完成了驱动的加载,查看驱动运行信息(驱动中可以使用 DbgPrint 来打印一些信息,方便调试)可以采用 DbgView.exe 工具,运行之后,勾选 “Capture Kernel” 捕获内核信息即可。

以下是源码:

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
#define ENCRYPTION
//#define DECRYPTION
#include <Ntddk.h>
NTSTATUS Encryption();
VOID EnCode(PUCHAR pBuffer, SIZE_T bufferSize);
VOID DeCode(PUCHAR pBuffer, SIZE_T bufferSize);
VOID DriverUnload(PDRIVER_OBJECT driver);

//Driver Entry
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
HANDLE hfile;
PUCHAR pBuffer;
NTSTATUS ntStatus;
/*
#if DBG
_asm int 3
#endif
*/
/*
内核打印需要使用DbgPrint函数。注意在内核中是不能使用应用态的库函数的,
因此,这里不能用printf,而只能使用内核专有的DbgPrint。
*/
DbgPrint("Entering OS Kernel!\r\n");
/*
DbgPrint("The Driver name is: %wZ\n", driver->DriverName);
DbgPrint("The Driver size is: %ld\n", driver->DriverSize);
DbgPrint("The Driver start address: %x\n", driver->DriverStart);
*/
ntStatus = Encryption();
//Driver Exit
driver->DriverUnload = DriverUnload;
return ntStatus;
}
VOID DriverUnload(PDRIVER_OBJECT driver)
{
DbgPrint("Leaving OS Kernel! Bye, Good luck!\r\n");
}
//文件读写操作
NTSTATUS Encryption()
{
HANDLE hfile;
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK ioStatus;
UNICODE_STRING logFileUnicodeString;
NTSTATUS ntStatus;
FILE_STANDARD_INFORMATION fsi;
PUCHAR pBuffer, tempPBuffer;
SIZE_T fileSize;
WCHAR *file2En = L"\\??\\D:\\TEDP_DEV\\TEST\\1.doc"; //读取此文件内容,之后对其加密
WCHAR *enFile = L"\\??\\D:\\TEDP_DEV\\TEST\\Encrypted.doc"; //加密以后写入到此文件
//初始化要读取文件的UNICODE_STRING字符串
RtlInitUnicodeString(&logFileUnicodeString, file2En);
//RtlInitUnicodeString(&logFileUnicodeString, &fileName);
//初始化要读取文件的objectAttributes
InitializeObjectAttributes(&objectAttributes,
&logFileUnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
//打开要读取的文件
ntStatus = ZwCreateFile(&hfile,
GENERIC_READ,
&objectAttributes,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS(ntStatus))
{
KdPrint(("Open read-file failed!\n"));
return ntStatus;
}
else
KdPrint(("Open read-file successfully!\n"));
//查询要读取文件的相关信息,以便读取
ntStatus = ZwQueryInformationFile(hfile,
&ioStatus,
&fsi,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
if (!NT_SUCCESS(ntStatus))
{
KdPrint(("Query of read-file failed!\n"));
return ntStatus;
}
//本来fsi.EndofFile.QuadPart是LONGLONG类型,但是,一个文件不会有64位大。32位已有4G,这里转为SIZE_T
fileSize = (SIZE_T)fsi.EndOfFile.QuadPart;
KdPrint(("Totally %ld bytes will be read\n", fileSize));
//为读取的文件分配缓冲区,在本程序中该缓冲区被释放
pBuffer = (PUCHAR)ExAllocatePoolWithTag(PagedPool, fileSize, 'Tag1');
if (pBuffer == NULL)
{
KdPrint(("Allocation memory for reading file failed!\n"));
return ntStatus; //?
}
else
tempPBuffer = pBuffer;
//读取文件内容
ntStatus = ZwReadFile(hfile,
NULL,
NULL,
NULL,
&ioStatus,
pBuffer,
fileSize,
NULL,
NULL);

if (!NT_SUCCESS(ntStatus))
{
KdPrint(("Open read-file failed!\n"));
ZwClose(hfile);
ExFreePool(pBuffer);
return ntStatus;
}
KdPrint(("Finally %ld bytes were read\n", ioStatus.Information));
//关闭被读取的文件
ZwClose(hfile);

//写入加密文件
//初始化加密文件的UNICODE_STRING字符串
RtlInitUnicodeString(&logFileUnicodeString, enFile);
//RtlInitUnicodeString(&logFileUnicodeString, &fileName);
//初始化objectAttributes
InitializeObjectAttributes(&objectAttributes,
&logFileUnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
//打开写文件以便写
ntStatus = ZwCreateFile(&hfile,
GENERIC_WRITE, //准备写文件
&objectAttributes,
&ioStatus,
NULL, //没有指定初始化分配的文件空间大小
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ, //其他进程不能写该文件
FILE_OPEN_IF, //创建文件,如果文件已存在的话,则清空打开
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);

if (!NT_SUCCESS(ntStatus))
{
KdPrint(("Open write-file failed!\n"));
ExFreePool(pBuffer);
return ntStatus;
}
KdPrint(("Create file successfully!\n"));
//对pBuffer的内容进行加密
#ifdef ENCRYPTION
EnCode(pBuffer, fileSize);
#endif
//开始写入文件
ntStatus = ZwWriteFile(hfile,
NULL,
NULL,
NULL,
&ioStatus,
pBuffer,
fileSize,
NULL,
NULL);
if (!NT_SUCCESS(ntStatus))
{
KdPrint(("Write to write-file failed!\n"));
ExFreePool(pBuffer);
ZwClose(hfile);
return ntStatus;
}
else
{
KdPrint(("Write to write-file successfully!\n"));
ExFreePool(pBuffer);
ZwClose(hfile);
return ntStatus;
}
}
//加密操作。需要替换为DES加密函数
VOID EnCode(PUCHAR pBuffer, SIZE_T bufferSize)
{
int index = 0;
while(index++ != bufferSize)
{
*pBuffer ^= 'z';
pBuffer++;
}
}
//解密操作。需要替换为DES解密函数
VOID DeCode(PUCHAR pBuffer, SIZE_T bufferSize)
{
int index = 0;
while(index++ != bufferSize)
{
*pBuffer ^= 'z';
pBuffer++;
}
}

# (1)将上述驱动编译成功。

使用 Windows XP x86 Checked Build Enviroment 进行编译:

img

img

编译后产生的 sys 文件(系统文件):

img

# (2)将编译成功的驱动插入到内核运行,并观察运行结果。

使用 DriverMonitor.exe 将 sys 文件加载到内核运行:

img

然后使用 DirverView.exe 观察驱动运行结果:

img

原文件内容:

img

加密后的文件:

img