上一篇提到过,Windows是不使用调用门的,所以在GDT表里没有找到调用门,那么Windows如何实现代码跨段,提权等行为呢?这里用的较多的是中断门,接下来就来介绍一下中断门。
IDT表
与GDT的区别
首先要提到IDT表(中断描述符表),在上一篇提到的调用门的门描述符,都在GDT表里,而中断门的门描述符在另一张叫做IDT的表里面。同GDT一样,IDT也是由一系列描述符组成的,每个描述符占8个字节。但需要注意的是,IDT表中的第一个元素不是NULL。
在Windbg中查看IDT表的基址和长度:
IDT的构成
IDT表可以包含3种门描述符:
- 任务门描述符
- 中断门描述符
- 陷阱门描述符
中断门执行流程
有了IDT表的概念后,咱们就可以开始讲讲中断门的执行流程,实际上和调用门差别不是很大,可以类比的来看:
- 执行调用门的指令:CALL CS:EIP,其中CS是段选择子,包含了查找GDT表的是一个索引.
- 执行中断门的指令:INT N,其中N是IDT表的一个索引
执行流程就只有这个差别,当CPU通过N这个索引在IDT表中找到了中断门描述符后,执行的步骤就和调用门的步骤完全一样了,可以参考调用们的执行流程。这里要注意一点,当找到中断门描述符后,还是会通过描述符里的段选择子,去GDT表中找需要跳转的代码段。所以说中断门的执行会查找两张表,先查找IDT表,再查找GDT表。(这里注意一点,2022年10月9日,key师傅发现了这个问题,GDT表主要是去找段基址,然而这个段基址的值通常为0,IDT表找的是段内偏移,而0加上这个段内偏移就是这个段内偏移本身,因此在后面的课程中,例如API调用3环进入0环时,可能会出现将IDT表查询到的地址直接代入的情况,在大部分情况下这么做是成立的,但是不严谨,特此记录)
中断门描述符
简要说完了IDT表(实际上和GDT表没啥差别)来看看中断门描述符的结构:
粗略一看,和调用门描述符没差呀。这不就是无参调用门描述符换了个Type域嘛。没错,的确是这样(这里解释下D位,可以理解为段描述符的DB位,置0时为16位中断门,置1时为32位中断门)。当你发现这点时,说明调用门的结构你理解清楚了。因此结构不再赘述,可以参考调用门
中断返回
与调用门使用长返回RETF不同,中断门使用中断返回指令:IRET/IRETD
INT N指令:
- 在没有权限切换时,会向堆栈压入3个值,分别是CS,EFLAG,返回地址
- 在有权限切换时,会向堆栈压入5个值,分别是SS,ESP,EFLAG,CS,返回地址
这也是与调用门不同的地方,中断门会多压入一个值。于是有小盆友就要问啦,“死肥宅哥哥,为什么中断门会多压入一个参数呢?” 这还不明显吗,你想想人家调用门为什么要压入值进入堆栈啊?肯定是这些值会改变啊,所以要用堆栈保存一下,等长返回的时候,再还原状态;中断门多压入了一个EFLAG说明通过中断门跨段时,EFLAG的值会变啊!
所以,在中断门中,不能通过RETF返回,而应通过IRET/IRETD返回,其实只要改改堆栈,就可以通过RETF返回中断门,IRETD返回调用门。
代码实现
中断门比较简单,这里演示一个实现的范例
1 |
|
根据GetValue函数的地址构造中断门描述符,然后填入中断门里即可
执行结果如下:
然后我们用int 3中断到Windbg里验证一下
验证成功。
总结
通过调用门与中断门的对比,来总结一下中断门:
- 调用门通过CALL FAR指令执行,但中断门通过INT指令
- 调用门查询GDT表,中断门查询IDT表(后续也会再查询GDT表)
- CALL CS:EIP中的CS是段选择子,由3部分组成,而INT N指令中的N只是索引,中断门不检查RPL,只检查CPL
- 调用门可以有参数,但中断门没有参数