保護模式-第五講-門-調用門


保護模式-第五講-門-調用門

一丶長調用與短調用

1.1 長跳轉 與長調用

在上一講 實現了利用 長跳轉 來實現了段間的跳轉

jmp far 0x00xx:xxxx地址

並且構造段描述符. 將段描述符放入GDT表中. 構造段選擇子來實現了 段間跳轉.

但是長跳轉只限於 段間跳轉. 也就是一個段中. 因為在一個段中. 最后的偏移 加 段描述符.base才能構成真正的跳轉地址.

那么如果想實現跨段調用. 就必須要用長調用

長調用 本質就是 call 與 ret的組合. 只不過有些許不同 因為會堆棧產生影響

call far 指令來實現長調用

1.1.1短調用

短調用本質就是 call + ret組合.

查看如下堆棧圖:

在我們正常調用的時候對戰圖就會如上. 首先有參數 然后緊接着返回地址.在每調用call 之前.參數位置就是esp的位置call 之后 esp就遞減 改變的寄存器有 ESP EIP 這就是短調用. 在ring3 x86下.遇到的call 基本都是段調用

call 其實我們要進行分開操作可以是如下

sub esp,4
mov eip,xxxx
push 下一行地址

call 只不過將三行匯編進行了總結.變成了一行.

ret指令的原理

mov eip,返回地址
如果是stdcall add esp,xxx
jmp eip

1.1.2 長調用 (跨段不提權的調用)

提權:

 提權之后.在ring3下可以使用特權指令.跟內核權限一樣

指令給格式為:

call cs:eip (EIP並不使用)

EIP不會使用的. cs是一個段選擇子. 這個段選擇子還是去查表. 查詢的是GDT表.

只不過有些許不同.查詢出來的GDT表中的段描述符.也是8個字節. 不過結構確實 門描述符

結構

也就說.長調用 最終調用在哪里. 是由調用們(段描述符)來指定的. 而不是EIP執行的. 所以說

EIP是廢棄的

跨段不提權的意思:

我們經過上面引出了 長調用. 長調用其實是去GDT表中查表. 查到的段描述符記錄着一個

地址.以及對應的選擇子.這個后面會說. 跨段不提權的意思就是. 你當前調用的環境CPL 是3

而調用門中記錄的 段選擇子以及對應的地址.DPL也是三. DPL為3 代表你CPL可以訪問.

雖然跨段了.但是權限還是一樣的. 所以這就叫做跨段不提權. 但是如果我們將構造的調用門的

DPL改為0. 那么在進行調用的時候就會發生提權了. 當然我們的RPL CPL也要進行修改.

跨段不提權的堆棧圖

與上面唯一不同的是.參數要進行保存.保存我們的CS寄存器.

發生改變的寄存器有 ESP EIP CS

在段調用中.我們是 call 與 ret 配合使用. 在長調用中. 比如是 retf 這點需要注意.

retf執行之后.我們的堆棧會產生如下結果

1.修改esp的值

2.將保存的cs的值 還原.

1.1.3 長調用(跨段並且提權)

指令格式是一樣. 堆棧圖進行了改變

call 執行之后的對戰圖

可以看到,在進行call 調用之前.會保存調用者的SS ESP CS

為什么要保存堆棧. 原因是 當跨段提權的時候. 堆棧不是ring3的堆棧了. 也就是 調用call 之前的esp 是ring3

的堆棧 .而在保存調用者的ss esp cs的時候堆棧已經切換到 ring0的了.

1.1.4 總結

1.跨段調用的時候. 一旦有權限的切換.那么就會切換堆棧

2.如果cs的權限一旦改變.那么對應的ss權限也要該表. cs與ss等級必須是一致. 這是inter規定

3.跨段跳轉(jmp far) 只能跳到同級別 非一致代碼段. 但是call far則可以通過調用們 提權. 提升CPL權限.

之前我們做實驗 jmp far 跳轉的時候.如果段描述如的DPL修改了. 就算你ring3的 RPL修改了.也是沒用了.

因為你的CPL還是3 也就是低權限. 所以我們要想辦法提權.

二丶調用門

2.1 調用門的執行流程

調用門 依賴於 call far 指令.指令格式為

call cs:EIP

當執行這條指令的時候指令的執行流程如下

1.根據CS段選擇子 查詢GDT表. 找到對應的段描述符. 這個段描述符一樣也是8個字節. 不過這個段描述符要解析為

調用門描述符

2.調用們描述符中 存儲這個一個你給定的地址. 以及一個代碼段的段選擇子. 以及DPL等權限

3.調用門中有地址. 也有段選擇子. 再根據段選擇子查詢對應段描述符. 從對應的段描述符中 找出記錄的BASE

