调用门

admin 2023年10月23日09:11:31评论3 views字数 2142阅读7分8秒阅读模式

前面提到,JMP指令可以实现段间跳转,那么如果想要实现段与段之间的跳转,我们该用什么指令?

答案是:CALL指令。

CALL指令能实现跨段的跳转,这种跳转是Windows允许的,举个例子:当我们想要调用0环的api时,我们需要使用Windows系统给我们提供的3环接口,在通过CALL去找到0环的api,进行调用。这就是跨段的调用,也是一种提权方式。

但是,CALL指令,也带来了更加复杂的逻辑,相对于JMP指令,CALL指令能够影响堆栈。

那么在了解调用门之前,我们需要先了解一些前置的知识。

1.长调用与短调用

短调用

指令格式:CALL 立即数/寄存器/内存

短调用的CALL,仅仅只是修改ESP和EIP的值。

长调用

跨段不提权

指令格式:CALL CS:EIP(EIP是废弃的)

我们需要遵守这个格式。废弃的意思是,我们可以随便写,但必须得有。

因为这里没有进行提权,所以仅仅只是改变了ESP、EIP、CS。

调用门

此时我们可以用RETF来将压入栈中的ESP和CS返回给原来的调用者。

跨段提权

跨段提权指令还是一样的,但是相对于没有提权,是比较复杂的。

在前文我们已经介绍了CS在3环为1B,在0环为08。但是在这里,我们需要考虑到因为CS的权限提升了,那么随之改变的栈段也需要发生改变。

这是为什么呢?因为当代码段发生权限变更时,SS栈段里面的属性我们也许用不了了,因此我们也要将SS栈段修改为对应的权限。

同时,SS既然也发生改变,那么意味着,堆栈也改变了。

调用门

因此发生的寄存器:CS、ESP、SS、EIP。

这里涉及到两个问题:

  1. SS和ESP从哪来?

从TSS段。这个在后续的笔记中会解释的。

  1. 既然堆栈更新了,那么为什么EBP不修改?

因为我们只需要知道ESP指向哪就能找到我们的CS、SS、ESP等,不需要知道EBP指向哪。

而对长调用与短调用了解之后,我们还需要一个权限认证,权限认证分为数据段、非一致性代码段、一致性代码段、系统段等几种方式。这里不细介绍了,前文简单说了一下。

那么说完了权限认证与CALL指令的调用方式,接下来,就可以进入正文了。

调用门

什么是调用门?如下图:

调用门

调用门的几个属性:

  • 属于GDT表

  • 属于系统段

  • Type域:1100 = C

  • S为必须为0

  • DPL 必须为 11,因为如果不是11,那么我们连敲门的资格都没有,我们的程序CPU的CPL都是11.

  • 结构与段描述符类似,但是细节不同,其中需要注意的是:

    • 低32位的后2个字节代表了我们指向的真正的段描述符,也就是我们需要提权的段描述符。

    • 访问的地址为:段.Base + 偏移地址 就是真正要执行的地址。这里的偏移地址位于该调用门的前2个字节和后2个字节。

调用门-无参

我们需要定义一个方法,并拿到该方法的地址:

调用门

拿到地址00401020,此时我们需要自己定义一个调用门,根据前面的描述,调用门为:0040EC00 00081020

其中40与1020组成偏移401020,EC代表该描述符为调用门,00代表0参,0008表示CS进入0环(提权)。

调用门

修改完毕之后,我们运行我们的程序。

调用门

发现已经断点到了。

具体代码如下:

void __declspec(naked) Test()
{
_asm
{
int 3 //系统中断
retf
}
}


int main(int argc, char* argv[])
{
char buff[6] = {0x44,0x33,0x22,0x11,0x48,0x00};
_asm
{
call fword ptr[buff] //48为我们自己定义的调用门的地址
}
getchar();
return 0;
}

调用门-有参

这里就不在解释了,直接上代码:

// 调用门有参.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

DWORD x;
DWORD y;
DWORD z;

void __declspec(naked) CateProc()
{
__asm
{
pushad
pushfd

mov eax,[esp+0x24+8+8]
mov dword ptr ds:[x],eax
mov eax,[esp+0x24+8+4]
mov dword ptr ds:[y],eax
mov eax,[esp+0x24+8+0]
mov dword ptr ds:[z],eax

popfd
popad

retf 0xC // ?????? ????
}
}

void PrintRegister()
{
printf("%x %x %x n", x, y, z);
}

int main(int argc, char* argv[])
{
char buff[6] = {0x44,0x33,0x22,0x11,0x48,0x00};

__asm
{
push 1 // ??1
push 2 // ??2
push 3 // ??3
call fword ptr[buff]
}
PrintRegister();
getchar();
return 0;
}

调用门

调用门

执行成功。

这里说一下上面的参数如何传递的

0x24:是十六进制的 36 ,先看看怎么来的吧。pushfd 会将 8 个 32 位寄存器压入堆栈中,即 32 个字节。 pushfd 会将 EFLAG 寄存器压入堆栈中,也是 4 个字节,总和即为 36 个字节。

0x8:是返回地址和 CS 所占的总字节数。


原文始发于微信公众号(loochSec):调用门

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月23日09:11:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   调用门http://cn-sec.com/archives/2135825.html

发表评论

匿名网友 填写信息