在上一篇文章中,我们其实只分析了终止型异常处理程序中正常的执行流程,这种情况的出现其实需要作如下假设:
- __try块中的代码执行过程中不会引发异常
- 这部分代码不会试图提前离开__try块的作用范围(如包含goto、break、continue、return等会导致执行流越出__try块部分的指令)
然而俗话说,“理想很丰满,现实太骨感”,在编程中,不得不考虑到各种各样的情况。当被保护的代码块中出现异常或尝试提前跳出时,按照终止型异常处理程序设立的初衷,都应该让__finally块中的代码得以执行。仔细分析,二者虽然都需要__finally块中的代码有机会执行,但实现原理却不尽相同:
当被保护代码块中引发异常时,OS捕获到该异常并传递到用户层,系统的SEH机制发挥作用,开始遍历FS:[0]指向的EXCEPTION_REGISTRATION结构,找到其中的异常处理函数入口并逐一执行,直到某个异常处理函数报告已处理该异常。
在此情况下,VC++的终止型异常处理程序为了保证即便在__try保护的代码块中出现异常也依然能执行__finally块中的代码,就必须利用系统的SEH机制,在进入__try块前在SEH上注册一个新节点。(这个步骤在上一篇中已经分析过了)
既然在进入__try块时向SEH链上加入了新的节点,那么在终止型异常处理程序结束后,必然也需要将之前向SEH链加入的节点摘掉。否则如果后续代码执行出现异常,系统SEH机制调用到的仍然是之前的处理函数。
在上一篇文章中,我们其实故意忽略掉了该异常处理段结束后,对SEH链相应节点的摘链操作。这部分对应的反汇编代码如下:
26: return 0; 00E52527 33 C0 xor eax,eax 27: } 00E52529 52 push edx 00E5252A 8B CD mov ecx,ebp 00E5252C 50 push eax 00E5252D 8D 15 5C 25 E5 00 lea edx,ds:[0E5255Ch] 00E52533 E8 A2 ED FF FF call @_RTC_CheckStackVars@8 (0E512DAh) 00E52538 58 pop eax 00E52539 5A pop edx 00E5253A 8B 4D F0 mov ecx,dword ptr [ebp-10h] 00E5253D 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx 00E52544 59 pop ecx 00E52545 5F pop edi 00E52546 5E pop esi 00E52547 5B pop ebx 00E52548 81 C4 F0 00 00 00 add esp,0F0h 00E5254E 3B EC cmp ebp,esp 00E52550 E8 18 EC FF FF call __RTC_CheckEsp (0E5116Dh) 00E52555 8B E5 mov esp,ebp 00E52557 5D pop ebp 00E52558 C3 ret
这段代码主要进行了以下4项工作:
- 清空eax寄存器,返回0
- 根据函数入口处在栈上设置的Cookie探针值,检查该值是否被修改(防止栈溢出攻击覆盖掉其下方的返回地址)
- 对之前加入SEH链的新节点执行摘链操作:从[ebp-0x10]处取得后继SEH节点的地址并赋给FS:[0]
- 校验堆栈平衡,确保esp的值在执行函数执行过程中保持了平衡(利用ebp作为参照)
当被保护代码块包含有试图提前跳出__try块的指令时,由于并没有异常发生,此时只能依靠编译器自己检测到这些指令,并在执行它们前让__finally块中的代码有机会被执行。
下面就来分析一下__try块中含有试图提早退出的代码。
DWORD funcTest02()
{
DWORD dwTemp = 3;
__try
{
dwTemp = 5;
cout << "before return:" << dwTemp << endl;
return dwTemp;
cout << "after return:" << dwTemp << endl;
}
__finally
{
if (AbnormalTermination())
{
cout << "__try块中执行时提前退出了" << endl;
}
else
{
cout << "执行流程自然转到了__finally块中" << endl;
}
cout << "enter finally:" << dwTemp << endl;
dwTemp = 10;
cout << "before exit finally:" << dwTemp << endl;
}
cout << "after finally:" << dwTemp << endl;
}
该函数的执行结果如下:
其反汇编代码如下:
28:
29: DWORD funcTest02()
30: {
00E525D0 55 push ebp
00E525D1 8B EC mov ebp,esp
00E525D3 6A FE push 0FFFFFFFEh
00E525D5 68 E8 AF E5 00 push 0E5AFE8h
00E525DA 68 D0 2C E5 00 push offset _except_handler4 (0E52CD0h)
00E525DF 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h]
00E525E5 50 push eax
00E525E6 81 C4 14 FF FF FF add esp,0FFFFFF14h
00E525EC 53 push ebx
00E525ED 56 push esi
00E525EE 57 push edi
00E525EF 8D BD 04 FF FF FF lea edi,[ebp-0FCh]
00E525F5 B9 39 00 00 00 mov ecx,39h
00E525FA B8 CC CC CC CC mov eax,0CCCCCCCCh
00E525FF F3 AB rep stos dword ptr es:[edi]
00E52601 A1 00 C0 E5 00 mov eax,dword ptr [__security_cookie (0E5C000h)]
00E52606 31 45 F8 xor dword ptr [ebp-8],eax
00E52609 33 C5 xor eax,ebp
00E5260B 50 push eax
00E5260C 8D 45 F0 lea eax,[ebp-10h]
00E5260F 64 A3 00 00 00 00 mov dword ptr fs:[00000000h],eax //在SEH链头注册新节点
31: DWORD dwTemp = 3;
00E52615 C7 45 E0 03 00 00 00 mov dword ptr [dwTemp],3
32: __try
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<下面将状态标志置为0了!<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
00E5261C C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<下面将提前退出标记置为true!<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
00E52623 C7 85 08 FF FF FF 01 00 00 00 mov dword ptr [ebp-0F8h],1
33: {
34: dwTemp = 5;
00E5262D C7 45 E0 05 00 00 00 mov dword ptr [dwTemp],5
35: cout << "before return:" << dwTemp << endl;