Windows內核中的CPU架構-8-任務段TSS(task state segment)


Windows內核中的CPU架構-8-任務段TSS(task state segment)

任務段tss(task state segment)是針對於CPU的一個概念。

舉一個簡單的例子,你一個電腦,肯定是同時會運行多個程序把,比如說QQ,微信,LOL。哪我們知道每個進程的內容是不一樣的,那么,這個時候如果說只有一塊CPU,這個CPU肯定不能只執行一個進程吧,不然這么多個程序就得卡死了,等LOL打完才能進行微信視頻聊天,這樣對很多和女朋友視頻的男生非常不友好吧。就CPU肯定是會切換的,這里的task是一個比較廣的概念,不僅僅局限於什么進程線程這樣子,可以抽象為當前做的事情。

就比如說一個人是CPU,他穿了一套打籃球的裝備在打籃球,那么突然要去踢足球,是不是得換一套踢足球的裝備再去踢足球啊。

這里的TSS就可以類比為人身上穿的東西,然后人做的事就可以類比為任務。就你切換任務的時候,根據任務不同你還得穿不同的TSS。

再通過OS和CPU的內容來描述,就是TSS是任務的狀態,每個任務有每個狀體。當切換成task時,tss就會賦值為別的tss。

所以我們就可以利用這個機制,把我們的代碼作為一種切換,讓CPU切換的我們的代碼,並且TSS采用我們寫的TSS進行賦值,這樣來作為一種提權,修改RPL和CPL。

TSS結構體:

 

 

 

這個結構體一共有104個字節。

 

字段 內容
灰色字段 保留字段,用0填充。
Previous Task Link 上一個TSS的段選擇子。
ESP0 SS0到 ESP2 SS2 ESP0 SS0到ESP2 SS2,這個就是CPU內部的設置了,因為intel有3中特權級,0 1 2 3,所以這個esp0 ss0就是對應的0環的棧空間。然后根據特權級,來使用不同特權級的棧空間到ss和esp寄存器來使用。
GS-EIP 普通的段寄存器和寄存器。
其它字段 其它字段暫時用不到,感興趣的可以去看intel手冊,這里只是為了一段提權代碼。

如何查找tss

tss就是一個結構體,肯定是保存在這個內存里面的,那么我們如何拿到它呢。

這里需要引入一個寄存器,叫tr寄存器,它是一個段寄存器,和CS,ES等等一樣,存放的也是段選擇子的內容。然后可以拿到段描述符的內容。

這里我們再從頭到尾解析一下:

首先輸出tr寄存器:

kd> r tr
tr=00000028
 

得到的段選擇子為0x28。然后解析它:

   0x28  = 0000 0000 0010 1 0   00

 

 

 

 

   0x28  = 0000 0000 00101(GDT的第5個)   0(GDT)   00(RPL)

然后通過段選擇子結構體,找到對應的段描述符:

kd> dq gdtr
80b93000 00000000`00000000 00cf9b00`0000ffff
80b93010 00cf9300`0000ffff 00cffa00`0000ffff
80b93020 00cff300`0000ffff 80008b1e`400020ab
80b93030 834093f6`dd003748 0040f200`00000fff
80b93040 0000f200`0400ffff 00000000`00000000
80b93050 830089f6`b0000068 830089f6`b0680068
80b93060 00000000`00000000 00000000`00000000
80b93070 800092b9`300003ff 00000000`00000000

tr段描述符對應的就是gdt表中的第五個值的內容:

80008b1e`400020ab

然后通過tss的段描述符結構進行解析:

 

 

TSS的地址就是幾個base address加起來的結果。

這里通過前面的段描述符來找內容:

80 00 8b 1e `40 00 20 ab
地址為:801e4000

然后查看一下當前tss的內容:

 

 

手動添加一個任務切換

自己構造一個tr,然后把tr寄存器賦值為我們自己的tr,再進行任務切換,這樣就會把tr結構體里的值賦值給切換后的環境的值,從而達到一個提權的作用。

那么首先我們得構造一個tss結構體:

typedef struct _KTSS
{
   USHORT link;                                                       //0x0
   USHORT Reserved0;                                                       //0x2
   ULONG esp0;                                                             //0x4
   USHORT ss0;                                                             //0x8
   USHORT Reserved1;                                                       //0xa
   ULONG notUsed1[4];                                                      //0xc
   ULONG CR3;                                                              //0x1c
   ULONG eip;                                                              //0x20
   ULONG eflags;                                                           //0x24
   ULONG eax;                                                              //0x28
   ULONG ecx;                                                              //0x2c
   ULONG edx;                                                              //0x30
   ULONG ebx;                                                              //0x34
   ULONG esp;                                                              //0x38
   ULONG ebp;                                                              //0x3c
   ULONG esi;                                                              //0x40
   ULONG edi;                                                              //0x44
   USHORT es;                                                              //0x48
   USHORT Reserved2;                                                       //0x4a
   USHORT cs;                                                              //0x4c
   USHORT Reserved3;                                                       //0x4e
   USHORT ss;                                                              //0x50
   USHORT Reserved4;                                                       //0x52
   USHORT ds;                                                              //0x54
   USHORT Reserved5;                                                       //0x56
   USHORT fs;                                                              //0x58
   USHORT Reserved6;                                                       //0x5a
   USHORT gs;                                                              //0x5c
   USHORT Reserved7;                                                       //0x5e
   USHORT LDT;                                                             //0x60
   USHORT Reserved8;                                                       //0x62
   USHORT flags;                                                           //0x64
   USHORT IoMapBase;                                                       //0x66
}TSS;

