异常处理机制

原来对异常的理解就是seh,FS:[0]

借此机会来好好的学习下

什么是异常

首先异常分为俩种,CPU产生的异常(除0,缺页)和软件模拟产生的异常

CPU产生异常

首先CPU检测到异常->查IDT表->CommonDispatchException->KiDispatchException

CommonDispatchException构造了异常结构体如下,参数是异常类型和发生地址

1
2
3
4
5
6
7
8
9
10
type struct _EXCEPTION_RECORD		
{
DWORD ExceptionCode; //异常代码
DWORD ExceptionFlags; //异常状态,CPU还是软件
struct _EXCEPTION_RECORD* ExceptionRecord; //下一个异常
PVOID ExceptionAddress; //异常发生地址
DWORD NumberParameters; //附加参数个数
ULONG_PTR ExceptionInformation
[EXCEPTION_MAXIMUM_PARAMETERS]; //附加参数指针
}

KiDispatchException分发异常,找到对应处理函数去处理

软件模拟产生异常

一般情况下软件模拟产生异常会调用RaiseException函数来自Kernel32.dll

该函数调用ntdll.dll里的RtlRaiseException函数,然后继续往下调用NtRaiseException和KiRaiseException,最后调用KiDispatchException

image-20200813174520978

整体如上图所示

内核层异常处理流程

首先不管是用户态异常还是内核异常,都要通过KiDispatchException函数来进行分发,所以我们主要分析该函数

image-20200814214113613

异常和APC很像,可以看到这边开始备份了,但是我们先分析内核如何处理所以没啥用

image-20200814214329255

首先判断先前模式 0是内核层1是用户层,仅接着判断是否是第一次调用,然后再看是否有内核调试器,没有或者内核调试器不处理就调用RtlDispatchException,返回false的话再次判断是否有内核调试器 有则调用,没有直接蓝屏

下面看看RtlDispatchException,这个就是我们"熟悉"的异常处理了

1
2
3
4
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next;
PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;

遍历异常链表,没错就是FS:[0]那的,调用异常处理函数,如果被处理了函数返回值为1,如果该异常处理函数不能处理则调用下一个,直到next指针为0xffffffff,如果没有函数能够处理该异常则返回0.

image-20200814215131298

注意这边fs并不是指向PEB,这边是内核处理,fs是kpcr,只是kpcr和peb第一个成员一样都是tib。

用户层异常处理流程

image-20200816132126545

和内核层一样先判断是否第一次调用,然后判断内核调试器是否存在,如果没有,把异常信息发送给三环调试器,如果三环调试器没有处理或者不存在,那么就开始返回三环恢复环境

image-20200816132242032

返回到ntdll中的KiUserExceptionDispatcher,可以看到这边修改了eip

VEH

向量化异常处理

关于这个得分析下_KiUserExceptionDispatcher@8

image-20200816193822433

调用_RtlDispatchException@8处理异常,如果处理成功则_ZwContinue@8进0环回到原来的地方

否则_ZwRaiseException@12分发第二轮异常

来看看_RtlDispatchException@8

image-20200816193924188

_RtlCallVectoredExceptionHandlers@8就是查找VEH链表,如果有的话就调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <stdlib.h>

typedef PVOID(NTAPI* FnAddVectoredExceptionHandler)(ULONG, _EXCEPTION_POINTERS*);
FnAddVectoredExceptionHandler MyAddVectoredExceptionHandler;

LONG NTAPI VectExcepHandler(PEXCEPTION_POINTERS pExcepInfo)
{
if (pExcepInfo->ExceptionRecord->ExceptionCode == 0xC0000094)//除0异常
{
//将除数修改为2
pExcepInfo->ContextRecord->Ecx = 2;
//或者修改发生异常的代码的Eip idiv ecx长度2字节 从下一行开始执行
//pExcepInfo->ContextRecord->Eip = pExcepInfo->ContextRecord->Eip + 2;
return EXCEPTION_CONTINUE_EXECUTION;//已处理
}
return EXCEPTION_CONTINUE_SEARCH;//未处理
}

int main()
{
HMODULE hModule = GetModuleHandle(L"Kernel32.dll");
MyAddVectoredExceptionHandler = (FnAddVectoredExceptionHandler)::GetProcAddress(hModule, "AddVectoredExceptionHandler");
//参数1表示插入VEH链的头部, 0插入到VEH链的尾部
MyAddVectoredExceptionHandler(0, (_EXCEPTION_POINTERS*)&VectExcepHandler);
//构造除0异常
int val = 0;
_asm
{
xor edx, edx
xor ecx, ecx
mov eax, 100
idiv ecx
mov val, eax //结果在eax
}
printf("val = %d\n", val);
getchar();
}

可见上面示例代码,除零异常处理

image-20200816194630333

修改了ecx为2后发现是50

SEH

SEH就是平时所熟知得结构化异常处理了,CTF中很常见

首先是处理VEH没有的话则SEH,见_RtlpGetStackLimits@8

image-20200816194859195

检测SEH结构是否在堆栈中

继续看_RtlpGetRegistrationHead@0

image-20200816194957384

把异常处理链表头取出

image-20200816195127156

image-20200816195411363

检测是否有效后执行SEH

文章目录
  1. 1. 什么是异常
    1. 1.1. CPU产生异常
    2. 1.2. 软件模拟产生异常
  2. 2. 内核层异常处理流程
  3. 3. 用户层异常处理流程
  4. 4. VEH
  5. 5. SEH
|