首页
社区
课程
招聘
[原创]内核层自己发送IRP请求操作文件全面总结(已完整调试, x86/x64各系统通用)
2020-12-8 00:30 16886

[原创]内核层自己发送IRP请求操作文件全面总结(已完整调试, x86/x64各系统通用)

2020-12-8 00:30
16886

一. 概述    

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)

每类操作对应的内核函数如下表所示:

IRP对应的内核函数操作
IRP_MJ_CREATEnt!NtCreateFile文件的生成/打开,需要创建文件对象
IRP_MJ_CLEANUPnt!NtClose文件关闭1:  发送该请求,向FSD表明,文件对象句柄数已为0了
IRP_MJ_CLOSEnt!ObDereferenceObject文件关闭2: 发送该请求,向FSD表明, 文件对象引用计数已为0了,需要销毁文件对象
IRP_MJ_READnt!NtReadFile读文件
IRP_MJ_WRITEnt!NTWriteFile写文件
IRP_MJ_SET_INFORMATIONnt!NtSetInformationFile 文件设置, 常用的有: 删除,重命名,属性设置等
IRP_MJ_QUERY_INFORMATIONnt!NtQueryInformationFile文件查询
IRP_MJ_DIRECTORY_CONTROL(MinorFunction: IRP_MN_QUERY_DIRECTORY)nt!NtQueryDirectoryFile目录下项查询


二. 自己发送IRP请求操作文件的封装:

1. 实现MDL链解锁及释放,完成例程的功能, 后续的各IRP完成时统一使用

(1)解锁并释放MDL链

//解锁并释放MDL
VOID 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, 该函数原型如下:

//未公开的函数,直接声明下就可以用了
//创建对象
NTKERNELAPI
NTSTATUS 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,该函数原型如下:

//设置安全状态
NTKERNELAPI
NTSTATUS 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);

	do {
		//打开根目录
		InitializeObjectAttributes(&objAttrs,
			&unsRootPath,
			OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
			NULL,
			NULL);
		ntStatus = IoCreateFile(&hRoot,
			FILE_READ_ATTRIBUTES | SYNCHRONIZE,
			&objAttrs,
			&userIosb,
			NULL,
			FILE_ATTRIBUTE_NORMAL,
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
			FILE_OPEN,
			FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
			NULL,
			0,
			CreateFileTypeNone,
			NULL,
			IO_NO_PARAMETER_CHECKING);
		*pIoStatusBlock = userIosb;
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//得到根目录文件对象
		ntStatus = ObReferenceObjectByHandle(hRoot,
			FILE_READ_ATTRIBUTES,
			*IoFileObjectType,
			KernelMode,
			(PVOID*)&pRootObject,
			NULL);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}
		if (pRootObject->Vpb == NULL || pRootObject->Vpb->DeviceObject == NULL || pRootObject->Vpb->RealDevice == NULL) {
			ntStatus = STATUS_INVALID_PARAMETER;
			break;
		}

		//得到卷设备
		pDeviceObject = pRootObject->Vpb->DeviceObject;  //文件系统卷设备
		pRealDeviceObject = pRootObject->Vpb->RealDevice; //卷管理器生成的卷设备(物理设备)
                ObDereferenceObject((PVOID)pRootObject);
		pRootObject = NULL;
		ZwClose(hRoot);
		hRoot = NULL;

		//创建文件对象
		InitializeObjectAttributes(&objAttrs,
			NULL,
			OBJ_CASE_INSENSITIVE,
			NULL,
			NULL);
		ntStatus = ObCreateObject(
			KernelMode,
			*IoFileObjectType,
			&objAttrs,
			KernelMode,
			NULL,
			sizeof(FILE_OBJECT),
			0,
			0,
			(PVOID*)&pFileObject);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//填写文件对象内容
		RtlZeroMemory(pFileObject,
			sizeof(FILE_OBJECT)
		);
		pFileObject->Type = IO_TYPE_FILE;
		pFileObject->Size = sizeof(FILE_OBJECT);
		pFileObject->DeviceObject = pRealDeviceObject;
		pFileObject->Flags = FO_SYNCHRONOUS_IO;
		pFileObject->FileName.Buffer = &punsFilePath->Buffer[2];
		pFileObject->FileName.Length = punsFilePath->Length - 2 * sizeof(WCHAR);
		pFileObject->FileName.MaximumLength = punsFilePath->MaximumLength - 2 * sizeof(WCHAR);
		KeInitializeEvent(&pFileObject->Lock,
			SynchronizationEvent,
			FALSE);
		KeInitializeEvent(&pFileObject->Event,
			NotificationEvent,
			FALSE);

		//分配AUX_ACCESS_DATA缓冲区
		pAuxData = (PAUX_ACCESS_DATA)ExAllocatePoolWithTag(NonPagedPool,
			sizeof(AUX_ACCESS_DATA),
			MY_TAG);
		if (pAuxData == NULL) {
			ntStatus = STATUS_INSUFFICIENT_RESOURCES;
			break;
		}

		//设置安全状态
		ntStatus = SeCreateAccessState(&AccessState,
			pAuxData,
			DesiredAccess,
			IoGetFileObjectGenericMapping());
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//分配IRP
		pIrp = IoAllocateIrp(pDeviceObject->StackSize,
			FALSE);
		if (pIrp == NULL) {
			ntStatus = STATUS_INSUFFICIENT_RESOURCES;
			break;
		}

		//填写IRP
		pIrp->MdlAddress = NULL;
		pIrp->UserBuffer = NULL;
		pIrp->AssociatedIrp.SystemBuffer = EaBuffer;
		pIrp->Flags = IRP_CREATE_OPERATION | IRP_SYNCHRONOUS_API;
		pIrp->RequestorMode = KernelMode;
		pIrp->PendingReturned = FALSE;
		pIrp->Cancel = FALSE;
		pIrp->CancelRoutine = NULL;
		KeInitializeEvent(&kEvent,
			NotificationEvent,
			FALSE);
		pIrp->UserEvent = &kEvent;
		pIrp->UserIosb = &userIosb;
		pIrp->Overlay.AllocationSize.QuadPart = AllocationSize != NULL ? AllocationSize->QuadPart : 0;
		
		pIrp->Tail.Overlay.Thread = PsGetCurrentThread();
		pIrp->Tail.Overlay.OriginalFileObject = pFileObject;
		pIrp->Tail.Overlay.AuxiliaryBuffer = NULL;

		//得到下层栈空间
		pIrpSp = IoGetNextIrpStackLocation(pIrp);
		ASSERT(pIrpSp != NULL);

		//填写下层栈空间
		pIrpSp->MajorFunction = IRP_MJ_CREATE;
		IoSecurityContext.SecurityQos = NULL;
		IoSecurityContext.AccessState = &AccessState;
		IoSecurityContext.DesiredAccess = DesiredAccess;
		IoSecurityContext.FullCreateOptions = 0;
		pIrpSp->Parameters.Create.SecurityContext = &IoSecurityContext;
		pIrpSp->Parameters.Create.Options = (Disposition << 24) | CreateOptions;
		pIrpSp->Parameters.Create.FileAttributes = (USHORT)FileAttributes;
		pIrpSp->Parameters.Create.ShareAccess = (USHORT)ShareAccess;
		pIrpSp->Parameters.Create.EaLength = EaLength;
		pIrpSp->FileObject = pFileObject;
		pIrpSp->DeviceObject = pDeviceObject;

		//设置完成例程: IoSetCompletionRoutine(pIrp,FileOperationCompletion,NULL,TRUE,TRUE,TRUE);
		pIrpSp->CompletionRoutine = FileOperationCompletion;
		pIrpSp->Context = NULL;
		pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

		//下发IRP请求
		ntStatus = IoCallDriver(pDeviceObject,
			pIrp);

		//等候完成
		(VOID)KeWaitForSingleObject(&kEvent,
			Executive,
			KernelMode,
			FALSE,
			NULL);
		
                //得到完成状态
		*pIoStatusBlock = userIosb;
		ntStatus = pIoStatusBlock->Status;
    } while (FALSE);

	if (NT_SUCCESS(ntStatus)) {
		//成功后的处理
		ASSERT(pFileObject != NULL && pFileObject->DeviceObject != NULL);
		InterlockedIncrement(&pFileObject->DeviceObject->ReferenceCount);
		if (pFileObject->Vpb != NULL) {
			InterlockedIncrement((PLONG)&pFileObject->Vpb->ReferenceCount);
		}
		*ppFileObject = pFileObject;
		*ppDeviceObject = pDeviceObject;
	}else {
		//失败后的处理
		if (pFileObject != NULL) {
			pFileObject->DeviceObject = NULL;
			ObDereferenceObject(pFileObject);
		}
		pIoStatusBlock->Status = ntStatus;
	}
	
	//检查并释放相关资源
	if (pAuxData != NULL) {
		ExFreePoolWithTag((pAuxData),
			MY_TAG);
	}
    if (pRootObject != NULL) {
		ObDereferenceObject((PVOID)pRootObject);
	}
	if (hRoot != NULL) {
		ZwClose(hRoot);
	}

	return ntStatus;
}


3. 文件关闭的实现:  分两步:1)发送主功能号为IRP_MJ_CLEANUP的IRP向FSD表明: 文件对象句柄数已为0 =>发送主功能号为IRP_MJ_CLOSE的IRP向FSD表明: 文件对象引用计数已为0, 由FSD负责销毁文件对象

(1)文件关闭1: IRP_MJ_CLEANUP

//自己发送IRP请求关闭文件的第1步(IRP_MJ_CLEANUP),表示文件对象句柄数为0
NTSTATUS 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 = PsGetCurrentThread();
	pIrp->Tail.Overlay.OriginalFileObject = pFileObject;

	//得到下层栈空间
	pIrpSp = IoGetNextIrpStackLocation(pIrp);
	ASSERT(pIrpSp != NULL);

	//填写下层栈空间
	pIrpSp->MajorFunction = IRP_MJ_CLEANUP;
	pIrpSp->FileObject = pFileObject;
	pIrpSp->DeviceObject = pDeviceObject;

	//设置完成例程: IoSetCompletionRoutine(pIrp,FileOperationCompletion,NULL,TRUE,TRUE,TRUE);
	pIrpSp->CompletionRoutine = FileOperationCompletion;
	pIrpSp->Context = NULL;
	pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

	//下发请求
	ntStatus = IoCallDriver(pDeviceObject,
		pIrp);
	
	//等待请求结束
	KeWaitForSingleObject(&kEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL);

	//得到完成状态
	ntStatus = userIosb.Status;

	return ntStatus;
}

(2) 文件关闭2:IRP_MJ_CLOSE

//自己发送IRP请求关闭文件的第2步(IRP_MJ_CLOSE),表示引用计数数为0
NTSTATUS IrpCloseFile(
	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->Flags & FO_DIRECT_DEVICE_OPEN)) {
		InterlockedDecrement(&pFileObject->Vpb->ReferenceCount);
		pFileObject->Flags |= FO_FILE_OPEN_CANCELLED;
	}

	//分配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 = PsGetCurrentThread();
	pIrp->Tail.Overlay.OriginalFileObject = pFileObject;

	//得到IRP下层栈空间
	pIrpSp = IoGetNextIrpStackLocation(pIrp);
	ASSERT(pIrpSp != NULL);

	//填写IRP下层栈空间
	pIrpSp->MajorFunction = IRP_MJ_CLOSE;
	pIrpSp->FileObject = pFileObject;
	pIrpSp->DeviceObject = pDeviceObject;

	//设置完成例程: IoSetCompletionRoutine(pIrp,FileOperationCompletion,NULL,TRUE,TRUE,TRUE);
	pIrpSp->CompletionRoutine = FileOperationCompletion;
	pIrpSp->Context = NULL;
	pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

	//下发IRP请求
	ntStatus = IoCallDriver(pDeviceObject,
		pIrp);

	//等待请求结束
	KeWaitForSingleObject(&kEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL);

	//得到完成状态
	ntStatus = userIosb.Status;

	return ntStatus;
}


4. 文件读/写的实现:

