编程利用调用门读取GDT[2]并打印。
因为在Windows中没有使用调用门,因此,在GDT表中是没有调用门描述符的.
想要试验一个调用门,就需要自己使用windbg开启双击调试,并自行在GDT中设置一个.
定义结构体并赋值
定义这种系统相关的结构体时,要注意粒度对齐。
#pragma pack(1)
typedef struct _CALLGATEDESCRIPT{
unsigned int functionAddrLow : 16; // 被调用函数地址的低16位
unsigned int SegmentSelector : 16; // 想要切换的段选择子
unsigned int paramCount : 5; // 参数个数
unsigned int none : 3; // 无
unsigned int Type : 4; // 系统段类型, 必须为12(1100b: 32位调用门)
unsigned int S : 1; // S位,必须为0
unsigned int DPL : 2; // 描述符特权级别
unsigned int P : 1; // 描述符有效位
unsigned int functionAddrHig : 16; //被调用函数地址的高16位
}CALLGATEDESCRIPT;
定义一个初始化函数:
unsigned long long createCallGateDescript(unsigned short selector, unsigned int functionAddr, int functionParamSize)
{
CALLGATEDESCRIPT cgd = { 0 };
cgd.P = 1; // 描述符有效位, 必须设置为1
cgd.S = 0; // 系统段描述符, 必须设置0
cgd.Type = 12; // 调用门描述符,必须设置为12
cgd.DPL = 3; // 设置为3,表示此描述符可被3环所使用.
cgd.SegmentSelector = selector;// 要切换的段选择子
cgd.functionAddrHig = (functionAddr & 0xFFFF0000) >> 16;
cgd.functionAddrLow = functionAddr & 0x0000FFFF;
cgd.paramCount = functionParamSize / 4; // 参数个数,如果函数没有参数,填0,如果有参数,则填参数占用栈的字节数 / 4
return *(unsigned long long*) & cgd;
}
GDT表
使用调用门的之前,需要将调用门的描述符写进系统的GDT表中。
2: kd> rgdtr
gdtr=8ff1dc20
读取gdtr寄存器,得到GDT的地址。然后查看GDT表中哪些是空闲的,值为0的都是空闲的。
2: kd> dq 8ff1dc20
8ff1dc20 00000000`00000000 00cf9b00`0000ffff
8ff1dc30 00cf9300`0000ffff 00cffb00`0000ffff
8ff1dc40 00cff300`0000ffff 8f008bf1`875020ab
8ff1dc50 8f4093f1`50003748 0040f300`00008000
8ff1dc60 0000f200`0400ffff 00000000`00000000
8ff1dc70 8f0089f1`aac00068 8f0089f1`ab300068
8ff1dc80 00000000`00000000 00000000`00000000
8ff1dc90 800092b9`500003ff 00000000`00000000
gdt[9]是空闲的,下面构造段选择子:
9 | 0 | 3
1001 0 11
0x4B
//低4字节是eip,高2字节是cs
char buff[] = { 0,0,0,0,0x4b,0};
_asm call fword ptr ds:[buff];
代码
#include <iostream>
#include <iomanip>
#pragma pack(1)
typedef struct _CALLGATEDESCRIPT {
unsigned int functionAddrLow : 16; // 被调用函数地址的低16位
unsigned int SegmentSelector : 16; // 想要切换的段选择子
unsigned int paramCount : 5; // 参数个数
unsigned int none : 3; // 无
unsigned int Type : 4; // 系统段类型, 必须为12(1100b: 32位调用门)
unsigned int S : 1; // S位,必须为0
unsigned int DPL : 2; // 描述符特权级别
unsigned int P : 1; // 描述符有效位
unsigned int functionAddrHig : 16; //被调用函数地址的高16位
}CALLGATEDESCRIPT;
struct SELECTOR {
unsigned short index : 13;
unsigned short T1 : 1;
unsigned short RPL : 2;
};
unsigned long long createCallGateDescript(unsigned short selector, unsigned int functionAddr, int functionParamSize)
{
CALLGATEDESCRIPT cgd = { 0 };
cgd.P = 1; // 描述符有效位, 必须设置为1
cgd.S = 0; // 系统段描述符, 必须设置0
cgd.Type = 12; // 调用门描述符,必须设置为12
cgd.DPL = 3; // 设置为3,表示此描述符可被3环所使用.
cgd.SegmentSelector = selector;// 要切换的段选择子
cgd.functionAddrHig = (functionAddr & 0xFFFF0000) >> 16;
cgd.functionAddrLow = functionAddr & 0x0000FFFF;
cgd.paramCount = functionParamSize / 4; // 参数个数,如果函数没有参数,填0,如果有参数,则填参数占用栈的字节数 / 4
return *(unsigned long long*) & cgd;
}
int g_num;
short g_ss;
int g_esp;
//通过调用门调用的函数
void _declspec(naked) GateFun()
{
g_num = 100;
_asm mov g_esp, esp;
_asm mov ax, ss;
_asm mov word ptr g_ss, ax
_asm retf;
}
int main()
{
printf("调用门函数地址:%08X\n", GateFun);
printf("切换的段选择子:%04X\n", 8);/*8是内核中的代码段选择子*/
unsigned long long descript =
createCallGateDescript(8/*8是内核中的代码段选择子*/, (unsigned int)GateFun, 0);
printf("请将这个段描述符写入到GDT[9]中: ");
std::cout << std::hex << std::uppercase << std::setfill('0') << std::setw(8) << descript << '\n';
system("pause");
// 获取当前寄存器的值.
_asm mov g_esp, esp;
_asm mov ax, ss;
_asm mov word ptr g_ss, ax
printf("调用前 esp=%08X, ss=%04X\n", g_esp, g_ss);
// 前4字节是EIP,后2字节是CS(0x004b)
char buff[] = { 0,0,0,0,0x4b,00 };
// 执行流程:
// 1. 从buff这块内存中取出段选择子:0x4b
//
// 解释 SEL T RPL
// 十进制 9 0 3
// 2. 将段选择子分解,100 1011 ==> 1001 0 11, 得到GDT表中的下标:9
// 3. 取出GDT表中第9项描述符, 是一个调用门描述符.
// 4. 将调用门描述符中的段选择子加载到CS段寄存器
// 5. 将调用门描述符中的函数地址设置到EIP寄存器.
_asm call fword ptr ds : [buff] ;
printf("调用后 esp=%08X, ss=%04X\n", g_esp, g_ss);
printf("g_num=%d\n", g_num);
system("pause");
return 0;
}
运行:
调用门函数地址:00D71080
切换的段选择子:0008
请将这个段描述符写入到GDT[9]中: D7EC0000081080
双机调试
运行程序后,windbg break。
修改
kd> eq 80b95048 00D7EC00`00081080
kd> dq 80b95000
80b95000 00000000`00000000 00cf9b00`0000ffff
80b95010 00cf9300`0000ffff 00cffb00`0000ffff
80b95020 00cff300`0000ffff 80008b1e`500020ab
80b95030 84409317`1c003748 0040f300`00000fff
80b95040 0000f200`0400ffff 00d7ec00`00081080
kd> g
调用前 esp=0019F7E0, ss=0023
调用后 esp=AA50FD9E, ss=0010
g_num=100
坑
虚拟机配置,1个cpu,1个核心。