(25)2-9-9-12分页(下)
一、2-9-9-12分頁結(jié)構(gòu)
PDPTE,PDE,PTE都占8字節(jié)。
二、頁目錄指針表項 Page-Directory-Point-Table Entry
PDPTE的12-35位存儲了頁目錄表基址的高24位,低12位補零。物理地址共占36位,接下來介紹的PDE,PTE均是如此。
PCD, PWT等屬性等學到TLB才知道是干嘛的。
三、頁目錄項 PDE
從上圖可以看出,PS位=1時,PDE直接指向大物理頁,其中,物理頁偏移由線性地址的剩余21位(32-2-9=21)構(gòu)成,由此推出大物理頁占2^21=2MB;PS=0時,PDE指向頁表。
解釋一下G位,G=1表示這是全局頁,是多個進程共享的,這種頁是通過 CreateFileMapping 申請的。與之對應的,G=0就是進程獨享的物理頁,這種頁是通過 VirtualAlloc 分配的。
G=1,即為全局頁,進程(CR3)切換時,TLB中的記錄不會被刷新。
最后解釋一下最高位(圖中沒有標出的保留位),稱為XD位或者NX位,當最高位置1,表示這個物理頁不能當成代碼執(zhí)行。XD位是PDE和PTE都有的,PDE或PTE的XD位只要有一個是1,這個物理頁就不能執(zhí)行。
四、頁表項 PTE
屬性部分和10-10-12差不多,沒什么新東西,注意物理頁基址是36位,最高位是XD位即可。
五、給0線性地址掛上物理頁
寫一個測試程序:
// ReadWriteNULL_2-9-9-12.cpp : Defines the entry point for the console application. //#include "stdafx.h"int _tmain(int argc, _TCHAR* argv[]) {char data[0x1000] = {0};int *p = NULL;printf("可用的物理頁基址:%p\n", data);printf("請在windbg中給NULL掛物理頁.\n");getchar(); // windbg...// 讀寫NULL*p = 0x20201008;printf("*NULL = %x\n", *p);getchar();return 0; }讓程序跑起來:
上節(jié)課已經(jīng)手動拆過線性地址啦,所以今天就用 !vtop 偷懶了。
查CR3
!process 0 0 CR3=134c03e0拆NULL
kd> !vtop 134c03e0 0 X86VtoP: Virt 00000000, pagedir 134c03e0 X86VtoP: PAE PDPE 134c03e0 - 0000000011046001 X86VtoP: PAE PDE 11046000 - 000000000f4e1067 X86VtoP: PAE PTE f4e1000 - 0000000000000000 X86VtoP: PAE zero PTE Virtual address 0 translation fails, error 0xD0000147.拆0x12ef60
kd> !vtop 134c03e0 12ef60 X86VtoP: Virt 0012ef60, pagedir 134c03e0 X86VtoP: PAE PDPE 134c03e0 - 0000000011046001 X86VtoP: PAE PDE 11046000 - 000000000f4e1067 X86VtoP: PAE PTE f4e1970 - 800000001d6b5067 X86VtoP: PAE Mapped phys 1d6b5f60 Virtual address 12ef60 translates to physical address 1d6b5f60.掛物理頁(沒有!eq指令??)
!ed f4e1000 1d6b5067 !ed f4e1004 80000000改好了,執(zhí)行剩余代碼:
實驗成功。
六、修改頁屬性,實現(xiàn)應用層讀寫高2G內(nèi)存地址
編寫一個程序,讀寫0x8003f048.
// ReadWriteH2G_2-9-9-12.cpp : Defines the entry point for the console application. //#include "stdafx.h" #include <windows.h>int _tmain(int argc, _TCHAR* argv[]) {printf("請在windbg修改8003f048的U/S位.\n");getchar();printf("%08x\n", *(PDWORD)0x8003f048); // 讀*(PDWORD)0x8003f048 = 0x12345678; // 寫printf("%08x\n", *(PDWORD)0x8003f048); // 讀getchar();return 0; }讓程序跑起來。
查CR3
!process 0 0 CR3 = 12100420拆8003f048
!vtop 12100420 8003f048 kd> !vtop 12100420 8003f048 X86VtoP: Virt 8003f048, pagedir 12100420 X86VtoP: PAE PDPE 12100430 - 00000000044c8001 X86VtoP: PAE PDE 44c8000 - 0000000000b5a163 X86VtoP: PAE PTE b5a1f8 - 000000000003f163 X86VtoP: PAE Mapped phys 3f048 Virtual address 8003f048 translates to physical address 3f048.改U/S位,和PTE的G位
G=1時,程序會崩,只有G=0才能成功。
簡單解釋一下G位:
PDE中,只有PS=1(大頁)時,G才有效。
G位是全局頁的意思,G=1時,這個線性地址對應的頁是全局頁,進程切換時,對應的TLB不會改變。多個進程的高2G數(shù)據(jù)大部分都是相同的,我們不希望在切換進程時對這部分TLB做多余的刷新,所以需要設置G=1以提高效率。
回到本文,我們已經(jīng)知道PDE的PS=0,所以我們不用管PDE的G。關(guān)鍵是PTE的G,為什么要改成0呢?因為線性地址 0x8003f048 默認是G=1,它在CPU中有TLB緩存。
我們修改了 0x8003f048 的PTE的U/S位,但是因為G=1,在CPU中有緩存,訪問線性地址時優(yōu)先讀取TLB緩存,而緩存中的ATTR并沒有改變,所以我們對U/S的修改是無效的。即使用 !ed 指令改了U/S,我們的應用層代碼試圖訪問 0x8003f048 時,用的仍然是舊的屬性,U/S仍然是0.
更多關(guān)于TLB和G位的知識,請看后續(xù)的TLB專題博客。
!ed 44c8000 00b5a167 !ed b5a1f8 0003f067執(zhí)行剩余的代碼:
七、逆向分析MmIsAddressValid函數(shù)
首先給出我自己畫的示意圖:
根據(jù)示意圖,0xC000000是第一張頁表的線性地址,0xC0600000是第一張頁目錄表的線性地址。
結(jié)合示意圖,理解下面的公式:
2-9-9-12 PDPTI-PDI-PTI-OFFSET 公式: pPDE = 0xc0600000 + (PDPTI*4KB) + (PDI*8) pPTE = 0xc0000000 + (PDPTI*2MB) + (PDI*4KB) + (PTI*8) 更高效的公式(MmIsAddressValid是這么干的) pPDE = 0xc0600000 + ((addr >> 18) & 0x3ff8) pPTE = 0xc0000000 + ((addr >> 9) & 0x7ffff8)解釋:
0xc0600000 是第一張頁目錄表,0xc0600000 + (PDPTI * 4KB) 就是找線性地址對應的頁目錄表,再加上(PDI * 8)就找到了對應的PDE。
0xc0000000 是第一張頁表,0xc0000000 + (PDPTI * 2MB) 就是找線性地址對應的2MB頁表的基址,然后加上 (PDI4KB) 就是對應的頁表,最后再加上(PTI8)就找到了PTE。
理解了2-9-9-12的映射結(jié)構(gòu),再來分析 MmIsAddressValid 函數(shù)就比較容易了。
該函數(shù)為了提高效率,移位看起來比較費腦,但只要理解了我上面畫的示意圖和那些公式,分析起來應該就沒什么問題了。
代碼中有一些莫名其妙的語句,比如:
.text:004399A1 mov [ebp+var_4], eax.text:004399B7 push 0 .text:004399B9 mov [ebp+var_8], edx .text:004399BC pop eax猜測是因為編程使用的是C語言,所以生成了一些冗余的代碼。
分析前:
--------------------------------------------------------------------------- .text:0043997A align 10h .text:00439980 ; Exported entry 685. MmIsAddressValid .text:00439980 .text:00439980 ; =============== S U B R O U T I N E ======================================= .text:00439980 .text:00439980 ; Attributes: bp-based frame .text:00439980 .text:00439980 ; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress) .text:00439980 public _MmIsAddressValid@4 .text:00439980 _MmIsAddressValid@4 proc near ; CODE XREF: IopIsAddressRangeValid(x,x)+2Fp .text:00439980 ; IopGetMaxValidMemorySize(x,x)+29p ... .text:00439980 .text:00439980 var_8 = dword ptr -8 .text:00439980 var_4 = dword ptr -4 .text:00439980 VirtualAddress = dword ptr 8 .text:00439980 .text:00439980 mov edi, edi .text:00439982 push ebp .text:00439983 mov ebp, esp .text:00439985 push ecx .text:00439986 push ecx .text:00439987 mov ecx, [ebp+VirtualAddress] .text:0043998A push esi .text:0043998B mov eax, ecx .text:0043998D shr eax, 12h .text:00439990 mov esi, 3FF8h .text:00439995 and eax, esi .text:00439997 sub eax, 3FA00000h .text:0043999C mov edx, [eax] .text:0043999E mov eax, [eax+4] .text:004399A1 mov [ebp+var_4], eax .text:004399A4 mov eax, edx .text:004399A6 push edi .text:004399A7 and eax, 1 .text:004399AA xor edi, edi .text:004399AC or eax, edi .text:004399AE jz short loc_439A11 .text:004399B0 mov edi, 80h .text:004399B5 and edx, edi .text:004399B7 push 0 .text:004399B9 mov [ebp+var_8], edx .text:004399BC pop eax .text:004399BD jz short loc_4399C3 .text:004399BF test eax, eax .text:004399C1 jz short loc_439A15 .text:004399C3 .text:004399C3 loc_4399C3: ; CODE XREF: MmIsAddressValid(x)+3Dj .text:004399C3 shr ecx, 9 .text:004399C6 and ecx, 7FFFF8h .text:004399CC mov eax, [ecx-3FFFFFFCh] .text:004399D2 sub ecx, 40000000h .text:004399D8 mov edx, [ecx] .text:004399DA mov [ebp+var_4], eax .text:004399DD push ebx .text:004399DE mov eax, edx .text:004399E0 xor ebx, ebx .text:004399E2 and eax, 1 .text:004399E5 or eax, ebx .text:004399E7 pop ebx .text:004399E8 jz short loc_439A11 .text:004399EA and edx, edi .text:004399EC push 0 .text:004399EE mov [ebp+var_8], edx .text:004399F1 pop eax .text:004399F2 jz short loc_439A15 .text:004399F4 test eax, eax .text:004399F6 jnz short loc_439A15 .text:004399F8 and ecx, esi .text:004399FA mov ecx, [ecx-3FA00000h] .text:00439A00 mov eax, 81h .text:00439A05 and ecx, eax .text:00439A07 xor edx, edx .text:00439A09 cmp ecx, eax .text:00439A0B jnz short loc_439A15 .text:00439A0D test edx, edx .text:00439A0F jnz short loc_439A15 .text:00439A11 .text:00439A11 loc_439A11: ; CODE XREF: MmIsAddressValid(x)+2Ej .text:00439A11 ; MmIsAddressValid(x)+68j .text:00439A11 xor al, al .text:00439A13 jmp short loc_439A17 .text:00439A15 ; --------------------------------------------------------------------------- .text:00439A15 .text:00439A15 loc_439A15: ; CODE XREF: MmIsAddressValid(x)+41j .text:00439A15 ; MmIsAddressValid(x)+72j ... .text:00439A15 mov al, 1 .text:00439A17 .text:00439A17 loc_439A17: ; CODE XREF: MmIsAddressValid(x)+93j .text:00439A17 pop edi .text:00439A18 pop esi .text:00439A19 leave .text:00439A1A retn 4 .text:00439A1A _MmIsAddressValid@4 endp分析后:
主要是判斷PDE PTE的P,PS位。
八、編寫代碼實現(xiàn)修改頁屬性,實現(xiàn)應用層讀寫高2G內(nèi)存地址
2-9-9-12 PDPTI-PDI-PTI-OFFSET 公式: pPDE = 0xc0600000 + (PDPTI*4KB) + (PDI*8) pPTE = 0xc0000000 + (PDPTI*2MB) + (PDI*4KB) + (PTI*8) 更高效的公式(MmIsAddressValid是這么干的) pPDE = 0xc0600000 + ((addr >> 18) & 0x3ff8) pPTE = 0xc0000000 + ((addr >> 9) & 0x7ffff8) // ReadWriteH2G_2-9-9-12.cpp : Defines the entry point for the console application. //#include "stdafx.h" #include <windows.h>//2-9-9-12 //PDPTI-PDI-PTI-OFFSETDWORD *GetPDE(DWORD addr) {//return (DWORD *)(0xc0600000 + ((addr >> 18) & 0x3ff8));DWORD PDPTI = addr >> 30;DWORD PDI = (addr >> 21) & 0x000001FF;DWORD PTI = (addr >> 12) & 0x000001FF;return (DWORD *)(0xC0600000 + PDPTI * 0x1000 + PDI * 8); }DWORD *GetPTE(DWORD addr) {//return (DWORD *)(0xc0000000 + ((addr >> 9) & 0x7ffff8));DWORD PDPTI = addr >> 30;DWORD PDI = (addr >> 21) & 0x000001FF;DWORD PTI = (addr >> 12) & 0x000001FF;return (DWORD *)(0xC0000000 + PDPTI * 0x200000 + PDI * 0x1000 + PTI * 8); }void __declspec(naked) R0Function() {__asm{push ebpmov ebp,espsub esp,0x1000pushadpushfd }__asm push fs//__asm int 3// 修改8003f048的U/S位*GetPDE(0x8003f048) |= 0x00000004;*GetPTE(0x8003f048) |= 0x00000004;// 修改PTE的G位*GetPTE(0x8003f048) &= 0xFFFFFEFF;//__asm int 3__asm pop fs__asm{popfdpopadadd esp,0x1000mov esp,ebppop ebpiretd} }int _tmain(int argc, _TCHAR* argv[]) {printf("在IDT表構(gòu)建中斷門,請在windbg中執(zhí)行下面的指令:\n");printf("eq 8003f500 %04xee00`0008%04x\n",(DWORD)R0Function>>16,(DWORD)R0Function & 0x0000FFFF);getchar();__asm int 0x20printf("0x8003f048 U/S,G位修改成功.\n");printf("*(PDWORD)0x8003f048 = %08x\n", *(PDWORD)0x8003f048); // 讀*(PDWORD)0x8003f048 = 0x12345678; // 寫printf("*(PDWORD)0x8003f048 = %08x\n", *(PDWORD)0x8003f048); // 讀getchar();return 0; }九、測試XD位
我寫了一個程序,是用一個指針指向一塊內(nèi)存,然后用匯編call過去。然后可以對比XD=0和XD=1時,CALL的結(jié)果。
// TestXD.cpp : Defines the entry point for the console application. //#include "stdafx.h" #include <Windows.h>char *buff;DWORD *GetPDE(DWORD addr) {//return (DWORD *)(0xc0600000 + ((addr >> 18) & 0x3ff8));DWORD PDPTI = addr >> 30;DWORD PDI = (addr >> 21) & 0x000001FF;DWORD PTI = (addr >> 12) & 0x000001FF;return (DWORD *)(0xC0600000 + PDPTI * 0x1000 + PDI * 8); }DWORD *GetPTE(DWORD addr) {//return (DWORD *)(0xc0000000 + ((addr >> 9) & 0x7ffff8));DWORD PDPTI = addr >> 30;DWORD PDI = (addr >> 21) & 0x000001FF;DWORD PTI = (addr >> 12) & 0x000001FF;return (DWORD *)(0xC0000000 + PDPTI * 0x200000 + PDI * 0x1000 + PTI * 8); }void __declspec(naked) R0Function() {__asm{push ebpmov ebp,espsub esp,0x1000pushadpushfd }__asm push fs//__asm int 3// 修改buff的XD位*(GetPDE((DWORD)buff) + 1) |= 0x80000000;*(GetPTE((DWORD)buff) + 1) |= 0x80000000;//__asm int 3__asm pop fs__asm{popfdpopadadd esp,0x1000mov esp,ebppop ebpiretd} }int _tmain(int argc, _TCHAR* argv[]) {// 申請一個內(nèi)存頁,寫入硬編碼 buff = (char *)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE);printf("buff: %p\n", buff);BYTE bytecode [7] = {0x90,0xB8,0x01,0x00,0x00,0x00,0xC3}; // NOP, MOV EAX,1, RETmemcpy(buff,bytecode,7);// 測試,對比XD=0和XD=1的運行結(jié)果printf("輸入XD位:");int xd;scanf("%d",&xd); // 輸入0可以調(diào)用,輸入1調(diào)用失敗getchar();if (xd == 1){printf("在IDT表構(gòu)建中斷門,請在windbg中執(zhí)行下面的指令:\n");printf("eq 8003f500 %04xee00`0008%04x\n",(DWORD)R0Function>>16,(DWORD)R0Function & 0x0000FFFF);getchar();__asm int 0x20printf("XD位修改成功,buff不可執(zhí)行.\n");}DWORD dwEAX = 0;__asm{push eaxxor eax,eaxcall buffmov dwEAX,eaxpop eax};if (dwEAX == 0) printf("調(diào)用失敗.\n");else if (dwEAX == 1) printf("調(diào)用成功.\n");printf("bye!\n");getchar();return 0; }如果XD=0,就是默認情況了,可以正常調(diào)用函數(shù)并返回。
如果將XD修改為1,那么執(zhí)行CALL會失敗,程序會卡死。
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的(25)2-9-9-12分页(下)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (24)2-9-9-12分页(上)
- 下一篇: (26)TLB