Windows内核中文件打开、读/写、关闭

1.使用OBJECT_ATTRIBUTES

这个结构用来说明对象属性,我们使用InitializeObjectAttributes初始化。

VOID InitializeObjectAttributes(
  [out]           POBJECT_ATTRIBUTES InitializedAttributes,
  [in]            PUNICODE_STRING ObjectName,
  [in]            ULONG Attributes,
  [in]            HANDLE RootDirectory,
  [in, optional]  PSECURITY_DESCRIPTOR SecurityDescriptor
);

InitializedAttributes是OBJECT_ATTRIBUTES结构的指针,ObjectName是对象名字字符串,可以是文件路径。

\??\c:\test.txt    我们要写成     \\??\\c:\\test.txt     记得转义。

Attributes则只需要填写OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE即可。不区分大小(Windows本来就不区别大小写),打开一个内核句柄。

RootDirectory用于相对打开的情况。

SecurityDescriptor用于设置安全描述符。

2.打开和关闭文件

打开文件的函数:ZwCreateFile

NTSTATUS ZwCreateFile(
  _Out_     PHANDLE FileHandle,
  _In_      ACCESS_MASK DesiredAccess,
  _In_      POBJECT_ATTRIBUTES ObjectAttributes,
  _Out_     PIO_STATUS_BLOCK IoStatusBlock,
  _In_opt_  PLARGE_INTEGER AllocationSize,
  _In_      ULONG FileAttributes,
  _In_      ULONG ShareAccess,
  _In_      ULONG CreateDisposition,
  _In_      ULONG CreateOptions,
  _In_opt_  PVOID EaBuffer,
  _In_      ULONG EaLength
);

FileHandle——–这是一个指向一个变量的指针,用来最后存放file object handle的。

DesiredAccess—-这个参数指定一个访问权限,大概有以下的权限:
FILE_ANY_ACCESS 0x0000 // any type
FILE_READ_ACCESS 0x0001 // file & pipe
FILE_READ_DATA 0x0001 // file & pipe
FILE_LIST_DIRECTORY 0x0001 // directory
FILE_WRITE_ACCESS 0x0002 // file & pipe
FILE_WRITE_DATA 0x0002 // file & pipe
FILE_ADD_FILE 0x0002 // directory
FILE_APPEND_DATA 0x0004 // file
FILE_ADD_SUBDIRECTORY 0x0004 // directory
FILE_CREATE_PIPE_INSTANCE 0x0004 // named pipe
FILE_READ_EA 0x0008 // file & directory
FILE_WRITE_EA 0x0010 // file & directory
FILE_EXECUTE 0x0020 // file
FILE_TRAVERSE 0x0020 // directory
FILE_DELETE_CHILD 0x0040 // directory
FILE_READ_ATTRIBUTES 0x0080 // all types
FILE_WRITE_ATTRIBUTES 0x0100 // all types
FILE_ALL_ACCESS // All of the preceding +STANDARD_RIGHTS_ALL
最后一个权限最大,里面要注意的是范围问题,有的值只适合目录,有的只适合管道,有的只适合命名管道,有的同时适用。

ObjectAttributes—-就是我们上面初始化的结构体

IoStatusBlock—–这个也是个指针变量,指向一个叫做IO_STATUS_BLOCK的结构体,最后函数返回的时候,这个结构体的成员 里面要填充一些值,具体的呢就是完成状态,请求操作的一些信息,最重要的一个成员就是Information成员,他显示了函数对文件的处理方式,他的值可能是下面的几个:
FILE_SUPERSEDED(文件被替代了)
FILE_OPENED(文件成功打开)
FILE_CREATED(文件被成功的创建了)
FILE_OVERWRITTEN(文件被覆盖了)
FILE_EXISTS(文件已存在)
FILE_DOES_NOT_EXIST(文件不存在)

