中断门
中断门
在使用调用门,我们可能会疑惑,为什么我们要去在GDT表里面,人为构造调用门,那是因为windows压根不使用调用门,而是使用中断门
GDT表中主要有以下几类段描述符:
代码段描述符、数据段描述符、系统段描述符(TSS)
那什么是调用门呢?
__asm
{
int 3;
}
这玩意就是一个我们最属性不过的中断门,只不过我们之前可能只认为它就是一个指令,然后作用是下一个断点。
知道调用门的那一刻,我才知道INT其实是一个指令,后面的3是Index
调用门的语法是 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位)

中断门执行过程
指令格式: INT N(N为中断门的索引
1.执行时堆栈变化
不提权,堆栈依次压入EFLAG、CS、EIP(返回地址);
提权,堆栈依次压入SS、ESP、EFLAG、CS、EIP(返回地址)
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

代码如下
#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

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


执行代码!并没有在windbg断在0x00401020, g 一下 直接挂了!

仔细研究一下,发现可能是 IRET,因为使用IRETD正确的断在了0x00401020

我们观察堆栈,可以验证压栈的顺序
SS -> ESP -> EFLAG -> CS -> 返回地址
这里有一个问题,IRET 和 IRETD 不是等价吗?,只用IRET会出现蓝屏
再试了一次,发现结果还是一样

这里我的想法是这样的
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;
}

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

更改后执行代码,观察windbg,然后分析堆栈。


这下结果和我们设想的一样了,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之前

运行到retf之前

和我们预想的一样,可以正常运行,没有出现异常


浙公网安备 33010602011771号