驱动开发:内核物理内存寻址读写

您所在的位置:网站首页 内存寻址过程 驱动开发:内核物理内存寻址读写

驱动开发:内核物理内存寻址读写

2023-07-08 13:16| 来源: 网络整理| 查看: 265

在某些时候我们需要读写的进程可能存在虚拟内存保护机制,在该机制下用户的CR3以及MDL读写将直接失效,从而导致无法读取到正确的数据,本章我们将继续研究如何实现物理级别的寻址读写。

首先,驱动中的物理页读写是指在驱动中直接读写物理内存页(而不是虚拟内存页)。这种方式的优点是它能够更快地访问内存,因为它避免了虚拟内存管理的开销,通过直接读写物理内存,驱动程序可以绕过虚拟内存的保护机制,获得对系统中内存的更高级别的访问权限。

想要实现物理页读写,第一步则是需要找到UserDirectoryTableBase的实际偏移地址,你一定会问这是个什么?别着急,听我来慢慢解释;

在操作系统中,每个进程都有一个KPROCESS结构体,它是进程的内部表示。该结构体中包含了一些重要的信息,包括UserDirectoryTableBase字段,它指向进程的页表目录表(Page Directory Table),也称为DirectoryTable页目录表。

Page Directory Table是一种数据结构,它在虚拟内存管理中起着重要的作用。它被用来存储将虚拟地址映射到物理地址的映射关系,其内部包含了一些指向页表的指针,每个页表中又包含了一些指向物理页面的指针。这些指针一起构成了一个树形结构,它被称为页表树(Page Table Tree)。

kd> dt _KPROCESS ntdll!_KPROCESS +0x278 UserTime : Uint4B +0x27c ReadyTime : Uint4B +0x280 UserDirectoryTableBase : Uint8B +0x288 AddressPolicy : UChar +0x289 Spare2 : [71] UChar #define GetDirectoryTableOffset 0x280

UserDirectoryTableBase字段包含了进程的页表树的根节点的物理地址,通过它可以找到进程的页表树,从而实现虚拟内存的管理。在WinDbg中,通过输入dt _KPROCESS可以查看进程的KPROCESS结构体的定义,从而找到UserDirectoryTableBase字段的偏移量,这样可以获取该字段在内存中的地址,进而获取DirectoryTable的地址。不同操作系统的KPROCESS结构体定义可能会有所不同,因此它们的UserDirectoryTableBase字段的偏移量也会不同。

通过上述原理解释,我们可知要实现物理页读写需要实现一个转换函数,因为在应用层传入的还是一个虚拟地址,通过TransformationCR3函数即可实现将虚拟地址转换到物理地址,函数内部实现了从虚拟地址到物理地址的转换过程,并返回物理地址。

// 从用户层虚拟地址切换到物理页地址的函数 // 将 CR3 寄存器的末尾4个比特清零,这些比特是用于对齐的,不需要考虑 /* 参数 cr3:物理地址。 参数 VirtualAddress:虚拟地址。 */ ULONG64 TransformationCR3(ULONG64 cr3, ULONG64 VirtualAddress) { cr3 &= ~0xf; // 获取页面偏移量 ULONG64 PAGE_OFFSET = VirtualAddress & ~(~0ul > 39) & (0x1ffll))), &a, sizeof(a), &BytesTransferred); // 如果 P(存在位)为0,表示该页表项没有映射物理内存,返回0 if (~a & 1) { return 0; } // 读取虚拟地址所在的二级页表项 ReadPhysicalAddress((PVOID)((a & ((~0xfull > 30) & (0x1ffll))), &b, sizeof(b), &BytesTransferred); // 如果 P 为0,表示该页表项没有映射物理内存,返回0 if (~b & 1) { return 0; } // 如果 PS(页面大小)为1,表示该页表项映射的是1GB的物理内存,直接计算出物理地址并返回 if (b & 0x80) { return (b & (~0ull > 12)) + (VirtualAddress & ~(~0ull 21) & (0x1ffll))), &c, sizeof(c), &BytesTransferred); // 如果 P 为0,表示该页表项没有映射物理内存,返回0 if (~c & 1) { return 0; } // 如果 PS 为1,表示该页表项映射的是2MB的物理内存,直接计算出物理地址并返回 if (c & 0x80) { return (c & ((~0xfull 30) & (0x1ffll))), &b, sizeof(b), &BytesTransferred); // 如果 P 为0,表示该页表项没有映射物理内存,返回0 if (~b & 1) { return 0; } // 如果 PS(页面大小)为1,表示该页表项映射的是1GB的物理内存,直接计算出物理地址并返回 if (b & 0x80) { return (b & (~0ull > 12)) + (VirtualAddress & ~(~0ull 21) & (0x1ffll))), &c, sizeof(c), &BytesTransferred); // 如果 P 为0,表示该页表项没有映射物理内存,返回0 if (~c & 1) { return 0; } // 如果 PS 为1,表示该页表项映射的是2MB的物理内存,直接计算出物理地址并返回 if (c & 0x80) { return (c & ((~0xfull DriverUnload = DriverUnload; return STATUS_SUCCESS; }

