Windows內核基礎知識-5-調用門(32-Bit Call Gate)

Windows內核基礎知識-5-調用門(32-Bit Call Gate)

調用門有一個關鍵的作用,就是用來提權。調用門其實就是一個段。

調用門:

 

 

這是段描述符的結構體,裡面的s欄位用來標記是程式碼段還是數據段還是系統段,前面解析的時候講的是 S==1的情況,也就是Code or data的情況,這次的調用門就是當s==0時的情況。

當s==0時,type的內容如下:

 

 

那麼調用門其實就是 type為12的情況下的一個段暫存器

32-Bit Call Gate。32位情況下的一個調用門。

就是一個 段描述符下且S==0 && type==12的一個段。

調用門的段描述符:

 

 

利用調用門提權

我們可以通過構造段選擇子和段描述符來自己添加一個調用門也就是添加一個段來自己使用。

因為段暫存器無非就是擁有一個起始地址然後作為一個段的內容來讓你使用。用彙編寫過程式碼的肯定知道如果構造一個段:

sample PROC
    ret
sample ENDP

 

那麼我們自己構造一個系統段來執行我們的程式碼這不就行了嗎。段暫存器只是系統默認的方便我們使用,我們自己通過 segment:eip這樣來跳轉使用不就完事了嗎。

段暫存器比如說 ss(stack segment)無法也就是把棧段的內容通過段選擇子和段描述符來集成了而已。

所以這裡我們直接添加自己的段,也就是這裡所謂的調用門

解析調用門描述符:

我們要自己配一個調用們,肯定需要知道怎麼配,段選擇子我們知道了,這裡還需要知道段描述符:

 

 

這個和前面的段暫存器的段描述符大相徑庭,只是有一些不一樣。

和前面的段描述符一樣,上面的是高32位地址,下面的是低32位地址。

欄位 內容
offset in segment 要跳轉的函數的地址,或者是要跳轉的地址
segment selector 段選擇子,要變成的段選擇子(提權的關鍵)
Param Count 函數參數個數
高位5-7 固定的三個0
Type 系統段只能是1100(10進位的12)
高12地址 就是段描述符的S欄位,就系統調用的必須是0
DPL 肯定賦值為3呀,這樣ring3才能。
p 和段描述符一樣表示該段是否有效,當P為0時無效,1時有效。

構造段描述符:

segment selector段描述符欄位,可以通過WinDbg附加Windows雙機調試時查看cs段暫存器的值就行了,因為此時系統正在啟動肯定是在r0的情況下的:

 

 

所以這裡的段選擇子就構造為: 0008(因為段選擇子是兩個位元組16位)

然後再根據前面的解析我們目前的構造是這樣的:

高32位:
    0-3:
    0(十六進位) 
    4-7:
    0(十六進位)
    8-11:
     C(十六進位)
    12-15:
     E(十六進位)
     16-31函數地址的高地址:xxxx
低32:
    0-15:
        函數地址的低地址:xxxx
    16-31:
        0008
合集:
    xxxxEC000008xxxx

 

目前就是函數地址需要解決,這個函數地址的話其實就是我們的程式碼的起始地址,然後就處理這個函數的內容了。

我們可以寫一個函數不就完了嘛,但是需要修改成固定基址,這樣函數地址就不會改變了。需要採用release版本,因為debug版本有個jmp,然後還得修改優化和隨機基址

 

 

 

 

 

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //這裡我們訪問一個0環才能訪問的地址
        //這樣就知道是否是拿到了0環的許可權
        push eax
        mov eax,0x80b95040
        mov eax,[eax]
        pop eax
        ret
    }
}
​
int main()
{
    printf("%x\n", test);//輸出函數的地址
    system("pause");
    return 0;
}

 

這裡我的函數地址是:00401080。

所以完整的段描述符值為:

    0040EC0000081080

 

將段描述符添加到gdt表:

 

 

這個段描述就到了gdt表偏移9的地方了。

使用調用門

前面調用們的段描述符我們已經配置好了。

現在需要使用調用門了,需要學習兩個指令:

call far    ;跨段調用  長調用
jmp far     ;跨段跳轉  長跳轉

 

調用門只能採用call far,jmp far無法,因為jmp far無法做到越級這個作用。

然後還要配一個段選擇子:

 

 

RPL:
    00//採用0環
TI:
    0