//文件读/写(IRP_MJ_READ/IRP_MJ_WRITE)
NTSTATUS IrpReadOrWriteFile(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PFILE_OBJECT pFileObject,
	OUT PIO_STATUS_BLOCK pIoStatusBlock,
	IN OUT PVOID pBuffer,
	IN ULONG ulLength,
	IN PLARGE_INTEGER pliByteOffset OPTIONAL,
	IN BOOLEAN bRead
)
{
	PIRP pIrp;
	PIO_STACK_LOCATION pIrpSp;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
	IO_STATUS_BLOCK userIosb;
	KEVENT kEvent;

    //分配IRP
	pIrp = IoAllocateIrp(
		pDeviceObject->StackSize,
		FALSE);
	RtlZeroMemory(pIoStatusBlock, 
		sizeof(IO_STATUS_BLOCK));
	if (pIrp == NULL) {
		pIoStatusBlock->Status = STATUS_INSUFFICIENT_RESOURCES;
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	//填写IRP
	pIrp->UserBuffer = NULL;
	pIrp->MdlAddress = NULL;
	pIrp->AssociatedIrp.SystemBuffer = NULL;
	if (pDeviceObject->Flags&DO_BUFFERED_IO) {
		//缓冲读写方式
		pIrp->AssociatedIrp.SystemBuffer = pBuffer;
	}else if (pDeviceObject->Flags&DO_DIRECT_IO) {
		//直接读写方式
		pIrp->MdlAddress = IoAllocateMdl(
			pBuffer,
			ulLength,
			FALSE,
			FALSE,
			NULL);
		if (pIrp->MdlAddress == NULL) {
			IoFreeIrp(pIrp);
			pIoStatusBlock->Status = STATUS_INSUFFICIENT_RESOURCES;
			return STATUS_INSUFFICIENT_RESOURCES;
		}
		MmBuildMdlForNonPagedPool(pIrp->MdlAddress);
	}else {
		//非缓冲非直接读写方式: 在文件读写请求中比较常见
		pIrp->UserBuffer = pBuffer;
	}
	pIrp->Flags = IRP_DEFER_IO_COMPLETION | IRP_NOCACHE;
	pIrp->Flags |= (bRead ? IRP_READ_OPERATION : IRP_WRITE_OPERATION);
	pIrp->RequestorMode = KernelMode;
	pIrp->UserIosb = &userIosb;
	KeInitializeEvent(&kEvent,
		NotificationEvent,
		FALSE);
	pIrp->UserEvent = &kEvent;
	pIrp->Tail.Overlay.Thread = PsGetCurrentThread();
	pIrp->Tail.Overlay.OriginalFileObject = pFileObject;

	//获取下层栈空间
	pIrpSp = IoGetNextIrpStackLocation(pIrp);
	ASSERT(pIrpSp != NULL);

	pIrpSp->MajorFunction = (bRead ? IRP_MJ_READ : IRP_MJ_WRITE);
	pIrpSp->MinorFunction = IRP_MN_NORMAL;
	if (bRead) {
		pIrpSp->Parameters.Read.Length = ulLength;
		pIrpSp->Parameters.Read.ByteOffset.QuadPart = 0;
		if (pliByteOffset != NULL) {
			pIrpSp->Parameters.Read.ByteOffset.QuadPart = pliByteOffset->QuadPart;
		}
	}else {
		pIrpSp->Parameters.Write.Length = ulLength;
		pIrpSp->Parameters.Write.ByteOffset.QuadPart = 0;
		if (pliByteOffset != NULL) {
			pIrpSp->Parameters.Write.ByteOffset.QuadPart = pliByteOffset->QuadPart;
		}
	}
	pIrpSp->FileObject = pFileObject;
	pIrpSp->DeviceObject = pDeviceObject;

	//设置完成例程: IoSetCompletionRoutine(pIrp,FileOperationCompletion,NULL,TRUE,TRUE,TRUE);
	pIrpSp->CompletionRoutine = FileOperationCompletion;
	pIrpSp->Context = NULL;
	pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

	//下发IRP请求
	ntStatus = IoCallDriver(pDeviceObject,
		pIrp);

	//等候IRP完成
	KeWaitForSingleObject((PVOID)&kEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL);

	//保存完成状态
	*pIoStatusBlock = userIosb;
	ntStatus = pIoStatusBlock->Status;

	return ntStatus;
}


5. 文件设置的实现:

//文件设置(IRP_MJ_SET_INFORMATION)
NTSTATUS IrpSetInformationFile(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PFILE_OBJECT pFileObject,
	IN PFILE_OBJECT pTargetFileObject OPTIONAL,
	OUT PIO_STATUS_BLOCK pIoStatusBlock,
	IN PVOID pFileInformation,
	IN ULONG ulLength,
	IN FILE_INFORMATION_CLASS FileInformationClass
)
{
	PIRP pIrp = NULL;
	PIO_STACK_LOCATION pIrpSp;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
	IO_STATUS_BLOCK userIosb;
	KEVENT kEvent;

	//分配IRP
	pIrp = IoAllocateIrp(
		pDeviceObject->StackSize,
		FALSE);
	RtlZeroMemory(pIoStatusBlock, 
		sizeof(IO_STATUS_BLOCK));
	if (pIrp == NULL) {
		pIoStatusBlock->Status = STATUS_INSUFFICIENT_RESOURCES;
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	//填写IRP
	pIrp->MdlAddress = NULL;
	pIrp->UserBuffer = NULL;
	pIrp->AssociatedIrp.SystemBuffer = pFileInformation;
	pIrp->Flags = IRP_SYNCHRONOUS_API;
	pIrp->RequestorMode = KernelMode;
	pIrp->UserIosb = &userIosb;
	KeInitializeEvent(&kEvent,
		NotificationEvent,
		FALSE);
	pIrp->UserEvent = &kEvent;
	pIrp->Tail.Overlay.Thread = PsGetCurrentThread();
	pIrp->Tail.Overlay.OriginalFileObject = pFileObject;

	//得到下层栈空间
	pIrpSp = IoGetNextIrpStackLocation(pIrp);
	ASSERT(pIrpSp != NULL);

	//填写栈空间
	pIrpSp->MajorFunction = IRP_MJ_SET_INFORMATION;
	pIrpSp->Parameters.SetFile.Length = ulLength;
	pIrpSp->Parameters.SetFile.FileInformationClass = FileInformationClass;
	pIrpSp->Parameters.SetFile.FileObject = pTargetFileObject;
	//对于文件重命名,创建硬链接需要考虑: ReplaceIfExists
	switch (FileInformationClass) {
	case FileRenameInformation:
		pIrpSp->Parameters.SetFile.ReplaceIfExists = ((PFILE_RENAME_INFORMATION)pFileInformation)->ReplaceIfExists;
		break;
	case FileLinkInformation:
		pIrpSp->Parameters.SetFile.ReplaceIfExists = ((PFILE_LINK_INFORMATION)pFileInformation)->ReplaceIfExists;
		break;
	}
	pIrpSp->FileObject = pFileObject;
	pIrpSp->DeviceObject = pDeviceObject;

	//设置完成例程: IoSetCompletionRoutine(pIrp,FileOperationCompletion,NULL,TRUE,TRUE,TRUE);
	pIrpSp->CompletionRoutine = FileOperationCompletion;
	pIrpSp->Context = NULL;
	pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

	//下发IRP请求
	ntStatus = IoCallDriver(pDeviceObject,
		pIrp);

	//等候IRP完成
	KeWaitForSingleObject((PVOID)&kEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL);

	//保存完成状态
	*pIoStatusBlock = userIosb;
	ntStatus = pIoStatusBlock->Status;

	return ntStatus;
}


6. 文件查询的实现:

//文件查询(IRP_MJ_QUERY_INFORMATION)
NTSTATUS IrpQueryInformationFile(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PFILE_OBJECT pFileObject,
	OUT PIO_STATUS_BLOCK pIoStatusBlock,
	OUT PVOID pFileInformation,
	IN ULONG ulLength,
	IN FILE_INFORMATION_CLASS FileInformationClass
)
{
	PIRP pIrp;
	PIO_STACK_LOCATION pIrpSp;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
	IO_STATUS_BLOCK userIosb;
	KEVENT kEvent;
	
	//分配IRP
	pIrp = IoAllocateIrp(pDeviceObject->StackSize,
		FALSE);
	RtlZeroMemory(pIoStatusBlock, 
		sizeof(IO_STATUS_BLOCK));
	if (pIrp == NULL) {
		pIoStatusBlock->Status = STATUS_INSUFFICIENT_RESOURCES;
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	//填写IRP
	pIrp->MdlAddress = NULL;
	pIrp->AssociatedIrp.SystemBuffer = pFileInformation;
	pIrp->UserBuffer = NULL;
	pIrp->Flags = IRP_SYNCHRONOUS_API;
	pIrp->RequestorMode = KernelMode;
	pIrp->UserIosb = &userIosb;
	KeInitializeEvent(&kEvent,
		NotificationEvent,
		FALSE);
	pIrp->UserEvent = &kEvent;
	pIrp->Tail.Overlay.Thread = PsGetCurrentThread();
	pIrp->Tail.Overlay.OriginalFileObject = pFileObject;

	//获取下层栈空间
	pIrpSp = IoGetNextIrpStackLocation(pIrp);
	ASSERT(pIrpSp != NULL);

	//填写栈空间
	pIrpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
	pIrpSp->Parameters.QueryFile.FileInformationClass = FileInformationClass;
	pIrpSp->Parameters.QueryFile.Length = ulLength;
	pIrpSp->FileObject = pFileObject;
	pIrpSp->DeviceObject = pDeviceObject;

	//设置完成例程: IoSetCompletionRoutine(pIrp,FileOperationCompletion,NULL,TRUE,TRUE,TRUE);
	pIrpSp->CompletionRoutine = FileOperationCompletion;
	pIrpSp->Context = NULL;
	pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

	//下发IRP请求
	ntStatus = IoCallDriver(
	    pDeviceObject,
		pIrp);

	//等候请求完成
	KeWaitForSingleObject(
		(PVOID)&kEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL);
	
    //得到完成状态
	*pIoStatusBlock = userIosb;
	ntStatus = pIoStatusBlock->Status;

	return ntStatus;
}


7. 目录下项查询项查询的实现:  IRP主功能号为: IRP_MJ_DIRECTORY_CONTROL, 次功能号为: IRP_MN_QUERY_DIRECTORY

//获取目录下的文件及子目录(MajorFunction: IRP_MJ_DIRECTORY_CONTROL, MinorFunction: IRP_MN_QUERY_DIRECTORY)
NTSTATUS IrpQueryDirectoryFile(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PFILE_OBJECT pFileObject,
	OUT PIO_STATUS_BLOCK pIoStatusBlock,
	OUT PVOID pFileInformation,
	IN ULONG ulLength,
	IN FILE_INFORMATION_CLASS FileInformationClass,
	IN BOOLEAN bReturnSingleEntry,
	IN PUNICODE_STRING punsFileName OPTIONAL,
	IN BOOLEAN bRestartScan
) 
{
	PIRP pIrp;
	PIO_STACK_LOCATION pIrpSp;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
	KEVENT kEvent;
	IO_STATUS_BLOCK userIosb;
	
	//分配IRP
	pIrp = IoAllocateIrp(pDeviceObject->StackSize,
		FALSE);
	RtlZeroMemory(pIoStatusBlock,
		sizeof(IO_STATUS_BLOCK));
	if (pIrp == NULL) {
		pIoStatusBlock->Status = STATUS_INSUFFICIENT_RESOURCES;
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	//填写IRP
	pIrp->MdlAddress = NULL;
	pIrp->AssociatedIrp.SystemBuffer = NULL;
	pIrp->UserBuffer = pFileInformation;
	pIrp->Flags = IRP_SYNCHRONOUS_API;
	pIrp->RequestorMode = KernelMode;
	pIrp->UserIosb = &userIosb;
	KeInitializeEvent(&kEvent,
		NotificationEvent,
		FALSE);
	pIrp->UserEvent = &kEvent;
	pIrp->Tail.Overlay.Thread = PsGetCurrentThread();
	pIrp->Tail.Overlay.OriginalFileObject = pFileObject;

	//得到下层栈空间
	pIrpSp = IoGetNextIrpStackLocation(pIrp);
	ASSERT(pIrpSp != NULL);
	
	//填写下层栈空间
	pIrpSp->MajorFunction = IRP_MJ_DIRECTORY_CONTROL;
	pIrpSp->MinorFunction = IRP_MN_QUERY_DIRECTORY;
	pIrpSp->Flags = 0;
	if (bReturnSingleEntry) pIrpSp->Flags |= SL_RETURN_SINGLE_ENTRY;
	if (bRestartScan) pIrpSp->Flags |= SL_RESTART_SCAN;
	pIrpSp->Parameters.QueryDirectory.Length = ulLength;
	pIrpSp->Parameters.QueryDirectory.FileName = punsFileName;
	pIrpSp->Parameters.QueryDirectory.FileInformationClass = FileInformationClass;
	pIrpSp->DeviceObject = pDeviceObject;
	pIrpSp->FileObject = pFileObject;

	//设置完成例程: IoSetCompletionRoutine(pIrp, FileOperationCompletion, NULL, TRUE, TRUE, TRUE);
	pIrpSp->CompletionRoutine = FileOperationCompletion;
	pIrpSp->Context = NULL;
	pIrpSp->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL;

	//下发IO请求
	ntStatus = IoCallDriver(pDeviceObject,
		pIrp);

	//等候请求完成
	KeWaitForSingleObject((PVOID)&kEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL);

	//得到完成状态
	*pIoStatusBlock = userIosb;
	ntStatus = pIoStatusBlock->Status;

	return ntStatus;
}


三. 文件IRP操作测试: 这里实现常用的几个功能就可以把前面的文件操作涵盖

  1.   文件删除

//自己发送IRP请求删除文件
NTSTATUS RootkitDeleteFile(
	IN PDEVICE_OBJECT pDeviceObject, //如果指定为pFileObject->Vpb->DeviceObject,可绕过文件系统过滤驱动
	IN PFILE_OBJECT pFileObject
)
{
	PFILE_BASIC_INFORMATION pFileBasicInfo = NULL;
	PFILE_DISPOSITION_INFORMATION pFileDispInfo = NULL;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
	IO_STATUS_BLOCK iosb;
	SECTION_OBJECT_POINTERS stSectionObjPointers;

	PAGED_CODE();

	ASSERT(pDeviceObject != NULL && pFileObject != NULL);
	do {
		//分配属性缓冲区
		pFileBasicInfo = (PFILE_BASIC_INFORMATION)ExAllocatePoolWithTag(PagedPool,
			sizeof(FILE_BASIC_INFORMATION),
			MY_TAG);
		if (pFileBasicInfo == NULL) {
			ntStatus = STATUS_INSUFFICIENT_RESOURCES;
			break;
		}

		//下发IRP_MJ_QUERY_INFORMATION请求查询文件属性
		ntStatus = IrpQueryInformationFile(pDeviceObject,
			pFileObject,
			&iosb,
			(PVOID)pFileBasicInfo,
			sizeof(FILE_BASIC_INFORMATION),
			FileBasicInformation);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//检查并去掉只读属性
		if (pFileBasicInfo->FileAttributes&FILE_ATTRIBUTE_READONLY) {
			pFileBasicInfo->FileAttributes &= ~FILE_ATTRIBUTE_READONLY;

			//下发IRP_MJ_SET_INFORMATION请求去掉文件只读属性
			ntStatus = IrpSetInformationFile(pDeviceObject,
				pFileObject,
				NULL,
				&iosb,
				(PVOID)pFileBasicInfo,
				sizeof(FILE_BASIC_INFORMATION),
				FileBasicInformation);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}
		}
		ExFreePoolWithTag((PVOID)pFileBasicInfo,
			MY_TAG);
		pFileBasicInfo = NULL;

		//分配删除操作缓冲区
		pFileDispInfo = (PFILE_DISPOSITION_INFORMATION)ExAllocatePoolWithTag(PagedPool,
			sizeof(FILE_DISPOSITION_INFORMATION),
			MY_TAG);
		if (pFileDispInfo == NULL) {
			ntStatus = STATUS_INSUFFICIENT_RESOURCES;
			break;
		}
		pFileDispInfo->DeleteFile = TRUE; //关闭时删除

		//检查并备份文件对象的SectionObjectPointer所指的内容,然后各域置空
		if (pFileObject->SectionObjectPointer != NULL) {
			stSectionObjPointers.DataSectionObject = pFileObject->SectionObjectPointer->DataSectionObject;
			stSectionObjPointers.SharedCacheMap = pFileObject->SectionObjectPointer->SharedCacheMap;
			stSectionObjPointers.ImageSectionObject = pFileObject->SectionObjectPointer->ImageSectionObject;

			pFileObject->SectionObjectPointer->DataSectionObject = NULL;
			pFileObject->SectionObjectPointer->SharedCacheMap = NULL;
			pFileObject->SectionObjectPointer->ImageSectionObject = NULL;
		}

		//下发IRP_MJ_SET_INFORMATION请求,设置关闭时删除
		ntStatus = IrpSetInformationFile(pDeviceObject,
			pFileObject,
			NULL,
			&iosb,
			(PVOID)pFileDispInfo,
			sizeof(FILE_DISPOSITION_INFORMATION),
			FileDispositionInformation);

		//检查并恢复文件对象的SectionObjectPointer所指的内容
		if (pFileObject->SectionObjectPointer != NULL) {
			pFileObject->SectionObjectPointer->DataSectionObject = stSectionObjPointers.DataSectionObject;
			pFileObject->SectionObjectPointer->SharedCacheMap = stSectionObjPointers.SharedCacheMap;
			pFileObject->SectionObjectPointer->ImageSectionObject = stSectionObjPointers.ImageSectionObject;
		}
	} while (FALSE);

	//检查并释放缓冲区
	if (pFileDispInfo != NULL) {
		ExFreePoolWithTag((PVOID)pFileDispInfo,
			MY_TAG);
	}
	if (pFileBasicInfo != NULL) {
		ExFreePoolWithTag((PVOID)pFileBasicInfo,
			MY_TAG);
	}
    return ntStatus;
}

//强删文件(设备控制请求中调用)
NTSTATUS RootkitDeleteFileForIoctl(
	IN LPCWSTR wFilePath
)
{
	PDEVICE_OBJECT pDeviceObject = NULL;
	PFILE_OBJECT pFileObject = NULL;
	NTSTATUS ntStatus = STATUS_NOT_SUPPORTED, ntLocStatus;
	IO_STATUS_BLOCK iosb;
	UNICODE_STRING unsFilePath;

	PAGED_CODE();
	ASSERT(wFilePath != NULL);

	//检查文件路径
	RtlInitUnicodeString(&unsFilePath,
		wFilePath);
	if (unsFilePath.Length < 4 * sizeof(WCHAR)) {
		return STATUS_INVALID_PARAMETER;
	}

	//判断是否以"\??\"打头
	if (wFilePath[0] == L'\\'
        &&wFilePath[1] == L'?'
		&&wFilePath[2] == L'?'
		&&wFilePath[3] == L'\\') {
		//以"\??\"打头,传入IrpCreateFile前需要去掉首部"\??\"
		unsFilePath.Buffer += 4;
		unsFilePath.Length -= 4 * sizeof(WCHAR);
		unsFilePath.MaximumLength -= 4 * sizeof(WCHAR);
	}
	
	//自己发送IRP_MJ_CREATE请求打开文件
	ntStatus = IrpCreateFile(&pFileObject,
		&pDeviceObject,
		FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | DELETE | SYNCHRONIZE,
		&unsFilePath,
		&iosb,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
		FILE_OPEN,
		FILE_SYNCHRONOUS_IO_NONALERT,
		NULL,
		0);
	if (NT_SUCCESS(ntStatus)) {
		//强删文件
		ntStatus = RootkitDeleteFile(pDeviceObject,
			pFileObject);

		//自己发送请求IRP_MJ_CLEANUP/IRP_MJ_CLOSE关闭文件
		ntLocStatus = IrpCleanupFile(pDeviceObject,
			pFileObject);
		ASSERT(NT_SUCCESS(ntLocStatus));

		if (NT_SUCCESS(ntLocStatus)) {
			ntLocStatus = IrpCloseFile(pDeviceObject,
				pFileObject);
			ASSERT(NT_SUCCESS(ntLocStatus));
		}
	}

	return ntStatus;
}


2. 文件更名: 

(1) 如果仅仅是当前路径下的文件更名, 可以把下层的栈单元参数: pIrpSp->Parameters.SetFile.FileObject = NULL

(2) 这里着重要说明的是第2种情况, 把文件移动到同一卷的不同目录下,这方面的例子比较少,经过逆向分析nt!NtSetInformationFile=>nt!IopOpenLinkOrRenameTarget,可以得出: 需要新创建移动后的目标文件对象pTargetFileObject,并填写: pIrpSp->Parameters.SetFile.FileObject=pTargetFileObject

Win10 X64(1809)分析过程如下(由于函数体过长,一些无关紧要的就不贴出来了, 有兴趣的可以自己慢慢分析,这里只贴出关键处的分析):

1)nt!NtSetInformationFile分析关键处: 

在这个内核函数中对于类型为:FileRenameInformation,FileLinkInformation的文件设置会检查:

 pFileLinkOrRenameInfo->FileName及FileLinkOrRenameInfo->RootDirectory,当:  pFileLinkOrRenameInfo->FileName[0]==L'\\'或pFileLinkOrRenameInfo->RootDirectory!=NULL时会执行调用nt!IopOpenLinkOrRenameTarget创建移动后的目标文件, 根据返回的文件句柄就可以很容易得到目标文件对象指针pTargetFileObject了。

.text:00000001400EC230 NtSetInformationFile proc near          ; DATA XREF: .pdata:00000001404EDDC4o
....
.text:00000001400ECD3C loc_1400ECD3C:                          ; CODE XREF: NtSetInformationFile+141DACj
.text:00000001400ECD3C                 cmp     word ptr [r8+14h], 5Ch ; pFileLinkOrRenameInfo->FileName[0]!=L'\\' or not
.text:00000001400ECD42                 jnz     loc_1400ECF74   ; pFileLinkOrRenameInfo->RootDirectory!=NULL or not
.text:00000001400ECD48
.text:00000001400ECD48 loc_1400ECD48:                          ; CODE XREF: NtSetInformationFile+D49j
.text:00000001400ECD48                 mov     r9, [rsp+138h+var_100] ; pFileObject
.text:00000001400ECD4D                 mov     rdx, rdi        ; pIrp
.text:00000001400ECD50                 lea     rcx, [rsp+138h+var_A8] ; &TargetHandle
.text:00000001400ECD58                 call    IopOpenLinkOrRenameTarget ; pFileLinkOrRenameInfo->FileName[0]==L'\\'或pFileLinkOrRenameInfo->RootDirectory!=NULL时会执行
.text:00000001400ECD5D                 mov     r13d, eax
.text:00000001400ECD60                 movzx   r14d, [rsp+138h+var_107]
.text:00000001400ECD66                 test    eax, eax
.text:00000001400ECD68                 jns     loc_1400EC93E
.text:00000001400ECD6E                 mov     [rdi+30h], eax
.text:00000001400ECD71                 jmp     loc_1400EC95F
...
.text:00000001400ECF74 loc_1400ECF74:                          ; CODE XREF: NtSetInformationFile+B12j
.text:00000001400ECF74                 cmp     qword ptr [r8+8], 0 ; pFileLinkOrRenameInfo->RootDirectory!=NULL or not
.text:00000001400ECF79                 jnz     loc_1400ECD48   ; pFileObject
.text:00000001400ECF7F                 jmp     loc_1400EC938
....


2)nt!IopOpenLinkOrRenameTarget分析

根据对nt!NtSetInformationFile进行的分析,不难推出:nt!IopOpenLinkOrRenameTarget函数原型如下:

NTSTATUS IopOpenLinkOrRenameTarget(
    OUT PHANDLE pTargetFileHandle,
    IN PIRP pIrp,
    IN PVOID pFileLinkOrRenameInfo, //PFILE_RENAME_INFORMATION 或者 PFILE_LINK_INFORMATION
    IN PFILE_OBJECT pSrcFileObject
);

typedef struct _FILE_RENAME_INFORMATION {#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1)    union {
        BOOLEAN ReplaceIfExists;  // FileRenameInformation
        ULONG Flags;              // FileRenameInformationEx
    } DUMMYUNIONNAME;#else
    BOOLEAN ReplaceIfExists;#endif
    HANDLE RootDirectory;
    ULONG FileNameLength;
    WCHAR FileName[1];
} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;

