保護模式篇——PAE分頁
- 2021 年 10 月 22 日
- 筆記
- 羽夏看Win系統內核
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章後面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?上一節教程學會了嗎?上一節課的練習做了嗎?沒有的話就不要繼續了。
🔒 華麗的分割線 🔒
練習及參考
本次答案均為參考,可以與我的答案不一致,但必須成功通過。
1️⃣ 拆兩個進程的4GB
物理頁。
🔒 點擊查看答案 🔒
自己拆吧,也不是真讓你把所有的都拆了。這玩意建議還是寫個驅動來進行拆解。不過我的教程沒寫,之後才會講解。故拆幾個比較有特點的就行了,之後學了驅動後可以回來再把這道題給完整地做了。
首先討論我們怎麼拆,一個物理頁有4KB
的大小,如果線性地址在同一個物理頁,那麼它的高20位一定是相同的。故如果完整的拆完,我們需要拆0x1000 * n
線性地址。怎麼拆已經給你說明白了。
需要拆解的線性地址類型:0x0 – 0x10000 和 0x10000 – 0x7FFFFFFF 和 高2G三部分,每個部分拆解2-3個即可。這裡就不拆了。
2️⃣ 定義一個只讀類型的變量,再另一個線性地址指向相同的物理頁,通過修改PDE
/PTE
屬性,實現可寫。
🔒 點擊查看答案 🔒
這題目說實話還是有一些坑,主要是獨自編寫驗證代碼上。可以點擊下方「查看代碼」進行查看。
首先運行我們的代碼,報內存訪問錯誤,這個是正常現象,因為它在所謂的常量區。
先讓它跑起來,看到常量所在的線性地址,如下圖所示:
然後我們拆分線性地址,為了圖省事,可以用計算器或者自己寫個工具。如下圖所示:
然後轉到WinDbg
中,找到它的進程結構體,找到CR3
:
根據10-10-12
分頁結構進行查看,最終看到如下圖所示結果:
最後發現是因PTE
屬性限制導致數據無法修改,故用!ed 41795088 410e4027
修改它,使它具有可寫屬性。
然後我們就能成功的訪問常量只讀地址了:
🔒 點擊查看代碼 🔒
#include "stdafx.h"
const int test = 5;
int main(int argc, char* argv[])
{
int* tmp=(int*)&test;
printf("%p\n",tmp);
*tmp=8;
printf("%d\n",*tmp);
return 0;
}
3️⃣ 分析0x8043F00C
線性地址的PDE
屬性。
🔒 點擊查看答案 🔒
在虛擬機打開一個notepad
進程,然後轉到Windbg
暫停虛擬機找到它的CR3
(我的是0x1b262000
)。然後輸入!dd 1b262000+0x201*4
找到該線性地址對應的PDE
,值為004001e3
,故該PDE
為一個大頁,有效可寫,並為僅超級用戶可用,是全局的,且被訪問過和寫過。
4️⃣ 修改一個高2G線性地址的PDE
/PTE
屬性,實現Ring3
可讀。
🔒 點擊查看答案 🔒
既然是做了第三題了,那我們直接拿第三題給的線性地址開刀好了。首先必須在合適的地方下斷點,運行我們的代碼,獲取的CR3
是4a818000
,故!dd 4a818000+0x201*4
得到PDE
後值為004001e3
,但為了更好的實驗,故把它指向的物理頁的偏移的值改為0x1234
。那麼如何改呢?這個是個大頁,故後22
位直接是物理頁的偏移。故!ed 0043F00C 1234
即可。
然後我們繼續進行檢驗,如下圖所示:
4660
就是16進制的0x1234
,故實驗成功。
🔒 點擊查看代碼 🔒
#include "stdafx.h"
#include <iostream>
int main(int argc, char* argv[])
{
int* test=(int*)0x8043F00C;
printf("讀取到值了:%d",*test);
system("pause");
return 0;
}
5️⃣在0
線性地址掛上物理頁並執行shellcode
調用MessageBox
。
🔒 點擊查看答案 🔒
這道題還是有一點坑,不過坑不太深,自己能跳出來。代碼我提供了且有注釋,自己打開看看。
通過查看0 地址
不能訪問是因為沒有正確的PTE
,如果掛上正確的PTE
,那麼這個地址就可以訪問了。那我們開始用代碼進行試驗。首先在合適的地方下斷點,運行我們的代碼,獲取的CR3
是3daa3000
,且申請的一個物理頁的地址是0x3D0000
。故按照分頁模式找到物理頁的PTE
,值為3db54067
。然後把它填到0 地址
的地方。
然後我們繼續進行檢驗,如下圖所示:
我們成功彈出了一個信息框,故實驗成功。
🔒 點擊查看代碼 🔒
#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;
//我們並不能直接把 shellcode 的 PTE 掛到 0 地址上。
//因為還有偏移,需要單獨申請一個獨立的物理頁。
LPVOID scaddr = VirtualAlloc(NULL,1024,MEM_COMMIT,PAGE_READWRITE);
memcpy(scaddr,shellcode,sizeof(shellcode));
printf("Shellcode物理頁:%p\n",scaddr);
//下斷點,掛物理頁
_asm
{
mov eax,0;
call eax;
}
system("pause");
return 0;
}
6️⃣ 逆向分析MmIsAddressValid
函數。
🔒 點擊查看答案 🔒
本題目主要目的是為了如何查詢PDE
和PTE
。逆向參考結果如下:
PAE 分頁
PAE
分頁是啥,其實他就是2-9-9-12
分頁的英文縮寫。為什麼要有2-9-9-12
分頁,其實還是物理頁不夠用了,需要擴展。但想要足夠的物理頁,位數在那裡,你想大也大不了。那麼我就需要擴展物理頁地址的位數,於是乎2-9-9-12
分頁誕生了,它整體分頁的結構如下:
與10-10-12
分頁不同的地方就是,多了一層名為頁目錄指針表
的東西,英文縮寫為PDPTT
。每個PDE
和PTE
被擴展為8個位元組,物理地址描述的位數擴展為24位
,故可以描述更多的物理頁,但個數減半,變成了512個。下面詳細查看它們的結構。
首先看PDPTT
的結構。由2-9-9-12
的2
可知第一部分由兩位二進制組成,那麼最多有4種結果。也就是為什麼有五個成員,它的結構如下圖所示:
然後是`PDE`,既然學過了`10-10-12`分頁,直接看下面的結構示意圖吧:
非大頁
大頁
然後是PTE
,同理不多說了:
我們之前做一道作業題目知道,我寫的shellcode
寫到一個頁上,它並沒有執行權限,但它不是代碼,仍然可以被我執行。為了彌補這個漏洞,Intel
給我們補了一個硬件層面上的漏洞,它是一個位,處於PDE
和PTE
的最高位,如下圖所示:
如果最高位是1
,說明被保護。如果這個是數據,且這個X
位被置為1
,則會被報出異常不能執行。反之,和正常的10-10-12
分頁沒什麼兩樣。
一個進程的線性地址仍是4GB
的線性空間,有再多的物理頁有啥用呢?在10-10-12
分頁下,假設進程一啟動,就把所有的物理頁都掛上,且沒有任何交換。那麼只能啟動一個;如果在2-9-9-12
分頁下,同樣的情況,它可以啟動4個進程。這個就是2-9-9-12
分頁的意義。
Windows
也提供了基於硬件層面X位
的保護,如下圖所示:
可以看出你自己寫的普通程序壓根和這個位無緣,但是還以啟用的。但是有些打開之後發現提出不支持基於硬件的保護,那是因為虛擬機沒開這個選項,如下圖所示,轉中如下圖選項即可:
練習
由於本節內容和
10-10-12
分頁相似,答案不會提供,自己實現,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習比較多,請保質保量的完成。
1️⃣ 定義一個只讀類型的變量,再另一個線性地址指向相同的物理頁,通過修改PDE
/PTE
屬性,實現可寫。
2️⃣ 自己實驗有和沒有DEP
保護的程序,看看效果。(本題將在下一篇提供參考)
3️⃣ 修改一個高2G線性地址的PDE
/PTE
屬性,實現Ring3
可讀。
4️⃣ 在0
線性地址掛上物理頁並執行shellcode
調用MessageBox
。
5️⃣ 逆向分析MmIsAddressValid
函數。
下一篇
保護模式篇——TLB與CPU緩存