利用门描述符从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
运行代码