当SEH也没有异常处理函数的时候,就会发生未处理异常,一般不存在
1、最后一道防线
<1>入口程序的最后一道防线
<2>线程启动的最后一道防线
测试程序:
#include <windows.h>
#include <stdio.h>
int main()
{
int x = 1;
return 0;
}
下一个断点,然后查看调用堆栈如下图:
我们发现函数并不是从main函数开始跑,其真正起始位置是从KERNEL32开始跑
7C817054 push 0Ch
7C817056 push 7C817080h
7C81705B call 7C8024D6 //注册一个异常处理函数
7C817060 and dword ptr [ebp-4],0
7C817064 push 4
7C817066 lea eax,[ebp+8]
7C817069 push eax
7C81706A push 9
7C81706C push 0FEh
7C81706E call dword ptr ds:[7C8013B0h]
7C817074 call dword ptr [ebp+8]
7C817077 push eax
7C817078 call 7C80C0F8
我们开到程序时从这里开始跑的,这从才是一个真正的进程开始 执行的
我们跟进0x7C8024D6这里,这里就能看到我们比较熟悉的代码了
push 7C839AD8h
7C8024DB mov eax,fs:[00000000]
7C8024E1 push eax
7C8024E2 mov eax,dword ptr [esp+10h]
7C8024E6 mov dword ptr [esp+10h],ebp
7C8024EA lea ebp,[esp+10h]
7C8024EE sub esp,eax
7C8024F0 push ebx
7C8024F1 push esi
7C8024F2 push edi
7C8024F3 mov eax,dword ptr [ebp-8]
7C8024F6 mov dword ptr [ebp-18h],esp
7C8024F9 push eax
7C8024FA mov eax,dword ptr [ebp-4]
7C8024FD mov dword ptr [ebp-4],0FFFFFFFFh
7C802504 mov dword ptr [ebp-8],eax
7C802507 lea eax,[ebp-10h]
7C80250A mov fs:[00000000],eax
7C802510 ret
7C802511 mov ecx,dword ptr [ebp-10h]
7C802514 mov dword ptr fs:[0],ecx
7C80251B pop ecx
7C80251C pop edi
7C80251D pop esi
7C80251E pop ebx
7C80251F leave
7C802520 push ecx
7C802521 ret
我们用IDA打开KERNEL32模块,找到BaseProcessStart函数,如下:
_BaseProcessStart@4 proc near ; CODE XREF: BaseProcessStartThunk(x,x)+5↑j
.text:7C817054
.text:7C817054 uExitCode = dword ptr -1Ch
.text:7C817054 ms_exc = CPPEH_RECORD ptr -18h
.text:7C817054 ThreadInformation= dword ptr 8
.text:7C817054
.text:7C817054 ; FUNCTION CHUNK AT .text:7C8438EA SIZE 00000011 BYTES
.text:7C817054 ; FUNCTION CHUNK AT .text:7C843900 SIZE 00000018 BYTES
.text:7C817054
.text:7C817054 ; __unwind { // __SEH_prolog
.text:7C817054 push 0Ch
.text:7C817056 push offset stru_7C817080
.text:7C81705B call __SEH_prolog
.text:7C817060 ; __try { // __except at loc_7C843900
.text:7C817060 and [ebp+ms_exc.registration.TryLevel], 0
.text:7C817064 push 4 ; ThreadInformationLength
.text:7C817066 lea eax, [ebp+ThreadInformation]
.text:7C817069 push eax ; ThreadInformation
.text:7C81706A push 9 ; ThreadInformationClass
.text:7C81706C push 0FFFFFFFEh ; ThreadHandle
.text:7C81706E call ds:__imp__NtSetInformationThread@16 ; NtSetInformationThread(x,x,x,x)
.text:7C817074 call [ebp+ThreadInformation]
.text:7C817077 push eax ; dwExitCode
.text:7C817078
.text:7C817078 loc_7C817078: ; CODE XREF: BaseProcessStart(x)+2C8B9↓j
.text:7C817078 call _ExitThread@4 ; ExitThread(x)
.text:7C817078 ; } // starts at 7C817060
.text:7C817078 ; } // starts at 7C817054
.text:7C817078 _BaseProcessStart@4 endp
对比发现,我们刚才的那个程序就是这个成序,说明进程从这里开始跑的;
__SEH_prolog就是在当前线程中注册一个异常处理程序。所以在我们开始时编译器已经给注册了一个异常处理程序,哪怕自己一个try没写,也基本不可能出现没有异常处理程序的情况,这个就是所谓的最后一道防线。那如果新启一个线程,是否还有最后一道防线。
代码如下:
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
int x = 1;
return 0;
}
int main()
{
CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
getchar();
return 0;
}
同样我们在int x = 1;处下一个断点,然后运行查看堆栈窗口,如下:
发现其也不是从我们的线程处理函数开始执行的
跟进去代码片段:
7C80B6F2 push 10h
7C80B6F4 push 7C80B730h
7C80B6F9 call 7C8024D6
7C80B6FE and dword ptr [ebp-4],0
7C80B702 mov eax,fs:[00000018]
7C80B708 mov dword ptr [ebp-20h],eax
7C80B70B cmp dword ptr [eax+10h],1E00h
7C80B712 jne 7C80B723
7C80B714 cmp byte ptr ds:[7C885008h],0
7C80B71B jne 7C80B723
7C80B71D call dword ptr ds:[7C8012F8h]
7C80B723 push dword ptr [ebp+0Ch]
7C80B726 call dword ptr [ebp+8]
7C80B729 push eax
7C80B72A call 7C80C0F8
跟进第一个call发现也给加了异常处理函数。
在IDA中查看线程真正开始的函数
.text:7C80B6F2 _BaseThreadStart@8 proc near ; CODE XREF: BaseThreadStartThunk(x,x)+6↓j
.text:7C80B6F2 ; BaseFiberStart()+12↓p
.text:7C80B6F2
.text:7C80B6F2 var_20 = dword ptr -20h
.text:7C80B6F2 uExitCode = dword ptr -1Ch
.text:7C80B6F2 ms_exc = CPPEH_RECORD ptr -18h
.text:7C80B6F2 arg_0 = dword ptr 8
.text:7C80B6F2 arg_4 = dword ptr 0Ch
.text:7C80B6F2
.text:7C80B6F2 ; FUNCTION CHUNK AT .text:7C83AB40 SIZE 00000011 BYTES
.text:7C80B6F2 ; FUNCTION CHUNK AT .text:7C83AB56 SIZE 00000018 BYTES
.text:7C80B6F2
.text:7C80B6F2 ; __unwind { // __SEH_prolog
.text:7C80B6F2 push 10h
.text:7C80B6F4 push offset stru_7C80B730
.text:7C80B6F9 call __SEH_prolog
.text:7C80B6FE ; __try { // __except at loc_7C83AB56
.text:7C80B6FE and [ebp+ms_exc.registration.TryLevel], 0
.text:7C80B702 mov eax, large fs:18h
.text:7C80B708 mov [ebp+var_20], eax
.text:7C80B70B cmp dword ptr [eax+10h], 1E00h
.text:7C80B712 jnz short loc_7C80B723
.text:7C80B714 cmp _BaseRunningInServerProcess, 0
.text:7C80B71B jnz short loc_7C80B723
.text:7C80B71D call ds:__imp__CsrNewThread@0 ; CsrNewThread()
.text:7C80B723
.text:7C80B723 loc_7C80B723: ; CODE XREF: BaseThreadStart(x,x)+20↑j
.text:7C80B723 ; BaseThreadStart(x,x)+29↑j
.text:7C80B723 push [ebp+arg_4]
.text:7C80B726 call [ebp+arg_0]
.text:7C80B729 push eax ; dwExitCode
.text:7C80B72A
.text:7C80B72A loc_7C80B72A: ; CODE XREF: BaseThreadStart(x,x)+2F471↓j
.text:7C80B72A call _ExitThread@4 ; ExitThread(x)
.text:7C80B72A ; } // starts at 7C80B6FE
.text:7C80B72A ; } // starts at 7C80B6F2
.text:7C80B72A _BaseThreadStart@8 endp
2、伪码
_try{
}_execpt(UnhandledExceptionFilter(GetExceptionInformation())){
//终止线程
//终止进程
}
3、只有程序被调试时,才会有未处理异常
UnhandledExceptionFilter的执行流程
<1>通过NtQueryInformationProcess查询当前进程是否被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发
<2>如果没有被调试
查询是否通过SetUnhandledExceptionFilter注册处理函数,如果有就调用
如果没有通过SetUnhandledExceptionFilter注册处理函数,弹出窗口,让用户选择终止调试,还是启动及时调试器
如果用户没有启动及时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLER
测试代码:
#include <windows.h>
#include <stdio.h>
long _stdcall Callback(_EXCEPTION_POINTERS *excp)
{
excp->ContextRecord->Ecx = 1;
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
SetUnhandledExceptionFilter(Callback);
_asm{
xor edx,edx
xor ecx,ecx
mov eax,0x10
idiv ecx //edx:eax除ecx
}
printf("程序正常执行\n");
getchar();
return 0;
}
当直接运行上面代码生成的.exe程序正常执行,而如果用OD则直接退出
4、KiUserExceptionDispatcher函数分析
<1>调用RtlDispatchException查找并执行异常处理函数
<2>如果RtlDispatchException返回真,调用ZeContinue再次进入0环,但线程再次返回3环时会从修正后的位置执行
<3>如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发