3、文件读写
在ring3 我们可以使用CreateFile、ReadFile 、WriteFile 等API,在ring0 同样很相似,不过函数变成了ZwCreateFile、ZwReadFile、ZwWriteFile 等内核函数。
1)ZwCreateFile与ring3的CreateFile函数有所不同,它不能直接将需要打开或创建的文件路径传递过去,我们必须首先填写一个OBJECT_ATTRIBUTES结构。
UNICODE_STRING str;
OBJECT_ATTRIBUTES obj_attrib;
RtlInitUnicodeString(&str, L"\\??\\C:\\windows\\notepad.exe");
InitializeObjectAttributes(&obj_attrib,
&str, // 需要操作的对象、比如文件或注册表路径等
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
第三个参数OBJ_CASE_INSENSITIVE 表示不区分大小写,OBJ_KERNEL_HANDLE
表示将要打开的句柄为内核句柄。内核句柄比起应用层句柄有很多的好处,例如它可以不受进程或线程的限制,而且在需要打开一个内核句柄时不需要考虑当前是否有权限访问该文件的问题。
2)创建、打开文件
创建和打开文件都可使用ZwCreateFile 函数,它的第一个参数将返回一个文件句柄,所有后续操作都可以通过这个句柄完成,在操作结束后,需要调用ZwClose 关闭句柄。
http://msdn.microsoft.com/en-us/library/ff566424%28VS.85%29.aspx
ZwCreateFile 函数的第三个参数就是使用我们此前填写的OBJECT_ATTRIBUTES 结构;它返回的信息通过第四个IO_STATUS_BLOCK 返回;第八、九个参数联合指明了如何打开或创建文件,其中IO_STATUS_BLOCK的定义如下所示:
typedef struct _IO_STATUS_BLOCK {
NTSTATUS Status;
ULONG Information;
}IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
http://msdn.microsoft.com/en-us/library/ff550671%28VS.85%29.aspx
其中Status指明了函数的执行结果,如果执行成功它的值将是STATUS_SUCCESS ,否
则它将会是一个形如STATUS_XXX 的错误提示。
此外,DDK 还提供了一个函数ZwOpenFile 用来简化打开文件的操作,它所需要的参数
比ZwCreateFile 更加简洁,使用更加简单。
3)打开文件
在内核中读写文件与用户模式下十分相似,它们分别使用ZwReadFile 和ZwWriteFile函数完成。
NTSTATUS
ZwReadFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL);
各参数的简要介绍如下所示:
FileHandle:函数ZwCreateFile 返回的句柄。如果它是一个内核句柄,则ZwReadFile 和
ZwCreateFile 并不需要在同一个进程中,因为内核句柄是各进程通用的。
Event :一个事件,用于异步完成读时;我们忽略这个参数。
ApcRoutine Apc:回调例程,用于异步完成读时;我们忽略这个参数。
IoStatusBlock:返回结果状态,与ZwCreateFile 中的同名参数相同。
Buffer:缓冲区,如果读取文件的内容成功,则内容将被读取到这里。
Length:描述缓冲区的长度,即试图读取文件的长度。
ByteOffset:要读取的文件的偏移量,也就是要读取的内容在文件中的位置。一般来说,不要将其设置为NULL,文件句柄不一定支持直接读取当前偏移。
Key:读取文件时用的一种附加信息,一般不使用。
当函数执行成功时返回STATUS_SUCCESS,实际上只要能够读取到任意字节的数据(不管它是否符合参数Length 的要求),都返回成功;但是,如果仅读取文件长度之外的部分,则返回STATUS_END_OF_FILE。ZwWriteFile 的参数与ZwReadFile 基本相同。
4)其它操作
ZwQueryInformationFile、ZwSetInformationFile可以分别用来获取和设置文件属性,包括文件大小、文件指针位置、文件属性(如只读、隐藏)、文件创建/修改日期等。
ZwSetInformationFile 函数:
NTSTATUS
ZwSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
FileInformationClass指定修改或查询的类别,它可能的值有很多种
http://msdn.microsoft.com/en-us/library/ff567096%28VS.85%29.aspx
在内核模式下操作文件的函数不像用户模式下那样丰富,想复制文件就调用CopyFile、想删除文件就调用DeleteFile等,在内核模式下除了读写文件的其他所有操作都是通过这两个ZwQueryInformation和ZwSetInformationFile 函数完成的,而如何使这两个函数精确完成我们需要的功能,就需要通过FileInformationClass参数来指定。
5)一个例子如下:
声明.h:
1 BOOLEAN
2
3 MyCopyFile(IN PUNICODE_STRING ustrDestFile,
4
5 IN PUNICODE_STRING ustrSrcFile);
6
7 .cpp定义
8
9 DriverEntry函数中
10
11 UNICODE_STRING ustrSrcFile, ustrDestFile;
12
13 RtlInitUnicodeString( & ustrSrcFile, L " \\??\\C:\\windows\\notepad.exe " );
14
15 RtlInitUnicodeString( & ustrDestFile, L " \\??\\C:\\notepad.exe " );
16
17 if (MyCopyFile( & ustrDestFile, & ustrSrcFile))
18
19 {
20
21 KdPrint(( " [Test] CopyFile Success! " ));
22
23 }
24
25 else
26
27 {
28
29 KdPrint(( " [Test] CopyFile Error! " ));
30
31 }
32
33 return status;
34
35 }
36
37 自定义函数:
38
39 /* ***********************************************************************
40
41 * 函数名称:MyCopyFile
42
43 * 功能描述:复制文件
44
45 * 参数列表:
46
47 ustrDestFile:目的文件
48
49 ustrSrcFile:源文件
50
51 * 返回 值:返回状态
52
53 ************************************************************************ */
54
55 BOOLEAN
56
57 MyCopyFile( IN PUNICODE_STRING ustrDestFile,
58
59 IN PUNICODE_STRING ustrSrcFile)
60
61 {
62
63 HANDLE hSrcFile, hDestFile;
64
65 PVOID buffer = NULL;
66
67 ULONG length = 0 ;
68
69 LARGE_INTEGER offset = { 0 };
70
71 IO_STATUS_BLOCK Io_Status_Block = { 0 };
72
73 OBJECT_ATTRIBUTES obj_attrib;
74
75 NTSTATUS status;
76
77 BOOLEAN bRet = FALSE;
78
79 do
80
81 { // 打开源文件
82
83 InitializeObjectAttributes( & obj_attrib,
84
85 ustrSrcFile,
86
87 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
88
89 NULL,
90
91 NULL);
92
93 status = ZwCreateFile( & hSrcFile,
94
95 GENERIC_READ,
96
97 & obj_attrib,
98
99 & Io_Status_Block,
100
101 NULL,
102
103 FILE_ATTRIBUTE_NORMAL,
104
105 FILE_SHARE_READ,
106
107 FILE_OPEN,
108
109 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
110
111 NULL, 0 );
112
113 if ( ! NT_SUCCESS(status))
114
115 {
116
117 bRet = FALSE;
118
119 goto END;
120
121 }
122
123 // 打开目标文件
124
125 InitializeObjectAttributes( & obj_attrib,
126
127 ustrDestFile,
128
129 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
130
131 NULL,
132
133 NULL);
134
135 status = ZwCreateFile( & hDestFile,
136
137 GENERIC_WRITE,
138
139 & obj_attrib,
140
141 & Io_Status_Block,
142
143 NULL,
144
145 FILE_ATTRIBUTE_NORMAL,
146
147 FILE_SHARE_READ,
148
149 FILE_OPEN_IF,
150
151 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
152
153 NULL, 0 );
154
155 if ( ! NT_SUCCESS(status))
156
157 {
158
159 bRet = FALSE;
160
161 goto END;
162
163 }
164
165 // 为buffer 分配4KB 空间
166
167 buffer = ExAllocatePool(NonPagedPool, 1024 * 4 );
168
169 if (buffer == NULL)
170
171 {
172
173 bRet = FALSE;
174
175 goto END;
176
177 }
178
179 // 复制文件
180
181 while ( 1 )
182
183 {
184
185 length = 4 * 1024 ;
186
187 // 读取源文件
188
189 status = ZwReadFile(hSrcFile,
190
191 NULL,
192
193 NULL,
194
195 NULL,
196
197 & Io_Status_Block,
198
199 buffer,
200
201 length,
202
203 & offset,
204
205 NULL);
206
207 if ( ! NT_SUCCESS(status))
208
209 {
210
211 // 如果状态为STATUS_END_OF_FILE,说明文件已经读取到末尾
212
213 if (status == STATUS_END_OF_FILE)
214
215 {
216
217 bRet = TRUE;
218
219 goto END;
220
221 }
222
223 }
224
225 // 获得实际读取的长度
226
227 length = Io_Status_Block.Information;
228
229 // 写入到目标文件
230
231 status = ZwWriteFile(hDestFile,
232
233 NULL,
234
235 NULL,
236
237 NULL,
238
239 & Io_Status_Block,
240
241 buffer,
242
243 length,
244
245 & offset,
246
247 NULL);
248
249 if ( ! NT_SUCCESS(status))
250
251 {
252
253 bRet = FALSE;
254
255 goto END;
256
257 }
258
259 // 移动文件指针
260
261 offset.QuadPart += length;
262
263 }
264
265 } while ( 0 );
266
267 END:
268
269 if (hSrcFile)
270
271 {
272
273 ZwClose(hSrcFile);
274
275 }
276
277 if (hDestFile)
278
279 {
280
281 ZwClose(hDestFile);
282
283 }
284
285 if (buffer = NULL)
286
287 {
288
289 ExFreePool(buffer);
290
291 }
292
293 return bRet;
294
295 }
6)创建文件
NTSTATUS ZwCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttribute,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG createOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength);
FileHandle:是一个句柄的指针。如果这个函数调用返回成成功(STATUS_SUCCESS),那就么打开的文件句柄就返回在这个地址内。
DesiredAccess:申请的权限。打开写文件用FILE_WRITE_DATA,读文件内容用FILE_READ_DATA,删除文件或者把文件改名用DELETE,想设置文件属性,请使用FILE_WRITE_ATTRIBUTES,读文件属性则使用 FILE_READ_ATTRIBUTES。这些条件可以用|(位或)来组合。有两个宏分别组合了常用的读权限和常用的写权限,为GENERIC_READ和GENERIC_WRITE,宏 GENERIC_ALL代表全部权限。如果想同步的打开文件,加上SYNCHRONIZE。
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
实际编程中很少用到Pointer。返回的结果在Status中。成功则为STATUS_SUCCESS。否则则是一个错误码。进一步的信息在 Information中。不同的情况下返回的Information的信息意义不同。针对ZwCreateFile调用的情况,Information 的返回值有以下几种可能:
·FILE_CREATED:文件被成功的新建了。
·FILE_OPENED: 文件被打开了。
·FILE_OVERWRITTEN:文件被覆盖了。
·FILE_SUPERSEDED: 文件被替代了。
·FILE_EXISTS:文件已存在。(因而打开失败了)。
·FILE_DOES_NOT_EXIST:文件不存在。(因而打开失败了)。
这些返回值和打开文件的意图有关(有时希望打开已存在的文件,有时则希望建立新的文件等。
AllocationSize参数很少使用,请设置为NULL。
FileAttributes控制新建立的文件的属性,设为FILE_ATTRIBUTE_NORMAL。
ShareAccess,实际上,这是在本代码打开这个文件的时候,允许别的代码同时打开这个文件所持有的权限。所以称为共享访问。一共有三种共享标记可以设置:FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_DELETE。这三个标记可以用|(位或)来组合。举例如下:如果本次打开只使用了 FILE_SHARE_READ,那么这个文件在本次打开之后,关闭之前,别次打开试图以读权限打开,则被允许,可以成功打开。如果别次打开试图以写权限打开,则一定失败。返回共享冲突。
CreateDisposition参数说明了这次打开的意图。可能的选择如下(这些选择不能组合):
·FILE_CREATE:新建文件。如果文件已经存在,则这个请求失败。
·FILE_OPEN:打开文件。如果文件不存在,则请求失败。
·FILE_OPEN_IF:打开或新建。如果文件存在,则打开。如果不存在,则失败。
·FILE_OVERWRITE:覆盖。如果文件存在,则打开并覆盖其内容。如果文件不存在,这个请求返回失败。
·FILE_OVERWRITE_IF:新建或覆盖。如果要打开的文件已存在,则打开它,并覆盖其内存。如果不存在,则简单的新建新文件。
·FILE_SUPERSEDE:新建或取代。如果要打开的文件已存在。则生成一个新文件替代之。如果不存在,则简单的生成新文件。
CreateOption, 笔者使用FILE_NON_DIRECTORY_FILE| FILE_SYNCHRONOUS_IO_NONALERT,此时文件被同步的打开。而且打开的是文件(而不是目录。创建目录请用FILE_ DIRECTORY_FILE)。同步的打开的意义在于,以后每次操作文件的时候,比如写入文件,调用ZwWriteFile,在ZwWriteFile 返回时,文件写操作已经得到了完成。而不会有返回STATUS_PENDING(未决)的情况。在非同步文件的情况下,返回未决是常见的。此时文件请求没有完成,使用者需要等待事件来等待请求的完成。当然,好处是使用者可以先去做别的事情。
要同步打开,前面的DesiredAccess必须含有SYNCHRONIZE。
此外还有一些其他的情况。比如不通过缓冲操作文件,希望每次读写文件都是直接往磁盘上操作的,此时CreateOptions中应该带标记 FILE_NO_INTERMEDIATE_BUFFERING。带了这个标记后,请注意操作文件每次读写都必须以磁盘扇区大小(最常见的是512字节)对齐,否则会返回错误。