查看原文
其他

彩蛋解密之物理内存读写到****的转变

鬼才zxy 看雪学院 2019-09-17


本文为看雪论坛精华文章
看雪论坛作者ID:鬼才zxy


这次的彩蛋解密是针对上次发的文章所写的。

传送门:驱动层代码隐藏执行(有码)https://bbs.pediy.com/thread-253677.htm

在上次的文章结尾处我挖了个坑,这次给它填上。

PhysicalMemoryOperation.h这个小东西是半年前写的了,当时写的目的是为了实现驱动对物理内存的高速读写加深我对分页机制的理解,写完这个小东西之后我发现经过升级还能有更多的玩法,这里我将只对基础版本进行详细分析,并在结尾处继续挖个坑。


令人挠头的操作



首先放上一段在DriverEntry中让人看了挠头的操作:


ContextVirtualToPhysical(&g_PhysicalOpCR3);
ULONG64 Read1 = *(PULONG64)0x0;
MyPrint(_TitleAndFunc "ReadTest1:%16IX\n", Read1);
ContextPhysicalToVirtual(&g_PhysicalOpCR3);


啊,读空指针啊,这不明摆着蓝屏吗?



唉,咋没蓝屏,还读到东西了呢?


下面我们来windbg看一下:



唉,有点东西是吧。



咋实现的啊,我先去看看代码吧:



咋看不懂呢?



使用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,关/开中断,就不做具体分析了。



逐渐奇怪的升级





直接给出方程式:



大概懂了吧。


经过测试,主产物的读取速度基本是千万次每秒级别的。




项目地址




https://github.com/zouxianyu/PhysicalMemoryRW


(别想了,这只是原料,反应需要自己实现的。)



后记:希望大家不要沉迷编程,多拿出一些时间陪陪家人恋人朋友,不要给自己留下遗憾。




- End -



看雪ID:鬼才zxy 

https://bbs.pediy.com/user-680946.htm  


*本文由看雪论坛 鬼才zxy 原创,转载请注明来自看雪社区






推荐文章++++

密码学基础:AES加密算法

微型加密算法实现及逆向分析

iOS LLDB中反反调试分析与实现

代码混淆之我见(四)

Windows Search远程代码执行漏洞简单分析及流量侧检测防御思路





进阶安全圈,不得不读的一本书








“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存