然后開始構造這個段描述符:

 

 

構建段描述符

由於tss是我們自己構建的,所以首先得獲取我們的tss首地址

獲取tss首地址

首先要注意是這個tss的首地址的問題,我采用的方式是在vs里面創建一個tss,並把地址寫死,然后把這個程序里的tss的地址作為這個段描述符中的地址來處理。修改了代碼這個值還是會改變。

需要進行兩個設置:

首先是隨機基址改為否:

 

 

然后是這個啟用增量鏈接改為否:

 

 

//獲取構建的tss地址
#include<iostream>
#include<Windows.h>
using namespace std;
typedef struct _KTSS
{
   USHORT link;                                                       //0x0
   USHORT Reserved0;                                                       //0x2
   ULONG esp0;                                                             //0x4
   USHORT ss0;                                                             //0x8
   USHORT Reserved1;                                                       //0xa
   ULONG notUsed1[4];                                                      //0xc
   ULONG CR3;                                                              //0x1c
   ULONG eip;                                                              //0x20
   ULONG eflags;                                                           //0x24
   ULONG eax;                                                              //0x28
   ULONG ecx;                                                              //0x2c
   ULONG edx;                                                              //0x30
   ULONG ebx;                                                              //0x34
   ULONG esp;                                                              //0x38
   ULONG ebp;                                                              //0x3c
   ULONG esi;                                                              //0x40
   ULONG edi;                                                              //0x44
   USHORT es;                                                              //0x48
   USHORT Reserved2;                                                       //0x4a
   USHORT cs;                                                              //0x4c
   USHORT Reserved3;                                                       //0x4e
   USHORT ss;                                                              //0x50
   USHORT Reserved4;                                                       //0x52
   USHORT ds;                                                              //0x54
   USHORT Reserved5;                                                       //0x56
   USHORT fs;                                                              //0x58
   USHORT Reserved6;                                                       //0x5a
   USHORT gs;                                                              //0x5c
   USHORT Reserved7;                                                       //0x5e
   USHORT LDT;                                                             //0x60
   USHORT Reserved8;                                                       //0x62
   USHORT flags;                                                           //0x64
   USHORT IoMapBase;                                                       //0x66
}TSS;
TSS tss{ 0 };


int main()
{
   printf("tss的地址為: %x\n", &tss);

system("pause");
return 0;
}

然后我這里的結果是00403378:

 

 

構建tss段描述符:

高32位:
Base 31:24 00

G:0(0表示tss大小以字節為單位,1表示以頁為單位)

AVL:這里暫時沒找到解釋,采用0就好

Limit 19:16 采用0,因為104個字節用不到這么高位

p: 1表示可用,0為不可用

DPL:特權級,這里采用3

Type中的B表示這個段是否在被使用中,這里用0 表示空閑

Base23:16: 40



低32位:
前16位地址:3378

Segment Limit(15-0):0068


總和下來就是

0000e940 33780068

安裝tss段描述符

方式tss段描述符需要根據段選擇子來確定,因為需要確定放置在gdt表的位置。這里段描述符我采用之前用的 0x48來處理。

這里的0x48就表明這個段描述符要在gdt表里的第10個,這個不會的建議回頭看看前面的知識。

 

 

對結構體進行賦值

我們在切換任務的時候,會把tss的值賦值給當前的環境,所以我們的tss肯定是要賦值的。

//申請0環棧空間
BYTE esp0[0x2000];
//申請普通棧空間
BYTE esp[0x2000];

   memset(esp0, 0, 0x2000);//清空0環棧空間
   memset(esp, 0, 0x2000);//情況普通棧空間
   tss.eip = (ULONG)test;//執行地址
   tss.cs = 0x08;  //賦予0環的RPL,別的不用管
   tss.ss = 0x10;  //賦予0環的RPL,別的不用管
   tss.ds = 0x23;  //數據段,不用管
   tss.es = 0x23;  //這個也不用管
   tss.fs = 0x30;//0環是30,三環是3B
   tss.esp0 = (ULONG)esp0+2000;   //賦值esp給前面開辟的空間
   tss.esp = (ULONG)esp + 2000;//賦值給前面開辟的esp空間

