保護模式第六講-IDT表-中斷門 陷阱門 任務門


保護模式第六講-IDT表-中斷門 陷阱門 任務門

一丶IDT表

之前所說 GDT表 中存儲了一些段描述符. 比如有調用門 段描述符. 代碼段段描述符. 數據段段描述符 TSS段段描述符

GDTR記錄的就是GDT表的首地址. GDTL是他這個數組的長度

那么同樣. IDT表也是 記錄在 IDTR 以及 IDTL 兩個寄存器中

其中IDT表中 只能存儲 中斷門段描述符表 陷阱門段描述符表 任務門段描述符表

其中 中斷門段描述符表 陷阱們段描述符表 其實都跟調用門用法一樣. 只不過有些許不同.

1.1 中斷門段描述符表

看一下inter手冊.將中斷門描述符表摳出來.

着重看一下 s位與TYpe位.

其中中斷門的0-4位必須為0. 不使用了. 而且8-12位都是固定的了.

跟調用門唯一不同的就是 s與type位了. 其它都是一樣的.

所以我們還可以根據調用門的相關知識. 構造一個中斷門. 並且提權.

其中在第12位哪里有一個D. 其實這個是1的意思.

所以如果判斷是不是一個中斷門 可以通過

p = 1

s = 0

type = 1110 來確定一個中斷門

1.2 中斷門的Call調用流程流程圖

觀看上圖可以得知

1.首先尋址到中斷門 ,然后從中斷門中分別取出記錄的 offset(函數偏移) + 代碼段段選擇子

2.然后根據代碼段段選擇子. 去GDT表或者LDT表中查詢 代碼段描述符

3.從代碼段描述符中取出記錄的 Base(基地址)

4.取出的基地址與中斷門中記錄額函數偏移(offset)相加得到一個真正的函數地址.

5.進行調用.

1.3 中斷門的調用以及返回

中斷門 是由int xxx匯編指令來進行觸發的. 進而去IDT表中中尋找中斷門段描述符. 與 調用門不同的是. 當返回的時候. 比如使用 (IRET / IRETD)來進行返回

int xxx xxx是索引. 是IDT表中的索引. IDT.base + xxx * 8 = 實際的中斷門所在的位置.

也可以理解為 IDT[xxx -1]

例如 int 3 查找的就是 IDT[2] 索引 數組從下標從0開始 0 1 2 正好查找的是第三項

調用門是 RETF

中斷門是 IRET /IRETD

RETF的本質就是 調用門當發生權限切換的時候.堆棧會保存 SS ESP CS EIP(返回地址)

RETF本質就是將這些堆棧值進行恢復. 並不是說調用門非要使用RETF才可以. 你如果自己進行POP也是可以的.

中斷門的堆棧會保存 SS ESP EFLAGS CS EIP(返回地址) 所以我們使用IRET來進行返回.

1.4 中斷門的構造與代碼

構造一個中斷門

0000EE~00080000

根據代碼中的函數地址則可以構造為如下

0040EE0000081030** 也可以構造成 **00408E0000081030 如果為8 代表P位有效. DPL = 00 S = 0 說明權限很高. 此時我們ring3就不能訪問了.

構造完畢之后寫入到IDT表中.

如下;

r idtr
dq address L40 顯示40項 以8字節顯示
eq 未使用的地址 0040EE00`00081030

顯示了100項發現下面都使用了. 而10 - 40中 只給了一個段選擇子就是08 我們隨便找一個位置寫入

以上圖為例子,寫入位置是 截圖位置

eq 80b95530 0040EE00`00081030

寫入完成則可以運行起來了.

  • 代碼調用

    我們寫入的IDT表 是第39項. 如果以下標來尋址 則是 39 - 1 = 38

    所以我們代碼中寫位 int 38即可. 注意40是10進制的. 如果你想寫為16進制

    那么轉為16進制則是 int 0x26

    代碼如下

    // 11.cpp : Defines the entry point for the console application.
    //
    
    #include <stdio.h>
    #include <WINDOWS.H>
    #include <STDLIB.H>
    
    
    DWORD x;
    DWORD y;
    DWORD z;
    
    __declspec(naked) void run()
    {
    	__asm 
    	{
    	
    		int 3;
    		IRETD;
    	}
    }
    
    void printXyz()
    {
    	printf("%d,%d,%d \r\n",x,y,z);
    }
    int main(int argc, char* argv[])
    {
    
    	__asm
    	{
    	
    		int 38;
    	}
    
    	printXyz();
    	system("pause");
    	return 0;
    }
    
    
    

    堆棧調用前

代碼執行后

我們可以發現

保存了 EIP(返回地址) cs EFlags esp ss 這就是中斷門的堆棧

