前面提到,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。
这里涉及到两个问题:
-
SS和ESP从哪来?
从TSS段。这个在后续的笔记中会解释的。
-
既然堆栈更新了,那么为什么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):调用门
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论