typedef struct _FILE_LINK_INFORMATION {#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS5)    union {
        BOOLEAN ReplaceIfExists;  // FileLinkInformation
        ULONG Flags;              // FileLinkInformationEx
    } DUMMYUNIONNAME;#else
    BOOLEAN ReplaceIfExists;#endif
    HANDLE RootDirectory;
    ULONG FileNameLength;
    WCHAR FileName[1];
} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION;

pFileLinkOrRenameInfo->FileName[0]==L'\\'或pFileLinkOrRenameInfo->RootDirectory!=NULL时都会nt!NtSetInformationFile调用nt!IopOpenLinkOrRenameTarget, 这里考虑: pFileLinkOrRenameInfo->FileName包含完整路径的情况就可以实现后面的功能了。

分析关键处:

PAGE:00000001405C03F0 IopOpenLinkOrRenameTarget proc near     ; CODE XREF: NtSetInformationFile+B28p
...
;指定目标文件打开权限
PAGE:00000001405C0417                 xor     edi, edi
PAGE:00000001405C0419                 mov     r15, r9
PAGE:00000001405C041C                 test    dword ptr [r9+50h], 800h ; pFileObject->Flags: FO_DIRECT_DEVICE_OPEN or not
PAGE:00000001405C0424                 mov     rbx, r8
PAGE:00000001405C0427                 mov     r13, rdx
PAGE:00000001405C042A                 mov     [rbp+90h+var_110], rdi
PAGE:00000001405C042E                 mov     r12, rcx
PAGE:00000001405C0431                 lea     esi, [rdi+2]    ; DesiredAccess初始化: FILE_WRITE_DATA
PAGE:00000001405C0434                 jnz     short loc_1405C0454
PAGE:00000001405C0436                 lea     rdx, [rbp+90h+var_78]
PAGE:00000001405C043A                 mov     rcx, r9
PAGE:00000001405C043D                 call    IopGetBasicInformationFile
PAGE:00000001405C0442                 test    eax, eax
PAGE:00000001405C0444                 js      loc_1405C05C2
PAGE:00000001405C044A                 test    [rbp+90h+var_58], 10h ; pFileBasicInfo->FileAttributes: FILE_ATTRIBUTE_DIRECTORY or not
PAGE:00000001405C044E                 lea     eax, [rdi+4]    ; FILE_ADD_SUBDIRECTORY
PAGE:00000001405C0451                 cmovnz  esi, eax        ; 后续打开权限: 如果是目录则为FILE_ADD_SUBDIRECTORY,否则FILE_WRITE_DATA
...
; 对objectAttributes,driverContext的一些赋值
PAGE:00000001405C0461                 movzx   eax, word ptr [rbx+10h] ; pFileRenameOrLnkInfo->FileNameLength
PAGE:00000001405C0465                 lea     rcx, [rbp+90h+Dst] ; Dst
PAGE:00000001405C0469                 mov     word ptr [rbp+90h+var_F8], ax ; unsName.Length: pFileRenameOrLnkInfo->FileNameLength
PAGE:00000001405C046D                 xorps   xmm0, xmm0
PAGE:00000001405C0470                 mov     word ptr [rbp+90h+var_F8+2], ax ; unsName.MaximumLength: pFileRenameOrLnkInfo->FileNameLength
PAGE:00000001405C0474                 xor     edx, edx        ; Val
PAGE:00000001405C0476                 lea     rax, [rbx+14h]  ; pFileRenameOrLnkInfo->FileName
PAGE:00000001405C047A                 mov     [rbp+90h+var_B8], 30h ; objectAttributes.Length: 0x30
PAGE:00000001405C0481                 mov     [rbp+90h+var_F0], rax ; unsName.Buffer: pFileRenameOrLnkInfo.Name
PAGE:00000001405C0485                 mov     ebx, 28h
PAGE:00000001405C048A                 mov     eax, [r15+50h]  ; pFileObject->Flags
PAGE:00000001405C048E                 mov     r8d, ebx        ; Size
PAGE:00000001405C0491                 shr     eax, 0Bh
PAGE:00000001405C0494                 not     eax
PAGE:00000001405C0496                 mov     [rbp+90h+var_B0], rdi ; objectAttributes.RootDirectory: NULL
PAGE:00000001405C049A                 and     eax, 40h
PAGE:00000001405C049D                 bts     eax, 9
PAGE:00000001405C04A1                 mov     [rbp+90h+var_A0], eax ; objectAttributes.Attributes: (((~(pFileObject->Flags >> 11))&0x40)|0x100)
PAGE:00000001405C04A4                 lea     rax, [rbp+90h+var_F8]
PAGE:00000001405C04A8                 mov     [rbp+90h+var_A8], rax ; objectAttributes.ObjectName: &unsName
PAGE:00000001405C04AC                 movdqu  [rbp+90h+var_98], xmm0 ; objectAttributes->SecurityDescriptor,objectAttributes->SecurityQualityOfService
PAGE:00000001405C04B1                 call    memset          ; RtlZeroMemroy(&driverContext,0x28)
PAGE:00000001405C04B6                 xor     r8d, r8d
PAGE:00000001405C04B9                 mov     word ptr [rbp+90h+Dst], bx ; driverContext.Size: 0x28 
...
;调用nt!IoCreateFileEx实现对更名或链接后的目标文件的打开
PAGE:00000001405C04E0                 mov     r13, [r13+0B8h] ; pIrp->CurrentStackLocation
PAGE:00000001405C04E7                 lea     rcx, [rbp+90h+Dst]
PAGE:00000001405C04EB                 mov     [rsp+158h+Dst], rcx ; DriverContext: &driverContext
PAGE:00000001405C04F0                 lea     r9, [rbp+90h+var_88] ; IoStatusBlock
PAGE:00000001405C04F4                 mov     [rbp+90h+var_D0], rax
PAGE:00000001405C04F8                 lea     rcx, [rbp+90h+Handle] ; &FileHandle
PAGE:00000001405C04FC                 mov     edx, esi        ; DesiredAccess
PAGE:00000001405C04FE                 mov     al, [r13-46h]   ; (pIrp->CurrentStackLocation-1)->Flags
PAGE:00000001405C0502                 bts     edx, 14h        ; 权限: DisiredAccess | SYNCHRONIZE
PAGE:00000001405C0506                 not     al
PAGE:00000001405C0508                 and     eax, 1
PAGE:00000001405C050B                 or      eax, 104h       ; 如果: 下层栈空间中的: (pIrp->CurrentStackLocation-1)->Flags不含标志位: SL_KEY_SPECIFIED(0x1),则options需要添加: IO_FORCE_ACCESS_CHECK(0x0001)
PAGE:00000001405C0510                 mov     dword ptr [rsp+158h+var_F0], eax ; Options: 0x104 or 0x105 <=>(IO_NO_PARAMETER_CHECKING|IO_OPEN_TARGET_DIRECTORY) or (IO_NO_PARAMETER_CHECKING|IO_OPEN_TARGET_DIRECTORY|IO_FORCE_ACCESS_CHECK)
PAGE:00000001405C0514                 and     [rsp+158h+var_F8], r8 ; InternalParameters: NULL
PAGE:00000001405C0519                 and     dword ptr [rsp+158h+var_100], r8d ; CreateFileType: CreateFileTypeNone
PAGE:00000001405C051E                 and     dword ptr [rsp+158h+Handle], r8d ; EaLength: 0
PAGE:00000001405C0523                 and     [rsp+158h+var_110], r8 ; EaBuffer: NULL
PAGE:00000001405C0528                 mov     [rsp+158h+var_118], 4000h ; CreateOptions: 0x4000 <=> FILE_OPEN_FOR_BACKUP_INTENT
PAGE:00000001405C0530                 mov     [rsp+158h+var_120], 1 ; Disposition: FILE_OPEN
PAGE:00000001405C0538                 mov     [rsp+158h+var_128], 3 ; ShareAccess: FILE_SHARE_READ|FILE_SHARE_WRITE
PAGE:00000001405C0540                 and     dword ptr [rsp+158h+HandleInformation], r8d ; FileAttributes: 0
PAGE:00000001405C0545                 and     [rsp+158h+Object], r8 ; AllocationSize: NULL
PAGE:00000001405C054A                 lea     r8, [rbp+90h+var_B8] ; ObjectAttributes
PAGE:00000001405C054E                 call    IoCreateFileEx
PAGE:00000001405C0553                 mov     ebx, eax
PAGE:00000001405C0555                 test    eax, eax
PAGE:00000001405C0557                 js      short loc_1405C05BB
...
;比较更名前或链接的源文件对象与更名后或链接的目标文件对象是否位于同一个卷,必须是同一个卷才能成功
PAGE:00000001405C0559                 mov     r8, cs:IoFileObjectType ; ObjectType
PAGE:00000001405C0560                 lea     rax, [rbp+90h+var_C0]
PAGE:00000001405C0564                 mov     [rsp+158h+HandleInformation], rax ; HandleInformation
PAGE:00000001405C0569                 mov     edx, esi        ; DesiredAccess
PAGE:00000001405C056B                 mov     rsi, [rbp+90h+Handle] ; HandleInformation: NULL
PAGE:00000001405C056F                 lea     rax, [rbp+90h+var_100] ; &pFileObject
PAGE:00000001405C0573                 xor     r9d, r9d        ; AccessMode
PAGE:00000001405C0576                 mov     [rsp+158h+Object], rax ; Object
PAGE:00000001405C057B                 mov     rcx, rsi        ; Handle
PAGE:00000001405C057E                 call    ObReferenceObjectByHandle
PAGE:00000001405C0583                 mov     ebx, eax
PAGE:00000001405C0585                 test    eax, eax
PAGE:00000001405C0587                 js      loc_14078231B
PAGE:00000001405C058D                 mov     r14, [rbp+90h+var_100]
PAGE:00000001405C0591                 mov     rcx, r14        ; Object
PAGE:00000001405C0594                 call    ObfDereferenceObject
PAGE:00000001405C0599                 mov     rcx, r15        ; FileObject
PAGE:00000001405C059C                 call    IoGetRelatedDeviceObject
PAGE:00000001405C05A1                 mov     rcx, r14        ; FileObject
PAGE:00000001405C05A4                 mov     rbx, rax
PAGE:00000001405C05A7                 call    IoGetRelatedDeviceObject
PAGE:00000001405C05AC                 cmp     rax, rbx        ; 比较源文件对象与目标文件对象文件系统卷设备栈上最顶层的设备是否相等来判断是否属于同一卷, 其实比较设备栈底层的: Vpb->DeviceObject也是可以的。
PAGE:00000001405C05AF                 jnz     short loc_1405C061B ; 如果源文件对象与目标文件对象不在同一个卷:最终返回: STATUS_NOT_SAME_DEVICE(0xC00000D4)
PAGE:00000001405C05B1                 mov     [r13-30h], r14
PAGE:00000001405C05B5                 xor     ebx, ebx
PAGE:00000001405C05B7                 mov     [r12], rsi      ; 成功了,保存目标文件对象句柄
...
;源文件对象与目标文件对象不在同一个卷:最终返回: STATUS_NOT_SAME_DEVICE(0xC00000D4)错误 
PAGE:00000001405C061B loc_1405C061B:                          ; CODE XREF: IopOpenLinkOrRenameTarget+1BFj
PAGE:00000001405C061B                 xor     edx, edx
PAGE:00000001405C061D                 mov     rcx, rsi
PAGE:00000001405C0620                 call    ObCloseHandle
PAGE:00000001405C0625                 mov     ebx, 0C00000D4h
PAGE:00000001405C062A                 jmp     short loc_1405C05BB


