保護模式篇——段權限檢查與代碼跨段跳轉
- 2021 年 9 月 21 日
- 筆記
- 羽夏看Win系統內核
寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章後面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。
看此教程之前,問幾個問題,基礎知識儲備好了嗎?上一節教程學會了嗎?上一節課的練習做了嗎?沒有的話就不要繼續了。
🔒 華麗的分割線 🔒
練習及參考
如下是從虛擬機讀取的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個段描述符。
🔒 點擊查看答案 🔒
r gdtr //讀取GDT表地址
r gdtl //讀取GDT表的大小
dq 8003f000 l30 //0x8003f000為GDT表的地址
2️⃣ 在給定的段描述符中,進行拆分練習(至少10個)。
🔒 點擊查看答案 🔒
本答案僅提供一個樣例,其餘自行校驗:
對於段描述符 0040f300`00000fff :
P:1
S:1
G:0
Type:3
DB:1
Base:0
Limit:FFF
DPL:3
3️⃣ 拆分如下段選擇子。
002B 0023 0010 001B 003B
🔒 點擊查看答案 🔒
002B:
RPL:3
TI:0
Index:5
0023:
RPL:3
TI:0
Index:4
0010:
RPL:0
TI:0
Index:2
001B:
RPL:3
TI:0
Index:3
003B:
RPL:3
TI:0
Index:7
4️⃣ 快速辨別給定段描述符是否可用以及段基址、段長(至少10個)。
5️⃣ 從給定段描述符,請按照下面的要求進行練習(全部):
- 快速找出所有數據段,並分析該段屬性:只讀、已訪問、可讀可寫、拓展方向
- 快速找出所有代碼段,並分析該段屬性:只執行、可讀可執行、已訪問、一致代碼
- 快速找出所有系統段,並分析屬性
🔒 技巧 🔒
我拿出一個段描述符為例進行介紹,學會後可以自行找別的段描述符練習,如下圖所示:
表示不同含義的位或十六進制數我都加以區分。由於直接標註在段描述符上非黑色數字通過結構示意圖直接能對應上,故不再詳細講解。我們先看c
,它的二進制就是1100
,這個位置大於8
就說明G位有效。同理看9
,這個位置大於8
就說明段描述符有效;為奇數說明為代碼段或者數據段,反之為系統段。其他的位不太常用於快速判斷,規律可自行總結。
🔒 點擊查看答案 🔒
1. 所有的數據段,分析屬性自行查表:
00cf9300`0000ffff
00cff300`0000ffff
ffc093df`f0000001
0040f300`00000fff
0000f200`0400ffff
00009302`2f40ffff
0000920b`80003fff
ff0092ff`700003ff
80009240`0000ffff
2. 所有的代碼段,分析屬性自行查表:
00cf9b00`0000ffff
00cffb00`0000ffff
80009a40`0000ffff
2. 所有的系統段,分析屬性自行查表:
80008b04`200020ab
80008955`22000068
80008955`22680068
6️⃣ 自行構造段選擇子和段描述符,並用加載段描述符至段寄存器
中的代碼模板和要求取得成功。如果有時間同樣把LSS
、LDS
、LFS
、LGS
的實驗也類比做了。
本題答案不唯一,合理並能夠正常運行即可,注意小端存儲。此題目有一個坑,自行踩坑。
🔒 點擊查看答案 🔒
#include "stdafx.h"
int main(int argc, char* argv[])
{
//索引為3的短描述符為:00cffb00`0000ffff
char buffer[6]={0xff,0xff,0xff,0xff,0x18,0x00};
_asm
{
les ecx,fword ptr ds:[buffer] //高2個位元組給es,低四個位元組給ecx
}
return 0;
}
7️⃣ 如何在調試器中快速判斷程序在幾環權限。
🔒 點擊查看答案 🔒
根據CS或者SS,因為它存儲的是段選擇子,可以通過低兩位直接判斷。
8️⃣ 自學修改GDT表的相關知識,並思考如下問題。
r gdtr
dq 8003f090 00cffb00`0000ffff
r gdtr
8003f090
是GDT表中的一個段描述符的地址,更改後發現並沒有更改,請思考為什麼會這樣。
🔒 點擊查看答案 🔒
你看我之前寫讀取GDT表為什麼用地址了嗎?你再用地址<code>dq</code>以下看看。
CPL/RPL/DPL
- CPL:CPU當前的權限級別
- DPL:如果你想訪問我,你應該具備什麼樣的權限(CPL)
- RPL:用什麼權限去訪問一個段
RPL存在的意義:
舉個例子,我們本可以用讀寫
的權限去打開一個文件,但為了避免出錯,有些時候我們使用只讀
的權限去打開。
一致代碼段與非一致代碼段
對於一致代碼段,也稱為共享段:
- 特權級高的程序不允許訪問特權級低的數據:核心態不允許訪問用戶態的數據
- 特權級低的程序可以訪問到特權級高的數據,但特權級不會改變:用戶態還是用戶態
對於非一致代碼段:
- 只允許同級訪問
- 絕對禁止不同級別的訪問:核心態不是用戶態,用戶態也不是核心態
數據段的權限檢查
數值上,CPL
<=DPL
且RPL
<=DPL
。同時滿足上述條件才能通過。
代碼段的權限檢查
下面的比較都是數值上的比較:
- 如果是非一致代碼段,要求:
CPL
==DPL
且RPL
<=DPL
- 如果是一致代碼段,要求:
CPL
>=DPL
代碼跨段基礎
代碼跨段本質就是修改CS段寄存器。前面的教程介紹過段寄存器讀寫,除CS外,其他的段寄存器都可以通過MOV
/LES
/LSS
/LDS
/LFS
/LGS
指令進行修改。但是CS
為什麼不可以直接修改呢?CS
的改變意味着EIP
的改變,改變CS
的同時必須修改EIP
,故我們無法使用上面的指令來進行修改,這個也是CPU不允許的。
代碼間的段間跳轉
段間跳轉,有2種情況,即要跳轉的段是一致代碼段還是非一致代碼段,它們不同做的權限檢查就不同。
同時修改CS
與EIP
的指令如下:JMP FAR
/CALL FAR
/RETF
/INT
/IRETED
本篇只介紹段間跳轉,故只使用JMP FAR
,即為長跳轉。下面我舉個示例來進行講解:
CPU
如何執行這行代碼JMP 0x20:0x004183D7
?
1️⃣ 段選擇子拆分
0x20
對應二進制形式:0000 0000 0010 0000
- 解析結果:
- RPL = 0
- TI = 0
- Index = 4
2️⃣ 查表得到段描述符
TI=0
所以查GDT表,Index=4
找到對應的段描述符。注意四種情況可以跳轉:代碼段、調用門、TSS任務段、任務門。後面的幾種將會在以後的教程詳細講解。
3️⃣ 權限檢查
請參考本節的代碼段的權限檢查
4️⃣ 加載段描述符
通過上面的權限檢查後,CPU會將段描述符加載到CS段寄存器中。
5️⃣ 代碼執行
CPU
將CS.Base + Offset
的值寫入EIP
然後跳轉到將要執行的CS:EIP
處的代碼,段間跳轉結束。
直接對代碼段進行
JMP
或者CALL
的操作,無論目標是一致代碼段還是非一致代碼段,CPL
都不會發生改變。如果要提升CPL
的權限,只能通過調用門。
本節練習
本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。
俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習比只有很少,請保質保量的完成。
1️⃣ 記住代碼段間跳轉的執行流程。
2️⃣ 自己實現一致代碼段的段間跳轉。
3️⃣ 自己實現非一致代碼段的段間跳轉。
下一篇
保護模式篇——調用門、中斷門、陷阱門