利用门描述符从3环进入0环


利用门描述符从3环进入0环

门描述符格式

在开始前先了解一下门描述符的作用

门描述符不描述某种内存段,而是描述控制转移的入口点,简单来说就是中介,通过这道门,可实现任务内特权级的变换和任务间的切换,获取最后的访问地址。

门描述符格式

Attributes

m+4 - m+5为门描述符的Attributes(全局描述符m+5 - m+6为Attributes),其中

· 5、6、7位为保留位,无作用

· DT位为1时代表这八位时全局描述符,DT为0表示门描述符,所以DT恒为0,这里的DT和全局描述符中的DT位置相同,都在m5的第5位

· P和全局描述符一样,1为有效

· DWORD Count:调用门(call 等)push的参数(上层堆栈的参数)数量存入DWORD Count ,存的值即为push入的参数个数

· Type见下图

系统段和门描述符类型字段的编码及含义

Selector和Offset

由于在上述的DT为0时,CS:EIP失效,所以要在门描述符中指定cs和EIP,这里的Selector(选择子)便代表CS的值,Offset高16位对应EIP高16位,低16位对应EIP低16位。

当Type == 0xC时,执行调用门,Selector获取GDT中的值,对于直接用CS访问GDT的方式又加了一层中转。

全局描述符中有

cs.base cs.attributes cs.selector cs.limit

eip #符中新增一项EIP,即门描述符表

offsetH attributes selector(CS) offsetL
|
–>获取GDT

段寄存器选择子 -> 访问门描述符 -> 门描述符选择子 -> 全局描述符

编写访问内存的函数

#include "stdafx.h"
#include <windows.h>

WORD w_cs;
WORD w_ss;

//因为要实现retf功能,所以使用裸函数
_declspec(naked)void fun() 
{
    _asm
    {
        push ebp
        mov ebp,esp
        sub esp,0x40
        push ebx
        push esi
        push edi
    }

    //此时以及在0环内,将0环的段寄存器存到全局变量
    //因为0环不能调用3环的函数,所以要在retf回到3环时打印出来,否则可能会蓝屏
    _asm
     {
         mov ax,cs
         mov cx,ss
         mov w_cs,ax  
         mov w_ss,cx
     }

    _asm
    {
        pop edi
        pop esi
        pop ebx
        mov esp,ebp
        pop ebp
        retf
    }
}

int main(int argc, char* argv[]){
    _asm
    {
        _emit(0x9a) //call far
        //下面4个字节可以随便填,因为后两个字节cs指向门描述符,EIP用门描述符中的偏移代替
        _emit(0x12)
        _emit(0x34)
        _emit(0x56)
        _emit(0x78)
        //b3对应偏移b0,在执行之前在全局描述符表b0偏移处设置门描述符
        _emit(0xb3)
        _emit(0x00)
    }

    printf("w_cs = %x  w_ss = %x\n", w_cs,w_ss);
}

WinDBG修改GDT

上述代码实现了简易进入0环的方式,一些步骤还需要在WinDBG中修改

在WinDBG中使用Ctrl+Pause断下虚拟机,输入dq gdtr L30,查看GDTR部分数据,寻找一个空的内存进行填充,此处选择b0

查找空地址

在函数调用前下个断点,F5编译运行代码后获取函数的入口地址,获取到fun的入口地址0x0040100a

获取函数内存地址

计算门描述符的值

offsetH P DPL DT TYPE 00000000 Selector OffsetL

0040 1 11 0 1100 00 0x0008 100a

其中DPL为3环权限,使用11,选择子为0环的CS,选择0x8

合起来就是0040ec00`0008100a

在WinDBG使用eq 8003f0b0 0040ec00`0008100a修改内存中的值

查找修改后的地址

接着去运行程序可以发现CS和SS被成功打印

优化程序 获取GDT

由于每次修改代码后都需要确认函数地址是否被修改,会很麻烦,所以直接将函数拷贝到固定地址中进行保存

#include "stdafx.h"
#include <windows.h>

WORD w_cs;
WORD w_ss;

_declspec(naked)void fun() 
{
    _asm
    {
        push ebp
        mov ebp,esp
         //此处多分配4个字节变量i
        sub esp,0x44
        push ebx
        push esi
        push edi
    }

    _asm
     {
         mov ax,cs
         mov cx,ss
         mov w_cs,ax  
         mov w_ss,cx
     }

    BYTE* pGdtAddress;
    int i;

    pGdtAddress = (BYTE*)0x8003f000; //GDTR地址

    for (i = 0; i < 0x400; i++ ){
        GdtBuffer[i] = pGdtAddress[i];
    }

    _asm
    {
        pop edi
        pop esi
        pop ebx
        mov esp,ebp
        pop ebp
        retf
    }
}

DWORD GetFunAddress(void* Fun)
{
    BYTE* p;
    p = (BYTE*)Fun;
    if (*p == 0xe9){
        p = p+5+ *(DWORD*)&p[1];
    }
    return (DWORD)p;
}

int main(int argc, char* argv[])
{
    //---分配内存,保存函数硬编码---
    //0x60000000地址在实际编译后的地址中很少见,分配后基本不冲突
    LPVOID pAddress;
    pAddress = VirtualAlloc((void*)0x60000000,0x4000, MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE); 
    if ( pAddress != NULL ){
        memcpy(pAddress, (void*)GetFunAddress(fun),0x200);    
    }
    memset(GdtBuffer,0x0,0x4000);
    //-----------------------------

    int i;
    _asm
    {
        _emit(0x9a)
        _emit(0x12)
        _emit(0x34)
        _emit(0x56)
        _emit(0x78)
        _emit(0xb3)
        _emit(0x00)
    }
    printf("cs = %x  ss = %x\n", w_cs,w_ss);
    for ( i = 0; i < 0x100; i+= 2){
        printf(" 0x%08x`0x%08x\n", *(DWORD*)&GdtBuffer[i*4+4], *(DWORD*)&GdtBuffer[i*4]);
    }
    return 0;
}

如果黑框一闪而过,使用Ctrl+F5运行代码

成功运行


文章作者: Kevin。
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kevin。 !
评论
 上一篇
系统段描述符表IDTR 系统段描述符表IDTR
系统段描述符表IDTR中断门和陷阱门不同描述符表中的值代表不同类型的门,放在gdtr表里面的门叫做调用门,放在idtr表里面的门叫做中断门和陷阱门 中断门和陷阱门描述中断/异常处理程序的入口点。中断门和陷阱门内的选择子必须指向代码段描述符,
2022-04-14
下一篇 
存储段描述符表 存储段描述符表
存储段描述符表GDT和GDTRGDTGDT(Global Descriptor Table)全局描述符表,一个处理器仅有一个GDT,主要用于内存转换,所有程序的内存访问都需要用到GDT中的有关内存区域即x86内存分段的信息。描述符每一项都为
2022-04-09
  目录