[保護模式]任務段

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

說明:

  1. 用LTR指令去裝載的話,僅僅是改變TR寄存器的值,並沒有真正改變TSS
  2. LTR指令只能在系統層使用
  3. 加載後TSS段描述符狀態位會發生改變

構造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, //esp0
	0x00000010,//ss0
	0x00000000,//esp1
	0x00000000,//ss1
	0x00000000,//esp2
	0x00000000,//ss2
	0x00000000,//cr3
	0x00401090,//eip-----填裸函數地址
	0x00000000,//eflags
	0x00000000,//eax
	0x00000000,//ecx
	0x00000000,//edx
	0x00000000,//ebx
	(DWORD)st, //esp
	0x00000000,//ebp
	0x00000000,//esi
	0x00000000,//edi
	0x00000023,//es  
	0x00000008,//cs  
	0x00000010,//ss
	0x00000023,//ds
	0x00000030,//fs
	0x00000000,//gs
	0x00000000,//ldt
	0x20ac0000
};

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: 0534
    DirBase: 7f0e7560  ObjectTable: c1dcf198  HandleCount:   7.
    Image: KernelTest.exe

將前進程的頁目錄基址填充到CR3,接着運行程序 即可完成TSS切換

編譯,放到虛擬機中執行,運行以後,在windbg中查看CR3寄存器的值

PROCESS 87065d40  SessionId: 1  Cid: 0cc0    Peb: 7ffd9000  ParentCid: 0534
    DirBase: 7f0e7560  ObjectTable: c1dcf198  HandleCount:   7.
    Image: KernelTest.exe

將前進程的頁目錄基址填充到CR3,接着運行程序 即可完成TSS切換

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章