MmGetPhysicalAddress分析

MmGetPhysicalAddress是一个Windows内核函数,用于获取虚拟地址对应的物理地址。它首先判断输入的BaseAddress是否已经是物理地址,如果是则直接转换;否则,通过MiGetPteAddress获取PTE,并检查其有效性。如果PTE无效,则返回错误信息;有效时,从PTE中获取物理页框号并完成物理地址的计算。函数最后调整字节偏移。此函数对于理解Windows内存管理至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码如下:

PHYSICAL_ADDRESS
MmGetPhysicalAddress (
     IN PVOID BaseAddress
     )
{
    PMMPTE PointerPte;
    PHYSICAL_ADDRESS PhysicalAddress;

 //判断是否在[0x80000000,0xA0000000)中,如果是,这段内存被映射到物理内存中[0,20000000),这段内存采用4MB大小的分页。

    if (MI_IS_PHYSICAL_ADDRESS(BaseAddress)) {
        PhysicalAddress.QuadPart = MI_CONVERT_PHYSICAL_TO_PFN (BaseAddress);
    } else {

        PointerPte = MiGetPteAddress(BaseAddress);

  //如果PTE的内容为NULL,则不对应任何物理内存。

        if (PointerPte->u.Hard.Valid == 0) {
            KdPrint(("MM:MmGetPhysicalAddressFailed base address wa

<think>根据提供的代码片段,问题在于`fn_pa_to_va`和`fn_va_to_pa`这两个函数的使用场景和实现方式。在之前的上下文中,我们讨论的是进程特定的Hook,而这里提供了物理地址和虚拟地址转换的函数。但是,这些函数在内核中转换的是当前系统上下文的地址,而不是目标进程的上下文。###问题分析在实现进程特定的Hook时,关键步骤是操作目标进程的页表(CR3)。而提供的两个函数:1.`fn_pa_to_va`:将物理地址转换为当前系统上下文(内核的虚拟地址空间)的虚拟地址。2.`fn_va_to_pa`:将当前系统上下文的虚拟地址转换为物理地址。当你在目标进程的上下文中修改页表时,你需要使用目标进程的CR3来访问其虚拟地址空间。但是,这两个函数没有考虑目标进程的页表,而是使用当前内核的页表(即当前CPU的CR3)进行转换。这会导致以下问题:-如果你使用`fn_va_to_pa`来转换一个属于目标进程的用户态虚拟地址(或者目标进程的私有内核映射),由于当前CR3是内核的CR3(系统进程的CR3),转换会失败或者得到错误的物理地址。-同样,`fn_pa_to_va`返回的虚拟地址是当前内核地址空间中的映射,而不是目标进程的地址空间。因此,在进程特定Hook中,直接使用这两个函数来操作目标进程的地址空间是不正确的。###正确的地址转换方法要操作目标进程的地址空间,必须切换到目标进程的页表(CR3),然后在该上下文中进行地址转换。具体步骤如下:1.保存当前CR3。2.切换到目标进程的CR3。3.在目标进程的上下文中,使用目标进程的页表进行虚拟地址到物理地址的转换(或者进行其他内存操作)。4.恢复原来的CR3。注意:在内核中,我们通常不能直接访问用户态虚拟地址(除非在目标进程上下文),但这里我们主要关注的是修改目标进程的内核映射部分(因为我们要Hook的是内核函数在目标进程中的映射)。###修正后的关键步骤####步骤1:获取目标进程的CR3```cpp//假设TargetProcess是目标进程的PEPROCESSULONG_PTRtargetCr3=(ULONG_PTR)PsGetProcessPcb(TargetProcess)->DirectoryTableBase;```####步骤2:在目标进程的上下文中进行地址转换我们需要一个函数,在指定的CR3(页表)下将虚拟地址转换为物理地址:```cppUINT64VaToPaWithCr3(ULONG_PTRcr3,void*va){//保存当前CR3ULONG_PTRoriginalCr3=__readcr3();//切换到目标CR3__writecr3(cr3);//刷新TLB,确保新页表生效__invlpg(va);//获取物理地址PHYSICAL_ADDRESSphysAddr=MmGetPhysicalAddress(va);UINT64pa=physAddr.QuadPart;//恢复原始CR3__writecr3(originalCr3);__invlpg(va);//刷新TLB,确保切换回来returnpa;}```####步骤3:在目标进程的上下文中修改内存同样,在修改目标进程的页表或内存时,也需要切换到目标进程的CR3:```cppvoidModifyMemoryWithCr3(ULONG_PTRcr3,void*va,void*buffer,size_tsize){ULONG_PTRoriginalCr3=__readcr3();__writecr3(cr3);__invlpg(va);//现在我们可以安全地写入目标进程的虚拟地址RtlCopyMemory(va,buffer,size);__writecr3(originalCr3);__invlpg(va);}```###为什么之前的实现会导致全局修改?在之前的实现中,你没有切换CR3,而是直接在内核的页表上下文中修改了内核函数的PTE。由于内核函数在内核地址空间中是全局共享的(所有进程的内核部分都映射到相同的物理地址),所以修改了该地址的PTE会影响所有进程。###如何实现进程特定的Hook?进程特定Hook的核心思想是为目标进程创建一份目标函数所在物理页的副本,然后修改副本(安装跳转指令),最后重映射目标进程中该函数虚拟地址的PTE,使其指向新的副本物理页。而其他进程仍然映射到原始的物理页。具体步骤:1.获取目标函数的虚拟地址(在内核中,这个地址是全局的,比如`nt!ObReferenceObjectByHandleWithTag`)。2.获取目标进程的CR3。3.在目标进程的页表上下文中,获取该虚拟地址对应的物理地址(使用上述`VaToPaWithCr3`)。4.分配新的物理页,将原始物理页的内容复制到新物理页。5.在新物理页上安装Hook(写入跳转指令)。6.修改目标进程的页表,将目标函数的虚拟地址映射到新的物理页(修改PTE)。7.刷新目标进程的TLB。这样,只有目标进程会执行新物理页上的代码(包含Hook),而其他进程仍然执行原始的物理页。###代码示例以下是进程特定Hook的关键函数(简化版):```cppvoidInstallProcessSpecificHook(PEPROCESSTargetProcess,PVOIDFunctionAddress,PVOIDHookFunction){//获取目标进程的CR3ULONG_PTRtargetCr3=(ULONG_PTR)PsGetProcessPcb(TargetProcess)->DirectoryTableBase;//在目标进程的上下文中获取目标函数地址的物理地址UINT64originalPa=VaToPaWithCr3(targetCr3,FunctionAddress);//分配一个新的物理页(连续内存)PVOIDnewPage=MmAllocateContiguousMemory(PAGE_SIZE);UINT64newPa=MmGetPhysicalAddress(newPage).QuadPart;//复制原始页内容到新页RtlCopyMemory(newPage,(PVOID)originalPa,PAGE_SIZE);//在新页上安装跳转指令(Hook)//假设FunctionAddress在页内的偏移为offsetULONGoffset=(ULONG)((ULONG_PTR)FunctionAddress&(PAGE_SIZE-1));PUCHARhookLocation=(PUCHAR)newPage+offset;InstallJump(hookLocation,HookFunction);//这里实现写入跳转指令//修改目标进程的页表,将FunctionAddress映射到新物理页//首先找到FunctionAddress在目标进程页表中的PTEPTE_ENTRY*pte=FindPteForVaWithCr3(targetCr3,FunctionAddress);if(pte){//备份原始PTEPTE_ENTRYoriginalPte=*pte;//修改PTE指向新的物理页pte->page_frame_number=newPa>>12;pte->present=1;//确保其他标志位正确//刷新TLB__invlpg(FunctionAddress);}//保存原始PTE和原始物理页,以便卸载时恢复//...}```###注意事项1.**地址转换**:`MmGetPhysicalAddress`函数在任意上下文中都可以使用,因为它只依赖于虚拟地址到物理地址的转换,而不依赖于当前进程的用户空间。但是,当我们要转换的虚拟地址是用户空间地址时,必须在目标进程上下文中(切换CR3)进行转换。对于内核空间地址,通常不需要切换CR3,因为内核空间是共享的。但是,在进程特定Hook中,我们操作的是目标进程的内核映射部分,所以切换CR3后操作的是目标进程的页表,这样修改PTE只会影响目标进程。2.**大页处理**:如果目标函数位于大页(2MB或1GB)中,需要先拆分大页为4KB页,然后才能修改单个页的映射。这可能是之前日志中看到拆分大页的原因。3.**并发和同步**:在修改页表时,需要确保目标进程的所有线程都处于安全状态,避免在修改过程中访问该页。通常需要挂起目标进程的所有线程。###结论问题在于地址转换函数没有考虑目标进程的页表上下文,导致在内核全局上下文中修改了全局内核函数。通过切换到目标进程的CR3,并在目标进程的页表上下文中进行地址转换和内存修改,可以实现进程特定的Hook。§§相关问题§§:1.如何安全地挂起和恢复目标进程的所有线程?2.在Windows内核中,如何在不切换CR3的情况下访问另一个进程的虚拟地址空间?3.如何正确处理大页拆分后的页表结构?4.在进程特定Hook中,如何管理新分配物理页的生命周期(防止内存泄漏)?5.如何实现跨平台(x64和ARM64)的进程特定Hook?6.Windows内部函数`MmGetPhysicalAddress`的工作原理是什么?它是否依赖于当前CR3?7.在Hook卸载时,如何确保没有线程正在执行被修改的代码页?8.如何检测和避免目标进程的页表修改与其他内核组件(如PatchGuard)冲突?</think>### 问题分析:全局修改的原因 根据您提供的 `fn_pa_to_va` 和 `fn_va_to_pa` 函数实现,**问题出在地址转换逻辑没有考虑进程特定的页表上下文**。以下是详细分析: #### 核心问题:地址转换未绑定到目标进程 ```cpp PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(va); return physAddr.QuadPart; ``` 1. **`MmGetPhysicalAddress` 函数特性**: - 此函数基于当前 CPU 的 CR3 寄存器进行地址转换 - 返回的是**系统全局物理地址**,不区分进程上下文 - 在驱动中调用时,总是在**系统进程上下文**中执行 2. **`MmGetVirtualForPhysical` 同理**: - 返回的是内核全局虚拟地址映射 - 与任何特定进程无关 #### 错误流程说明 ```mermaid graph TD A[获取目标函数地址] --> B[使用 fn_va_to_pa 转换] B --> C[得到全局物理地址] C --> D[修改PTE] D --> E[全局生效] ``` ### 正确解决方案:进程特定的地址转换 要实现进程特定的 Hook,必须使用目标进程的 CR3 进行地址转换: #### 修改后的地址转换函数 ```cpp // 获取当前CR3 UINT64 GetCurrentCr3() { return __readcr3(); } // 使用指定CR3获取物理地址 UINT64 VaToPaWithCr3(UINT64 cr3, void* va) { // 保存当前CR3 UINT64 originalCr3 = GetCurrentCr3(); // 切换到目标CR3 __writecr3(cr3); __invlpg(va); // 刷新TLB // 获取物理地址 PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(va); UINT64 pa = physAddr.QuadPart; // 恢复原始CR3 __writecr3(originalCr3); __invlpg(va); return pa; } // 使用指定CR3获取虚拟地址 void* PaToVaWithCr3(UINT64 cr3, UINT64 pa) { // 保存当前CR3 UINT64 originalCr3 = GetCurrentCr3(); // 切换到目标CR3 __writecr3(cr3); // 获取虚拟地址 PHYSICAL_ADDRESS physAddr; physAddr.QuadPart = pa; void* va = MmGetVirtualForPhysical(physAddr); // 恢复原始CR3 __writecr3(originalCr3); return va; } ``` #### 完整的进程特定 Hook 流程 ```cpp void InstallProcessSpecificHook(PEPROCESS targetProcess, PVOID targetFunction, PVOID hookFunction) { // 1. 获取目标进程CR3 UINT64 targetCr3 = GetProcessCr3(targetProcess); // 2. 获取目标函数在目标进程中的物理地址 UINT64 targetPa = VaToPaWithCr3(targetCr3, targetFunction); // 3. 创建物理页副本 PHYSICAL_ADDRESS physAddr; physAddr.QuadPart = targetPa; PVOID originalPage = MmGetVirtualForPhysical(physAddr); PVOID newPage = ExAllocatePool(NonPagedPool, PAGE_SIZE); RtlCopyMemory(newPage, originalPage, PAGE_SIZE); // 4. 在副本上安装Hook InstallJump((PUCHAR)newPage + OFFSET, hookFunction); // 5. 获取新页面的物理地址 PHYSICAL_ADDRESS newPhysAddr = MmGetPhysicalAddress(newPage); // 6. 切换到目标进程CR3修改PTE UINT64 originalCr3 = GetCurrentCr3(); __writecr3(targetCr3); // 查找目标地址的PTE PMMPTE pte = FindPteForVa(targetFunction); if (pte) { // 修改PTE指向新物理页 pte->u.Hard.PageFrameNumber = newPhysAddr.QuadPart >> 12; pte->u.Hard.Valid = 1; } // 7. 恢复原始CR3 __writecr3(originalCr3); __invlpg(targetFunction); // 8. 保存原始页面引用 g_OriginalPages[targetProcess] = originalPage; } ``` ### 关键修改点说明 1. **CR3 切换**: - 所有地址转换操作必须在目标进程的 CR3 上下文中执行 - 使用 `__writecr3()` 和 `__invlpg` 确保上下文切换正确 2. **物理地址转换**: - 在目标进程 CR3 下获取的物理地址才是该进程的私有映射 - 全局 `MmGetPhysicalAddress` 只能获取系统视图的物理地址 3. **页面复制**: - 创建原始页面的副本 - 在副本上安装 Hook(跳转指令) - 修改目标进程的 PTE 指向副本 ### 验证方法 在 WinDbg 中检查不同进程的视图: ```windbg # 在目标进程中 .process /p /r <target_pid> u nt!ObReferenceObjectByHandleWithTag # 应显示跳转指令到你的Hook # 在系统进程中 .process /p /r 4 # System PID u nt!ObReferenceObjectByHandleWithTag # 应显示原始函数代码 ``` ### 补充工具函数 获取进程 CR3 的实现: ```cpp UINT64 GetProcessCr3(PEPROCESS Process) { #if defined(_AMD64_) return *(UINT64*)((UCHAR*)Process + 0x28); // KPROCESS.DirectoryTableBase #elif defined(_X86_) return *(UINT64*)((UCHAR*)Process + 0x18); // KPROCESS.DirectoryTableBase #endif } ``` 查找 PTE 的函数: ```cpp PMMPTE FindPteForVa(PVOID Va) { ULONG_PTR vaddr = (ULONG_PTR)Va; ULONG_PTR pml4 = (vaddr >> 39) & 0x1FF; ULONG_PTR pdp = (vaddr >> 30) & 0x1FF; ULONG_PTR pd = (vaddr >> 21) & 0x1FF; ULONG_PTR pt = (vaddr >> 12) & 0x1FF; PMMPTE pml4e = (PMMPTE)__readcr3(); if (!pml4e[pml4].u.Hard.Valid) return nullptr; PMMPTE pdpe = (PMMPTE)(pml4e[pml4].u.Hard.PageFrameNumber << 12); if (!pdpe[pdp].u.Hard.Valid) return nullptr; PMMPTE pde = (PMMPTE)(pdpe[pdp].u.Hard.PageFrameNumber << 12); if (!pde[pd].u.Hard.Valid) return nullptr; PMMPTE pte = (PMMPTE)(pde[pd].u.Hard.PageFrameNumber << 12); return &pte[pt]; } ``` ### 总结 问题根源在于: 1. 使用全局地址转换函数 `MmGetPhysicalAddress`/`MmGetVirtualForPhysical` 2. 未切换到目标进程的 CR3 上下文 3. 直接修改了全局内核映射 通过切换到目标进程 CR3 上下文进行地址转换和 PTE 修改,可以确保 Hook 只影响特定进程,而不会全局生效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值