以 BASE+調用門中記錄的地址 進行調用. 記錄的地址就是我們真正要執行的地址. 只不過我們要 以base+偏移的方式進行組合.組合為一個真正的地址.

2.2 調用門描述符

根據inter手冊所說.調用們總共完成了六種功能

1.確定了你要訪問的代碼段(也就是低16-31位 記錄了一個代碼段的選擇子.可以繼續進行查表得出代碼段)

2.定義了在指定的代碼中的一個例程入口 (也就是指定了一個偏移地址.我們知道都是段.base+偏移進行調用的

高16-31位 記錄一個偏移地址的高地址,低32位字節中的 0-15位 記錄了偏移的低地址 也就是我們要執行一個函數

要把這個函數的地址填寫到這里.根據高低位分開)

3.指明了該例程的特權級別 (也就是DPL )

4.如果棧發生了切換. 要確定在棧之間拷貝的 可選參數的個數 (也就是高位 0-4 代表了參數個數)

5.定義了棧的尺寸. 16位執行16位的棧 32執行32的棧

6.確定了調用們描述符是否有效(P位)

我們之前的段描述符 有 s位以及type位. 在調用門描述符里面. 統一進行寫死了. 第12位 = 0 11-8固定的是1100

因為 s位 我們學過段描述符 當 s位 = 0 代表是系統段描述符. 調用門描述符就是系統段描述符. 而當其type = 1100的時候.才代表是一個調用門描述符

2.3 調用門進行代碼段訪問的流程

調用門進行代碼段訪問的時候會執行如下流程

1.驗證CPL當前的特權級別

2.驗證調用門的段選擇子的RPL

3.驗證調用門的 DPL權限

4.驗證以下目標代碼段中的 段一致性 (這句意思就是 我們門不是保存了一個代碼段的段選擇子嗎. 段選擇自己進行查表得出段描述符. 然后驗證這個段描述符的 CFLAG)

2.4 構造門描述符表

根據門描述表我們先進行構造

1.31-16位 為Base位. 我們不知道首先設置為0 0x0000

2.p DPL 以及第13位. 其中P DPL都為1 DPL位0的話我們ring3無法訪問. 的初始 1110 16進制 = 0xE

3.type位是寫死的. 所以是 0x1100 16進制 = 0xC

4.第八位到第0位.其中包括參數個數.以及一些標志.這里沒有都設置為0 = 0x00

高四個字節總結出來位

0x0000EC00

低32位構造

1.31-16段選擇子 設置為8 拆分開 =1000 也就是第一項.RPL = 0 找到的GDT表也就是第一項(注意從0開始)

這里設置位GDT[1] 要注意GDT[1]的DPL權限. 我們上已經構造 jmp far的時候講過了.

而我們說的調用們提權與不提權 就是這里的段選擇子的設置. 0x008代表提權. RPL = 0 如果不想提權 就尋找ring3的可訪問的代碼段. 比如 0x1B 二進制 = 11011 RPL = 11 TI = 0 高兩位也是11 也即是3 也就是尋找GDT[2]

2.跳轉地址的低2位. 沒有也設置為0

總結出來段描述符 = 0x0000EC00~0x00080000

這個就是一個段描述符.但是我們沒有指定地址. 如何指定地址.需要我們在ring3中寫代碼.

然后將函數地址填入到里面.

比如你ring3的函數地址為 0x00401230 那么構造到門描述符中就是

0x0040EC00 ~ 0X00081230 此時將調用們寫入到GDT表中即可. 然后ring3 使用 Call Far cs:xxxx 來調用即可. 其中cs是段選擇子. 這個段選擇子就是你寫入GDT表中的門描述符

2.5 代碼實現 調用門 無參提權

首先調用門我們可以構造出來

0x0040EC00 ~ 0X00081230 當然我程序中的地址可能不一樣.這里舉個例子

然后將調用們寫入到 GDT表中空項中

構造選擇子給ring3 使用

其中比如0x401230 是我們在ring3看的. 我們想讓它跳轉到哪里. 我們就切換到反匯編窗口進行拷貝地址. 然后設置進去.

所以我們的ring3調用代碼.首先就要先調試以下獲取 要跳轉的函數地址.

代碼如下:

#include <stdio.h>
#include <WINDOWS.H>
#include <STDLIB.H>

//我們要跳轉的地址在這里. 地址是 0x00401020
__declspec(naked) void run()
{
	__asm 
	{
		int 3;

		retf;
	}
}
int main(int argc, char* argv[])
{
	char szAddress[6] = {0};

	*(unsigned int*)&szAddress[0] = 0x00000000;  //EIP No use  EIP 沒有 使用
	*(unsigned short *)&szAddress[4] = 0x00;     //設置調用門 所在的段描述符的選擇子 待我們將門描述符設置到GDT表中在修改

	_asm
	{
		// call far fword ptr ss:[ebp-xxx];
		call  fword ptr[szAddress];
	}
	return 0;
}

