[保护模式]任务段
文章目錄
- TSS
- 什么是TSS
- TSS的作用
- 如何找到TSS
- TR寄存器讀寫
- 將TSS段描述符加載到TR寄存器
- 構造TSS段描述符
- 修改TSS
- JMP 訪問代碼段
- JMP 訪問任務段
- JMP FAR和CALL FAR訪問任務段的區別
- TSS切換實驗
- 示例代碼
- 構造段描述符
- 填充CR3
TSS
什么是TSS
TSS是一塊內存,大小為104字節,結構如圖所示:
結構體如下:
typedef struct TSS {DWORD link; // 保存前一個 TSS 段選擇子,使用 call 指令切換寄存器的時候由CPU填寫。// 這 6 個值是固定不變的,用于提權,CPU 切換棧的時候用DWORD esp0; // 保存 0 環棧指針DWORD ss0; // 保存 0 環棧段選擇子DWORD esp1; // 保存 1 環棧指針DWORD ss1; // 保存 1 環棧段選擇子DWORD esp2; // 保存 2 環棧指針DWORD ss2; // 保存 2 環棧段選擇子// 下面這些都是用來做切換寄存器值用的,切換寄存器的時候由CPU自動填寫。DWORD cr3;DWORD eip;DWORD eflags;DWORD eax;DWORD ecx;DWORD edx;DWORD ebx;DWORD esp;DWORD ebp;DWORD esi;DWORD edi;DWORD es;DWORD cs;DWORD ss;DWORD ds;DWORD fs;DWORD gs;DWORD ldt;// 這個暫時忽略DWORD io_map; } TSS;- Previous Task Link 前一個TSS的鏈接。通過這個字段可以找到上一個TSS
- ESP0:零環的ESP
- SS0:零環的SS
- ESP1:1環的ESP
- SS1:1環的SS
- ESP2:2環的ESP
- SS2:2環的SS
- LDT Segment Seletor:LDT段選擇子,保存了LDT表的基址和長度。 這個選擇子對應的段描述符必須是系統段描述符。LDT段選擇子的數量有多少個取決于TSS有多少個
- IO Map Base Address:IO權限位圖 與硬件相關 可忽略
TSS的作用
TSS是CPU設計的東西,與操作系統無關。一個CPU只有一個TSS,TSS存在的意義在于讓一個CPU可以同時執行多個任務。但是操作系統并沒有使用TSS來進行任務切換,而是直接將任務切換所需要保存的寄存器直接存到了堆棧里。
如何找到TSS
那么CPU如果想執行任務切換的話就必須先找到TSS。通過TR段寄存器。
TR寄存器讀寫
將TSS段描述符加載到TR寄存器
指令:LTR
說明:
構造TSS段描述符
Base 31:24:00 G:0 0 0 AVL:0 ->0 Limit19:16:0 Type:9(表示未被加載過 加載過后為B) Base 23:16:00 Base Address 15:00:0000 Segment Limit 15:00:0068 XX00E9XX`XXXX0068 # Base Address指的是新的TSS的內存地址修改TSS
在Ring3我們可以通過call far或者jmp far指令來修改TSS。
JMP 訪問代碼段
JMP 0x48 0x12345678如果0x48是代碼段,執行后CS=0x48 EIP=0x12345678
JMP 訪問任務段
JMP 0x48 0x12345678如果0x48是TSS段描述符,先修改TR寄存器,再用TR.Base指向的TSS中的值修改當前寄存器
JMP FAR和CALL FAR訪問任務段的區別
- 當使用JMP FAR來實現任務切換時,TSS結構體中的Previous Task Link的值在任務切換完成之后為0,CPU不會為其賦值;如果使用CALL FAR來實現任務切換,Previous Task Link的值在任務切換完成之后會CPU會將其填充為原來的TSS段選擇子
- 當使用JMP FAR來實現任務切換時,EFLAGS寄存器中的NT位不變;當使用CALL FAR來實現任務切換時,EFLAGS寄存器中的NT位就會被置1(NT位會對iret指令產生影響 NT位如果為0,iret的值從堆棧中取(中斷返回);如果NT位為1,會找TSS中的Previous Task Link進行返回)
TSS切換實驗
示例代碼
首先準備好需要切換的104個字節,并且賦上正確的值,示例代碼如下
#include "pch.h" #include<windows.h> #include<stdio.h>typedef struct TSS {DWORD link; // 保存前一個 TSS 段選擇子,使用 call 指令切換寄存器的時候由CPU填寫。// 這 6 個值是固定不變的,用于提權,CPU 切換棧的時候用DWORD esp0; // 保存 0 環棧指針DWORD ss0; // 保存 0 環棧段選擇子DWORD esp1; // 保存 1 環棧指針DWORD ss1; // 保存 1 環棧段選擇子 DWORD esp2; // 保存 2 環棧指針DWORD ss2; // 保存 2 環棧段選擇子// 下面這些都是用來做切換寄存器值用的,切換寄存器的時候由CPU自動填寫。DWORD cr3;DWORD eip;DWORD eflags;DWORD eax;DWORD ecx;DWORD edx;DWORD ebx;DWORD esp; DWORD ebp;DWORD esi;DWORD edi;DWORD es;DWORD cs;DWORD ss;DWORD ds;DWORD fs;DWORD gs;DWORD ldt;// 這個暫時忽略DWORD io_map; } TSS;char st[10] = { 0 }; DWORD g_esp = 0; DWORD g_cs = 0;TSS tss = { 0x00000000,//link(DWORD)st, //esp00x00000010,//ss00x00000000,//esp10x00000000,//ss10x00000000,//esp20x00000000,//ss20x00000000,//cr30x00401090,//eip-----填裸函數地址0x00000000,//eflags0x00000000,//eax0x00000000,//ecx0x00000000,//edx0x00000000,//ebx(DWORD)st, //esp0x00000000,//ebp0x00000000,//esi0x00000000,//edi0x00000023,//es 0x00000008,//cs 0x00000010,//ss0x00000023,//ds0x00000030,//fs0x00000000,//gs0x00000000,//ldt0x20ac0000 };void __declspec(naked) func() {//00401090__asm {int 3} } int main(int argc, char* argv[]) {printf("%p\n", func);printf("%x\n", &tss);printf("cr3:\n");scanf_s("%x", &(tss.cr3));char buffer[6] = { 0, 0, 0, 0, 0x48, 0 };__asm{call fword ptr[buffer]}printf("g_cs = %08x\ng_esp = %08x\n", g_cs, g_esp);getchar();return 0; }需要注意的是,我們需要將eip設置為指定的地址,讓TSS切換完成之后,跳轉到那個地址。這里將裸函數的地址打印出來之后替換掉了EIP。通過主函數的這句代碼可以打印出要跳轉的裸函數地址
printf("%p\n", func);構造段描述符
將我們之前準備好的任務段描述符直接拿過來
XX00E9XX`XXXX0068將地址設置為我們準備好的TSS結構體,通過下面的代碼打印結構體地址
printf("%x\n", &tss);構造好的段描述符如下:
0000E940`30180068接著修改GDT表的段描述符
kd> eq 80b95048 0000E940`30180068填充CR3
接著編譯,放到虛擬機中執行,運行以后,在windbg中查看CR3寄存器的值
PROCESS 87065d40 SessionId: 1 Cid: 0cc0 Peb: 7ffd9000 ParentCid: 0534DirBase: 7f0e7560 ObjectTable: c1dcf198 HandleCount: 7.Image: KernelTest.exe將前進程的頁目錄基址填充到CR3,接著運行程序 即可完成TSS切換
編譯,放到虛擬機中執行,運行以后,在windbg中查看CR3寄存器的值
PROCESS 87065d40 SessionId: 1 Cid: 0cc0 Peb: 7ffd9000 ParentCid: 0534DirBase: 7f0e7560 ObjectTable: c1dcf198 HandleCount: 7.Image: KernelTest.exe將前進程的頁目錄基址填充到CR3,接著運行程序 即可完成TSS切換
總結
- 上一篇: [保护模式]中断门
- 下一篇: [保护模式]非PAE模式