(转)深入理解文件操作函数native api之ZwQueryInformationFile 获取File ID

来源:互联网 发布:淘宝老板跑路 编辑:程序博客网 时间:2024/05/16 10:11
<摘自 http://hi.baidu.com/liushijianlu/blog/item/b104c810a08f8b28dc5401fb.html>

深入理解文件操作函数native api之ZwQueryInformationFile

看这个函数的名字和参数就能够知道他是干嘛用的了,但是他的作用远不止这些,这需要一些技巧和深入的理解才能有了新的思路。

现在仅仅是明白了函数的用法,以后还要深入的理解不实现。最终达到根据自己的理解能够自己实现api的水平。

只有明白了内部的实现机制,才能催生出新的思路。说的通俗点,只有充分的了解了对方的脾气,才能“找茬”。

那么今天这个函数能够很好的体现这一点。到底是怎么“找茬”的?那么由此我们可以理解并且找到一种学习的思路,那就是如何i掌握一个nativa api,我觉得这才是重要的。

原型:
NTSTATUS   ZwQueryInformationFile
(   
IN HANDLE FileHandle,   
OUT PIO_STATUS_BLOCK IoStatusBlock,   
OUT PVOID FileInformation,   
IN ULONG Length,   
IN FILE_INFORMATION_CLASS FileInformationClass   
);

参数理解:
这个函数的参数不是很多,实际上内容很丰富的。
-out:
FileInformation---这个参数指向一个缓冲区来存放返回的信息,实际上一般是一个与最后一个参数相关的结构体,那么可想而知,我们查询结构体的成员就可以输出我们要找的信息了。

FileInformationClass---这个参数就是指定我们要查询哪方面的信息,这算是理所当然的事情,如果我是函数设计者的话,我也会这样做的。
本来不准备具体的说这些FILE_INFORMATION_CLASS的具体值的。但是发现基本上写程序的话,这个参数是必须指定的,而且你必须很明确,否则你就查询不到你要的信息。那么具体来看看:
value                        说明
1.FileAccessInformation       查询文件的访问权限
::附加说明,这个值返回一个FILE_ACCESS_INFORMATION的结构,这个结构呢我们看下
typedef struct _FILE_ACCESS_INFORMATION
{
ACCESS_MASK AccessFlags;
} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION;
实际上就是返回一个ACCESS_MASK的结构,接着看
typedef ULONG ACCESS_MASK;

实际上我理解的话,ACCESS_MASK是一个常量,这个常量是wdk里面定义好的,他来表示一个权限。那么我们来看看对于一个对象,都有哪些权限:
Flag                 Description
DELETE               The caller can delete the object.
READ_CONTROL         The caller can read the access control list (ACL) and ownership information for the file.
SYNCHRONIZE          The caller can perform a wait operation on the object. (For example, the object can be passed to KeWaitForMultipleObjects.)
WRITE_DAC            The caller can change the discretionary access control list (DACL) information for the object.
WRITE_OWNER          The caller can change the ownership information for the file.

wdk上面说了,对于驱动开发来说的话,DELETE and SYNCHRONIZE是我们最关心的。这个我后面解释。

我为什么要在这个参数上面延伸这么多,因为实际上稍微有编码的经验的话,你就可以理解,这些通常是一条链上的,比如我们指定什么标志,最后返回到一个什么结构里面,然后我们if判断结构里面的成员值是不是文档中定义的 XXXXH定植,是的话,我们怎么处理,不是的话怎么处理。所以这些是必须串下来的。

2.FileAlignmentInformation    这个我不是很明白,有点费解
3.FileBasicInformation        这个用的最多了,呼呼。
::附加说明:说是basic,那都有哪些basic信息呢?这得看一个结构FILE_BASIC_INFORMATION
typedef struct FILE_BASIC_INFORMATION
{
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
ULONG FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
这个等下就清楚了。
4.FileInternalInformation     这个返回FILE_INTERNAL_INFORMATION
::附加说明:这个到底是干嘛的,我们看下FILE_INTERNAL_INFORMATION:
typedef struct _FILE_INTERNAL_INFORMATION
{
LARGE_INTEGER IndexNumber;
} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION;
看到只有个IndexNumber,他的值能代表什么呢?
wdk上说这个值是8位的由文件系统分配指定的。在这里我突然想到了File object id。我们记得我以前在空间写native api实现的系列程序的时候,师傅跟我提过这个file object id,说是可以唯一的确定一个文件。我在想这个东西和这里的这个indexnumber有什么联系呢?

了解后才发现,实际上可以基本上等同。我们看下这个结构:
typedef struct _FILE_ID_BOTH_DIR_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaSize;
CCHAR ShortNameLength;
WCHAR ShortName[12];
LARGE_INTEGER FileId;
WCHAR FileName[1];
} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION;
这里面有个FileId,这个呢 那个IndexNumber是一样的值。

在NTFS系统上,这个值对于一个文件时唯一的,但是其他的文件系统就不一定了。比如FAT32,可能来个磁盘整理这个值就变了,而且win2000上面这个值做了扩展,变成了16位的了。

