保護模式篇——段描述符與段選擇子
- 2021 年 9 月 20 日
- 筆記
- 羽夏看Win系統內核
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎回饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並聲明我的個人資訊和本人部落格地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?搭建好環境了嗎?上一節教程學會了嗎?沒有的話就不要繼續了。
🔒 華麗的分割線 🔒
CPU分級
如果要講段描述符與段選擇子,先介紹CPU
分級的概念。數值上越小,許可權越大。如果低許可權訪問高許可權的東西,會導致失敗。0環
被內核使用,雖然1環
和2環
存在,但Windows
只用了3環
。注意在學習保護模式是時候不要把作業系統的概念扯進去,還沒到作業系統層面。 CPU分級示意圖如下:
GDT 與 LDT
GDT
是全局描述符表。LDT
為局部描述符表,但Windows
並沒有使用它,故不再介紹,感興趣請查詢Intel白皮書。當我們執行類似MOV DS,AX
指令時,CPU
會查表,根據AX
的值來決定查找GDT
還是LDT
,並找到對應的段描述符。段描述符將會在後面部分進行介紹。
GDT表
存在於記憶體之中。CPU
要想找到它,就必須知道它的位置。於是乎CPU
有一個暫存器。它被稱之為GDTR
,存儲了GDT表的位置和大小,是一個48位的暫存器,用C語言表示如下:
struct GDTR
{
DWORD GDTBase; //GDT表的地址
SHORT limit; //GDT表的大小
}
那麼,我們如何通過WinDbg
來獲取GDT
的地址、大小和GDT
表裡的成員呢?首先提一句,段描述符大小為64位。有關其操作見下圖:
r gdtr
指令表示讀取GDT表的地址;r gdtl
指令表示讀取GDT的大小;dq 8003f000 l20
指令表示從0x8003f000
地址(即為GDT表的地址)讀取0x20
個64位數據,如果沒有l20
,默認0x10
個。這些都是以後常用的指令,需要熟練掌握。
段選擇子
段選擇子結構簡單,那我先介紹它。它是一個16位的描述符,指向了定義該段的段描述符(段描述符比較複雜,後面將會完整介紹)。段選擇子結構如下圖所示:
它的成員解釋如下:
- RPL:請求特權級別,通俗的講我用什麼許可權來請求。
- TI:TI=0時,查GDT表;TI=1時,查LDT表。
- Index:處理器將索引值乘以8在加上GDT或者LDT的基地址,就是要載入的段描述符。
是不是很簡單?該結構一定要牢記於心,後面將給出練習訓練。
段描述符
既然提到段描述符,那我來介紹一下它的結構如下圖所示:
段描述符有很多成員,它的成員將會在下面詳細介紹,學習的時候一定要按照我介紹的順序進行學習:
P位
P = 1
段描述符有效,P = 0
段描述符無效。
Base
Base
被分成了三個部分,從圖可知:Base
的低16位被放到了段描述符的低四個位元組,高16位被均分到段描述符的高四個位元組的頭和尾。把它們依次拼接起來就是完整的Base
。
Limit
由圖可知,把段描述符中所有的Limit拼接起來就只有20位。上一節教程說它有32位的Limit。那就是要看G位
了。
G位
如果G = 0
,說明段描述符中的Limit的單位是位元組,段長度Limit
範圍可從1B~1MB,即在20位的前面補3個0即可;如果G = 1
,說明段描述符中的Limit的單位是位元組為4KB,即段長度Limit範圍可從4KB~4GB,在20位的後面補充FFF
即可。舉個例子,如果Limit拼接後的為FFFFF
,如果G為0則為000FFFFF
,反之為FFFFFFF
。
S位
S = 1
程式碼段或者數據段描述符,S = 0
系統段描述符。
TYPE域
TYPE域
是比較複雜的成員,它表示的含義受G位
的影響。
當S位為1時
此時段描述符表示的是程式碼段或者數據段,如下圖所示:
對於表格中Type域的屬性和含義,如下表格所示:
屬性 | 含義 | 屬性 | 含義 |
---|---|---|---|
A | 訪問位 | E | 向下擴展位 |
R | 可讀位 | W | 可寫位 |
C | 一致位 |
對於比較特殊的屬性,我們將進一步介紹:
C位
C = 1
:一致程式碼段;C = 0
:非一致程式碼段。什麼是一致程式碼段,什麼是非一致程式碼段,將在後面的教程進行介紹。
E位
什麼是向下拓展位,我們以fs
為例來看一下如下示意圖:
左邊表示向上拓展,右邊是向下拓展。即向上拓展base
到base+limit
之間區域有效,其餘無效;向下拓展base
到base+limit
之間的區域無效,其餘有效。這個位針對數據段有效。
當S位為0時
此時段描述符表示的是系統段,系統段有很多種,將會在後面的教程進行詳細講解。Type域每一個數值的含義如下圖所示:
DB位
DB位
對不同的段具有不同的影響,情況如下:
1️⃣ 對CS段的影響
D = 1
採用32位定址方式,D = 0
採用16位定址方式。
2️⃣ 對SS段的影響
D = 1
隱式堆棧訪問指令(如:PUSH POP CALL)使用32位堆棧指針暫存器ESP
,D = 0
隱式堆棧訪問指令(如:PUSH POP CALL)使用16位堆棧指針暫存器SP
。
3️⃣ 向下拓展的數據段
D = 1
段上線為4GB
,D = 0
段上線為64KB
。至於是什麼意思,我們來看下面一張圖。
紅色表示向下拓展能定址的範圍。可以看出,如果D = 0
,就算原來能定址4GB
,因為DB位
的限制導致最大範圍是64KB
。
DPL
DPL(Descriptor Privilege Level),即描述符特權級別,規定了訪問該段所需要的特權級別是什麼。如果通俗的理解,就是:如果你想訪問我,那麼你應該具備什麼許可權。
AVL
AVL指示是否可供系統軟體使用,由作業系統來使用,CPU
並不使用它。
載入段描述符至段暫存器
除了MOV
指令,我們還可以使用LES
、LSS
、LDS
、LFS
、LGS
指令修改暫存器。CS
不能通過上述的指令進行修改,CS
為程式碼段,CS
的改變會導致EIP
的改變,要改CS
,必須要保證CS
與EIP
同時改,後面會講解。
給一個程式碼模板,具體如下,作為本節的一個作業。
char buffer[6];
//自己構造一個段選擇子
//自己自行賦值,你構造的的段描述符位置不一樣,段選擇子就不一樣
_asm
{
les ecx,fword ptr ds:[buffer] //高2個位元組給es,低四個位元組給ecx
}
本節練習
本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習比較多,請保質保量的完成,不得使用任何拆分工具。
如下是從虛擬機讀取的GDT表的前18個段描述符,下面的實驗均按照此進行練習。
8003f000 00000000`00000000 00cf9b00`0000ffff
8003f010 00cf9300`0000ffff 00cffb00`0000ffff
8003f020 00cff300`0000ffff 80008b04`200020ab
8003f030 ffc093df`f0000001 0040f300`00000fff
8003f040 0000f200`0400ffff 00000000`00000000
8003f050 80008955`22000068 80008955`22680068
8003f060 00009302`2f40ffff 0000920b`80003fff
8003f070 ff0092ff`700003ff 80009a40`0000ffff
8003f080 80009240`0000ffff 00009200`00000000
1️⃣ 練習讀取GDT表的位置和長度,並顯示GDT表前48個段描述符。
2️⃣ 在給定的段描述符中,進行練習(至少10個)。
3️⃣ 拆分如下段選擇子。
002B 0023 0010 001B 003B
4️⃣ 快速辨別給定段描述符是否可用以及段基址、段長(至少10個)。
5️⃣ 從給定段描述符,請按照下面的要求進行練習(全部):
- 快速找出所有數據段,並分析該段屬性:只讀、已訪問、可讀可寫、拓展方向
- 快速找出所有程式碼段,並分析該段屬性:只執行、可讀可執行、已訪問、一致程式碼
- 快速找出所有系統段,並分析屬性
6️⃣ 自行構造段選擇子和段描述符,並用載入段描述符至段暫存器
中的程式碼模板和要求取得成功。如果有時間同樣把LSS
、LDS
、LFS
、LGS
的實驗也類比做了。
7️⃣ 如何在調試器中快速判斷程式在幾環許可權。
8️⃣ 自學修改GDT表的相關知識,並思考如下問題。
r gdtr
dq 8003f090 00cffb00`0000ffff
r gdtr
8003f090
是GDT表中的一個段描述符的地址,更改後發現並沒有更改,請思考為什麼會這樣。
下一篇
保護模式篇——段許可權檢查