原文地址: http://blog.csdn.net/q1007729991/article/details/52644720
除了使用调用门进行提权,本篇的中断门显的更加重要。因为在 Windows 中,大量使用了中断门。
中断门的结构
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节
|76543210|76543210|7 65 4 3210|76543210|76543210|76543210|76543210|76543210| 比特
|-----------------|1|--|0|1110|--------|--------|--------|--------|--------| 占位 |offset in segment|P|D |S|TYPE| |segment selector |offset in segment| 含义
| 31-16 |P | | | 15-0 |
|L |
通常中断门是以 ----ee00 0008----
这种形式出现的,当然了,第 5 个十六进制数不一定就是 e,如果 DPL = 0 的话,那这个数就是 8. 后面的 0008 是段选择子,而左右两侧的占位符是要跳转的地址。
中断门安装在哪?
和前面的调用门一样,中断门也可以实现提权。但是我们已经知道,调用门是安装在 GDT 表中的,但是中断门并不是,它是安装在一个被称作 IDT(中断描述符表)中的,它同 GDT 一样,每个元素占 8 个字节。可以在 WinDbg 中通过 r idtr
来查看 IDT 表在内存中的位置。
从上图中我们除了可以看见中断门的描述符外,还看见了其它我们暂时还不知道的描述符,刚刚已经知道,中断门的五六位通常是ee
或者8e
,但是除此之外,我们还看见了85
这种类型(任务门),其实在 IDT 表中,除了有中断门外,还有任务门,陷阱门(通常是8f
),后面会陆续讲到。
中断门提权实验
为了能够感受一下中断门是如何从3环进入0环的,这里使用 WinDbg + xp 系统做一个简单的中断门实验。
编写我们自己的 0 环入口函数
在虚拟机中,打开 VC6.0,编写入口函数。
int g_high2G; int g_eflagsBefore; int g_eflagsAfter; int g_eax; __declspec(naked) void func() {
__asm { mov g_eax, eax pushfd
pop g_eflagsAfter mov eax, [esp+0x08]
mov g_eflagsBefore, eax mov eax, ds:[0x8003f500]
mov g_high2G, eax mov eax, g_eax iretd
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
构造中断门描述符
通过在 VC6.0 中,可以查看到 func 函数的起始地址为 0x00401020. 根据中断门描述符构造规则,可以设计如下描述符。
| 函数的偏移地址 | .__. .__. | | | | 0040-ee00-0008-1020 |<----->| ^ | '----这地方较为固定。ee00是中断门的特征,0008表示要通过中断门跨入0008描述的代码段中。
eq 8003f500 0040ee00`00081020
具体过程
图1 发现这个地方的描述符无效
图2 安装中断门描述符后的样子
中断门对堆栈的影响
提权
和调用门稍稍有点不一样的地方是,中断门提权会在堆栈中多压入一个值——EFLAGS.
不提权
如果不提权,意味着不会切换栈,所以也没有必要在栈中压入栈段选择子和栈顶指针。
使用 INT 指令进入中断门
IDT表中各种类型的门,都可以通过 int [index]
汇编指令进入。有一点需要说明的是,使用 int 指令进入中断门,会影响栈。如果从 3 环进入 0 环,有两件事情要做。
切换成 0 环栈
在 0 环栈压入 ss3, esp3, eflags3, cs3, eip3
上面的 ss3 等后面的 3 表示的是 3 环下的栈,栈顶指针,eflags, 3环代码段选择子和返回地址。这些值都是被 CPU 自动保存起来的,和操作系统没有任何关系,这是 CPU 本身固有的特性。
很少有使用中断门从 3 环进入 3 环的,这种设计感觉有点傻。但是我们也要知道,如果真的是这样,那么 CPU 做的事情和上面就完全不一样了,既不会切换栈,也不会压力入ss3, esp3. IF 位也没什么变化。
int main(int argc, char* argv[])
{
__asm { // 构造的中断门描述符安装在 IDT[20] 这个位置。 int 0x20;
} printf("0x8003f500: %08x\n", g_high2G); printf("进入中断门前的 EFLAGS = %08x\n", g_eflagsBefore); printf("进入中断门后的 EFLAGS = %08x\n", g_eflagsAfter); return 0;
}
执行结果
从结果中可以看到,已经成功读取了我们在 8003f500 位置处安装的描述符,和我们期望的值一样。另外这里还读取了 EFLAGS 寄存器,它的值是有变化的。变化的那一个比特位实际上是第 9 位,叫 IF(Interrupt Flag)位。它由原来的 1 变成了 0. 意思是说,我(CPU)已经进入中断门了,如果还有可屏蔽中断信号到来,我将不理睬。
这里出现了很多新概念,暂时不用理会。我们只记住这样的事实:
中断门会把 IF 位置 0
等到学习的深入,再深究它。

图 3 中断门提权读取高2G内存
完整代码
#include <stdio.h> int g_high2G; int g_eflagsBefore; int g_eflagsAfter; int g_eax; __declspec(naked) void func() {
__asm { mov g_eax, eax pushfd
pop g_eflagsAfter mov eax, [esp+0x08]
mov g_eflagsBefore, eax mov eax, ds:[0x8003f500]
mov g_high2G, eax mov eax, g_eax iretd
}
} int main(int argc, char* argv[])
{
__asm { int 0x20;
} printf("0x8003f500: %08x\n", g_high2G); printf("进入中断门前的 EFLAGS = %08x\n", g_eflagsBefore); printf("进入中断门后的 EFLAGS = %08x\n", g_eflagsAfter); return 0;
}