首页
社区
课程
招聘
[原创]基于windows软件异常触发原理
发表于: 2021-3-20 14:03 9170

[原创]基于windows软件异常触发原理

2021-3-20 14:03
9170

在使用C++编写的调试器项目时,引发了对Windows软件异常机制的兴趣。

分析工具:IDA7.5

分析方式:静态分析

分析用到的DLL模块以及内核程序:Kernel32.dll、Ntdll.dll 、ntoskrnl.exe

初学者飘过,如有错误,还请指正,共同学习。

​ CPU捕获

→查IDT表,执行中断处理函数(中断处理函数不处理异常,给程序员一次机会)

→调用CommonDispatchException(给构建的结构体进行赋值)

→在堆栈上构建了一个结构体(记录相关异常信息)

→KiDispatchExceptin(分发异常:目的是找到异常的处理函数)判断是用户异常还是内核异常

KiDispatchException (

IN PEXCEPTION_RECORD ExceptionRecord,

IN PKEXCEPTION_FRAME ExceptionFrame,

IN PKTRAP_FRAME TrapFrame, 备份到context里面

IN KPROCESSOR_MODE PreviousMode, 0是内核 1是用户

IN BOOLEAN FirstChance ;是否是第一次执行 1是第一次执行

)

→KeContextFormKframes(将Trap_Frame备份到context 为返回3环做准备)

→RtlDispatchException(如果没有内核调试器,或者调试器没有处理,才会执行)

→RtlpGetRegistrationHead注册登记循环遍历

→返回0,否则未处理异常

→再次判断是否存在内核调试器

→蓝屏

→KiDispatchException 通过IRETD返回3环

→返回CommonDispatchException进行第二次异常触发

​ CPU捕获

→查IDT表,执行中断处理函数(中断处理函数不处理异常,给程序员一次机会)

→调用CommonDispatchException(给构建的结构体进行赋值)

→在堆栈上构建了一个结构体(记录相关异常信息)

→KiDispatchExceptin(分发异常:目的是找到异常的处理函数)判断是用户异常还是内核异常

→KeContextFormKframes(将Trap_Frame备份到context 为返回3环做准备)

​ 前面的处理流程和CPU异常处理的流程是一致的,用户异常到这就开始有所不同

→KiDispatchException中修改Trap_Frame里面的寄存器的值

→最关键的修改是在0环中的全局变量KeUserExceptionDispatcher会赋一个值是3环中的KiUserExceptionDispatcher函数

​ 修正EIP为KiUserExceptionDispatcher ,通过系统调用返回3环

→返回CommonDispatchException

→当线程再次回到3环时,将执行KiUserExceptionDispatcher 函数

为什么叫模拟异常,因为当用户异常第二次产生的时候,就是走的模拟异常的流程,它是系统或编写程序的作者主动产生的。

throw关键字(依赖编译器)(主动抛异常)

→CxxThrowException不同编译器所调用的模拟异常的函数

→KERNEL32.DLL!RaiseException(DWORD dwExceptionCode,DWORD dwExceptionFlags,
DWORD nNumberOfArguments,const ULONG_PTR* lpArguments)(填充EXceptionRecord结构体)

(KERNEL32.DLL)RaiseException函数分析:

​ 和CPU异常的差异:

​ ExceptionCode根据编译器的不同,存在不同的异常代码

​ ExceptionAddress 存储的是RaiseException的地址

​ 相同点:使用相同的_EXCEPTION_RECORD结构体

_EXCEPTION_RECORD结构体:

​ type struct _EXCEPTION_RECORD

​ {

​ DWORD ExceptionCode ; 异常代码

​ DWORD ExceptionFlags ; 异常状态

​ struct _EXCEPTION_RECORD* ExcptionRecord ;下一个异常

​ PVOID ExceptionAddress ; 异常发生地址

​ DWORD NumberParameters ; 附加参数个数

​ ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];附加参数指针

}

→NTDLL.DLL!RtRaiseException()

→NT!NtRaiseException (进内核啦)

​ 原型:

→NT!KiRaiseException(EXceptionRecord 最高位清零)

原型:

→KiDispatchExceptin(分发异常:目的是找到异常的处理函数)判断是用户异常还是内核异常

→KeContextFormKframes(将Trap_Frame备份到context 为返回3环做准备)

→KiDebugRoutine(内核调试器)(判断是否存在)

→DbgkForwardException(内核调试器不存在或返回FALSE,那么此函数将调用3环调试器)

→如果3环的调试器没有处理这个异常那么就得修改Trap_Frame里面的寄存器的值

→最关键的修改是在0环中的全局变量KeUserExceptionDispatcher会赋一个值是3环中的KiUserExceptionDispatcher函数

​ 修正EIP为KiUserExceptionDispatcher ,通过系统调用返回3环


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-3-20 14:05 被APT_华生编辑 ,原因:
收藏
免费 10
支持
分享
最新回复 (4)
雪    币: 3736
活跃值: (3867)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
感谢分享!
2021-3-20 16:42
0
雪    币: 137
活跃值: (1350)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
最近也在学习异常处理机制(目前在学SEH) X64下我有点不解的地方是x64的exception table似乎是全局共享的(加载一个dll,这个dll的exception table会被放到全局的一个表里面),也就是说其他模块能给不属于自己的领空的地址加SEH?
2021-3-28 14:17
0
雪    币: 3903
活跃值: (3564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
SEH局部链表,所以在使用的时候只对当前线程有效,VEH才是属于全局链表,当VEH中无法处理此异常时,才会交给SEH
2021-3-28 15:00
0
雪    币: 3903
活跃值: (3564)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
当然,OD这种调试器为何无法调试其他线程,需要切换才能调试,就因为是使用这种机制实现的
2021-3-28 15:02
0
游客
登录 | 注册 方可回帖
返回
//