Windows保護模式下的段機制(PART2:系統段介紹)

1. 概述

PART1那篇文章基本上已經把段機制交待清楚了,這一部分主要是介紹在PART1裏沒有介紹的系統段。
我們知道段描述符裏的S位爲1就是代碼段或數據段,如果爲0就是系統段。

2. 調用門

2.1 調用門介紹

之前介紹過長調用,想通過長調用提升CPL只能通過調用門,調用門是門描述符的一種,保存在GDT中,Windows系統雖然沒有使用調用門,但是爲了演示提升CPL,我們可以手動構造一個調用門。大概的步驟如下:
1. 使用指令CALL CS:EIP
2. 分解CS的值,然後查GDT表,在Index位置處我們手動構造一個調用門描述符
3. 在調用門描述符中保存着一個代碼段的段選擇子(Segment Selector),指向的段描述符Base+調用門描述符中的偏移(Offset inSegment)就是真正要執行的地址
4. 經過調用門的處理以後CPL會變成0
門描述符的結構如下:

上圖可以看出S=0,Type=1100,1100就指代調用門,Offset inSegment指代要執行的代碼偏移,具體的用法在上面的第3步已經說了。
Segment Selector的值決定了長調用是否提權,如果RPL爲0代表提權,RPL爲3不提權。

2.2 調用門實例

(要進行以下操作步驟,WinDbg首先要設置好雙機調試)

構造一個調用門:
1. 在WinDbg中使用r gdtr查看系統GDT表,顯示它的地址是0x8003f000
2. 使用dq 8003f000顯示GDT表內容
3. 使用eq 8003f048 0040ec00`00081030將構造好的調用門寫入到地址0x8003f048處(調用門需要根據門描述符的結構來構造,如果你不知道這個調用門是怎麼構造的,你將它轉成2進制,再跟門描述符表中的成員進行對照就會明白的)

構造好調用門以後,使用長調用的方式訪問GDT中的調用門,就可以實現權限的提升。

int main()
{
	char buff[6];
	*(DWORD *)&buff[0] = 0x12345678;
	*(WORD *)&buff[4] = 0x48;//賦值給高2個字節的CS段寄存器,0x48是段選擇子
	_asm
	{
		call fword ptr[buff] ;//6個字節的指針,高2個字節是CS,低4個字節是IP,可以忽略低4個字節
	}
	getchar();
	return 0;
}

最後再通過調用門描述符中的偏移處的代碼驗證是否提權成功(要先找到裸函數在內存中的地址,然後填進構造好的調用門描述符中作爲它的偏移,用前面說的方式將GDT表中的門描述符修改成0040ec00 000810d0)。

void __declspec(naked) GetRegister()
{
	_asm
	{
		int 3
		retf 
	}
}

運行程序,程序會中斷在函數GetRegister()的int 3處,在WinDbg中注意觀察SS、ESP、CS的值,會發現在這時已經改變了,分析它的選擇子發現CPL已經變成0了,權限成功提升。

權限提升以後就可以訪問高2G的內存了,我們將GetRegister()重寫如下:

BYTE GDT[6] = {0};
DWORD dwH2GValue;
void __declspec(naked) GetRegister()
{
	_asm
	{
		pushad
		pushfd
			mov eax,0x8003f00c
			mov ebx,[eax]
			mov dwH2GValue,ebx
			sgdt GDT;
		popfd
		popad
		
		retf
	}
}
void PrintRegister()
{
	DWORD GDT_ADDR = *(PDWORD)(&GDT[2]);
	WORD GDT_LIMIT = *(PWORD)(&GDT[0]);
	printf("%x %x %x\n",dwH2GValue,GDT_ADDR,GDT_LIMIT);
}

3. 中斷門

Windows系統大量使用中斷門,主要應用有:
1. 老的CPU在使用系統調用時要從3環進入0環,使用的就是中斷門
2. int 3就是執行中斷門的指令

中斷門保存在IDT中,IDT是由多個描述符組成的,跟GDT不同的是,IDT的第一個元素不是NULL,IDT中全部都是門描述符,它們分別是任務門描述符、中斷門描述符、陷阱門描述符。
在WinDbg中查看IDT的基址使用r idtr指令,查看IDT的長度使用r idtl。
中斷門描述符結構如下圖所示:

中斷門的調用可以直接使用INT 0x20這樣的指令,0x20代表IDT表中的下標,下標處的中斷門描述符決定了是否提權和接下來要執行的代碼偏移。

4. 陷阱門

陷阱門跟中斷門基本是一樣的,主要的區別是中斷門執行時,IF位會被清零,但陷阱門不會。IF位是EFLAGS寄存器裏下標爲9的位,IF位爲0的話程序不會再接收可屏蔽中斷,什麼是可屏蔽中斷?即可以被屏蔽的中斷,什麼意思?舉個例子,鍵盤是通過向CPU發送中斷來讓系統感知的,但是這類中斷是可以被屏蔽的,屏蔽了以後按鍵就不會再被接收了。

5.TSS段

之前說過,當CS的CPL改變時,SS也會隨之改變,ESP跟着也會產生變化,那麼這兩個的值是從哪裏來的呢?答案是TSS,即任務狀態段。

需要注意的是TSS、TSS段描述符、TR寄存器是三個不同的結構

5.1 TSS

TSS是一塊內存,大小104字節,它不在CPU中,它在內存中,它的結構如下:

TSS雖然叫任務段,Intel最初設計它的時候希望用於任務切換,但是Windows並沒有採用,TSS在Windows中只是用於換掉一堆寄存器。

5.2 TR寄存器

CPU中有一個TR寄存器,它是段寄存器的一種,它的選擇子保存了它在GDT表中的位置,通過加載TSS段描述符到TR寄存器中給寄存器賦值,當系統需要訪問TSS的時候,系統會通過TR寄存器中的Base和Limit獲取到TSS的位置和大小。

5.3 TSS段描述符

TSS段描述符是系統段的一種,它的結構跟其他段描述符差別不大

通過LTR指令可以將TSS段描述符加載到TR寄存器,但是LTR指令只能在0環使用,使用STR指令可以讀TR寄存器,但是隻能讀TR寄存器可見的16位選擇子。

5.4 修改TSS

我們可以通過LTR指令修改TR寄存器,修改了TR寄存器就可以修改TSS,但是LTR指令只能在0環使用。
我們知道使用JMP FAR訪問一個代碼段的時候,改變的是CS和IP,當JMP FAR訪問的是一個TSS段的時候,改變的就是TR寄存器了,比如說JMP 0x48:0x12345,0x48是段選擇子,如果這個段選擇子內部的Index處是一個TSS段描述符,系統就會將這個描述符加載到TR寄存器中。

6.任務門

通過任務門可以訪問TSS,任務門描述符如下圖:

任務門中保存了一個TSS段的選擇子,這個選擇子用來找到GDT表中的TSS段描述符。

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