再看下这个IO_STATUS_BLOCK的具体结构:
typedef struct _IO_STATUS_BLOCK {
union {
    NTSTATUS Status;
    PVOID Pointer;
} ;

ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

FileAttributes—这个参数指定文件的属性,刚才是文件对象的属性。可以是以下的:
FILE_ATTRIBUTE_READONLY
FILE_ATTRIBUTE_HIDDEN
FILE_ATTRIBUTE_SYSTEM
FILE_ATTRIBUTE_DIRECTORY
FILE_ATTRIBUTE_ARCHIVE
FILE_ATTRIBUTE_NORMAL    //一般设置
FILE_ATTRIBUTE_TEMPORARY
FILE_ATTRIBUTE_SPARSE_FILE
FILE_ATTRIBUTE_REPARSE_POINT
FILE_ATTRIBUTE_COMPRESSED
FILE_ATTRIBUTE_OFFLINE
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
FILE_ATTRIBUTE_ENCRYPTED

ShareAccess—指定共享的权限,以下三种的组合:
FILE_SHARE_READ
FILE_SHARE_WRITE
FILE_SHARE_DELETE

CreateDisposition—这个参数指定要对文件打开的意图,可以是下面的值:
FILE_SUPERSEDE
FILE_OPEN
FILE_CREATE
FILE_OPEN_IF
FILE_OVERWRITE
FILE_OVERWRITE_IF

CreateOptions—这个参数指定创建或者打开文件的时候做的一些事情,可以是以下的组合:
FILE_DIRECTORY_FILE
FILE_WRITE_THROUGH
FILE_SEQUENTIAL_ONLY
FILE_NO_INTERMEDIATE_BUFFERING
FILE_SYNCHRONOUS_IO_ALERT
FILE_SYNCHRONOUS_IO_NONALERT
FILE_NON_DIRECTORY_FILE
FILE_CREATE_TREE_CONNECTION
FILE_COMPLETE_IF_OPLOCKED
FILE_NO_EA_KNOWLEDGE
FILE_OPEN_FOR_RECOVERY
FILE_RANDOM_ACCESS
FILE_DELETE_ON_CLOSE
FILE_OPEN_BY_FILE_ID
FILE_OPEN_FOR_BACKUP_INTENT
FILE_NO_COMPRESSION
FILE_RESERVE_OPFILTER
FILE_OPEN_REPARSE_POINT
FILE_OPEN_NO_RECALL
FILE_OPEN_FOR_FREE_SPACE_QUERY

我们来看一个例子:

// 要返回的文件句柄
HANDLE file_handle = NULL;
// 返回值
NTSTATUS status;
// 首先初始化含有文件路径的OBJECT_ATTRIBUTES
OBJECT_ATTRIBUTES object_attributes;
UNICODE_STRING ufile_name = RTL_CONST_STRING(L”\\??\\C:\\a.dat”);
InitializeObjectAttributes(
&object_attributes,
&ufile_name,
OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
NULL,
NULL);
// 以OPEN_IF方式打开文件。
status = ZwCreateFile(
&file_handle,
GENERIC_READ | GENERIC_WRITE,
&object_attributes,
&io_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE |
FILE_RANDOM_ACCESS |
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
值得注意的是路径的写法。并不是像应用层一样直接写“C:\\a.dat”。而是写成了“\\??\\C:\\a.dat”。这是因为ZwCreateFile使用的是对象路径。“C:”是一个符号链接对象。符号链接对象一般都在“\\??\\”路径下。
这种文件句柄的关闭非常简单。调用ZwClose即可。内核句柄的关闭不需要和打开在同一进程中。示例如下:
ZwClose(file_handle)

3.文件读写操作

打开文件之后,最重要的操作是对文件的读写。读与写的方法是对称的。只是参数输入与输出的方向不同。读取文件内容一般用ZwReadFile,写文件一般使用ZwWriteFile。

NTSTATUS ZwReadFile(
  _In_      HANDLE FileHandle,
  _In_opt_  HANDLE Event,
  _In_opt_  PIO_APC_ROUTINE ApcRoutine,
  _In_opt_  PVOID ApcContext,
  _Out_     PIO_STATUS_BLOCK IoStatusBlock,
  _Out_     PVOID Buffer,
  _In_      ULONG Length,
  _In_opt_  PLARGE_INTEGER ByteOffset,
  _In_opt_  PULONG Key
);

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用来拷贝一个文件。这个要求未能被满足。如果有这个需求,这个函数必须自己来编写。下面是一个例子,用来拷贝一个文件。

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
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;
}

本文链接:http://www.alonemonkey.com/kernel-filereadwrite.html

一条评论

Comments are closed.