代碼有了.將門描述符寫入到GDT表中.

將門描述符寫入到GDT表中的第 13項. 因為下標是從0開始. 所以下標為12才為我們實際的段描述符位置. 所以段選擇子

構造為 12 RPL = 0 TI = 0 12的二進制為 1100 組合起來 TI RPL = 1100000 = 0x60 所以在代碼中我們設置我們的門描述符的段選擇子為0x60

當我們進行調用的時候發現我們的內核調試器會優先的捕獲到. 可以在內核調試其中看到 我們ring3的代碼. int 3 與 retf

這里就說明了我們ring3中調用的函數 是具有內核權限的了.

因為我們是提權了. 調用們中的記錄的段選擇子是提權的 所以我們看下堆棧

分別記錄了 返回地址 調用者的CS 調用者的ESP 調用者的SS

2.6 構造有參調用門

之前講的調用們是無參數的. 那么有參數的也需要自己構造其中 在調用門的 參數位置我們設置一個參數個數. 然后在調用的時候自己去Push 以及注意平棧

如調用門為: 0x0040EC03 ~ 0X0008D480 這個調用們指定了我們 參數個數為三個. 一個四個字節大小.

2.7 有參調用門代碼

// 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 
	{
		pushad;
		pushfd;
		mov eax,[esp + 0x24 + 0x8];
		mov dword ptr ds:[x],eax;

		mov eax,[esp + 0x24 + 0xC];
		mov dword ptr ds:[y],eax;

		mov eax,[esp + 0x24 + 0x10];
		mov dword ptr ds:[z],eax
		
		popfd;
		popad;
		retf  0xC;   //壓入 三個參數 所以平棧的時候也就是3個參數 x86下一個參數4個字節 所以是0xC 
	}
}

void printXyz()
{
	printf("%d,%d,%d \r\n",x,y,z);
}
int main(int argc, char* argv[])
{
	char szAddress[6] = {0};

	*(unsigned int*)&szAddress[0] = 0x12345678;  //EIP No use  EIP 沒有 使用
	*(unsigned short *)&szAddress[4] = 0x60;     //設置調用門 所在的段描述符的選擇子 

	_asm
	{
		// call far fword ptr ss:[ebp-xxx];
		push 1;
		push 2;
		push 3;
		call  fword ptr[szAddress];
	}

	printXyz();
	return 0;
}


2.8 有參調用門的堆棧

當我們進行有參數調用的時候查看堆棧.

堆棧已經完全改變了. 特別是我們執行完 pushad pushfd 之后.

我們先看一下原始堆棧

畫圖看一下

查看堆棧圖可以看到. 我們的返回地址下面.就是調用者CS. 而調用者CS下面 變成了參數了.而不是調用者的ESP了.

所以我們想要尋到參數的值. 就必須 使用 [esp + 8 + 4] = 參數1 +8 = 參數2 + c = 參數3 +10 = 調用者ESP +14 = 調用者SS

而我們如果pushad pushfd之后.那么顯而易見. pushad 是所有通用寄存器入棧. 也就是8個 pushfd 是EFlag入棧. 所以總共

是入棧9個參數. 9個參數之后才是返回地址. 其它依次類推. 所以我們想要尋得參數的值 才必須要寫為如下

0x24 = (pushad + pushfd) = 9個入棧. 一個四個字節  9 * 4 = 36 16進制 = 0x24

[esp + 0x24]   == 返回地址位置

[esp + 0x24 + 4] == 調用者CS
[esp + 0x24 + 8] == 參數1
[esp + 0x24 + C] == 參數2
[esp + 0x24 + 10] == 參數3
依次類推
也可以寫為如下.直接尋找參數的值
[esp + 0x24 + 8 + 0] = 參數1
[esp + 0x24 + 8 + 4] = 參數2
[esp + 0x24 + 8 + 8] = 參數3

三丶總結

1.調用門提權的話 直接在 門描述符中記錄的選擇子進行設置即可提權

2.調用門的本質就是 記錄一個函數地址. 以及一個代碼段的段選擇子. 當使用調用門的時候. 本質就是 讓代碼段的.base+ 記錄的偏移進行跳轉

3.有參的調用門其堆棧是已經完全改變了. 所以要注意. 因為ring3進入內核權限了. 所以要使用寄存器 必須 pushad pushfd 用來保存.


免責聲明!

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



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