一. 概述
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_CREATE | nt!NtCreateFile | 文件的生成/打开,需要创建文件对象 |
IRP_MJ_CLEANUP | nt!NtClose | 文件关闭1: 发送该请求,向FSD表明,文件对象句柄数已为0了 |
IRP_MJ_CLOSE | nt!ObDereferenceObject | 文件关闭2: 发送该请求,向FSD表明, 文件对象引用计数已为0了,需要销毁文件对象 |
IRP_MJ_READ | nt!NtReadFile | 读文件 |
IRP_MJ_WRITE | nt!NTWriteFile | 写文件 |
IRP_MJ_SET_INFORMATION | nt!NtSetInformationFile | 文件设置, 常用的有: 删除,重命名,属性设置等 |
IRP_MJ_QUERY_INFORMATION | nt!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操作测试: 这里实现常用的几个功能就可以把前面的文件操作涵盖
文件删除
//自己发送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编辑
,原因: