寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看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_EFER
的NXE
位為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
分頁,屬性是PDPE
、PDE
、PTE
三個屬性共同決定的。如果是10-10-12
分頁就是PDE
和PTE
共同決定。
2. 不同的CPU
這個表的大小不一樣。
3. 只要Cr3
變了,TLB
立馬刷新,一核一套TLB
。
學到現在我們知道,操作系統的高2G映射基本不變,如果Cr3
改了,TLB
刷新重建高2G以上很浪費。所以PDE
和PTE
中有個G
標志位,如果G
位為1刷新TLB
時將不會刷新PDE/PTE
的G
位為1的頁,當TLB
滿了,根據統計信息將不常用的地址廢棄,最近最常用的保留。
TLB
有不同的種類,用於不同的緩存目的,它在X86
體系里的實際應用最早是從Intel
的486CPU
開始的,在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 Through
,PWT = 1
時,寫Cache
的時候也要將數據寫入內存中。
PCD
全稱為Page Cache Disable
,PCD = 1
時,禁止某個頁寫入緩存,直接寫內存。比如,做頁表用的頁,已經存儲在TLB
中了,可能不需要再緩存了。
本節練習
本節的答案將會在下一節進行講解,務必把本節練習做完后看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到后面,不做練習的話容易夾生了,開始還明白,后來就真的一點都不明白了。本節練習很少,請保質保量的完成。
1️⃣ 在10-10-12
分頁模式下體會TLB
的存在。要求:通過代碼掛物理頁,不能通過Windbg
掛。原物理頁掛完寫入值讀取,然后把地址換物理頁繼續讀取,看看值是否發生變化。然后用INVLPG
指令之后再看看值是否變化。