寫在前面
此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看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快取