Index
    10010100 1011

 

最終的段選擇子就是:0x48

那麼應該call far的地址為: 0x48:xxxx

但是在內聯彙編里不能這樣寫,只能這樣寫:

    BYTE code[] = {0,0,0,0,0x48,0};
    //0,0,0,0 是eip,然後0x0048是段選擇子,採用的是小端位元組序
    //這裡用0000是因為這裡不會用到
    //因為段描述符裡面已經指定了函數的地址了會自動跳轉,所以寫啥都行
    _asm
    {
        call far fwrod ptr code
    }

 

完整程式碼:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //這裡我們訪問一個0環才能訪問的地址
        //這樣就知道是否是拿到了0環的許可權
        push eax
        mov eax,0x80b95040
        mov eax,[eax]
        pop eax
        ret
    }
}
​
int main()
{
    //跳轉
    BYTE code[] = {0,0,0,0,0x48,0};
    //段選擇子為 0000
    _asm
    {
        call far fwrod ptr code
    }
    system("pause");
    return 0;
}

 

然後拿到虛擬機里運行一下:

 

 

 

 

直接系統出錯WinDbg捕獲了並且藍屏了,但是至少有一個可以確定,就是我們肯定是跑到內核去執行了,不然怎麼會導致系統出錯呢,r3的應用層程式碼肯定不會導致系統的問題的。

就出現問題很正常,打幾個斷點觀察下:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //這裡我們訪問一個0環才能訪問的地址
        //這樣就知道是否是拿到了0環的許可權
        int 3
        push eax
        mov eax, 0x80b95040
        mov eax, [eax]
        pop eax
        ret
    }
}
​
int main()
{
    //跳轉
    BYTE code[6] = {0,0,0,0,0x48,0};
    //段選擇子為0x0048
    __asm
    {
        call far fword ptr code
    }
    printf("%x\n", test);
    system("pause");
    return 0;
}

 

 

 

通過單步調試彙編程式碼發現是,這個ret的原因,call far或者jmp far,需要採用retf來使用。

改成retf:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //這裡我們訪問一個0環才能訪問的地址
        //這樣就知道是否是拿到了0環的許可權
        int 3
        push eax
        mov eax, 0x80b95040
        mov eax, [eax]
        pop eax
        retf
    }
}
​
int main()
{
    //跳轉
    BYTE code[6] = {0,0,0,0,0x48,0};
    //段選擇子為0x0048
    __asm
    {
        call far fword ptr code
    }
    printf("%x\n", test);
    system("pause");
    return 0;
}

 

這樣之後我們函數內的程式碼是正常執行了,但是還是藍屏了,通過我的觀察,是段暫存器的問題,這裡就通過od的觀察看進入前和進入後的問題就可以判斷是這個問題了。

所以這裡我直接不要這個int 3斷點了,可能在r0和r3下的int 3斷點執行的東西不一樣把:

#include<iostream>
#include<Windows.h>
using namespace std;
​
void _declspec(naked) test()
{
    _asm
    {
        //這裡我們訪問一個0環才能訪問的地址
        //這樣就知道是否是拿到了0環的許可權
        push eax
        mov eax, 0x80b93040
        mov eax, [eax]
        pop eax
        retf
    }
}
​
int main()
{
    //跳轉
    BYTE code[6] = {0,0,0,0,0x48,0};
    //段選擇子為0x0048
    __asm
    {
        call far fword ptr code
    }
    printf("%x\n", test);
    system("pause");
    return 0;
}

 

這樣我們的程式就可以美美的執行結束了:

 

 

小結

段是一個很重要的概念用來進行記憶體分割,一個段的描述有很多資訊保存在段描述符裡面,由於段很多所有就有段描述符表,然後這個表呢由一個段選擇子來指向獲取表內哪一個段描述符,而段暫存器是CPU為了方便使用的一個暫存器,用來保存的是段選擇子這個東西。所以這裡我們用的是一個系統調用段來執行我們的指令就是作業系統內部的操作所允許也是intel架構的內容,比較複雜,這裡的話我們就明白到這裡就可以了想深入的可以閱讀本部落格後面的參考文獻。

 

 

 

參考文獻:(3條消息) 段選擇符 段暫存器farmwang的專欄-CSDN部落格段暫存器

參考文獻:(3條消息) 段選擇符tasty-CSDN部落格段選擇符