大家好,我是科锐逆向49期学员,此系列文章旨在探讨在VS2019环境下,代码还原中遇到x86的异常和x64的异常时如何准确还原try-catch的嵌套关系,如何定位核心的catch代码块,准确还原try的包含范围,以及获取thorw参数的值及其类型。如果表述或者内容有误恳请各位前辈斧正。
编译器: Microsoft Visual Studio Community 2019 16.11.34
系统:Microsoft Windows 11 专业工作站版 10.0.22631 版本 22631
分析工具: IDA 7.5
此系列文章我们将从如下5个方面入手讨论有关异常还原的奥妙。
我们分为3个章节,本文为第一章,章节目录如下
首先对于异常还原我们需要解决的问题有如下几点
当我们能够准确做到以上四点之后就能够准确还原源程序中的try-catch结构,在微软的异常体系架构中并未使用C++标准的异常体系,反而发展出了自己的异常体系微软的异常架构按照还原难度从易到难如下排列
本篇文章将会介绍x86中的三代异常还原相关的还原技巧,首先我们从32位程序的3代异常说起。
首先要识别x86的3代异常是比较简单的,在拥有异常处理的函数当中其开头一般都会有如下两点行为,请谨记这两点识别技巧
形如下图

在我们点击进入到SEH回调函数指针之后会见到__CxxFrameHandler3
这个调用,一般来讲IDA都会将其进行标记,此标志就代表着3代异常处理框架。如下图所示

此调用主要依托于RTTI机制进行异常的派发。
接下来我们先回到throw
的识别,对于throw来讲我们一般是通过_CxxThrowException
来抛出异常的,一般我们在IDA中看到有形如_CxxThrowException
时就可以判定为抛出点。形如下图所示

当我们找到此调用的时候还原throw参数的值及其类型就比较简单了,此调用传入两个参数
想要确定值就查看跟踪pExceptionObject
的赋值流程。想要确定抛出参数类型就分析_ThrowInfo
结构体。接下来我们对_ThrowInfo
结构体进行解析,在微软的头文件eh.h里面有包含异常管理的结构体定义。值得注意的是VC6和VS2019的结构体是具有差异的。此_ThrowInfo
结构体的定义如下
字段解释
CatchableTypeArray
定义
字段解析
nCatchableTypes
类型
arrayOfCatchableTypes
CatchableType*
结构体
CatchableType*
结构体定义如下
我们给出TypeDescriptor
定义
至此我们可以知道抛出异常的类型了,我们再用图像梳理一遍参数的指向流程。

这就是throw的识别方式,根据传参分析到thorw的类型然后跟踪第一个参数pExceptionObject
拿到参数的值。接下来我们回到catch的还原方法。
对于catch的还原我们需要对传入的SEH回调函数指针进行分析,此函数的调用会传入一个参数,此参数IDA在DEBUG版是不会解析的,我们需要搞清楚这个表的结构,此结构体叫FuncInfo
此结构体定义如下
重要字段解析
其中固定第一个magicNumber
为19930522是固定值据传是发明这个结构体的日期。TryBlockMapEntry
结构体记录了Try的各种信息定义如下
重要参数解析
HandlerType
结构体解析,此结构体主要用来描述Catch的信息。
重要参数解析
我们至此可以通过分析FuncInfo来获取到catch代码块的位置,我贴出图来辅助大家理解此结构

接下来我们需要确定try的范围,在Debug版中可以通过确定try等级下标来确定进入的是第几个try,但是在Release版中等级下标的语句是有可能被编译器优化掉的,我们先给出Debug版的图例稍后在编译器优化后的异常代码结构还原技巧此章节讨论Release版本的判断技巧。

接下来我们需要看的就是,try的嵌套范围。Try的嵌套不能简单通过stat_tryLevel
看出,在Release版中因为没有退出赋值-1
的情况所以就无法看出,所以通过tryLow
tryHigh
catchHigh
这三个字段来描述是否有嵌套。

以如上的TryBlockMap
举例很明显能看出包含关系,其中下面的参数为0,2,3
也就是说从0到2都会进入,然后上面那个只是1,1,3
也就是说下面的try
包含上面的try
,这样就可以明确的还原出嵌套关系。
我们给出复杂点的示例用于判断练习

