保護模式篇——TLB與CPU緩存


寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。

  看此教程之前,問幾個問題,基礎知識儲備好了嗎?上一節教程學會了嗎?上一節課的練習做了嗎?沒有的話就不要繼續了。


🔒 華麗的分割線 🔒


練習及參考

本次答案均為參考,可以與我的答案不一致,但必須成功通過。

2️⃣ 自己實驗有和沒有DEP保護的程序,看看效果。

🔒 點擊查看答案 🔒


  這題目有一個坑,接下來聽我娓娓道來。

  首先說一句:你設置操作系統的DEP保護所有程序有效了嗎?(具體啟用查看上一節教程,選中為除下列選定程序之外的所有程序和服務啟用DEP,直接點確定),不然不起效果,白皮書原話:

  If IA32_EFER.NXE = 1, execute-disable (if 1, instruction fetches are not allowed from the 2-MByte region controlled by this entry; see Section 4.6); otherwise, reserved (must be 0).

  也就是說IA32_EFERNXE位為0,就算最高位是1也是無盡於是。我們更改這個選項就是使它為1,這個就是是坑。好了,根據我的代碼先運行一下:

  發現報錯了,這個和10-10-12分頁的不同之處。然后把PAGE_READWRITE改為PAGE_EXECUTE_READWRITE,然后再運行:

  這次運行成功,既然它們的頁的首地址都是一樣的,我們來看看它們的PDE或者PTE哪里不同,先是PAGE_READWRITE的:

PROCESS 8947dda0  SessionId: 0  Cid: 0690    Peb: 7ffde000  ParentCid: 0204
    DirBase: 13b00380  ObjectTable: e1702740  HandleCount:  18.
    Image: mytest.exe
kd> !dq 13b00380
# 13b00380 00000000`4ac31001 00000000`4a972001
# 13b00390 00000000`4ac33001 00000000`4aa70001
# 13b003a0 00000000`baf903c0 00000000`4a8a3001
# 13b003b0 00000000`4a6a4001 00000000`4a5a1001
# 13b003c0 00000000`baf90360 00000000`4a1a6001
# 13b003d0 00000000`4a227001 00000000`4a264001
# 13b003e0 00000000`baf90400 00000000`00000000
# 13b003f0 00000000`00000000 00000000`00000000
kd> !dq 4ac31000
# 4ac31000 00000000`4a92a067 00000000`4aa13067
# 4ac31010 00000000`4aaa9067 00000000`4ab2f067
# 4ac31020 00000000`00000000 00000000`00000000
# 4ac31030 00000000`00000000 00000000`00000000
# 4ac31040 00000000`00000000 00000000`00000000
# 4ac31050 00000000`00000000 00000000`00000000
# 4ac31060 00000000`00000000 00000000`00000000
# 4ac31070 00000000`00000000 00000000`00000000
kd> !dq 4aa13000+1d0*8
# 4aa13e80 80000000`4adc7067 00000000`00000000
# 4aa13e90 00000000`00000000 00000000`00000000
# 4aa13ea0 00000000`00000000 00000000`00000000
# 4aa13eb0 00000000`00000000 00000000`00000000
# 4aa13ec0 00000000`00000000 00000000`00000000
# 4aa13ed0 00000000`00000000 00000000`00000000
# 4aa13ee0 00000000`00000000 00000000`00000000
# 4aa13ef0 00000000`00000000 00000000`00000000

  然后是PAGE_EXECUTE_READWRITE

PROCESS 895c8da0  SessionId: 0  Cid: 068c    Peb: 7ffdf000  ParentCid: 0204
    DirBase: 13b00300  ObjectTable: e168ea78  HandleCount:  18.
    Image: mytest.exe
kd> !dq 13b00300
# 13b00300 00000000`4711c001 00000000`472dd001
# 13b00310 00000000`4735e001 00000000`4741b001
# 13b00320 00000000`319e5001 00000000`31ae6001
# 13b00330 00000000`31b27001 00000000`31b64001
# 13b00340 00000000`37e87001 00000000`37c88001
# 13b00350 00000000`37d89001 00000000`37c46001
# 13b00360 00000000`baf903a0 00000000`46a2a001
# 13b00370 00000000`46a6b001 00000000`46ba8001
kd> !dq 4711c000
# 4711c000 00000000`475d6067 00000000`472bf067
# 4711c010 00000000`47715067 00000000`4751b067
# 4711c020 00000000`00000000 00000000`00000000
# 4711c030 00000000`00000000 00000000`00000000
# 4711c040 00000000`00000000 00000000`00000000
# 4711c050 00000000`00000000 00000000`00000000
# 4711c060 00000000`00000000 00000000`00000000
# 4711c070 00000000`00000000 00000000`00000000
kd> !dq 472bf000+1d0*8
# 472bfe80 00000000`476b3067 00000000`00000000
# 472bfe90 00000000`00000000 00000000`00000000
# 472bfea0 00000000`00000000 00000000`00000000
# 472bfeb0 00000000`00000000 00000000`00000000
# 472bfec0 00000000`00000000 00000000`00000000
# 472bfed0 00000000`00000000 00000000`00000000
# 472bfee0 00000000`00000000 00000000`00000000
# 472bfef0 00000000`00000000 00000000`00000000

  可以看出PTE的最高位置為1,導致這個頁被認為是數據,不是代碼,故導致內存訪問錯誤,如果我們在訪問之前改了它,那么就能訪問它嗎?結果是可以的,我就不贅述了。


