保護模式篇——分頁基礎


寫在前面

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

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

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


🔒 華麗的分割線 🔒


前言

  在講解分頁基礎之前,我們先大體了解CPU是如何在保護模式下訪問數據的,如下圖所示:

  比如我們執行mov eax,ds:[0x12345678]這句匯編指令的時候,0x12345678這個線性地址會傳遞給CPU,先查詢TLB緩存有沒有,有的話直接取出來返回;如果沒有,經過MMU(內存管理單元)處理得到物理地址,通過固定的分頁模式直接找到,取出數據返回。

分頁

  前面的教程講解了段的機制,接下來將介紹頁的機制。CPU為了方便管理物理內存,按照頁的方式進行管理內存。可用的所有內存可以類比為一本書,而所有的內存被分為這本書的一個頁。對於32位來說,它有10-10-12分頁和2-9-9-12分頁。其中10-10-12分頁最為簡單,故拿其作為詳細講解,作為分頁講解的基礎。
  我們都了解一個進程都有4GB的虛擬地址空間,它們並不是真正的地址,而是個索引。它通過某種方式進行轉換,從而指向真正的物理地址,示意圖如下所示:

  而虛擬地址也被稱作線性地址。舉個例子,比如某個進程里面我想讀取一個0x12345678,它就是線性地址,通過一些轉換,找到了對應的物理地址0x10101010,如下圖所示:

  每個進程都有一個CR3,准確的說是都一個CR3的值。CR3本身是個寄存器,一核一套。CR3里面放的是一個真正的物理地址,指向一個物理頁,一共4096字節,如下圖所示:

  對於10-10-12分頁來說,線性地址對應的物理地址是有對應關系的,它被分成了三個部分,每個部分都有它具體的含義。線性地址分配的結構如下圖所示:

  第一個部分指的是PDEPDT的索引,第二部分是PTEPTT的索引,第三個部分是在PTE指向的物理頁的偏移。PDT被稱為頁目錄表,PTT被稱為頁表。PDEPTE分別是它們的成員,大小為4個字節。我們接下來將詳細介紹每一個部分是咋用的。

  直接純理論的講解挺抽象的,我們先通過一個實現初步探測一下它們的存在。注意在學習之前需要對操作系統的調試設置進行修改,因為系統默認的是2-9-9-12分頁。如下圖所示修改並重啟操作系統即可啟用10-10-12分頁:

實驗探測

  這次就需要我們的CheatEngine,它是一個強大的內存搜索工具。我們打開一個記事本,然后打開CheatEngine 6.3,如下圖所示:

  然后我們按照下圖所示來打開notepad進程:

  然后在記事本中寫入一個字符串This is a test,然后在CheatEngine搜索這個字符串,注意選中Unicode,否則字符串搜不到。

  然后隨便改一下記事本最后一個字符串的字母,如下圖所示,即可定位到真正的存儲記事本填寫內容的線性地址。

  找到線性地址后,打開WinDbg,找到記事本的CR3,如下圖所示:

  我們按字節讀取線性地址的內存時,在WinDbg的指令是dd [地址]。如果是物理地址的內存的話,需要在前面加一個英文嘆號,即為!dd [地址],查詢內容的流程如下圖所示:

10-10-12 分頁整體結構

  通過實驗我們了解了它們的結構,接下來將詳細介紹了。根據實驗結果的體驗,可以給出如下圖:

  分頁並不是由操作系統決定的,而是由CPU決定的。只是操作系統遵守了CPU的約定來實現的。物理頁是什么?物理頁是操作系統對可用的物理內存的抽象,按照4KB的大小進行管理(Intel是按照這個值做的,別的CPU就不清楚了),和真實硬件層面上的內存有一層的映射關系,這個不是保護模式的范疇,故不介紹。

PDE 與 PTE

  前面我們簡單了解PDEPTE,接下來將學習它們的屬性結構,結構如下:

P 位

  表示PDE或者PTE是否有效,如果有效為1,反之為0

R/W 位

  如果R/W = 0,表示是只讀的,反之為可讀可寫。

U/S 位

  如果U/S = 0,則為特權用戶(super user),即非3環權限。反之,則為普通用戶,即為3環權限。

PS位

  這個位只對PDE有意義。如果PS == 1,則PDE直接指向物理頁,不再指向PTE,低22位是頁內偏移。它的大小為4MB,俗稱“大頁”。

A 位

  是否被訪問,即是否被讀或者寫過,如果被訪問過則置1

