最近回顾学习,复习到了这个知识点。在 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
[原创]Windows不附加进程从而读写物理地址
266 浏览
8 回复
tql
大佬带带
现在帖子都用ai写了啊?
萌克力
现在帖子都用ai写了啊?
不是的,只是让ai帮忙润色一下
现在帖子都用ai写了啊?
不是的,只是让ai帮忙润色一下
厉害???? 我也算是复习复习十年前学的内容了
mmmapiospace在1909以后映射页表被pass了 我的bro
wx_垃圾债券之王
mmmapiospace在1909以后映射页表被pass了 我的bro
原来如此,那我得再看看了,感谢反馈
mmmapiospace在1909以后映射页表被pass了 我的bro
原来如此,那我得再看看了,感谢反馈
BOSC叛忍
原来如此,那我得再看看了,感谢反馈
直接mmcopymemory或者pte映射+刷tlb可以
原来如此,那我得再看看了,感谢反馈
直接mmcopymemory或者pte映射+刷tlb可以