中断门
Windows 系统没有使用调用门,但使用了中断门。
中断描述符表(IDT)
描述
IDT即中断描述符表,同GDT一样,IDT也是由一系列描述符组成的,每个描述符占8个字节
但要注意的是,IDT表中的第一个元素不是NULL
相关寄存器
- idtr:IDT 表基址
- idtl:IDT 表长度
WinDbg 查看 IDT 表
kd> r idtr
idtr=8003f400
kd> r idtl
idtl=000007ff
kd> dq idtr
8003f400 804e8e00`00080360 804e8e00`000804db
8003f410 00008500`0058113e 804eee00`000808ad
8003f420 804eee00`00080a30 804e8e00`00080b91
8003f430 804e8e00`00080d12 804e8e00`0008137a
8003f440 00008500`00501198 804e8e00`0008179f
8003f450 804e8e00`000818bc 804e8e00`000819f9
8003f460 804e8e00`00081c52 804e8e00`00081f48
8003f470 804e8e00`0008267e 804e8e00`000828f3
IDT 表中门描述符种类
- 任务门描述符
- 中断门描述符
- 陷阱门描述符
中断门描述符
执行步骤
- 根据中断号查找 IDT 表得到中断门描述符
- 根据门描述符中的段选择子查找 GDT 表的段描述符
- 根据门描述符的偏移加上段描述符的 base 得到要跳转的地址
中断门执行前后堆栈变化
中断门和调用门堆栈变化的不同点是多压了一个 EFLAGS 标志寄存器,因此返回使用的是 iretd 而不是 retf。
构造中断门
运行如下代码,获得 test 函数地址 0x40100A 。
注意:在裸函数中使用 iret 是对应 16 位的返回,iretd 对应的是 32 位的返回,而在普通函数中两个返回都是 32 位的,因为编译器会做替换。
#include<stdio.h>
#include<stdlib.h>
int val = 0;
void __declspec(naked) test() {
__asm {
int 3
mov ebx, 0x10
mov val, ebx
iretd
}
}
int main() {
printf("%X\n",&test);
system("pause");
__asm{
push fs
int 0x20
pop fs
}
return 0;
}
在 IDT 表 0x100 偏移处构造一个中断门,对应中断号为 0x20 。
kd> dq idtr l30
8003f400 804e8e00`00080360 804e8e00`000804db
8003f410 00008500`0058113e 804eee00`000808ad
8003f420 804eee00`00080a30 804e8e00`00080b91
8003f430 804e8e00`00080d12 804e8e00`0008137a
8003f440 00008500`00501198 804e8e00`0008179f
8003f450 804e8e00`000818bc 804e8e00`000819f9
8003f460 804e8e00`00081c52 804e8e00`00081f48
8003f470 804e8e00`0008267e 804e8e00`000828f3
8003f480 804e8e00`00082a10 804e8e00`00082b46
8003f490 804e8500`00a028f3 804e8e00`00082cac
8003f4a0 804e8e00`000828f3 804e8e00`000828f3
8003f4b0 804e8e00`000828f3 804e8e00`000828f3
8003f4c0 804e8e00`000828f3 804e8e00`000828f3
8003f4d0 804e8e00`000828f3 804e8e00`000828f3
8003f4e0 804e8e00`000828f3 804e8e00`000828f3
8003f4f0 804e8e00`000828f3 806f8e00`00081fd0
8003f500 00000000`00080000 00000000`00080000
8003f510 00000000`00080000 00000000`00080000
8003f520 00000000`00080000 00000000`00080000
8003f530 00000000`00080000 00000000`00080000
8003f540 00000000`00080000 00000000`00080000
8003f550 804dee00`0008fba2 804dee00`0008fca5
8003f560 804dee00`0008fe44 804eee00`0008078c
8003f570 804dee00`0008f631 804e8e00`000828f3
kd> eq 8003f500 0040ee00`0008100a
kd> dq idtr l30
8003f400 804e8e00`00080360 804e8e00`000804db
8003f410 00008500`0058113e 804eee00`000808ad
8003f420 804eee00`00080a30 804e8e00`00080b91
8003f430 804e8e00`00080d12 804e8e00`0008137a
8003f440 00008500`00501198 804e8e00`0008179f
8003f450 804e8e00`000818bc 804e8e00`000819f9
8003f460 804e8e00`00081c52 804e8e00`00081f48
8003f470 804e8e00`0008267e 804e8e00`000828f3
8003f480 804e8e00`00082a10 804e8e00`00082b46
8003f490 804e8500`00a028f3 804e8e00`00082cac
8003f4a0 804e8e00`000828f3 804e8e00`000828f3
8003f4b0 804e8e00`000828f3 804e8e00`000828f3
8003f4c0 804e8e00`000828f3 804e8e00`000828f3
8003f4d0 804e8e00`000828f3 804e8e00`000828f3
8003f4e0 804e8e00`000828f3 804e8e00`000828f3
8003f4f0 804e8e00`000828f3 806f8e00`00081fd0
8003f500 0040ee00`0008100a 00000000`00080000
8003f510 00000000`00080000 00000000`00080000
8003f520 00000000`00080000 00000000`00080000
8003f530 00000000`00080000 00000000`00080000
8003f540 00000000`00080000 00000000`00080000
8003f550 804dee00`0008fba2 804dee00`0008fca5
8003f560 804dee00`0008fe44 804eee00`0008078c
8003f570 804dee00`0008f631 804e8e00`000828f3
kd> g
继续运行代码,成功在 test 函数断下来,并且完成提权,栈结构也与前面描述的相符。另外可以发现进入中断后 IF 寄存器清零,而 iret 返回后 if 寄存器又被置为 1 。
Break instruction exception - code 80000003 (first chance)
00401020 cc int 3
kd> uf eip
00401020 cc int 3
00401021 bb10000000 mov ebx,10h
00401026 891dec554200 mov dword ptr ds:[4255ECh],ebx
0040102c cf iretd
kd> r if
if=0
kd> r cs
cs=00000008
kd> r ss
ss=00000010
kd> r esp
esp=b13c0dcc
kd> r ebp
ebp=0012ff80
kd> dd esp
b13c0dcc 0040107b 0000001b 00000202 0012ff30
b13c0ddc 00000023 00000000 00000000 00000000
b13c0dec 00000000 0000027f 7c930000 00000000
b13c0dfc 00000000 00000000 00000000 00001f80
b13c0e0c 23222120 27262524 00380178 00380188
b13c0e1c 00000002 37363534 3b3a3938 00380000
b13c0e2c 003823a0 003804e8 0012f8f0 00380178
b13c0e3c 0012f8f0 7c930981 00380608 000000bc
kd> p
00401021 bb10000000 mov ebx,10h
kd> p
00401026 891dec554200 mov dword ptr ds:[4255ECh],ebx
kd> p
0040102c cf iretd
kd> p
001b:0040107b 0fa1 pop fs
kd> r if
if=1
kd> dq esp
0012ff30 00241fe4`0042003b 7ffd6000`0012f7bc
0012ff40 cccccccc`cccccccc cccccccc`cccccccc
0012ff50 cccccccc`cccccccc cccccccc`cccccccc
0012ff60 cccccccc`cccccccc cccccccc`cccccccc
0012ff70 cccccccc`cccccccc cccccccc`cccccccc
0012ff80 00401389`0012ffc0 00430e70`00000001
0012ff90 00241fe4`00430da0 7ffd6000`0012f7bc
0012ffa0 b11d0d04`00000006 80586123`0012ff94
练习:尝试在中断门中使用 RETF 指令返回
观察发现中断门和调用门在返回时有相似之处,因此可以尝试采用 RETF 0x4
指令返回。
但是在返回后 RETF 0x4
指令会平衡堆栈,而中断门没有参数,因此会造成堆栈不平衡。为了平衡堆栈要将 esp 减 4。
最终代码如下:
#include<stdlib.h>
void __declspec(naked) test() {
__asm {
int 3
retf 4
}
}
int main() {
printf("%X\n",&test);
system("pause");
__asm{
push fs
pushad
pushfd
int 0x20
sub esp,4
popfd
popad
pop fs
}
return 0;
}
程序可以正常退出。
陷阱门
陷阱门描述符
陷阱门与中断门的区别
中断门执行时,会将IF标志位清零,但陷阱门不会。
IF=0 时:程序不再接收可屏蔽中断
- 可屏蔽中断:比如程序正在运行时,我们通过键盘敲击了锁屏的快捷键,若IF位为1,CPU就能够接收到我们敲击键盘的指令并锁屏
- 不可屏蔽中断:断电时,电源会向CPU发出一个请求,这个请求叫作不可屏蔽中断,此时不管IF位是否为0,CPU都要去处理这个请求
IF位是否会被清零是陷阱门与中断门唯一的区别
构造陷阱门
在 IDT 表 0x100 偏移处构造一个陷阱门,对应中断号为 0x20 。
kd> dq idtr l30
8003f400 804e8e00`00080360 804e8e00`000804db
8003f410 00008500`0058113e 804eee00`000808ad
8003f420 804eee00`00080a30 804e8e00`00080b91
8003f430 804e8e00`00080d12 804e8e00`0008137a
8003f440 00008500`00501198 804e8e00`0008179f
8003f450 804e8e00`000818bc 804e8e00`000819f9
8003f460 804e8e00`00081c52 804e8e00`00081f48
8003f470 804e8e00`0008267e 804e8e00`000828f3
8003f480 804e8e00`00082a10 804e8e00`00082b46
8003f490 804e8500`00a028f3 804e8e00`00082cac
8003f4a0 804e8e00`000828f3 804e8e00`000828f3
8003f4b0 804e8e00`000828f3 804e8e00`000828f3
8003f4c0 804e8e00`000828f3 804e8e00`000828f3
8003f4d0 804e8e00`000828f3 804e8e00`000828f3
8003f4e0 804e8e00`000828f3 804e8e00`000828f3
8003f4f0 804e8e00`000828f3 806f8e00`00081fd0
8003f500 0040ee00`0008100a 00000000`00080000
8003f510 00000000`00080000 00000000`00080000
8003f520 00000000`00080000 00000000`00080000
8003f530 00000000`00080000 00000000`00080000
8003f540 00000000`00080000 00000000`00080000
8003f550 804dee00`0008fba2 804dee00`0008fca5
8003f560 804dee00`0008fe44 804eee00`0008078c
8003f570 804dee00`0008f631 804e8e00`000828f3
kd> eq 8003f500 0040ef00`0008100a
kd> dq idtr l30
8003f400 804e8e00`00080360 804e8e00`000804db
8003f410 00008500`0058113e 804eee00`000808ad
8003f420 804eee00`00080a30 804e8e00`00080b91
8003f430 804e8e00`00080d12 804e8e00`0008137a
8003f440 00008500`00501198 804e8e00`0008179f
8003f450 804e8e00`000818bc 804e8e00`000819f9
8003f460 804e8e00`00081c52 804e8e00`00081f48
8003f470 804e8e00`0008267e 804e8e00`000828f3
8003f480 804e8e00`00082a10 804e8e00`00082b46
8003f490 804e8500`00a028f3 804e8e00`00082cac
8003f4a0 804e8e00`000828f3 804e8e00`000828f3
8003f4b0 804e8e00`000828f3 804e8e00`000828f3
8003f4c0 804e8e00`000828f3 804e8e00`000828f3
8003f4d0 804e8e00`000828f3 804e8e00`000828f3
8003f4e0 804e8e00`000828f3 804e8e00`000828f3
8003f4f0 804e8e00`000828f3 806f8e00`00081fd0
8003f500 0040ef00`0008100a 00000000`00080000
8003f510 00000000`00080000 00000000`00080000
8003f520 00000000`00080000 00000000`00080000
8003f530 00000000`00080000 00000000`00080000
8003f540 00000000`00080000 00000000`00080000
8003f550 804dee00`0008fba2 804dee00`0008fca5
8003f560 804dee00`0008fe44 804eee00`0008078c
8003f570 804dee00`0008f631 804e8e00`000828f3
在前面 3 环调 0 环函数代码中的调用门改为陷阱门:
#include<stdlib.h>
typedef struct _STRING {
unsigned short Length;
unsigned short MaximumLength;
char* Buffer;
} STRING;
typedef void(__stdcall* RtlInitAnsiString)(STRING* DestinationString,const char* SourceString);
RtlInitAnsiString initStrFunction=(RtlInitAnsiString)0x804da26f;
STRING str;
const char* bufstr="123456789";
void __declspec(naked) test() {
__asm {
int 3
pushad
pushfd
push fs
mov ax,0x30
mov fs,ax
push bufstr
lea eax,str
push eax
call initStrFunction
pop fs
popfd
popad
iretd
}
}
int main() {
printf("%X\n",&test);
system("pause");
__asm{
push fs
pushad
pushfd
int 0x20
popfd
popad
pop fs
}
return 0;
}
执行后调用 0 环函数。