3.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成功后所得到的FileHandle。如果是内核句柄,ZwReadFile和ZwCreateFile并不需要在同一个进程中。句柄是各进程通用的。
Event :一个事件。用于异步完成读时。下面的举例始终用同步读,所以忽略这个参数。请始终填写NULL。
ApcRoutine Apc:回调例程。用于异步完成读时。下面的举例始终用同步读,所以忽略这个参数。请始终填写NULL。
IoStatusBlock:返回结果状态。同ZwCreateFile中的同名参数。
Buffer:缓冲区。如果读文件的内容成功,则内容被被读到这个缓冲里。
Length:描述缓冲区的长度。这个长度也就是试图读取文件的长度。
ByteOffset:要读取的文件的偏移量。也就是要读取的内容在文件中的位置。一般的说,不要设置为NULL。文件句柄不一定支持直接读取当前偏移。
Key:读取文件时用的一种附加信息,一般不使用。设置NULL。
返回值:成功的返回值是STATUS_SUCCESS。只要读取到任意多个字节(不管是否符合输入的Length的要求),返回值都是 STATUS_SUCCESS。即使试图读取的长度范围超出了文件本来的大小。但是,如果仅读取文件长度之外的部分,则返回 STATUS_END_OF_FILE。
ZwWriteFile的参数与ZwReadFile完全相同。当然,除了读写文件外,有的读者可能会问是否提供一个ZwCopyFile用来拷贝一个文 件。这个要求未能被满足。如果有这个需求,这个函数必须自己来编写。下面是一个例子,用来拷贝一个文件。利用到了 ZwCreateFile,ZwReadFile和ZwWrite这三个函数。不过作为本节的例子,只举出ZwReadFile和ZwWriteFile 的部分:
- NTSTATUS MyCopyFile(
- PUNICODE_STRING target_path,
- PUNICODE_STRING source_path)
- {
- // 源和目标的文件句柄
- HANDLE target = NULL,source = NULL;
- // 用来拷贝的缓冲区
- PVOID buffer = NULL;
- LARGE_INTEGER offset = { 0 };
- IO_STATUS_BLOCK io_status = { 0 };
- do {
- // 这里请用前一小节说到的例子打开target_path和source_path所对应的
- // 句柄target和source,并为buffer分配一个页面也就是4k的内存。
- … …
- // 然后用一个循环来读取文件。每次从源文件中读取4k内容,然后往
- // 目标文件中写入4k,直到拷贝结束为止。
- while(1) {
- length = 4*1024; // 每次读取4k。
- // 读取旧文件。注意status。
- status = ZwReadFile (
- source,NULL,NULL,NULL,
- &my_io_status,buffer, length,&offset,
- NULL);
- if(!NT_SUCCESS(status))
- {
- // 如果状态为STATUS_END_OF_FILE,则说明文件
- // 的拷贝已经成功的结束了。
- if(status == STATUS_END_OF_FILE)
- status = STATUS_SUCCESS;
- break;
- }
- // 获得实际读取到的长度。
- length = IoStatus.Information;
- // 现在读取了内容。读出的长度为length.那么我写入
- // 的长度也应该是length。写入必须成功。如果失败,
- // 则返回错误。
- status = ZwWriteFile(
- target,NULL,NULL,NULL,
- &my_io_status,
- buffer,length,&offset,
- NULL);
- if(!NT_SUCCESS(status))
- break;
- // offset移动,然后继续。直到出现STATUS_END_OF_FILE
- // 的时候才结束。
- offset.QuadPart += length;
- }
- } while(0);
- // 在退出之前,释放资源,关闭所有的句柄。
- if(target != NULL)
- ZwClose(target);
- if(source != NULL)
- ZwClose(source);
- if(buffer != NULL)
- ExFreePool(buffer);
- return STATUS_SUCCESS;
- }
除了读写之外,文件还有很多的操作。比如删除、重新命名、枚举。这些操作将在后面实例中用到时,再详细讲解。
4.1注册键的打开操作
和在应用程序中编程的方式类似,注册表是一个巨大的树形结构。操作一般都是打开某个子键。子键下有若干个值可以获得。每一个值有一个名字。值有不同的类型。一般需要查询才能获得其类型。
子键一般用一个路径来表示。和应用程序编程的一点重大不同是这个路径的写法不一样。一般应用编程中需要提供一个根子键的句柄。而驱动中则全部用路径表示。相应的有一张表表示如下:
应用编程中对应的子键 驱动编程中的路径写法
HKEY_LOCAL_MACHINE \Registry\Machine
HKEY_USERS \Registry\User
HKEY_CLASSES_ROOT 没有对应的路径
HKEY_CURRENT_USER 没有简单的对应路径,但是可以求得
实际上应用程序和驱动程序很大的一个不同在于应用程序总是由某个“当前用户”启动的。因此可以直接读取HKEY_CLASSES_ROOT和 HKEY_CURRENT_USER。而驱动程序和用户无关,所以直接去打开HKEY_CURRENT_USER也就不符合逻辑了。
打开注册表键使用函数ZwOpenKey。新建或者打开则使用ZwCreateKey。一般在驱动编程中,使用ZwOpenKey的情况比较多见。下面以此为例讲解。ZwOpenKey的原型如下:
- NTSTATUS
- ZwOpenKey(
- OUT PHANDLE KeyHandle,
- IN ACCESS_MASK DesiredAccess,
- IN POBJECT_ATTRIBUTES ObjectAttributes
- );
这个函数和ZwCreateFile是类似的。它并不接受直接传入一个字符串来表示一个子键。而是要求输入一个OBJECT_ATTRIBUTES的指针。如何初始化一个OBJECT_ATTRIBUTES请参考前面的讲解ZwCreateFile的章节。
DesiredAccess支持一系列的组合权限。可以是下表中所有权限的任何组合:
? KEY_QUERY_VALUE:读取键下的值。
? KEY_SET_VALUE:设置键下的值。
? KEY_CREATE_SUB_KEY:生成子键。
? KEY_ENUMERATE_SUB_KEYS:枚举子键。
不过实际上可以用KEY_READ来做为通用的读权限组合。这是一个组合宏。此外对应的有KEY_WRITE。如果需要获得全部的权限,可以使用KEY_ALL_ACCESS。
下面是一个例子,这个例子非常的有实用价值。它读取注册表中保存的Windows系统目录(指Windows目录)的位置。不过这里只涉及打开子键。并不读取值。读取具体的值在后面的小节中再完成。
Windows目录的位置被称为SystemRoot,这一值保存在注册表中,路径是“HKEY_LOCAL_MACHINE\SOFTWARE \Microsoft\Windows NT\CurrentVersion”。当然,请注意注意在驱动编程中的写法有所不同。下面的代码初始化一个OBJECT_ATTRIBUTES。
- HANDLE my_key = NULL;
- NTSTATUS status;
- // 定义要获取的路径
- UNICODE_STRING my_key_path =
- RTL_CONSTANT_STRING(
- L” \\ Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion”);
- OBJECT_ATTRIBUTE my_obj_attr = { 0 };
- // 初始化OBJECT_ATTRIBUTE
- InitializeObjectAttributes(
- &my_obj_attr,
- &my_key_path,
- OBJ_CASE_INSENSITIVE,
- NULL,
- NULL);
- // 接下来是打开Key
- status = ZwOpenKey(&my_key,KEY_READ,&my_obj_attr);
- if(!NT_SUCCESS(status))
- {
- // 失败处理
- ……
- }
上面的代码得到了my_key。子键已经打开。然后的步骤是读取下面的SystemRoot值。这在后面一个小节中讲述。