0%

C-10_SEH异常处理

SEH 结构化异常处理

img
1
2
3
4
5
6
7
8
typedef struct _EXCEPTION_RECORD32 {
DWORD ExceptionCode; //异常事件码
DWORD ExceptionFlags; //异常标志
DWORD ExceptionRecord; //下一个EXCEPTION_RECORD结构的地址
DWORD ExceptionAddress; //引发了异常的指令的地址
DWORD NumberParameters;
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD32, *PEXCEPTION_RECORD32;
img

使用筛选器处理异常

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
#include <Windows.h>
#include <stdio.h>

DWORD address = 0;
DWORD WINAPI callback(EXCEPTION_POINTERS* lpExceptionInfo) {
EXCEPTION_RECORD* record = lpExceptionInfo->ExceptionRecord;
CONTEXT* context = lpExceptionInfo->ContextRecord;

char* str = "异常发生的位置:%08x,异常代码:%08x,异常标志:%08x";
char buf[1024] = { 0 };
sprintf_s(buf, sizeof(buf), str, context->Eip, record->ExceptionCode, record->ExceptionFlags);
MessageBoxA(0, buf, "aaa", 0);
context->Eip += address - 3;
//return EXCEPTION_CONTINUE_SEARCH //终止 有提示
return EXCEPTION_CONTINUE_EXECUTION; //继续运行
}

void main() {

SetUnhandledExceptionFilter(callback); // 筛选器 发生异常时先找 callback 在默认的异常处理之前进行筛选

_asm {
mov eax, offset safe
sub eax, offset err
mov address, eax
}

char* p = 0x0;
err: *p = 20; //会触发空指针异常 c
MessageBoxA(0, "不可以执行", "不可以执行", 0);

//_asm int 3

safe:
MessageBoxA(0, "跳到安全地方执继续行", "跳到安全地方执继续行", 0);

}
img
img

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
42
43
44
#include <Windows.h>
#include <stdio.h>

void fn() {
MessageBoxA(0, "安全地方执行", "安全地方执行", 0);
ExitProcess(0);
}

DWORD address = 0;
DWORD WINAPI callback(EXCEPTION_POINTERS* lpExceptionInfo) {
EXCEPTION_RECORD* record = lpExceptionInfo->ExceptionRecord;
CONTEXT* context = lpExceptionInfo->ContextRecord;

char* str = "异常发生的位置:%08x,异常代码:%08x,异常标志:%08x";
char buf[1024] = { 0 };
sprintf_s(buf,sizeof(buf),str,context->Eip, record->ExceptionCode, record->ExceptionFlags);
MessageBoxA(0, buf, "aaa", 0);
context->Eip =fn;
//return EXCEPTION_CONTINUE_SEARCH //终止 有提示
return EXCEPTION_CONTINUE_EXECUTION; //继续运行
}

void main() {

SetUnhandledExceptionFilter(callback); // 筛选器 发生异常时先找 callback 在默认的异常处理之前进行筛选

//_asm {
// mov eax, offset safe
// sub eax, offset err
// mov address, eax
//}

// char* p = 0x0;
//err: *p = 20; //会触发空指针异常 c
// MessageBoxA(0, "不可以执行", "不可以执行", 0);
//
_asm int 3

MessageBoxA(0, "不可以执行", "不可以执行", 0);
//
//safe:
// MessageBoxA(0, "跳到安全地方执继续行", "跳到安全地方执继续行", 0);

}
img

TIB线程信息块

TIB(Thread Information Block 线程信息块) 是保存线程基本信息的数据结构,user mode下位于TEB(Thread Enviroment Block线程环境块)的头部,TEB是操作系统为了保存每个线程的私有数据而创建的,所以每个线程都有TEB。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//线程信息块 保存线程的一些属性
typedef struct _NT_TIB {
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; //seh链入口
PVOID StackBase; //栈基址
PVOID StackLimit;//栈大小
PVOID SubSystemTib;
#if defined(_MSC_EXTENSIONS)
union {
PVOID FiberData;
DWORD Version;
};
#else
PVOID FiberData;
#endif
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self; //指向TIB结构自身
} NT_TIB;


typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD* Next;//指向下一个_EXCEPTION_REGISTRATION_RECORD
PEXCEPTION_ROUTINE Handler; //异常处理回调函数地址
} EXCEPTION_REGISTRATION_RECORD;

