note:本文部分代码参考了《Windows内核安全与驱动开发》这本书
一、OBJECT_ATTRIBUTES
对象属性结构。在ntdef.h文件中有该结构的定义:
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR
PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
内核中都是以对象为操作单位,所以我们不论打开文件还是关闭文件等 都必须先填充这个结构。这个结构有个函数用来初始化信息。
#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
p :传入OBJECT_ATTRIBUTES 指针
n :PUNICODE_STRING ObjectName 对象名字(文件注册表等操作要输入绝对路径)
a:一般填写 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE 名字字符串不区分大小写,打开的句柄是一个内核句柄;内核句柄可以不受线程和进程限制,在任何进程中都可以读写,同时打开的内核文件句柄不需要顾及当前进程是否有权限访问该文件。
r::根目录 用于相对打开的情况,个人建议采用绝对路径方法访问,这个地方填写NULL
s :设置安全描述符号,填写NULL
二、打开和关闭文件
ZwCreateFile(...) 这个函数参数比较多,这里我们先介绍里面的一个重要结构 IO_STATUS_BLOCK
typedef struct _IO_STATUS_BLOCK {
union {
NTSTATUS Status;
PVOID Pointer;
} DUMMYUNIONNAME;
ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
IO_STATUS_BLOCK 结构中的Pointer很少用,一般返回的结果在Status中,成功则 Status = STATUS_SUCCESS,否则是一个错误代码;具体错误信息在Information中。
针对ZwCreateFile文件调用,Information返回值一般包括:
FILE_CREATE: 文件成功创建
FILE_OPEN :文件打开
FILE_OVERWRITEN:文件被覆盖
FILE_SUPERSEDED:文件被替代
FILE_EXISTS:文件已经存在
FILE_DOES_NOT_EXISTS:文件不存在(因而打开失败了)
下面给出一个ZwCreateFile的例子:(注意参数的填写)
// 要返回的文件句柄
HANDLE file_handle = NULL ;
// 返回值
NTSTATUS status ;
// 首先初始化含有文件路径的 OBJECT_ATTRIBUTES
OBJECT_ATTRIBUTES object_attributes ;
IO_STATUS_BLOCK io_status ;
UNICODE_STRING ufile_name = RTL_CONSTANT_STRING(L"\\??\\C:\\a.txt");
InitializeObjectAttributes(
&object_attributes,
&ufile_name,
OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
NULL,
NULL);
// 以FILE_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);
这里要注意:ufile_name 字符串写法:“\\??\\C:\\a.dat” 。 这是因为ZwCeateFile使用的是对象路径,“C:”是一个符号链接对象,符号链接对象一般都在“\\??\\‘’ 路径下。
文件打开就意味着一定要关闭,打开时候用来OBJ_KERNEL_HANDLE标志,关闭的时候也是需要该句柄,内核关闭对象句柄不需要再同一进程中。
ZwClose(file_handle);
三、文件读、写
在wdm.h中找到ZwReadFile的定义
ZwReadFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
HANDLE:就是打开文件获取的文件句柄
EVENT:事件用于异步完成时候,设置NULL
PIO_APC_ROUTINE :回调例程用于异步完成读,设NULL
ApcContext:NULL
PIO_STATUS_BLOCK : 返回结果的状态,和ZwCreateFile中的一样功能
BUFFER:读取的缓冲
Length:描述缓冲区的长度
PLARGE_INTEGER:要读取的文件的偏移量,这个参数很重要
KEY:直接填写NULL 读取文件使用的附加信息
我们找到ZwWriteFile的定义:
ZwWriteFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_reads_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
对比后我们发现ZwWriteFile和ZwReadFile 的参数一样,这里就不做过多介绍。
下面给出一个使用ZwCreateFile和ZwReadFile 以及ZwWriteFile三成个函数完成文件的复制操作。
// 文件复制操作 借鉴 @楚狂人 的代码
NTSTATUS MyCopyFile(
PUNICODE_STRING target_path,
PUNICODE_STRING source_path)
{
// 源和目标的文件句柄
HANDLE target = NULL , source = NULL;
// ObjectAttributes
OBJECT_ATTRIBUTES object_attribute_target = {0} ;
OBJECT_ATTRIBUTES object_attribute_source = {0} ;
IO_STATUS_BLOCK io_status ;
// 用来拷贝的缓冲区
PVOID buffer = NULL ;
ULONG length = 0 ;
LARGE_INTEGER offset = {0} ;
#if DBG
// _asm int 3 ;
#endif
// 初始化OBJECT_ATTRIBUTES target
InitializeObjectAttributes(
&object_attribute_target,
target_path,
OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
NULL,
NULL);
// source
InitializeObjectAttributes(
&object_attribute_source,
source_path,
OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
NULL,
NULL) ;
do{
// 打开target_path文件
status = ZwCreateFile(
&target,
GENERIC_READ | GENERIC_WRITE ,
&object_attribute_target,
&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);
if( !NT_SUCCESS(status) )
{
DbgPrint("打开targe文件失败");
return status;
}
// 打开source_path文件
status = ZwCreateFile(
&source,
GENERIC_READ | GENERIC_WRITE ,
&object_attribute_source,
&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);
if( !NT_SUCCESS(status) )
{
DbgPrint("打开source文件失败");
return status;
}
// 为buffer 分配一个缓冲区
buffer = ExAllocatePoolWithTag(PagedPool,4*1024,MEM_TAG);
if( !buffer )
{
// 分配内存失败
DbgPrint("分配内存失败!");
return STATUS_INSUFFICIENT_RESOURCES ;
}
// 然后循环读取文件,每次从文件中读取4KB的数据 向目标文件中写入4Kb
// 直到拷贝完成为止
while(1)
{
length = 4*1024 ; // 每次读取4KB
// 读取旧的文件,注意status
status = ZwReadFile(
source,NULL,NULL,NULL,
&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 = io_status.Information ;
// 现在读取了内容,读取长度为length,写入的长度也应该为Length
// 写入必须成功 如果失败则返回错误
status = ZwWriteFile(
target,NULL,NULL,NULL,
&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 )
{
ExFreePoolWithTag(buffer,MEM_TAG);
}
return STATUS_SUCCESS ;
}
在函数中调用测试:
UNICODE_STRING source_path = RTL_CONSTANT_STRING(L"\\??\\C:\\a.txt");
UNICODE_STRING targe_path = RTL_CONSTANT_STRING(L"\\??\\C:\\b.txt");
NTSTATUS rStat ;
rStat = MyCopyFile(&targe_path,&source_path);
if( !NT_SUCCESS(rStat) )
{
DbgPrint("文件拷贝失败!");
}
else
{
DbgPrint("文件拷贝成功!");
}
总结:
1. 内核中的文件即使对象操作,所以必须先填充OBJECT_ATTRIBUTES结构
2. ZwCreateFile 用于打开一个现有的文件(如果存在),或者创建一个新的文件
3. IO_STATUS_BLOCK 比较重要,里面的information参数记录了详细操作返回信息
4. ZwReadFile 、ZwWriteFile 采用一个文件指针记录当前操作位置(LARGE_INTEGER)
5. 文件操作完成后一定记得好关闭ZwClose,释放相应的资源