🔒 點擊查看代碼 🔒
#include "stdafx.h"
#include <iostream>
#include <windows.h>

char shellcode[]={
    0x6A,0x00,           //PUSH 0
    0x6A,0x00,           //PUSH 0
    0x6A, 0x00 ,        //PUSH 0
    0x6A ,0x00 ,        //PUSH 0
    0xB8, 0x00 ,0x00 ,0x00, 0x00 ,    //MOV EAX,0000
    0xFF, 0xD0 ,        //CALL EAX
    0xC3        //RET
};

int main(int argc, char* argv[])
{

    int msgbox=(int)MessageBoxW;
    *(int*)&shellcode[9]=msgbox;

    LPVOID scaddr = VirtualAlloc(NULL,1024,MEM_COMMIT,PAGE_READWRITE);
    memcpy(scaddr,shellcode,sizeof(shellcode));
    printf("Shellcode物理頁:%p\n",scaddr);

    _asm
    {
        mov eax,scaddr;
        call eax;
    }

    VirtualFree(scaddr,0,MEM_RELEASE);
    system("pause");
    return 0;
}

TLB

  CPU通過頁的方式對物理內存進行了,需要通過某種運算方式才能訪問真正的內存。但是,如果頻繁訪問某一個線性地址,每次都得通過推算到真正的物理地址然后進行讀寫操作,是不是挺浪費效率的?Intel就考慮到性能的問題,提供了TLB這一個機制,提供緩存提高讀寫效率。
  TLB的全稱為Translation Lookaside Buffer,它的結構如下:

LA(線性地址) PA(物理地址) ATTR(屬性) LRU(統計)
0x81010111 …… …… 1

  對於TLB,給出如下說明:
  1. ATTR(屬性):如果是2-9-9-12分頁,屬性是PDPEPDEPTE三個屬性共同決定的。如果是10-10-12分頁就是PDEPTE共同決定。
  2. 不同的CPU這個表的大小不一樣。
  3. 只要Cr3變了,TLB立馬刷新,一核一套TLB
  學到現在我們知道,操作系統的高2G映射基本不變,如果Cr3改了,TLB刷新重建高2G以上很浪費。所以PDEPTE中有個G標志位,如果G位為1刷新TLB時將不會刷新PDE/PTEG位為1的頁,當TLB滿了,根據統計信息將不常用的地址廢棄,最近最常用的保留。
  TLB有不同的種類,用於不同的緩存目的,它在X86體系里的實際應用最早是從Intel486CPU開始的,在X86體系的CPU里邊,一般都設有如下4組TLB
  第一組:緩存一般頁表(4K字節頁面)的指令頁表緩存:Instruction-TLB
  第二組:緩存一般頁表(4K字節頁面)的數據頁表緩存:Data-TLB
  第三組:緩存大尺寸頁表(2M/4M字節頁面)的指令頁表緩存:Instruction-TLB
  第四組:緩存大尺寸頁表(2M/4M字節頁面)的數據頁表緩存:Data-TLB

CPU緩存

  CPU緩存是位於CPU與物理內存之間的臨時存儲器,它的容量比內存小的多但是交換速度卻比內存要快得多。它可以做的很大,但不是TLB,它們有很大的不同。TLB存的是線性地址與物理地址的對應關系,CPU緩存存的是物理地址與內容對應關系。
  更多的細節請參考白皮書的Chapter 11 Memory Cache Control,本篇教程主要是針對內核安全層面,就不再贅述了。

PWT 與 PCD

  PWT全稱為Page Write ThroughPWT = 1時,寫Cache的時候也要將數據寫入內存中。
  PCD全稱為Page Cache DisablePCD = 1時,禁止某個頁寫入緩存,直接寫內存。比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再緩存了。

本節練習

本節的答案將會在下一節進行講解,務必把本節練習做完后看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。

  俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到后面,不做練習的話容易夾生了,開始還明白,后來就真的一點都不明白了。本節練習很少,請保質保量的完成。

1️⃣ 在10-10-12分頁模式下體會TLB的存在。要求:通過代碼掛物理頁,不能通過Windbg掛。原物理頁掛完寫入值讀取,然后把地址換物理頁繼續讀取,看看值是否發生變化。然后用INVLPG指令之后再看看值是否變化。

下一篇

  保護模式篇——中斷與異常和控制寄存器


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM