跨进程内存读写原理学习
跨进程内存读写原理学习
参考
[原创]通过修改物理内存实现跨进程内存读写-编程技术-看雪安全社区|专业技术交流与安全研究论坛
主要内容
1.根据进程名找PEPROCESS
PEPROCESS是EPROCESS结构体的指针(Pointer)
找偏移可以用的网站https://www.vergiliusproject.com/
起点是当前程序的PEPROCESS, 用的PsGetCurrentProcess();函数获取。
通过AciveProcessLinks遍历所有链在一起的EPROCESS, 判断ImageFileName(15字节截断)
有了PEPROCESS以后可以直接调用MmCopyVirtualMemory, 但是调用函数太逊了, 而且MmCopyVirtualMemory有可能被AC监控。
2.根据虚拟地址以及DirBase转换成物理地址
DirBase就是程序在执行时cr3中的值。也是第四级页表的起始地址(物理地址)
将虚拟地址的低48位进行9-9-9-9-12划分, 得到下面五个值
UINT64 pml4_idx = (addr >> 39) & 0x1FF;
UINT64 pdpt_idx = (addr >> 30) & 0x1FF;
UINT64 pd_idx = (addr >> 21) & 0x1FF;
UINT64 pt_idx = (addr >> 12) & 0x1FF;
UINT64 offset = addr & 0xFFF;
还有一些细节
1.要用掩码去掉一些标记用的位
2.判断页面是否有效
3.是否是大页
具体看UINT64 VirtToPhys(UINT64 cr3, UINT64 addr);函数的实现。
这里0x000FFFFFFFFFF000掩码和文章里的12-35bit不太一样,只要标记位不超过高12位0x000FFFFFFFFFF000应该没问题。
3.怎么读写物理地址
这里用的MmCopyMemory, 调用函数还是太逊了,直接修改页表更叼一点
系统版本:WIN11 25H2
#include <ntifs.h>
VOID ReadPhysical(UINT64 phys_addr, SIZE_T size, VOID* out)
{
if (out == NULL || size == 0) return;
PHYSICAL_ADDRESS sourceAddress;
sourceAddress.QuadPart = phys_addr;
SIZE_T bytesCopied = 0;
MM_COPY_ADDRESS copyAddress;
copyAddress.PhysicalAddress = sourceAddress;
NTSTATUS status = MmCopyMemory(
out,
copyAddress,
size,
MM_COPY_MEMORY_PHYSICAL,
&bytesCopied
);
}
BOOLEAN ReadPhysUint64(UINT64 physAddr, UINT64* outValue) {
MM_COPY_ADDRESS copyAddr;
copyAddr.PhysicalAddress.QuadPart = physAddr;
SIZE_T bytesCopied = 0;
NTSTATUS status = MmCopyMemory(outValue, copyAddr, sizeof(UINT64), MM_COPY_MEMORY_PHYSICAL, &bytesCopied);
return (NT_SUCCESS(status) && bytesCopied == sizeof(UINT64));
}
UINT64 VirtToPhys(UINT64 cr3, UINT64 addr)
{
UINT64 pml4_idx = (addr >> 39) & 0x1FF;
UINT64 pdpt_idx = (addr >> 30) & 0x1FF;
UINT64 pd_idx = (addr >> 21) & 0x1FF;
UINT64 pt_idx = (addr >> 12) & 0x1FF;
UINT64 offset = addr & 0xFFF;
UINT64 entry = 0;
UINT64 current_base = cr3 & 0x000FFFFFFFFFF000;
if (!ReadPhysUint64(current_base + pml4_idx * 8, &entry) || !(entry & 1)) return 0;
current_base = entry & 0x000FFFFFFFFFF000;
if (!ReadPhysUint64(current_base + pdpt_idx * 8, &entry) || !(entry & 1)) return 0;
if (entry & 0x80) return (entry & 0x000FFFFFFFFFF000 & ~0x3FFFFFFF) + (addr & 0x3FFFFFFF);
current_base = entry & 0x000FFFFFFFFFF000;
if (!ReadPhysUint64(current_base + pd_idx * 8, &entry) || !(entry & 1)) return 0;
if (entry & 0x80) return (entry & 0x000FFFFFFFFFF000 & ~0x1FFFFF) + (addr & 0x1FFFFF);
current_base = entry & 0x000FFFFFFFFFF000;
if (!ReadPhysUint64(current_base + pt_idx * 8, &entry) || !(entry & 1)) return 0;
return (entry & 0x000FFFFFFFFFF000) + offset;
}
VOID ReadVirtual(UINT64 cr3, UINT64 addr, SIZE_T size, VOID* out)
{
if (out == NULL || size == 0 || cr3 == 0) return;
SIZE_T totalRead = 0;
PUCHAR pOut = (PUCHAR)out;
while (totalRead < size)
{
UINT64 currentAddr = addr + totalRead;
UINT64 offset = currentAddr & 0xFFF;
// 计算当前页面还能读多少字节(不能超过 size,也不能跨页)
SIZE_T bytesInThisPage = 0x1000 - offset;
SIZE_T bytesToRead = (size - totalRead) < bytesInThisPage ? (size - totalRead) : bytesInThisPage;
// 1. 解析当前虚拟地址对应的物理地址 (调用你之前的 4 级页表解析逻辑)
UINT64 physAddr = VirtToPhys(cr3, currentAddr);
if (physAddr == 0) {
return;
}
else {
// 2. 只读取当前页面内的部分
ReadPhysical(physAddr, bytesToRead, pOut + totalRead);
}
totalRead += bytesToRead;
}
}
UINT64 GetDirectoryTableBase(PEPROCESS process)
{
if (process == NULL) return 0;
return *(UINT64*)((CHAR*)process + 0x28); // process.Pcb.DirectoryTableBase
}
#define OFFSET_AciveProcessLinks 0x1d8
#define OFFSET_ImageFileName 0x338
#define OFFSET_SectionBaseAddress 0x2b0
PEPROCESS GetProcessByName(const char* name)
{
if (name == NULL) return NULL;
// 1. 获取当前进程作为起点
PEPROCESS startProcess = PsGetCurrentProcess();
// 2. 获取链表头 (指向的是 LIST_ENTRY 成员)
PLIST_ENTRY startList = (PLIST_ENTRY)((PUCHAR)startProcess + OFFSET_AciveProcessLinks);
PLIST_ENTRY currList = startList->Flink;
// 3. 开始遍历
while (currList != startList)
{
// 4. 通过 LIST_ENTRY 地址计算出 EPROCESS 头部地址
PEPROCESS pEProcess = (PEPROCESS)((PUCHAR)currList - OFFSET_AciveProcessLinks);
// 5. 获取进程名 (EPROCESS->ImageFileName 是 15 字节截断名)
char* imageName = (char*)((PUCHAR)pEProcess + OFFSET_ImageFileName);
// 6. 比较名称 (使用 strnicmp 忽略大小写更方便)
if (_strnicmp(imageName, name, 15) == 0)
{
return pEProcess;
}
// 7. 移动到下一个链表节点
currList = currList->Flink;
}
return NULL;
}
NTSTATUS DriverUnload(PDRIVER_OBJECT driver)
{
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING path)
{
__debugbreak();
driver->DriverUnload = DriverUnload;
PEPROCESS process = GetProcessByName("explorer.exe");
if (!process) return STATUS_SUCCESS;
UINT64 dirbase = GetDirectoryTableBase(process);
char buf[8];
UINT64 imageBase = *(UINT64*)((CHAR*)process + OFFSET_SectionBaseAddress);
ReadVirtual(dirbase, imageBase, 8, buf);
DbgPrint("%llx\n", *(UINT64*)buf);
return STATUS_SUCCESS;
}