(3)根据前面的分析,得出文件更名IRP操作的实现:

//自己发送IRP请求更名文件
//wNewFileNameOrPath: 如果只是文件名,则简单进行更名。如果是文件路径,则表示文件移动到相同卷的不同目录下,比如:移到回收站是最常见的操作
NTSTATUS RootkitRenameFile(
	IN PDEVICE_OBJECT pDeviceObject, //如果指定为pFileObject->Vpb->DeviceObject,可绕过文件系统过滤驱动
	IN PFILE_OBJECT pFileObject,
	IN LPCWSTR wNewFileNameOrPath,
	IN BOOLEAN bReplaceIfExists
)
{
	LPWSTR lpwNewFileNameOrPath = NULL;
	PFILE_OBJECT pRootFileObject = NULL, pTargetFileObject = NULL;
	PDEVICE_OBJECT pTargetDeviceObject = NULL;
	PFILE_RENAME_INFORMATION pFileRenameInfo = NULL;
	HANDLE hRoot = NULL, hTargetFile = NULL;
	SIZE_T nRenameInfoLen;
	NTSTATUS ntStatus = STATUS_NOT_SUPPORTED;
	UNICODE_STRING unsNewFileNameOrPath, unsRootPath;
	OBJECT_ATTRIBUTES objAttrs;
	IO_STATUS_BLOCK iosb;
	BOOLEAN bNeedPrefix = TRUE;
	
    PAGED_CODE();
	ASSERT(pDeviceObject != NULL && pFileObject != NULL && wNewFileNameOrPath != NULL);

	do {
		//检查文件对象卷参数块
		if (pFileObject->Vpb == NULL || pFileObject->Vpb->DeviceObject == NULL) {
			ntStatus = STATUS_INVALID_PARAMETER;
			break;
		}

		//检查更名后的文件名或路径
		RtlInitUnicodeString(&unsNewFileNameOrPath,
			wNewFileNameOrPath);
		if (unsNewFileNameOrPath.Length < sizeof(WCHAR)) {
			ntStatus = STATUS_INVALID_PARAMETER;
			break;
		}

		//检查wNewFileNameOrPath是否只包含文件名,如果是文件路径,则表示: 把文件移动到相同卷的不同的目录下,比如:移到回收站是最常见的操作
		if (wcsrchr(wNewFileNameOrPath, L'\\') != NULL) {
			//这是文件路径,文件移动到相同卷的不同的目录下,因此需要创建移动后的文件对象: pTargetFileObject
			//检查是否以"\??\"打头
			if (unsNewFileNameOrPath.Length >= 4 * sizeof(WCHAR)) {
				if (wNewFileNameOrPath[0] == L'\\'
					&&wNewFileNameOrPath[1] == L'?'
					&&wNewFileNameOrPath[2] == L'?'
					&&wNewFileNameOrPath[3] == L'\\') {
					bNeedPrefix = FALSE;
				}
			}

			if (bNeedPrefix) {
				//不以"\??\"打头,需要分配缓冲区并以"\??\"打头
				lpwNewFileNameOrPath = (LPWSTR)ExAllocatePoolWithTag(PagedPool,
					(MAX_PATH + 4) * sizeof(WCHAR),
					MY_TAG);
				if (lpwNewFileNameOrPath == NULL) {
					ntStatus = STATUS_INSUFFICIENT_RESOURCES;
					break;
				}
				RtlZeroMemory(lpwNewFileNameOrPath,
					(MAX_PATH + 4) * sizeof(WCHAR));
				RtlInitEmptyUnicodeString(&unsNewFileNameOrPath,
					lpwNewFileNameOrPath,
					(MAX_PATH + 4) * sizeof(WCHAR));

				ntStatus = RtlAppendUnicodeToString(&unsNewFileNameOrPath,
					L"\\??\\");
				ASSERT(NT_SUCCESS(ntStatus));

				ntStatus = RtlAppendUnicodeToString(&unsNewFileNameOrPath,
					wNewFileNameOrPath);
				if (!NT_SUCCESS(ntStatus)) {
					break;
				}
			}else {
				lpwNewFileNameOrPath = (LPWSTR)wNewFileNameOrPath;
			}
			ASSERT(unsNewFileNameOrPath.Length >= 4 * sizeof(WCHAR));
			//"\??\C:\"
			if (unsNewFileNameOrPath.Length < 7 * sizeof(WCHAR)) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}

			//打开根目录
			unsRootPath.Buffer = lpwNewFileNameOrPath;
			unsRootPath.MaximumLength = unsRootPath.Length = 7 * sizeof(WCHAR); //"\??\C:\"
			InitializeObjectAttributes(&objAttrs,
				&unsRootPath,
				OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
				NULL,
				NULL);
			ntStatus = IoCreateFile(&hRoot,
				FILE_READ_ATTRIBUTES | SYNCHRONIZE,
				&objAttrs,
				&iosb,
				NULL,
				FILE_ATTRIBUTE_NORMAL,
				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
				FILE_OPEN,
				FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
				NULL,
				0,
				CreateFileTypeNone,
				NULL,
				IO_NO_PARAMETER_CHECKING);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}
			
			//得到根目录文件对象
			ntStatus = ObReferenceObjectByHandle(hRoot,
				FILE_READ_ATTRIBUTES,
				*IoFileObjectType,
				KernelMode,
				(PVOID*)&pRootFileObject,
				NULL);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}
			if (pRootFileObject->Vpb == NULL || pRootFileObject->Vpb->DeviceObject == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}

			//卷校验: 移动前的文件与移动后的文件所在的卷必须一致
			if (pFileObject->Vpb->DeviceObject != pRootFileObject->Vpb->DeviceObject) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			pTargetDeviceObject = pRootFileObject->Vpb->DeviceObject; //卷设备
			ObDereferenceObject((PVOID)pRootFileObject);
			pRootFileObject = NULL;
			ZwClose(hRoot);
			hRoot = NULL;

			InitializeObjectAttributes(&objAttrs,
				&unsNewFileNameOrPath,
				OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
				NULL,
				NULL);
			
			//绕过过滤层,打开移动后的文件从而创建移动后的文件对象
			//由于需要绕过文件系统过滤驱动,这里直接向最底层的卷设备pTargetDeviceObject发送请求
			//除了IoCreateFileEx外, 用: IoCreateFileSpecifyDeviceObjectHint最后一个参数指定为pTargetDeviceObject也能达到这一目的
			//这与CreateFileEx中的: DriverContext->DeviceObjectHint指定为设备栈最底层的卷设备pTargetDeviceObject的作用是一样的
            /*  
			    NtSetInformationFile => IopOpenLinkOrRenameTarget
				...
				PAGE:00000001405C04E7                 lea     rcx, [rbp+90h+Dst]
                PAGE:00000001405C04EB                 mov     [rsp+158h+Dst], rcx ; DriverContext: &driverContext
			    PAGE:00000001405C04F8                 lea     rcx, [rbp+90h+Handle] ; &FileHandle
                PAGE:00000001405C04FC                 mov     edx, esi        ; DesiredAccess: 目录为FILE_ADD_SUBDIRECTORY,否则FILE_WRITE_DATA
                PAGE:00000001405C04FE                 mov     al, [r13-46h]   ; (pIrp->CurrentStackLocation-1)->Flags
                PAGE:00000001405C0502                 bts     edx, 14h        ; 权限: DisiredAccess | SYNCHRONIZE
                PAGE:00000001405C0506                 not     al
                PAGE:00000001405C0508                 and     eax, 1
                PAGE:00000001405C050B                 or      eax, 104h       ; 如果: 下层栈空间中的: (pIrp->CurrentStackLocation-1)->Flags不含标志位: SL_KEY_SPECIFIED(0x1),则options需要添加: IO_FORCE_ACCESS_CHECK(0x0001)
                PAGE:00000001405C0510                 mov     dword ptr [rsp+158h+var_F0], eax ; Options: 0x104 or 0x105 <=>(IO_NO_PARAMETER_CHECKING|IO_OPEN_TARGET_DIRECTORY) or (IO_NO_PARAMETER_CHECKING|IO_OPEN_TARGET_DIRECTORY|IO_FORCE_ACCESS_CHECK)
                PAGE:00000001405C0514                 and     [rsp+158h+var_F8], r8 ; InternalParameters: NULL
                PAGE:00000001405C0519                 and     dword ptr [rsp+158h+var_100], r8d ; CreateFileType: CreateFileTypeNone
                PAGE:00000001405C051E                 and     dword ptr [rsp+158h+Handle], r8d ; EaLength: 0
                PAGE:00000001405C0523                 and     [rsp+158h+var_110], r8 ; EaBuffer: NULL
                PAGE:00000001405C0528                 mov     [rsp+158h+var_118], 4000h ; CreateOptions: 0x4000 <=> FILE_OPEN_FOR_BACKUP_INTENT
                PAGE:00000001405C0530                 mov     [rsp+158h+var_120], 1 ; Disposition: FILE_OPEN
                PAGE:00000001405C0538                 mov     [rsp+158h+var_128], 3 ; ShareAccess: FILE_SHARE_READ|FILE_SHARE_WRITE
                PAGE:00000001405C0540                 and     dword ptr [rsp+158h+HandleInformation], r8d ; FileAttributes: 0
                PAGE:00000001405C0545                 and     [rsp+158h+Object], r8 ; AllocationSize: NULL
                PAGE:00000001405C054A                 lea     r8, [rbp+90h+var_B8] ; ObjectAttributes
                PAGE:00000001405C054E                 call    IoCreateFileEx
				...
			*/
			//#define FILE_APPEND_DATA          ( 0x0004 )    // file
			//#define FILE_ADD_SUBDIRECTORY     ( 0x0004 )    // directory
			//根据逆向分析的内容进行填写
			ntStatus = IoCreateFileSpecifyDeviceObjectHint(&hTargetFile,
				FILE_WRITE_DATA | FILE_APPEND_DATA | SYNCHRONIZE, //also corret
				&objAttrs,
				&iosb,
				NULL,
				0,
				FILE_SHARE_READ | FILE_SHARE_WRITE, //0x3
				FILE_OPEN, //0x1
				FILE_OPEN_FOR_BACKUP_INTENT, //0x4000
				NULL,
				0,
				CreateFileTypeNone,
				NULL,
				IO_NO_PARAMETER_CHECKING | IO_OPEN_TARGET_DIRECTORY, //0x104
				pTargetDeviceObject);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}

			//得到移动后的文件对象
			ntStatus = ObReferenceObjectByHandle(hTargetFile,
				FILE_WRITE_DATA | FILE_APPEND_DATA,
				*IoFileObjectType,
				KernelMode,
				(PVOID*)&pTargetFileObject,
				NULL);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}
        }//end if (wcsrchr(wNewFileNameOrPath, L'\\') != NULL)

		//分配文件更名缓冲区并进行填写
		nRenameInfoLen = sizeof(FILE_RENAME_INFORMATION) + (SIZE_T)unsNewFileNameOrPath.Length;
		pFileRenameInfo = (PFILE_RENAME_INFORMATION)ExAllocatePoolWithTag(PagedPool,
			nRenameInfoLen,
			MY_TAG);
		if (pFileRenameInfo == NULL) {
			ntStatus = STATUS_INSUFFICIENT_RESOURCES;
			break;
		}
		RtlZeroMemory(pFileRenameInfo,
			nRenameInfoLen);
		pFileRenameInfo->ReplaceIfExists = bReplaceIfExists;
		pFileRenameInfo->RootDirectory = NULL;
		pFileRenameInfo->FileNameLength = (ULONG)unsNewFileNameOrPath.Length;
		RtlCopyMemory(&pFileRenameInfo->FileName[0],
			unsNewFileNameOrPath.Buffer,
			unsNewFileNameOrPath.Length);

		//自己发送IRP请求实现文件更名
		ntStatus = IrpSetInformationFile(pDeviceObject,
			pFileObject,
			pTargetFileObject, //如果不移动文件则为NULL,否则为先前创建的移动后的文件对象
			&iosb,
			(PVOID)pFileRenameInfo,
			(ULONG)nRenameInfoLen,
			FileRenameInformation);
    } while (FALSE);

	//检查并解引文件对象,关闭句柄
	if (pRootFileObject != NULL) {
		ObDereferenceObject((PVOID)pRootFileObject);
	}
	if (hRoot != NULL) {
		ZwClose(hRoot);
	}
	if (pTargetFileObject != NULL) {
		ObDereferenceObject((PVOID)pTargetFileObject);
	}
	if (hTargetFile != NULL) {
		ZwClose(hTargetFile);
	}

	//检查并释放缓冲区
	if (pFileRenameInfo != NULL) {
		ExFreePoolWithTag((PVOID)pFileRenameInfo,
			MY_TAG);
	}
	if (lpwNewFileNameOrPath != NULL && lpwNewFileNameOrPath!=(LPWSTR)wNewFileNameOrPath) {
		ExFreePoolWithTag((PVOID)lpwNewFileNameOrPath,
			MY_TAG);
	}

    return ntStatus;
}

