内核学习之调用门试验
-
调用门实验
目标: 使得一个3环程序能够读取出0环的数据
步骤分析:
-
3环程序的cs段寄存器保存的是3环的CPL
-
3环程序的cs段寄存器不可见部分保存的段描述符,是一个DPL为3的段描述符
-
需要将3环的cs段寄存器的CPL改成0 , 将一个0环的代码段描述符加载到cs的不可见部分. 最后3环程序才有0环的特权等级去访问0环的数据.
-
实现步骤:
-
在GDT表中手工构造一个调用门
-
在3环程序中加载这个调用门.(加载调用门是为了将0环的段描述符加载到3环的cs段寄存器.)
2.1 要在环将一个描述符加载cs中必须满足以下要求:
2.1.1 需要用到间接修改cs段寄存器的指令
2.1.2 CPL<=DPL && RPL <= DPL,此处的DPL指的是调用门保存的DPL
因此, 调用门的DPL必须是3
因此, 可以将一个0环的代码段的段选择子保存调用门的段选择子部分
2.1.4 加载之后, 需要转移到一个地址上执行代码,这个地址是在调用门的段内偏移部分来指定的.
因此, 可以将一个函数的地址存放到这个位置.
// 实现的主要代码 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 }; _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" ); }
-
使用调用门之前,需要将调用门的描述符写进系统的GDT表中,使用WinDbg双机调试,获取GDT表的地址
kd> rgdtr
gdtr=80b95000
-
使用dq命令查看GDT表中哪些是空闲的(值为0是空闲的)
kd> dq 80b95000 80b95000 00000000`00000000 00cf9b00`0000ffff 80b95010 00cf9300`0000ffff 00cffb00`0000ffff 80b95020 00cff300`0000ffff 80008b1e`500020ab 80b95030 84409313`ac003748 0040f300`00000fff 80b95040 0000f200`0400ffff 00000000`00000000 80b95050 84008913`80000068 84008913`80680068 80b95060 00000000`00000000 00000000`00000000 80b95070 800092b9`500003ff 00000000`00000000
2. 构造一个可以在3环代码中使用的段选择子,构造规则:
按照段选择子格式: | 描述符在表中的下标:13 | T1:0 | RPL:2 |
由上面可以知道在GDT表中,第9项是空闲的,门描述符将保存在这个地方,则对应的段选择子:
13位描述符表索引 | 1位:T1 | 2位:RPL |
---|---|---|
十进制 9 | 0 | 3 |
二进制 1001 | 0 | 0x11 |
合并为: 0100 1011 十六进制为 4B
前4字节是EIP,后2字节是CS,后两个字节是0x004B,此数值就是在第二步中构造出来的
char buff[] = {0,0,0,0,0x4b,00}; __asm call fword ptr ds:[buff]
3. 把生成的程序放入虚拟机中运行 0119EC00~000811CC
kd> eq 80b95048 0119EC00`000811CC
5. 写入之后查看发现已经写入成功
kd> dq 80b95000 80b95000 0000000000000000 00cf9b000000ffff 80b95010 00cf93000000ffff 00cffb000000ffff 80b95020 00cff3000000ffff 80008b1e500020ab 80b95030 84409313ac003748 0040f30000000fff 80b95040 0000f2000400ffff 0119ec00000811cc 80b95050 8400891380000068 8400891380680068 80b95060 0000000000000000 0000000000000000 80b95070 800092b9500003ff 0000000000000000
6. 继续g回到虚拟机中往下查看,写入成功实验完成。