?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?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
?