二丶陷阱門

2.1 陷阱門段描述符

陷阱門同上,唯一不同的 段描述符中的第八位是不同的. 中斷門為0 陷阱門為1

如果按照16進制來說. 一個是E 一個是F

陷阱門的構造 以及代碼調用與中斷門一樣. 而且參數也不能有

0000EF00`00080000

2.2 陷阱門與中斷門的不同

陷阱門與中斷門唯一的不同就是 EFLAGS 位中的 IF位

中斷門 -執行后 IF 設置為0

陷阱門 -執行后 - IF不變

inter手冊中的 EFlags介紹

3.4.3 是介紹E flags的

IF 位跟中斷有關. CPU 必須支持中斷

IF = 1 響應可屏蔽中斷

IF = 0 禁止可屏蔽中斷

2.2.1 中斷

CPU 必須支持中斷 不管你當前執行了多少流程. 但是只要你操作鍵盤. 那么CPU就會第一時間響應

比如 win + D 顯示桌面 win + L 鎖屏

中斷時基於硬件的.

可屏蔽中斷 中 比如鍵盤. 鍵盤是可屏蔽中斷

按電源. 電源屬於不可屏蔽中斷. 當我們拔掉電源之后,CPU並不是直接熄滅的. 而是有電容的.

此時不管你IF是什么.都會執行 int 2中斷. 來進行一些收尾的動作.

看下inter手冊怎么說

inter手冊說. 中斷和異常 是強制性執行流的轉移 也就是不管你處於那個環境.只要有中斷 和異常.都會強制執行的.換句話來講就是 從當前流程強制轉到另一個流程

中斷是可以進行軟件模擬的. 稱為軟中斷. 也就是通過 int n 來進行模擬. 我們構造的中斷門. 並且進行int n 模擬 就是模擬了一次軟中斷

inter手冊所說. 任何可屏蔽中斷 可以通過使用 interl 架構定義的中斷向量(0`255) 也就是說有一個中斷表. 大小是255項. 通過 E Flags 中的 if 位 可以進行屏蔽 所有可屏蔽的硬件中斷

軟件中斷模擬的注意事項. inter手冊所屬. 0-255 我們都是可以通過 int n進行模擬的. 但是有一個注意的問題就是. 比如不可屏蔽中斷. int 2. 如果我們用int 2來進行模擬 是不會產生真正的效果的 意思就是說.你執行int 2確實是執行了. 但是硬件上並沒有激活. 也就是說你是假的. 真正的執行時硬件上的處理這塊的地方也執行了.

inter手冊所說 0-31中斷向量是給 NMI中斷使用的.也就是給CPU使用的. 剩下的32 - 255 才是

給用戶使用的.

三丶任務段與任務門

3.1 TSS 學習.

3.1.1 TSS簡介

通過中斷門 陷阱門 以及調用們我們知道. 其實不管提權不提權. 本質就是 修改寄存器的值.

比如:

jmp cs:EIP          提權 修改的是 cs ss esp eip
call far            eip cs eflags esp ss 等等

TSS 主要就是為了替換寄存器的. 存在的意義就是任務切換. 在CPU 層面只有任務層面. 沒有進程

沒有線程等概念. TSS也是一個好設計. 設想在單核時代.沒有TSS 根本不能多任務切換.

而有了TSS 則可以當A程序執行到一個地址. 保存地址處的 通用寄存器 段 以及控制寄存器等.

然后執行B. 當切換到A的時候.在還原回來進行繼續執行. 如果對應操作系統就是線程的概念.

  • TR段寄存器(96位)

    TR寄存器是一個96位的段寄存器.保存了TSS的首地址

    LTR STR 相關匯編指令  L = load的意思 就是裝載TR寄存器. 屬於特權指令 STR是讀,讀出來也就是16位的段選擇子. 沒有特權要求
    

    TR既然是段寄存器.那么可見部分肯定還是只有段選擇子. 所以還是去GDT查表.

    tr寄存器段選擇自查找到段描述符

    段描述符.base = TSS首地址

    段描述符.limit = TSS這塊內存的大小

    而我們查的表就是 TSS段描述符

    r tr
    dg xxx 解析段選擇子
    

    段寄存器結構復習

    struct segment
    {
      WORD Selector, //段選擇子  16位
      WORD Attribute,//段屬性    16位
      DWORD Base,    //段基地址 32位
      DWORD Limit,   //段限長 32位
    }
    

    三者之間的關系

    TSS 屬於是一塊內存

    TSS段描述符. 指明了TSS在哪.

    TR寄存器.段寄存器. 指明了去GDT表中查找TSS段描述符

3.1.2 TSS內存結構