编译并运行上述代码片段,则会读取进程ID为4116的0x401000处的地址数据,并以字节的方式输出前四位,输出效果图如下所示;

驱动开发:内核物理内存寻址读写_微软技术

写出数据与读取数据基本一致,只是调用方法从ReadPhysicalAddress变为了WritePhysicalAddress其他的照旧,但需要注意的是读者再使用写出时需要自行填充一段堆用于存储需要写出的字节集。

// 驱动卸载例程 extern "C" VOID DriverUnload(PDRIVER_OBJECT pDriver) { UNREFERENCED_PARAMETER(pDriver); DbgPrint("Uninstall Driver \n"); } // 驱动入口地址 extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING path) { DbgPrint("Hello LyShark \n"); // 物理页写 PEPROCESS pEProcess = NULL; NTSTATUS Status = PsLookupProcessByProcessId((HANDLE)4116, &pEProcess); // 判断pEProcess是否有效 if (NT_SUCCESS(Status) && pEProcess != NULL) { ULONG64 TargetAddress = 0x401000; SIZE_T TargetSize = 4; SIZE_T read = 0; // 申请空间并填充写出字节0x90 BYTE* ReadBuffer = (BYTE *)ExAllocatePool(NonPagedPool, 1024); for (size_t i = 0; i < 4; i++) { ReadBuffer[i] = 0x90; } // 获取CR3用于转换 PUCHAR Var = reinterpret_cast(pEProcess); ULONG64 CR3 = *(ULONG64*)(Var + bit64); if (!CR3) { CR3 = *(ULONG64*)(Var + GetDirectoryTableOffset); // DbgPrint("[CR3] 寄存器地址 = 0x%p \n", CR3); } while (TargetSize) { // 开始循环切换到CR3 ULONG64 PhysicalAddress = TransformationCR3(CR3, TargetAddress + read); if (!PhysicalAddress) { break; } // 写入物理内存 ULONG64 WriteSize = min(PAGE_SIZE - (PhysicalAddress & 0xfff), TargetSize); SIZE_T BytesTransferred = 0; Status = WritePhysicalAddress(reinterpret_cast(PhysicalAddress), reinterpret_cast(ReadBuffer + read), WriteSize, &BytesTransferred); TargetSize -= BytesTransferred; read += BytesTransferred; // DbgPrint("[写出数据] => %d | %0x02X \n", WriteSize, ReadBuffer + read); if (!NT_SUCCESS(Status)) { break; } if (!BytesTransferred) { break; } } // 关闭引用 ObDereferenceObject(pEProcess); } // 关闭引用 UNREFERENCED_PARAMETER(path); // 卸载驱动 pDriver->DriverUnload = DriverUnload; return STATUS_SUCCESS; }

如上代码运行后,会向进程ID为4116的0x401000处写出4字节的0x90机器码,读者可通过第三方工具验证内存,输出效果如下所示;

驱动开发:内核物理内存寻址读写_驱动开发_02



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3