Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)
2021-07-15 15:15
By Lthis
上个月就想写了,一直没时间...网上大概搜了一下,原理与操作倒是一大堆,一直没看到源码实现,总得有人动手,这回轮到我了。东西写得很烂,请大牛勿喷。一直觉得靠源码的方式驱动学习是非常好的一种学习方法,比较直观!声明一下,本教程只有讨论开启PAE与关闭PAE两种,至于PSE是否开启没有管...我的虚拟机默认PSE貌似是开启滴?不知是不是写的小工具有问题....对于x64下的等我有时间再写吧。
东西都上传在压缩包中了,Codes文件夹下是工程源码,Demo文件夹下是测试案例,Tool文件夹放的是小工具的Demo和源码。
我的环境:开发环境(win7 sp1 x64 + vs2013社区版 update5 + wdk8.1)
测试环境(vm10 + win7 sp1 x86)
一、先说说未开启PAE的情况,祭出intel手册的经典图例:
这幅图就是虚拟地址转为物理地址的原理图(4k页面),看图说话,用伪代码描述一下:
1.Directory Entry(PDE) = PDBR[Directory];
2.Page-Table Entry(PTE) = PDE + Table * 4;
3.Physical Address = PTE + Offset;
由上可知,Linear Address(线性地址)中的Directory和Table其实就是个索引,在未开启PAE的情况下,PDE、PTE均是32bit(4字节,所以要Table*4),以上只是原理上的描述,实际上,PDE、PTE的后3位是属性值,所以需要把后3位抹掉。
下边上关键代码,基本都步骤都写了注释了,有需要的可以封装成函数。此外,本段代码只是测试用,写的很不规范,比如,在调用MmMapIoSpace应该调用MmUnMapIoSpace释放内存。
// 得到ring3传入的虚拟地址 size_t* pOutAddress = (size_t*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority); VIRTUAL_ADDRESS virtualAddress = { 0 }; virtualAddress.ulVirtualAddress = *pOutAddress; ULONG pdbr; _asm{ mov eax, cr3; mov pdbr, eax; } PHYSICAL_ADDRESS phyAddress = { 0 }; phyAddress.LowPart = pdbr; PULONG pPdbr = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint(("pdbr = 0x%08X, 映射后的地址0x%p\n", pdbr, pPdbr)); // pPdbr[ulDirBaseIdx] 页目录项 ULONG ulDirBaseIdx = virtualAddress.stVirtualAddress.dirBaseIndex; ULONG ulDirIdx = virtualAddress.stVirtualAddress.dirIndex; KdPrint(("第一级,已找到页目录所在项:pPdbr[%d]:0x%08X", ulDirBaseIdx,pPdbr[ulDirBaseIdx])); ULONG ulDir = pPdbr[ulDirBaseIdx] & 0xFFFFF000; // 抹去后3位得到真正的页目录项 ULONG ulDirPlus = ulDir + ulDirIdx * 4; // 页表项 phyAddress.LowPart = ulDirPlus; PULONG pDirPlus = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint(("第二级,已找到页表项:ulDirPlus = 0x%08X, 映射后的地址0x%p\n", ulDirPlus, pDirPlus)); ULONG ulPageTable = *pDirPlus & 0xFFFFF000; // 抹去后3位得到真正的页表项 // 得到物理地址 ULONG ulPhyAddress = ulPageTable + virtualAddress.stVirtualAddress.offset; // 映射为虚拟地址,获取其值进行验证 phyAddress.LowPart = ulPhyAddress; PWCHAR pPhyAddress = (PWCHAR)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint(("虚拟地址:0x%08X, 对应物理地址:0x%08X, Value:%S\n", *pOutAddress, ulPhyAddress, pPhyAddress)); // 传出对应物理地址 *pOutAddress = ulPhyAddress;
二、开启PAE的情况
同样是4k页面的,伪代码描述如下:
1.Dir.Pointer Entry(PDPTE) = PDPTR[Directory Pointer];
2.Director Entry(PDE) = PDPTE + Directory * 0x8;
3.Page-Table Entry(PTE) = PDE + Table * 0x8;
4.Physical Address = PTE+Offset;
在开启PAE的情况下,PDE、PTE均是64bit(8字节,所以要*8),同样PDE、PTE的后3位是属性值,所以需要把后3位抹掉。
关键代码如下:
// 得到传入的ring3层虚拟地址 size_t* pOutAddress = (size_t*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority); VIRTUAL_ADDRESS virtualAddress = { 0 }; virtualAddress.ulVirtualAddress = *pOutAddress; ULONG pdbr; // 得到页目录指针物理地址 _asm{ mov eax, cr3; mov pdbr, eax; } // 映射为虚拟地址以便取值 PHYSICAL_ADDRESS phyAddress = { 0 }; phyAddress.LowPart = pdbr; PULONG pPdbr = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint(("pdbr = 0x%08X, 映射后的地址0x%p\n", pdbr, pPdbr)); // 定位页目录指针表并获取页目录表物理页地址 // ulDirAddress 为页目录表物理页地址 ULONG ulPointerIdx = virtualAddress.stVirtualAddress.dirPointer; ULONG ulDirBaseAddress = pPdbr[ulPointerIdx]; ulDirBaseAddress &= 0xFFFFF000; // 中间物理地址 // 定位页表项 ULONG ulDirAddress = ulDirBaseAddress + virtualAddress.stVirtualAddress.dirIndex * 0x8; phyAddress.LowPart = ulDirAddress; PULONG pPageTable = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); ULONG ulPageTable = *pPageTable; ulPageTable &= 0xFFFFF000; // 中间物理地址 // 定位物理页面 ulPageTable += virtualAddress.stVirtualAddress.tableIndex * 0x8; phyAddress.LowPart = ulPageTable; PULONG pPageBase = (PULONG)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); ULONG ulPageBase = *pPageBase; ulPageBase &= 0xFFFFF000; // 得到物理地址 ULONG ulPhyAddress = ulPageBase + virtualAddress.stVirtualAddress.offset; // 映射为虚拟地址,获取其值进行验证 phyAddress.LowPart = ulPhyAddress; PWCHAR pPhyAddress = (PWCHAR)MmMapIoSpace(phyAddress, sizeof(PHYSICAL_ADDRESS), MmNonCached); KdPrint(("虚拟地址:0x%08X, 对应物理地址:0x%08X, Value:%S\n", *pOutAddress, ulPhyAddress, pPhyAddress)); // 传出对应物理地址 *pOutAddress = ulPhyAddress; pIrp->IoStatus.Information = cout;
以上代码步骤是参考安于此生的文章写的,看不懂的可以先看看安于此生的文章《启用PAE后虚拟地址到物理地址的转换》
另附上小工具源码,该工具用于检测系统是否开启PAE、PSE等。
#define BUFFERSIZE 0x3000 char g_szMemInfo[BUFFERSIZE] = { 0 }; // 以下code在 DriverEntry 中 DWORD dwPE = 0; // Protection Enable cr0[0] DWORD dwWP = 0; // Write Protect cr0[16] DWORD dwPG = 0; // Paging cr0[31] DWORD dwPAE = 0; // 物理地址扩展 cr4[5] DWORD dwPSE = 0; // Page Size Extension cr4[4] DWORD dwCr0 = 0; DWORD dwCr4 = 0; // 注册卸载函数 pDriverObj->DriverUnload = driverUnload; _asm{ pushad; mov eax, cr0; mov dwCr0, eax; // PE标志位 and eax, 0x01; mov dwPE, eax; mov eax, cr0; // WP标志位 and eax, 0x10000; mov dwWP, eax; mov eax, cr0; // PG标志位 and eax, 0x80000000; mov dwPG, eax; // PAE //mov eax, cr4; 机器码如下 _emit 0x0F; _emit 0x20; _emit 0xE0; mov dwCr4, eax; and eax, 0x20; mov dwPAE, eax; // PSE _emit 0x0F; _emit 0x20; _emit 0xE0; and eax, 0x10; mov dwPSE, eax; popad; } KdPrint(("PE = 0x%08X\r\n",dwPE)); KdPrint(("WP = 0x%08X\r\n",dwWP)); KdPrint(("PG = 0x%08X\r\n",dwPG)); KdPrint(("PAE = 0x%08X\r\n",dwPAE)); KdPrint(("PSE = 0x%08X\r\n",dwPSE)); KdPrint(("Cr0 = 0x%08X\r\n",dwCr0)); KdPrint(("Cr4 = 0x%08X\r\n",dwCr4)); //---------------------------------------------------------------------------- // PE标志位 if (0 != dwPE){ RtlStringCchCatNA( g_szMemInfo, BUFFERSIZE, "----------------------保护模式(PE=1)-------------------\r\n", BUFFERSIZE - sizeof("----------------------保护模式(PE=1)-------------------\r\n")); } else{ RtlStringCchCatNA( g_szMemInfo, BUFFERSIZE , "----------------------实地址模式(PE=0)-------------------\r\n", BUFFERSIZE - sizeof("----------------------实地址模式(PE=0)-------------------\r\n")); } //---------------------------------------------------------------------------- // WP标志位 if (0 != dwWP){ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "内存写保护(WP)开启...\r\n" ); } else{ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "内存写保护(WP)禁止...\r\n" ); } //---------------------------------------------------------------------------- // PG标志位 if (0 != dwPG){ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "页机制(PG)启用\r\n" ); } else{ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "页机制(PG)禁止\r\n" ); } //---------------------------------------------------------------------------- // PAE标志位 if (0 != dwPAE){ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "物理地址扩展(PAE)已开启\r\n" ); } else{ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "物理地址扩展(PAE)未启用\r\n" ); } //---------------------------------------------------------------------------- // PSE标志位 if (0 != dwPSE){ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "页面大小扩展(PSE)已开启\r\n" ); } else{ RtlStringCchCatA( g_szMemInfo, BUFFERSIZE, "页面大小扩展(PSE)未启用\r\n" ); } KdPrint(("%s\r\n", g_szMemInfo));
最后,看看效果运行图。Demo是在ring3层定义一个Unicoe字符串:“Lthis”,然后将其虚拟地址传入ring0层,ring0解析后传出对应的物理地址。
开启PAE下运行的效果:
未开启PAE的运行效果:
附件地址:链接:http://pan.baidu.com/s/1kTENdnL 密码:g5j7
jpg 改 rar
文章标题:Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)
文章链接:http://soscw.com/index.php/essay/105617.html