这次的彩蛋解密是针对上次发的文章所写的。
在上次的文章结尾处我挖了个坑,这次给它填上。
PhysicalMemoryOperation.h这个小东西是半年前写的了,当时写这玩意的目的是为了实现驱动对物理内存的高速读写加深我对分页机制的理解,写完这个小东西之后我发现经过升级还能有更多的玩法,这里我将只对基础版本进行详细分析,并在结尾处继续挖个坑。
首先放上一段在DriverEntry中让人看了挠头的操作
ContextVirtualToPhysical(&g_PhysicalOpCR3); ULONG64 Read1 = *(PULONG64)0x0; MyPrint(_TitleAndFunc "ReadTest1:%16IX\n", Read1); ContextPhysicalToVirtual(&g_PhysicalOpCR3);
啊,读空指针啊,这不明摆着蓝屏吗?
唉,咋没蓝屏,还读到东西了呢?
下面我们来windbg看一下
唉,有点东西是吧
咋看不懂呢
这有个cr3,看看是啥东西?
这一堆整整齐齐的都是啥玩意啊?
不管了直接!vtop看一看
我相信看到这里,对分页机制比较熟悉的大手子们就应该大概懂实现原理了
首先需要熟悉分页机制,不懂的查阅intel手册和网上的一些文章进行学习。
下面贴出intel手册中对于分页使用规则的详细说明。
调用CreatePhysicalOpCR3BySystemCR3进行初始化
在其中分别依次调用pAllocPhysicalOpPageTableMemory、pMapSystemPML4T、pFillGeneratedPML4TandPDPT进行初始化,并填充结构
需要读取物理内存时调用ContextVirtualToPhysical、ContextPhysicalToVirtual进行环境切换
卸载时调用FreePhysicalOpCR3
在其中依次调用pUnmapSystemPML4T、pFreePhysicalOpPageTableMemory并清理结构
首先使用:CreatePhysicalOpCR3BySystemCR3
进行初始化工作,下面是这个函数的代码
NTSTATUS CreatePhysicalOpCR3BySystemCR3(ULONG64 SystemCR3, PPHYSICAL_OP_CR3 pPhysicalOpCR3) { //check the init state if (g_IsPhysicalOpInit) return STATUS_UNSUCCESSFUL; //allocate page table memory and fill the structure if (!NT_SUCCESS(pAllocPhysicalOpPageTableMemory(pPhysicalOpCR3))) return STATUS_UNSUCCESSFUL; //map pSystemPML4T to virtual address and fill the structure if (!NT_SUCCESS(pMapSystemPML4T(SystemCR3, pPhysicalOpCR3))) return STATUS_UNSUCCESSFUL; //fill PML4T and PDPT page table if (!NT_SUCCESS(pFillGeneratedPML4TandPDPT(pPhysicalOpCR3))) return STATUS_UNSUCCESSFUL; //generate new cr3 for reading the physical memory and add cr3 flag ULONG64 SystemCR3Flag = GetCR3Flag(SystemCR3); pPhysicalOpCR3->CR3Generated = (ULONG64)pPhysicalOpCR3->pAllocPA_PML4T | SystemCR3Flag; //fill the structure part:CR3System pPhysicalOpCR3->CR3System = SystemCR3; //print structure pPrintPhysicalOpStructure(pPhysicalOpCR3); g_IsPhysicalOpInit = TRUE; return STATUS_SUCCESS; }
在这个函数中完成了所有初始化操作
第一步调用pAllocPhysicalOpPageTableMemory
NTSTATUS pAllocPhysicalOpPageTableMemory(PPHYSICAL_OP_CR3 pPhysicalOpCR3) { //PML4T pPhysicalOpCR3->pAllocVA_PML4T = MmAllocateNonCachedMemory(PAGE_TABLE_SIZE); //check allocate state if (pPhysicalOpCR3->pAllocVA_PML4T == NULL) goto Lable_Error; pPhysicalOpCR3->pAllocPA_PML4T = (PVOID)MmGetPhysicalAddress(pPhysicalOpCR3->pAllocVA_PML4T).QuadPart; //PDPT pPhysicalOpCR3->pAllocVA_PDPT = MmAllocateNonCachedMemory(PAGE_TABLE_SIZE); //check allocate state if (pPhysicalOpCR3->pAllocVA_PDPT == NULL) goto Lable_Error; pPhysicalOpCR3->pAllocPA_PDPT = (PVOID)MmGetPhysicalAddress(pPhysicalOpCR3->pAllocVA_PDPT).QuadPart; return STATUS_SUCCESS; Lable_Error: //free allocated memory pFreePhysicalOpPageTableMemory(pPhysicalOpCR3); return STATUS_UNSUCCESSFUL; }
这个函数的作用是申请两个页面,分别用于存储自己创建的PML4T和用于存放大页的PDPT
第二步调用pMapSystemPML4T
NTSTATUS pMapSystemPML4T(ULONG64 SystemCR3, PPHYSICAL_OP_CR3 pPhysicalOpCR3) { ULONG64 SystemCR3NonFlag = ClearCR3Flag(SystemCR3); PVOID pSystemPML4T = (PVOID)SystemCR3NonFlag; if (g_SectionHandle == NULL) g_SectionHandle = OpenPhysicalMemory(); pPhysicalOpCR3->pSystemPML4TMap = MapPhysicalMemory(pSystemPML4T, PAGE_TABLE_SIZE); return pPhysicalOpCR3->pSystemPML4TMap == NULL ? STATUS_UNSUCCESSFUL : STATUS_SUCCESS; }
这个函数的作用是将系统的PML4T映射一份并保存到结构中
第三步调用pFillGeneratedPML4TandPDPT
NTSTATUS pFillGeneratedPML4TandPDPT(PPHYSICAL_OP_CR3 pPhysicalOpCR3) { //copy the system space map PVOID pSystemStart = (PVOID)VA_SYSTEM_START; ULONG64 SystemPML4TStart = ((PMMVA)&pSystemStart)->PML4T; MyPrint(_TitleAndFunc"SystemPML4TStart:%16X\n", SystemPML4TStart); RtlCopyMemory((PVOID)((ULONG64)pPhysicalOpCR3->pAllocVA_PML4T + SystemPML4TStart*ENTRY_SIZE), (PVOID)((ULONG64)pPhysicalOpCR3->pSystemPML4TMap + SystemPML4TStart*ENTRY_SIZE), (MAX_ENTRY_COUNT - SystemPML4TStart)*ENTRY_SIZE ); //make the first address point to my PDPT table *(PULONG64)pPhysicalOpCR3->pAllocVA_PML4T = (ULONG64)pPhysicalOpCR3->pAllocPA_PDPT | PAGE_TABLE_PML4T_FLAG; //fill the PDPT page table //add flag ULONG64 CurrentPDPTEntry = PAGE_TABLE_PDPT_FLAG; for (int i = 0; i < MAX_ENTRY_COUNT; i++) { //change pfn ((PMMPDPTE)&CurrentPDPTEntry)->PageFrameNumber = i; // *(PULONG64)((ULONG64)pPhysicalOpCR3->pAllocVA_PDPT + i*ENTRY_SIZE) = CurrentPDPTEntry; } return STATUS_SUCCESS; }
这个函数的作用是填充第一步申请的两块内存
到此为止初始化工作就基本结束了
难以理解的函数主要是 pFillGeneratedPML4TandPDPT,这里要详细讲解一下
通过查阅资料我们发现驱动层的代码不会出现在PML4T表的前0x80项中的,这样等于我们就有了0x80*(0x1000/8)*1G=65536G的空间是可以用来映射物理内存的
这里我们决定使用1G的大页面映射方式进行映射,我们的代码中只对前512G进行了映射,基本上现在内存没有超过这个大小的了。
第一步把系统的PML4T的内容拷贝到自己的PML4T中,这样操作一下子相当于拷贝了一份系统的映射关系,我们的代码就能正常的跑在我们新创建的这个PML4T中了
第二步我们让我们的PML4T的第一项指向我们的PDPT,并给上相应的标志位,具体标志位对应的权限请查阅上面的图表或intel手册
第三步我们填充PDPT,让其指向物理页面,使前512G的映射关系为虚拟地址和物理地址一一对应,同样的给上相应的标志
到此初始化部分的分析算是结束了
读写物理内存的环境切换代码很简单
就是个切cr3,切irql,关/开中断,就不做具体分析了
直接给出方程式
大概懂了吧
经过测试,主产物的读取速度基本是千万次每秒级别的
希望大家不要沉迷编程,多拿出一些时间陪陪家人恋人朋友,不要给自己留下遗憾。