标 题
:
SEH源码赏析之C篇
作 者
:
xinlin
时 间
:
2007
-
10
-
24
,
12
:
12
链 接
:
http
:
//bbs.pediy.com/showthread.php?t=53778
SEH结构化异常处理源码赏析
(
C篇
)
关键字
:
C SEH 结构化异常处理 _try _finally _except _except_handler3 VC
工程使用工具
:
VC7
.1.3088
IDA pro
4.9.0.863
cdb Windbg Editplus
1.
起因
C
++
程序员对
try
,
catch
,
throw
都应该很熟悉
,
能知道VC怎么实现它的人就不多了
,
不过网络世界使很多人知道了它与SEH
(
structured exception handling)有密切关系
,
我也不例外
,
也是在若干年前从网络知道了SEH
,
并且大致也知道SEH的流程
.
但是和多数人一样在我的实践也很少直接使用 SEH
,
对SEH也就仅限于网络上一些文章的介绍
.
曾经在用Windbg对某些软件作分析
,
我遇到了断点失效的情况
,
查找资料介绍是SEH中的 Handler清除了调试寄存器
,
在分析SEH代码中由于VC没有SEH的源码
,
于是我产生了一种想法
,
详细完整地翻译VC的SEH的代码
,
一劳永逸的解决问题
.
C和C
++
的SEH有所不同
,
C
++
的要复杂些
,
我在此介绍的仅为C的SEH代码
,
也就是__try
,
__finally
,
__except
,
__leave所产生的SEH代码
,
C
++
篇有时间的话我再作
.
我以前看过的资料大都以比较专业的语言介绍
,
在此我仅以我自己感觉比较通俗的语言介绍
,
希望能有更多的人能认识
,
认清SEH
.
2.SEH
术语
:
SEH中术语虽然不多
,
但由于没有SDK的明确定义有时很难区别
,
各家说的表述也不太统一
,
因此为此文特定义如下术语
,
这些术语可能与其它文献有点冲突或细微差别
,
有的也是我自己的定义
:
A
.
SEH
(
structured exception handling
):
在C语言中关键字是__try
(
_try
),
__finally
(
_finally
),
_except
(
__except
),
而C
++
使用的关键字是
try
,
catch
.
在以下的表述中所有的
try
均不再特别声明为C关键字
,
一律默认为C关键字
.
B
.
EH3_List
:
_EH3_EXCEPTION_REGISTRATION链表
,
表头位于FS
:[
0
],
0xFFFFFFFF
为链表结束标志
.
编译器在编译一个函数时只要检测到含有_try或__try则为此函数生成一个_EH3_EXCEPTION_REGISTRATION节点
,
并插入到表头
.
因为每个函数编译只生成一个节点
,
因此在一个函数中C和C
++
的SEH不能同时存在
,
如果代码中同时有
catch
和except则不能通过编译就是此原因
.
C
.
EH3_ScopeTable
:
是一个由编译器在data section生成的一张表
(
数组
),
实质可看作是二叉树结构
(
可能有多个二叉树顺序存放
),
节点为_SCOPETABLE_ENTRY类型
,
其中 _SCOPETABLE_ENTRY
.
ParentLevel是父节点在数组中的位置
,
EH3_ScopeTable
[
0
]
是根节点
,
_SCOPETABLE_ENTRY
.
ParentLevel
=
0xFFFFFFFF.
由此可见ParentLevel很重要
,
是SEH判断
try
嵌套层次的唯一依据
.
编译器从函数入口点开始遍历
try
,
每遇到一个
try
生成一个节点_SCOPETABLE_ENTRY
,
并放在表最后
,
注意节点的先后与
try
的嵌套层次无关
.
D
.
filter handler
:
是异常发生后让用户决定是否认识此异常
,
通过修改异常语句的上下文环境
(
CONTEXT
)
可使应用程序能继续正常运行
.
其返回值有三
.
EXCEPTION_EXECUTE_HANDLER
(
1
):
去执行exception handler
,
然后进程终止
,
且不显示出错提示框
.
EXCEPTION_CONTINUE_SEARCH
(
0
):
不执行exception handler
,
显示出错提示框
,
进程终止或者进入调试器进行调试
.
EXCEPTION_CONTINUE_EXECUTION
(-
1
):
不执行exception handler
,
系统用CONTEXT重新设置CPU环境
,
进程继续执行
,
如果修改了EIP则从新的EIP开始执行
,
否则从原异常点开始执行
.
E
.
exception handler
:
是异常发生后检测到该异常无法被处理
,
而进程终止前提醒应用程序执行的收尾工作
F
.
termination handler
:
如果
try
语句被过早终止
,
不管是正常离开或者是非正常离开
,
包括
goto
,
leave及异常时均执行此handler
,
具体执行过程可查MSDN
.
G
.
展开
(
unwind
):
这个名词很让人费解
,
翻译也确实不好命名
,
我也沿用此名
.
展开的目的是执行finally对应的termination handler
,
对照在下面的源代码中我们就能很容易理解MSDN关于finally的解释了
.
展开分为本地局部展开
(
_local_unwind
)
和全局展开
(
_global_unwind
);
本地展开
:
展开此
try
所在函数
try
嵌套关系并分别执行其finally对应的termination handler
;
全局展开
:
当exception handler不在本
try
函数时
,
执行exception handler前需要先执行这之前的termination handler
,
全局展开就是查找这些termination handler并执行它
.
需要说明的是全局展开不含本地展开
.
3.SEH
数据结构
typedef struct
// (sizeof=0xC)
{
DWORD ParentLevel
;
// 当前Handler父层TRY在EH3_ScopeTable中的位置,根没有上一层,故值=-1
// 形成TRY层次的二叉树结构,与_EH3_EXCEPTION_REGISTRATION.TryLevel物理意义一样
DWORD FilterFunc
;
// 非NULL则HandlerFunc是exception handler,否则termination handler
DWORD HandlerFunc
;
// exception handler or termination handler
}
_SCOPETABLE_ENTRY
;
typedef struct
// (sizeof=0x10)
{
_EH3_EXCEPTION_REGISTRATION
*
pPrev
;
// 栈上一级EH3_List节点,=0xFFFFFFFF则为最后一个节点
EXCE_HANDLER ExceptionHandler
;
// VC7.1中统一指向_except_handler3
_SCOPETABLE_ENTRY
*
pScopeTable
;
// 指向一个_SCOPETABLE_ENTRY数组,函数有n个TRY,则数组有n个元素
// p[0]->Try0为根,p[1]->Try1,p[2]->Try2...
DWORD TryLevel
;
// 指示当前指令在Try的层次级别,-1未进入TRY,进第一个TRY为0,第二个为1,...
// 但它与嵌套层次无关,在编译时确定,从函数代码开始处开始计数
}
_EH3_EXCEPTION_REGISTRATION
;
typedef struct
// (sizeof=0x10)还有待进一步分析其用处
{
DWORD unKnown
;
// 未知:被编译器赋值
DWORD HandlerFunc
;
// _SCOPETABLE_ENTRY.HandlerFunc
DWORD firstPara
;
// Try所在函数第一个参数:crtMain!EBP+8
DWORD TryEBP
;
// Try所在函数EBP
}
_NLGDestination
;
// 以下在MSDN中均有定义,不再作解释
typedef struct
_EXCEPTION_RECORD
{
DWORD ExceptionCode
;
DWORD ExceptionFlags
;
struct
_EXCEPTION_RECORD
*
ExceptionRecord
;
PVOID ExceptionAddress
;
DWORD NumberParameters
;
ULONG_PTR ExceptionInformation
[
EXCEPTION_MAXIMUM_PARAMETERS
];
}
EXCEPTION_RECORD
,*
PEXCEPTION_RECORD
;
typedef struct
_CONTEXT
{
...
// 依据CPU类型有不同定义,具体可见winnt.h
}
CONTEXT
,*
PCONTEXT
;
typedef struct
_EXCEPTION_POINTERS
{
PEXCEPTION_RECORD ExceptionRecord
;
PCONTEXT ContextRecord
;
}
EXCEPTION_POINTERS
,*
PEXCEPTION_POINTERS
;
4.SEH
汇编要点
A
.
函数中
try
的数据模型
:
VC将会为有
try
函数在栈上首先建立三个变量
:
EBP
-
18h
:
SaveESP
// TRY前保存当时ESP,因此有:SaveESP<&Eh3Exception
EBP
-
14h
:
pExceInfo
// GetExceptionPointers(),在handler中调用filter前赋值
EBP
-
10h
:
Eh3Exception
// 一个_EH3_EXCEPTION_REGISTRATION,其大小刚好为10h哦
EBP
+
00h
:
ebp
// 上一frame的EBP
EBP
+
04h
:
EIP
// CALL返回地址
在这里我们应该注意到这个公式是成立的
:
EBP
=&
Eh3Exception
+
10h
而在_except_handler3中参数
:
pEh3Exce刚好为
&
Eh3Exception
,
所以每当在_except_handler3中要回调filter
,
exception
,
termination handler时
,
在这之前汇编中均有一句
:
lea ebp
, [
ebx
+
10h
]
// ebx=_except_handler3!arg_pEh3Exception
明白了这点就不难明白如何在_except_handler3中访问pExceInfo及SaveESP了
!
B
.
try
块的终止
:
异常发生
,
goto
,
return
,
leave
,
正常终止
.
goto
和
return
可能跨过其它
try
,
所以必须要展开到目的地所在的 TryLevel
,
但是leave关键字不会跨过其它
try
,
只是跳出自己这层
try
,
如果编译器检测到这层
try
有termination handler
,
则用CALL xxx直接调用
.
当然也有例外
,
这也是VC聪明的地方
,
如果函数只有一个
try
,
则根本不用展开而直接使用CALL了
,
这种情况有时可见
.
这样就不难理解如下代码
:
goto
004013fb
终止代码
:
// ---------------goto跳出try----------------------------------------
goto
try0
;
push
0
或者
(
仅一个
try
时
)
CALL
00401070
// 直接调termination handler
lea eax
, [
ebp
+
var_Eh3Exce
]
jmp loc_4013FB
push eax
call __local_unwind2
// 展开到0(即第一个try内,goto目的地肯定在第一个try内)
add esp
,
8
jmp loc_4013FB
return
终止代码
:(
无返回值
)
// ---------------return跳出try--------------------------------------
push
0FFFFFFFFh
lea eax
,[
ebp
-
10h
]
push eax
call __local_unwind2
(
401786h
)
// 展开到-1(即所有try外)
add esp
,
8
return
;
jmp $L19800
(
401139h
)
return
var_i 终止代码
:(
有返回值
)
// ---------------return(带返回值)跳出try----------------------------
mov eax
,
dword ptr
[
i
]
mov dword ptr
[
ebp
-
100h
],
eax
// 返回值先被保存
push
0FFFFFFFFh
lea ecx
,[
ebp
-
10h
]
push ecx
call __local_unwind2
(
4017D6h
)
// 再展开,即使finally中改变了i,也不会改变返回值!!!
add esp
,
8
return
i
;
mov eax
,
dword ptr
[
ebp
-
100h
]
// 取保存的返回值来保存
jmp $L19800
(
40117Bh
)
// 跳到复原先前SEH链处
leave和正常退出的代码
:
// ---------------leave跳出try---------------------------------------
if
(
x
>
18
)
004010FD
cmp dword ptr
[
x
],
12h
00401101
jle FunC
+
55h
(
401105h
)
__leave
;
00401103
jmp FunC
+
66h
(
401116h
)
// 直接调用termination handler
printf
(
"%s Try!\n"
,
fun
);
00401105
mov eax
,
dword ptr
[
fun
]
00401108
push eax
00401109
push offset string
"%s Try!\n"
(
410130h
)
0040110E
call printf
(
401650h
)
00401113
add esp
,
8
00401116
mov dword ptr
[
ebp
-
4
],
0FFFFFFFFh
// 退出try
// ---------------正常退出try----------------------------------------
0040111D
call $L19798
(
401124h
)
// 直接调用termination handler
00401122
jmp $L19801
(
40113Fh
)
C
.
_except_handler3执行两次原因
,
实际上理解了展开就能理解它
,
举例解释如下
:
EH3_List如
:
FS
[
0
]->
E1
->
E2
->
E3
->
E4
,
在E4中发生异常
,
依次搜索E1
,
E2
,
E3
,
最后在E4中找到能处理此异常的filter handler
,
这样E4
,
E3
,
E2
,
E1的_except_handler3均执行了一次
,
这是第一次
.
第二次
:
在执行E4的exception handler前要先调用全局展开
(
_global_unwind2
)
以运行E1
,
E2
,
E3的termination handler在全局展开
(
_global_unwind2
)
中将为EXCEPTION_RECORD
.
ExceptionFlags增加标志 _EH_UNWINDING
(
2
),
再依次调用它们的_except_handler3
,
这就是E1
,
E2
,
E3的_except_handler3的第二次调用
,
但E4只有一次
.
5.
VC7
.1
下的SEH的C代码
:
// 源码基本以汇编为蓝本,没有进行优化,主要是为了方便大家与汇编对照阅读
#define
MAX_PAGES
0x10
int
ValidateEH3RN
(
_EH3_EXCEPTION_REGISTRATION
*
pEh3Exce
)
{
// 1.验证_EH3_EXCEPTION_REGISTRATION.pScopeTable的合法性:4字节对齐,且不在栈空间内,因为它是编译器生成的全局变量
_SCOPETABLE_ENTRY
*
pScopeTable
=
pEh3Exce
->
pScopeTable
;
if
(((
DWORD
)
pScopeTable
&
0x3
) ==
0
)
return
0
;
NT_TIB
*
pTeb
= (
NT_TIB
*)
FS
[
0x18
];
DWORD nStackLimit
=
pTeb
->
StackLimit
;
if
(
pScopeTable
>=
pTeb
->
StackLimit
&&
pScopeTable
<
pTeb
->
StackBase
)
return
0
;
// pScopeTable在栈内肯定不合法
// 2.判断二叉树pScopeTable是否合法,并统计exception handler数量
if
(
pEh3Exce
->
TryLevel
== -
1
)
return
1
;
// 表示语句不在try中
DWORD nFilters
,
count
;
nFilters
=
count
=
0
;
for
(;
nFilters
<=
pEh3Exce
->
TryLevel
;
nFilters
++)
{
// LOOP1:验证已进入的TRY的SCOPETABLE_ENTRY是否都合法
if
(
pScopeTable
[
nFilters
].
ParentLevel
!=-
1
&&
pScopeTable
[
nFilters
].
ParentLevel
>=
nFilters
)
return
0
;
// EnclosingLevel在二叉树中不合法:不是根结点且ParentLevel>=nFilters
// ParentLevel>=nFilters不合法原因:二叉树存贮不可能儿子先存
if
(
pScopeTable
[
nFilters
].
FilterFunc
==
NULL
)
continue
;
// termination handler不统计
count
++;
}
if
(
count
!=
0
)
{
// 有异常处理,验证saveESP
PVOID saveESP
=
0
;
// 运行时真实值 = crtMain!saveESP=crtMain!(ebp-18h);
if
(
saveESP
<
nStackLimit
||
saveESP
>=
pEh3Exce
)
return
0
;
// 不在栈内,或不在TRY函数内,saveESP为TRY函数的栈顶
}
// 3.找g_rgValidPages中是否已经有pScopeTable的页基址
static int
g_nValidPages
=
0
;
// g_rgValidPages数组有效元素个数
static int
g_rgValidPages
[
MAX_PAGES
]={
0
};
// MaxPages=0x10
DWORD nPageBase
= (
DWORD
)
pScopeTable
&
0xfffff000
;
// 取pScopeTable所在页基址
int
nFind
=
0
;
if
(
g_nValidPages
>
0
)
{
do
{
// LOOP2
if
(
nPageBase
==
g_rgValidPages
[
nFind
])
goto
Finded
;
// 找到
nFind
++;
}
while
(
nFind
<
g_nValidPages
);
}
// 4.没有过在g_rgValidPages找到nPageBase则判断pScopeTable是否在合法的EXE映像内
MEMORY_BASIC_INFORMATION memInfo
;
if
(
VirtualQuery
(
pScopeTable
,&
memInfo
,
sizeof
(
memInfo
)) ==
0
)
return
-
1
;
if
(
memInfo
.
Type
!=
0x01000000
)
// MEM_IMAGE
return
-
1
;
DWORD protect
=
0xCC
;
// PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY;
if
(
memInfo
.
Protect
&
protect
)
{
// 有指定属性
IMAGE_DOS_HEADER
*
pDosHeader
= (
IMAGE_DOS_HEADER
*)
memInfo
.
AllocationBase
;
if
(
pDosHeader
->
e_magic
!=
'ZM'
)
return
-
1
;
// 非法DOS头,pScopeTable不为编译器分配的空间
IMAGE_NT_HEADERS
*
pPeHeader
= (
IMAGE_NT_HEADERS
*)((
char
*)
pDosHeader
+
pDosHeader
->
e_lfanew
);
if
(
pPeHeader
->
Signature
!=
'ZM'
)
return
-
1
;
// 非法PE头,pScopeTable不为编译器分配的空间
IMAGE_OPTIONAL_HEADER32
*
pOptHeader
= &
pPeHeader
->
OptionalHeader
;
if
(
pOptHeader
->
Magic
!=
0x10b
)
return
-
1
;
// 非WIN32 EXE
DWORD rvaScope
= (
DWORD
)
pScopeTable
-(
DWORD
)
pDosHeader
;
// 计算pScopeTable的RAV
if
(
pPeHeader
->
FileHeader
.
NumberOfSections
<=
0
)
return
-
1
;
IMAGE_SECTION_HEADER
*
pSection
= (
IMAGE_SECTION_HEADER
*)(
pPeHeader
+
1
);
if
(
rvaScope
>=
pSection
->
VirtualAddress
&&
rvaScope
<
pSection
->
VirtualAddress
+
pSection
->
Misc
.
VirtualSize
)
{
// rvaScope在代码节内
if
(
pSection
->
Characteristics
&
0x80000000
)
// 0x80000000=IMG_SCN_MEM_WRITE
return
0
;
}
}
// 5.对新验证的nPageBse插入到数组中
static int
g_nLock
=
0
;
// 1:上锁,0:解锁
if
(
InterlockedExchange
(&
g_nLock
,
1
)!=
0
)
// 写线程锁
return
1
;
// 其它线程已经进入,则不能再进入
int
k
=
g_nValidPages
;
if
(
k
>
0
)
{
do
{
// LOOP4:判断其它线程是否写入这个页基址
if
(
nPageBase
==
g_rgValidPages
[
k
-
1
])
break
;
// 找到,k>0
k
--;
}
while
(
k
>
0
);
}
if
(
k
==
0
)
{
// 没有找到,nPageBase插入到g_rgValidPages头
int
pages
=
0x0F
;
if
(
g_nValidPages
<=
pages
)
pages
=
g_nValidPages
;
k
=
0
;
if
(
pages
>=
0
)
{
do
{
// LOOP5
int
temp
=
g_rgValidPages
[
k
];
g_rgValidPages
[
k
]=
nPageBase
;
nPageBase
=
temp
;
k
++;
}
while
(
k
<=
pages
);
}
if
(
g_nValidPages
<
0x10h
)
g_nValidPages
++;
}
InterlockedExchange
(&
g_nLock
,
0
);
// 解锁
return
1
;
// 6.找到的nPageBase移到头
// 但前面找到的结果也可能被其它线程改变位置甚至推出数组,但无论如何这个nPageBase合法可不再验证
Finded
:
if
(
nFind
<=
0
)
// 相当于if(nFind == 0)
return
1
;
// nPageBase已经在头,可直接返回
if
(
InterlockedExchange
(&
g_nLock
,
1
)!=
0
)
// 写线程锁
return
1
;
// 其它线程已经进入,则不能再进入
if
(
g_rgValidPages
[
nFind
] !=
nPageBase
)
{
// 再次对找到的pos进行比较,因为其它线程可能又修改了这个元素的值
nFind
=
g_nValidPages
-
1
;
if
(
nFind
>=
0
)
{
while
(
nFind
>=
0
)
{
// LOOP3
if
(
g_rgValidPages
[
nFind
]==
nPageBase
)
break
;
nFind
--;
}
if
(
nFind
>=
0
)
goto
End1
;
}
// 没找到,新增加
if
(
g_nValidPages
<
0x10
)
g_nValidPages
++;
nFind
=
g_nValidPages
-
1
;
goto
end2
;
}
else
goto
end2
;
end1
:
if
(
nFind
!=
0
)
{
end2
:
if
(
nFind
>=
0
)
{
for
(
int
j
=
0
;
j
<=
nFind
;
j
++)
{
// LOOP6:g_rgValidPages中找到的nPageBase移到头或新nPageBase插入头,其余每个元素向后推
int
temp
=
g_rgValidPages
[
j
];
g_rgValidPages
[
j
]=
nPageBase
;
nPageBase
=
temp
;
}
}
}
InterlockedExchange
(&
g_nLock
,
0
);
// 解线程锁
return
1
;
}
void
_global_unwind2
(
_EH3_EXCEPTION_REGISTRATION
*
pEh3Exce
)
{
// 对调用RtlUnwind的封装
RtlUnwind
(
pEh3Exce
,
offset exit
,
0
,
0
);
exit
:
return
;
}
int
_UnwindHandler
(
EXCEPTION_RECORD
*
pExceRec
,
_EH3_EXCEPTION_REGISTRATION
*
pEh3Exce
,
void
*,
_EH3_EXCEPTION_REGISTRATION
**
ppEh3Exce
)
{
if
(
pExceRec
->
ExceptionFlags
&&
6
==
0
)
return
1
;
// ExceptionContinueSearch
*
ppEh3Exce
=
pEh3Exce
;
return
3
;
// ExceptionCollidedUnwind
}
// NLG == "non-local-goto"
_NLGDestination g_NLGDestination
;
// 此全局变量我至始至终未见到任何其它EXE处或者DLL使用,通知是何意义暂无知!!!!!!!
void
_NLG_Notify1
(
int
x
)
// --------------------------CallSettingFrame调用
{
// g_NLGDestination全局分配变量:0x19930520
g_NLGDestination
.
dwInCode
=
EBP
+
8
;
// Try函数中第一个参数???
g_NLGDestination
.
HandlerFunc
=
EAX
;
// 调用前通过EAX传入
g_NLGDestination
.
TryEBP
=
EBP
;
// Try中的EBP???
}
void
_NLG_Notify
(
int
x
)
// --------------------------_except_handler3,_local_unwind2调用
{
// 传入参数未用
// g_NLGDestination.unKnown全局分配变量:0x19930520
g_NLGDestination
.
dwInCode
=
EBP
+
8
;
// Try函数中第一个参数,如:FunB(i,j)!i,crtMain!ebp+8
g_NLGDestination
.
HandlerFunc
=
EAX
;
// 调用前通过EAX传入:pScopeTable.HandlerFunc
g_NLGDestination
.
TryEBP
=
EBP
;
// Try中的EBP
}
// nToLevel:展开到此为止(不含nToLevel),举例:在goto中nToLevel由目标所在try决定,因为系统规定跳入TRY是不合法的,因此
// goto不能跳入其它TRY(嵌套的上层TRY是合法的,很明显平级层是不合法的)层,这时编译是通不过的.
void
_local_unwind2
(
_EH3_EXCEPTION_REGISTRATION
*
pEh3Exce
,
int
nToLevel
)
{
// 用ESP,未用EBP,之前的EBP传入内调的_NLG_Notify,__try中的goto将用此函数展开得到其handler
_EH3_EXCEPTION_REGISTRATION eh3Unwind
;
eh3Unwind
.
TryLevel
= (
int
)
pEh3Exce
;
eh3Unwind
.
pScopeTable
= (
_SCOPETABLE_ENTRY
*)-
2
;
eh3Unwind
.
ExceptionHandler
= (
EXCE_HANDLER
)
_UnwindHandler
;
eh3Unwind
.
pPrev
= (
_EH3_EXCEPTION_REGISTRATION
*)
FS
[
0
];
FS
[
0
]=
ESP
;
int
nLevel
;
//
while
(
1
)
{
_SCOPETABLE_ENTRY
*
pScope
=
pEh3Exce
->
pScopeTable
;
ESI
=
pEh3Exce
->
TryLevel
;
if
(
ESI
== -
1
)
break
;
if
(
ESI
==
nToLevel
)
break
;
nLevel
=
pScope
[
ESI
].
ParentLevel
;
pEh3Exce
->
TryLevel
=
nLevel
;
if
(
pScope
[
ESI
].
FilterFunc
!=
NULL
)
// 有FilterFunc则是exception handler
continue
;
EAX
=
pScope
[
ESI
].
HandlerFunc
;
// 无FilterFunc则是termination handler
_NLG_Notify
(
0x101
);
// 无返回值
CALL
(
EAX
);
// 这就是展开所求得的Handler,可以是termination handler,exception Handler
}
FS
[
0
]=(
DWORD
)
eh3Unwind
.
pPrev
;
}
// _except_handler3执行两次原因:
// 举例:EH3_List如 FS[0]->E1->E2->E3->E4,在E4中发生异常,依次搜索E1,E2,E3,最后在E4中找到能处理此异常的 filter handler,这样E4,E3,E2,E1的_except_handler3均执行了一次,这是第一次.
// 第二次:在执行E4的exception handler前要先调用全局展开(_global_unwind2)以运行E1,E2,E3的termination handler
// 在全局展开(_global_unwind2)中将为EXCEPTION_RECORD.ExceptionFlags增加标志_EH_UNWINDING(2),再依次调用它们的
// _except_handler3,这就是E1,E2,E3的_except_handler3的第二次调用,但E4只有一次.
int
_except_handler3
(
EXCEPTION_RECORD
*
pExceRec
,
_EH3_EXCEPTION_REGISTRATION
*
pEh3Exce
,
CONTEXT
*
pContextRecord
/*,void * DispatcherContext*/
)
{
if
((
pExceRec
->
ExceptionFlags
&
6
) ==
0
)
{
// 无_EH_UNWINDING and _EH_EXIT_UNWIND
EXCEPTION_POINTERS ePointer
;
ePointer
.
ExceptionRecord
=
pExceRec
;
ePointer
.
ContextRecord
=
pContextRecord
;
// pEh3Exce=&EhRecord
// lea EAX, [EBP+var_ExcePointers]
// mov [ebx-4], EAX ;EBX=pEh3Exce
*((
DWORD
*)
pEh3Exce
-
1
)=(
DWORD
)&
ePointer
;
// 给_XcptFilter的参数pExceptPointers赋值
if
(
ValidateEH3RN
(
pEh3Exce
))
{
_SCOPETABLE_ENTRY
*
pScopeTable
=
pEh3Exce
->
pScopeTable
;
int
i
=
pEh3Exce
->
TryLevel
;
while
(
1
)
{
if
(
i
==-
1
)
return
1
;
// handler拒绝处理这个异常
EAX
=
pScopeTable
[
i
].
FilterFunc
;
if
(
EAX
!=
NULL
)
{
// 属于exception handler
EBP
=(
DWORD
)
pEh3Exce
+
0x10
;
// 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
CALL
(
EAX
);
// 没有参数,ePointer已传过去
if
(
EAX
!=
0
)
{
if
((
int
)
EAX
<
0
)
return
0
;
// 如果在filter中返回值<0,此返回后会引起代码为
// EXCEPTION_NONCONTINUABLE_EXCEPTION(0xC0000025)的异常,反复如此栈溢出
_global_unwind2
(
pEh3Exce
);
// 调用handler前,展开前级函数中的termination handler
EBP
=(
DWORD
)
pEh3Exce
+
0x10
;
// 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
_local_unwind2
(
pEh3Exce
,
i
);
// 此函数内不直接用EBP,但暗传EBP给可能执行的_NLG_Notify
// 并展开本函数中的termination handler
EAX
=
pScopeTable
[
i
].
HandlerFunc
;
_NLG_Notify
(
1
);
// 此函数内要用EAX
pEh3Exce
->
TryLevel
=
pScopeTable
[
i
].
ParentLevel
;
// 修改节点所在层次使EIP在TRY中位置表示正确
EAX
=
pScopeTable
[
i
].
HandlerFunc
;
CALL
(
EAX
);
// 进入异常处理,不再返回此函数(内部可接受异常并修改环境,继续执行)
}
}
i
=
pScopeTable
[
i
].
ParentLevel
;
}
return
0
;
}
else
{
pExceRec
->
ExceptionFlags
|=
8
;
// 置pEh3Exce无效标志,它不属于此handler
return
1
;
// handler拒绝处理这个异常,系统转调上一级
}
}
else
{
// 有_EH_UNWINDING or _EH_EXIT_UNWIND
EBP
=(
DWORD
)
pEh3Exce
+
0x10
;
// 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
_local_unwind2
(
pEh3Exce
,-
1
);
}
return
1
;
}
6.
结语
SEH代码还有很多
,
它们有许多包含在NTDLL
.
DLL中
,
比如RtlUnwind我曾经非常想翻译它
,
但至今我也还未完成
,
因此我也就没有粘出来
.
实际上从我以上粘出的初步代码
,
我们应该能看出SEH并不复杂
,
翻译DLL中的相关代码也并不困难
,
我估计我们唯一无法看见的代码就是中断处理的起始部分
,
把它想像成一个黑匣子就成了
.
附件有我的IDA注释说明
,
注释由于是一个动态过程
,
特别是起初的注释可能有错
,
我没有精力再去一一阅读更正
,
希望大家理解
,
一并奉献上
,
希望对大伙有用
.
在这里
,
我只是作了SEH源码的一部分翻译工作
,
在翻译过程中我也在网上查看了很多关于SEH方面的文章
,
在此我不再一一列出
,
一并致谢这些无私奉献的网友和同行们
!
看了此文相信再看其它SEH专著就应该容易多了
!
今天就是我们的嫦娥飞天的日子
,
谨以此文预祝她能回宫成功
!
顺便也希望此文是我下一个工作的起始点
!