在inter手冊 3A卷第七章可以看到TSS 內存結構. 主要保存了 通用寄存器 段寄存器.

而且一個TSS 對應一個LDT段寄存器

MOV ds,0x5C   5c段選擇子拆開就是 查詢LDT表. 如果你在TSS夠早了LDT.那么當任務切換的時候LDTR則會被賦值,當執行 0x5C的時候.CPU會去LDTR中找到到對應的段描述符進行操作

LDT TSS 一一對應, 只對單個任務有意義.

TSS 中的 第0位置處. Previous Task Link 是一個鏈表.指向了上一個TSS. TSS是在內存中的.

當我們中斷門 陷阱門調用的時候. 切換堆棧的時候. esp ss 都是從TSS這塊內存中拿到的.

這里有ESP0 SS0 等. 意思就是當你切換到0環的時候. 那么就是用ESP 0 如果是1環 就切換到ESP 1

以此類推

通過Windbg內核.可以得知TR寄存器保存的是TSS的段選擇子. 通過TR 可以找到TSS段描述符.

然后通過TSS段描述符來尋找到 TSS內存所在的位置.

dg tr    解析出TSS段描述符結構
kd> dg tr
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0028 801e4000 000020ab TSS32 Busy 0 Nb By P  Nl 0000008b

db 801e4000 L0x68  可以查看TSS的內存

windbg如下.

TSS內存是從低到高的. 高地址最后一個4個字節保存的就是IO了.

3.1.3 TSS 段描述符

inter手冊圖片. 其實跟之前的段描述符是一樣的. 唯一不同的是 type位.

這里 1001 在這里還有個B. inter手冊說. 如果為B那么就代表TSS正在忙碌.

G位在這里為1表示字節

3.1.4 TSS下的尋址形式

TSS段描述符在64位下進行擴展了. 而且TSS在64位下已經不支持了. 但是還有保留

等講解 x64保護模式與32位包括模式擴展的時候在進行詳細講解.

image-20200719235026995

1.先從任務段寄存器(TR)中得出段選擇子(visible part位置)

2.查詢GDT表.找到TSS段描述符

3.根據TSS中段描述符的描述的 BaseAddres(基地址) 以及limit 去TSS寄存器中進行查找

3.1.5 windows下的TSS使用

windows覺得TSS設計不好.只使用了TSS中的 esp 0 ss0等寄存器

3.2模擬TSS任務切換

1.了解 TR TSS 與 TSS段描述符之間的關系

TSS段描述符 存儲在GDT表中. 其中 TSS的base 指向了一個TSS.也就是一塊內存 大小為0x64(104)個字節

TR寄存器. 指向了 TSS內存. 但是TR得值.是從TSS段描述符中獲取到的. (TR是一個段選擇子)

也就是說我們要模擬TSS任務切換首先要做到如下.

1.構建TSS內存.大小為0x68個字節 怎么構建,參考104字節每個字節代表的意思 參考 3.1.2 TSS內存結構

2.構建TSS段描述符. 然后寫入到GDT表中

3.利用LTR修改TR寄存器.來達到從TRR段描述符中獲取TSS的內存.

4.使用遠call 來進行內核與ring3的切換.參考模擬調用們權限. 或者jmp 也可以.