D 位

  臟位,指示是否被寫過。若沒有被寫過為0,被寫過為1

注意,下面的三個位的講解將涉及 TLB 和控制寄存器相關知識,為了保證文章的完整性,故先介紹。之后將會詳細講解。

G 位

  表示是否為全局頁。它的作用是什么呢?舉個例子,操作系統的進程的高2G映射基本不變,如果Cr3改了,TLB刷新重建高2G以上很浪費。所以PDEPTE中有個G位,如果為1,刷新TLB時將不會刷新它指向的頁。

PWT 位

  當PWT = 1,寫緩存的時候也要將數據寫入內存中。

PCD 位

  當PCD = 1時,禁止某個頁寫入緩存,直接寫內存。比如,做頁表用的頁,已經存儲在TLB中了,可能不需要再緩存了。

注意事項

  • PTE可以沒有物理頁,且只能對應一個物理頁。
  • 多個PTE也可以指向同一個物理頁。
  • PDEPTE重合的屬性共同決定着最終物理頁的屬性。比如P位,如果有一個是0,那么最終的物理頁就是無效的。但是PDEPTE它們的屬性的影響范圍是不一樣的。數值上:物理頁的屬性 = PDE屬性 & PTE屬性。

頁目錄表基址與頁表基址

  在學習本部分之前,請把練習的第一題做一下,以加深對此的印象。
  如果系統要保證某個線性地址是有效的,必須為其填充正確的PDEPTE,如果我們想填充PDEPTE那么必須能夠訪問。有的人會想,直接拿CR3去填寫就行了,還需要頁目錄表基址干嘛?這里我強調一下:操作系統只能用線性地址,不能用物理地址CR3存儲的是物理地址,這個是給CPU看的,不是給操作系統看的。操作系統訪問它就必須知道它的線性地址才行。CPU可不幫我們掛物理頁,它做不到這點,只能提供要求標准,而操作系統按照標准進行辦事。於是乎頁目錄表基址與頁表基址這兩個東西就出現了。
  通過頁目錄表基址,操作系統可以幫我們程序掛上正確的PDE,通過頁表基址掛上正確的PTE,然后指向正確的物理頁。
  先說一下頁目錄表基址,我們先拆分一下這個線性地址:0xC0300000。請讀者先自行拆分,並用虛擬機查看物理內存后,再查看下面的結果。


🔒 華麗的分割線 🔒


  我在虛擬機啟動了一個notepad進程,中斷操作系統轉到Windbg中。查看進程可以得到它的CR3。然后我們查看它的物理地址,即可看到它指向的PDT。拆一下該線性地址,我們可以得到如下結果:

PDI -- 11 0000 0000
PTI -- 11 0000 0000
物理頁偏移 -- 0x000

  為什么查詢的后面加0xC00,而不是0x300呢。是因為PDEPTE的大小都是4個字節,乘上4后就是0xC00了。最后查看到,到最后內容又指向了自己。即0xC0300000存儲的就是帶PTE屬性的CR3。我們根據當前所學的概念可以重新定義10-10-12分頁的結構框圖:

  同理,操作系統也需要頁表基址。如果你老老實實地做了第一道題,你就知道我們找PDT的時候就已經發現了指向PTT的線性地址了,那就是0xC0000000。具體實驗我就不再贅述了,最終我們得到下面的結構圖:

  好了,我們思考一下有了0xC03000000xC0000000能做什么?如果我們掌握了這兩個地址,就掌握了一個進程所有的物理內存讀寫權限。尋找PDE和PTE的公式總結如下:

  • 訪問頁目錄表的公式:0xC0300000 + PDI * 4
  • 訪問頁表的公式:0xC0000000 + PDI * 4096 + PTI * 4

練習

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

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

1️⃣ 拆兩個進程的4GB物理頁。(建議閱讀頁目錄表基址與頁表基址這個部分前完成)
2️⃣ 定義一個只讀類型的變量,再另一個線性地址指向相同的物理頁,通過修改PDE/PTE屬性,實現可寫。
3️⃣ 分析0x8043F00C線性地址的PDE屬性。
4️⃣ 修改一個高2G線性地址的PDE/PTE屬性,實現Ring3可讀。
5️⃣ 在0線性地址掛上物理頁並執行shellcode調用MessageBox
6️⃣ 逆向分析MmIsAddressValid函數。

下一篇

  保護模式篇——PAE分頁


免責聲明!

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



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