Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)

来源:互联网 发布:qq群优化软件怎么用 编辑:程序博客网 时间:2024/05/17 22:14

Windows虚拟地址转物理地址(原理+源码实现,附简单小工具)

http://www.cnblogs.com/Lthis/p/4746795.html

上个月就想写了,一直没时间...网上大概搜了一下,原理与操作倒是一大堆,一直没看到源码实现,总得有人动手,这回轮到我了。东西写得很烂,请大牛勿喷。一直觉得靠源码的方式驱动学习是非常好的一种学习方法,比较直观!声明一下,本教程只有讨论开启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(线性地址)中的DirectoryTable其实就是个索引,在未开启PAE的情况下,PDEPTE均是32bit4字节,所以要Table*4),以上只是原理上的描述,实际上,PDEPTE的后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的情况下,PDEPTE均是64bit8字节,所以要*8),同样PDEPTE的后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后虚拟地址到物理地址的转换》

另附上小工具源码,该工具用于检测系统是否开启PAEPSE等。

 

复制代码
#define BUFFERSIZE    0x3000char 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


1 0