(4) [保护模式]调用门

call指令

call指令其实分为call 和 call far

平时我们看见最多的是call指令,而不是call far,call far指令很复杂,这里也只是介绍其中一种用法


call的执行

回顾一下以前学的call(短调用)干了什么

//call 地址 
push 返回地址(call指令的下一行地址)
mov EIP, 地址
--------------
//ret
pop EIP

call 有着另一种形态,长得和jmp far一模一样,call CS:EIP,我们称之为长调用(call far)
这里的 CS只是一个偏移,不代表最后放入CS寄存器的值

那么call far又是怎么执行的呢


简单了解call far

call CS:EIP(这里的EIP不会被使用到,写什么都可以)

如果将call far写成简单易懂的汇编指令:

  • 跨段不提权
	//call 段选择子:地址
	push CS
	push 返回地址(call far指令的下一行地址)
	//将 段选择(selector)子 分解,用index查GDT表(TI == 0)
	//门描述符中有一段描述的是EIP填什么(offset in segment)
	mov EIP, GDT[index].offset
  • 跨段提权
	push SS	//多出了这个
	push ESP //和多出了这个
	push CS
	push 返回地址
	mov CS, GDT[index].selector
	mov EIP, GDT[index].offset

需要注意的是:这里会将SS:ESP也存入堆栈,这是因为堆栈将要换成其他权限时使用的堆栈,所以保存起来,以便ret的时候能够恢复原来的堆栈

windows并没有使用调用门


如何使用call far

call CS:EIP会使用CS作为GDT表的偏移
使用dq gdtr就能查询到GDT表了
GDT的首地址 + CS(偏移)就是我们要找的调用门描述符地址了

调用门描述符

在了解call far 之前首先要了解什么是调用门,因为跨段提权的call far是通过调用门来进行跨段跳转的

在这里插入图片描述
首先我们可以看到原来的S(system)位直接被指定成了0,这也就是说门描述符其实是系统描述符的其中之一
type被指定成1100,这个type = 1100表示这是个门描述符,定义就是如此,记住就行

  • P位依旧表示这个描述符是否有效
  • DLP位访问这个段需要的特权级别
  • Selector表示真正要调用的代码所在的段,也就是说这个选择子等会儿会被写入CS寄存器中
  • Offset in Segment所表示的就是真正要执行的代码存放在的位置(GDT[Selector.index].base + Offset 才是等会儿所要执行的代码的位置)
  • Param Count 是参数的个数,至于左边那3个0就是保留位咯

手动构造call far

好了,了解了调用门之后,我们就可以手动构造调用门
首先要获得我们等会儿要跳转的地址是什么,所以先写个小程序等会儿测试用

#include <windows.h>
//naked是告诉编译器这段我自己写(堆栈自己处理),不要加额外的代码,是真正的空白函数
void __declspec(naked) func{ 
	_asm{
		int 3
		retf 
	}
}
int main(){
	BYTE addrs[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };//暂时不知道CS,先写0
	//打印地址,填入自己构造的调用门中,以实现跳转到自己写的函数里
	printf("function addrs: %x", func);
	_asm{
	
		call fword ptr[addrs]
		
	}
	return 0;
}

运行,先看函数地址,等会填描述符要用

在这里插入图片描述

可以看到这个函数的地址就是等会儿我们要跳过去的位置,当然也可以不是一个函数,这个稍后再演示

构造出一个调用门描述符:0040 E C 00 ` 0008 100a
加粗的是将要跳转的地址;斜体是1 11 0 1100;黄色部分就是一个0环的段选择子,因为调用门存在的意义就是为了提权,不提权辛辛苦苦整这东西干啥,所以我们这里直接写成提权的

在这里插入图片描述
我们将构造好的调用门写在一个空白的地方。

记住我们选中的空白位置的偏移(0x8003f048 - 0x8003f000 = 0x48),用来填充 call CS:EIP 中的 CS


上面写那个小程序的时候我已经把这个偏移量写到一个BYTE里了,addrs = {00,00,00,00,0x48,00},这个要逐个倒过来写。0x0048就是等会儿要用来call的值,前面那四个随便写就行,因为指令不会使用前四个值。

g命令继续执行后,再在小程序里继续执行,就会发现,程序停在windbg里了,因为int 3这个东西会优先被windbg调试器拦截下来(稍微了解就行)
在这里插入图片描述
这里写的 jmp 00401020,跳转后就是我们写的int3语句的位置了


首先观察寄存器的变化:
在这里插入图片描述

在这里插入图片描述
对比一下前后的堆栈,还有CS寄存器的值,都发生了巨大的变化

新的堆栈里也有着奇妙的事情,切换后的堆栈的值恰好存放了SS ESP CS EIP,这是为retf准备的

至于retf是什么,可以看看这个RET指令的不同

在这里插入图片描述
使用g命令继续执行,就能跳回来啦~


总结

  1. jmp far 只能跳转到 相同权限的地方 和 不同权限的一致代码段
  2. call far 可以调用相同权限的地方(这个可以自己动手试试,并观察堆栈变化),也可以通过调用门进行提升 当前权限 (CPL)
  3. 权限切换,堆栈也会切换,且CS与SS的权限一定是相同级别的


补充:带参数的调用门

如果对带参数的调用门带有一定的好奇那可以接着往下看

之前提到过,调用门会在跳转时发生堆栈变化。
那么会发生怎样的变化呢?

在这里插入图片描述
将之前的调用门的参数位改为3,表示我们将要传3个参数,其实多传几个也行

int main(){
	BYTE addrs[6] = {0x00, 0x00, 0x00, 0x00, 0x48, 0x00 };
	printf("function addrs: %x", func);
	-asm{
		push 0x11111111
		push 0x22222222
		push 0x33333333
		call fword ptr[addrs]
	}
	return 0;
}

在这里插入图片描述
这里main里传入三个参数再调用call far,随后观看堆栈

在这里插入图片描述
可以看到参数被复制到了0环堆栈,ESP因为多了几条push所以有所变化
记得注意堆栈平衡,否则会蓝屏,蓝屏!!!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值