个人备忘,一些总结。中断门和调用门的细节不赘述,网上很多。
调用门,用 iretd 返回。来深度理解调用门(正常应该 retf 返回)
void __declspec(naked) Dym()
{
__asm
{
int 3;
//retf; // 正常应该用 retf 返回
mov eax, 0x11112222;
mov ebx, 0x22222222;
mov ecx, 0x33333333;
mov edx, 0x44444444;
iretd;
}
}
struct CVal
{
long val;
short seg;
};
int _tmain(int argc, _TCHAR* argv[])
{
printf("Dym : %p\n", Dym);
if (1)
{ // 调用门
// 去GDT 表将 48 位置改成 0040ec00 00081030
// 以实际的 NoArg 地址为准
CVal _vv= {0x0,0x48};
__asm
{
pushfd;
call fword ptr[_vv];
add esp, 4;
}
printf("调用门 iretd 顺利返回了\n");
}
getchar();
return 0;
}
执行上述代码,断点设置在 if (1)。查看 Dym 函数的地址
Windbg 双击调试环境下(配环境不赘述)。暂停 windbg,输入(可以复制页面内容,修改)
其中 48 就是上面代码中写的 48.不固定。自己可以改。匹配就行了。
调用门描述符一共 8字节共64位:00f0ec01`00081000
其中 00f01000 是 Dym 的函数地址。 0008 是代码段选择子,也就是
ec01,01 是参数个数,有关参见调用门细节,简单贴图如下:
ec 是 P DPL 0 type 位的值:P= 1, DPL = 11, type = 1100(可参见本人的总结)
代码中有 pushfd ,所以是一个参数。执行调用门之后会在windbg 断点,在如下代码出:
Windbg 图如下:
上面说过,函数地址是 f01000 这里也得到验证。在内存中查看 esp 附近的栈值
连续五个值,分别是 :
返回地址 EIP
调用者 CS
EFLAGS
调用者 ESP
调用者 SS
参考:长调用与端调用
正常情况下调用门在提权的情况下没有 eflags 。我们是通过 pushfd 参数,将 eflags 作为参数压入的。
这也说明调用门压参数的情况下,参数是放在中间的,如下:
返回地址 EIP
调用者 CS
参数…
调用者 ESP
调用者 SS
由于上述栈内容和中断门的内容一致(原因下面讲),所以可以直接用 iretd 返回了。但是由于保存的esp 的愿因,会导致站空间还保存了 eflags ( 3 环)不正确,毕竟我们多push 了一个 eflags。通过额外的 add esp, 4 来平衡堆栈(显然可以用 popfd 实现)。
由于有 int 3 。会在调用门退出来的时候报异常,去掉则正常了。
下面贴一个完整流程截图:
call 之前暂停,查看相关寄存器值 esp eip cs ss eflag
总结,可以看到进入调用门函数之后,(0环)栈里面顺序保存了 eip cs eflag esp ss。值和进入调用门之前的(3环)完全一致。其中 eip 进入之前是 F01076,调用门里面保存的是 F01079。
退出来之后,断点在 add esp, 4。这时候看栈里面还压入了 eflag 的值,这个需要退出来,所以 add esp, 4。完成堆栈平衡。当然也可以通过 popfd 实现。
进入反汇编查看:
发现 add esp,4 这句指令地址就是 F01079 。这也就是在 0 环里面保存的 eip。完美解释!下面通过对中断门的描述,理解这里为什么要 pushfd。
中断门中用 retf 返回(正常应该用 iretd ).
先上代码
void __declspec(naked) Zdm()
{
__asm
{
int 3;
//iretd; // 正常就是 iretd 返回, 16 位用 iret
mov eax,dword ptr ds:[esp+0x8] //取ELFAGE
push eax;
popfd //修改ELFAGE寄存器
//重组堆栈,按照调用门堆栈的格式,这样就可以使用RETF返回
mov eax,dword ptr ds:[esp+0x0c] //esp
mov edx,dword ptr ds:[esp+0x010] //ss
mov dword ptr ds:[esp+0x8],eax
mov dword ptr ds:[esp+0xc],edx
RETF
}
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("Zdm : %p\n", Zdm);
if(1)
{ // 中断门
__asm
{
int 32;
}
printf("中断门用 retf 顺利返回了\n");
}
getchar();
return 0;
}
先在 int32 下断点,停住查看 Zdm 函数地址(注意这个地址是错误的,因为当时代码忘记了写 int 3,后面加上 int 3 的时候,函数地址忘记截图了,真实地址是下面用到的 1051010):
然后 windbg 暂停。输入
这个地址刚好是 索引 32(0开始)。当然也可以选其他地址,修改代码中的索引就可以了 比如:
int 33。 地址选 …508。
修改了 idt 表后 windbg 按 f5 继续执行。
截图保存查看寄存器值:
单步执行,断点,停在 windbg:
地址正是上面的 1051010 。查看 esp 相关值:
罗列数据如下:
0环 | 三环 |
---|---|
EIP 0105108E | EIP 01051090 |
– | – |
CS 001B | CS 001B |
– | – |
EFLAG 0202 | EFLAG 0202 |
– | – |
ESP 17F93C | ESP 17F93C |
– | – |
SS 0023 | SS 0023 |
可以看到数据一致。EIP除外,三环存的EIP 是0环返回后需要执行的指令地址。
上述顺序分别是
低地址
EIP
CS
EFLAGS
ESP
SS
高地址
这比调用门在中间多了一个 EFLAGS。这也正是上面调用门通过 pushfd 将参数压到中间,最后可以通过 iretd (中断门返回指令)从调用门返回的原因。
同理,这里我们如果要用 retf (调用门返回指令)从中断门返回,也需要模拟调用门的堆栈环境:
EIP
CS
ESP
SS
也就是 将 esp ss 两个值分别覆盖到 eflags esp。再看代码:
void __declspec(naked) Zdm()
{
__asm
{
int 3;
//iretd;
mov eax,dword ptr ds:[esp+0x8] //取ELFAGE
push eax;
popfd //修改ELFAGE寄存器
//重组堆栈,按照调用门堆栈的格式,这样就可以使用RETF返回
mov eax,dword ptr ds:[esp+0x0c] //esp
mov edx,dword ptr ds:[esp+0x010] //ss
mov dword ptr ds:[esp+0x8],eax
mov dword ptr ds:[esp+0xc],edx
RETF
}
}
正是做了这样的操作。其中将 eflags 存到了 eflags。
同样由于有 int 3 的存在执行会报异常。去掉就正常了。
OVER。