1. 程式人生 > >Windows虛擬地址轉物理地址(原理+源碼實現,附簡單小工具)

Windows虛擬地址轉物理地址(原理+源碼實現,附簡單小工具)

size_t \n 動手 機器碼 status 情況 direct nload amp

      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(線性地址)中的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    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虛擬地址轉物理地址(原理+源碼實現,附簡單小工具)