上一篇重点讲解了没有参数的调用门实验,本节讲解有参数的调用门实验。
1. 编写R0函数
int g_a, g_b, g_c;
__declspec(naked) void getParam(int a, int b, int c) {
__asm {
// int 3 // 取消注释可以在WinDbg中看R0栈数据
pushad // 0x20 B
pushfd // 0x04 B
// .- 8 个通用寄存器和标志寄存器占用大小
// | .- cs 和 eip 占用大小
// | |
mov eax, [esp+0x24+0x08+0x08] // 参数 a
mov g_a, eax
mov eax, [esp+0x24+0x08+0x04] // 参数 b
mov g_b, eax
mov eax, [esp+0x24+0x08+0x00] // 参数 c
mov g_c, eax
popfd
popad
retf 0x0c
}
}
int main(int argc, char* argv[])
{
// 构造cs:eip
char cs_eip[6] = {0, 0, 0, 0, 0x48, 0};
__asm {
push 1
push 2
push 3
call fword ptr [cs_eip];
}
printf("g_a = %d\ng_b = %d\ng_c = %d\n", g_a, g_b, g_c);
return 0;
}
2. 记录getParam函数地址
可以看到地址为 0x00401020
3. 构造带3个参数的调用门描述符
调用门描述符结构:
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节
|76543210|76543210|7 65 4 3210|765 43210|76543210|76543210|76543210|76543210| 比特
|-----------------|1|--|0|1100|000|-----|--------|--------|--------|--------| 占位
|offset in segment|P|D |S|TYPE| |param|segment selector |offset in segment| 含义
| 31-16 |P | |nums | | 15-0 |
|L |
注意有些比特位是固定的。
根据调用门描述符结构,可以构造出描述符 0040ec03`00081020,然后安装到gdt表的8003f048的位置。
4. 执行结果
5. 查看R0栈数据
这里需要取消 int 3那行注释,这样会在 WinDbg 下中断。可以看到CS/SS都已经是R0的段了,esp也指向了高2G的地址。(为什么会这样?CPU需要始终保持 cs 中的 CPL 和 ss 中的 CPL 一致)
可以看到这时候 esp 的值为 0xb2391dc4,去看一下栈内存数据
6. 总结
当使用调用门进行提权的时候,程序由3环进入0环,这时候需要切换栈,也就是说要更改 ss 段选择子和 esp 的值。这时候 CPU 会自动的帮我们把原始 3 环的 ss, esp, 参数(如果有的话), cs 和 eip 复制到这个 0 环栈中去。
一定要注意,这是 CPU 自动帮我们做的事情,和 OS 没有任何关系。
引起注意的地方是,切换栈,需要 0 环 ss 段选择子和 esp ,这些值 CPU 是如何找到了呢?这个问题先放在这儿,后续文章解决会揭晓答案。这里,只给出一个线索——TSS.