Windows系統並沒有使用調用門,但是沒用並不等於沒有,所以我們這次主要是靠自己實現,來完成這次的探究
關於調用門的說明在手冊第三卷的5.83中
下面是調用門的段描述符格式
可以看到有些值是固定的,這是調用門特有的,我們來逐個看一下段描述符格式裏的各個意思
The segment selector field in a call gate specifies the code segment to be accessed. The offset field specifies the
entry point in the code segment. This entry point is generally to the first instruction of a specific procedure. The
DPL field indicates the privilege level of the call gate, which in turn is the privilege level required to access the selected procedure through the gate. The P flag indicates whether the call-gate descriptor is valid. (The presence
of the code segment to which the gate points is indicated by the P flag in the code segment’s descriptor.) The
parameter count field indicates the number of parameters to copy from the calling procedures stack to the new
stack if a stack switch occurs (see Section 5.8.5, “Stack Switching”). The parameter count specifies the number of
words for 16-bit call gates and doublewords for 32-bit call gates.
Note that the P flag in a gate descriptor is normally always set to 1. If it is set to 0, a not present (#NP) exception
is generated when a program attempts to access the descriptor. The operating system can use the P flag for special
purposes. For example, it could be used to track the number of times the gate is used. Here, the P flag is initially
set to 0 causing a trap to the not-present exception handler. The exception handler then increments a counter and
sets the P flag to 1, so that on returning from the handler, the gate descriptor will be valid.
段選擇子確定了將要訪問的代碼段
偏移量確定了在該代碼段中的入口點(該入口點通常指向特定例程的第一條指令)
DPL指明瞭該調用門的特權級,也就是通過該調用門訪問該例程所
必須具備的特權級
P標誌指明該調用門描述符是否有效
參數計數域的作用是,當發生了棧切換時,需要從調用進程的棧拷貝到新
棧的參數個數
調用門的大概執行流程
CALL CS:EIP//(EIP是廢棄的)
1.根據CS的值查GDT表找到對應的門描述符,這個描述符是一個調用門描述符
2.根據門調用門描述符裏的代碼段段選擇子查GDT表找到代碼段的段描述符
3.根據段選擇子指向的段描述符中的Base+調用門中的偏移量 就是真正要執行的地址
當然這裏沒有說權限檢查的問題,一是前面說過,二如果想看更詳細的權限問題可以去翻下手冊中第三卷5.8.4
下面我們來手動構造一個調用門(無參)
假設調用門的偏移是0x00401000 代碼段選擇子爲08(0環代碼段)
Offset in Segment 31:16 P DPL S Type Param Count
0000 0000 0100 0000 1 11 0 1100 0000 0000
Segment Selector Offset in Segment 15:00
0000 0000 0000 1000 0001 0000 0000 0000
然後寫入GDT表
然後編譯以下代碼
#include "pch.h"
#include <iostream>
#include <windows.h>
BYTE GDT[6] = { 0 };
DWORD GDT_ADDR = 0;
DWORD dwValue = 0;
//0401000h
void __declspec(naked)GetRegister()
{
__asm
{
//int 3;
pushad;
pushfd;
mov eax, 0x80b95048;
mov ebx, [eax];
mov dwValue, ebx;
sgdt GDT;//特權指令
popfd;
popad;
retf //不是ret
}
}
int main()
{
char buff[6] = { 0 };
*(DWORD*)&buff[0] = 0x12345678;
*(WORD*)&buff[4] = 0x48;
__asm
{
call fword ptr[buff]
}
GDT_ADDR = *(PDWORD)(&GDT[2]);
printf("GDT:%x HighAddress:%x\n", GDT_ADDR,dwValue);
system("pause");
GetRegister();
return 0;
}
運行,查看輸出
可以看到成功運行了調用門的代碼,讀取了高2g的內存,說明我們成功通過調用門提權,讀取高2g的的內存
下面我們再來做一下調用門有參的實驗,如果需要傳參那麼我們只需要修改Param Count字段即可
Offset in Segment 31:16 P DPL S Type Param Count
0000 0000 0100 0000 1 11 0 1100 0000 0011
Segment Selector Offset in Segment 15:00
0000 0000 0000 1000 0001 0000 0000 0000
寫入GDT表
編譯如下代碼
#include "pch.h"
#include <iostream>
#include <windows.h>
DWORD A = 0;
DWORD B = 0;
DWORD C = 0;
//0401000h
void __declspec(naked)GetRegister()
{
__asm
{
//int 3;
pushad;
pushfd;
mov eax,[esp + 0x24 + 0x8 + 0x8];
mov dword ptr ds : [A], eax;
mov eax,[esp + 0x24 + 0x8 + 0x4];
mov dword ptr ds : [B], eax;
mov eax,[esp + 0x24 + 0x8 + 0x0];
mov dword ptr ds : [C], eax;
popfd;
popad;
retf 0xc //不是ret
}
}
int main()
{
char buff[6] = { 0 };
*(DWORD*)&buff[0] = 0x12345678;
*(WORD*)&buff[4] = 0x48;
__asm
{
push 1;
push 2;
push 3;
call fword ptr[buff]
}
printf("A:%x B:%x C:%x\n", A,B,C);
system("pause");
GetRegister();
return 0;
}
運行
可以看到我們已經讀出了我們傳入的參數,證明了傳參是有效的,這裏需要我們手動壓棧和平衡棧,那麼我們在尋址參數時候爲什麼要這樣寫呢
mov eax,[esp + 0x24 + 0x8 + 0x8];
mov dword ptr ds : [A], eax;
mov eax,[esp + 0x24 + 0x8 + 0x4];
mov dword ptr ds : [B], eax;
mov eax,[esp + 0x24 + 0x8 + 0x0];
mov dword ptr ds : [C], eax;
首先+0x24是因爲我們使用了pushad和pushfd
加起來壓入的一共是0x24位,再+0x8可以參考我們以前發過的圖
是代表返回的eip和cs,我們可以下斷看看
這一段就是代表返回的eip和cs,後面的就是我們的參數
總結:
當通過門,權限不變的時候,只會PUSH兩個值:CS 返回地址
新的CS的值由調用門決定
當通過門,權限改變的時候,會PUSH四個值:SS ESP CS 返回地址 新的CS的值由調用門決定 新的SS和ESP由TSS提供
通過門調用時,要執行哪行代碼有調用門決定,但使用RETF返回時,由堆棧中壓人的值決定,這就是說,進門時只能按指定路線走,出門時可以翻牆(只要改變堆棧裏面的值就可以想去哪去哪)
可不可以再建個門出去呢?也就是用Call 當然可以了 前門進 後門出
擴展:進門的時候只能走大門進去,但是出去的時候,RETF返回的依據就是堆棧中的值,ESP+0x24,如果在堆棧裏直接更改這個值爲新的函數,就會直接返回到別的地方