Windows內核分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.html
TLB
1. CPU尋址模式
2. TLB
3. 緩存
4. shadowwalk技術
5.TLB感知實驗
6.全局頁
1. CPU尋址模式
之前在分頁時介紹過這張圖,里面分析過其對應的尋址模式:
1)CPU先嘗試從TLB找到物理地址;
2)如果TLB中不存在對應的物理地址,則手動計算出物理地址;
3)之后再從緩存中嘗試獲取物理地址對應的數據;
4)如果緩存中沒有,則去物理內存中找到相應地址來讀取對應的數據。
2. TLB
TLB與緩存要區分清楚,TLB是通過線性地址找物理地址的,緩存是通過物理地址找數據的。
TLB中以頁為單位來存儲的,緩存直接按照字節來進行存儲的,要區分他倆之間的關系,不要混為一談。
3. 緩存
以三級緩存為例,當一級緩存滿了之后會往二級緩存中存儲,二級滿了會往三級緩存中存儲,三級緩存定期備份到物理內存中。
4. shadowwalk技術
有很多游戲等會存在CRC校驗,其讀取的是數據TLB,但執行的是指令TLB,因此我們保留數據TLB,修改指令TLB,這樣就實現了過CRC校驗。
shadowwalk等工具就是利用TLB的兩張表分離實現過CRC校驗的。
5.TLB感知實驗
1)實驗大體步驟
① 准備兩個地址,在不同的頁上;
② 向零地址掛上TLB,
③ 讀取數據,加載到TLB中;
④ 再向0地址掛另一個物理頁的PTE;
⑤ 我再讀零地址,如果讀出來的還是原來的數據,則證明存在TLB。
2)實驗代碼:
如下,我們利用windbg創建調用門:eq 8003f048 0040ec00`00081005
// 123.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #include <stdlib.h> char* p = NULL; char* p1 = NULL; int temp = 0; int temp1 = 0; // eq 8003f048 0040ec00`00081005 void __declspec(naked)test(){ _asm{ pushfd; pushad; push fs; mov ax,0x30; mov fs,ax; // 掛p位的 mov eax,dword ptr ds:[p]; // 取出p的地址 shr eax,9; // 由移9位 and eax,0x7FFFF8; // add eax,0xc0000000; // 取出pte mov ebx,dword ptr [eax]; mov ecx,[eax+4]; // 往0地址掛 mov dword ptr ds:[0xc0000000],ebx; mov dword ptr ds:[0xc0000004],ecx; // 加載到tlb中 mov eax, dword ptr ds:[0]; //int 3; //取出值 mov temp,eax; /*---------------*/ // 掛p1的 mov eax,dword ptr[p1]; // 取出p1的地址 shr eax,9; // 由移9位 and eax,0x7FFFF8; // add eax,0xc0000000; // 取出pte mov ebx,dword ptr [eax]; mov ecx,[eax+4]; // 往0地址掛 mov dword ptr ds:[0xc0000000],ebx; mov dword ptr ds:[0xc0000004],ecx; // 加載到tlb中 mov eax, dword ptr ds:[0]; // 取出值 mov temp1,eax; //int 3; pop fs; popad; popfd; retf; } } int main(int argc, char* argv[]) { // 分配內存 p = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE); p1 = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE); char buf[]={0,0,0,0,0x48,0}; printf("%x,p1=%x,p=%x\r\n",test,p1,p); system("pause"); temp = 100; temp1 = 200; *(int*)p = 0x1000; *(int*)p1 = 0x2000; _asm{ call fword ptr buf; } printf("%x,%x\t\n",temp,temp1); printf("hello world!\r\n"); system("pause"); return 0; }
3)實驗結果:
實驗結果如下,可以發現第二次雖然我們在0地址處手工掛靠了物理頁,但其因為第一次保存在TLB緩存當中,當第二次再開始讀取的時候,通過TLB獲取第一個PTE的線性地址,因此讀到的是第一個。
因此通過這個實驗你就可以感知到TLB緩存的存在,下面我們學習如何刷新TLB緩存。
4)TLB刷新
如何刷新TLB呢?其如果刷新cr3,則就刷新TLB,因此我們如果想刷新,寫上 mov eax,cr3; mov cr3,eax即可。
6.全局頁
1)將其修改為全局頁
如果將頁置為全局頁,則需要修改PTE的第8位,G位,表示全局頁。
因此我們在寫入PTE前,使用 or ebx,0x100將該位置1,哪怕再刷新CR3也沒有效果。
2)通過cr4來禁止修改全局頁
cr4存在一個PGE位,當該位為1,則允許使用全局頁,現在我們將該位關掉。
因為在vc6.0環境下不支持cr4的修改,因此我們借助IDA來直接硬編碼:
// cr4 PGE位關閉全局頁 __emit 0x0f; // mov eax,cr4; __emit 0x20; __emit 0xE0; mov ecx,0x80; not ecx; and eax,ecx; __emit 0x0f; // mov cr4,eax; __emit 0x22; __emit 0xe0;
之后我們發現及時設置全局頁,其也是無效的,會被置換出來
其實CPU中存在一個強制刷新地址的指令,哪怕是全局頁表也會被刷新
invlpg dword ptr ds:[0],使用該條指令則會強制刷新緩存
全部試驗代碼如下:
// 123.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <windows.h> #include <stdlib.h> char* p = NULL; char* p1 = NULL; int temp = 0; int temp1 = 0; // eq 8003f048 0040ec00`00081005 void __declspec(naked)test(){ _asm{ pushfd; pushad; push fs; mov ax,0x30; mov fs,ax; // 掛p位的 mov eax,dword ptr ds:[p]; // 取出p的地址 shr eax,9; // 由移9位 and eax,0x7FFFF8; // add eax,0xc0000000; // 取出pte mov ebx,dword ptr [eax]; mov ecx,[eax+4]; // 往0地址掛 xor ebx,0x100; mov dword ptr ds:[0xc0000000],ebx; mov dword ptr ds:[0xc0000004],ecx; // 加載到tlb中 mov eax, dword ptr ds:[0]; //int 3; //取出值 mov temp,eax; mov eax,cr3; mov cr3,eax; /*---------------*/ // cr4 PGE位關閉全局頁 /* __emit 0x0f; // mov eax,cr4; __emit 0x20; __emit 0xE0; mov ecx,0x80; not ecx; and eax,ecx; __emit 0x0f; // mov cr4,eax; __emit 0x22; __emit 0xe0; */ // 掛p1的 mov eax,dword ptr[p1]; // 取出p1的地址 shr eax,9; // 由移9位 and eax,0x7FFFF8; // add eax,0xc0000000; // 取出pte mov ebx,dword ptr [eax]; mov ecx,[eax+4]; // 往0地址掛 mov dword ptr ds:[0xc0000000],ebx; mov dword ptr ds:[0xc0000004],ecx; // 加載到tlb中 invlpg dword ptr ds:[0]; mov eax, dword ptr ds:[0]; // 取出值 mov temp1,eax; //int 3; pop fs; popad; popfd; retf; } } int main(int argc, char* argv[]) { // 分配內存 p = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE); p1 = (char*)VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_EXECUTE_READWRITE); char buf[]={0,0,0,0,0x48,0}; printf("%x,p1=%x,p=%x\r\n",test,p1,p); system("pause"); temp = 100; temp1 = 200; *(int*)p = 0x1000; *(int*)p1 = 0x2000; _asm{ call fword ptr buf; } printf("%x,%x\t\n",temp,temp1); printf("hello world!\r\n"); system("pause"); return 0; }