在开发Windows下文件系统过滤驱动程序时,我们经常需要先查询一下文件的属性信息,为了实现这个小目标,可以调用Windows Native API函数ZwQueryInformationFile并提供希望查询的文件信息类的名字及结构即可。不过如果我们在驱动程序当中自己处理信息查询请求,可以避免IRP重入的问题。
1.自己创建文件信息查询IRP
NTSTATUS
QueryFileInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_OBJECT FileObject,
FILE_INFORMATION_CLASS FileInformationClass,
PVOID FileInfo,
ULONG FileInfoLength
)
{
PIRP Irp;
KEVENT event;
IO_STATUS_BLOCK IoStatusBlock;
PIO_STACK_LOCATION IoStackLocation;
//
// Initialize the event
//
KeInitializeEvent(&event, NotificationEvent, FALSE);
//
// 分配一个IRP,用目标设备对象的StackSize作为新创建IRP的堆栈大小,这是
// 标准的提供堆栈大小的方法。
//
Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
if (!Irp)
return FALSE;
//
// 设置IRP头的相关参数,由于我们采用IoAllocateIrp创建的IRP,因此很多
// 域要自己设置,包括安装输入缓冲区,同步事件以及IRP标志字段,这个字段
// 对文件信息查询没有太大的用处,对文件读写十分关键。
//
Irp->AssociatedIrp.SystemBuffer = FileInfo;
Irp->UserEvent = &event;
Irp->UserIosb = &IoStatusBlock;
Irp->Tail.Overlay.Thread = PsGetCurrentThread();
Irp->Tail.Overlay.OriginalFileObject = FileObject;
Irp->RequestorMode = KernelMode;
Irp->Flags = 0;
//
// 设置IRP堆栈位置中的相关参数,最重要的是MajorFunction,它决定了这个
// IRP的主要功能,另外Parameters.QueryFile.FileInformationClass定义了
// 希望查询哪种文件信息。
//
IoStackLocation = IoGetNextIrpStackLocation(Irp);
IoStackLocation->MajorFunction = IRP_MJ_QUERY_INFORMATION;
IoStackLocation->DeviceObject = DeviceObject;
IoStackLocation->FileObject = FileObject;
IoStackLocation->Parameters.QueryFile.Length = FileInfoLength;
IoStackLocation->Parameters.QueryFile.FileInformationClass = FileInformationClass;
//
// 安装IRP完成例程
//
IoSetCompletionRoutine(Irp, QueryFileInformationCompleted, 0, TRUE, TRUE, TRUE);
(void) IoCallDriver(DeviceObject, Irp);
//
// 等待同步时间返回,我们在IRP的完成例程当中将事件置为信号态
//
KeWaitForSingleObject(&event, Executive, KernelMode, TRUE, 0);
return( NT_SUCCESS( IoStatusBlock.Status ));
}
NTSTATUS
QueryFileInformationCompleted(
PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context
)
{
//
// Copy the status information back into the "user" IOSB.
//
*Irp->UserIosb = Irp->IoStatus;
if( !NT_SUCCESS(Irp->IoStatus.Status) ) {
DbgPrint(("QueryFileInformationCompleted ERROR ON IRP: %x/n", Irp->IoStatus.Status ));
}
//
// 在完成例程中设置同步事件为信号态。
//
KeSetEvent(Irp->UserEvent, 0, FALSE);
//
// 在完成例程里需要自己释放刚刚创建的IRP,因为I/O管理器不会为我们做这个工作
//
IoFreeIrp(Irp);
//
// 我们返回STATUS_MORE_PROCESSING_REQUIRED,就是告诉I/O管理器这个IRP还有其它
// 的进程在访问,请不要将其删除,事实上这个IRP应该由我们自己来释放而不是I/O
// 管理器,所以在自己创建的IRP的完成例程当中必须返回这个值。另外,这个我们不
// 能为这个IRP调用IoCompleteRequest。
//
return STATUS_MORE_PROCESSING_REQUIRED;
}
2.文件基本信息类在IFS中的定义如下:
typedef struct _FILE_BASIC_INFORMATION { // ntddk wdm nthal
LARGE_INTEGER CreationTime; // ntddk wdm nthal
LARGE_INTEGER LastAccessTime; // ntddk wdm nthal
LARGE_INTEGER LastWriteTime; // ntddk wdm nthal
LARGE_INTEGER ChangeTime; // ntddk wdm nthal
ULONG FileAttributes; // ntddk wdm nthal
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; // ntddk wdm nthal
该结构包含了文件的创建时间以及修改时间等等,另外,文件属性域FileAttributes包含了用来记录该文件的属性,包括是否系统文件、隐藏文件等等,这个字段可能是如下标记的组合:
#define FILE_ATTRIBUTE_READONLY 0x00000001 // 只读
#define FILE_ATTRIBUTE_HIDDEN 0x00000002 // 隐藏
#define FILE_ATTRIBUTE_SYSTEM 0x00000004 // 系统
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 // 目录
#define FILE_ATTRIBUTE_ARCHIVE 0x00000020 // 流
#define FILE_ATTRIBUTE_DEVICE 0x00000040 // 设备
#define FILE_ATTRIBUTE_NORMAL 0x00000080 // 正常
#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 // 临时
#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200 // 稀疏文件
#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400 //
#define FILE_ATTRIBUTE_COMPRESSED 0x00000800 // 压缩
#define FILE_ATTRIBUTE_OFFLINE 0x00001000 //
#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000 //
#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000 // 加密,仅NTFS5以后有效
#define FILE_ATTRIBUTE_VALID_FLAGS 0x00007fb7
#define FILE_ATTRIBUTE_VALID_SET_FLAGS 0x000031a7
所以我们可以通过测试成功打开的文件的基本信息中的属性字段就能够判断该文件的性质。
函数定义如下:
NTSTATUS
QueryFileBasicInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_OBJECT FileObject,
PFILE_BASIC_INFORMATION FileBasicInfo,
ULONG FileInfoLength
)
{
return QueryFileInformation(
DeviceObject,
FileObject,
FileBasicInformation,
FileBasicInfo,
FileInfoLength
);
}
调用实例:
FileBasicInfo = ExAllocatePoolWithTag(NonPagedPool, sizeof(FILE_BASIC_INFORMATION), FILTER_POOL_FLAG);
if(!FileBasicInfo)
{
FreeFullPathNameBuffer(AnsiPathName, fullPathName);
ExFreePool(FileCtx);
return CompleteRequest(Irp, STATUS_INSUFFICIENT_RESOURCES, 0);
}
Status = QueryFileBasicInformation(
hookExt->FileSystem,
FileObject,
FileBasicInfo,
sizeof(FILE_BASIC_INFORMATION)
);
if(!NT_SUCCESS(Status))
{
ExFreePool(FileBasicInfo);
Irp->IoStatus.Status = Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
if(FileBasicInfo->FileAttributes & FILE_ATTRIBUTE_SYSTEM)
{
ExFreePool(FileBasicInfo);
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
if(FileBasicInfo->FileAttributes & FILE_ATTRIBUTE_ENCRYPTED)
{
ExFreePool(FileBasicInfo);
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
if(FileBasicInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
ExFreePool(FileBasicInfo);
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
ExFreePool(FileBasicInfo);
3.标准文件信息定义如下:
typedef struct _FILE_STANDARD_INFORMATION { // ntddk wdm nthal
LARGE_INTEGER AllocationSize; // ntddk wdm nthal
LARGE_INTEGER EndOfFile; // ntddk wdm nthal
ULONG NumberOfLinks; // ntddk wdm nthal
BOOLEAN DeletePending; // ntddk wdm nthal
BOOLEAN Directory; // ntddk wdm nthal
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; // ntddk wdm nthal
AllocationSize是文件创建时或者文件修改以后文件系统为该文件分配的磁盘空间的大小,这个值一般是物理扇区大小的整数倍,并且比文件的实际大小要大,如果文件的实际大小超过这个值,则文件系统会自动将这个值扩大以满足文件增大的需要。
EndOfFile描述了文件的真实大小。
NumberOfLinks描述了文件的所有硬连接数。
Directory描述了文件是否是目录,我们可以通过对文件标准信息的查询获取文件长度以及是否为目录的信息。
函数定义如下:
NTSTATUS
QueryFileStandardInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_OBJECT FileObject,
PFILE_STANDARD_INFORMATION FileStandardInfo,
ULONG FileInfoLength
)
{
return QueryFileInformation(
DeviceObject,
FileObject,
FileStandardInformation,
FileStandardInfo,
FileInfoLength
);
}
调用实例同上。