5.FileStandardInformation      这个也比较常见。

运行级别:
PASSIVE_LEVEL

扩展:
(1)网上有一种hook r3 api来实现防止文件删除的思路。实际上就是从DeleteFile入手一步一步的跟踪,直到ZwSetInformationFile,这中间是必然有ZwQueryInformationFile,一般都是先query再set的。这个源码很简单,也很好实现。
我重点想说的就是一个思路问题。

通过深入的研究函数的内部实现,我们才能找到出奇的思路。才能有新的突破。所以说思路哪里来,靠刨根究底得来,什么东西都浅尝辄止 话,根本是学到皮毛。

但是我们也不能总是陷在这些小的细节里寻找臭虫,有时候也需要站在稍微高的地方来把握下整体的架构,总而言之:准确,全面。

(2)为什么上面我说一般都是先Query再set呢?实际上有好多时候 的使用ObReferenceObjectByHandle函数和ObQueryNameString函数。
这种方法更为通用,而且可以获取完整的信息。
一般这样使用:
PFILE_OBJECT        pFileObject;
UNICODE_STRING      DosName;
UNICODE_STRING      uniFileName;
ANSI_STRING         aniFileName;

ObReferenceObjectByHandle(FileHandle, 0, 0, KernelMode, &pFileObject, NULL);
IoVolumeDeviceToDosName(pFileObject->DeviceObject, &DosName);

KdPrint(("[nokyo] %s%s", DosName.Buffer, pFileObject->FileName.Buffer));

ExFreePool(DosName.Buffer);
ObDereferenceObject(pFileObject);


我们跟踪下:
kd> u
nt!NtQueryInformationFile+0xb1:
8057bacf 51              push    ecx
8057bad0 ff75cc          push    dword ptr [ebp-34h]
8057bad3 ff3558bd5580    push    dword ptr [nt!IoFileObjectType (8055bd58)]
8057bad9 50              push    eax
8057bada ff7508          push    dword ptr [ebp+8]
8057badd e8b0090400      call    nt!ObReferenceObjectByHandle (805bc492)
实际上NtQueryInformationFile内部就是调用了ObReferenceObjectByHandle。

网上说ObReferenceObjectByHandle函数和ObQueryNameString函数配合使用可以获取完整的路径,貌似他那个是在说,要是 用NtQueryInformationFile时不能获取完整的路径的,实际上我分析完了,认为他这样的说法是错误的。
要想获得完整的路径得看ZwCreateFile这个里面的CreateOption参数你指定了什么,wdk上面有详细的说明。要获取完整的路径是完全有可能的,这个我以后会亲自实践下的。

当然我建议还是用那个组合,因为一般情况下获取的文件路径是不包括盘符的,我们得通过文件句柄获得文件 的盘符,然后来个拼接。

具体的可以参考下:http://adly.blog.sohu.com/95965411.html


(3)貌似我们可以写个这样的辅助小工具。
可以列举所有正在使用某个指定文件的所有文件路径信息。
NtQuerySystemInformation获取所有的打开的句柄信息
ZwQueryInformationFile获取指定文件的句柄信息
对比以上两个获取的句柄信息

(4)在Ring3上实现文件碎甲(解锁)功能http://www.xfocus.net/articles/200708/935.html这个文章有参考价值。


最简单的使用:
      NTSTATUS Status ;
      HANDLE fhandle ;
      UNICODE_STRING     ConfigFileName ;
      OBJECT_ATTRIBUTES objAttr ;
      IO_STATUS_BLOCK    ioStatusBlock ;
      FILE_STANDARD_INFORMATION FileInfo ;
      LONG BytesRead ;
      UCHAR *buf = NULL ;

     RtlInitUnicodeString( &ConfigFileName, FilePath ) ;
     InitializeObjectAttributes(
         &objAttr,
         &ConfigFileName,
         OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
         NULL,
         NULL ) ;

     Status = ZwCreateFile(
         &fhandle ,
         SYNCHRONIZE | FILE_READ_DATA,
         &objAttr,
         &ioStatusBlock,
         NULL,
         FILE_ATTRIBUTE_NORMAL,
         0,
         FILE_OPEN_IF,
         FILE_SYNCHRONOUS_IO_NONALERT,
         NULL,
         0
         ) ;

     if( !NT_SUCCESS(Status) )
     {
         DbgPrint("Create file filed /n") ;
         *BufferAddress = NULL ;
         *BufferSize = 0 ;
         return ;
     }

     Status = ZwQueryInformationFile(fhandle,
         &ioStatusBlock,
         &FileInfo,
         sizeof(FILE_STANDARD_INFORMATION),
         FileStandardInformation);

    
     BytesRead = FileInfo.EndOfFile.LowPart ;
     buf = ExAllocatePool(PagedPool , BytesRead) ;


     ZwReadFile(
         fhandle ,
         NULL ,
         NULL ,
         NULL ,
         &ioStatusBlock ,
         buf ,
         BytesRead ,
         NULL ,
         NULL
         ) ;

     *BufferAddress = buf ;
     *BufferSize = BytesRead ;
     ZwClose(fhandle) ;