解析:从上到下我们将其标号为0~5,其嵌套关系如下表示
如果在Catch中再写try只是单独在Catch块中具有try-catch
结构不会嵌入当前层次的分析。
对于异常处理的还原,在还原代码结构的时候是十分重要的。我们需要准确地还原出异常结构,并且识别出编译器对其的优化处理,这样才能更好地还原软件的行为,做到二进制上的相同,同时使得还原后的代码更具有健壮性,维护性,最后谢谢大家观看。
PS:本来我是想将此系列文章作为一篇文章发出,由于论坛编辑器过于卡顿所以我分为了三个文章一个系列,如果我的内容有误恳请各位前辈在评论区斧正。
CxxThrowException(pExceptionObj, __ThrowInfo)
CxxThrowException(pExceptionObj, __ThrowInfo)
typedef const struct _s_ThrowInfo {
unsigned
int
attributes;
(Bit field)
PMFN
pmfnUnwind;
when exception has been handled
or
aborted
int
(__cdecl
*
pForwardCompat)(...);
frame handler
CatchableTypeArray
*
pCatchableTypeArray;
pointers to types
} ThrowInfo;
typedef const struct _s_ThrowInfo {
unsigned
int
attributes;
(Bit field)
PMFN
pmfnUnwind;
when exception has been handled
or
aborted
int
(__cdecl
*
pForwardCompat)(...);
frame handler
CatchableTypeArray
*
pCatchableTypeArray;
pointers to types
} ThrowInfo;
typedef const struct _s_CatchableTypeArray {
int
nCatchableTypes;
CatchableType
*
arrayOfCatchableTypes[];
} CatchableTypeArray;
typedef const struct _s_CatchableTypeArray {
int
nCatchableTypes;
CatchableType
*
arrayOfCatchableTypes[];
} CatchableTypeArray;
typedef const struct _s_CatchableType {
unsigned
int
properties;
/
/
Catchable
Type
properties (Bit field)
int
pType;
/
/
Image relative offset of TypeDescriptor
TypeDescriptor
*
pType;
/
/
这里记录类型
PMD thisDisplacement;
/
/
Pointer to instance of catch
type
within thrown
object
.
int
sizeOrOffset;
/
/
Size of simple
-
type
object
or
offset into
/
/
buffer
of
'this'
pointer
for
catch
object
PMFN copyFunction;
/
/
这里会记录拷贝构造函数的地址
} CatchableType;
typedef const struct _s_CatchableType {
unsigned
int
properties;
/
/
Catchable
Type
properties (Bit field)
int
pType;
/
/
Image relative offset of TypeDescriptor
TypeDescriptor
*
pType;
/
/
这里记录类型
PMD thisDisplacement;
/
/
Pointer to instance of catch
type
within thrown
object
.
int
sizeOrOffset;
/
/
Size of simple
-
type
object
or
offset into
/
/
buffer
of
'this'
pointer
for
catch
object
PMFN copyFunction;
/
/
这里会记录拷贝构造函数的地址
} CatchableType;
typedef struct TypeDescriptor
{
const void
*
pVFTable;
/
/
Field overloaded by RTTI
unsigned
long
hash
;
/
/
Hash
value computed
from
type
's decorated name
void
*
spare;
/
/
reserved, possible
for
RTTI
char name[];
/
/
这里的name就是名称在IDA中会标注出来
} TypeDescriptor;
typedef struct TypeDescriptor
{
const void
*
pVFTable;
/
/
Field overloaded by RTTI
unsigned
long
hash
;
/
/
Hash
value computed
from
type
's decorated name
void
*
spare;
/
/
reserved, possible
for
RTTI
char name[];
/
/
这里的name就是名称在IDA中会标注出来
} TypeDescriptor;
typedef const struct _s_FuncInfo
{
unsigned
int
magicNumber:
29
;
/
/
Identifies version of compiler
unsigned
int
bbtFlags:
3
;
/
/
flags that may be
set
by BBT processing
__ehstate_t maxState;
/
/
Highest state number plus one (thus
/
/
number of entries
in
unwind
map
)
UnwindMapEntry
*
pUnwindMap;
/
/
Where the unwind
map
is
unsigned
int
nTryBlocks;
/
/
Number of
'try'
blocks
in
this function
*
*
*
*
*
TryBlockMapEntry
*
pTryBlockMap;
/
/
Where the handler
map
is
*
*
*
*
*
unsigned
int
nIPMapEntries;
/
/
void
*
pIPtoStateMap;
/
/
An IP to state
map
. NYI (reserved).
ESTypeList
*
pESTypeList;
/
/
List
of types
for
exception specifications
int
EHFlags;
/
/
Flags
for
some features.
} FuncInfo;
typedef const struct _s_FuncInfo
{
unsigned
int
magicNumber:
29
;
/
/
Identifies version of compiler
unsigned
int
bbtFlags:
3
;
/
/
flags that may be
set
by BBT processing
__ehstate_t maxState;
/
/
Highest state number plus one (thus
/
/
number of entries
in
unwind
map
)
UnwindMapEntry
*
pUnwindMap;
/
/
Where the unwind
map
is
unsigned
int
nTryBlocks;
/
/
Number of
'try'
blocks
in
this function
*
*
*
*
*
TryBlockMapEntry
*
pTryBlockMap;
/
/
Where the handler
map
is
*
*
*
*
*
unsigned
int
nIPMapEntries;
/
/
void
*
pIPtoStateMap;
/
/
An IP to state
map
. NYI (reserved).
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2025-3-1 21:59
被TeddyBe4r编辑
,原因: 优化文章结构