论坛首页 安全编程开发区 阅读主题

[原创]Windows不附加进程从而读写物理地址

266 浏览 8 回复
#1 楼主 2026-06-01 21:08:56
最近回顾学习,复习到了这个知识点。在 Windows 内核安全与反作弊对抗(驱动开发)领域,内存读写是最基础也是最核心的技术。传统的读写方式通常依赖于 KeStackAttachProcess 将当前线程附加到目标进程的上下文中,然后直接使用 memcpy 或 RtlCopyMemory 进行数据拷贝。然而,在高级的安全对抗中,“附加进程”这一操作会留下明显的痕迹(如修改跨进程上下文、触发某些回调等),极易被安全软件或反作弊系统(EAC/BE等)拦截或记录。那么,有没有一种方法可以完全不改变进程上下文,静悄悄地读取目标进程的内存呢?答案就是:手动解析多级页表,直接读取物理内存(Physical Memory)。本文将带你从零开始,详细解析并实现两套完整的“无附加物理内存读写”方案。一、核心思路:从虚拟地址转换为物理地址在 64 位 Windows 系统中,每个进程都有自己独立的虚拟地址空间。我们平时在 Cheat Engine 或代码中看到的地址(如 0x7ff600000000),都是虚拟地址(Virtual Address, VA)。虚拟地址本身并不存储数据,它必须通过 CPU 的页表机制翻译成内存条上的物理地址(Physical Address, PA)。这个翻译过程由 CPU 的 CR3 寄存器(保存了当前进程页表的物理根地址)主导,经过四级页表层层深入:PML4 (Page Map Level 4) - 第 1 级PDPT (Page Directory Pointer Table) - 第 2 级PDE (Page Directory Entry) - 第 3 级PTE (Page Table Entry) - 第 4 级我们的整体思路就是:在内核驱动中,获取目标进程的 CR3 寄存器值,然后模仿 CPU 的行为,手动一层一层地拨开这四级页表,算出最终的物理地址,最后直接对这块物理内存进行读写!二、 源码解析:四大核心模块为了彻底掌握这项技术,我这里将代码分为四个关键部分进行拆解。
模块 1:如何找到目标进程?要操作内存,首先要获取目标进程的 ID。虽然系统提供了 ZwQuerySystemInformation 等 API,但为了底层和隐蔽,我们通过遍历内核的 EPROCESS 链表来实现。// 辅助函数:通过进程名获取 PID
int GetProcessIdByImageFileName(char* TargetImageName) {
    // 1. 获取当前系统进程的 EPROCESS 对象
    PEPROCESS ProcessObject = PsGetCurrentProcess();

    // 2. 根据硬编码偏移,找到 ActiveProcessLinks(活动进程双向链表)
    // 注意:0x188 是特定 Windows 版本的偏移,不同系统版本可能不同
    PLIST_ENTRY ListStart = (PLIST_ENTRY)((PUCHAR)ProcessObject + 0x188); 
    PLIST_ENTRY ListCurrent = ListStart;

        // 3. 通过链表节点地址,减去偏移,反推当前进程 EPROCESS 的首地址
        PEPROCESS Entry = (PEPROCESS)((PUCHAR)ListCurrent - 0x188);

        // 4. 获取进程名 (偏移 0x2e0) 并比较
        if (_stricmp((char*)((PUCHAR)Entry + 0x2e0), TargetImageName) == 0) { 
            // 5. 如果名字匹配,返回对应的 PID (偏移 0x180)
            return (int)*(PULONG64)((PUCHAR)Entry + 0x180); 

        // 移动到下一个链表节点
        ListCurrent = ListCurrent->Flink;
    } while (ListCurrent != ListStart); // 遍历一圈回到起点则结束

    return 0; // 没找到
}Windows 内核将所有正在运行的进程用一个双向链表(ActiveProcessLinks)串了起来。我们只需要拿到其中任意一个进程,顺藤摸瓜遍历一圈,对比 ImageFileName,就能找到我们的目标(如 calc.exe)。模块 2:方案一:基于 MmMapIoSpace 的优雅解析法这是官方推荐且最稳定的物理内存映射方法。它的核心思想是:拿到一段物理地址后,调用 MmMapIoSpace 将它临时映射到内核的虚拟地址空间供我们读取,读完立刻用 MmUnmapIoSpace 释放。ULONG64 GetPhysicalAddressByMmMap(int ProcessId, ULONG64 VirtualAddress) {
    PEPROCESS ProcessObject = NULL;

    // 1. 根据进程ID获取进程对象
    NTSTATUS Status = PsLookupProcessByProcessId((HANDLE)ProcessId, &ProcessObject);
    if (!NT_SUCCESS(Status)) {
        return 0;

    // 2. 获取该进程的页目录基地址 (Directory Table Base),也就是 CR3 寄存器的值
    // 偏移量 0x28 对应的是 64 位系统下 EPROCESS 结构中的 DirectoryTableBase
    ULONG64 DirectoryTableBase = *(PULONG64)((PUCHAR)ProcessObject + 0x28);

    // 使用完 ProcessObject 后,减少引用计数
    ObDereferenceObject(ProcessObject);

    // 3. 拆解虚拟地址。将 64 位虚拟地址拆解为 4 级索引和 1 个页内偏移
    // 每级索引占据虚拟地址中的 9 位 (掩码为 0x1FF)
    unsigned short Pml4Index = (VirtualAddress >> 39) & 0x1FF; // 第1级:Page Map Level 4
    unsigned short DirectoryPointerIndex = (VirtualAddress >> 30) & 0x1FF; // 第2级:Page Directory Pointer
    unsigned short DirectoryI

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-290776.htm
#2 2026-06-01 21:08:56
tql
#3 2026-06-01 21:08:56
大佬带带
#4 2026-06-01 21:08:56
现在帖子都用ai写了啊?
#5 2026-06-01 21:08:56
萌克力

现在帖子都用ai写了啊?
不是的,只是让ai帮忙润色一下
#6 2026-06-01 21:08:56
厉害????  我也算是复习复习十年前学的内容了
#7 2026-06-01 21:08:56
mmmapiospace在1909以后映射页表被pass了 我的bro
#8 2026-06-01 21:08:56
wx_垃圾债券之王

mmmapiospace在1909以后映射页表被pass了 我的bro
原来如此,那我得再看看了,感谢反馈
#9 2026-06-01 21:08:56
BOSC叛忍

原来如此,那我得再看看了,感谢反馈
直接mmcopymemory或者pte映射+刷tlb可以

请登录后参与讨论

立即登录 注册账号