//更名文件(设备控制请求中调用)
/*
自定义相关数据结构:
#ifndef MAX_PATH
#define MAX_PATH 260
#endif
...
//文件重命名数据结构(与应用层交互)
typedef struct _IOCTL_RENAME_FILE_INFO {
	WCHAR wOldFilePath[MAX_PATH];    //更名前的文件路径
	WCHAR wNewFileNameOrPath[MAX_PATH]; //更名后的文件名或路径
	BOOLEAN bReplaceIfExists;   //文件名如果存在是否替换该文件
}IOCTL_RENAME_FILE_INFO,*PIOCTL_RENAME_FILE_INFO,*LPIOCTL_RENAME_FILE_INFO;
*/
NTSTATUS RootkitRenameFileForIoctl(
	IN PIOCTL_RENAME_FILE_INFO pIoctlRenameFileInfo
)
{
	PFILE_OBJECT pFileObject = NULL;
	PDEVICE_OBJECT pDeviceObject = NULL;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL, ntLocStatus;
	UNICODE_STRING unsOldFilePath;
	IO_STATUS_BLOCK iosb;

	PAGED_CODE();
	ASSERT(pIoctlRenameFileInfo != NULL);
	
	do {
		//检查更名前的文件路径
		RtlInitUnicodeString(&unsOldFilePath,
			pIoctlRenameFileInfo->wOldFilePath);
		if (unsOldFilePath.Length < 3 * sizeof(WCHAR)) {
			ntStatus = STATUS_INVALID_PARAMETER;
			break;
		}

		//检查路径是否以"\??\"打头
		if (unsOldFilePath.Length >= 4 * sizeof(WCHAR)) {
			if (unsOldFilePath.Buffer[0] == L'\\'
				&&unsOldFilePath.Buffer[1] == L'?'
				&&unsOldFilePath.Buffer[2] == L'?'
				&&unsOldFilePath.Buffer[3] == L'\\') {
				//如果是以"\??\"打头,则传入IrpCreateFile前需要去掉头部"\??\"
				unsOldFilePath.Buffer += 4;
				unsOldFilePath.Length -= 4 * sizeof(WCHAR);
				unsOldFilePath.MaximumLength -= 4 * sizeof(WCHAR);
			}
		}

		//自己发送IRP请求打开更名前的文件
		ntStatus = IrpCreateFile(&pFileObject,
			&pDeviceObject,
			DELETE | SYNCHRONIZE,
			&unsOldFilePath, //必须以"盘符:\"开头, 比如: "C:\..."
			&iosb,
			NULL,
			FILE_ATTRIBUTE_NORMAL,
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
			FILE_OPEN,
			FILE_SYNCHRONOUS_IO_NONALERT,
			NULL,
			0);
    } while (FALSE);
	
	if (NT_SUCCESS(ntStatus)) {
		//自己发送IRP请求实现文件更名
		ntStatus = RootkitRenameFile(pDeviceObject,
			pFileObject,
			pIoctlRenameFileInfo->wNewFileNameOrPath,
			pIoctlRenameFileInfo->bReplaceIfExists);
		
		//自己发送IRP请求: IRP_MJ_CLEANUP,IRP_MJ_CLOSE 关闭文件
		ntLocStatus = IrpCleanupFile(pDeviceObject,
			pFileObject);
		ASSERT(NT_SUCCESS(ntLocStatus));

		if (NT_SUCCESS(ntLocStatus)) {
			IrpCloseFile(pDeviceObject,
				pFileObject);
			ASSERT(NT_SUCCESS(ntLocStatus));
		}
	}

	return ntStatus;
}



3. 文件的拷贝/移动: 打开源文件及目标文件,从源文件读取再写入目标文件就可以了,如果需要移动,拷贝成功后再删除源文件。

#define FILE_TRANS_BUF_MAXLEN (4*1024*1024)

//拷贝文件
NTSTATUS RootkitCopyFile(
	IN PDEVICE_OBJECT pSrcDeviceObject,
	IN PFILE_OBJECT pSrcFileObject,
	IN PDEVICE_OBJECT pDestDeviceObject,
	IN PFILE_OBJECT pDestFileObject,
	IN BOOLEAN bDeleteSrcFile
)
{
	PVOID lpTransBuf;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
	IO_STATUS_BLOCK iosb;
	LARGE_INTEGER liBytesOffset;

	PAGED_CODE();

	//分配文件读写缓冲区
	lpTransBuf = ExAllocatePoolWithTag(NonPagedPool,
		FILE_TRANS_BUF_MAXLEN,
		MY_TAG);
	if (lpTransBuf == NULL) {
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	liBytesOffset.QuadPart = 0;
	do {
		//自己发送IRP请求读取源文件
		ntStatus = IrpReadOrWriteFile(pSrcDeviceObject,
			pSrcFileObject,
			&iosb,
			lpTransBuf,
			FILE_TRANS_BUF_MAXLEN,
			&liBytesOffset,
			TRUE);
		if (ntStatus == STATUS_END_OF_FILE) {
			//已到达文件尾
			ntStatus = STATUS_SUCCESS;
			break;
		}
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//自己发送IRP请求写目标文件
		ntStatus = IrpReadOrWriteFile(pDestDeviceObject, 
			pDestFileObject,
			&iosb,
			lpTransBuf,
			(ULONG)iosb.Information,
			&liBytesOffset,
			FALSE);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//调整读写偏移量
		liBytesOffset.QuadPart += (LONGLONG)iosb.Information;
	} while (NT_SUCCESS(ntStatus));
	
	//释放文件读写缓冲区
	ExFreePoolWithTag(lpTransBuf,
		MY_TAG);
	
	if (NT_SUCCESS(ntStatus)) {
		if (bDeleteSrcFile) {
			//拷贝成功后需要删除源文件
			RootkitDeleteFile(pSrcDeviceObject,
				pSrcFileObject);
		}
	}

	return ntStatus;
}

//拷贝文件(设备控制请求中调用)
/*
    自定义的相关数据结构
    //文件拷贝数据结构(与应用层交互)
    typedef struct _IOCTL_COPY_FILE_INFO {
	WCHAR wSrcFilePath[MAX_PATH];  //源文件路径
	WCHAR wDestFilePath[MAX_PATH]; //目标文件路径
	BOOLEAN bDeleteSrcFile;   //拷贝成功后是否删除源文件
    }IOCTL_COPY_FILE_INFO, *PIOCTL_COPY_FILE_INFO, *LPIOCTL_COPY_FILE_INFO;
*/
NTSTATUS RootkitCopyFileForIoctl(
	IN PIOCTL_COPY_FILE_INFO pIoctlCopyFileInfo
)
{
	PDEVICE_OBJECT pSrcDeviceObject = NULL, pDestDeviceObject = NULL;
	PFILE_OBJECT pSrcFileObject = NULL, pDestFileObject = NULL;
	ACCESS_MASK ulDesiredAccess;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL, ntLocStatus;
	UNICODE_STRING unsFilePath;
	IO_STATUS_BLOCK iosb;

	PAGED_CODE();
	ASSERT(pIoctlCopyFileInfo != NULL);

	do {
		//检查源文件路径是否以"\??\"打头
		RtlInitUnicodeString(&unsFilePath,
			&pIoctlCopyFileInfo->wSrcFilePath[0]);
		if (unsFilePath.Length >= 4 * sizeof(WCHAR)) {
			if (unsFilePath.Buffer[0] == L'\\'
				&&unsFilePath.Buffer[1] == L'?'
				&&unsFilePath.Buffer[2] == L'?'
				&&unsFilePath.Buffer[3] == L'\\') {
				//如果以"\??\"打头,传入IrpCreateFile前需要去掉头部"\??\"
				unsFilePath.Buffer += 4;
				unsFilePath.Length -= 4 * sizeof(WCHAR);
				unsFilePath.MaximumLength -= 4 * sizeof(WCHAR);
			}
		}

		//自己发送IRP请求打开源文件
		ulDesiredAccess = FILE_READ_DATA | SYNCHRONIZE;
		if (pIoctlCopyFileInfo->bDeleteSrcFile) ulDesiredAccess |= DELETE;
		ntStatus = IrpCreateFile(&pSrcFileObject,
			&pSrcDeviceObject,
			ulDesiredAccess,
			&unsFilePath,
			&iosb,
			NULL,
			FILE_ATTRIBUTE_NORMAL,
			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
			FILE_OPEN,
			FILE_NON_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT,
			NULL,
			0);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}
		
		//检查目标文件路径是否以"\??\"打头
		RtlInitUnicodeString(&unsFilePath,
			&pIoctlCopyFileInfo->wDestFilePath[0]);
		if (unsFilePath.Length >= 4 * sizeof(WCHAR)) {
			if (unsFilePath.Buffer[0] == L'\\'
				&&unsFilePath.Buffer[1] == L'?'
				&&unsFilePath.Buffer[2] == L'?'
				&&unsFilePath.Buffer[3] == L'\\') {
				//如果以"\??\"打头,传入IrpCreateFile前需要去掉头部"\??\"
				unsFilePath.Buffer += 4;
				unsFilePath.Length -= 4 * sizeof(WCHAR);
				unsFilePath.MaximumLength -= 4 * sizeof(WCHAR);
			}
		}

		//自己发送IRP请求打开或创建目标文件
		ntStatus = IrpCreateFile(&pDestFileObject,
			&pDestDeviceObject,
			FILE_WRITE_DATA | SYNCHRONIZE,
			&unsFilePath,
			&iosb,
			NULL,
			FILE_ATTRIBUTE_ARCHIVE,
			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
			FILE_OVERWRITE_IF,
			FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
			NULL,
			0);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//拷贝文件
		ntStatus = RootkitCopyFile(pSrcDeviceObject,
			pSrcFileObject,
			pDestDeviceObject,
			pDestFileObject,
			pIoctlCopyFileInfo->bDeleteSrcFile);
	} while (FALSE);

	//自己发送IRP请求关闭目标文件
	if (pDestFileObject != NULL) {
		ASSERT(pDestDeviceObject != NULL);

		ntLocStatus = IrpCleanupFile(pDestDeviceObject,
			pDestFileObject);
		ASSERT(NT_SUCCESS(ntLocStatus));

		if (NT_SUCCESS(ntLocStatus)) {
			ntLocStatus = IrpCloseFile(pDestDeviceObject,
				pDestFileObject);
			ASSERT(NT_SUCCESS(ntLocStatus));
		}
	}

	//自己发送IRP请求关闭源文件
	if (pSrcFileObject != NULL) {
		ASSERT(pSrcDeviceObject != NULL);

		ntLocStatus = IrpCleanupFile(pSrcDeviceObject,
			pSrcFileObject);
		ASSERT(NT_SUCCESS(ntLocStatus));

		if (NT_SUCCESS(ntLocStatus)) {
			ntLocStatus = IrpCloseFile(pSrcDeviceObject,
				pSrcFileObject);
			ASSERT(NT_SUCCESS(ntLocStatus));
		}
	}

	return ntStatus;
}


4. 模拟FindFirstFile/FindNextFile/FindClose实现目录下文件及子目录的枚举

(1)自定义的相关数据结构:

//
//查询目录下的文件相关数据结构(与应用层交互)
//
//被查询的目录打开信息
typedef struct _FIND_FILE_HANDLE_INFO {
	PVOID pDeviceObject; //卷设备(内部用)
	PVOID pFileObject;   //目录文件对象(内部用)
}FIND_FILE_HANDLE_INFO, *PFIND_FILE_HANDLE_INFO, *LPFIND_FILE_HANDLE_INFO;

//发现项信息
typedef struct _FIND_FILE_OUTPUT {
	LARGE_INTEGER CreationTime;  //创建时间
	LARGE_INTEGER LastAccessTime; //最近访问时间
	LARGE_INTEGER LastWriteTime;  //最近写入时间
	LARGE_INTEGER ChangeTime;     //变更时间
	LARGE_INTEGER EndOfFile;      //文件大小
	LARGE_INTEGER AllocationSize; //占用空间大小
	ULONG    ulFileAttributes;  //属性
	WCHAR    wShortFileName[14]; //8.3 格式名
	WCHAR    wFileName[MAX_PATH]; //名称
} FIND_FILE_OUTPUT, *PFIND_FILE_OUTPUT, *LPFIND_FILE_OUTPUT;

//首次查询的输出缓冲区结构
typedef struct _FIND_FIRST_FILE_OUTPUT {
	FIND_FILE_HANDLE_INFO stFileFileHandleInfo; //被查询的目录打开信息
	FIND_FILE_OUTPUT stFindFileItem; //发现项信息
}FIND_FIRST_FILE_OUTPUT,*PFIND_FIRST_FILE_OUTPUT,*LPFIND_FIRST_FILE_OUTPUT;


(2)实现:

//自己发送IRP请求查询目录下的1项封装
NTSTATUS RootkitFindFileItem(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PFILE_OBJECT pFileObject,
	IN BOOLEAN bRestartScan,
	OUT PFIND_FILE_OUTPUT pFindFileOut
)
{
	PFILE_BOTH_DIR_INFORMATION pFileBothDirInfo;
	IO_STATUS_BLOCK iosb;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;

	PAGED_CODE();
	ASSERT(pDeviceObject != NULL && pFileObject != NULL && pFindFileOut != NULL);

	//分配缓冲区
	pFileBothDirInfo = (PFILE_BOTH_DIR_INFORMATION)ExAllocatePoolWithTag(PagedPool,
		PAGE_SIZE,
		MY_TAG);
	if (pFileBothDirInfo == NULL) {
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	RtlZeroMemory(pFileBothDirInfo,
		PAGE_SIZE);

	//发送IRP请求查询目录下的1项
	ntStatus = IrpQueryDirectoryFile(pDeviceObject,
		pFileObject,
		&iosb,
		(PVOID)pFileBothDirInfo,
		PAGE_SIZE,
		FileBothDirectoryInformation,
		TRUE,
		NULL,
		bRestartScan);
	if (NT_SUCCESS(ntStatus)) {
		//成功了就进行保存
		RtlZeroMemory(pFindFileOut,
			sizeof(FIND_FILE_OUTPUT));
		pFindFileOut->CreationTime.QuadPart = pFileBothDirInfo->CreationTime.QuadPart;
		pFindFileOut->LastAccessTime.QuadPart = pFileBothDirInfo->LastAccessTime.QuadPart;
		pFindFileOut->LastWriteTime.QuadPart = pFileBothDirInfo->LastWriteTime.QuadPart;
		pFindFileOut->ChangeTime.QuadPart = pFileBothDirInfo->ChangeTime.QuadPart;
		pFindFileOut->EndOfFile.QuadPart = pFileBothDirInfo->EndOfFile.QuadPart;
		pFindFileOut->AllocationSize.QuadPart = pFileBothDirInfo->AllocationSize.QuadPart;
		pFindFileOut->ulFileAttributes = pFileBothDirInfo->FileAttributes;
		if (pFileBothDirInfo->ShortNameLength > 0) {
			RtlCopyMemory(pFindFileOut->wShortFileName,
				pFileBothDirInfo->ShortName,
				(SIZE_T)pFileBothDirInfo->ShortNameLength);
			pFindFileOut->wShortFileName[sizeof(pFindFileOut->wShortFileName) / sizeof(WCHAR) - 1] = L'\0';
		}
		if (pFileBothDirInfo->FileNameLength > 0) {
			if (pFileBothDirInfo->FileNameLength > sizeof(pFindFileOut->wFileName)) {
				pFileBothDirInfo->FileNameLength = sizeof(pFindFileOut->wFileName);
			}
			RtlCopyMemory(pFindFileOut->wFileName,
				pFileBothDirInfo->FileName,
				(SIZE_T)pFileBothDirInfo->FileNameLength);
			pFindFileOut->wFileName[sizeof(pFindFileOut->wFileName) / sizeof(WCHAR) - 1] = L'\0';
		}
	}

	//释放缓冲区
	ExFreePoolWithTag((PVOID)pFileBothDirInfo,
		MY_TAG);

	return ntStatus;
}

//第1次查询目录下的项(设备控制请求中调用)
NTSTATUS RootkitFindFirstFileForIoctl(
	IN LPCWSTR wDirPath,
	OUT PFIND_FIRST_FILE_OUTPUT pFindFirstFileOutput
)
{
	PDEVICE_OBJECT pDeviceObject = NULL;
	PFILE_OBJECT pFileObject = NULL;
	NTSTATUS ntStatus = STATUS_UNSUCCESSFUL, ntLocStatus;
	UNICODE_STRING unsDirPath;
	IO_STATUS_BLOCK iosb;

	PAGED_CODE();
	ASSERT(wDirPath != NULL && pFindFirstFileOutput != NULL);

	do {
		//检查目录路径是否以"\??\"打头
		RtlInitUnicodeString(&unsDirPath,
			wDirPath);
		if (unsDirPath.Length >= 4 * sizeof(WCHAR)) {
			if (unsDirPath.Buffer[0] == L'\\'
				&&unsDirPath.Buffer[1] == L'?'
				&&unsDirPath.Buffer[2] == L'?'
				&&unsDirPath.Buffer[3] == L'\\') {
				//如果以"\??\"打头,传入IrpCreateFile前需要去掉头部"\??\"
				unsDirPath.Buffer += 4;
				unsDirPath.Length -= 4 * sizeof(WCHAR);
				unsDirPath.MaximumLength -= 4 * sizeof(WCHAR);
			}
		}

		//自己发送IRP请求,打开目标目录
		ntStatus = IrpCreateFile(&pFileObject,
			&pDeviceObject,
			FILE_LIST_DIRECTORY | FILE_TRAVERSE | SYNCHRONIZE,
			&unsDirPath,
			&iosb,
			NULL,
			FILE_ATTRIBUTE_NORMAL,
			FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
			FILE_OPEN,
			FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
			NULL,
			0);
		if (!NT_SUCCESS(ntStatus)) {
			break;
		}

		//自己发送IRP请求,查询目录下的第1条
		ntStatus = RootkitFindFileItem(pDeviceObject,
			pFileObject,
			TRUE,
			&pFindFirstFileOutput->stFindFileItem);
		if (NT_SUCCESS(ntStatus)) {
			pFindFirstFileOutput->stFileFileHandleInfo.pDeviceObject = (PVOID)pDeviceObject;
			pFindFirstFileOutput->stFileFileHandleInfo.pFileObject = (PVOID)pFileObject;
		}
	} while (FALSE);

	if (!NT_SUCCESS(ntStatus)) {
		//失败时的处理
		if (pFileObject != NULL) {
			ASSERT(pDeviceObject != NULL);

			//自己发送IRP请求关闭查询目录
			ntLocStatus = IrpCleanupFile(pDeviceObject,
				pFileObject);
			ASSERT(NT_SUCCESS(ntLocStatus));
			
			ntLocStatus = IrpCloseFile(pDeviceObject,
				pFileObject);
			ASSERT(NT_SUCCESS(ntLocStatus));
		}
	}

	return ntStatus;
}

//再次查询目录下的项(设备控制请求中调用)
NTSTATUS RootkitFindNextFileForIoctl(
	IN PFIND_FILE_HANDLE_INFO pFileFirstFileHandleInfo,
	OUT PFIND_FILE_OUTPUT pFindFileOutput
)
{
	NTSTATUS ntStatus;

	PAGED_CODE();
	ASSERT(pFileFirstFileHandleInfo != NULL && pFindFileOutput != NULL);

	//检查参数合法性
	if (pFileFirstFileHandleInfo->pDeviceObject == NULL || pFileFirstFileHandleInfo->pFileObject == NULL) {
		return STATUS_INVALID_PARAMETER;
	}

	//自己发送IRP请求,查询目录下的1条
	ntStatus = RootkitFindFileItem(
		(PDEVICE_OBJECT)pFileFirstFileHandleInfo->pDeviceObject,
		(PFILE_OBJECT)pFileFirstFileHandleInfo->pFileObject,
		FALSE,
		pFindFileOutput);

	return ntStatus;
}

//结束目录下项的查询(设备控制请求中调用)
NTSTATUS RootkitFindCloseForIoctl(
	IN PFIND_FILE_HANDLE_INFO pFileFirstFileHandleInfo
)
{
	NTSTATUS ntStatus = STATUS_INVALID_PARAMETER;
	
	PAGED_CODE();
	ASSERT(pFileFirstFileHandleInfo != NULL);

	if (pFileFirstFileHandleInfo->pFileObject != NULL && pFileFirstFileHandleInfo->pDeviceObject != NULL) {
		//自己发送IRP请求关闭目标目录
		ntStatus = IrpCleanupFile((PDEVICE_OBJECT)pFileFirstFileHandleInfo->pDeviceObject,
			(PFILE_OBJECT)pFileFirstFileHandleInfo->pFileObject);
		if (NT_SUCCESS(ntStatus)) {
			ntStatus = IrpCloseFile((PDEVICE_OBJECT)pFileFirstFileHandleInfo->pDeviceObject,
				(PFILE_OBJECT)pFileFirstFileHandleInfo->pFileObject);
		}
	}

	return ntStatus;
}

5. 自己的设备控制请求处理:

//

// 设备控制请求

//

#define BASE_DEVICE_TYPE FILE_DEVICE_UNKNOWN

#define IOCTL_IRP_DELETEFILE CTL_CODE(BASE_DEVICE_TYPE, 0x101, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_IRP_RENAMEFILE CTL_CODE(BASE_DEVICE_TYPE, 0x102, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_IRP_COPYFILE CTL_CODE(BASE_DEVICE_TYPE, 0x103, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_IRP_FINDFIRSTFILE CTL_CODE(BASE_DEVICE_TYPE, 0x104, METHOD_OUT_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_IRP_FINDNEXTFILE CTL_CODE(BASE_DEVICE_TYPE, 0x105, METHOD_OUT_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCLT_IRP_FINDCLOSE CTL_CODE(BASE_DEVICE_TYPE, 0x106, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)


//不可换页的节,确保:有关自己发送IRP请求的封装函数Irpxxx是不可换页的,因为前面的完成例程FileOperationCompletion中要访问

//pIrp->UserIosb及pIrp->UserEvent,而这2个指针成员指向的是Irpxxx栈上的局部变量, 其余对其调用的外围函数不受此限制

#pragma alloc_text(PAGELK,IrpCreateFile)

#pragma alloc_text(PAGELK,IrpCleanupFile)

#pragma alloc_text(PAGELK,IrpCloseFile)

#pragma alloc_text(PAGELK,IrpQueryInformationFile)

#pragma alloc_text(PAGELK,IrpSetInformationFile)

#pragma alloc_text(PAGELK,IrpReadOrWriteFile)

#pragma alloc_text(PAGELK,IrpQueryDirectoryFile)

//IRP_MJ_DEVICE_CONTROL分发函数
NTSTATUS DispatchDeviceControl(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PIRP pIrp
)
{
	PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
	PVOID pInputBuffer, pOutputBuffer;
	ULONG_PTR ulptrInformation = 0;
	ULONG ulInputBufLen, ulOutputBufLen;
	NTSTATUS ntStatus = STATUS_INVALID_DEVICE_REQUEST;
	IO_STATUS_BLOCK iosb;
	UNICODE_STRING unsName;

	PAGED_CODE();
	ASSERT(pIrpSp != NULL);
	ASSERT(pIrpSp->MajorFunction == IRP_MJ_DEVICE_CONTROL);

	switch (pIrpSp->Parameters.DeviceIoControl.IoControlCode) {
	case IOCTL_IRP_DELETEFILE: //强删文件(绕过文件系统过滤驱动)
	{
		ASSERT((pIrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_IN_DIRECT);
		do {
			//检查输入缓冲区合法性
			pInputBuffer = pIrp->AssociatedIrp.SystemBuffer;
			if (pInputBuffer == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			ulInputBufLen = pIrpSp->Parameters.DeviceIoControl.InputBufferLength; //最短: C:\x
			if (ulInputBufLen < 5 * sizeof(WCHAR)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			//执行强删文件
			ntStatus = RootkitDeleteFileForIoctl((LPCWSTR)pInputBuffer);
		} while (FALSE);
	}
	break;
	case IOCTL_IRP_RENAMEFILE: //更名文件(绕过文件系统过滤驱动)
	{
		ASSERT((pIrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_IN_DIRECT);
		do {
			//检查输入缓冲区合法性
			pInputBuffer = pIrp->AssociatedIrp.SystemBuffer;
			if (pInputBuffer == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			ulInputBufLen = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
			if (ulInputBufLen < sizeof(IOCTL_RENAME_FILE_INFO)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			//执行更名文件
			ntStatus = RootkitRenameFileForIoctl((PIOCTL_RENAME_FILE_INFO)pInputBuffer);
		} while (FALSE);
	}
	break;
	case IOCTL_IRP_COPYFILE: //拷贝文件(绕过文件系统过滤驱动)
	{
		ASSERT((pIrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_IN_DIRECT);
		do {
			//检查输入缓冲区合法性
			pInputBuffer = pIrp->AssociatedIrp.SystemBuffer;
			if (pInputBuffer == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			ulInputBufLen = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
			if (ulInputBufLen < sizeof(IOCTL_COPY_FILE_INFO)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			//执行文件拷贝
			ntStatus = RootkitCopyFileForIoctl((PIOCTL_COPY_FILE_INFO)pInputBuffer);
		} while (FALSE);
	}
	break;
	case IOCTL_IRP_FINDFIRSTFILE:  //查询目录的第1项(绕过文件系统过滤驱动)
	{
		ASSERT((pIrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_OUT_DIRECT);
		do {
			//检查输入缓冲区合法性
			pInputBuffer = pIrp->AssociatedIrp.SystemBuffer;
			if (pInputBuffer == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			ulInputBufLen = pIrpSp->Parameters.DeviceIoControl.InputBufferLength; //最短: "C:\"
			if (ulInputBufLen < 4 * sizeof(WCHAR)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			//检查输出缓冲区合法性
			if (pIrp->MdlAddress == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			pOutputBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,
				NormalPagePriority);
			if (pOutputBuffer == NULL) {
				ntStatus = STATUS_INSUFFICIENT_RESOURCES;
				break;
			}
			ulOutputBufLen = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
			if (ulOutputBufLen < sizeof(FIND_FIRST_FILE_OUTPUT)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}
			if (MmGetMdlByteCount(pIrp->MdlAddress) < ulOutputBufLen) {
				ntStatus = STATUS_BUFFER_OVERFLOW;
				break;
			}

			//执行查询
			ntStatus = RootkitFindFirstFileForIoctl((LPCWSTR)pInputBuffer,
				(PFIND_FIRST_FILE_OUTPUT)pOutputBuffer);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}

			//成功
			ulptrInformation = sizeof(FIND_FIRST_FILE_OUTPUT); //保存实际得到的字节数
		} while (FALSE);
	}
		break;
	case IOCTL_IRP_FINDNEXTFILE: //查询目录的下1项(绕过文件系统过滤驱动)
	{
		ASSERT((pIrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_OUT_DIRECT);
		do {
			//检查输入缓冲区合法性
			pInputBuffer = pIrp->AssociatedIrp.SystemBuffer;
			if (pInputBuffer == NULL || (ULONG_PTR)pInputBuffer == (ULONG_PTR)(-1)) {
				ntStatus = STATUS_INVALID_HANDLE;
				break;
			}
			ulInputBufLen = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
			if (ulInputBufLen < sizeof(FIND_FILE_HANDLE_INFO)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			//检查输出缓冲区合法性
			if (pIrp->MdlAddress == NULL) {
				ntStatus = STATUS_INVALID_PARAMETER;
				break;
			}
			pOutputBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,
				NormalPagePriority);
			if (pOutputBuffer == NULL) {
				ntStatus = STATUS_INSUFFICIENT_RESOURCES;
				break;
			}
			ulOutputBufLen = pIrpSp->Parameters.DeviceIoControl.OutputBufferLength;
			if (ulOutputBufLen < sizeof(FIND_FILE_OUTPUT)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}
			if (MmGetMdlByteCount(pIrp->MdlAddress) < ulOutputBufLen) {
				ntStatus = STATUS_BUFFER_OVERFLOW;
				break;
			}

			//执行查询
			ntStatus = RootkitFindNextFileForIoctl((PFIND_FILE_HANDLE_INFO)pInputBuffer,
				(PFIND_FILE_OUTPUT)pOutputBuffer);
			if (!NT_SUCCESS(ntStatus)) {
				break;
			}

			//成功
			ulptrInformation = sizeof(FIND_FILE_OUTPUT); //保存实际得到的字节数
		} while (FALSE);
	}
		break;
	case IOCLT_IRP_FINDCLOSE:  //停止目录项的查询(绕过文件系统过滤驱动)
	{
		ASSERT((pIrpSp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED);
		do {
			//检查输入缓冲区合法性
			pInputBuffer = pIrp->AssociatedIrp.SystemBuffer;
			if (pInputBuffer == NULL || (ULONG_PTR)pInputBuffer == (ULONG_PTR)(-1)) {
				ntStatus = STATUS_INVALID_HANDLE;
				break;
			}
			ulInputBufLen = pIrpSp->Parameters.DeviceIoControl.InputBufferLength;
			if (ulInputBufLen < sizeof(FIND_FILE_HANDLE_INFO)) {
				ntStatus = STATUS_BUFFER_TOO_SMALL;
				break;
			}

			//停止目录下项查询
			ntStatus = RootkitFindCloseForIoctl((PFIND_FILE_HANDLE_INFO)pInputBuffer);
		} while (FALSE);
	}
		break;
	}
	
    //填写状态参数并自己结束I/O请求
	pIrp->IoStatus.Status = ntStatus;
	pIrp->IoStatus.Information = ulptrInformation;
	IoCompleteRequest(pIrp,
		IO_NO_INCREMENT);
	return ntStatus;
}


6. 用户层实现封装:

//
// 设备控制请求
//
#define BASE_DEVICE_TYPE FILE_DEVICE_UNKNOWN
#define IOCTL_IRP_DELETEFILE CTL_CODE(BASE_DEVICE_TYPE, 0x101, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
#define IOCTL_IRP_RENAMEFILE CTL_CODE(BASE_DEVICE_TYPE, 0x102, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
#define IOCTL_IRP_COPYFILE CTL_CODE(BASE_DEVICE_TYPE, 0x103, METHOD_IN_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
#define IOCTL_IRP_FINDFIRSTFILE CTL_CODE(BASE_DEVICE_TYPE, 0x104, METHOD_OUT_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
#define IOCTL_IRP_FINDNEXTFILE CTL_CODE(BASE_DEVICE_TYPE, 0x105, METHOD_OUT_DIRECT, FILE_READ_DATA | FILE_WRITE_DATA)
#define IOCLT_IRP_FINDCLOSE CTL_CODE(BASE_DEVICE_TYPE, 0x106, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)

//
//交互数据结构
//
//文件重命名数据结构(与应用层交互)
typedef struct _IOCTL_RENAME_FILE_INFO {
	WCHAR wOldFilePath[MAX_PATH];    //更名前的文件路径
	WCHAR wNewFileNameOrPath[MAX_PATH]; //更名后的文件名或路径
	BOOLEAN bReplaceIfExists;   //文件名如果存在是否替换该文件
}IOCTL_RENAME_FILE_INFO, *PIOCTL_RENAME_FILE_INFO, *LPIOCTL_RENAME_FILE_INFO;

//文件拷贝数据结构(与应用层交互)
typedef struct _IOCTL_COPY_FILE_INFO {
	WCHAR wSrcFilePath[MAX_PATH];  //源文件路径
	WCHAR wDestFilePath[MAX_PATH]; //目标文件路径
	BOOLEAN bDeleteSrcFile;   //拷贝成功后是否删除源文件
}IOCTL_COPY_FILE_INFO, *PIOCTL_COPY_FILE_INFO, *LPIOCTL_COPY_FILE_INFO;

//
//查询目录下的文件相关数据结构(与应用层交互)
//
//被查询的目录打开信息
typedef struct _FIND_FILE_HANDLE_INFO {
	PVOID pDeviceObject; //卷设备(内部用)
	PVOID pFileObject;   //目录文件对象(内部用)
}FIND_FILE_HANDLE_INFO, *PFIND_FILE_HANDLE_INFO, *LPFIND_FILE_HANDLE_INFO;

//发现项信息
typedef struct _FIND_FILE_OUTPUT {
	LARGE_INTEGER CreationTime;  //创建时间
	LARGE_INTEGER LastAccessTime; //最近访问时间
	LARGE_INTEGER LastWriteTime;  //最近写入时间
	LARGE_INTEGER ChangeTime;     //最近更改时间
	LARGE_INTEGER EndOfFile;      //文件大小
	LARGE_INTEGER AllocationSize; //占用空间大小
	ULONG    ulFileAttributes;  //属性
	WCHAR    wShortFileName[14]; //8.3 格式名
	WCHAR    wFileName[MAX_PATH]; //名称
} FIND_FILE_OUTPUT, *PFIND_FILE_OUTPUT, *LPFIND_FILE_OUTPUT;

//首次查询的输出缓冲区结构
typedef struct _FIND_FIRST_FILE_OUTPUT {
	FIND_FILE_HANDLE_INFO stFileFileHandleInfo; //被查询的目录打开信息
	FIND_FILE_OUTPUT stFindFileItem; //发现项信息
}FIND_FIRST_FILE_OUTPUT, *PFIND_FIRST_FILE_OUTPUT, *LPFIND_FIRST_FILE_OUTPUT;
...
//
//功能函数
//
//文件强删
BOOL RootkitDeleteFile(
	IN HANDLE hDevice,
	IN LPCTSTR szFilePath
)
{
	LPCWSTR lpwFilePath;
	BOOL bSuc;
	DWORD dwBytesRet;

	//指定输入缓冲区,UNICODE字符集与多字节字符集分别考虑
#if defined(UNICODE) || defined(_UNICODE)
	lpwFilePath = (LPCWSTR)szFilePath;
#else
	WCHAR wFilePath[MAX_PATH] = { L'\0' };
	::MultiByteToWideChar(CP_ACP,
		0,
		(LPCSTR)szFilePath,
		-1,
		wFilePath,
		sizeof(wFilePath)/sizeof(WCHAR));
	wFilePath[sizeof(wFilePath) / sizeof(WCHAR) - 1] = L'\0';
	lpwFilePath = &wFilePath[0];
#endif
	
	//发送设备控制请求
	bSuc = ::DeviceIoControl(hDevice,
		IOCTL_IRP_DELETEFILE,
		(LPVOID)lpwFilePath,
		(::wcslen(lpwFilePath)+1)*sizeof(WCHAR),
		NULL,
		0,
		&dwBytesRet,
		NULL);
	return bSuc;
}

//文件更名
BOOL RootkitRenameFile(
	IN HANDLE hDevice,
	IN LPCTSTR szOldFilePath,
	IN LPCTSTR szNewFileNameOrPath, //如果只包含文件名,则进行简单更名。如果是文件全路径,则移动到相同卷的不同目录下, 比如:移到回收站是最常见的操作
	IN BOOLEAN bReplaceIfExists
)
{
	LPIOCTL_RENAME_FILE_INFO lpIoctlRenameFileInfo;
	BOOL bSuc;
	DWORD dwBytesRet, dwErr;

	//分配输入缓冲区
	lpIoctlRenameFileInfo = new IOCTL_RENAME_FILE_INFO;
	if (lpIoctlRenameFileInfo == NULL) {
		::SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return FALSE;
	}
	::ZeroMemory(lpIoctlRenameFileInfo, sizeof(IOCTL_RENAME_FILE_INFO));

	//填写输入缓冲区,UNICODE字符集与多字节字符集分别考虑
#if defined(UNICODE) || defined(_UNICODE)
	::wcscpy_s(lpIoctlRenameFileInfo->wOldFilePath, sizeof(lpIoctlRenameFileInfo->wOldFilePath) / sizeof(WCHAR), (LPCWSTR)szOldFilePath);
	::wcscpy_s(lpIoctlRenameFileInfo->wNewFileNameOrPath, sizeof(lpIoctlRenameFileInfo->wNewFileNameOrPath) / sizeof(WCHAR), (LPCWSTR)szNewFileNameOrPath);
#else
	::MultiByteToWideChar(CP_ACP,
		0,
		(LPCSTR)szOldFilePath,
		-1,
		lpIoctlRenameFileInfo->wOldFilePath,
		sizeof(lpIoctlRenameFileInfo->wOldFilePath)/sizeof(WCHAR));
	lpIoctlRenameFileInfo->wOldFilePath[sizeof(lpIoctlRenameFileInfo->wOldFilePath) / sizeof(WCHAR) - 1] = L'\0';

	::MultiByteToWideChar(CP_ACP,
		0,
		(LPCSTR)szNewFileNameOrPath,
		-1,
		lpIoctlRenameFileInfo->wNewFileNameOrPath,
		sizeof(lpIoctlRenameFileInfo->wNewFileNameOrPath) / sizeof(WCHAR));
	lpIoctlRenameFileInfo->wNewFileNameOrPath[sizeof(lpIoctlRenameFileInfo->wNewFileNameOrPath) / sizeof(WCHAR) - 1] = L'\0';
#endif
	lpIoctlRenameFileInfo->bReplaceIfExists = bReplaceIfExists;

	//发送设备控制请求
	if ((bSuc = ::DeviceIoControl(hDevice,
		IOCTL_IRP_RENAMEFILE,
		(LPVOID)lpIoctlRenameFileInfo,
		sizeof(IOCTL_RENAME_FILE_INFO),
		NULL,
		0,
		&dwBytesRet,
		NULL))){
		dwErr = ERROR_SUCCESS;
	}else {
		dwErr = ::GetLastError();
	}

	//释放输入缓冲区
	delete lpIoctlRenameFileInfo;

	::SetLastError(dwErr);
	return bSuc;
}

//文件复制/移动(虽然RootkitRenameFile也能移动文件,但不同的是: RootkitCopyFile可以把文件移动到不同的卷下)
BOOL RootkitCopyFile(
	IN HANDLE hDevice,
	IN LPCTSTR szSrcFilePath,
	IN LPCTSTR szDestFilePath,
	IN BOOLEAN bDeleteSrcFile //为TRUE: 表示移动文件
)
{
	LPIOCTL_COPY_FILE_INFO lpIoctlCopyFileInfo;
	BOOL bSuc;
	DWORD dwBytesRet, dwErr;

	//分配输入缓冲区
	lpIoctlCopyFileInfo = new IOCTL_COPY_FILE_INFO;
	if (lpIoctlCopyFileInfo == NULL) {
		::SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return FALSE;
	}
	::ZeroMemory(lpIoctlCopyFileInfo, sizeof(IOCTL_COPY_FILE_INFO));

	//填写输入缓冲区,UNICODE字符集与多字节字符集分别考虑
#if defined(UNICODE) || defined(_UNICODE)
	::wcscpy_s(lpIoctlCopyFileInfo->wSrcFilePath, sizeof(lpIoctlCopyFileInfo->wSrcFilePath) / sizeof(WCHAR), (LPCWSTR)szSrcFilePath);
	::wcscpy_s(lpIoctlCopyFileInfo->wDestFilePath, sizeof(lpIoctlCopyFileInfo->wDestFilePath) / sizeof(WCHAR), (LPCWSTR)szDestFilePath);
#else
	::MultiByteToWideChar(
		CP_ACP,
		0,
		(LPCSTR)szSrcFilePath,
		-1,
		lpIoctlCopyFileInfo->wSrcFilePath,
		sizeof(lpIoctlCopyFileInfo->wSrcFilePath)/sizeof(WCHAR));
	lpIoctlCopyFileInfo->wSrcFilePath[sizeof(lpIoctlCopyFileInfo->wSrcFilePath) / sizeof(WCHAR) - 1] = L'\0';

	::MultiByteToWideChar(
		CP_ACP,
		0,
		(LPCSTR)szDestFilePath,
		-1,
		lpIoctlCopyFileInfo->wDestFilePath,
		sizeof(lpIoctlCopyFileInfo->wDestFilePath) / sizeof(WCHAR));
	lpIoctlCopyFileInfo->wDestFilePath[sizeof(lpIoctlCopyFileInfo->wDestFilePath) / sizeof(WCHAR) - 1] = L'\0';
#endif
	lpIoctlCopyFileInfo->bDeleteSrcFile = bDeleteSrcFile;

	//发送设备控制请求
	if (bSuc = ::DeviceIoControl(hDevice,
		IOCTL_IRP_COPYFILE,
		(LPVOID)lpIoctlCopyFileInfo,
		sizeof(IOCTL_COPY_FILE_INFO),
		NULL,
		0,
		&dwBytesRet,
		NULL)) {
		dwErr = ERROR_SUCCESS;
	}else {
		dwErr = ::GetLastError();
	}

	//释放输入缓冲区
	delete lpIoctlCopyFileInfo;
	
	::SetLastError(dwErr);
	return bSuc;
}

//查询目录下的第1项
HANDLE RootkitFindFirstFile(
	IN HANDLE hDevice,
	IN LPCTSTR szDirPath,
	OUT LPFIND_FILE_OUTPUT lpFindFileOutput
)
{
	HANDLE hFindFile = INVALID_HANDLE_VALUE;
	LPFIND_FIRST_FILE_OUTPUT lpFindFirstFileOutput;
	LPCWSTR lpwDirPath;
	DWORD dwBytesRet, dwErr;
	WCHAR wDirPath[MAX_PATH] = {L'\0'};

	//分配输出缓冲区
	lpFindFirstFileOutput = new FIND_FIRST_FILE_OUTPUT;
	if (lpFindFirstFileOutput == NULL) {
		::SetLastError(ERROR_NOT_ENOUGH_MEMORY);
		return INVALID_HANDLE_VALUE;
	}
	
    //指定输入缓冲区, UNICODE字符集与多字节字符集分别考虑
#if defined(UNICODE) || defined(_UNICODE)
	lpwDirPath = (LPCWSTR)szDirPath;
#else
	::MultiByteToWideChar(CP_ACP,
		0,
		(LPCSTR)szDirPath,
		-1,
		wDirPath,
		sizeof(wDirPath) / sizeof(WCHAR));
	wDirPath[sizeof(wDirPath) / sizeof(WCHAR) - 1] = L'\0';
	lpwDirPath = &wDirPath[0];
#endif

	//发送设备控制请求
	if (::DeviceIoControl(hDevice,
		IOCTL_IRP_FINDFIRSTFILE,
		(LPVOID)lpwDirPath,
		(::wcslen(lpwDirPath) + 1) * sizeof(WCHAR),
		(LPVOID)lpFindFirstFileOutput,
		sizeof(FIND_FIRST_FILE_OUTPUT),
		&dwBytesRet,
		NULL)) {
		hFindFile = (HANDLE)lpFindFirstFileOutput; //将第1次查询目录的输出缓冲区地址作为返回句柄值
		dwErr = ERROR_SUCCESS;
	}else {
		dwErr = ::GetLastError();
	}

	if (hFindFile != INVALID_HANDLE_VALUE) {
	    //成功时的处理	
		::CopyMemory(lpFindFileOutput,
			&lpFindFirstFileOutput->stFindFileItem,
			sizeof(FIND_FILE_OUTPUT)); //文件项信息保存
	}else {
		//失败时的处理
		delete lpFindFirstFileOutput;
	}

	return hFindFile;
}

//查询目录的下1项
BOOL RootkitFindNextFile(
	IN HANDLE hDevice,
	IN HANDLE hFindFile,
	OUT LPFIND_FILE_OUTPUT lpFindFileOutput
)
{
	LPFIND_FIRST_FILE_OUTPUT lpFindFirstFileOutput;
	BOOL bSuc;
	DWORD dwBytesRet;

	//检查参数合法性
	if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL) {
		::SetLastError(ERROR_INVALID_HANDLE);
		return FALSE;
	}

	//发送设备控制请求
	lpFindFirstFileOutput = (LPFIND_FIRST_FILE_OUTPUT)hFindFile;
	bSuc = ::DeviceIoControl(hDevice,
		IOCTL_IRP_FINDNEXTFILE,
		(LPVOID)&lpFindFirstFileOutput->stFileFileHandleInfo,
		sizeof(FIND_FILE_HANDLE_INFO),
		(LPVOID)lpFindFileOutput,
		sizeof(FIND_FILE_OUTPUT),
		&dwBytesRet,
		NULL);

	return bSuc;
}

//停止查询目录
BOOL RootkitFindClose(
	IN HANDLE hDevice,
	IN HANDLE hFindFile
)
{
	LPFIND_FIRST_FILE_OUTPUT lpFindFirstFileOutput;
	BOOL bSuc;
	DWORD dwBytesRet, dwErr;

	//检查参数合法性
	if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL) {
		::SetLastError(ERROR_INVALID_HANDLE);
		return FALSE;
	}

	//发送设备控制请求
	lpFindFirstFileOutput = (LPFIND_FIRST_FILE_OUTPUT)hFindFile;
	if (bSuc = ::DeviceIoControl(hDevice,
		IOCLT_IRP_FINDCLOSE,
		(LPVOID)&lpFindFirstFileOutput->stFileFileHandleInfo,
		sizeof(FIND_FILE_HANDLE_INFO),
		NULL,
		0,
		&dwBytesRet,
		NULL)) {
		dwErr = ERROR_SUCCESS;
		delete lpFindFirstFileOutput;
	}else {
		dwErr = ::GetLastError();
	}

	::SetLastError(dwErr);
	return bSuc;
}




阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开 发者可享99元/年,续费同价!

最后于 2020-12-8 11:42 被低调putchar编辑 ,原因:
收藏
点赞17
打赏
分享
最新回复 (46)
雪    币: 80
活跃值: (74)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
skyddr我 2020-12-8 20:33
2
0
学习了,谢谢大佬
雪    币: 2129
活跃值: (4191)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
默NJ 2020-12-8 22:42
3
0
学习一下,感谢分享
雪    币: 83
活跃值: (1052)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
killpy 2 2020-12-9 06:44
4
0
jianro兼容win7 到10?
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
低调putchar 1 2020-12-9 09:14
5
0
killpy jianro兼容win7 到10?

嗯!是兼容的!对ntfs/fastfat(fat32)文件系统发送IRP操作文件,各个版本的windows基本相同,

以前做小工具的时候从WinXP SP2到Win7 SP1都用过,Win10上现在也进行了调试,全部兼容。这里做个总结!

最后于 2020-12-9 09:32 被低调putchar编辑 ,原因:
雪    币: 5413
活跃值: (11835)
能力值: ( LV12,RANK:312 )
在线值:
发帖
回帖
粉丝
一半人生 5 2020-12-9 16:21
6
0
好总结
雪    币: 1773
活跃值: (1718)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
曹操abc 2020-12-9 17:38
7
0
感谢分享
雪    币: 18867
活跃值: (60348)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2020-12-9 17:49
8
0
感谢分享!
雪    币: 11357
活跃值: (4944)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
hhkqqs 1 2020-12-9 18:12
9
0
膜拜,看原理应该跟pchunter差不多,绕过ssdt函数直接自行下发irp
雪    币: 2854
活跃值: (2518)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
iamasbcx 2020-12-9 20:16
10
0
都快忘得差不多了
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
低调putchar 1 2020-12-9 20:43
11
0
hhkqqs 膜拜,看原理应该跟pchunter差不多,绕过ssdt函数直接自行下发irp[em_63]

您也不错!
实际中应该还有不少的工作量,比如下发IRP请求前: 检查FSD的IRP钩子,inline Hook钩子(ntfs.sys/fastfat.sys,属于文件系统驱动, 低版本的PG应该不会扫的),如果发现钩子就摘除一下,再下发下去。我这里主要是回顾和总结一下文件驱动方面的一些知识。

最后于 2020-12-21 23:03 被低调putchar编辑 ,原因:
雪    币: 665
活跃值: (1060)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lytywg 2020-12-9 21:32
12
0
除了nb以外说不出别的话来
雪    币: 1126
活跃值: (2086)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Oday小斯 2020-12-10 14:49
13
0
感谢分享
雪    币: 3438
活跃值: (3492)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2020-12-10 22:09
14
0
感谢分享
雪    币: 1989
活跃值: (1501)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
basketwill 1 2020-12-11 09:02
15
2
虽然东西是老知识,但是做了这么详细的总结以及从win7到win10 都可以用,不错点赞!!!!!!!!!!!
雪    币: 149
活跃值: (2058)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
saloyun 2020-12-11 10:36
16
0
厉害!
雪    币: 378
活跃值: (2908)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
appview 2020-12-16 11:14
17
0
感谢分享!
雪    币: 16
活跃值: (527)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
沉醉星渊 2020-12-16 16:54
18
0
请问大佬,如果采用你上面说的那个直接发送给最底层的设备对象,过掉中间的过滤设备,这种情况下的请求有什么办法能过滤到吗
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
低调putchar 1 2020-12-16 17:32
19
0
沉醉星渊 请问大佬,如果采用你上面说的那个直接发送给最底层的设备对象,过掉中间的过滤设备,这种情况下的请求有什么办法能过滤到吗
微过滤管理器L"\\FileSystem\\FltMgr"本质上还是一个文件系统过滤驱动FltMgr.sys,通过把过滤设备绑定在pVpb->DeviceObject卷设备之上来监控文件操作的,如果发送IRP请求时指定ntfs.sys/fastfat.sys的文件系统卷设备pVpb->DeviceObject,请求不会送往过滤设备,MiniFilter是捕获不到的。
如果HOOK了ntfs.sys/fastfat.sys派发例程, pDriverObject->MajorFunction就可以过滤到。
但HOOK是可以摘除的,如果下发IRP请求前检测并摘除了IRP钩子/inline Hook钩子就可以绕过。
雪    币: 16
活跃值: (527)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
沉醉星渊 2020-12-16 18:32
20
0
明白了,谢谢您的回答
雪    币: 16
活跃值: (527)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
沉醉星渊 2020-12-17 09:44
21
0
请问大佬,有没有hook ntfs.sys派遣函数的例子呀
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
低调putchar 1 2020-12-17 10:07
22
0
沉醉星渊 请问大佬,有没有hook ntfs.sys派遣函数的例子呀

可以实现!成本比较高!一些标签水印系统就是HOOK的fsd的MajorFunction.
可以通过ObReferenceObjectByName获得名为: L“\\FileSystem\\Ntfs”和L"\\FileSystem\\fastfat"的驱动对象pDriverNtfs和pDriverFastFat.
然后: 对于关注的IRP把驱动对象对应的MajFunction替换成自己的MajForunction或者inline Hook后跳转到你的MajorFunction
实现上工作量比较大,处理上需要很细致:
FSD有文件系统控制设备和文件系统卷设备,文件系统控制设备可以收到卷的挂载, 文件系统识别器处理文件系统真正加载等有关的请求,文件系统卷设备会收到一些文件/目录的操作请求。


文件系统控制设备也能收到:IRP_MJ_CREATE, IRP_MJ_CLEANUP, IRP_MJ_CLOSE等请求

所以有必要区分文件系统的控制设备和卷设备,另外视情况决定是否要过滤卷影设备。

区分文件系统的控制设备和卷设备比较靠谱的方法就是:

IoRegisterFsRegistrationChange注册一个文件系统变动回调

//注册文件系统变动回调
NTSTATUS IoRegisterFsRegistrationChange(  
  PDRIVER_OBJECT          DriverObject,   //你的驱动对象指针
  PDRIVER_FS_NOTIFICATION DriverNotificationRoutine   //文件系统变动回调
);

//文件系统变动回调
void DriverFsNotification(
  _DEVICE_OBJECT *DeviceObject,  //激活或注销的文件系统的控制设备(包括文件系统识别器控制设备)
  BOOLEAN FsActive  当文件系统激活时FsActive为TRUE,文件系统注销时FsActive为FALSE
)

自己增删设备维护一个设备链(包含了文件系统识别器及当前已激活的文件系统的控制设备),只要不在设备链中的设备可以视为文件系统卷设备(包括卷影)。

卷影设备的判断sfilter中有例子: 

https://github.com/ExpLife0011/sfilter-1

可以自行学习掌握。


有人可能会想到另一种方法: 用ObQueryNameString查询设备栈最底层的设备名,对于没有名字的就认为是文件系统卷设备。

但除了IRP_MJ_CREATE/IRP_MJ_CLOSE/IRP_MJ_CLEANUP外,文件系统其余IRP请求中用ObQueryNameString容易死锁。所以还是

注册一个文件系统变动回调自己增删设备维护一个设备链的方法稳妥些。


最后于 2020-12-20 18:44 被低调putchar编辑 ,原因:
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
低调putchar 1 2020-12-20 15:46
23
0
沉醉星渊 请问大佬,有没有hook ntfs.sys派遣函数的例子呀

以HOOK 64位ntfs主功能号为IRP_MJ_DIRECTORY_CONTROL派遣函数为例,的伪代码

1. 获得驱动对象指针伪代码:

extern POBJECT_TYPE *IoDriverObjectType; //自己外部声明: 导出驱动对象类型

//一个未公开的内核API函数,直接声明一下就可以用了
NTKERNELAPI
NTSTATUS NTAPI ObReferenceObjectByName(
	IN PUNICODE_STRING ObjectName,
	IN ULONG Attributes,
	OUT PACCESS_STATE AccessState OPTIONAL,
	IN ACCESS_MASK DesiredAccess,
	IN POBJECT_TYPE ObjectType OPTIONAL,
	IN KPROCESSOR_MODE AccessMode,
	IN PVOID ParseContext OPTIONAL,
	OUT PVOID *Object
);

...
//获得ntfs驱动对象指针
RtlInitUnicodeString(&unsObjectName,
    L"\\FileSystem\\Ntfs");
ntStatus=ObReferenceObjectByName(&unsObjectName,
    OBJ_CASE_INSENSITIVE,
    NULL,
    FILE_READ_DATA|FILE_WRITE_DATA,
    *IoDriverObjectType,
    KernelMode,
    NULL,
    (PVOID*)&g_pNtfsDriver);
if(NT_SUCCESS(ntStatus)){
    ...
    //挂钩
    //...
    ObDereferenceObject((PVOID)g_pNtfsDriver);
    ...
}

2. IRP钩子伪代码:

//IRP挂钩
if(NT_SUCCESS(ntStatus)){
    //保存ntfs以前的派遣例程,把ntfs的派遣例程替换成你自己的钩子函数
    g_sysDispatchDirectoryControl=InterlockedExchangePointer(&g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL],
         DispatchDirectoryControlHook);

    ObDereferenceObject((PVOID)pNtfsDriver);
}
...
//钩子函数
NTSTATUS DispatchDirectoryControlHook(IN PDEVICE_OBJECT pDeviceObject,
    IN PIRP pIrp)
{
     NTSTATUS ntStatus;
     
     //执行前过滤
     //...
     
     ntStatus=g_sysDispatchDirectoryControl(pDeviceObject,
         pIrp); //执行ntfs以前的派遣例程
         
     //执行后的过滤
     //...   
         
     return ntStatus;
}


2. inline hook 钩子伪代码

//中转函数(类似32位inline hook中的__declspec(naked)裸函数功能,必须保证至少容纳24字节)
//在hook时,前12字节被替换为: g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]的前12个字节节
//然后: mov rax, imm64(机器码: 48 b8 64位立即数)
//jmp rax(机器码: ff e0)
//在hook时, imm64被替换为: (ULONG_PTR)g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]+12
NTSTATUS NakedDispatchDirectoryControl(
	IN PDEVICE_OBJECT pDeviceObject,
	IN PIRP pIrp
)
{
   //这里可以自己任意写些功能性代码,只要保证函数体长度不少于24字节即可
   ...
}


//inline hook
VOID InlineHook(VOID)
{
    //mov rax,imm64 (机器码: 48 b8 64位立即数)
	//jmp rax(机器码: ff e0)
	UCHAR ucCode[12] = { 0x48,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xe0 },  //imm64: 为钩子函数HookedDispatchDirectoryControl地址
	      ucCodeNaked[12] = { 0x48,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xe0 }; //imm64: 为ntfs派遣例程地址+12
    ULONGLONG ullAddr;
    KIRQL kSavedIrql;
    
    //保存ntfs派遣例程的前12字节指令集
	/*
	    WIN7 SP0
		PAGE:00000000000B1680                 mov     [rsp+arg_0], rbx
        PAGE:00000000000B1685                 mov     [rsp+arg_8], rdx
        PAGE:00000000000B168A                 push    rsi
        PAGE:00000000000B168B                 push    rdi
	*/
	/*
	    WIN7 SP1
		PAGE:00000000000A97E0                 mov     [rsp+arg_0], rbx
        PAGE:00000000000A97E5                 mov     [rsp+arg_8], rdx
        PAGE:00000000000A97EA                 push    rsi
        PAGE:00000000000A97EB                 push    rdi
	*/
	RtlCopyMemory(g_oldCode,
		g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL],
		12);

	//钩子派遣例程地址
	ullAddr = (ULONGLONG)HookedDispatchDirectoryControl; 
	RtlCopyMemory(&ucCode[2],
		&ullAddr,
		8);

	//ntfs派遣例程地址的12字节偏处
	ullAddr = (ULONGLONG)g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL] + 12;
	RtlCopyMemory(&ucCodeNaked[2],
		&ullAddr,
		8);

	
         CloseWriteProtect(&kSavedIrql); //关闭写保护(自实现的中断请求级别提升及CR0的bit16置0)
	
	//Patch掉NakedDispatchDirectoryControl的前24字节
	//调用时, 先执行ntfs派遣例程原来的12字节指令集,然后跳转到ntfs派遣例程+12处,发挥类似裸函数的功能
	RtlCopyMemory(NakedDispatchDirectoryControl,
		g_oldCode,
		12); 
	RtlCopyMemory((PUCHAR)NakedDispatchDirectoryControl + 12,
		ucCodeNaked,
		12); 

	//修改ntfs派遣例程前12字节,以便跳转到我们的钩子函数
	RtlCopyMemory(g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL],
		ucCode,
		12);  
	
	RestoreWriteProtect(kSavedIrql); //恢复写保护(自实现的中断请求级别恢复及CR0bit16置1)
    
    return;
}

//inline unhook
VOID UnInlineHook(VOID)
{
    KIRQL kSavedIrql;
    
    CloseWriteProtect(&kSavedIrql); //关闭写保护(自实现的中断请求级别提升及CR0的bit16置0)
    RtlCopyMemory(g_pNtfsDriver->MajorFunction[IRP_MJ_DIRECTORY_CONTROL],
        g_oldCode,
        12); //恢复派遣例程前12字节
    RestoreWriteProtect(kSavedIrql); //恢复写保护 (自实现的中断请求级别恢复及CR0bit16置1)
    
    return;
}

...

//inline hook挂钩
if(NT_SUCCESS(ntStatus)){
    InlineHook();
    ObDereferenceObject((PVOID)g_pNtfsDriver);
}
...

//钩子函数
NTSTATUS DispatchDirectoryControlHook(IN PDEVICE_OBJECT pDeviceObject,
    IN PIRP pIrp)
{
    NTSTATUS ntStatus;
    
    //执行前过滤
    //... 
    
    //
	// 转去中转函数(类似32位inline hook的裸函数),最终转去执行ntfs的原来的派遣例程功能(这里作了改进,不用考虑线程同步)
	//
    ntStatus=NakedDispatchDirectoryControl(pDeviceObject,
        pIrp);
 
    //执行后的过滤
    //...  
 
    return ntStatus;
 }
最后于 2020-12-22 20:30 被低调putchar编辑 ,原因:
雪    币: 16
活跃值: (527)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
沉醉星渊 2020-12-21 14:39
24
0
谢谢大佬,我去研究一下
雪    币: 310
活跃值: (1917)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2020-12-21 14:44
25
0
nice
游客
登录 | 注册 方可回帖
返回