概述
本文会利用内核驱动进行读写取第三方应用内存。
内核实现会使用内联汇编 所以对于内核数据结构每个windwos
版本不一样需要判断,本文使用19041
所写代码。
命令行:winver
即可查看你当前的版本,如下图19042.631
就是构建版本号
或者调用对应内核API
.
BOOLEAN PsGetVersion(
[out, optional] PULONG MajorVersion,
[out, optional] PULONG MinorVersion,
[out, optional] PULONG BuildNumber,
[out, optional] PUNICODE_STRING CSDVersion
);
PsGetVersion 文档
或者链接windbg
的时候查看如下图所示。 19041
便是构建号。
获取内核进程信息
在内核中IoGetCurrentProcess
可以获取到当前进程对应的结构体
如下图所示
// ntddk.h
PEPROCESS IoGetCurrentProcess();
typedef struct _EPROCESS *PEPROCESS;
IoGetCurrentProcess
_EPROCESS
结构体因构建版本不同而有所差异
可以在windbg 输入如下命令查看
dt nt!_EPROCESS
19041
构建版本的核心输出:
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+... .... : ....
+0x0e4 UniqueProcessId : Ptr32 Void
+0x0e8 ActiveProcessLinks : _LIST_ENTRY
+... .... : ....
+0x1ac ImageFileName : [15] UChar
+... .... : ....
Pcb
: 进程控制块,包含很多核心信息比如分页表分段表信息等
UniqueProcessId
: 进程id
ActiveProcessLinks
: 所有进程的双向链表循环,可以通过这个遍历所有进程
ImageFileName
: 进程名
_LIST_ENTRY
数据结构
nt!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
Flink
指向上个进程的_EPROCESS
对象的ActiveProcessLinks
地址
Blink
指向下个进程的_EPROCESS
对象的ActiveProcessLinks
地址
Pcb
是一个非常重要的数据结构这里贴出我们关心的数据结构:
nt!_KPROCESS
+... ... : ....
+0x018 DirectoryTableBase : Uint4B
+... ... : ....
DirectoryTableBase
指向的就是cr3
中保存的物理地址.注意是物理地址非虚拟地址。我们知道现代操作系统都在弱化段内存强化分页内存,因此我们需要获取其他进程的分页内存进而直接读取内存数据,这样可以无视任何检测。
我们现在创建一个函数传入一个进程pid然后返回对应的分页物理地址:
PVOID GetDirectoryTableBase(HANDLE hProcess) {
PEPROCESS Process = NULL;
__try {
//DbgBreakPoint();
Process = IoGetCurrentProcess();
//IoGetCurrentProcess内核实现其实非常简单如下所示,可以使用windbg : u IoGetCurrentProcess 查看反编译
//__asm {
// //fs 指向 _kpcr 的结构
// //fs 0x120指向_KPRCB
// //_KPRCB偏移0x4指向 _KTHREAD
// //也就是fs : [00000124h]指向一个_KTHREAD结构、、 _ETHTREAD(PsGetCurrentThread函数就是eax, dword ptr fs : [00000124h] 实现的)
// //
// mov eax, dword ptr fs : [00000124h]
// mov eax, dword ptr[eax + 80h]
// mov Process,eax
//}
PEPROCESS Head = Process;
//遍历双向链表
while (Process)
{
//校验内存是否在物理页映射了( 是否分页结构体p标志位有效)
if (MmIsAddressValid(Process)) {
//相关偏移
int pidOffset = 0x0e4;
int imageFileNameOffset = 0x1ac;
int DirectoryTableBaseOffset = 0x018;
int activeProcessLinksOffset = 0x0e8;
if (MmIsAddressValid((char*)Process + pidOffset)) {
// _EPROCESS --->>+0x0e4 UniqueProcessId : Ptr32 Void
//获取进程pid
HANDLE ProcessID = *(HANDLE*)((char*)Process + pidOffset);
if (MmIsAddressValid((char*)Process + imageFileNameOffset)) {
//_EPROCESS --->> +0x1ac ImageFileName : [15] UChar
//获取进程名称
UCHAR* ImageFileName = (UCHAR *)((char*)Process + imageFileNameOffset);
if (MmIsAddressValid((char*)Process + DirectoryTableBaseOffset)) {
//_EPROCESS --->> +0x000 Pcb : _KPROCESS
//_KPROCESS --->> +0x018 DirectoryTableBase : Uint4B
//分页地址
PVOID DirectoryTableBase = *(PVOID*)((char*)Process + DirectoryTableBaseOffset);
//如果当前进程pid和寻找pid相同直接返回分页地址
if (ProcessID == hProcess)
{
DbgPrint("pid %d ImageFileName :%s DirectoryTableBase:%p\n", ProcessID, ImageFileName, DirectoryTableBase);
return DirectoryTableBase;
}
else {
DbgPrint("[My learning] find next \r\n", __FUNCTION__);
}
if (MmIsAddressValid((char*)Process + activeProcessLinksOffset)) {
// _EPROCESS --->> +0x0e8 ActiveProcessLinks : _LIST_ENTRY
//遍历双向链表,寻找下一个
PLIST_ENTRY Entry = (PLIST_ENTRY)((char*)Process + activeProcessLinksOffset);
Process = (PEPROCESS)((char *)Entry->Flink - activeProcessLinksOffset);
//兜底双向链表全部搜寻不到
if (Process == Head)
{
DbgPrint("[My learning] Process == Head \r\n ", __FUNCTION__);
break;
}
}
}
}
}
}
}
}
__except (1) {
Process = NULL;
DbgPrint("[My learning] %s __exception\r\n", __FUNCTION__);
}
return NULL;
}
读取内存信息
PVOID pOldDirectoryTableBase;
//hProcess目标进程
//lpBaseAddress读取的进程内存地址
//lpBuffer读取到哪 必须内核地址
//nSize 读取字节
NTSTATUS MyReadProcessMemory(
HANDLE hProcess,
PVOID lpBaseAddress,
PVOID lpBuffer,
SIZE_T nSize) {
__try {
//获取目标进程的页表
PVOID pDirectoryTableBase = GetDirectoryTableBase((HANDLE)hProcess);
if (pDirectoryTableBase == NULL)
{
return STATUS_UNSUCCESSFUL;
}
__asm {
//防止当前进程被切换出去
cli
//保存环境
pushad
pushf
//保存旧的CR3
mov eax, cr3
mov pOldDirectoryTableBase, eax
//修改CR3
mov eax, pDirectoryTableBase
mov cr3, eax
}
if (MmIsAddressValid(lpBaseAddress))
{
ProbeForRead(lpBaseAddress, nSize, 4);
RtlCopyMemory(lpBuffer, lpBaseAddress, nSize);
DbgPrint("[My learning] MmIsAddressValid is Ok\r\n", __FUNCTION__);
}
__asm {
//恢复环境
mov eax, pOldDirectoryTableBase
mov pOldDirectoryTableBase, eax
popf
popad
//恢复
sti
}
DbgPrint("[My learning] STATUS_SUCCESS\r\n", __FUNCTION__);
return STATUS_SUCCESS;
}
__except (1) {
DbgPrint("[My learning] %s __exception\r\n", __FUNCTION__);
__asm {
//恢复环境
mov eax, pOldDirectoryTableBase
mov pOldDirectoryTableBase, eax
popf
popad
//恢复
sti
}
}
DbgPrint("[My learning] STATUS_UNSUCCESSFUL\r\n", __FUNCTION__);
return STATUS_UNSUCCESSFUL;
}
核心思想:把目标进程的页目录置换到自己的内核进程中,那么你读取内存信息操作系统通过MMU等来自己算出结果。
__asm {
//略....
//保存旧的CR3
mov eax, cr3
mov pOldDirectoryTableBase, eax
//修改CR3
mov eax, pDirectoryTableBase
mov cr3, eax
}
当我们读出结果的时候需要把cr3
换回来
__asm {
//恢复环境
mov eax, pOldDirectoryTableBase
mov pOldDirectoryTableBase, eax
}
cli
指令的作用是用于不响应中断,那么会导致操作变为原子执行,直到调用sti
才能恢复。这里是为了防止内核进程,当前运行此代码的线程被切走运行当前进程的其他代码,但是此时cr3已经变成了目标进程的页目录,切换线程不会更新cr3
会导致可能读写到目标进程数据的风险。
另外注意cli
会导致__try{}__except
不能捕获异常(无法响应中断的后果,所以上面的代码存在逻辑问题)
tip:
CR3
存放当前进程的页目录物理地址CR3
切换线程不会改变- windbg 的
!process 0 0
命令可以查看所有进程信息
写内存信息
我们知道页表中有读写属性,如果我们写入的是具有只读的内存那么回抛出错误,我们可以通过简单修改CR0.WP为0来关闭这个保护。
cr0寄存器如下:
NTSTATUS MyWriteProcessMemory(
HANDLE hProcess,
PVOID lpBaseAddress,
PVOID lpBuffer,
SIZE_T nSize) {
NTSTATUS Status = STATUS_SUCCESS;
PVOID pDirectoryTableBase = GetDirectoryTableBase((HANDLE)hProcess);
if (pDirectoryTableBase == NULL)
{
DbgPrint("[My learning] pDirectoryTableBase ==null %s\r\n", __FUNCTION__);
return STATUS_UNSUCCESSFUL;
}
KdBreakPoint();
__asm {
//防止当前进程被切换出去
cli
//保存环境
//pushad
//pushf
//保存旧的CR3
mov eax, cr3
mov pOldDirectoryTableBase, eax
//修改CR3
mov eax, pDirectoryTableBase
mov cr3, eax
//关闭写保护
mov eax,cr0
and eax,not 10000h
mov cr0,eax
}
if (MmIsAddressValid(lpBaseAddress))
{
RtlCopyMemory(lpBaseAddress, lpBuffer, nSize);
DbgPrint("[My learning] MmIsAddressValid is %s \r\n", __FUNCTION__);
}
else {
DbgPrint("[My learning] MmIsAddressinValid is %s \r\n", __FUNCTION__);
}
__asm {
//恢复环境
mov eax, pOldDirectoryTableBase
mov cr3, eax
//popfd
//popad
//关闭写保护
mov eax, cr0
or eax, 10000h
mov cr0, eax
//恢复
sti
}
return Status;
}
源码
https://github.com/fanmingyi/winkernel_read_mm