jmp cs:eip
call far cs:eip
  • 1.構造TSS內存

    char espBuffer[0x30] = { 0 }; //開辟空間構建TSS內存的棧頂
    	printf("Please Input Cr3 Value \r\n");
    	unsigned int Cr3Value = 0;
    	scanf("%d", &Cr3Value);
    	//構建TSS的內存
    	unsigned int TssMemory[0x64] = {
    	0x00000000,// Previous TaskLink 操作系統會給寫入
    	0x00000000,// esp 0
    	0x00000000,// ss  0
    	0x00000000,// esp 1
    	0x00000000,// ss  1
    	0x00000000,// esp 2
    	0x00000000,// ss  2
    	(unsigned int)Cr3Value,
    	(unsigned int)EipFunction,// EIP              執行你函數的地址.EIP指向.這樣才可以執行你的函數
    	0x00000000,// eflags 
    	0x12345678,// eax
    	0x87654321,// ecx
    	0x11223344,// edx
    	0x44332211,// ebx              寄存器隨便給.便於調試的時候更直觀的看到
    	(unsigned int)espBuffer,	// esp              棧頂的值.需要我們指定
    	0x00000000,// ebp              棧底的值
    	0x00000000,// esi
    	0x00000000,// edi
    	0x00000023,// es				段寄存器我們也要給 如果切換到內核則按照內核中的給.
    	0x00000008,// cs				0環的代碼段選擇子. 如果想要切換rign3就給ring3的. 可以windbg調試
    	0x00000010,// ss                同cs一樣.ss與cs必須在同一代碼段下
    	0x00000023,// ds
    	0x00000030,// fs
    	0x00000000,// gs
    	0x00000000,// LDT
    	0x20ac0000,// Io              這個根據Windbg調試得出來.看一下填寫即可.
    	};
    	
    

    這里涉及到1個點

    如何查看 Cr3的值並且寫入

    ​ 運行我們的模擬切換的程序. 此時會段在scanf位置. 在windbg下中斷. 中斷后. 輸入 ! process 0 0 來查看所有進程.找到我們的進程.復制一下 Dirbase即可. 然后輸入進去.

    • 構造TSS段描述符.

      構造很簡單.根據上面所說的 TSS段描述符進行構造即可.

      這里則是將我們的TSS的內存的地址.構造進TSS段描述符.

      完整代碼如下:

      // TSS.cpp : Defines the entry point for the console application.
      //
      
      #include <stdlib.h>
      #include <stdio.h>
      
      
      void __declspec(naked) EipFunction()
      {
      	__asm
      	{
      		int 3;   //便於內核調試器斷下
      		push ebp;
      		mov ebp, esp;
      
      		
      	}
      }
      
      
      int main(int argc, char* argv[])
      {
      	char espBuffer[0x30] = { 0 }; //開辟空間構建TSS內存的棧頂
      	printf("Please Input Cr3 Value \r\n");
      	unsigned int Cr3Value = 0;
      	scanf("%d", &Cr3Value);
      	//構建TSS的內存
      	unsigned int TssMemory[0x64] = {
      	0x00000000,// Previous TaskLink 操作系統會給寫入
      	0x00000000,// esp 0
      	0x00000000,// ss  0
      	0x00000000,// esp 1
      	0x00000000,// ss  1
      	0x00000000,// esp 2
      	0x00000000,// ss  2
      	(unsigned int)Cr3Value,
      	(unsigned int)EipFunction,// EIP              執行你函數的地址.EIP指向.這樣才可以執行你的函數
      	0x00000000,// eflags 
      	0x12345678,// eax
      	0x87654321,// ecx
      	0x11223344,// edx
      	0x44332211,// ebx              寄存器隨便給.便於調試的時候更直觀的看到
      	(unsigned int)espBuffer,	// esp              棧頂的值.需要我們指定
      	0x00000000,// ebp              棧底的值
      	0x00000000,// esi
      	0x00000000,// edi
      	0x00000023,// es				段寄存器我們也要給 如果切換到內核則按照內核中的給.
      	0x00000008,// cs				0環的代碼段選擇子. 如果想要切換rign3就給ring3的. 可以windbg調試
      	0x00000010,// ss                同cs一樣.ss與cs必須在同一代碼段下
      	0x00000023,// ds
      	0x00000030,// fs
      	0x00000000,// gs
      	0x00000000,// LDT
      	0x20ac0000,// Io              這個根據Windbg調試得出來.看一下填寫即可.
      	};
      
      	printf("TSS內存的地址為 : %p \r\n", TssMemory);     //輸出一下TSS內存的地址. 便於構造段描述符.
      	getchar();
      	char FarAddress[6] = { 0 };//構造我們的遠call 指令.同調用們一樣
      	//call far 段選擇子:eip 
      	*(unsigned int*)&FarAddress[0] = 0x00000000; //EIP在遠call的時候不用.隨便給.
      	*(unsigned short*)&FarAddress[4] = 0xC0;    //構造的TSS段在GDT表中的段選擇子.
      	__asm
      	{
      		call fword ptr[FarAddress];
      	}
      
      	printf("over _ \r\n");
      	system("pause");
      	return 0;
      }
      
      

      假設我們的TSS內存地址為 0x0012FD84 那么構造的段描述符就為

      eq 8003f0c0 0000e912`FD800068
      

3.3 任務門

任務門如下

其實就是存儲了一個 TSS的段描述符的段選擇子.

任務門是存在中斷表中的(IDT)而TSS段描述符是在GDT表中的.

任務門執行流程如下

  • 1.INT N指令來去IDT表中執行代碼
  • 2.查詢IDT表找到任務門描述符
  • 3.通過任務描述符表.查詢GDT表.找到任務段描述符.
  • 4.使用TSS段中的值修改寄存器
  • 5.IRETD返回

所以要構造一個任務門 我們要構造一個TSS段. 還要構造一個任務門才可以.
如果是任務門跟以上構造TSS模擬任務切換一樣.只不過我們要修改為 int n來進行調用了.
所以我們要修改IDT表. 而且代碼的返回使用的是 IRETD指令


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM