10 未处理异常

当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进行第二轮异常分发

猜你喜欢

转载自blog.csdn.net/lifeshave/article/details/87622858