這里還需要一個關鍵值CR3,別的值可以直接忽略掉了。但是這個CR3比較麻煩,就直接先說一下怎么找吧,這里采用一個直接輸入的方式來添加cr3:

(完整代碼)

#include<iostream>
#include<Windows.h>
using namespace std;
typedef struct _KTSS
{
   USHORT link;                                                       //0x0
   USHORT Reserved0;                                                       //0x2
   ULONG esp0;                                                             //0x4
   USHORT ss0;                                                             //0x8
   USHORT Reserved1;                                                       //0xa
   ULONG notUsed1[4];                                                      //0xc
   ULONG CR3;                                                              //0x1c
   ULONG eip;                                                              //0x20
   ULONG eflags;                                                           //0x24
   ULONG eax;                                                              //0x28
   ULONG ecx;                                                              //0x2c
   ULONG edx;                                                              //0x30
   ULONG ebx;                                                              //0x34
   ULONG esp;                                                              //0x38
   ULONG ebp;                                                              //0x3c
   ULONG esi;                                                              //0x40
   ULONG edi;                                                              //0x44
   USHORT es;                                                              //0x48
   USHORT Reserved2;                                                       //0x4a
   USHORT cs;                                                              //0x4c
   USHORT Reserved3;                                                       //0x4e
   USHORT ss;                                                              //0x50
   USHORT Reserved4;                                                       //0x52
   USHORT ds;                                                              //0x54
   USHORT Reserved5;                                                       //0x56
   USHORT fs;                                                              //0x58
   USHORT Reserved6;                                                       //0x5a
   USHORT gs;                                                              //0x5c
   USHORT Reserved7;                                                       //0x5e
   USHORT LDT;                                                             //0x60
   USHORT Reserved8;                                                       //0x62
   USHORT flags;                                                           //0x64
   USHORT IoMapBase;                                                       //0x66
}TSS;
TSS tss{ 0 };
//申請0環棧空間
BYTE esp0[0x2000];
//申請普通棧空間
BYTE esp[0x2000];

void _declspec(naked)test()
{

   _asm
{
int 3
iretd
}

}

int main()
{
BYTE code[6] = {0,0,0,0,0x48,0 };
   printf("tss的地址為: %x\n", &tss);
   system("pause");
   memset(esp0, 0, 0x2000);//清空0環棧空間
   memset(esp, 0, 0x2000);//情況普通棧空間
   tss.eip = (ULONG)test;//執行地址
   tss.cs = 0x08;  //賦予0環的RPL,別的不用管
   tss.ss = 0x10;  //賦予0環的RPL,別的不用管
   tss.ds = 0x23;  //數據段,不用管
   tss.es = 0x23;  //這個也不用管
   tss.fs = 0x30;//0環是30,三環是3B
   tss.ss0 = 0x10;//賦予0環的RPL,別的不用管
   tss.esp0 = (ULONG)esp0+0x1600;   //賦值esp給前面開辟的空間
   tss.esp = (ULONG)esp + 0x1600;//賦值給前面開辟的esp空間
   DWORD cr3 = 0;
   printf("please input cr3\n");
   scanf_s("%x", &cr3);
   _asm
{
call far fword ptr code
}
system("pause");
return 0;
}

這里建議查看前面的tss全局變量是否地址改變,如果改變了需要重新配置段描述符。

使用TSS

前面我們還有個坑cr3沒有處理。

先讓程序在虛擬機里跑起來,然后WinDbg把虛擬機斷下來,來找cr3:

 

 

 

!process 0 0 //查看所有進程,找到我們的程

 

 

這里的DirBase就是cr3。具體原因就先不解釋了。

 

正常運行首先得先安裝段描述符,但是需要注意的是如果我們代碼修改了,前面的tss全局變量的地址可能是會改變的,需要重新編寫段描述符,然后這個cr3也是會改變的,每次都得重新添加才行。

這樣再運行就會跑到我們寫好的int 3中斷里:

同時再查看一下寄存器的內容:

 

 

就成功了。

這里有一些補充知識:這里的esp就是給寫好的代碼段進行正常使用的esp,然后esp0就是給你代碼要執行0環的代碼的時候采用的。

然后這個previou task link會自動賦值為上一個的tss段選擇子。

還有就是iretd這個指令,它會根據EFLAG寄存器的nt位來不同的選擇返回方式:

 

 

如果nt為0,就會通過esp來返回,和retf差不多,如果為1就會通過tss來返回。但是如果執行了int 3就會把eflag寄存器的nt位清零。

小結

tss作為x86架構中的一個段,主要作用是用來保存環境。我感覺這個x86我其實沒學明白,准備后面幾天把x86架構閱讀一遍,再來重新修改這些內容,不好意思各位,可能這里沒講清楚,等我后面再來修改這系列x86 CPU架構的博客。


免責聲明!

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



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