TIB永远放在FS段选择器指向的数据段的0偏移处 FS:[0](x86平台的user mode上,fs:[0]总是指向TEB)

参考 https://bbs.pediy.com/thread-273332.htm


SEH安装与卸载

SEH本质就是一个链表,所以我们只需要把我们写好的一个_EXCEPTION_REGISTRATION_RECORD结构插入到链表头就行。首先push指向我们handler的地址,然后push fs:[0],此时就成功的创造了一个_EXCEPTION_REGISTRATION_RECORD。最后mov fs:[0],esp,就成功的修改了我们的TEB,相当于插入了一个新的节点。

img

卸载就是把esp赋值为刚刚存入fs:[0]的(像上图的右下角的那个_EXCEPTION_REGISTRATION_RECORD的next的地址),然后pop一下保证栈帧平衡。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
.386		;使用80386处理器指令集
.model flat,stdcall ;用来定义程序工作的模式,flat:内存模式,stdcall:语言模式
option casemap:none ;是否对变量与子程序名大小写敏感,必须要进行设置

include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib

.const
msg db '异常发生的位置:%08x,异常代码:%08x,异常标志:%08x',0
safe db '转到安全的地方执行',0
caption db '标题aa',0

.code
_Handle proc _lpExceptionRecord, _lp_SEH, _lpContext, _lpDispatcherContext
; loacl 定义局部变量
local szBuffer[256]:byte
pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
;关联起来
assume esi:ptr EXCEPTION_RECORD, edi:ptr CONTEXT

;eip 要用 regEip
invoke wsprintf, addr szBuffer, addr msg, [edi].regEip, [esi].ExceptionCode, [esi].ExceptionFlags
invoke MessageBox, 0, addr szBuffer , addr caption, MB_OK

;调用
mov [edi].regEip, offset safefn
;调用完释放掉
assume esi:nothing, edi:nothing

popad
;正常执行
mov eax, EXCEPTION_CONTINUE_EXECUTION
ret
_Handle endp

start:
;安装seh 在栈中构造一个EXCEPTION_REGISTRATION结构体
assume fs:nothing ;启用fs寄存器
push offset _Handle
push fs:[0]
mov fs:[0], esp

;触发异常
xor eax, eax
mov dword ptr [eax],0 ;产生访问异常
;
;如果这里有指令 不会执行


safefn:
invoke MessageBox, 0, addr safe, addr caption, MB_OK

;卸载seh 把结构体pop出来
pop fs:[0]
pop eax
invoke ExitProcess, NULL

end start

编译后运行

img

会先产生一个 c0000005的异常

然后会在产生一个异常c0000026(看发生位置应该是内核中的) 标志00000001 表示程序不可恢复执行(前面在编写汇编时使用了EXCEPTION_CONTINUE_EXECUTION)没有成功跳到正常执行的地方

img

这里先用给的demo进行编译演示

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.const
szMsg db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szSafe db '回到了安全的地方!',0
szCaption db 'SEH例子',0

.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 错误 Handler
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler proc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
local @szBuffer[256]:byte

pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
invoke wsprintf,addr @szBuffer,addr szMsg,\
[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
;********************************************************************
; 将 EIP 指向安全的位置并恢复堆栈
;********************************************************************
mov eax,_lpSEH
push [eax + 8]
pop [edi].regEip
push [eax + 0ch]
pop [edi].regEbp
push eax
pop [edi].regEsp
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueExecution
ret

_Handler endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test proc

;********************************************************************
; 在堆栈中构造一个 EXCEPTION_REGISTRATION 结构
;********************************************************************
assume fs:nothing
push ebp
push offset _SafePlace
push offset _Handler
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 会引发异常的指令
;********************************************************************
pushad
xor ebp,ebp
xor eax,eax
mov dword ptr [eax],0
popad ;这一句将无法被执行
_SafePlace:
invoke MessageBox,NULL,addr szSafe,addr szCaption,MB_OK
;********************************************************************
; 恢复原来的 SEH 链
;********************************************************************
pop fs:[0]
add esp,0ch
ret

_Test endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke _Test
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
img
img

(1)系统查看产生异常的进程是否正在被调试,如果正在被调试的话,那么向调试器发送EXCEPTION_DEBUG_EVENT事件。

(2)如果进程没有被调试或者调试器不去处理这个异常,那么系统检查异常所处的线程,并在这个线程的环境中查看fs:[0]来确定是否安装有SEH 异常处理回调函数,如果有的话则调用它。

(3)回调函数尝试处理这个异常,如果可以正确处理的话,则修正错误并将返回值设置为ExceptionContinueExecution,这时系统将结束整个查找过程。

(4)如果回调函数返回ExceptionContinueSearch,告知系统它无法处理这个异常,那么系统将根据SEH 链中的 prev字段得到上一个回调函数地址并重复步骤(3),直到链中的某个回调函数返回ExceptionContinueExecution为止,查找结束。

(5)如果到了SEH链的尾部却没有一个回调函数愿意处理这个异常,那么系统将再次检测进程是否正在被调试,如果被调试的话,则再一次通知调试器。

(6)如果调试器还是不去处理这个异常或者进程没有被调试,那么系统检查有没有安装筛选器回调函数,如果有,则去调用它,筛选器回调函数返回时,系统默认的异常处理程序根据这个返回值将做相应的动作。

(7)如果没有安装筛选器回调函数,系统直接调用默认的异常处理程序终止进程。


SEH展开操作

img

展开操作demo

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
		.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
szMsg1 db '这是外层异常处理程序(将处理异常)',0dh,0ah
db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szMsg2 db '这是内层异常处理程序(对异常不进行处理)',0dh,0ah
db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szCaption db '提示信息',0
szBeforeUnwind db '现在将开始 Unwind,当前的 FS:[0] = %08X',0
szAfterUnwind db 'Unwind 返回,当前的 FS:[0] = %08X',0
szSafe1 db '回到了外层子程序的安全位置!',0
szSafe2 db '回到了内层子程序的安全位置!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 外层错误 Handler,将处理异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler1 proc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
local @szBuffer[256]:byte

pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT,fs:nothing
invoke wsprintf,addr @szBuffer,addr szMsg1,\
[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
;********************************************************************
; 将 EIP 指向安全的位置并恢复堆栈
;********************************************************************
mov eax,_lpSEH
push [eax + 8]
pop [edi].regEip
push _lpSEH
pop [edi].regEsp
;********************************************************************
; 对前面的 Handler 进行 Unwind 操作
;********************************************************************
invoke wsprintf,addr @szBuffer,addr szBeforeUnwind,dword ptr fs:[0]
invoke MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK

invoke RtlUnwind,_lpSEH,NULL,NULL,NULL

invoke wsprintf,addr @szBuffer,addr szAfterUnwind,dword ptr fs:[0]
invoke MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK
;********************************************************************
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueExecution
ret

_Handler1 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 内层错误 Handler,不处理异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler2 proc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
local @szBuffer[256]:byte

pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
invoke wsprintf,addr @szBuffer,addr szMsg2,\
[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueSearch
ret

_Handler2 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test2 proc

assume fs:nothing
push offset _SafePlace
push offset _Handler2
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 会引发异常的指令
;********************************************************************
pushad
xor eax,eax
mov dword ptr [eax],0
popad ;这一句将无法被执行
_SafePlace:
invoke MessageBox,NULL,addr szSafe2,addr szCaption,MB_OK
pop fs:[0]
add esp,8
ret

_Test2 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test1 proc

assume fs:nothing
push offset _SafePlace
push offset _Handler1
push fs:[0]
mov fs:[0],esp
invoke _Test2
_SafePlace:
invoke MessageBox,NULL,addr szSafe1,addr szCaption,MB_OK
pop fs:[0]
add esp,8
ret

_Test1 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke _Test1
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
img

References

https://www.cnblogs.com/Rev-omi/p/13893547.html

https://bbs.pediy.com/thread-273332.htm

欢迎关注我的其它发布渠道

------------- 💖 🌞 本 文 结 束 😚 感 谢 您 的 阅 读 🌞 💖 -------------