CPU 内部产生的异常(软中断)可分为 3 种:
1. 陷阱
由代码执行int xx指令,指令执行成功后陷入内核,根据idt表中对应的段描述符,执行对应的中断处理函数,因为是int xx执行成功后才陷入的内核,中断处理函数中得到的异常触发地址为int xx的下一条指令的地址。
2.
故障
一条指令可以分解为多条微指令,如在执行div
ecx时,该指令被分为多条微指令,执行过程中,微指令会先判断操作数的值是否符合要求。如果此时ecx的值为0不符合要求,会产生除0故障(类似int
0指令的效果),陷入内核后,对于cpu来说,该故障已经被处理。
对于操作系统来说异常处理才刚刚开始,这里我们不谈操作系统的异常处理,此时该指令还没执行完毕,应该说执行失败,中断处理函数中得到的异常触发地址为该指令的地址。
例子:触发一个故障
#include
int main(int argc, char *argv[])
{
_asm int 0x20;
return 0;
}
在执行int 0x20过程中,微指令判断对应的门描述符是否有效,要陷入的函数权限是否符合要求,任意不满足则触发故障,指令执行失败。
0x20 号中断在 idt 中的描述符为 0000000000080000,描述符 p 位为0,描述符无效,该指令执行失败触发一个故障,故障产生于应用程序,对于操作系统来说是一个3环异常,所以产生如下效果:
例子:触发一个陷阱一个故障
#include
int main(int argc, char *argv[])
{
_asm int 0x20;
return 0;
}
依旧使用int 0x20,此时我们使用windbg修改idt表将0x20号的门描述符修改为对于cpu来说,3环程序可用的门描述符。
0号门描述符的地址为0x80b95400
20 号门描述符的地址应为 0x80b95400 + 0x20*8 = 0x80b95500
修改为0000 ee 00 0008 0000,此时虽然中断处理函数的地址为00000000,但对于cpu来说是一个3环程序可用的门描述符。
int
0x20执行成功后程序陷入0环,并且试图去0地址执行代码,对于cpu来说陷阱已经处理完毕,在windows中0-0xffff地址为空指针区域,都不是一个合法区域,所以会产生一个访问冲突的0环异常,其对应一个故障,我们在程序的内核态中并没有注册过任何seh结构化异常处理函数,所以该异常将成为一个不可除去的异常,因为处于0环导致系统崩溃蓝屏。
例子:触发两个故障
#include
int main(int argc, char *argv[])
{
_asm{
xor ecx, ecx;
div ecx
}
return 0;
}
该程序会产生一个除0的故障对应中断号为0。
门描述符为84038e0000088650这是一个0环才可用的门描述符,但是我们在3环下触发除0异常,可以使用到此门,这可能是cpu体供的一些特殊机制,对此我们不深究。
当跳转到该门的中断函数地址后,cpu视为该故障已被处理。正常情况下,将执行系统的异常处理机制,此时我们修改该门描述符为00008e0000080000,只修改了中断处理函数,第一个故障处理后cpu将处于0环模式并且试图在0地址执行代码之后流程和之前情况相同蓝屏。
3. 中止
一些文章中写到此类异常标志的最严重的错误,诸如硬件错误,此类异常总是无法精确地报告引起错误的指令的位置,一个比较典型的终止类异常是"双重故障"(中断号为8),当发生一次异常之后,处理器在转入该中断的处理程序时,又发生了另外的异常。
双重故障理解:
首先故障是在执行某条指令的过程中产生的,对于cpu来说,陷入内核跳转到该故障对应的中断处理函数地址,便认为该故障已经被处理,双重故障则是在此过程中又将产生新的故障,此时故障无法被处理,便产生一个双重故障,也就是说所有的故障全都因为一条指令的执行。
例子:触发一个双重故障
#include
int main(int argc, char *argv[])
{
_asm{
xor ecx, ecx;
div ecx
}
}
在执行div ecx过程中将产生一个除0故障,此时cpu对于此故障处理将执行一个int 0的操作,我们修改0 号中断的门符号,将其修改为一个无效的中断描述符此时将发生第二个故障,产生此故障的不是某条指令而是cpu本身的故障处理机制,此时便触发一个双重故障。
修改0号中断的门描述符为一个无效的门描述符, 0000000000080000
双重故障的中断号为8,同时修改8号门描述符,使其处理函数为KiTrap03,方便我们观察效果。
程序运行触发8号中断,并且异常已无法精确地报告引起错误的指令的位置。
此时我又做了一个有趣的小实验:
#include
int main(int argc, char *argv[])
{
_asm{
xor ecx, ecx;
div ecx
}
}
双重故障产生后,cpu依旧会处理这个故障,执行一个int 8的操作,此时我们让int 8也产生一个故障会怎么样呢?
按照原来的思路构建一个双重故障,修改0号终端的门描述符为无效。
修改8号终端的门描述符为无效
很遗憾虚拟机直接重启了。
实验大多是在我的推测基础上进行的,可能有些不严谨或细节的错误,希望大家发现可以给我指出,我对此非常感兴趣。
本文由 看雪论坛 布衣勇者 原创
转载请注明来自看雪论坛
如果你喜欢的话,不要忘记点个赞哦!
热门阅读文章:
更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!
看雪论坛:http://bbs.pediy.com/
微博:看雪安全
商务合作:[email protected]