Windows保護模式學習筆記(五)—— 任務段&任務門
-
- 要點回顧
- 任務段
-
- TSS (Task-state segment )
- TR段寄存器
-
- TR段寄存器的讀寫
- TSS段描述符
- 實驗:加載自定義TSS
-
- 第一步:擷取必要參數
- 第二步:構造TSS段描述符
- 第三步:将TSS段描述符寫入GDT表
- 第四步:解除中斷,繼續執行代碼
- 任務門
-
- 任務門執行過程
- 實驗:通過任務門切換TSS
-
- 第一步:構造任務門描述符
- 第二步:将任務門描述符寫入IDT表
- 第三步:構造TSS段描述符
- 第四步:運作代碼
要點回顧
- 在調用門、中斷門與陷阱門中,一旦出現權限切換,那麼就會有堆棧的切換;而且,由于CS的CPL發生改變,也導緻了SS也必須要切換
-
思考:切換時,會有新的ESP和SS(CS是由中斷門或者調用門指定)這2個值從哪裡來的呢?
答案:TSS (Task-state segment ):任務狀态段
任務段
TSS (Task-state segment )
描述:
TSS是一塊記憶體
大小:104位元組
TSS存儲了一堆寄存器的值
TSS結構圖:
TSS的作用:
- Intel的設計思想:通過使用TSS以達到任務(線程)的切換
- 作業系統的設計思想:與Intel設計思想不同的是,Windows并沒有根據Intel的設計思想來做,甚至Linux也沒有這樣做
TSS的本質:
- 不要把TSS與"任務切換"聯系到一起
- TSS的意義就在于可以同時換掉"一堆"寄存器
CPU通過TR段寄存器尋找TSS
TR段寄存器
描述:
TR寄存器的值是當作業系統啟動時,從TSS段描述符中加載出來的,TSS段描述符在GDT表中
TR.Base = TSS起始位址
TR.Limit = TSS大小
TR段寄存器的讀寫
一、将TSS段描述符加載到TR寄存器
指令:
LTR
說明:
- 用LTR指令去裝載的話 僅僅是改變TR寄存器的值(96位)
- 并沒有真正改變TSS
- LTR指令隻能在系統層使用
- 加載後TSS段描述符的狀态位會發生改變
二、讀TR寄存器
指令:
STR
說明:如果用STR去讀的話,隻讀了TR的16位,也就是段選擇子
TSS段描述符
描述:
TSS段描述符是系統段描述符中的一種
結構圖:
Type = 二進制1001:說明該TSS段描述符未被加載到TR段寄存器中
Type = 二進制1011:說明該TSS段描述符已被加載到TR段寄存器中
TSS、TSS段描述符、TR段寄存器關系示意圖:
實驗:加載自定義TSS
第一步:擷取必要參數
在VC6中運作如下代碼并在main函數頭部設定中斷:
#include <windows.h>
DWORD dwOK;
DWORD dwESP;
DWORD dwCS;
void __declspec(naked) func()
{
dwOK = 1;
__asm
{
int 3
mov eax,esp
mov dwESP,eax
mov ax,cs
mov word ptr [dwCS],ax
//回去的代碼沒寫。。。
}
}
int main(int argc, char* argv[])
{
char bu[0x10]; //12ff70
int iCr3;
printf("input CR3:\n");
scanf("%x", &iCr3);
DWORD iTss[0x68] = {
0x00000000, //link
0x00000000, //esp0 //(DWORD)bu
0x00000000, //ss0
0x00000000, //esp1
0x00000000, //ss1
0x00000000, //esp2
0x00000000, //ss2
(DWORD)iCr3, //cr3
0x0040DE50, //eip
0x00000000, //eflags
0x00000000, //eax
0x00000000, //ecx
0x00000000, //edx
0x00000000, //ebx
(DWORD)bu, //esp
0x00000000, //ebp
0x00000000, //esi
0x00000000, //edi
0x00000023, //es
0x00000008, //cs 0x0000001B
0x00000010, //ss 0x00000023
0x00000023, //ds
0x00000030, //fs
0x00000000, //gs
0x00000000, //dit
0x20ac0000};
char buff[6];
*(DWORD*)&buff[0] = 0x12345678;
*(WORD*)&buff[4] = 0xC0;
__asm
{
call fword ptr[buff]
}
printf("ok=%d \t ESP=%x \t CS=%x \n", dwOK, dwESP, dwCS);
return 0;
}
進入反彙編視窗檢視 func 函數起始位址,我這裡是0x401020
将位址填入iTss數組注釋為eip的地方,表示TSS切換後EIP的值
再通過 memory 視窗檢視iTss數組所在位址,記下來備用
注意:代碼中如有地方發生修改,需要先停止程式,重新編譯
第二步:構造TSS段描述符
Offset in Segment 31:16 = 0x0000 // 暫定
G = 0
AVL = 0
Limit = 二進制:0000
P = 1
DPL = 二進制:11
Type = 二進制:1001
Segment Limit = 0068H // Intel規定TSS段描述符G=0時Limit必須大于或等于67H
Offset in Segment 15:00 = 0x0000 // 暫定
由上述參數構造出的門描述符為:0000E900`00000068
在第一步中,我們已經知道iTss數組所在位址為0x12FDCC
是以,TSS段描述符最終确定為:0000E9
12`FDCC
0068
第三步:将TSS段描述符寫入GDT表
我寫入的位址是8003f0c0,若寫入其他位址,則需要修改buff數組的後兩個位元組
這時候先不要急着繼續運作代碼,先在WinDbg中輸入指令:
!process 0 0
獲得目前程序的Cr3(我這裡程序名叫TestDoor.exe,之前是用來做調用門的實驗,TSS沒有建立項目)
DirBase的值就是Cr3
第四步:解除中斷,繼續執行代碼
輸入上一步得到的Cr3,回車
WinDbg成功獲得了中斷信号
這時候看一下反彙編代碼
可以确定正在執行func函數的代碼
再看一下寄存器
eip、cs、ss都符合我們想要的結果
至此,TSS切換成功!
思考:TSS切換完成後,如何回到切換前的下一行繼續執行?
任務門
描述:
任務門存在于IDT表
任務門中包含TSS段選擇子
可以通過通路任務門達到切換TSS的目的
結構圖:
任務門執行過程
- INT N(N為IDT表索引号)
- 系統通過使用者指定的索引查找IDT表,找到對應的門描述符
- 門描述符若為任務門描述符,則根據任務門描述符中TSS段選擇子查找GDT表,找到TSS段描述符
- 将TSS段描述符中的内容加載到TR段寄存器
- TR段寄存器通過Base和Limit找到TSS
- 使用TSS中的值修改寄存器
- IRETD傳回
實驗:通過任務門切換TSS
第一步:構造任務門描述符
任務門描述符結構圖灰色部分預設填充為0
P = 1
DPL = 二進制:11
TSS Segment Selector = 0x00C3 // TSS段描述符選擇子
由上述參數及預設參數構造出的門描述符為:
0000e500`00C30000
第二步:将任務門描述符寫入IDT表
第三步:構造TSS段描述符
TSS段描述符構造具體過程參照任務段實驗部分,這裡不再詳解,隻給出測試代碼
代碼如下:
#include <windows.h>
DWORD dwOK;
DWORD dwESP;
DWORD dwCS;
void __declspec(naked) func()
{
dwOK = 1;
__asm
{
mov eax,esp
mov dwESP,eax
mov ax,cs
mov word ptr [dwCS],ax
iretd
}
}
int main(int argc, char* argv[])
{
char bu[0x10]; //12ff70
int iCr3;
printf("input CR3:\n");
scanf("%x", &iCr3);
DWORD iTss[0x68] = {
0x00000000, //link
0x00000000, //esp0 //(DWORD)bu
0x00000000, //ss0
0x00000000, //esp1
0x00000000, //ss1
0x00000000, //esp2
0x00000000, //ss2
(DWORD)iCr3, //cr3
0x00401020, //eip
0x00000000, //eflags
0x00000000, //eax
0x00000000, //ecx
0x00000000, //edx
0x00000000, //ebx
(DWORD)bu, //esp
0x00000000, //ebp
0x00000000, //esi
0x00000000, //edi
0x00000023, //es
0x00000008, //cs 0x0000001B
0x00000010, //ss 0x00000023
0x00000023, //ds
0x00000030, //fs
0x00000000, //gs
0x00000000, //dit
0x20ac0000};
__asm
{
int 0x20
}
printf("ok=%d \t ESP=%x \t CS=%x \n", dwOK, dwESP, dwCS);
getchar();
return 0;
}
第四步:運作代碼
需要輸入Cr3,Cr3擷取流程參照任務段實驗部分,這裡不再詳解
運作結果:
TSS切換成功!
至此,我們已經學會通過CALL/JMP以及任務門來切換TSS
思考:既然已經可以直接通路任務段了,為什麼還要有任務門?