在上一篇章中提过,实现跨段的跳转,可以使用JMP FAR指令,但是想要实现跨段的调用,就需要学习一个新的指令CALL FAR;CALL FAR指令要更为复杂一些,原因是JMP指令是不影响堆栈的,而CALL指令会影响堆栈。
短调用
首先来回顾下短调用,短调用其实就是普通的CALL调用,是相对于CALL FAR而言,所以叫做短调用。短调用会影响EIP寄存器和ESP寄存器,因此在返回时需要额外去平衡一下堆栈;短调用属于三环知识,这里不再赘述,具体的调用和返回时的堆栈变化如下图所示:
长调用(跨段不提权)
长调用分为提权和不提权两种,这里先讲不提权的情况。
这图看不明白没关系,一个个分析。首先,EIP为什么是废弃的?因为,这个长调用指令,压根不会跳转到你给的EIP的位置,而是会跳转到调用门里提供的地址。那什么是调用门呢?下一篇会具体提到, 这里简单概括调用门就是一种位于GDT表里特殊的描述符。
回到长调用这里,当CALL执行后,与一般的调用指令不同,长调用使得堆栈提升了8个字节,除了返回地址外,还压入了调用者的CS,以便在调用返回至原来程序时,CS也能得到恢复。其中返回地址依然是位于[esp]处,调用者的CS位于[esp+4]处。
长调用返回也与普通调用不同,普通调用使用ret指令即可返回到原来程序的位置,而长调用返回时需要使用长返回指令RETF,长返回指令除了会将返回地址送入EIP寄存器,还会将CS恢复至执行前的状态,同时平衡堆栈。
长调用(跨段并提权)
不提权的长调用还稍微好理解一点,提权的长调用,就稍微有些复杂了,由于发生了提权,CS的CPL发生了改变,根据Intel的规定,CS和SS的CPL一定要保持一致,所以此时SS的值也会发生改变,除此之外,因为发生了提权,堆栈从3环的堆栈变为了0环的堆栈,因此ESP也会发生改变。所以提权的长调用会4个寄存器的值发生改变,分别是EIP,CS,ESP,SS,来看一下执行前后的变化
提权后,分别将返回地址,调用者CS,ESP,SS压入了0环的堆栈中,这样在返回3环时,可以确保这些寄存器恢复到原本的状态。
同样,提权后返回用的也是RETF,长返回指令,分别将返回地址,调用者的CS,ESP,SS压入相应的寄存器中,大致如下:
总结
长调用相较长跳转更为复杂,这篇只是做个简单的介绍,在后面的篇章中,将通过分析调用门的实现过程来详细讲解长调用,这里对本篇提到的几个特点做些总结。
- 跨段调用时,一旦有权限切换,就会切换堆栈
- CS的权限一旦改变,SS的权限也要随着改变,CS与SS的等级必须一样
- JMP FAR只能跳转到同级非一致代码段或者共享段,但CALL FAR可以通过调用门提权,提升CPL的权限