继续填坑,本系列的上一篇文章《Windows异常世界历险记(三)——VC6中结构化异常处理机制的反汇编分析(上)》是差不多1年前写的,由于时间间隔相对较长,因此在正式开始前,先简要回顾一下WinMain和RaiseExcept函数中的主要流程。
接着上一篇的那个SEH测试程序说,还是从最开始的一段入手分析:
45: __try
00401255 mov dword ptr [ebp-4],0
46: {
47: __try
0040125C mov dword ptr [ebp-4],1
48: {
49: RaiseExcept();
00401263 call @ILT+5(_RaiseExcept) (0040100a)
00401268 mov dword ptr [ebp-4],0
0040126F call $L74596 (00401276)
00401274 jmp $L74599 (00401294)
由于一来就是两个try块,因此ebp-4这个局部变量处存放的是tryLevel,进入外层的try后该标记被置为0,进入内层try块后该标记被设置为1。接着执行RaiseExcept函数,由于执行完RaiseExcept函数后,内层try块的作用域就结束了,因此紧接着将tryLevel又设置为0了(表明内层try执行结束)。
下面call到外层finally函数中执行(在之前的分析中,我们已经了解了正常情况下finally块的调用是以call和ret形式完成的,因此完全可将其称为finally函数)。finally函数执行完ret后,流程返回到00401274处,jmp到00401294,此时即将出外层try块,因此将tryLevel设置为-1,然后跳到外层try块外即WinMain尾部。
理解了WinMain中的代码再看这一段会发现其实很相似,因此此处关于tryLevel的设置不再赘述。直接看函数入口处:
24: void RaiseExcept()
25: {
00401100 push ebp
00401101 mov ebp,esp
00401103 push 0FFh
00401105 push offset string "In Inner::finally"+18h (004220c8)
0040110A push offset __except_handler3 (00401460)
0040110F mov eax,fs:[00000000]
00401115 push eax
00401116 mov dword ptr fs:[0],esp
0040111D add esp,0FFFFFFB4h
00401120 push ebx
00401121 push esi
00401122 push edi
00401123 mov dword ptr [ebp-18h],esp
据此可以画出栈上的情况:
地址 | 内容 | 备注 |
---|---|---|
ebp-18 | 当前esp | 指向保存有“易挥发”寄存器组值的栈顶 |
ebp-14 | 异常信息结构指针 | 分析except_handler3可知 |
ebp-10 | 原fs:[00000000] | fs:[0] |
ebp-C | __except_handler3 | 和上面一起构成标准的SEH链结构 |
ebp-8 | 004220c8 | scopetable指针 |
ebp-4 | 0xFF | tryLevel |
ebp | 原ebp | ebp也被异常处理巧妙地使用了 |
ebp+4 | 返回地址 |
接下来由于两次进入try块分别设置了tryLevel为0和1,然后就到了我们故意制造Access Violation的现场了。按照之前文章《Windows异常世界历险记(一)——Windows系统用户级结构化异常处理机制(SEH)的基础知识和Unwind展开操作》的分析,异常触发后,当用户层的SEH机制开始处理该异常时,首先找到的异常处理函数就应该是__except_handler3。下面的重点就来分析这个函数的行为。
这部分原计划是把调试和逆向分析过程逐步展现出来,但考虑到这样做以后文章篇幅会大幅度增加,并且整个分析过程其实也没什么特别之处,反倒有些枯燥,因此这里直接给出我逆向后得到的C语言伪码。
需要说明的是,下面大部分的结构和变量名都是我在分析中自己取的,个人认为except_handler3函数本就是用汇编写的。这里给出C伪码仅仅是为了方便理解其工作原理,对这部分感兴趣的朋友建议还是应该自己逆下_except_handler3
#include
typedef struct VC6_EXCEPTION_INFORMATION
{
PEXCEPTION_RECORD pExceptRecord;
PCONTEXT pContext;
}VC6_EXCEPTION_INFORMATION, *PVC6_EXCEPTION_INFORMATION;
typedef struct VC6_SCOPETABLE
{
DWORD dwOuterLevel;
PVOID pfnFilter;
PVOID pfnHandler;
}VC6_SCOPETABLE, *PVC6_SCOPETABLE;
typedef struct VC6_EXCEPTION_REGISTRATION_RECORD
{
DWORD dwCurrentESP; // 保存的ESP值
PVC6_EXCEPTION_INFORMATION pExceptInfo; // 异常信息结构指针
EXCEPTION_REGISTRATION_RECORD pSEHNode; // 经典的异常链节点
PVC6_SCOPETABLE pScopeTable; // VC的惯用伎俩——表结构
DWORD dwTryLevel; // 当前try的层级
DWORD dwEBP; // 这个其实不是本结构的成员,只是本结构在栈上挨着函数入口处压入的EBP而已
}VC6_EXCEPTION_REGISTRATION_RECORD, *PVC6_EXCEPTION_REGISTRATION_RECORD;
void global_unwind2(const PEXCEPTION_REGISTRATION_RECORD pSEHNode)
{
RtlUnwind(pSEHNode, NULL, NULL, NULL);
}
void NLG_Notify(int i)
{
// 这个函数看起来就是记录了下传入·的参数、eax和ebp的值到一个指定位置,暂时不清楚用途
}
// 局部展开(注意与原生SEH的局部展开概念加以区分),以函数为单位,当前的tryLevel为起点,
// 传入的nFinishTryLevel为终点(或达到本函数内最外层的try之外),由内向外执行所有的try-finally中的finally函数
PEXCEPTION_REGISTRATION_RECORD __cdecl local_unwind2(PEXCEPTION_REGISTRATION_RECORD pSEHNode, int nFinishTryLevel)
{
PVC6_EXCEPTION_REGISTRATION_RECORD pVC6SEHNode = CONTAINING_RECORD(pSEHNode, VC6_EXCEPTION_REGISTRATION_RECORD, pSEHNode);
PVC6_SCOPETABLE pScopeTable;
int tryLevel;
while (TRUE)
{
pScopeTable = pVC6SEHNode->pScopeTable;
tryLevel = pVC6SEHNode->dwTryLevel;
if (tryLevel == -1 || tryLevel == nFinishTryLevel)
{
break;
}
pVC6SEHNode->dwTryLevel = pScopeTable[tryLevel].dwOuterLevel;
if (!pScopeTable[tryLevel].pfnFilter) // finally型pfnFilter为NULL
{
NLG_Notify(257);
pScopeTable[tryLevel].pfnHandler(); // 执行finally函数
}
}
return pSEHNode;
}
EXCEPTION_DISPOSITION __cdecl _except_handler3(
PEXCEPTION_RECORD pExceptionRecord,
PEXCEPTION_REGISTRATION_RECORD pSEHNode,
PCONTEXT pContext,
void *pDispatchContext)
{
PVC6_EXCEPTION_REGISTRATION_RECORD pVC6SEHNode;
int tryLevel;
PVC6_SCOPETABLE pScopeTable;
EXCEPTION_DISPOSITION result;
VC6_EXCEPTION_INFORMATION stVC6ExceptInfo;
// #define CONTAINING_RECORD(address, type, field) ((type *)((PCHAR)(address) - (ULONG_PTR)(&((type *)0)->field)))
pVC6SEHNode = CONTAINING_RECORD(pSEHNode, VC6_EXCEPTION_REGISTRATION_RECORD, pSEHNode);
// 若为退出展开,则调用local_unwind2执行局部展开
if (pExceptionRecord->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
{
local_unwind2(pSEHNode, -1);
result = ExceptionContinueSearch;
}
else
{
// 构建异常信息结构,并写入栈上对应字段中
stVC6ExceptInfo.pExceptRecord = pExceptionRecord;
stVC6ExceptInfo.pContext = pContext;
pVC6SEHNode->pExceptInfo = &stVC6ExceptInfo;
tryLevel = pVC6SEHNode->dwTryLevel;
pScopeTable = pVC6SEHNode->pScopeTable;
while (tryLevel != -1)
{
// 若为try-except型
if (pScopeTable[tryLevel].pfnFilter)
{
// 执行过滤函数,并根据其返回值进行判定
int nRet = pScopeTable[tryLevel].pfnFilter();
if (nRet != 0)
{
//#define EXCEPTION_EXECUTE_HANDLER 1
//#define EXCEPTION_CONTINUE_SEARCH 0
//#define EXCEPTION_CONTINUE_EXECUTION -1
if (nRet < 0)
{
return ExceptionContinueExecution;
}
// 执行全局展开
global_unwind2(pSEHNode);
DWORD dwOrgEBP = pVC6SEHNode->dwEBP;
// 栈基址指针切换
_asm
{
mov ebp, dwOrgEBP
}
// 执行局部展开
local_unwind2(pSEHNode, tryLevel);
NLG_Notify(1);
pVC6SEHNode->dwTryLevel = pScopeTable[tryLevel].dwOuterLevel;
// 调用异常处理函数
pScopeTable[tryLevel].pfnHandler();
}
}
tryLevel = pScopeTable[tryLevel].dwOuterLevel;
}
result = ExceptionContinueSearch;
}
return result;
}
为了方便分析和阅读,已在关键之处加入注释。在下一节中,将结合例子的执行流程对该函数执行流程进行分析。