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 關閉句柄。
<a href="http://msdn.microsoft.com/en-us/library/ff566424%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff566424%28VS.85%29.aspx</a>
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 函數:
ZwSetInformationFile(
IN PVOID FileInformation,
IN FILE_INFORMATION_CLASS FileInformationClass
);
FileInformationClass指定修改或查詢的類别,它可能的值有很多種
<a href="http://msdn.microsoft.com/en-us/library/ff567096%28VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/ff567096%28VS.85%29.aspx</a>
在核心模式下操作檔案的函數不像使用者模式下那樣豐富,想複制檔案就調用CopyFile、想删除檔案就調用DeleteFile等,在核心模式下除了讀寫檔案的其他所有操作都是通過這兩個ZwQueryInformation和ZwSetInformationFile 函數完成的,而如何使這兩個函數精确完成我們需要的功能,就需要通過FileInformationClass參數來指定。
5)一個例子如下:
聲明.h:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
代碼
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,
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。
union {
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位元組)對齊,否則會傳回錯誤。