![b8dd8c54f6cd4107fdf8211e0a3dc8bf.png](https://i-blog.csdnimg.cn/blog_migrate/e2b74e35713c29b4c39369e0416602cc.jpeg)
本文为看雪论精华文章
看雪论坛作者ID:低调putchar
![e741b88e3ecc4960ce61e65cd4d9023c.gif](https://i-blog.csdnimg.cn/blog_migrate/0167979f4b8337c611f23cd040761d33.gif)
一、概述
![e741b88e3ecc4960ce61e65cd4d9023c.gif](https://i-blog.csdnimg.cn/blog_migrate/0167979f4b8337c611f23cd040761d33.gif)
Windows操作系统中,文件系统过滤驱动的过滤设备绑定在文件系统(FSD)设备之上,监视和过滤我们的文件访问。 当应用层发起文件操作调用时内核层都会转换为IRP发送到设备栈中位于栈顶的设备,然后通过IO栈单元(IO_STACK_LOCATION)保存一些参数把IRP请求继续向下层设备发送,最后至FSD,由FSD完成实际的文件操作。 卷参数块(VPB)保存了文件系统中在一个卷设备创建(卷的挂载)成功时的一些卷参数信息。wdm.h中对其结构定义如下:
typedef struct _VPB {
CSHORT Type; CSHORT Size; USHORT Flags; USHORT VolumeLabelLength; // in bytes struct _DEVICE_OBJECT *DeviceObject; //文件系统卷设备 struct _DEVICE_OBJECT *RealDevice; //卷管理器生成的卷设备 ULONG SerialNumber; ULONG ReferenceCount; WCHAR VolumeLabel[MAXIMUM_VOLUME_LABEL_LENGTH / sizeof(WCHAR)];} VPB, *PVPB;
文件系统过滤驱动对文件操作的过滤,实际上就是把过滤设备绑定在文件系统卷设备: Vpb->DeviceObject上来实现过滤的。 这里要总结的是:如何在内核层自己创建并填写IRP及下层栈单元,把请求直接送往FSD的卷设备上。常用的文件操作包括:
文件的生成/打开(IRP_MJ_CREATE)
文件的关闭(IRP_MJ_CLEANUP: 表示句柄数为0,IRP_MJ_CLOSE: 表示文件对象引用计数为0,即将销毁)
文件读/写(IRP_MJ_READ/IRP_MJ_WRITE)
文件设置(IRP_MJ_SET_INFORMATION)
文件查询(IRP_MJ_QUERY_INFORMATION)
目录下项查询(MajorFuncton: IRP_MJ_DIRECTORY_CONTROL, MinorFunction: IRP_MN_QUERY_DIRECTORY)
每类操作对应的内核函数如下表所示:
![49130264ad7a673dfc833a051a591a79.png](https://i-blog.csdnimg.cn/blog_migrate/4edf73c506e7504e1360da732392d62e.png)
![e741b88e3ecc4960ce61e65cd4d9023c.gif](https://i-blog.csdnimg.cn/blog_migrate/0167979f4b8337c611f23cd040761d33.gif)
二、自己发送IRP请求操作文件的封装
![e741b88e3ecc4960ce61e65cd4d9023c.gif](https://i-blog.csdnimg.cn/blog_migrate/0167979f4b8337c611f23cd040761d33.gif)
(1)解锁并释放MDL链
//解锁并释放MDLVOID MyFreeMdl( IN PMDL pMdl){
PMDL pMdlCurrent = pMdl, pMdlNext; while (pMdlCurrent != NULL) {
pMdlNext = pMdlCurrent->Next; if (pMdlCurrent->MdlFlags&MDL_PAGES_LOCKED) {
MmUnlockPages(pMdlCurrent); } IoFreeMdl(pMdlCurrent); pMdlCurrent = pMdlNext; } return;}
(2)文件操作完成例程:
//文件操作完成例程NTSTATUS FileOperationCompletion( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp, IN PVOID pContext OPTIONAL){
PKEVENT pkEvent = pIrp->UserEvent; ASSERT(pkEvent != NULL); //趁机拷贝完成状态 RtlCopyMemory( pIrp->UserIosb, &pIrp->IoStatus, sizeof(IO_STATUS_BLOCK) ); //检查并释放MDL if (pIrp->MdlAddress != NULL) {
MyFreeMdl(pIrp->MdlAddress); pIrp->MdlAddress = NULL; } //释放IRP IoFreeIrp(pIrp); //设置事件 KeSetEvent(pkEvent, IO_NO_INCREMENT, FALSE ); return STATUS_MORE_PROCESSING_REQUIRED;}
2. 文件的打开/生成: 与其它IRP不同,在发送IRP_MJ_CREATE请求前就需要创建并填写好文件对象,FSD的卷设备可以从根目录pRootFileObject->Vpb->DeviceObject得到,对应的卷管理器生成的卷设备可以从: pRootFileObject->Vpb->RealDevice得到。 (1)创建文件对象可以用已导出但未公开的nt!ObCreateObject,该函数原型如下:
//未公开的函数,直接声明下就可以用了//创建对象NTKERNELAPINTSTATUS NTAPI ObCreateObject( IN KPROCESSOR_MODE ObjectAttributesAccessMode OPTIONAL, IN POBJECT_TYPE Type, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext OPTIONAL, IN ULONG ObjectSize, IN ULONG PagedPoolCharge OPTIONAL, IN ULONG NonPagedPoolCharge OPTIONAL, OUT PVOID * Object);
(2)设置安全状态可以用已导出但未公开的nt!SeCreateAccessState,该函数原型如下:
//设置安全状态NTKERNELAPINTSTATUS NTAPI SeCreateAccessState( __out PACCESS_STATE AccessState, __out PAUX_ACCESS_DATA AuxData, __in ACCESS_MASK DesiredAccess, __in_opt PGENERIC_MAPPING GenericMapping);未公开的数据结构//SeCreateAccessState第2个参数typedef struct _AUX_ACCESS_DATA {
PPRIVILEGE_SET PrivilegesUsed; GENERIC_MAPPING GenericMapping; ACCESS_MASK AccessesToAudit; ACCESS_MASK MaximumAuditMask; ULONG Unknown[256];} AUX_ACCESS_DATA, *PAUX_ACCESS_DATA;
(3)自己创建并发送IRP进行文件打开/生成的实现:
//自己发送IRP请求生成/打开文件(IRP_MJ_CREATE)NTSTATUS IrpCreateFile( OUT PFILE_OBJECT *ppFileObject, OUT PDEVICE_OBJECT *ppDeviceObject, //如果成功,这里保存:(*ppFileObject)->Vpb->DeviceObject IN ACCESS_MASK DesiredAccess, IN PUNICODE_STRING punsFilePath, //必须以"盘符:\"开头, 比如: "C:\..." OUT PIO_STATUS_BLOCK pIoStatusBlock, IN PLARGE_INTEGER AllocationSize OPTIONAL, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG Disposition, IN ULONG CreateOptions, IN PVOID EaBuffer OPTIONAL, IN ULONG EaLength){
PAUX_ACCESS_DATA pAuxData = NULL; HANDLE hRoot = NULL; PFILE_OBJECT pRootObject = NULL, pFileObject = NULL; PDEVICE_OBJECT pDeviceObject = NULL, pRealDeviceObject = NULL; PIRP pIrp; PIO_STACK_LOCATION pIrpSp; NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; ACCESS_STATE AccessState; KEVENT kEvent; IO_SECURITY_CONTEXT IoSecurityContext; OBJECT_ATTRIBUTES objAttrs; UNICODE_STRING unsRootPath; IO_STATUS_BLOCK userIosb; WCHAR wRootPath[8]; //"\??\C:\" //路径长度检查(最短: "C:\") if (punsFilePath->Length < 3 * sizeof(CHAR)) {
return STATUS_INVALID_PARAMETER; } RtlZeroMemory(pIoStatusBlock, sizeof(IO_STATUS_BLOCK)); //根目录符号链接 ntStatus = RtlStringCbPrintfW( wRootPath, sizeof(wRootPath), L"\\??\\%c:\\", punsFilePath->Buffer[0]); ASSERT(NT_SUCCESS(ntStatus)); RtlInitUnicodeString(&unsRootPath, (PCWSTR)wRootPath);
3. 文件关闭的实现: 分两步: 发送主功能号为IRP_MJ_CLEANUP的IRP向FSD表明: 文件对象句柄数已为0 => 发送主功能号为IRP_MJ_CLOSE的IRP向FSD表明: 文件对象引用计数已为0,由FSD负责销毁文件对象。 (1)文件关闭1:IRP_MJ_CLEANUP
//自己发送IRP请求关闭文件的第1步(IRP_MJ_CLEANUP),表示文件对象句柄数为0NTSTATUS IrpCleanupFile( IN PDEVICE_OBJECT pDeviceObject, IN PFILE_OBJECT pFileObject){
PIRP pIrp; PIO_STACK_LOCATION pIrpSp; NTSTATUS ntStatus = STATUS_UNSUCCESSFUL; KEVENT kEvent; IO_STATUS_BLOCK userIosb; //卷参数块检查 if (pFileObject->Vpb == NULL || pFileObject->Vpb->DeviceObject == NULL) {
return STATUS_INVALID_PARAMETER; } // // IRP_MJ_CLEANUP // //分配IRP pIrp = IoAllocateIrp(pDeviceObject->StackSize, FALSE); if (pIrp == NULL) {
return STATUS_INSUFFICIENT_RESOURCES; } //填写IRP pIrp->MdlAddress = NULL; pIrp->AssociatedIrp.SystemBuffer = NULL; pIrp->UserBuffer = NULL; pIrp->Flags = IRP_CLOSE_OPERATION | IRP_SYNCHRONOUS_API; pIrp->RequestorMode = KernelMode; pIrp->UserIosb = &userIosb; KeInitializeEvent(&kEvent, NotificationEvent, FALSE); pIrp->UserEvent = &kEvent; pIrp->Tail.Overlay.Thread = PsGetCurrentThrea