内核层自己发送IRP请求操作文件全面总结

  • A+
所属分类:逆向工程
内核层自己发送IRP请求操作文件全面总结

本文为看雪论精华文章

看雪论坛作者ID:低调putchar




内核层自己发送IRP请求操作文件全面总结

一、概述

内核层自己发送IRP请求操作文件全面总结

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请求操作文件全面总结

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

内核层自己发送IRP请求操作文件全面总结

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

(1)解锁并释放MDL链
//解锁并释放MDLVOID MyFreeMdl(    IN PMDL pMdl){    PMDL pMdlCurrent = pMdl, pMdlNext;
while (pMdlCurrent != NULL) { pMdlNext = pMdlCurrent->Next;
if (pMdlCurrent->MdlFlags&MDL_PAGES_LOCKED) { MmUnlockPages(pMdlCurrent); } IoFreeMdl(pMdlCurrent);
pMdlCurrent = pMdlNext; }
return;}

(2)文件操作完成例程:
//文件操作完成例程NTSTATUS FileOperationCompletion(    IN PDEVICE_OBJECT pDeviceObject,    IN PIRP pIrp,    IN PVOID pContext OPTIONAL){    PKEVENT pkEvent = pIrp->UserEvent;
ASSERT(pkEvent != NULL);
//趁机拷贝完成状态 RtlCopyMemory( pIrp->UserIosb, &pIrp->IoStatus, sizeof(IO_STATUS_BLOCK) );
//检查并释放MDL if (pIrp->MdlAddress != NULL) { MyFreeMdl(pIrp->MdlAddress); pIrp->MdlAddress = NULL; }
//释放IRP IoFreeIrp(pIrp);
//设置事件 KeSetEvent(pkEvent, IO_NO_INCREMENT, FALSE );
return STATUS_MORE_PROCESSING_REQUIRED;}

2. 文件的打开/生成: 与其它IRP不同,在发送IRP_MJ_CREATE请求前就需要创建并填写好文件对象,FSD的卷设备可以从根目录pRootFileObject->Vpb->DeviceObject得到,对应的卷管理器生成的卷设备可以从: pRootFileObject->Vpb->RealDevice得到。

(1)创建文件对象可以用已导出但未公开的nt!ObCreateObject,该函数原型如下:
//未公开的函数,直接声明下就可以用了//创建对象NTKERNELAPINTSTATUS NTAPI ObCreateObject(    IN KPROCESSOR_MODE ObjectAttributesAccessMode  OPTIONAL,    IN POBJECT_TYPE  Type,    IN POBJECT_ATTRIBUTES ObjectAttributes  OPTIONAL,    IN KPROCESSOR_MODE  AccessMode,    IN OUT PVOID ParseContext  OPTIONAL,    IN ULONG  ObjectSize,    IN ULONG PagedPoolCharge  OPTIONAL,    IN ULONG NonPagedPoolCharge  OPTIONAL,    OUT PVOID * Object);

(2)设置安全状态可以用已导出但未公开的nt!SeCreateAccessState,该函数原型如下:
//设置安全状态NTKERNELAPINTSTATUS NTAPI SeCreateAccessState(    __out PACCESS_STATE AccessState,    __out PAUX_ACCESS_DATA AuxData,    __in ACCESS_MASK DesiredAccess,    __in_opt PGENERIC_MAPPING GenericMapping);
未公开的数据结构//SeCreateAccessState第2个参数typedef struct _AUX_ACCESS_DATA { PPRIVILEGE_SET PrivilegesUsed; GENERIC_MAPPING GenericMapping; ACCESS_MASK AccessesToAudit; ACCESS_MASK MaximumAuditMask; ULONG Unknown[256];} AUX_ACCESS_DATA, *PAUX_ACCESS_DATA;

(3)自己创建并发送IRP进行文件打开/生成的实现:
//自己发送IRP请求生成/打开文件(IRP_MJ_CREATE)NTSTATUS IrpCreateFile(    OUT PFILE_OBJECT *ppFileObject,    OUT PDEVICE_OBJECT *ppDeviceObject, //如果成功,这里保存:(*ppFileObject)->Vpb->DeviceObject    IN ACCESS_MASK DesiredAccess,    IN PUNICODE_STRING punsFilePath, //必须以"盘符:"开头, 比如: "C:..."    OUT PIO_STATUS_BLOCK pIoStatusBlock,    IN PLARGE_INTEGER AllocationSize OPTIONAL,    IN ULONG FileAttributes,    IN ULONG ShareAccess,    IN ULONG Disposition,    IN ULONG CreateOptions,    IN PVOID EaBuffer OPTIONAL,    IN ULONG EaLength){    PAUX_ACCESS_DATA pAuxData = NULL;    HANDLE hRoot = NULL;    PFILE_OBJECT pRootObject = NULL, pFileObject = NULL;    PDEVICE_OBJECT pDeviceObject = NULL, pRealDeviceObject = NULL;    PIRP pIrp;    PIO_STACK_LOCATION pIrpSp;    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;    ACCESS_STATE AccessState;    KEVENT kEvent;    IO_SECURITY_CONTEXT IoSecurityContext;    OBJECT_ATTRIBUTES objAttrs;    UNICODE_STRING unsRootPath;    IO_STATUS_BLOCK userIosb;    WCHAR wRootPath[8];  //"??C:"
//路径长度检查(最短: "C:") if (punsFilePath->Length < 3 * sizeof(CHAR)) { return STATUS_INVALID_PARAMETER; }
RtlZeroMemory(pIoStatusBlock, sizeof(IO_STATUS_BLOCK));
//根目录符号链接 ntStatus = RtlStringCbPrintfW( wRootPath, sizeof(wRootPath), L"\??\%c:\", punsFilePath->Buffer[0]); ASSERT(NT_SUCCESS(ntStatus)); RtlInitUnicodeString(&unsRootPath, (PCWSTR)wRootPath); ......完整代码请点击阅读原文查看

