通过cr3读写进程内存
本文总结于lyshark的《Windows内核安全编程技术实践》。
技术原理
cr3保存着当前进程的最高级页目录地址(物理地址),修改cr3为目标进程的最高级页目录地址,即可读写目标进程的内存空间。
目标进程的最高级页目录地址存放于KPEOCESS结构体偏移0x28处,同时也是EPROCESS结构体0x28处。
我们通过PsLookupProcessByProcessId和PsGetProcessImageFileName函数获取获取目标进程的EPROCESS地址。之后加上0x28便得到最高级页目录地址。
获取目标进程EPROCESS
我们希望通过目标进程的可执行文件名称获取EPROCESS结构体:
cpp
static PEPROCESS sg_pEprocess = nullptr;
EXTERN_C NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId,
PEPROCESS* Process);
EXTERN_C NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process);
// 通过进程名获取EPROCESS结构
NTSTATUS GetProcessObjectByName(char* name)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
SIZE_T i;
__try
{
for (i = 0; i < 20000; i++)
{
PEPROCESS ep;
NTSTATUS st = PsLookupProcessByProcessId((HANDLE)i, &ep);
if (NT_SUCCESS(st))
{
char* pn = PsGetProcessImageFileName(ep);
// 带下划线版本的是微软的标准扩展实现,应该优先使用
if (_stricmp(pn, name) == 0)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Found %s\n", name);
sg_pEprocess = ep;
__leave;
}
}
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Not Found %s\n", name);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return status;
}
return STATUS_SUCCESS;
}
读写目标进程的内存
现在有了EPROCESS,我们开着手修改cr3寄存器的内容,也就是开始切换页表:
cpp
// 切换页表
_disable();
UINT64 cr3 = __readcr3();
__writecr3(pDTB);
_enable();
这里使用_disable()来关闭中断,CR3寄存器控制当前进程的页表基址,切换CR3会立即改变地址空间映射。如果在切换过程中被中断打断,中断处理程序可能在不正确的地址空间中执行,导致数据访问错误或系统崩溃。
示例代码
cpp
#include <ntddk.h>
#include <windef.h>
#include <intrin.h>
// 页表偏移
#define DIRECTORY_TABLE_BASE 0x028
// _disable直接生成cli指令,需要在内核模式中使用
#pragma intrinsic(_disable) // 将_disable声明为内联函数
#pragma intrinsic(_enable)
EXTERN_C NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId,
PEPROCESS* Process);
EXTERN_C NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process);
static PEPROCESS sg_pEprocess = nullptr;
// 通过进程名获取EPROCESS结构
NTSTATUS GetProcessObjectByName(char* name)
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
SIZE_T i;
__try
{
for (i = 0; i < 20000; i++)
{
PEPROCESS ep;
NTSTATUS st = PsLookupProcessByProcessId((HANDLE)i, &ep);
if (NT_SUCCESS(st))
{
char* pn = PsGetProcessImageFileName(ep);
// 带下划线版本的是微软的标准扩展实现,应该优先使用
if (_stricmp(pn, name) == 0)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Found %s\n", name);
sg_pEprocess = ep;
__leave;
}
}
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Not Found %s\n", name);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return status;
}
return STATUS_SUCCESS;
}
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
UNREFERENCED_PARAMETER(pDriverObj);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Uninstall Driver Successfully\n");
}
ULONG64 CheckAddressVal(PVOID p)
{
if (MmIsAddressValid(p) == FALSE)
{
return 0;
}
// 返回p指向的地址所存的内存
return *(PULONG64)p;
}
BOOLEAN CR3_ReadProcessMemory(PEPROCESS ep, PVOID addr, UINT32 length, PVOID buf)
{
ULONG64 pDTB = CheckAddressVal((UCHAR*)ep + DIRECTORY_TABLE_BASE);
if (pDTB == 0)
{
return false;
}
// 切换页表
_disable();
UINT64 cr3 = __readcr3();
__writecr3(pDTB);
_enable();
if (MmIsAddressValid(addr))
{
RtlCopyMemory(buf, addr, length);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"read data: %ld\n", *(PDWORD)buf);
return true;
}
_disable();
__writecr3(cr3);
_enable();
return FALSE;
}
EXTERN_C NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegPath)
{
UNREFERENCED_PARAMETER(pRegPath);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "AccessMem Hello\n");
pDriverObj->DriverUnload = DriverUnload;
NTSTATUS status = GetProcessObjectByName("x64dbg.exe");
if (NT_SUCCESS(status))
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[+] eprocess = %p\n", sg_pEprocess);
}
DWORD buf = 0;
PVOID addr = (PVOID)0x0000000009edc800;
BOOLEAN bl = CR3_ReadProcessMemory(sg_pEprocess, addr, 4, &buf);
UNREFERENCED_PARAMETER(bl);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"read process x64 address %p: %x, %d\n", addr, buf, buf);
return STATUS_SUCCESS;
}