绕过Copy-On-Write机制安装全局Hook

创建时间:2005-10-22
文章属性:原创
文章提交: Addylee (Addylee2004_at_163.com)

Jeffrey Richter在他的<<widows核心编程>>一书中对Ring 3级的API Hook方法做了详细的介绍,但是一般的Ring 3无论是修改IAT,还是插入JMP XXX都将导致Copy-On-Write的发生,如果,要在系统范围内安装一个全局的Hook的话,就不得不枚举系统中所有进程,对所有进程中的相应模块做同样的修改,这样以来,对系统性能,是有一定的负面影响的。另一方面,如果要做系统范围内的全局Hook的话,可以直接在Ring 0级通过Hook系统调,修改目标API的指令等方法实现。但是,代码在Ring 0的地址空间中,Ring 3环境下的程序无法直接调用。
  由于Windows利用了PTE中的第9位用于Copy-On-Write机制。而Ring 3的代码无法访问PTE的,因此要绕过Copy-On-Write的话,该程序还是无法避免的要工作在Ring 0环境下。本文将以修改Kernel32.dll内存映象中的CreateProcessW为例,介绍绕过Copy-On-Write实现全局Hook的一种方法。我的实验环境是Windows 2000 SP4 内部版本2195。因为EProcess的未公开原因,本例在其它版本的Windows不能保证正确运行。
  一般情况下,每个进程都加载了Kernel32.dll这个模块,并且绝大多数情况下Kernel32在每个进程中所加载的基址都一样,在物理内存中,也只有一份Kernel32的映象,所以可以让用户程序LoadLibrary后,把Kernel32的基地址发到Ring 0的驱动程序中,让驱动程序来修改相应PTE,禁了Copy-On-Write后再修改相应的API指令就行了,但是,为了防止某种可能,比如:之前有一个进程也对它进行了写操作,让系统中有了两份或多份Kernel32的映象,而在用户级LoadLibrary,最多只可能修改到某一个映象,所以,我从内核中枚举了所有的EPROCESS结构,再根据PEB_LDR_DATA结构中找到它的所加载的模信息,对其修改。 直接操作各个进程地址空间的数据,很不方便,可以用Windows 未公开API,KeAttachProcess, 函数来切换到指定进程的内存上下文环境。把CreateProcessW的入口处改成了JMP XXX,但是,跳到哪去呢?程序工作在
Ring 0下,CreateProcessW不可能直接那里边的一个函数中的,但是,PE文件中每个节都会存在一些“空洞”,kernel32也不例外,就把代码Copy到Kernel32的某个节区的“空洞”中去吧。如果“空洞”太小,怎么办呢?可以把我们的代码写成一个DLL,在那个“空洞”中放上一小段代码来Load这个DLL,当然,也有可能在某种极端的情况下,这点“空洞”还是不够 就:(
struct   _hardware_pte_x86 (sizeof=4)
       +0 bits0-0 valid
       +0 bits1-1 write
       +0 bits2-2 owner
       +0 bits3-3 writethrough
       +0 bits4-4 cachedisable
       +0 bits5-5 accessed
       +0 bits6-6 dirty
       +0 bits7-7 largepage
       +0 bits8-8 global
       +0 bits9-9 copyonwrite
       +0 bits10-10 prototype
       +0 bits11-11 reserved
       +0 bits12-31 pageframenumber
从上面可以看出,bits 9 被用于Copy-On-Write机制,以下这段内容摘自<<Undocumented Windows NT>>
  The VirtualProtect() function does not mark the page as read-write–it keeps the page as
read-only. Nevertheless, to distinguish this page from normal read-only pages, it is marked for copy-on-write. Windows NT uses one of the available PTE bits for doing this. When this page is written onto, because it is a read-only page, the processor raises a page fault exception. The page fault handler makes a copy of the page and modifies the page table of the faulting process accordingly. The new copy is marked as read-write so that the process can write to it.
所以只要把Read-Only属性去掉,再对这个DLL进行写入,就可以绕过Copy-On-Write机制了。:)

#include <ntddk.h>
#include "proc.h"   // 进程块的结构信息
#include "PE.h"     // PE文件的一些结构信息
#include "Page.h"   // 页表,页目录操作

#define PEBOFFSET 0x1B0   // PEB指针位于EPPROCESS中偏移0x1B0处
#define FLINKOFFSET 0xA0  // 进程的链表指针。这些信息可以通过kd得到。

// 为了突出重点,节省篇幅,硬编码了两个未公开API的地址,其实也可以通过操作PE
// 的导出表来得到这些信息。
typedef NTSTATUS (NTAPI *KEATTACHPROCESS)(PPEB);
typedef NTSTATUS (NTAPI *KEDETACHPROCESS)();
KEATTACHPROCESS KeAttachProcess = 0x8042bd32;
KEDETACHPROCESS KeDetachProcess = 0x8042beca;

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath)
{
  UNICODE_STRING Kernel32;
  RtlInitUnicodeString(&Kernel32, L"C://WINNT//SYSTEM32//KERNEL32.dll");
  __try
{  
    Hook(&Kernel32, "CreateProcessW", NewCreateProcessW);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
   DbgPrint("Error");
}
RtlFreeUnicodeString(&Kernel32);
return STATUS_SUCCESS;
}

VOID Hook(PUNICODE_STRING pModuleName, PCHAR pFunctionName, PVOID pfnNewFunction)
{
  PLIST_ENTRY pCurrentList = NULL, pTempList = NULL, pLoadOrderModuleList, list;
  PPEB pPeb = NULL;
  PHYSICAL_ADDRESS paOld,  paCurrent;
  ULONG hModule, temp, pEProcess;
  paOld.QuadPart = 0;
  paCurrent.QuadPart = 0;
    
  pEProcess = (ULONG)IoGetCurrentProcess();
  pCurrentList = (PLIST_ENTRY)(pEProcess + FLINKOFFSET);
  pTempList = pCurrentList;
  // 对所有进程进行枚举。
  do
  {
    pEProcess = (ULONG)pTempList - FLINKOFFSET;
    pPeb = (PPEB)(*(PULONG)(pEProcess + PEBOFFSET));
    if (pPeb != NULL)
    {
      KeAttachProcess(pEProcess);  // 切换内存上下文到指定的进程
      pLoadOrderModuleList = pPeb->LoaderData->InLoadOrderModuleList.Flink;
      list = pLoadOrderModuleList;
      do   // 遍历进程所加载模块中,直到找到kernel32
      {
      if(0==RtlCompareUnicodeString(&(((PLDR_MODULE)list)->FullDllName), pModuleName, TRUE)))
      {
        hModule = ((PLDR_MODULE)list)->BaseAddress;
        //Kernel32可能被调出,对它进行一次读操作,由于KeAttachProcess切换到了该进程的地址空间
        //所以让Windows自动处理缺页,把它调入吧,以免它“漏网”:)
        temp = *(PULONG)hModule;
        paCurrent = MmGetPhysicalAddress(hModule);
        // 如果上次处理过的,和现在的在同一物理地址上,就不处理了,当然如果,在内存中,有两个
        // 以上的映像的话,这种处理方法不是很有效(还是造成了重复的工作)不过,在我实验中,
        // 似乎总是只有一份Kernel32的内存映象,做个循环,只不过是为了以防万一。:)
        if (paOld.QuadPart != paCurrent.QuadPart)
        {
          paOld.QuadPart = paCurrent.QuadPart;
     Patch(hModule, pFunctionName, pfnNewFunction);
         }
    break;
        }
        list = list->Flink;
       } while(list != pLoadOrderModuleList);
       KeDetachProcess();
      }
      pTempList = pTempList->Flink;
  } while(pTempList != pCurrentList);
}

