中断门

中断门

在使用调用门,我们可能会疑惑,为什么我们要去在GDT表里面,人为构造调用门,那是因为windows压根不使用调用门,而是使用中断门

GDT表中主要有以下几类段描述符:
代码段描述符、数据段描述符、系统段描述符(TSS)

那什么是调用门呢?

__asm
{
  int 3;
}

这玩意就是一个我们最属性不过的中断门,只不过我们之前可能只认为它就是一个指令,然后作用是下一个断点。
知道调用门的那一刻,我才知道INT其实是一个指令,后面的3Index

调用门的语法是 INT Index
这里的Index 查的表其实是 IDT表
构建中断门,我们就需要在IDT表中构建
windbg下

r idtr //查看idt表的地址,结构类似于GDT表

构建的操作和调用门差不多,但是需要注意一下
调用门是在GDT构建,然后去查GDT表里面对应的段描述符
中断门是在IDT构建,然后去查GDT表里面对应的段描述符

IDT表

IDT(Interrupt Descriptor Table,中断描述符表)是CPU用于处理中断和异常的核心数据结构,通过索引中断向量来定位对应的中断服务程序。

中断门结构

S = 0 type = D110(D = 1 对应32位 D = 0 对应16位)
o_2109221153587-5

中断门执行过程

指令格式: INT N(N为中断门的索引
1.执行时堆栈变化
不提权,堆栈依次压入EFLAGCSEIP(返回地址);
提权,堆栈依次压入SSESPEFLAGCSEIP(返回地址)
2.权限检查
门描述符中并没有RPL,检查CPL
所以和中断门相同,满足CPL >= DPL即可
3.中断门返回
中断门不能通过RETF返回,而是使用IRETD或者IRET指令返回。

提权换栈的一些思考

使用调用门(提权),我们会发展程序执行在0换,其中很大一个特点是换栈,这里的换栈的地址,其实存储在TSS,对于每一个线程都有自己特有的TSS,所以每一个线程,从3环(ESP3) -> 0环(ESP0),更换的栈都是根据自己的TSS来确定的,每个线程独一份,谁也不耽误谁,并且这个TSS的值是有操作系统设置的,CPU提供TSS,但是具体的操作离不开操作系统。
打个有意思的比方,CPU提供了一块好地,操作系统在地上培育庄稼。
这里留一个问题给自己,线程是如何切换的? 目前不清楚,之后我回来补充

实验

1.实现中断门

我们选好在IDT表中构建中断门的位置
Index = 0x20

INT 0x20
PixPin_2026-07-28_10-49-51

代码如下

#include "stdafx.h"

void __declspec(naked) test()  //生成裸函数,不生成 ebp寻址 等代码
{
	__asm
	{
		IRET;
	}
}

int main(int argc, char* argv[])
{
	__asm
	{
		INT 0x20
	}
	printf("Hello World!\n");
	return 0;
}

函数地址 Address = 0x00401020
PixPin_2026-07-28_10-58-12

构建中断门
eq ffffffff8003f500 0040EE0000081020
0040EE00 00081020

PixPin_2026-07-28_10-56-14

PixPin_2026-07-28_11-01-55

执行代码!并没有在windbg断在0x00401020, g 一下 直接挂了!
PixPin_2026-07-28_11-02-41

仔细研究一下,发现可能是 IRET,因为使用IRETD正确的断在了0x00401020
PixPin_2026-07-28_13-12-16

我们观察堆栈,可以验证压栈的顺序
SS -> ESP -> EFLAG -> CS -> 返回地址

这里有一个问题,IRETIRETD 不是等价吗?,只用IRET会出现蓝屏
再试了一次,发现结果还是一样
PixPin_2026-07-28_13-17-49

这里我的想法是这样的
IRETD 只能在32位下使用,
IRET 可以在32位和16位下使用,这个向下兼容了。
也许这其中有什么问题,当以目前的认知,无法解释。。

2.在调用门中实现使用IRETD返回, 在中断门中实现用RETF返回

从堆栈的角度分析,我们可以尝试更改堆栈的数据,调用门使用IRETD,也就是说还需要一个EFLAG的值,还记得调用门在提权并且有参数的情况下,参数是压在堆栈的什么位置吗?
SS ESP 和 CS 返回地址的中间!
所以我们只需要在调用之前 pushfd即可
理论成立,实验看看,代码如下,具体调用门的实现,肯定已经非常了解了,这里就省略了

#include "stdafx.h"

`eq ffff
void __declspec(naked) test()    //生成裸函数,不生成 ebp寻址 等代码
{
    _asm
    {
        int 3;    //让断点停在WinDbg中
        iretd;
    }
}

const char buffer[6]={0,0,0,0,0x4B,0};

int main(int argc, char* argv[])
{

    _asm
    {
        pushfd;
        call fword ptr ds:[buffer];    //在此处下断点,填写正确的调用门
    }
    printf("helloworld!");
    return 0;
}

PixPin_2026-07-28_19-03-54

我们执行代码,观察堆栈,居然蓝屏了! no!!!!!!!
一定要注意参数啊!
我们忽略了一个很关键的东西!
在调用门结构中有一个成员是Param.Count这个是用来记录参数的个数

PixPin_2026-07-28_19-16-51
更改后执行代码,观察windbg,然后分析堆栈。
PixPin_2026-07-28_19-22-59
PixPin_2026-07-28_19-25-13
这下结果和我们设想的一样了,perfect!解决!
//
//
接下来,让我们分析中断门下,使用retf正常返回函数
思路:调整堆栈数据的位置,既然retf是弹出四个参数,我们这里显然多了EFLAG
我让我们先用EAX保存EFLAG数据,然后让CS的数据覆盖EFLAG返回地址 覆盖 CS, 再把保存EFLAG 覆盖 返回地址,然后再retf之前popfd,就可以实现了!
接下来我们开始实验
代码如下

#include "stdafx.h"


void __declspec(naked) test()  //生成裸函数,不生成 ebp寻址 等代码
{
	__asm
	{
		pushad;

		mov eax, dword ptr ds:[esp + 0x4 * 8 + 0x8]; //EFLAG
		

		lea ecx, [esp + 0x4 * 8 + 0x8];
		mov ebx, dword ptr ds:[esp + 0x4 * 8 + 0x4]; // CS
		mov dword ptr ds:[ecx], ebx;

		lea ecx, [esp + 0x4 * 8 + 0x4];
		mov ebx, dword ptr ds:[esp + 0x4 * 8 + 0x0]; // ret_Address
		mov dword ptr ds:[ecx], ebx;

		lea ecx, [esp + 0x4 * 8 + 0x0];
		mov dword ptr ds:[ecx], eax;
		popad;
		popfd;
		retf;
	}
}

int main(int argc, char* argv[])
{


	__asm
	{
		INT 0x20;
	}
	printf("Hello World!\n");
	return 0;
}

我们添加中断门,执行代码分析堆栈。
运行到popad之前
PixPin_2026-07-28_19-56-28
运行到retf之前
PixPin_2026-07-28_19-58-44
和我们预想的一样,可以正常运行,没有出现异常
PixPin_2026-07-28_20-00-15

posted @ 2026-07-29 19:45  书与兔子  阅读(5)  评论(0)    收藏  举报