3. 文件关闭的实现:  分两步:

发送主功能号为IRP_MJ_CLEANUP的IRP向FSD表明: 文件对象句柄数已为0 =>
发送主功能号为IRP_MJ_CLOSE的IRP向FSD表明: 文件对象引用计数已为0,由FSD负责销毁文件对象。

(1)文件关闭1:IRP_MJ_CLEANUP
//自己发送IRP请求关闭文件的第1步(IRP_MJ_CLEANUP),表示文件对象句柄数为0NTSTATUS IrpCleanupFile(    IN PDEVICE_OBJECT pDeviceObject,    IN PFILE_OBJECT pFileObject){    PIRP pIrp;    PIO_STACK_LOCATION pIrpSp;    NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;    KEVENT kEvent;    IO_STATUS_BLOCK userIosb;
//卷参数块检查 if (pFileObject->Vpb == NULL || pFileObject->Vpb->DeviceObject == NULL) { return STATUS_INVALID_PARAMETER; }
// // IRP_MJ_CLEANUP // //分配IRP pIrp = IoAllocateIrp(pDeviceObject->StackSize, FALSE); if (pIrp == NULL) { return STATUS_INSUFFICIENT_RESOURCES; }
//填写IRP pIrp->MdlAddress = NULL; pIrp->AssociatedIrp.SystemBuffer = NULL; pIrp->UserBuffer = NULL; pIrp->Flags = IRP_CLOSE_OPERATION | IRP_SYNCHRONOUS_API; pIrp->RequestorMode = KernelMode; pIrp->UserIosb = &userIosb; KeInitializeEvent(&kEvent, NotificationEvent, FALSE); pIrp->UserEvent = &kEvent; pIrp->Tail.Overlay.Thread = 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),表示引用计数数为0NTSTATUS 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; } ......完整代码请点击阅读原文查看

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; } ......完整代码请点击阅读原文查看

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; } ......完整代码请点击阅读原文查看

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请求操作文件全面总结

三、文件IRP操作测试

内核层自己发送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,            ......完整代码请阅读原文

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, ediPAGE:00000001405C0419                 mov     r15, r9PAGE:00000001405C041C                 test    dword ptr [r9+50h], 800h ; pFileObject->Flags: FO_DIRECT_DEVICE_OPEN or notPAGE:00000001405C0424                 mov     rbx, r8PAGE:00000001405C0427                 mov     r13, rdxPAGE:00000001405C042A                 mov     [rbp+90h+var_110], rdiPAGE:00000001405C042E                 mov     r12, rcxPAGE:00000001405C0431                 lea     esi, [rdi+2]    ; DesiredAccess初始化: FILE_WRITE_DATAPAGE:00000001405C0434                 jnz     short loc_1405C0454PAGE:00000001405C0436                 lea     rdx, [rbp+90h+var_78]PAGE:00000001405C043A                 mov     rcx, r9PAGE:00000001405C043D                 call    IopGetBasicInformationFilePAGE:00000001405C0442                 test    eax, eaxPAGE:00000001405C0444                 js      loc_1405C05C2PAGE:00000001405C044A                 test    [rbp+90h+var_58], 10h ; pFileBasicInfo->FileAttributes: FILE_ATTRIBUTE_DIRECTORY or notPAGE:00000001405C044E                 lea     eax, [rdi+4]    ; FILE_ADD_SUBDIRECTORYPAGE:00000001405C0451                 cmovnz  esi, eax        ; 后续打开权限: 如果是目录则为FILE_ADD_SUBDIRECTORY,否则FILE_WRITE_DATA...完整代码请点击阅读原文

(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); ...... 完整代码请点击阅读原文

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; } ...... 完整代码请点击阅读原文

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项 ...... 完整代码请点击阅读原文

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)


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;}



内核层自己发送IRP请求操作文件全面总结

- End -


内核层自己发送IRP请求操作文件全面总结


看雪ID:低调putchar

https://bbs.pediy.com/user-home-817607.htm

  *本文由看雪论坛 低调putchar 原创,转载请注明来自看雪社区。




# 往期推荐





内核层自己发送IRP请求操作文件全面总结
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



内核层自己发送IRP请求操作文件全面总结

球分享

内核层自己发送IRP请求操作文件全面总结

球点赞

内核层自己发送IRP请求操作文件全面总结

球在看



内核层自己发送IRP请求操作文件全面总结

点击“阅读原文”,了解更多!

本文始发于微信公众号(看雪学院):内核层自己发送IRP请求操作文件全面总结

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: