Windows保护模式(四)中断门&陷阱门

中断门

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 环函数。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_sky123_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值