跨进程内存读写原理学习 2
跨进程内存读写原理学习-2
在上一集的基础上, 又实现了
1. GetModuleBaseByName函数
UINT64 GetModuleBaseByName(PEPROCESS process, const wchar_t* moduleName)
{
if (process == NULL || moduleName == NULL) return 0;
// 获取 PEB 地址
UINT64 pebAddr = *(UINT64*)((CHAR*)process + OFFSET_Peb);
if (!pebAddr) {
return 0;
}
// 获取模块列表地址
UINT64 dirbase = GetDirectoryTableBase(process);
UINT64 ldrAddr = 0;
ReadVirtual(dirbase, pebAddr + OFFSET_Ldr, 8, &ldrAddr);
if(!ldrAddr) {
return 0;
}
UINT64 moduleListAddr = ldrAddr + OFFSET_InMemoryOrderModuleList;
// 遍历模块列表
UINT64 currEntry = 0;
ReadVirtual(dirbase, moduleListAddr + offsetof(LIST_ENTRY, Flink), 8, &currEntry);
if (!currEntry) {
return 0;
}
int count = 0;
while (currEntry != moduleListAddr && count < 500)
{
count++;
UINT64 moduleBaseAddr = 0;
ReadVirtual(dirbase, currEntry - OFFSET_InMemoryOrderLinks + OFFSET_DllBase, 8, &moduleBaseAddr);
if (!moduleBaseAddr) {
return 0;
}
UNICODE_STRING fullDllName;
ReadVirtual(dirbase, currEntry - OFFSET_InMemoryOrderLinks + OFFSET_BaseDllName, sizeof(UNICODE_STRING), &fullDllName);
if (fullDllName.Buffer && fullDllName.Length > 0) {
WCHAR nameBuf[256] = { 0 };
USHORT readLen = (fullDllName.Length < 510) ? fullDllName.Length : 510;
// 读取真正的模块名宽字符内容
ReadVirtual(dirbase, (UINT64)fullDllName.Buffer, readLen, nameBuf);
nameBuf[readLen / sizeof(WCHAR)] = L'\0';
// 比较宽字符模块名 (忽略大小写)
if (_wcsnicmp(nameBuf, moduleName, readLen / sizeof(WCHAR)) == 0)
{
return moduleBaseAddr;
}
}
// go next
ReadVirtual(dirbase, currEntry + offsetof(LIST_ENTRY, Flink), 8, &currEntry);
if (!currEntry) {
return 0;
}
}
return 0;
}
我们需要这个函数是因为光有跨进程的虚拟内存读取能力不够, 我们需要知道该往哪里读。
有了这个函数我们就可以获取一个进程内部任意一个模块的基址, 例如在cs2.exe进程中读取client.dll的基址, 再根据偏移找人物坐标。
从目标进程的EPROCESS中能得到Peb, 但由于Peb是一个目标进程内的地址, 而不是内核区域的全局地址所以不能直接读取, 所以需要利用实现的ReadVirtual函数读取。根据Peb再找Ldr, 再找链表去遍历所有模块比较名字。
2. 实现了ATTACH,READ,GETMODULEBASE
write也很容易写, 跟read没啥区别, 主要是read-only ESP用不到write。
通信方式基于内存共享, 还没看到有其他人这样写。看到过有用syscall hook的和正统的IOCTL的。
运行效果
Hello World!
ECHO Response: Hello from user mode!
PEPROCESS of explorer.exe: FFFFB5897F9C8080
Module Base: 00007FFB09B10000
4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00
b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 f8 00 00 00
0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68
69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f
74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20
6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00
fb 46 ea 39 bf 27 84 6a bf 27 84 6a bf 27 84 6a
b6 5f 17 6a ef 27 84 6a c6 a6 85 6b b6 27 84 6a
bf 27 85 6a c1 23 84 6a bf 27 84 6a be 27 84 6a
c6 a6 80 6b b0 27 84 6a c6 a6 87 6b bb 27 84 6a
c6 a6 84 6b be 27 84 6a c6 a6 8c 6b 32 27 84 6a
c6 a6 7b 6a be 27 84 6a c6 a6 86 6b be 27 84 6a
52 69 63 68 bf 27 84 6a 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 50 45 00 00 64 86 08 00
main.c
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <assert.h>
/* Protocal definitions BEGIN*/
#define CMD_ECHO 0x1
#define CMD_GETMODULEBASE 0x2
#define CMD_READ 0x3
#define CMD_ATTACH 0x4
#define FLAG_MESSAGE_READY 0x1
#define FLAG_OPERATION_COMPLETE 0x2
#pragma pack(push, 1)
typedef struct Message
{
UINT64 cmd;
UINT64 addr;
UINT64 size;
char inBuf[0x100];
char outBuf[0x500];
volatile UINT64 flag;
}Message;
#pragma pack(pop)
/* Protocal definitions END */
VOID Prepare_ECHO(Message* msg, const char* input, UINT64 size)
{
if(size > 0x100) {
printf("Input too large for ECHO command!\n");
return;
}
memset(msg, 0, sizeof(Message));
memcpy(msg->inBuf, input, size);
msg->cmd = CMD_ECHO;
msg->size = size;
MemoryBarrier();
msg->flag = FLAG_MESSAGE_READY;
}
VOID Prepare_GETMODULEBASE(Message* msg, const wchar_t* moduleName)
{
if(wcslen(moduleName) + 2 > 0x100) {
printf("Module name too large for GETMODULEBASE command!\n");
return;
}
memset(msg, 0, sizeof(Message));
wcscpy(msg->inBuf, moduleName);
msg->cmd = CMD_GETMODULEBASE;
MemoryBarrier();
msg->flag = FLAG_MESSAGE_READY;
}
VOID Prepare_READ(Message* msg, UINT64 addr, SIZE_T size)
{
if(size > 0x500) {
printf("Requested read size too large for READ command!\n");
return;
}
memset(msg, 0, sizeof(Message));
msg->addr = addr;
msg->size = size;
msg->cmd = CMD_READ;
MemoryBarrier();
msg->flag = FLAG_MESSAGE_READY;
}
// Attach to a process, (get dirbase)
VOID Prepare_ATTACH(Message* msg, const char* processName)
{
if(strlen(processName) + 1 > 0x100) {
printf("Process name too large for ATTACH command!\n");
return;
}
memset(msg, 0, sizeof(Message));
strcpy(msg->inBuf, processName);
msg->cmd = CMD_ATTACH;
MemoryBarrier();
msg->flag = FLAG_MESSAGE_READY;
}
char* GetResponse(Message* msg)
{
while (msg->flag != FLAG_OPERATION_COMPLETE) {
Sleep(1); // 等待操作完成
}
MemoryBarrier();
return msg->outBuf;
}
int main()
{
printf("Hello World!\n");
volatile Message* msg = (Message*)VirtualAlloc(0xDEAD0000, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (msg != 0xDEAD0000) {
printf("Alloc Failed! msg: %p, Error: %d\n", msg, GetLastError());
return -1;
}
if (!VirtualLock(msg, 0x1000)) {
printf("VirtualLock Failed! Error: %d\n", GetLastError());
return -1;
}
const char* targetProcess = "explorer.exe";
const wchar_t* targetModule = L"user32.dll";
Prepare_ECHO(msg, "Hello from user mode!", strlen("Hello from user mode!"));
char* resp = GetResponse(msg);
printf("ECHO Response: %s\n", resp);
Prepare_ATTACH(msg, targetProcess);
resp = GetResponse(msg);
printf("PEPROCESS of %s: %p\n", targetProcess, *(UINT64*)resp);
Prepare_GETMODULEBASE(msg, targetModule);
resp = GetResponse(msg);
UINT64 moduleBase = *(UINT64*)resp;
printf("Module Base: %p\n", moduleBase);
Prepare_READ(msg, moduleBase, 0x100);
resp = GetResponse(msg);
// hex dump
for (int i = 0; i < 0x100; i++) {
printf("%02x ", (unsigned char)resp[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
getchar();
return 0;
}
entry.c
#include <ntifs.h>
#include <stddef.h>
/* Protocal definitions BEGIN*/
#define CMD_ECHO 0x1
#define CMD_GETMODULEBASE 0x2
#define CMD_READ 0x3
#define CMD_ATTACH 0x4
#define FLAG_MESSAGE_READY 0x1
#define FLAG_OPERATION_COMPLETE 0x2
#pragma pack(push, 1)
typedef struct Message
{
UINT64 cmd;
UINT64 addr;
UINT64 size;
char inBuf[0x100];
char outBuf[0x500];
volatile UINT64 flag;
}Message;
#pragma pack(pop)
/* Protocal definitions END */
/// @brief 写入指定内容到物理内存中
/// @param address 被写入的物理地址
/// @param buffer 需要写入的缓冲区指针
/// @param size 需要写入的大小
/// @param BytesTransferred 写入成功后的大小
/// @return
NTSTATUS WritePhysical(IN PVOID64 address, IN PVOID64 buffer,
IN SIZE_T size)
{
PVOID map;
PHYSICAL_ADDRESS Write = { 0 };
if (!address) {
DbgPrint("Address value error. \r\n");
return STATUS_UNSUCCESSFUL;
}
Write.QuadPart = (LONG64)address;
map = MmMapIoSpaceEx(Write, size, PAGE_READWRITE);
if (!map) {
DbgPrint("Write Memory faild.\r\n");
return STATUS_UNSUCCESSFUL;
}
RtlCopyMemory(map, buffer, size);
//*BytesTransferred = size;
MmUnmapIoSpace(map, size);
return STATUS_SUCCESS;
}
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;
UINT64 bytes_transferred = 0;
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_Peb 0x2e0 // process.Peb
#define OFFSET_Ldr 0x18 //Peb.Ldr
#define OFFSET_InMemoryOrderModuleList 0x20 // Ldr->InMemoryOrderModuleList
#define OFFSET_InMemoryOrderLinks 0x10 // LDR_DATA_TABLE_ENTRY.InMemoryOrderLinks
#define OFFSET_DllBase 0x30 // LDR_DATA_TABLE_ENTRY.DllBase
#define OFFSET_BaseDllName 0x58 // LDR_DATA_TABLE_ENTRY.BaseDllName
UINT64 GetModuleBaseByName(PEPROCESS process, const wchar_t* moduleName)
{
if (process == NULL || moduleName == NULL) return 0;
// 获取 PEB 地址
UINT64 pebAddr = *(UINT64*)((CHAR*)process + OFFSET_Peb);
if (!pebAddr) {
return 0;
}
// 获取模块列表地址
UINT64 dirbase = GetDirectoryTableBase(process);
UINT64 ldrAddr = 0;
ReadVirtual(dirbase, pebAddr + OFFSET_Ldr, 8, &ldrAddr);
if(!ldrAddr) {
return 0;
}
UINT64 moduleListAddr = ldrAddr + OFFSET_InMemoryOrderModuleList;
// 遍历模块列表
UINT64 currEntry = 0;
ReadVirtual(dirbase, moduleListAddr + offsetof(LIST_ENTRY, Flink), 8, &currEntry);
if (!currEntry) {
return 0;
}
int count = 0;
while (currEntry != moduleListAddr && count < 500)
{
count++;
UINT64 moduleBaseAddr = 0;
ReadVirtual(dirbase, currEntry - OFFSET_InMemoryOrderLinks + OFFSET_DllBase, 8, &moduleBaseAddr);
if (!moduleBaseAddr) {
return 0;
}
UNICODE_STRING fullDllName;
ReadVirtual(dirbase, currEntry - OFFSET_InMemoryOrderLinks + OFFSET_BaseDllName, sizeof(UNICODE_STRING), &fullDllName);
if (fullDllName.Buffer && fullDllName.Length > 0) {
WCHAR nameBuf[256] = { 0 };
USHORT readLen = (fullDllName.Length < 510) ? fullDllName.Length : 510;
// 读取真正的模块名宽字符内容
ReadVirtual(dirbase, (UINT64)fullDllName.Buffer, readLen, nameBuf);
nameBuf[readLen / sizeof(WCHAR)] = L'\0';
// 比较宽字符模块名 (忽略大小写)
if (_wcsnicmp(nameBuf, moduleName, readLen / sizeof(WCHAR)) == 0)
{
return moduleBaseAddr;
}
}
// go next
ReadVirtual(dirbase, currEntry + offsetof(LIST_ENTRY, Flink), 8, &currEntry);
if (!currEntry) {
return 0;
}
}
return 0;
}
int g_ShouldExit = 0;
PETHREAD g_ThreadHandle = NULL;
#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)
{
g_ShouldExit = 1;
if (g_ThreadHandle) {
KeWaitForSingleObject(g_ThreadHandle, Executive, KernelMode, FALSE, NULL);
ObDereferenceObject(g_ThreadHandle);
}
return STATUS_SUCCESS;
}
VOID KSleep(UINT64 milliseconds)
{
LARGE_INTEGER interval;
interval.QuadPart = milliseconds * -10000LL; // 1 ms = 10,000 * 100 ns
KeDelayExecutionThread(KernelMode, FALSE, &interval);
}
PEPROCESS targetProcess = NULL;
VOID MainThread(PVOID Context)
{
UNREFERENCED_PARAMETER(Context);
PEPROCESS clientProcess = NULL;
while (!g_ShouldExit)
{
clientProcess = GetProcessByName("Client.exe");
if (!clientProcess) {
KSleep(500);
continue;
}
UINT64 clientDirbase = GetDirectoryTableBase(clientProcess);
const UINT64 msg_base = 0xDEAD0000;
Message msg = { 0 };
while (!g_ShouldExit)// Main Loop
{
UINT64 sharedBase = VirtToPhys(clientDirbase, msg_base);
if (!sharedBase) {
KSleep(1);
break;
}
ReadPhysical(sharedBase, sizeof(Message), &msg);
if (msg.flag != FLAG_MESSAGE_READY) {
// IF NOT READY
KSleep(1);
continue;
}
switch (msg.cmd)
{
case CMD_ECHO:
{
memcpy(msg.outBuf, msg.inBuf, msg.size);
break;
}
case CMD_ATTACH:
{
targetProcess = GetProcessByName(msg.inBuf);
UINT64 targetDirbase = GetDirectoryTableBase(targetProcess);
memcpy(msg.outBuf, &targetProcess, sizeof(UINT64));
break;
}
case CMD_GETMODULEBASE: {
UINT64 base = GetModuleBaseByName(targetProcess, (const wchar_t*)msg.inBuf);
memcpy(msg.outBuf, &base, sizeof(UINT64));
break;
}
case CMD_READ:
{
UINT64 targetDirbase = GetDirectoryTableBase(targetProcess);
ReadVirtual(targetDirbase, msg.addr, msg.size, msg.outBuf);
break;
}
default:
{
DbgPrint("Unknown command received: %llu\n", msg.cmd);
memcpy(msg.outBuf, "Unknown command", 16);
break;
}
}
KeMemoryBarrier();
msg.flag = FLAG_OPERATION_COMPLETE;
WritePhysical(sharedBase, &msg, sizeof(msg));
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING path)
{
driver->DriverUnload = DriverUnload;
HANDLE threadHandle;
NTSTATUS status = PsCreateSystemThread(
&threadHandle,
THREAD_ALL_ACCESS,
NULL,
NULL,
NULL,
MainThread,
NULL
);
if (NT_SUCCESS(status)) {
ObReferenceObjectByHandle(threadHandle, THREAD_ALL_ACCESS, NULL, KernelMode, &g_ThreadHandle, NULL);
ZwClose(threadHandle);
}
return STATUS_SUCCESS;
}