VOID Patch(PVOID hModule, PCHAR pFunctionName, PVOID pfnNewFunction)
{
  ULONG len, n;
  PVOID pfnOrig, SectionGapStart;
  pfnOrig = GetFunctionAddress(hModule, pFunctionName);
  len = GetMyFunctionLen(pfnNewFunction);
  SectionGapStart = GetSectionGap(hModule, len);
  if (SectionGapStart == NULL)
    return NULL;

  PTE_ENTRY((ULONG)SectionGapStart) |= 2; // Read-Only 位。
  for (n = 0; n < len; n++)
  {
    *(PUCHAR)((PUCHAR)SectionGapStart + n) = *(PUCHAR)((PUCHAR)pfnNewFunction + n);
  }
  /* 我的机器上的CreateProcessW的代码,是这样了。
    KERNEL32!CreateProcessW
    001B:77E6B252    55    PUSH     EBP
    001B:77E6B253    8BEC    MOV    EBP, ESP
    001B:77E6B255    FF752C    PUSH    DWORD PTR [EBP+2C]
    第二条和第三条指令正好是5Byte的长度,所以,我选择把第二条和第三条改成跳转指令。
    跳转指令码为0xE9,位移计算:目的地址 - 起始地址 - 跳转指令本身的长度。
  */
  for (len = 1; len <= 5; len++, n++)
    *(PUCHAR)((PUCHAR)SectionGapStart + n) = *(PUCHAR)((PUCHAR)pfnOrig + len);
  *(PUCHAR)((PUCHAR)SectionGapStart + n) = 0xE9;
  *(PULONG)((PUCHAR)SectionGapStart+n+1) = (ULONG)(((PUCHAR)pfnOrig + 6)
                                          - (ULONG)((PUCHAR)SectionGapStart + n) - 5);
  PTE_ENTRY((ULONG)SectionGapStart) &= 0xFFD;
    
  PTE_ENTRY((ULONG)pfnOrig) |= 2;  
  n += 6;
  __asm CLI                        
  *(PUCHAR)((PUCHAR)pfnOrig + 1) = 0xE9;
  *(PULONG)((PCHAR)pfnOrig + 2) = ((ULONG)SectionGapStart - (ULONG)((PUCHAR)pfnOrig+1) - 5);
  __asm STI
  PTE_ENTRY((ULONG)pfnOrig) &= 0xFFD;
}
// 根据指定的模块获取代码节的“空洞”偏移地址。
PVOID GetSectionGap(PVOID hModule, USHORT GapSize)
{
  PIMAGE_DOS_HEADER pDosHeader = hModule;
  PIMAGE_NT_HEADERS pNtHeader;
  PIMAGE_SECTION_HEADER pSectionHeader;
  ULONG n = 0;

  if (pDosHeader->e_magic != 'ZM')
    return NULL;
  pNtHeader = (PIMAGE_NT_HEADERS)((PCHAR)hModule + pDosHeader->e_lfanew);
  if (pNtHeader->Signature != 'EP')
    return NULL;
  pSectionHeader = (PCHAR)pNtHeader + sizeof(IMAGE_NT_HEADERS);
  for (n = 0; n < pNtHeader->FileHeader.NumberOfSections; n++)
  {
    pSectionHeader += n;
    // 找到代码节,该法不总是有效,Borland编译器好像代码节为.CODE
    if (IsStringEqual(pSectionHeader->Name, ".text"))
    {
      if ((PAGE_SIZE - (pSectionHeader->Misc.VirtualSize & PAGE_SIZE)) > GapSize)
      {
        return (ULONG)((PCHAR)hModule + pSectionHeader->VirtualAddress
            + pSectionHeader->Misc.VirtualSize);
       }
      }
    }
  return NULL;
}
// 从指定模块根据导出表获取导出函数地址。
PVOID GetFunctionAddress(PVOID hModule, PCHAR pFunctionName)
{
  PIMAGE_DOS_HEADER pDosHeader = hModule;
  PIMAGE_NT_HEADERS pNtHeader;
  PIMAGE_EXPORT_DIRECTORY pExportDirectory;
  ULONG n;
  PULONG pExportFunction;
  PULONG pFunctionAddress;
  PUSHORT pAddressOridinals;

  if (pDosHeader->e_magic != 'ZM')
    return NULL;
  pNtHeader = (PIMAGE_NT_HEADERS)((PCHAR)hModule + pDosHeader->e_lfanew);
  if (pNtHeader->Signature != 'EP')
    return NULL;
  pExportDirectory = (PCHAR)hModule +
      pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  pExportFunction = (PCHAR)hModule + pExportDirectory->AddressOfNames;
  pFunctionAddress = (PCHAR)hModule + pExportDirectory->AddressOfFunctions;
  pAddressOridinals = (PCHAR)hModule + pExportDirectory->AddressOfNameOrdinals;

  for (n = 0; n < pExportDirectory->NumberOfNames; n++)
  {
    if (IsStringEqual(((PCHAR)hModule + *(pExportFunction + n)), pFunctionName) == TRUE)
      return  (PVOID)((PCHAR)hModule + *(pFunctionAddress + *(pAddressOridinals + n)));
  }
}
这个函数是最晦涩的了。这段代码将被Copy到Kernel32的一个“空洞”中执行,既要处理重定位, 又要在那里,手工获取LoadLibrary和GetProcAddress的地址,而且VC中inline asm也没有masm32那么直接,更可恨的是 naked 函数中申明局部变量,好像会破坏了堆栈平衡,本来,想申明一两个register变量,加强程序的可读性,但是,又不能保证编译器,总是成功分配register变量,然后,又想,在一个__forceinline函数中,写所有的代码,在这个__forceinline函数,使用局部变量,加强可读性,而这个naked函数只要调用__forceinline函数就好了,但是,似乎也没办法让编译器总是inline成功。就只有全部自己写了 :(
这个函数从PEB中取kernel32的基地址,再根据导出表获取LoadLibrary和GetProcAddress的地址,然后加载User32.dll,关获取MessageBoxW的地址,再调用它。最后,再Free User32.dll。
__declspec(naked) NewCreateProcessW()
{
  __asm
  {
    PUSHAD
    MOV EAX, DWORD PTR FS:[0x30]
    MOV EAX, DWORD PTR [EAX+0xC]
    MOV ESI,  DWORD PTR [EAX+0x1C]
    LODSD
    MOV EBX,  DWORD PTR [EAX+0x8]  // EBX: KERNEL32.DLL的基址
    MOV EAX,  EBX
    ADD EAX,  0x3C
    MOV EAX,  [EAX]
    ADD EAX,  EBX                                 
    // EAX 定位到 IMAGE_DIRECTORY_ENTRY_EXPORT
    ADD EAX, ((TYPE IMAGE_NT_HEADERS) - ((TYPE IMAGE_DATA_DIRECTORY)*16))
    MOV EAX, [EAX]
    ADD EAX, EBX    // EAX -> 导出表    
    MOV ESI, [EAX + 0x20]
    ADD ESI, EBX    // ESI -> 导出函数名字的 RVA 数组
        
    PUSH ESI
    // int 3
    MOV ECX, 0xB
    CALL a0
a0:
    POP EBP
    LEA EDI, a0
    SUB EBP, EDI
    LEA EDI, pFreeLibrary
    ADD EDI, EBP
    CALL GetProcAddr  // GetProcAddr 在 EDX 中返回 FreeLibrary 的地址
    POP ESI
    PUSH EDX   // EDX = FreeLibrary 的地址, 保存起来
        
    PUSH ESI
    MOV ECX, 0xE
    CALL a1
a1:
    POP EBP
    LEA EDI, a1
    SUB EBP, EDI
    LEA  EDI, pGetProcAddress
    ADD  EDI, EBP    
    CALL GetProcAddr  // GetProcAddr 在 EDX 中返回 GetProcAddress 的地址
    POP ESI      
    PUSH EDX  // 保存 EDX = GetProcAddress 的地址, 保存起来
        
    MOV ECX, 0xC
    CALL a2
a2:
    POP  EBP
    LEA  EDI, a2
    SUB  EBP, EDI
    LEA  EDI, pLoadLibraryA
    ADD  EDI, EBP    
    CALL GetProcAddr  // GetProcAddr 在 EDX 中返回 LoadLibraryA 的地址
        
    CALL a3
a3:
    POP EBP
    LEA EDI, a3
    SUB EBP, EDI
    LEA EDI, pUser32dll
    ADD EBP, EDI
    PUSH EBP
    CALL EDX  // 调用 LoadLibraryA
        
    POP EDX   // EDX = GetProcAddress 的地址
    CALL a4
a4:
    POP EBP
    LEA EDI, a4
    SUB EBP, EDI
    LEA EDI, pMessageBoxW
    ADD EBP, EDI
    PUSH EAX
    PUSH EBP
    PUSH EAX  // EAX = user32.dll的模块句柄
    CALL EDX  // 调用 GetProcAddress
        
    PUSH 0
    PUSH [esp + 0x38]
    PUSH [esp + 0x3C]
    PUSH 0
    CALL EAX  // 调用MessageBoxW
    POP EAX
    POP EDX  // FreeLibrary 的地址
    PUSH EAX
    CALL EDX
    JMP over
    
GetProcAddr:    
    MOV EDX, [EAX+0x18] //以名字导出的函数个数
    FindNext:
    PUSH EDI
    PUSH ECX
    PUSH EDX
    PUSH ESI
    MOV ESI, [ESI]
    ADD ESI, EBX
    CLD
    REPE CMPSB
    POP ESI
    POP EDX
    POP ECX
    POP EDI
    JZ Found
    ADD ESI, 4
    DEC EDX
    JNZ FindNext

Found:    
    MOV ECX, [EAX+0x18]
    SUB ECX, EDX
    SAL ECX, 1
    MOV EDX, [EAX+0x24] // 导出函数序号表 的 RVA
    ADD EDX, EBX
    ADD EDX, ECX
    XOR ECX, ECX
    MOV CX, WORD PTR [EDX]        // ECX <= 编号
    SAL ECX, 1
    SAL ECX, 1
    MOV EDX, [EAX+0x1C]
    ADD EDX, EBX
    ADD EDX, ECX
    MOV EDX, [EDX]
    ADD EDX, EBX
    RET    
        
pLoadLibraryA:
    _emit 'L'
    _emit 'o'
    _emit 'a'
    _emit 'd'
    _emit 'L'
    _emit 'i'
    _emit 'b'
    _emit 'r'
    _emit 'a'
    _emit 'r'
    _emit 'y'
    _emit 'A'
    _emit 0
pGetProcAddress:
    _emit 'G'
    _emit 'e'
    _emit 't'
    _emit 'P'
    _emit 'r'
    _emit 'o'
    _emit 'c'
    _emit 'A'
    _emit 'd'
    _emit 'd'
    _emit 'r'
    _emit 'e'
    _emit 's'
    _emit 's'
    _emit 0
pMessageBoxW:
    _emit 'M'
    _emit 'e'
    _emit 's'
    _emit 's'
    _emit 'a'
    _emit 'g'
    _emit 'e'
    _emit 'B'
    _emit 'o'
    _emit 'x'
    _emit 'W'
    _emit 0
pUser32dll:
    _emit 'U'
    _emit 's'
    _emit 'e'
    _emit 'r'
    _emit '3'
    _emit '2'
    _emit '.'
    _emit 'd'
    _emit 'l'
    _emit 'l'
    _emit 0
pFreeLibrary:
    _emit 'F'
    _emit 'r'
    _emit 'e'
    _emit 'e'
    _emit 'L'
    _emit 'i'
    _emit 'b'
    _emit 'r'
    _emit 'a'
    _emit 'r'
    _emit 'y'
    _emit 0
over:        
    POPAD
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
    _emit 0x90
  }
}
要把代码Copy到指别的地方,就要知道要Copy的字节数,本来,在masm32中很简单的一件事,定义两个标号就OK了,在这VC里确变得麻烦起来。
ULONG GetMyFunctionLen(PULONG pfn)
{
  ULONG res = 0;
  __asm
  {
    MOV EAX, 0x90909090     // 新的函数以0x90909090作为结束的标志
    MOV ECX, 0xFFFFFFFF
    MOV EDI, pfn
    CLD
    REPNZ SCASD
    NOT ECX
    DEC ECX
    MOV res, ECX
  }
  return res * 4;
}
自己写了一段代码比较两个字符串。
BOOLEAN IsStringEqual(PCHAR psrc, PCHAR pdest)
{
  BOOLEAN res = FALSE;
  __asm
  {
    XOR EAX, EAX
    MOV ECX, EAX
    DEC ECX
    MOV EDI, psrc
    CLD
    REPNZ SCASB
    NOT ECX
    DEC ECX
    MOV EDX, ECX

    MOV ECX, EAX
    DEC ECX
    MOV EDI, pdest
    REPNZ SCASB
    NOT ECX
    DEC ECX
    
    CMP ECX, EDX
    JNE over

    MOV ESI, psrc
    MOV EDI, pdest    
    REPE CMPSB
    JNZ over
    MOV res, TRUE
over:
    }
  return res;
}

水平有限,纰漏之处难免,希望诸位大虾斧正。
以上几个头文件都是一些结构和宏的定义,就没有帖出来了。
若有不正确的地方,欢迎交流,QQ: 22517257

参考资料:
Windows 环境下32位汇编语言程序设计 罗云彬 著
Rootkits: Subverting the Windows Kernel By Greg Hoglund, James Butler
Undocumented Windows NT
Undocumented Windows 2000 Secrets
kd> x nt!ObReferenceObjectByHandleWithTag fffff805`7b2efb60 nt!ObReferenceObjectByHandleWithTag (ObReferenceObjectByHandleWithTag) kd> !pte fffff805`7b2efb60 VA fffff8057b2efb60 PXE at FFFFE271389C4F80 PPE at FFFFE271389F00A8 PDE at FFFFE2713E015EC8 PTE at FFFFE27C02BD9778 contains 0000000004609063 contains 000000000470A063 contains 0A00000002A001A1 contains 0000000000000000 pfn 4609 ---DA--KWEV pfn 470a ---DA--KWEV pfn 2a00 -GL-A--KREV LARGE PAGE pfn 2aef [+] [InstallHook] 找到目标函数地址: FFFFF8057B2EFB60 [PTE_HOOK] 开始隔离面: PID=7176, 地址=0xFFFFF8057B2EFB60 [PTE_HOOK] 正在拆分大: 输入PDE=0xa00000002a001a1, 输出PDE=0xFFFFBD0464F73BB0 [PTE_HOOK] 大拆分完成: 新PTE表物理地址=0x239de6000 [PTE_HOOK] G-Bit Info: Align Address: 0xFFFFF8057B2EF000 IsLargePage: 1 PDE: 0xa00000002a000a1 (Address: 0xFFFFE2713E015EC8) [PTE_HOOK] 清除大G: PDE=0xa00000002a000a1 [PTE_HOOK] 直接入隔离成功: VA=FFFFF8057B2EFB60 -> Hook=FFFFF805802B1500 [+] [InstallHook] Hook 成功安装. 跳板地址: FFFFF8057B2EFB60 Break instruction exception - code 80000003 (first chance) obpcallback!InstallHook+0xee: fffff805`802b138e cc int 3 kd> !pte fffff805`7b2efb60 VA fffff8057b2efb60 PXE at FFFFE271389C4F80 PPE at FFFFE271389F00A8 PDE at FFFFE2713E015EC8 PTE at FFFFE27C02BD9778 contains 0000000004609063 contains 000000000470A063 contains 0A00000002A000E1 contains 0000000000000000 pfn 4609 ---DA--KWEV pfn 470a ---DA--KWEV pfn 2a00 --LDA--KREV LARGE PAGE pfn 2aef 我这个代码隔离指定进程ptehook有问题,隔离功能不能隔离。你看看打印结果。隔离后结果是全局hook,没有隔离指定进程。代码如下:#include <ntifs.h> #include <ntddk.h> #include <intrin.h> #include "ptehook.h" #define CR0_WP (1 << 16) HANDLE targetProcessId = NULL; typedef INT(*LDE_DISASM)(PVOID address, INT bits); typedef unsigned long DWORD; typedef unsigned __int64 ULONG64; // 使用WDK标准类型 typedef unsigned char BYTE; typedef LONG NTSTATUS; // 修正后的跳转指令结构 #pragma pack(push, 1) typedef struct _JMP_ABS { BYTE opcode[6]; // FF 25 00 00 00 00 ULONG64 address; // 8字节绝对地址 } JMP_ABS, * PJMP_ABS; #pragma pack(pop) LDE_DISASM lde_disasm; // 初始化引擎 VOID lde_init() { lde_disasm = (LDE_DISASM)ExAllocatePool(NonPagedPool, 12800); memcpy(lde_disasm, szShellCode, 12800); } // 得到完整指令长度,避免截断 ULONG GetFullPatchSize(PUCHAR Address) { ULONG LenCount = 0, Len = 0; // 至少需要14字节 while (LenCount <= 14) { Len = lde_disasm(Address, 64); Address = Address + Len; LenCount = LenCount + Len; } return LenCount; } #define PROCESS_NAME_LENGTH 16 #define DRIVER_TAG &#39;HKOB&#39; EXTERN_C char* PsGetProcessImageFileName(PEPROCESS process); char target_process_name[] = "oxygen.exe"; typedef NTSTATUS(*fn_ObReferenceObjectByHandleWithTag)( HANDLE Handle, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, ULONG Tag, PVOID* Object, POBJECT_HANDLE_INFORMATION HandleInformation ); fn_ObReferenceObjectByHandleWithTag g_OriginalObReferenceObjectByHandleWithTag = NULL; // PTE Hook Framework #define MAX_G_BIT_RECORDS 128 #define MAX_HOOK_COUNT 64 #define PAGE_ALIGN(va) ((PVOID)((ULONG_PTR)(va) & ~0xFFF)) #define PDPTE_PS_BIT (1 << 7) #define PDE_PS_BIT (1 << 7) #define PTE_NX_BIT (1ULL << 63) #define CACHE_WB (6ULL << 3) // 表结构定义 typedef struct _PAGE_TABLE { UINT64 LineAddress; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 dirty : 1; UINT64 pat : 1; UINT64 global : 1; UINT64 ignored_1 : 3; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_2 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*PteAddress; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 dirty : 1; UINT64 large_page : 1; UINT64 global : 1; UINT64 ignored_2 : 3; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_3 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*PdeAddress; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 ignored_1 : 1; UINT64 page_size : 1; UINT64 ignored_2 : 4; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_3 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*PdpteAddress; UINT64* Pml4Address; BOOLEAN IsLargePage; BOOLEAN Is1GBPage; UINT64 OriginalPte; UINT64 OriginalPde; UINT64 OriginalPdpte; UINT64 OriginalPml4e; HANDLE ProcessId; } PAGE_TABLE, * PPAGE_TABLE; // G信息记录结构体 typedef struct _G_BIT_INFO { void* AlignAddress; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 dirty : 1; UINT64 large_page : 1; UINT64 global : 1; UINT64 ignored_2 : 3; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_3 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*PdeAddress; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 dirty : 1; UINT64 pat : 1; UINT64 global : 1; UINT64 ignored_1 : 3; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_2 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*PteAddress; BOOLEAN IsLargePage; } G_BIT_INFO, * PG_BIT_INFO; typedef struct _HOOK_INFO { void* OriginalAddress; void* HookAddress; UINT8 OriginalBytes[20]; UINT8 HookBytes[20]; UINT32 HookLength; BOOLEAN IsHooked; HANDLE ProcessId; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 dirty : 1; UINT64 pat : 1; UINT64 global : 1; UINT64 ignored_1 : 3; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_2 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*HookedPte; union { struct { UINT64 present : 1; UINT64 write : 1; UINT64 user : 1; UINT64 write_through : 1; UINT64 cache_disable : 1; UINT64 accessed : 1; UINT64 dirty : 1; UINT64 large_page : 1; UINT64 global : 1; UINT64 ignored_2 : 3; UINT64 page_frame_number : 36; UINT64 reserved_1 : 4; UINT64 ignored_3 : 7; UINT64 protection_key : 4; UINT64 execute_disable : 1; } flags; UINT64 value; }*HookedPde; } HOOK_INFO; class PteHookManager { public: bool fn_pte_inline_hook_bp_pg(HANDLE process_id, _Inout_ void** ori_addr, void* hk_addr); bool fn_remove_hook(HANDLE process_id, void* hook_addr); static PteHookManager* GetInstance(); HOOK_INFO* GetHookInfo() { return m_HookInfo; } char* GetTrampLinePool() { return m_TrampLinePool; } UINT32 GetHookCount() { return m_HookCount; } bool fn_resume_global_bits(void* align_addr); ~PteHookManager(); // 添加析构函数声明 private: bool WriteTrampolineInstruction(void* trampoline, const JMP_ABS& jmpCmd); void fn_add_g_bit_info(void* align_addr, void* pde_address, void* pte_address); bool fn_isolation_pagetable(UINT64 cr3_val, void* replace_align_addr, void* split_pde); bool fn_isolation_pages(HANDLE process_id, void* ori_addr); bool fn_split_large_pages(void* in_pde, void* out_pde); NTSTATUS get_page_table(UINT64 cr3, PAGE_TABLE& table); void* fn_pa_to_va(UINT64 pa); UINT64 fn_va_to_pa(void* va); __forceinline KIRQL DisableWriteProtection(); __forceinline void EnableWriteProtection(KIRQL oldIrql); void logger(const char* info, bool is_err, LONG err_code = 0); void PrintPageTableInfo(const PAGE_TABLE& table); void PrintHookInfo(const HOOK_INFO& hookInfo); void PrintGBitInfo(const G_BIT_INFO& gbitInfo); static constexpr SIZE_T MAX_HOOKS = 256; // 根据需求调整 G_BIT_INFO m_GbitRecords[MAX_G_BIT_RECORDS]; UINT32 m_GbitCount = 0; void* m_PteBase = 0; HOOK_INFO m_HookInfo[MAX_HOOK_COUNT] = { 0 }; DWORD m_HookCount = 0; char* m_TrampLinePool = nullptr; // 合并为一个声明 UINT32 m_PoolUsed = 0; static PteHookManager* m_Instance; }; PteHookManager* PteHookManager::m_Instance = nullptr; // 实现部分 __forceinline KIRQL PteHookManager::DisableWriteProtection() { KIRQL oldIrql = KeRaiseIrqlToDpcLevel(); UINT64 cr0 = __readcr0(); __writecr0(cr0 & ~0x10000); // 清除CR0.WP _mm_mfence(); return oldIrql; } __forceinline void PteHookManager::EnableWriteProtection(KIRQL oldIrql) { _mm_mfence(); UINT64 cr0 = __readcr0(); __writecr0(cr0 | 0x10000); // 设置CR0.WP KeLowerIrql(oldIrql); } void PteHookManager::logger(const char* info, bool is_err, LONG err_code) { if (is_err) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] ERROR: %s (0x%X)\n", info, err_code); } else { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] INFO: %s\n", info); } } void PteHookManager::PrintPageTableInfo(const PAGE_TABLE& table) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] Page Table Info for VA: 0x%p\n", (void*)table.LineAddress); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " PML4E: 0x%llx (Address: 0x%p)\n", table.OriginalPml4e, table.Pml4Address); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " PDPTE: 0x%llx (Address: 0x%p), Is1GBPage: %d\n", table.OriginalPdpte, table.PdpteAddress, table.Is1GBPage); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " PDE: 0x%llx (Address: 0x%p), IsLargePage: %d\n", table.OriginalPde, table.PdeAddress, table.IsLargePage); if (!table.IsLargePage && !table.Is1GBPage) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " PTE: 0x%llx (Address: 0x%p)\n", table.OriginalPte, table.PteAddress); } } void PteHookManager::PrintHookInfo(const HOOK_INFO& hookInfo) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] Hook Info:\n"); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Original Address: 0x%p\n", hookInfo.OriginalAddress); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Hook Address: 0x%p\n", hookInfo.HookAddress); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Hook Length: %d\n", hookInfo.HookLength); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Is Hooked: %d\n", hookInfo.IsHooked); // 打印原始字节 DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Original Bytes: "); for (UINT32 i = 0; i < sizeof(hookInfo.OriginalBytes); i++) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "%02X ", hookInfo.OriginalBytes[i]); } DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "\n"); // 打印Hook字节 DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Hook Bytes: "); for (UINT32 i = 0; i < sizeof(hookInfo.HookBytes); i++) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "%02X ", hookInfo.HookBytes[i]); } DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "\n"); } void PteHookManager::PrintGBitInfo(const G_BIT_INFO& gbitInfo) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] G-Bit Info:\n"); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Align Address: 0x%p\n", gbitInfo.AlignAddress); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " IsLargePage: %d\n", gbitInfo.IsLargePage); if (gbitInfo.PdeAddress) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " PDE: 0x%llx (Address: 0x%p)\n", gbitInfo.PdeAddress->value, gbitInfo.PdeAddress); } if (gbitInfo.PteAddress) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " PTE: 0x%llx (Address: 0x%p)\n", gbitInfo.PteAddress->value, gbitInfo.PteAddress); } } void* PteHookManager::fn_pa_to_va(UINT64 pa) { PHYSICAL_ADDRESS physAddr; physAddr.QuadPart = pa; return MmGetVirtualForPhysical(physAddr); } UINT64 PteHookManager::fn_va_to_pa(void* va) { PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(va); return physAddr.QuadPart; } NTSTATUS PteHookManager::get_page_table(UINT64 cr3_val, PAGE_TABLE& table) { UINT64 va = table.LineAddress; UINT64 pml4e_index = (va >> 39) & 0x1FF; UINT64 pdpte_index = (va >> 30) & 0x1FF; UINT64 pde_index = (va >> 21) & 0x1FF; UINT64 pte_index = (va >> 12) & 0x1FF; // PML4 UINT64 pml4_pa = cr3_val & ~0xFFF; UINT64* pml4_va = (UINT64*)fn_pa_to_va(pml4_pa); if (!pml4_va) return STATUS_INVALID_ADDRESS; table.Pml4Address = &pml4_va[pml4e_index]; table.OriginalPml4e = *table.Pml4Address; if (!(table.OriginalPml4e & 1)) return STATUS_ACCESS_VIOLATION; // PDPTE UINT64 pdpte_pa = table.OriginalPml4e & ~0xFFF; UINT64* pdpte_va = (UINT64*)fn_pa_to_va(pdpte_pa); if (!pdpte_va) return STATUS_INVALID_ADDRESS; table.PdpteAddress = (decltype(table.PdpteAddress))&pdpte_va[pdpte_index]; table.OriginalPdpte = table.PdpteAddress->value; table.Is1GBPage = (table.PdpteAddress->flags.page_size) ? TRUE : FALSE; if (!(table.OriginalPdpte & 1)) return STATUS_ACCESS_VIOLATION; if (table.Is1GBPage) return STATUS_SUCCESS; // PDE UINT64 pde_pa = table.OriginalPdpte & ~0xFFF; UINT64* pde_va = (UINT64*)fn_pa_to_va(pde_pa); if (!pde_va) return STATUS_INVALID_ADDRESS; table.PdeAddress = (decltype(table.PdeAddress))&pde_va[pde_index]; table.OriginalPde = table.PdeAddress->value; table.IsLargePage = (table.PdeAddress->flags.large_page) ? TRUE : FALSE; if (!(table.OriginalPde & 1)) return STATUS_ACCESS_VIOLATION; if (table.IsLargePage) return STATUS_SUCCESS; // PTE UINT64 pte_pa = table.OriginalPde & ~0xFFF; UINT64* pte_va = (UINT64*)fn_pa_to_va(pte_pa); if (!pte_va) return STATUS_INVALID_ADDRESS; table.PteAddress = (decltype(table.PteAddress))&pte_va[pte_index]; table.OriginalPte = table.PteAddress->value; if (!(table.OriginalPte & 1)) return STATUS_ACCESS_VIOLATION; // 打印表信息 PrintPageTableInfo(table); return STATUS_SUCCESS; } bool PteHookManager::fn_split_large_pages(void* in_pde_ptr, void* out_pde_ptr) { auto in_pde = (decltype(PAGE_TABLE::PdeAddress))in_pde_ptr; auto out_pde = (decltype(PAGE_TABLE::PdeAddress))out_pde_ptr; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 正在拆分大: 输入PDE=0x%llx, 输出PDE=0x%p\n", in_pde->value, out_pde); PHYSICAL_ADDRESS LowAddr = { 0 }, HighAddr = { 0 }; HighAddr.QuadPart = MAXULONG64; auto pt = (decltype(PAGE_TABLE::PteAddress))MmAllocateContiguousMemorySpecifyCache( PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached); if (!pt) { logger("分配连续内存失败 (用于拆分大)", true); return false; } UINT64 start_pfn = in_pde->flags.page_frame_number; for (int i = 0; i < 512; i++) { pt[i].value = 0; pt[i].flags.present = 1; pt[i].flags.write = in_pde->flags.write; pt[i].flags.user = in_pde->flags.user; pt[i].flags.write_through = in_pde->flags.write_through; pt[i].flags.cache_disable = in_pde->flags.cache_disable; pt[i].flags.accessed = in_pde->flags.accessed; pt[i].flags.dirty = in_pde->flags.dirty; pt[i].flags.global = 0; pt[i].flags.page_frame_number = start_pfn + i; } out_pde->value = in_pde->value; out_pde->flags.large_page = 0; out_pde->flags.page_frame_number = fn_va_to_pa(pt) >> 12; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 大拆分完成: 新PTE表物理地址=0x%llx\n", fn_va_to_pa(pt)); return true; } bool PteHookManager::fn_isolation_pagetable(UINT64 cr3_val, void* replace_align_addr, void* split_pde_ptr) { PHYSICAL_ADDRESS LowAddr = { 0 }, HighAddr = { 0 }; HighAddr.QuadPart = MAXULONG64; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 开始隔离表: CR3=0x%llx, 地址=0x%p\n", cr3_val, replace_align_addr); auto Va4kb = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached); auto VaPt = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached); auto VaPdt = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached); auto VaPdpt = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached); if (!VaPt || !Va4kb || !VaPdt || !VaPdpt) { if (VaPt) MmFreeContiguousMemory(VaPt); if (Va4kb) MmFreeContiguousMemory(Va4kb); if (VaPdt) MmFreeContiguousMemory(VaPdt); if (VaPdpt) MmFreeContiguousMemory(VaPdpt); logger("分配连续内存失败 (用于隔离表)", true); return false; } PAGE_TABLE Table = { 0 }; Table.LineAddress = (UINT64)replace_align_addr; NTSTATUS status = get_page_table(cr3_val, Table); if (!NT_SUCCESS(status)) { MmFreeContiguousMemory(VaPt); MmFreeContiguousMemory(Va4kb); MmFreeContiguousMemory(VaPdt); MmFreeContiguousMemory(VaPdpt); logger("获取表信息失败", true, status); return false; } UINT64 pte_index = (Table.LineAddress >> 12) & 0x1FF; UINT64 pde_index = (Table.LineAddress >> 21) & 0x1FF; UINT64 pdpte_index = (Table.LineAddress >> 30) & 0x1FF; UINT64 pml4e_index = (Table.LineAddress >> 39) & 0x1FF; memcpy(Va4kb, replace_align_addr, PAGE_SIZE); if (Table.IsLargePage && split_pde_ptr) { auto split_pde = (decltype(PAGE_TABLE::PdeAddress))split_pde_ptr; memcpy(VaPt, (void*)(split_pde->flags.page_frame_number << 12), PAGE_SIZE); } else { memcpy(VaPt, (void*)(Table.PdeAddress->flags.page_frame_number << 12), PAGE_SIZE); } memcpy(VaPdt, (void*)(Table.PdpteAddress->flags.page_frame_number << 12), PAGE_SIZE); memcpy(VaPdpt, (void*)(Table.Pml4Address[pml4e_index] & ~0xFFF), PAGE_SIZE); auto new_pte = (decltype(PAGE_TABLE::PteAddress))VaPt; new_pte[pte_index].flags.page_frame_number = fn_va_to_pa(Va4kb) >> 12; auto new_pde = (decltype(PAGE_TABLE::PdeAddress))VaPdt; new_pde[pde_index].value = Table.OriginalPde; new_pde[pde_index].flags.large_page = 0; new_pde[pde_index].flags.page_frame_number = fn_va_to_pa(VaPt) >> 12; auto new_pdpte = (decltype(PAGE_TABLE::PdpteAddress))VaPdpt; new_pdpte[pdpte_index].flags.page_frame_number = fn_va_to_pa(VaPdt) >> 12; auto new_pml4 = (UINT64*)fn_pa_to_va(cr3_val & ~0xFFF); new_pml4[pml4e_index] = (new_pml4[pml4e_index] & 0xFFF) | (fn_va_to_pa(VaPdpt) & ~0xFFF); __invlpg(replace_align_addr); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 表隔离完成: 新PFN=0x%llx\n", fn_va_to_pa(Va4kb) >> 12); return true; } bool PteHookManager::fn_isolation_pages(HANDLE process_id, void* ori_addr) { PEPROCESS Process; if (!NT_SUCCESS(PsLookupProcessByProcessId(process_id, &Process))) { logger("查找进程失败", true); return false; } DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 开始隔离面: PID=%d, 地址=0x%p\n", (ULONG)(ULONG_PTR)process_id, ori_addr); KAPC_STATE ApcState; KeStackAttachProcess(Process, &ApcState); void* AlignAddr = PAGE_ALIGN(ori_addr); PAGE_TABLE Table = { 0 }; Table.LineAddress = (UINT64)AlignAddr; UINT64 target_cr3 = *(UINT64*)((UCHAR*)Process + 0x28); if (!NT_SUCCESS(get_page_table(target_cr3, Table))) { KeUnstackDetachProcess(&ApcState); ObDereferenceObject(Process); logger("获取目标进程表失败", true); return false; } bool success = false; decltype(PAGE_TABLE::PdeAddress) split_pde = nullptr; if (Table.IsLargePage) { split_pde = (decltype(PAGE_TABLE::PdeAddress))ExAllocatePoolWithTag(NonPagedPool, sizeof(*split_pde), &#39;pdeS&#39;); if (!split_pde || !fn_split_large_pages(Table.PdeAddress, split_pde)) { if (split_pde) ExFreePoolWithTag(split_pde, &#39;pdeS&#39;); KeUnstackDetachProcess(&ApcState); ObDereferenceObject(Process); logger("拆分大失败", true); return false; } if (Table.PdeAddress->flags.global) { Table.PdeAddress->flags.global = 0; fn_add_g_bit_info(AlignAddr, Table.PdeAddress, nullptr); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 清除大G: PDE=0x%llx\n", Table.PdeAddress->value); } } else if (Table.PteAddress && Table.PteAddress->flags.global) { Table.PteAddress->flags.global = 0; fn_add_g_bit_info(AlignAddr, nullptr, Table.PteAddress); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 清除PTE G: PTE=0x%llx\n", Table.PteAddress->value); success = fn_isolation_pagetable(target_cr3, AlignAddr, split_pde); if (split_pde) ExFreePoolWithTag(split_pde, &#39;pdeS&#39;); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 表状态: IsLargePage=%d, Is1GBPage=%d\n", Table.IsLargePage, Table.Is1GBPage); if (Table.PteAddress) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] PTE 值: 0x%llx (G=%d)\n", Table.PteAddress->value, Table.PteAddress->flags.global); } KeUnstackDetachProcess(&ApcState); ObDereferenceObject(Process); if (success) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 面隔离成功\n"); } else { logger("面隔离失败", true); } return success; } KeUnstackDetachProcess(&ApcState); ObDereferenceObject(Process); return true; } bool PteHookManager::WriteTrampolineInstruction(void* trampoline, const JMP_ABS& jmpCmd) { if (!MmIsAddressValid(trampoline)) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] 错误: 内存地址无效 (VA=%p)\n", trampoline); return false; } PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(trampoline); if (physAddr.QuadPart == 0) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] 错误: 无法获取物理地址 (VA=%p)\n", trampoline); return false; } KIRQL oldIrql = KeRaiseIrqlToDpcLevel(); BOOLEAN wpEnabled = (__readcr0() & CR0_WP); if (wpEnabled) { __writecr0(__readcr0() & ~CR0_WP); _mm_mfence(); } PMDL pMdl = IoAllocateMdl(trampoline, sizeof(JMP_ABS), FALSE, FALSE, NULL); if (!pMdl) { if (wpEnabled) __writecr0(__readcr0() | CR0_WP); KeLowerIrql(oldIrql); return false; } NTSTATUS status = STATUS_SUCCESS; __try { MmBuildMdlForNonPagedPool(pMdl); MmProtectMdlSystemAddress(pMdl, PAGE_READWRITE); // 正确入 FF25 00000000 和 8字节地址 memcpy(trampoline, jmpCmd.opcode, 6); // FF25 00000000 *(ULONG64*)((BYTE*)trampoline + 6) = jmpCmd.address; // 地址入 RIP+0 的置 _mm_sfence(); _mm_clflush(trampoline); _mm_clflush((BYTE*)trampoline + 8); __invlpg(trampoline); _mm_mfence(); } __except (EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] 异常: 入跳板失败 (代码: 0x%X)\n", status); } IoFreeMdl(pMdl); if (wpEnabled) { __writecr0(__readcr0() | CR0_WP); _mm_mfence(); } KeLowerIrql(oldIrql); if (!NT_SUCCESS(status)) return false; // 验证入结果 if (*(USHORT*)trampoline != 0x25FF || *(ULONG64*)((BYTE*)trampoline + 6) != jmpCmd.address) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] 验证失败: 跳板内容不匹配\n" " 预期: FF25 [%p]\n" " 实际: %02X%02X %02X%02X%02X%02X [%p]\n", jmpCmd.address, ((BYTE*)trampoline)[0], ((BYTE*)trampoline)[1], ((BYTE*)trampoline)[2], ((BYTE*)trampoline)[3], ((BYTE*)trampoline)[4], ((BYTE*)trampoline)[5], *(ULONG64*)((BYTE*)trampoline + 6)); return false; } return true; } bool PteHookManager::fn_pte_inline_hook_bp_pg(HANDLE process_id, _Inout_ void** ori_addr, void* hk_addr) { // [1] 表隔离 if (!fn_isolation_pages(process_id, *ori_addr)) { return false; } // [2] 获取目标进程上下文 PEPROCESS targetProcess; if (!NT_SUCCESS(PsLookupProcessByProcessId(process_id, &targetProcess))) { return false; } KAPC_STATE apcState; KeStackAttachProcess(targetProcess, &apcState); // [3] 构造跳转指令 JMP_ABS jmpCmd = {}; memcpy(jmpCmd.opcode, "\xFF\x25\x00\x00\x00\x00", 6); // FF25 00000000 jmpCmd.address = reinterpret_cast<ULONG64>(hk_addr); // [4] 直接入被隔离 void* targetFunc = *ori_addr; bool success = false; // 禁用保护 KIRQL oldIrql = DisableWriteProtection(); __try { // 保存原始指令 (用于卸载) RtlCopyMemory(m_HookInfo[m_HookCount].OriginalBytes, targetFunc, sizeof(jmpCmd)); // 入跳转指令到隔离 memcpy(targetFunc, &jmpCmd, 6); *(ULONG64*)((BYTE*)targetFunc + 6) = jmpCmd.address; // 刷新缓存 _mm_sfence(); _mm_clflush(targetFunc); __invlpg(targetFunc); _mm_mfence(); success = true; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 直接入隔离成功: VA=%p -> Hook=%p\n", targetFunc, hk_addr); } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] 入隔离异常: 0x%X\n", GetExceptionCode()); } // 恢复保护 EnableWriteProtection(oldIrql); // [5] 记录Hook信息 if (success) { m_HookInfo[m_HookCount].OriginalAddress = targetFunc; m_HookInfo[m_HookCount].HookAddress = hk_addr; m_HookInfo[m_HookCount].ProcessId = process_id; m_HookInfo[m_HookCount].IsHooked = TRUE; m_HookCount++; } // [6] 清理 KeUnstackDetachProcess(&apcState); ObDereferenceObject(targetProcess); return success; } // 析构函数清理资源 PteHookManager::~PteHookManager() { if (m_TrampLinePool) { MmFreeContiguousMemory(m_TrampLinePool); m_TrampLinePool = nullptr; } } bool PteHookManager::fn_remove_hook(HANDLE process_id, void* hook_addr) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 尝试移除Hook: Hook地址=0x%p\n", hook_addr); for (UINT32 i = 0; i < m_HookCount; i++) { if (m_HookInfo[i].HookAddress == hook_addr && m_HookInfo[i].IsHooked) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 找到匹配的Hook: 原始地址=0x%p\n", m_HookInfo[i].OriginalAddress); KIRQL oldIrql = DisableWriteProtection(); memcpy(m_HookInfo[i].OriginalAddress, m_HookInfo[i].OriginalBytes, sizeof(m_HookInfo[i].OriginalBytes)); EnableWriteProtection(oldIrql); m_HookInfo[i].IsHooked = FALSE; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[PTE_HOOK] Hook已成功移除\n"); return true; } } logger("未找到匹配的Hook", true); return false; } void PteHookManager::fn_add_g_bit_info(void* align_addr, void* pde_address, void* pte_address) { if (m_GbitCount >= MAX_G_BIT_RECORDS) { logger("达到最大G记录数量限制", true); return; } PG_BIT_INFO record = &m_GbitRecords[m_GbitCount++]; record->AlignAddress = align_addr; record->PdeAddress = (decltype(G_BIT_INFO::PdeAddress))pde_address; record->PteAddress = (decltype(G_BIT_INFO::PteAddress))pte_address; record->IsLargePage = (pde_address && ((decltype(PAGE_TABLE::PdeAddress))pde_address)->flags.large_page); // 打印G信息 PrintGBitInfo(*record); } bool PteHookManager::fn_resume_global_bits(void* align_addr) { KIRQL oldIrql = DisableWriteProtection(); bool found = false; DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL, "[PTE_HOOK] 开始恢复G: 对齐地址=0x%p\n", align_addr); for (UINT32 i = 0; i < m_GbitCount; i++) { PG_BIT_INFO record = &m_GbitRecords[i]; if (align_addr && record->AlignAddress != align_addr) continue; if (record->PteAddress) { record->PteAddress->flags.global = 1; __invlpg(record->AlignAddress); DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL, " 恢复PTE G: PTE=0x%llx, 地址=0x%p\n", record->PteAddress->value, record->AlignAddress); } if (record->PdeAddress) { record->PdeAddress->flags.global = 1; if (record->IsLargePage) { __invlpg(record->AlignAddress); } DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL, " 恢复PDE G: PDE=0x%llx, 地址=0x%p, 大=%d\n", record->PdeAddress->value, record->AlignAddress, record->IsLargePage); } found = true; if (align_addr) break; } EnableWriteProtection(oldIrql); if (found) { DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL, "[PTE_HOOK] G恢复完成\n"); } else { logger("未找到匹配的G记录", true); } return found; } PteHookManager* PteHookManager::GetInstance() { if (!m_Instance) { m_Instance = static_cast<PteHookManager*>( ExAllocatePoolWithTag(NonPagedPool, sizeof(PteHookManager), &#39;tpHk&#39;)); if (m_Instance) { RtlZeroMemory(m_Instance, sizeof(PteHookManager)); DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL, "[PTE_HOOK] PTE Hook管理器实例已创建: 地址=0x%p\n", m_Instance); } else { DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_ERROR_LEVEL, "[PTE_HOOK] 创建PTE Hook管理器实例失败\n"); } } return m_Instance; } // 全局PTE Hook管理器实例 PteHookManager* g_PteHookManager = nullptr; // 辅助函数:检查是否为目标进程 BOOLEAN IsTargetProcess(CHAR* imageName) { CHAR currentName[16]; // 复制到本地缓冲区并确保 NULL 终止 RtlCopyMemory(currentName, imageName, 16); currentName[15] = &#39;\0&#39;; // 确保终止 // 修剪尾部空格 for (int i = 15; i >= 0; i--) { if (currentName[i] == &#39; &#39;) currentName[i] = &#39;\0&#39;; else if (currentName[i] != &#39;\0&#39;) break; } return (strcmp(currentName, target_process_name) == 0); } // Hook 函数 NTSTATUS MyObReferenceObjectByHandleWithTag( HANDLE Handle, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, ULONG Tag, PVOID* Object, POBJECT_HANDLE_INFORMATION HandleInformation ) { PEPROCESS currentProcess = PsGetCurrentProcess(); CHAR* imageName = PsGetProcessImageFileName(currentProcess); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[!] [HookFunction] 进入 Hook 函数! 当前进程: %s\n", imageName); __debugbreak(); // 强制中断,确认是否执行到这里 if (IsTargetProcess(imageName)) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[!] [HookFunction] 拒绝访问目标进程 PID=%d\n", HandleToULong(PsGetCurrentProcessId())); return STATUS_ACCESS_DENIED; } return g_OriginalObReferenceObjectByHandleWithTag( Handle, DesiredAccess, ObjectType, AccessMode, Tag, Object, HandleInformation ); } NTSTATUS InstallHook() { UNICODE_STRING funcName; RtlInitUnicodeString(&funcName, L"ObReferenceObjectByHandleWithTag"); g_OriginalObReferenceObjectByHandleWithTag = (fn_ObReferenceObjectByHandleWithTag)MmGetSystemRoutineAddress(&funcName); if (!g_OriginalObReferenceObjectByHandleWithTag) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[-] [InstallHook] 找不到 ObReferenceObjectByHandleWithTag\n"); return STATUS_NOT_FOUND; } __debugbreak(); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [InstallHook] 找到目标函数地址: %p\n", g_OriginalObReferenceObjectByHandleWithTag); void* targetFunc = (void*)g_OriginalObReferenceObjectByHandleWithTag; void* hookFunc = (void*)MyObReferenceObjectByHandleWithTag; if (!g_PteHookManager->fn_pte_inline_hook_bp_pg(targetProcessId, &targetFunc, hookFunc)) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[-] [InstallHook] PTE Hook 安装失败\n"); return STATUS_UNSUCCESSFUL; } g_OriginalObReferenceObjectByHandleWithTag = (fn_ObReferenceObjectByHandleWithTag)targetFunc; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [InstallHook] Hook 成功安装. 跳板地址: %p\n", targetFunc); __debugbreak(); // 强制中断,验证是否执行到这里 return STATUS_SUCCESS; } // 移除 Hook VOID RemoveHook() { if (g_OriginalObReferenceObjectByHandleWithTag && g_PteHookManager) { g_PteHookManager->fn_remove_hook(PsGetCurrentProcessId(), (void*)MyObReferenceObjectByHandleWithTag); } } // 工作线程函数 VOID InstallHookWorker(PVOID Context) { targetProcessId = (HANDLE)Context; DbgPrint("[+] Worker thread started for hook installation on PID: %d\n", HandleToULong(targetProcessId)); InstallHook(); PsTerminateSystemThread(STATUS_SUCCESS); } // 进程创建回调 VOID ProcessNotifyCallback( _In_ HANDLE ParentId, _In_ HANDLE ProcessId, _In_ BOOLEAN Create ) { UNREFERENCED_PARAMETER(ParentId); if (Create) { PEPROCESS process = NULL; if (NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &process))) { CHAR* imageName = PsGetProcessImageFileName(process); CHAR currentName[16]; RtlCopyMemory(currentName, imageName, 16); currentName[15] = &#39;\0&#39;; for (int i = 15; i >= 0; i--) { if (currentName[i] == &#39; &#39;) currentName[i] = &#39;\0&#39;; else if (currentName[i] != &#39;\0&#39;) break; } if (strcmp(currentName, target_process_name) == 0) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [ProcessNotifyCallback] 目标进程 %s 创建 (PID: %d)\n", currentName, HandleToULong(ProcessId)); HANDLE threadHandle; NTSTATUS status = PsCreateSystemThread( &threadHandle, THREAD_ALL_ACCESS, NULL, NULL, NULL, InstallHookWorker, (PVOID)ProcessId // 关键:传递目标进程ID ); if (NT_SUCCESS(status)) { ZwClose(threadHandle); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [ProcessNotifyCallback] 工作线程已创建\n"); } else { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[-] [ProcessNotifyCallback] 创建线程失败: 0x%X\n", status); } } ObDereferenceObject(process); } } } // 驱动卸载函数 VOID DriverUnload(PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER(DriverObject); DbgPrint("[+] Driver unloading...\n"); // 移除进程通知回调 PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessNotifyCallback, TRUE); // 移除Hook RemoveHook(); // 清理PTE Hook资源 if (g_PteHookManager) { DbgPrint("[PTE_HOOK] Cleaning up PTE...\n"); // 恢复所有被修改的G g_PteHookManager->fn_resume_global_bits(nullptr); // 移除所有活动的Hook HOOK_INFO* hookInfo = g_PteHookManager->GetHookInfo(); UINT32 hookCount = g_PteHookManager->GetHookCount(); for (UINT32 i = 0; i < hookCount; i++) { if (hookInfo[i].IsHooked) { g_PteHookManager->fn_remove_hook(PsGetCurrentProcessId(), hookInfo[i].HookAddress); } } // 释放跳板池内存 char* trampLinePool = g_PteHookManager->GetTrampLinePool(); if (trampLinePool) { ExFreePoolWithTag(trampLinePool, &#39;JmpP&#39;); } // 释放管理器实例 ExFreePoolWithTag(g_PteHookManager, &#39;tpHk&#39;); g_PteHookManager = nullptr; } DbgPrint("[+] Driver unloaded successfully\n"); } extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [DriverEntry] 驱动加载开始\n"); DriverObject->DriverUnload = DriverUnload; g_PteHookManager = PteHookManager::GetInstance(); if (!g_PteHookManager) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[-] [DriverEntry] 初始化 PteHookManager 失败\n"); return STATUS_INSUFFICIENT_RESOURCES; } int ab = 7176; targetProcessId = (HANDLE)ab; InstallHook(); DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [DriverEntry] 驱动加载成功\n"); return STATUS_SUCCESS; }
最新发布
07-12
<think>我们正在处理一个Windows内核驱动开发中的问题,特别是关于PTE Hook的。用户希望修改`PteHookManager::fn_isolation_pages`和`fn_pte_inline_hook_bp_pg`方法,使得隔离(isolation)功能仅对指定进程生效,而不是全局生效。 根据用户描述,当前实现的隔离功能是全局生效的,而他们希望只对特定进程进行隔离。这意味着我们需要在代码中引入进程上下文的概念,并在进行PTE操作时检查当前进程是否为目标进程。 ### 关键点分析 1. **PTE Hook的基本原理**:PTE(Page Table Entry)是表项,每个物理面对应一个PTE。通过修改PTE,我们可以改变面的属性(如只读、可等)或者重定向到另一个面。 2. **隔离功能**:隔离功能可能是通过修改PTE来创建一个面的副本(copy-on-write机制),使得对特定面的修改不会影响其他进程。这通常用于hook操作,例如在函数入口处插入断点(int3)而不影响其他进程。 3. **问题所在**:当前的实现没有区分进程,因此修改了全局PTE,导致所有进程都受到影响。 ### 解决方案思路 我们需要在隔离面(即创建副本)和设置PTE Hook时,检查当前进程是否是我们想要隔离的目标进程。如果不是,则不应该应用这些修改。 具体步骤: 1. **在隔离面时**:在`fn_isolation_pages`函数中,我们可能需要传递目标进程的PID或EPROCESS指针,以便在创建面副本时,只针对该进程的地址空间进行操作。 2. **在设置PTE Hook时**:同样,在`fn_pte_inline_hook_bp_pg`函数中,我们需要检查当前进程是否为目标进程。如果是,则应用hook;否则,跳过。 但是,需要注意的是,PTE是进程相关的,每个进程都有自己的表。因此,我们修改一个进程的PTE不会直接影响其他进程。那么为什么用户会观察到全局生效呢?可能的原因有: -面是共享面(例如,系统DLL面)。如果多个进程共享同一个物理面,那么修改PTE会导致所有共享该面的进程都受到影响。 - 代码中可能错误地修改了所有进程的PTE。 因此,正确的做法应该是: - 对于共享面,我们需要先通过复制物理面(copy-on-write)来为该进程创建一个私有副本,然后再修改这个私有副本的PTE- 在修改PTE时,确保只修改目标进程的表。 ### 代码修改建议 #### 1. 修改`fn_isolation_pages`函数 这个函数可能是用来为指定面创建私有副本(隔离)的。我们需要确保只对目标进程进行隔离。 假设原函数签名如下(根据上下文推测): ```cpp BOOLEAN fn_isolation_pages(IN PVOID VirtualAddress, IN ULONG NumberOfPages); ``` 修改建议: - 增加目标进程的参数,例如`PEPROCESS TargetProcess`。 - 在函数内部,切换到目标进程的地址空间(通过`KeStackAttachProcess`),然后进行面隔离操作。 伪代码示例: ```cpp BOOLEAN fn_isolation_pages(IN PEPROCESS TargetProcess, IN PVOID VirtualAddress, IN ULONG NumberOfPages) { KAPC_STATE apc_state; BOOLEAN result = FALSE; // 附加到目标进程 KeStackAttachProcess(TargetProcess, &apc_state); // 执行面隔离操作:这里可能需要复制面并修改PTE,使得目标进程的虚拟地址指向新的私有面 // ... 具体操作 ... // 解除附加 KeUnstackDetachProcess(&apc_state); return result; } ``` #### 2. 修改`fn_pte_inline_hook_bp_pg`函数 这个函数可能是用来在指定面设置断点(int3)的。同样,我们需要确保只对目标进程生效。 假设原函数签名: ```cpp BOOLEAN fn_pte_inline_hook_bp_pg(IN PVOID VirtualAddress, IN UCHAR OriginalByte); ``` 修改建议: - 增加目标进程参数`PEPROCESS TargetProcess`。 - 在函数内部,切换到目标进程的地址空间,然后修改PTE(如果需要隔离则先隔离)并在指定地址入int3(0xCC)指令。 伪代码示例: ```cpp BOOLEAN fn_pte_inline_hook_bp_pg(IN PEPROCESS TargetProcess, IN PVOID VirtualAddress, IN UCHAR OriginalByte) { KAPC_STATE apc_state; BOOLEAN result = FALSE; // 附加到目标进程 KeStackAttachProcess(TargetProcess, &apc_state); // 1. 先隔离该面(如果需要),确保我们修改的是目标进程的私有副本 // 调用修改后的fn_isolation_pages,只隔离一 if (fn_isolation_pages(TargetProcess, VirtualAddress, 1)) { // 2. 修改目标地址的指令为int3(0xCC) UCHAR* pTarget = (UCHAR*)VirtualAddress; *pTarget = 0xCC; result = TRUE; } // 解除附加 KeUnstackDetachProcess(&apc_state); return result; } ``` ### 注意事项 1. **内存保护**:在修改内存之前,可能需要修改面的保护属性(例如,设置为可)。这可以通过修改PTE权限来实现。 2. **同步问题**:在修改内存时,需要确保没有其他线程同时访问该面。可以使用`MmLockPagableSection`或禁用中断等方式来保证原子性,但更简单的方法是在修改前先使该面变成当前进程的私有面(通过隔离操作),然后修改。 3. **共享面处理**:在隔离面时,如果面是共享的,我们需要复制面并修改PTE指向新面。这可以通过`MmCopyMemory`或手动复制面内容并更新PTE来实现。 4. **性能考虑**:频繁地附加到目标进程可能会影响性能,因此应尽量减少这种操作。 ### 引用说明 在Windows内核中,每个进程都有自己的表,因此通过附加到目标进程的地址空间,我们可以确保后续的地址操作(如读虚拟地址)都是在该进程的上下文中进行的[^1]。同时,对于共享面的处理,需要采用时复制(Copy-on-Write机制,以避免影响其他进程[^2]。 以上修改方案应该能够解决用户的问题,即确保隔离和Hook操作只对指定进程生效。 §§相关问题§§ 1. 如何在内核驱动中安全地附加到另一个进程的地址空间? 2. Windows内核中处理共享面的最佳实践是什么? 3. 如何在内核模式下复制物理面? 4. 在修改PTE时需要注意哪些同步问题?</think>### Windows 内核驱动 PTE Hook 问题分析与修复 在 Windows 内核驱动中实现 PTE Hook 时,确保隔离功能仅针对特定进程生效的关键在于正确处理进程上下文和表隔离机制。您遇到的全局生效问题通常由以下原因导致: #### 核心问题诊断 1. **进程上下文缺失** 当前实现可能未正确绑定目标进程的地址空间,导致 PTE 修改应用到所有进程共享的全局表。需要显式附加到目标进程的地址空间上下文。 2. **共享面未隔离** 系统 DLL 等共享面默认被多个进程映射,直接修改 PTE 会影响所有进程。必须通过时复制(Copy-on-Write)创建进程私有副本。 3. **CR3 寄存器未切换** x86/x64 架构中,CR3 寄存器控制当前地址空间。未切换 CR3 会导致操作始终在系统全局地址空间执行。 #### 修复方案 需修改以下两个关键函数: **1. `fn_isolation_pages` 函数改造** ```cpp BOOLEAN PteHookManager::fn_isolation_pages( PEPROCESS TargetProcess, // 新增目标进程参数 PVOID VirtualAddress, ULONG NumberOfPages) { KAPC_STATE apcState; KeStackAttachProcess(TargetProcess, &apcState); // 附加到目标进程上下文 PHYSICAL_ADDRESS physAddr = {0}; PVOID isolatedPage = MmAllocateIndependentPages(NumberOfPages); // 分配独立物理 if(!isolatedPage) return FALSE; // 复制原始面内容 PMDL mdl = IoAllocateMdl(VirtualAddress, NumberOfPages * PAGE_SIZE, FALSE, FALSE, NULL); MmProbeAndLockPages(mdl, KernelMode, IoReadAccess); PVOID src = MmMapLockedPagesSpecifyCache(mdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority); RtlCopyMemory(isolatedPage, src, NumberOfPages * PAGE_SIZE); MmUnmapLockedPages(src, mdl); MmUnlockPages(mdl); IoFreeMdl(mdl); // 获取目标地址的 PTE PT_ENTRY* pte = MmGetPteAddress(VirtualAddress); PT_ENTRY origPte = *pte; // 修改 PTE 指向新物理 pte->PageFrameNumber = MmGetPhysicalAddress(isolatedPage).QuadPart >> PAGE_SHIFT; pte->Write = 1; // 确保可 pte->Owner = 1; // 用户模式访问 __invlpg(VirtualAddress); // 刷新 TLB KeUnstackDetachProcess(&apcState); // 解除附加 return TRUE; } ``` **2. `fn_pte_inline_hook_bp_pg` 函数改造** ```cpp BOOLEAN PteHookManager::fn_pte_inline_hook_bp_pg( PEPROCESS TargetProcess, // 新增目标进程参数 PVOID HookAddress, PVOID HandlerFunc) { // 先隔离目标面 if(!fn_isolation_pages(TargetProcess, HookAddress, 1)) return FALSE; KAPC_STATE apcState; KeStackAttachProcess(TargetProcess, &apcState); // 获取 PTE 并修改权限 PT_ENTRY* pte = MmGetPteAddress(HookAddress); pte->Write = 1; pte->DisableExecute = 0; // 允许执行 // 入 INT3 (0xCC) + 跳转指令 UCHAR hookCode[] = { 0xCC, 0x48, 0xB8, 0,0,0,0,0,0,0,0, 0xFF, 0xE0 }; // [INT3][MOV RAX, addr][JMP RAX] *(PVOID*)(hookCode + 3) = HandlerFunc; // 原子入指令 UCHAR oldBytes[sizeof(hookCode)]; RtlCopyMemory(oldBytes, HookAddress, sizeof(hookCode)); InterlockedExchange128( (LONG64*)HookAddress, *(LONG64*)&hookCode[8], // 高64 *(LONG64*)hookCode, // 低64 (LONG64*)oldBytes ); __invlpg(HookAddress); KeUnstackDetachProcess(&apcState); return TRUE; } ``` #### 关键修复点说明 1. **进程上下文绑定** 通过 `KeStackAttachProcess/KeUnstackDetachProcess` 显式绑定目标进程地址空间,确保所有内存操作在目标进程的表上下文中执行[^1]。 2. **物理隔离** `MmAllocateIndependentPages` 创建进程私有物理,避免修改共享系统面。复制原始内容保持兼容性。 3. **TLB 刷新** 使用 `__invlpg` 指令立即刷新转换后备缓冲器(TLB),确保修改立即生效。 4. **原子指令入** `InterlockedExchange128` 保证多核环境下指令替换的原子性,防止竞争条件。 #### 验证方法 调试时可检查: ```bash 0: kd> !pte 7ff45d3a1000 # 检查目标地址PTE VA 00007ff45d3a1000 PXE at FFFF89813F7F8F08 PPE at FFFF89813F7FED20 PDE at FFFF89813DE6D350 PTE at FFFF8981EF1A1D00 contains 0F0000000B460867 contains 0F0000000B460867 contains 0F0000000B460867 contains 800000000B4A1867 pfn b460 ---DA--KWEV pfn b460 ---DA--KWEV pfn b460 ---DA--KWEV pfn b4a1 ---DA--KW-V # 注意Owner变化 0: kd> !pool isolatedPage # 验证独立物理 Pool page (ffff8981ef1a1000) is... ``` ### 注意事项 1. 在 64 系统需处理 `PTI`(内核表隔离),通过 `__readcr3()` 获取目标进程 CR3 2. 对 `ntoskrnl.exe` 等核心模块,需先禁用 `PatchGuard` 3. 使用 `MmIsAddressValid` 验证用户空间地址有效性 4. 在卸载驱动时恢复原始 PTE 和指令
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值