提供对文件的读写功能是操作系统的一项重要任务。我们来看一下NT家族的操作系统都为我们提供了那些功能。
9.1 核心句柄表
在开始讨论本文的主题之前,我们先来讨论一个重要的问题,我们之前并未对其给予应有的注意。为了取得对象的句柄需要填充OBJECT_ATTRIBUTES结构体我们已经做过很多遍了,其样子如下:
代码:
InitializeObjectAttributes(oa, @g_usName,OBJ_CASE_INSENSITIVE,0, nil);
从Windows 2000开始,在系统中有了专门的核心句柄表。在这个表中的句柄只能内核模式下的任意进程上下文中访问,与进程特有的句柄不同。甚至于,比如说如果在System进程的上下文、在DriverEntry函数中获得句柄,则就不能在用户进程上下文中使用对象。System进程实现了自己私有的句柄表,其与核心句柄表不同。
对于在核心句柄表中的句柄,需要在调用InitializeObjectAttributes宏时显式地设置OBJ_KERNEL_HANDLE标志,形式如下:
InitializeObjectAttributes(oa, @g_usName, OBJ_KERNEL_HANDLE, 0,nil);
9.2 FileWorks驱动程序源代码
就像上一例中的驱动程序,本例的驱动程序的代码也是由几个独立的函数构成的:CreateDirectory、CreateFile、WriteFile、MarkAsReadOnly、ReadFile、UnmarkAsReadOnly、AppendFile、TruncateFile、DeleteFile、DeleteDirectory和EnumerateFiles。几乎所有的函数都是独立工作的。
代码:
unit FileWorks; interface uses nt_status, ntoskrnl, ntifs, native, winioctl, fcall, macros; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation var g_usDirName: UNICODE_STRING; g_usFileName: UNICODE_STRING; procedure CreateDirectory; var oa: OBJECT_ATTRIBUTES; iosb: IO_STATUS_BLOCK; hDirectory: THANDLE; RtnCode: NTSTATUS; begin { 还记得吧, 传递给DbgPrint函数的用于格式化Unicode的代码(%C, %S, %lc, %ls, %wc, %ws, %wZ)只能在 IRQL = PASSIVE_LEVEL下调用! } DbgPrint(#13#10'FileWorks: Creating %ws directory'#13#10, g_usDirName.Buffer); InitializeObjectAttributes(oa, @g_usDirName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwCreateFile(@hDirectory, SYNCHRONIZE, @oa, @iosb, nil, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT, nil, 0); if RtnCode = STATUS_SUCCESS then begin if iosb.Information = FILE_CREATED then begin DbgPrint('FileWorks: Directory created'#13#10); end else if iosb.Information = FILE_OPENED then begin DbgPrint('FileWorks: Directory exists and was opened'#13#10); end; ZwClose(hDirectory); end else begin DbgPrint('FileWorks: Can''t create directory. Status: %08X'#13#10, RtnCode); end; end; procedure CreateFile; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; RtnCode: NTSTATUS; begin DbgPrint(#13#10'FileWorks: Creating %ws file'#13#10, g_usFileName.Buffer); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwCreateFile(@hFile, SYNCHRONIZE, @oa, @iosb, nil, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File created'#13#10); ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t create file. Status: %08X'#13#10, RtnCode); end; end; procedure WriteFile; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; RtnCode: NTSTATUS; g_szData: PAnsiChar; begin DbgPrint(#13#10'FileWorks: Opening file for writing'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwCreateFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); g_szData := 'Data can be written to an open file'; //RtlInitUnicodeString(g_szData, 'Data can be written to an open file'); RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szData, strlen(g_szData), nil, nil); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File was written'#13#10); end else begin DbgPrint('FileWorks: Can''t write to the file. Status: %08X\n', RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode); end; end; procedure MarkAsReadOnly; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THandle; fbi:FILE_BASIC_INFORMATION; RtnCode:NTSTATUS; begin DbgPrint(#13#10'FileWorks: Opening file for changing attributes'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File attributes were: %08X'#13#10, fbi.FileAttributes); fbi.FileAttributes := fbi.FileAttributes or FILE_ATTRIBUTE_READONLY; RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: Now file marked as read-only'#13#10); end else begin DbgPrint('FileWorks: Can''t change file attributes. Status: %08X'#13#10, RtnCode); end; end else begin DbgPrint('FileWorks: Can''t query file attributes. Status: %08X'#13#10, RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X\n', RtnCode); end; end; procedure ReadFile; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; p:PVOID; cb:DWORD; fsi:FILE_STANDARD_INFORMATION; RtnCode: NTSTATUS; begin DbgPrint(#13#10'FileWorks: Opening file for reading'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwOpenFile(@hFile, FILE_READ_DATA + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi, sizeof(fsi), FileStandardInformation); if RtnCode = STATUS_SUCCESS then begin cb := fsi.EndOfFile.LowPart + 1; p := ExAllocatePool(PagedPool, cb); if p <> nil then begin memset(p, 0, cb); RtnCode := ZwReadFile(hFile, 0, nil, nil, @iosb, p, cb, nil, nil); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File content: \=%s\='#13#10, p); end else begin DbgPrint('FileWorks: Can''t read from the file. Status: %08X'#13#10, RtnCode); end; ExFreePool(p); end else begin DbgPrint('FileWorks: Can''t allocate memory. Status: %08X'#13#10, RtnCode); end; end else begin DbgPrint('FileWorks: Can''t query file size. Status: %08X'#13#10, RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode); end; end; procedure UnmarkAsReadOnly; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; fbi:FILE_BASIC_INFORMATION; RtnCode:NTSTATUS; begin DbgPrint(#13#10'FileWorks: Opening file for changing attributes'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File attributes were: %08X'#13#10, fbi.FileAttributes); fbi.FileAttributes := fbi.FileAttributes and (not FILE_ATTRIBUTE_READONLY); RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: Now file can be written or deleted'#13#10); end else begin DbgPrint('FileWorks: Can''t change file attributes. Status: %08X'#13#10, RtnCode); end; end else begin DbgPrint('FileWorks: Can''t query file attributes. Status: %08X'#13#10, RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode); end; end; procedure AppendFile; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; g_szDataToAppend:PAnsiChar; RtnCode:NTSTATUS; begin DbgPrint(#13#10'FileWorks: Opening file to append data'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwOpenFile(@hFile, FILE_APPEND_DATA + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); g_szDataToAppend := ' using ZwWriteFile'; RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szDataToAppend, strlen(g_szDataToAppend), nil, nil); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: Data appended to the file'#13#10); end else begin DbgPrint('FileWorks: Can''t append data to file. Status: %08X'#13#10, RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode); end; end; procedure TruncateFile; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; fsi:FILE_STANDARD_INFORMATION; feofi:FILE_END_OF_FILE_INFORMATION; RtnCode:NTSTATUS; begin DbgPrint(#13#10'FileWorks: Opening file to truncate'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwOpenFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi, sizeof(fsi), FileStandardInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: EOF was: %08X'#13#10, fsi.EndOfFile.LowPart); feofi.EndOfFile.HighPart := 0; feofi.EndOfFile.LowPart := (fsi.EndOfFile.LowPart) shr 1; RtnCode := ZwSetInformationFile(hFile, @iosb, @feofi, sizeof(feofi), FileEndOfFileInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File truncated to its half size'#13#10); end else begin DbgPrint('FileWorks: Can''t truncate file. Status: %08X'#13#10, RtnCode); end; end else begin DbgPrint('FileWorks: Can''t query file info. Status: %08X'#13#10, RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode); end; end; procedure DeleteFile; var oa:OBJECT_ATTRIBUTES; iosb:IO_STATUS_BLOCK; hFile:THANDLE; fdi:FILE_DISPOSITION_INFORMATION; RtnCode:NTSTATUS; begin DbgPrint(#13#10'FileWorks: Opening file for deletion'#13#10); InitializeObjectAttributes(oa, @g_usFileName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwCreateFile(@hFile, _DELETE + SYNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); fdi.DeleteFile := True; RtnCode := ZwSetInformationFile(hFile, @iosb, @fdi, sizeof(fdi), FileDispositionInformation); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File has been marked for deletion'#13#10); DbgPrint('FileWorks: It should be deleted when the last open handle is closed'#13#10); end else begin DbgPrint('FileWorks: Can''t mark file for deletion. Status: %08X'#13#10, RtnCode); end; ZwClose(hFile); end else begin DbgPrint('FileWorks: Can''t open file. Status: %08X'#13#10, RtnCode); end; end; procedure DeleteDirectory; var oa:OBJECT_ATTRIBUTES; RtnCode:NTSTATUS; begin InitializeObjectAttributes(oa, @g_usDirName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwDeleteFile(@oa); if RtnCode = STATUS_SUCCESS then begin DbgPrint(#13#10'FileWorks: Directory should be deleted'#13#10); end else begin DbgPrint(#13#10'FileWorks: Can''t delete directory. Status: %08X'#13#10, RtnCode); end; end; procedure EnumerateFiles; var oa:OBJECT_ATTRIBUTES; hSystemRootDirectory:THANDLE; hDriversDirectory:THANDLE; _as:ANSI_STRING; us:UNICODE_STRING; iosb:IO_STATUS_BLOCK; tf:TIME_FIELDS; cb:DWORD; pfdi:PFILE_DIRECTORY_INFORMATION; RtnCode:NTSTATUS; g_usTemp:UNICODE_STRING; begin DbgPrint(#13#10'FileWorks: Opening directory to enumerate files'#13#10); RtlInitUnicodeString(g_usTemp, '\SystemRoot'); InitializeObjectAttributes(oa, @g_usTemp, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil); RtnCode := ZwOpenFile(@hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin RtlInitUnicodeString(g_usTemp, 'system32\drivers'); InitializeObjectAttributes(oa, @g_usTemp, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, nil); RtnCode := ZwOpenFile(@hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin cb := sizeof(FILE_DIRECTORY_INFORMATION) + 256; pfdi := ExAllocatePool(PagedPool, cb); if pfdi <> nil then begin RtlInitUnicodeString(g_usTemp, 'c*'); DbgPrint(#13#10'FileWorks: ---------- Starting enumerate files ----------'#13#10); RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb, pfdi, cb, FileDirectoryInformation, true, @g_usTemp, true); while RtnCode <> STATUS_NO_MORE_FILES do begin if RtnCode = STATUS_SUCCESS then begin us.Length := pfdi^.FileNameLength; us.MaximumLength := pfdi^.FileNameLength; us.Buffer := PWideChar(@pfdi^.FileName); if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then begin RtlTimeToTimeFields(@pfdi^.CreationTime, @tf); DbgPrint(' %s size=%d created on %d.%02d.%04d'#13#10, _as.Buffer, pfdi^.EndOfFile.LowPart, BYTE(tf.Day), BYTE(tf.Month), WORD(tf.Year)); RtlFreeAnsiString(@_as); end; end; RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb, pfdi, cb, FileDirectoryInformation, true, nil, false); end; {End While} DbgPrint('FileWorks: ------------------------------------------------'#13#10); ExFreePool(pfdi); end; ZwClose(hDriversDirectory); end else begin DbgPrint('FileWorks: Can''t open drivers directory. Status: %08X', RtnCode); end; ZwClose(hSystemRootDirectory); end else begin DbgPrint('FileWorks: Can''t open system root directory. Status: %08X'#13#10, RtnCode); end; end; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; begin DbgPrint(#13#10'FileWorks: Entering DriverEntry'#13#10); RtlInitUnicodeString(g_usFileName, '\??\c:\FileWorks\test.txt'); RtlInitUnicodeString(g_usDirName, '\??\c:\FileWorks'); CreateDirectory; CreateFile; WriteFile; MarkAsReadOnly; ReadFile; UnmarkAsReadOnly; AppendFile; ReadFile; TruncateFile; ReadFile; DeleteFile; DeleteDirectory; EnumerateFiles; DbgPrint(#13#10'FileWorks: Leaving DriverEntry'#13#10); result := STATUS_DEVICE_CONFIGURATION_ERROR; end; end.
代码:
InitializeObjectAttributes(oa, @g_usDirName, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil);
创建目录和文件用的都是ZwCreateFile函数。从系统角度看,目录也是文件,所以创建目录的函数与创建文件的函数没有本质上的差别。所以,创建目录与创建文件使用同一个函数,只是创建目录要用FILE_DIRECTORY_FILE标志。
代码:
RtnCode := ZwCreateFile(@hDirectory, SYNCHRONIZE, @oa, @iosb, nil,FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF,FILE_DIRECTORY_FILE+FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
ZwCreateFile函数有相当多的参数,所以我在下面给出了它的原型,而且不得不使用了C语言的语义,以显示出输入(IN)、输出(OUT)和可选的参数。
代码:
NTSTATUS ZwCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, 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 );
代码:
if RtnCode = STATUS_SUCCESS then begin if iosb.Information = FILE_CREATED then begin …… end else if iosb.Information = FILE_OPENED then begin …… end;
还记得我们是如何处理I/O请求的(见前面的章节)。例如,在驱动程序SharingMemory中对IRP_MJ_CREATE和IRP_MJ_CLOSE的处理如下:
代码:
p_Irp^.IoStatus.Status := STATUS_SUCCESS; p_Irp^.IoStatus.Information := 0; IofCompleteRequest(p_Irp, IO_NO_INCREMENT);
因为我们定义了FILE_OPEN_IF标志,通过iosb.Information的值,我们可以知道是该建立新的目录还是因该目录已经存在而将其打开。
ZwClose(hDirectory);
我再重复一遍,在内核模式下一定要显式地关闭所有打开的句柄。
代码:
RtnCode := ZwCreateFile(@hFile, SYNCHRONIZE, @oa, @iosb, nil, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
9.4 文件对象
每一个打开的文件句柄都对应着一个文件对象(file object),在内核内存区中有FILE_OBJECT结构体。
代码:
TFileObject=packed record wType:Word; Size:Word; DeviceObject:PDeviceObject; DoNotUser1:Pointer; FsContext:Pointer; FsContext2:Pointer; SectionObjectPointer:Pointer; PrivateCacheMap:Pointer; FinalStatus:NTSTATUS; RelatedFileObject:PFileObject; LockOperation:Boolean; DeletePending:Boolean; ReadAccess:Boolean; WriteAccess:Boolean; DeleteAccess:Boolean; SharedRead:Boolean; SharedWrite:Boolean; SharedDelete:Boolean; Flags:Cardinal; FileName:TUnicodeString; CurrentByteOffset:TLargeInteger; Waiters:Cardinal; Busy:Cardinal; LastLock:Pointer; Lock:TKEvent; Event:TKEvent; CompletionContext:Pointer; end;
DeviceObject域将包含指向设备对象\Device\HarddiskVolume1的指针,因为这个设备是对\??\c:符号链接,我们创建文件时在文件名中使用了\??\c:。
在读完本文后您可能会进行文件操作的实验并会发现系统是如何填充并管理FILE_OBJECT结构体的。如果在内存中定位它时发生问题,可以使用ObReferenceObjectByHandle函数,形式大致如下:
代码:
var pFileObject:PFILE_OBJECT; . . . begin RtnCode := ObReferenceObjectByHandle(hFile, FILE_READ_DATA, nil, KernelMode, @pFileObject,nil); if RtnCode = STATUS_SUCCESS then begin {pFileObject指向对应于hFile的FILE_OBJECT} ObfDereferenceObject(pFileObject); end; end;
ObReferenceObjectByHandle向参数pFileObject中返回指向对应于该文件句柄的FILE_OBJECT结构体的指针。这时,引用计数会增一。之后一定要调用ObfDereferenceObject来使其复原。
9.5 写入文件
到这里我们已经有了大小为0目录和文件。该向文件中写点东西了。
代码:
RtnCode := ZwCreateFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
为了写入文件,需要相应的访问权使用FILE_WRITE_DATA。
在单元文件中定义了几个常量以用于对文件的一般访问。例如,FILE_GENERIC_WRITE不仅允许向文件中写入数据,还可以更改其属性并添加数据。
代码:
FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or FILE_APPEND_DATA or SYNCHRONIZE);
代码:
if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); g_szData := 'Data can be written to an open file'; RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szData, strlen(g_szData), nil, nil);
函数ZwWriteFile见名则知意。
代码:
NTSTATUS ZwWriteFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID Buffer, IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL );
9.6 修改文件属性
我们需要防止我们的文件被删除。
代码:
RtnCode := ZwCreateFile(@hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
代码:
if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); RtnCode := ZwQueryInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation); if RtnCode = STATUS_SUCCESS then begin
我们需要建立只读属性,但由于文件有其它的属性需要保留,故有:
代码:
fbi.FileAttributes := fbi.FileAttributes or FILE_ATTRIBUTE_READONLY; RtnCode := ZwSetInformationFile(hFile, @iosb, @fbi, sizeof(fbi), FileBasicInformation);
代码:
fbi.FileAttributes := fbi.FileAttributes and (not FILE_ATTRIBUTE_READONLY);
现在,为了各种操作,我们用ZwOpenFile函数打开文件来从中读取。所需要的参数与ZwCreateFile的类似。
代码:
NTSTATUS ZwOpenFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG ShareAccess, IN ULONG OpenOptions );
代码:
RtnCode := ZwOpenFile(@hFile, FILE_READ_DATA + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT); FILE_READ_DATA在这里我们只从文件中读取数据。标志FILE_SHARE_READ、FILE_SHARE_WRITE和FILE_SHARE_DELETE的组合允许对文件进行完全的访问。 if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi, sizeof(fsi), FileStandardInformation); if RtnCode = STATUS_SUCCESS then begin
cb := fsi.EndOfFile.LowPart + 1;
我们来为最后的零添加一个字节。
代码:
p := ExAllocatePool(PagedPool, cb); if p <> nil then begin memset(p, 0, cb); RtnCode := ZwReadFile(hFile, 0, nil, nil, @iosb, p, cb, nil, nil); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File content: \=%s\='#13#10, p); End; ExFreePool(p); end;
分配所需的缓冲区,将其清零并传递给ZwReadFile函数。这个函数的原型与ZwWriteFile函数的原型类似,只是缓冲区用处不同。因为在使用之前我们清零了缓冲区,并且其大小比文件的内容多一个字节,所以不会发生将其内容输出到调试信息的问题。
9.8 向文件追加数据
向文件中追加数据有几种方法。可以用标志FILE_WRITE_DATA将其打开,在当前位置建立指向文件末尾的指针,在函数ZwWriteFile的参数ByteOffset中传递偏移量并写入数据。指示文件当前位置的指针保存在FILE_OBJECT.CurrentByteOffset中。我们还可以用标志FILE_APPEND_DATA打开文件,文件当前位置指针会自动指向其末尾。
代码:
RtnCode := ZwOpenFile(@hFile, FILE_APPEND_DATA + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin DbgPrint('FileWorks: File openeded'#13#10); g_szDataToAppend := ' using ZwWriteFile'; RtnCode := ZwWriteFile(hFile, 0, nil, nil, @iosb, g_szDataToAppend, strlen(g_szDataToAppend), nil, nil);
9.9 截短文件
假设我们需要截短文件,去掉不需要的数据。在本例中,我为了简单起见,将文件缩减为原来的一半。
代码:
RtnCode := ZwOpenFile(@hFile, FILE_WRITE_DATA + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
代码:
if RtnCode = STATUS_SUCCESS then begin RtnCode := ZwQueryInformationFile(hFile, @iosb, @fsi, sizeof(fsi), FileStandardInformation);
代码:
feofi.EndOfFile.HighPart := 0; feofi.EndOfFile.LowPart := (fsi.EndOfFile.LowPart) shr 1; RtnCode := ZwSetInformationFile(hFile, @iosb, @feofi, sizeof(feofi), FileEndOfFileInformation);
9.10 删除文件与目录
小事一桩将c:恢复为初始的状态。令人奇怪的是,2000 DDK里没有提到存在ZwDeleteFile函数。在XP DDK中说系统终归提供了这个函数,但是是从Windows XP开始的。但事实并非如此。Windows 2000甚至Windows NT4中就有ZwDeleteFile。但对于文件的删除,我们有几种更为复杂的方法,而对目录的删除则借助于ZwDeleteFile。
代码:
RtnCode := ZwCreateFile(@hFile, _DELETE + SYNCHRONIZE, @oa, @iosb, nil, 0, FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, nil, 0);
代码:
if RtnCode = STATUS_SUCCESS then begin fdi.DeleteFile := True; RtnCode := ZwSetInformationFile(hFile, @iosb, @fdi, sizeof(fdi), FileDispositionInformation);
ZwClose(hFile);
现在文件唯一的句柄被关闭,文件被删除。
ZwDeleteFile(@oa);
使用ZwDeleteFile删除目录很简单,我就不多说了。
9.11 列举目录内容
一般说来,有两种方法可以用来定义创建/打开的对象的名字,而文件却特别。访问命名对象可以使用完整的路径,也可以使用符号链接。这里我们只使用绝对路径。例如,路径\??\c:\FileWorks\test.txt就是绝对路径。在本例中,使用InitializeObjectAttributes宏填充InitializeObjectAttributes结构体,其形式如下:
代码:
InitializeObjectAttributes(oa, @g_usTemp, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil);
倒数第二个参数RootDirectory为0。InitializeObjectAttributes的参数RootDirectory和该函数填充的OBJECT_ATTRIBUTES的同名域定义了目录容器对象的句柄。
代码:
OBJECT_ATTRIBUTES=record . . . RootDirectory:THANDLE; . . . end;
代码:
RtlInitUnicodeString(g_usFileName, '\??\c:\FileWorks\test.txt'); InitializeObjectAttributes(a,@g_usFileName, OBJ_CASE_INSENSITIVE, hDirectory, nil);
在目录容器下就意味着目录不只是在磁盘上,还在对象管理器的名字空间中。
对于列出系统目录\%SystemRoot%\System32\Drivers\的内容,我们来使用相对路径。
代码:
RtlInitUnicodeString(g_usTemp, '\SystemRoot'); InitializeObjectAttributes(oa, @g_usTemp, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, 0, nil);
我们来填充OBJECT_ATTRIBUTES结构体,使用符号链接\SystemRoot这个是绝对路径。
代码:
RtnCode := ZwOpenFile(@hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT);
代码:
if RtnCode = STATUS_SUCCESS then begin RtlInitUnicodeString(g_usTemp, 'system32\drivers'); InitializeObjectAttributes(oa, @g_usTemp, OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, nil);
代码:
RtnCode := ZwOpenFile(@hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, @oa, @iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT); if RtnCode = STATUS_SUCCESS then begin
我们来用相对路径打开目录\%SystemRoot%\System32\Drivers\。
代码:
cb := sizeof(FILE_DIRECTORY_INFORMATION) + 256; pfdi := ExAllocatePool(PagedPool, cb); if pfdi <> nil then begin
分配必要的缓冲区,在缓冲区中应放得下FILE_DIRECTORY_INFORMATION结构体和文件名。
代码:
RtlInitUnicodeString(g_usTemp, 'c*'); RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb, pfdi, cb, FileDirectoryInformation, true, @g_usTemp, true);
我们开始列举目录文件,这里用的是信息类FileDirectoryInformation。函数ZwQueryDirectoryFile的原型如下。
代码:
NTSTATUS ZwQueryDirectoryFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation, IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass, IN BOOLEAN ReturnSingleEntry, IN PUNICODE_STRING FileName OPTIONAL, IN BOOLEAN RestartScan );
我们令参数ReturnSingleEntry为TRUE,这就使ZwQueryDirectoryFile函数只返回一个文件的信息,而且是第一个文件的。参数FileName指向带有搜索条件“c*”的字符串,即只列出文件名以“c”开头的文件。这样能缩短输出的调试信息。在第一次调用ZwQueryDirectoryFile时,我们将参数RestartScan设为TRUE。这就使得ZwQueryDirectoryFile函数开始检查目录的内容。
while RtnCode <> STATUS_NO_MORE_FILES do
begin
循环调用ZwQueryDirectoryFile直到ZwQueryDirectoryFile返回STATUS_NO_MORE_FILES,即目录中所有文件都已经列举。
代码:
if RtnCode = STATUS_SUCCESS then begin
如果突然发现有文件的文件名超过256字节(实际上是不会的,因为驱动的文件名不会超过8个字符),ZwQueryDirectoryFile返回非STATUS_NO_MORE_FILES的错误代号。这时,我们只简单的过滤掉这个文件。
代码:
us.Length := pfdi^.FileNameLength; us.MaximumLength := pfdi^.FileNameLength; us.Buffer := PWideChar(@pfdi^.FileName); if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then begin RtlTimeToTimeFields(@pfdi^.CreationTime, @tf); DbgPrint(' %s size=%d created on %d.%02d.%04d'#13#10, _as.Buffer, pfdi^.EndOfFile.LowPart, BYTE(tf.Day), BYTE(tf.Month), WORD(tf.Year)); RtlFreeAnsiString(@_as); End;
格式化获得的信息,输出文件名、大小以及创建时间。
代码:
RtnCode := ZwQueryDirectoryFile(hDriversDirectory, 0, nil, nil, @iosb, pfdi, cb, FileDirectoryInformation, true, nil, false); end; {End While}
在调用ZwQueryDirectoryFile的循环中,参数ReturnSingleEntry、FileName和RestartScan分别为为TRUE、NULL和FALSE。这就使得ZwQueryDirectoryFile能继续列举文件。
代码:
ExFreePool(pfdi); end; ZwClose(hDriversDirectory); end else begin DbgPrint('FileWorks: Can''t open drivers directory. Status: %08X', RtnCode); end; ZwClose(hSystemRootDirectory);
回收所有用过的资源。
以下是DebugView里输出的调试信息,你可以看到驱动程序正常工作并完成了相关的文件和目录操作。
代码:
FileWorks: Entering DriverEntry FileWorks: Creating \??\c:\FileWorks directory FileWorks: Directory created FileWorks: Creating \??\c:\FileWorks\test.txt file FileWorks: File created FileWorks: Opening file for writing FileWorks: File openeded FileWorks: File was written FileWorks: Opening file for changing attributes FileWorks: File openeded FileWorks: File attributes were: 00000020 FileWorks: Now file marked as read-only FileWorks: Opening file for reading FileWorks: File openeded FileWorks: File content: \=Data can be written to an open file\= FileWorks: Opening file for changing attributes FileWorks: File openeded FileWorks: File attributes were: 00000021 FileWorks: Now file can be written or deleted FileWorks: Opening file to append data FileWorks: File openeded FileWorks: Data appended to the file FileWorks: Opening file for reading FileWorks: File openeded FileWorks: File content: \=Data can be written to an open file using ZwWriteFile\= FileWorks: Opening file to truncate FileWorks: File openeded FileWorks: EOF was: 00000035 FileWorks: File truncated to its half size FileWorks: Opening file for reading FileWorks: File openeded FileWorks: File content: \=Data can be written to an \= FileWorks: Opening file for deletion FileWorks: File openeded FileWorks: File has been marked for deletion FileWorks: It should be deleted when the last open handle is closed FileWorks: Directory should be deleted FileWorks: Opening directory to enumerate files FileWorks: ---------- Starting enumerate files ---------- cbidf2k.sys size=13952 created on 2.05.2008 cd20xrnt.sys size=7680 created on 2.05.2008 cdaudio.sys size=18688 created on 2.05.2008 cdfs.sys size=63744 created on 2.05.2008 cdr4_xp.sys size=2432 created on 5.06.2006 cdralw2k.sys size=2560 created on 5.06.2006 cdrom.sys size=62976 created on 2.05.2008 ch7xxnt5.dll size=15423 created on 2.05.2008 cinemst2.sys size=262528 created on 2.05.2008 classpnp.sys size=49536 created on 2.05.2008 cmbatt.sys size=13952 created on 2.05.2008 cmdide.sys size=6656 created on 2.05.2008 compbatt.sys size=10240 created on 2.05.2008 cpqarray.sys size=14976 created on 2.05.2008 cpqdap01.sys size=11776 created on 2.05.2008 crusoe.sys size=39552 created on 2.05.2008 cxthsfs2.cty size=129045 created on 2.05.2008 FileWorks: ------------------------------------------------ FileWorks: Leaving DriverEntry