SEH 进阶(2)

上一篇我们自己手动了解了编译器SHE 的机构,下面我们来学习一下其结构体定义以及如何借用这样的结构构建一个完整的SHE 结构。

首先我们来看加入系统的SHE 链表的结构:

typedef struct _EXTEND_EXCEPTION_RECORD_
{
    //Stdandard ESP in frame;                      
    //PEXCEPTION_POINTERSlpPreExceptionPointers;//GetExceptionPointers 函数返回的就是这里的地址
    DWORD               PreExCeptionRegistration;
    DWORD               HandlerFunction;
    PSCOPETABLE         scopetablePtr;
    DWORD               TravelLevel;
    DWORD               _ebp;// 并不使用这个成员,而是使用的这个成员的地址。
}EXTEND_EXCEPTION_RECORD,*PEXTEND_EXCEPTION_RECORD;


对比刚开始介绍的异常链表中的异常结构,后面增加了四个结构,其中scopetable 代表那个不定长数据机构,其表示不同代码块与对应的she 结构的对应和嵌套关系。Trylevel 代表了当前所在的代码块对应在scopetable 中的位置。_ebp 代表的当前函数刚进来的时候的EBP(PUSH EBP MOV EBP,ESP 的这个新的EBP)

 

 

对应的宏定义

#define TRYLEVEL_NONE		-1
#define TRYLEVEL_INVALID    -2

typedef struct _SCOPETABLE_ENTRY_
{
	DWORD	previousTryLevel;
	DWORD	lpfnFilter;
	DWORD	lpfnHandler;
}SCOPETABLE_ENTRY,*PSCOPETABLE_ENTRY;
typedef struct _SCOPETABLE_
{
	DWORD			dwUnknown[4];// 这16 个字节用于安全检查工作
	SCOPETABLE_ENTRY	lpScopetable[1];
}SCOPETABLE,*PSCOPETABLE;


另外,我们看到前面汇编代码中,该结构的指针与__security_cookie进行了异或加密。后面的使用中需要再次xor 才能取出真正的内容

 

后面需要用到的宏

#define EXCEPTION_NONCONTINUABLE 0x1    // 错误特别严重,不能继续了
#define EXCEPTION_UNWINDING 0x2         // 正在执行展开操作
#define EXCEPTION_EXIT_UNWIND 0x4       // 正在退出展开操作
#define EXCEPTION_STACK_INVALID 0x8     // 栈溢出或者没有对齐
#define EXCEPTION_NESTED_CALL 0x10      // Nested exception handler call
#define EXCEPTION_TARGET_UNWIND 0x20    // Target unwind in progress
#define EXCEPTION_COLLIDED_UNWIND 0x40  // Collided exception handler call

#define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \
                                                         EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)


// scopetable_entry::lpfnFilter 的返回值,也就是 __except 过滤块的返回值

EXCEPTION_EXECUTE_HANDLER

1

__except 块中包含的代码将被执行,跳到__except块后的代码继续执行

EXCEPTION_CONTINUE_SEARCH

0

程序中遇到异常后,__except中的代码不会被执行,而是寻找外层__try所对应的__except

EXCEPTION_CONTINUE_EXECUTION

-1

__except 块中包含的代码将被执行,跳到异常发生处的代码,重新执行该代码

 

__except_handler4函数

异或加密的原理:

(A ^ B) ^ C = A ^ (B ^ C);

(A ^ B) ^ B = A ^ (B ^ B) = A

函数内部多次进行安全检查。

 

逆向等到如下函数,如果有不对的地方,请各位指正。

这里我们把验证的行为封装成一个函数,简化代码的大小。

我们来回顾一下前面的一个堆栈的状态


再看通常的结构体__ScopeTable的前四个字节的内容



再看如下函数,首先介绍这个安全检查,这些操作由于对于整个流程没有影响,为了代码便于阅读,将其放到一个函数中。

void check(PSCOPETABLE			lpScopeTableRecord,PEXTEND_EXCEPTION_RECORD EstablisherFrame)
{
	if(lpScopeTableRecord->dwUnknown[0] != -2) {
		__security_check_cookie((lpScopeTableRecord->dwUnknown[1]+&EstablisherFrame->_ebp) ^ (*(PDWORD)(lpScopeTableRecord->dwUnknown[0]+&EstablisherFrame->_ebp)));
	}
	__security_check_cookie((lpScopeTableRecord->dwUnknown[3]+&EstablisherFrame->_ebp) ^ (*(PDWORD)(lpScopeTableRecord->dwUnknown[2]+&EstablisherFrame->_ebp)));
}


上述第二个异或实际 ebp xor ( ebp xor___security_cookie) 即 __security_cookie。通过栈的结构我们可以看出来,自己算一下就知道了。


my_except_handler4

下述代码以及其它的代码均为自己逆向得到,如有不对,请指正。
EXCEPTION_DISPOSITION	my_except_handler4( 
	IN struct _EXCEPTION_RECORD *ExceptionRecord, 
	IN PEXTEND_EXCEPTION_RECORD EstablisherFrame, 
	IN OUT struct _CONTEXT *ContextRecord, 
	IN OUT PVOID DispatcherContext )
{
	EXCEPTION_POINTERS	LocalExceptionPointers = {0};
	PSCOPETABLE			lpScopeTableRecord = 0;
	DWORD				dwDisposition = 0;
	BYTE				bCallFilter = 0;
	int					iFilterRet = -1;
	EXCEPTION_DISPOSITION ret_exceptiondiposition = ExceptionContinueSearch;
	// 上一篇我们讲过,刚开始该结构地址放入的时候是通过异或放入的,现在重新异或解密,以得到原来的该结构的地址
	lpScopeTableRecord = (PSCOPETABLE)((DWORD)(EstablisherFrame->scopetablePtr) ^ (*(PDWORD)(__security_cookie)));
	// 安全检查
	check(lpScopeTableRecord,EstablisherFrame);
	
	if(ExceptionRecord->ExceptionFlags & EXCEPTION_UNWIND)	{
		// 如果是展开操作
		if(EstablisherFrame->TravelLevel == TRYLEVEL_INVALID) {
			// 当前没有在try 中
			return ret_exceptiondiposition;
		} else {
			// 进行局部展开
			_EH4_LocalUnwind((DWORD)EstablisherFrame,EstablisherFrame->TravelLevel,&EstablisherFrame->_ebp,__security_cookie);
		}
	} else {
		// 不是展开操作

		// 下面三句是填充我们在上一篇中为我们预留的栈空间的内容的操作,也就是通常我们GetExceptionPointers 得到的地址
		*(PDWORD)((DWORD)EstablisherFrame - 4) = (DWORD)&LocalExceptionPointers;
		LocalExceptionPointers.ContextRecord = ContextRecord;
		LocalExceptionPointers.ExceptionRecord = ExceptionRecord;


		DWORD	lpTravel = EstablisherFrame->TravelLevel;
		if(lpTravel != TRYLEVEL_INVALID) {
			do 
			{
				DWORD	lpFilterFunction = lpScopeTableRecord->lpScopetable[lpTravel].lpfnFilter;
				if(lpFilterFunction == 0) {
					lpTravel = lpScopeTableRecord->lpScopetable[lpTravel].previousTryLevel;
					if(lpTravel != TRYLEVEL_INVALID) {
						// 仅仅是当前没有Filter函数
						continue;
					} else {
						// 遍历结束
						if(bCallFilter == 0) {
							return ret_exceptiondiposition;
						} else {
							goto LABLE;
						}
					}
				} else {
					// 调用Filter函数
					bCallFilter = 1;
					iFilterRet = _EH4_CallFilterFunc(lpFilterFunction,&EstablisherFrame->_ebp);
					if(iFilterRet < 0) {	//EXCEPTION_CONTINUE_EXECUTION
						// 函数继续从原来的位置执行就可以了

						ret_exceptiondiposition = ExceptionContinueExecution;
						goto LABLE;
					} else if (iFilterRet > 0) {	// EXCEPTION_EXECUTE_HANDLER

						// 函数应该执行我们的处理函数,然后沿着处理函数向下运行,因此需要进行全局展开
						// 清理那些由于直接跳出的程序申请的资源
						_EH4_GlobalUnwind(EstablisherFrame);
						if(EstablisherFrame->TravelLevel != lpTravel) {
							// 清理本函数内部的由于程序运行位置的直接改变而可能泄露的资源
							_EH4_LocalUnwind((DWORD)EstablisherFrame,lpTravel,&EstablisherFrame->_ebp,__security_cookie);
						}
						EstablisherFrame->TravelLevel = lpTravel;
						check(lpScopeTableRecord,EstablisherFrame);
						// 前面已经进行过清理工作了,现在可以放心大胆的跳转到 lpfnHandler 处运行
						// 好吧,其实也并不是太放心
						_EH4_TransferToHandler(lpScopeTableRecord->lpScopetable[lpTravel].lpfnHandler,&EstablisherFrame->_ebp);// 跳转到去执行handler 函数,该函数不会返回
					}
					// 否则继续循环遍历ScopeTable结构
				}
				
				lpTravel = lpScopeTableRecord->lpScopetable[lpTravel].previousTryLevel;
			} while (lpTravel != TRYLEVEL_INVALID);
		} else {
			return ret_exceptiondiposition;
		}
	}
LABLE:
	check(lpScopeTableRecord,EstablisherFrame);
	return ret_exceptiondiposition;
}

代码中的注释非常详细了。函数两个重要分支,异常和展开。

如果展开的话,直接调用局部展开函数就好了。

如果是异常的话:

在while 循环里面遍历SCOPETABLE,依次调用其SCOPETABLE_ENTRY::lpfnFilter,针对不同的返回值做不同的处理。

1.      EXCEPTION_CONTINUE_EXECUTION       异常被修复,返回ExceptionContinueExecution;

2.      EXCEPTION_CONTINUE_SEARCH              继续遍历下一个SCOPETABLE_ENTRY

3.      EXCEPTION_EXECUTE_HANDLER              当前SCOPETABLE_ENTRY::lpfnHandler负责处理它,于是调用它,然后程序继续向下执行,不返回。

其中负责调用lpfnFilter lpfnHandler 函数的汇编如下:


; __fastcall _EH4_CallFilterFunc(x, x)
@_EH4_CallFilterFunc@8 proc near
push    ebp
push    esi
push    edi
push    ebx
mov     ebp,edx
xor     eax,eax
xor     ebx,ebx
xor     edx,edx
xor     esi,esi
xor     edi,edi
call    ecx
pop     ebx
pop     edi
pop     esi
pop     ebp
retn
@_EH4_CallFilterFunc@8 endp
 
 
; __fastcall _EH4_TransferToHandler(x, x)
@_EH4_TransferToHandler@8 proc near
mov    ebp, edx
mov    esi, ecx
mov    eax, ecx
push   1
call   __NLG_Notify
xor    eax, eax
xor    ebx, ebx
xor    ecx, ecx
xor    edx, edx
xor    edi, edi
jmp    esi                       ; 跳走就不回来了
@_EH4_TransferToHandler@8 endp


了解上述的三个函数之后,我们应该了解什么是展开(unwind),这也是解决上一篇文章的疑问的方法。

所谓的展开,就是清理函数内部使用的资源的过程。从上面的介绍我们可以看到,如果lpfnFilter函数返回EXCEPTION_EXECUTE_HANDLER            当前SCOPETABLE_ENTRY::lpfnHandler负责处理它,于是调用它,然后程序继续向下执行,不返回。此时函数的执行直接跳转到了lpfnHandler代码块。如果异常是由try块中调用的没有异常处理函数的子函数或嵌套更深的没有设置异常处理函数的子函数产生的,那么此时函数的执行流程直接从该子函数跳转到lpfnHandler代码块,此时如果不做处理,可能导致局部类的析构函数未执行,申请的内存未被释放等等资源泄露事件。因此需要展开操作,给函数一个清理函数内部资源的机会

 

展开分为两种,“全局展开”和“局部展开”。“局部展开”就是对异常链表中的某个项进行展开,“全局展开”就是一系列的“局部展开”,以清理当前位置包含的内部代码中的所有的嵌套的项。

全局展开

; Attributes: bp-based frame

; __fastcall _EH4_GlobalUnwind(x)
@_EH4_GlobalUnwind@4 proc near
push    ebp
mov     ebp, esp
push    ebx
push    esi
push    edi
push    0
push    0
push    offset ReturnPoint
push    ecx
call    _RtlUnwind@16   ; RtlUnwind(x,x,x,x)

ReturnPoint:
pop     edi
pop     esi
pop     ebx
pop     ebp
retn
@_EH4_GlobalUnwind@4 endp
RtlUnwind 为导入函数


Wrk 中为源码,我们分析如下

我们首先看到,这里传给它的参数为RtlUnwind(EstablisherFrame,offset ReturnPoint,0,0);

E:\wrk-v1.2\wrk-v1.2\base\ntos\rtl\i386\exdsptch.c---------Line 371

函数原型

VOID
RtlUnwind (
   IN PVOID TargetFrame OPTIONAL,
   IN PVOID TargetIp OPTIONAL,
    INPEXCEPTION_RECORD ExceptionRecord OPTIONAL,
   IN PVOID ReturnValue
    )


除了一些栈地址范围及对齐的检查,函数遍历异常链表,以参数unwind调用异常处理函数,直到当前异常处理结构为传入的异常处理结构。每调用一个异常处理函数,将该异常处理结构从异常处理链表中清除(由于程序运行EIP 被重新设置,该异常处理函数所包含的内容已经不会再被执行)。如果到了目标异常处理结构。调用ZwContinue从当前函数返回。


局部展开

_EH4_LocalUnwind(
	(DWORD)EstablisherFrame,
	EstablisherFrame->TravelLevel,
	&EstablisherFrame->_ebp,
	__security_cookie);
; __fastcall _EH4_LocalUnwind(x, x, x, x)
@_EH4_LocalUnwind@16 proc near

arg_0= dword ptr  4
arg_4= dword ptr  8

push    ebp
mov     ebp, [esp+4+arg_0]	;// 这里特别重要,因为局部展开的操作是建立在原来函数的堆栈上的,必须有原来函数的堆栈才可
push    edx					;TravelLevel
push    ecx					;EstablisherFrame
push    [esp+0Ch+arg_4]		;__security_cookie
call    __local_unwind4
add     esp, 0Ch
pop     ebp
retn    8
@_EH4_LocalUnwind@16 endp

对于函数__local_unwind4 分析逆向得如下函数:

// 局部展开的函数
void __cdecl _local_unwind4(DWORD TargetTravelLevel, PEXTEND_EXCEPTION_RECORD EstablisherFrame, unsigned int __security_cookie)
{
	__asm
	{
		;var_20          = dword ptr -20h
		;arg_0           = dword ptr  4
		;arg_4           = dword ptr  8
		;arg_8           = dword ptr  0Ch
		
		push    ebx
		push    esi
		push    edi
		mov     edx, [esp+0Ch+arg_0]	; edx=__security_cookie
		mov     eax, [esp+0Ch+arg_4]	; eax=EstablisherFrame
		mov     ecx, [esp+0Ch+arg_8]	; ecx=InTravelLevel
		push    ebp						; 下面会用到该地址,mov ebp , [eax+0x18] 其中eax = 下面的fs 寄存器存储的值
		push    edx						; __security_cookie
		push    eax						; EstablisherFrame
		push    ecx						; InTravelLevel
		push    ecx						; ___security_cookie xor fs寄存器
		push    offset _unwind_handler4
		push    large dword ptr fs:0	; 这就是新的fs寄存器指向的地址,即传入 _unwind_handler4的EstablisherFrame参数
		mov     eax, ___security_cookie
		xor     eax, esp
		mov     [esp+28h+var_20], eax
		mov     large fs:0, esp			; 这里设置了一个新的异常处理结构,其结构与之前都不同,需要重新分析
	}
	PSCOPETABLE lpScopetablePtr = __security_cookie ^ (EstablisherFrame->scopetablePtr);
	while(1)
	{
		if(EstablisherFrame->TravelLevel != TRYLEVEL_INVALID) {
			if(TargetTravelLevel == TRYLEVEL_INVALID || TargetTravelLevel < EstablisherFrame->TravelLevel) {
				DWORD	dwNowTravelLevel = EstablisherFrame->TravelLevel;
				EstablisherFrame->TravelLevel = lpScopetablePtr->lpScopetable[dwNowTravelLevel].previousTryLevel;

				if( lpScopetablePtr->lpScopetable[dwNowTravelLevel].lpfnFilter == 0) { // _finally 块
					DWORD	dwLpfnHandler =  lpScopetablePtr->lpScopetable[dwNowTravelLevel].lpfnHandler;
					__asm
					{
						push    101h
						mov     eax,dwLpfnHandler
						call	__NLG_Notify
						mov     ecx, 1
						mov     eax, dwLpfnHandler
						call    __NLG_Call			; 内部就是call eax
					}
					
				} else {	// _except 块

				}
				continue;
			}
		}
		break;
	}
	
	__asm
	{
		pop fs:[0]
	}
}

掌握了局部展开的目的就比较好理解其代码,循环遍历scopetable表,查找_finally块对应的代码块(没有filter 函数的块)并执行。此函数开头安装了一个“用于捕获展开操作中产生的异常的异常处理结构”下面再进行分析。另外,如果是其它的自己安装的异常处理函数,自己应该判断ExceptionFlags标志,然后再做相应的处理。

要用到的两个结构体

typedef struct _EXCEPTION_REGISTRATION_RECORD {
	struct _EXCEPTION_REGISTRATION_RECORD *Next;
	PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;
typedef struct _DISPATCHER_CONTEXT {
	DWORD RegistrationPointer;
} DISPATCHER_CONTEXT;

上面介绍的异常处理函数

// 展开函数内部设置的用于捕获展开异常的函数
EXCEPTION_DISPOSITION	my_unwind_handler4(
	IN struct _EXCEPTION_RECORD *ExceptionRecord, 
	IN PEXTEND_EXCEPTION_RECORD EstablisherFrame, 
	IN OUT struct _CONTEXT *ContextRecord, 
	IN OUT DISPATCHER_CONTEXT DispatcherContext ) {
		// 如上所述,这里的EstablisherFrame为上面建立的栈里的那个新的与众不同的异常处理结构
		__asm
		{
			mov     ecx, [esp+4]		 ; ecx = ExceptionRecord
			test    dword ptr [ecx+4], 6 ; test ExceptionRecord->ExceptionFlags EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND
			mov     eax, 1				 ; 默认返回值为ExceptionContinueSearch(1)
			jz      short locret_11391	 ; 如果不是展开操作中调用此函数,直接返回

			// 异常处理过程中又发生了异常
			mov     eax, [esp+arg_4]		; eax = EstablisherFrame
			mov     ecx, [eax+8]			; ecx = ___security_cookie  这个的值是根据__local_unwind4中建立的栈得到的
			xor     ecx, eax				; ecx = ___security_cookie xor EstablisherFrame
			call    @__security_check_cookie@4 ; __security_check_cookie(x)
			push    ebp					; 
			mov     ebp, [eax+18h]		;设置 ebp 为调用那个local_unwind 函数时push ebp 操作对应的上一个ebp 的地址,其实就是为了构建原来的调用__local_unwind4 时的环境
			push    dword ptr [eax+0Ch]	;这三个push 都是用的__local_unwind4 里面push 的,请看之前的push操作及注释
			push    dword ptr [eax+10h]
			push    dword ptr [eax+14h]
			call    __local_unwind4		
			;__local_unwind4(InTravelLevel,EstablisherFrame,___security_cookie)
			; 调用__local_unwind4 函数,以释放资源。这里的参数实际和__导致异常的__local_unwind4 参数相同
			; 只是EstablisherFrame->TravelLevel不同,即忽略导致异常的TravelLevel然后继续进行局部展开操作
			; 相当于忽略异常,继续向下执行了
			; 可能是因为如果嵌套异常,事情就变得非常不可控了吧,操作系统也只能尽力而为
			add     esp, 0Ch
			pop     ebp
			mov     eax, [esp+8]		; eax=EstablisherFrame
			mov     edx, [esp+0x10]		; DispatcherContext
			mov     [edx], eax			; DispatcherContext->RegistrationPointer=EstablisherFrame->PreExCeptionRegistration
			; 设置异常处理结构为上一个异常处理结构,即执行展开过程中产生异常的异常处理结构
			; 然后展开操作会跳过产生异常的异常处理函数,继续展
			mov     eax, 3				; ExceptionCollidedUnwind (3)
locret_11391:
			retn
		}
}

在RtlUnwind中


case ExceptionCollidedUnwind :

                //
                // Pick up the registration pointer that was active at
                // the time of the unwind, and simply continue.
                //

                RegistrationPointer = DispatcherContext.RegistrationPointer;

因此可以在全局展开的时候直接将这个捕获嵌套异常的异常结构以及产生异常的异常结构跳过,然后直接继续运行。体现了展开异常的思想,把能做的尽量做完,然后假设其真的已经做完了并继续执行


嵌套异常的介绍

第一篇文章中我们说过,嵌套异常的捕获和处理是在系统的那个循环调用异常处理函数的函数中。该函数在调用一个异常结构的异常处理函数的时候之所以要通过函数调用来调用,而不是直接调用异常处理函数,原因就在于要构建一个异常处理结构,以捕获在调用异常处理函数中所导致的异常。

另外一种异常的情况,异常处理过程中产生异常:

RtlDispatchException

--->    _RtlpExecuteHandlerForException@20

         ---->     short ExecuteHandler@20

                   ----->    ExecuteHandler2@20

_RtlpExecuteHandlerForException@20 procnear

mov     edx, offsetExceptionHandler@16                 //这里设置了edx 寄存器

jmp    short ExecuteHandler@20

_RtlpExecuteHandlerForException@20 endp

 

 

ExecuteHandler@20 proc near

 

arg_0= dwordptr  4                //ExceptionRecord

arg_4= dword ptr  8               //倒数第二个push的参数,即传递给RtlpExecuteHandlerForException的第二个参数,即RegistrationPointer

arg_8= dwordptr  0Ch           //ContextRecord

arg_C= dwordptr  10h           //DispatcherContext

arg_10= dwordptr  14h         //RegistrationPointer->Handler

 

push    ebx  

push    esi

push    edi

xor     eax, eax

xor     ebx, ebx

xor     esi, esi

xor     edi, edi

push    [esp+0Ch+arg_10]   // Handler

push    [esp+10h+arg_C]     // DispatcherContext

push    [esp+14h+arg_8]      // ContextRecord

push    [esp+18h+arg_4]   // 我们看到其值为上层函数传进来的RegistrationPointer

push    [esp+1Ch+arg_0]     // ExcetionRecord

call    ExecuteHandler2@20

pop     edi

pop     esi

pop     ebx

retn    14h                                //返回并清理14h 堆栈

ExecuteHandler@20endp

 

 

ExecuteHandler2@20 proc near

 

arg_0= dword ptr  8                //ExceptionRecord

arg_4= dword ptr  0Ch           // 倒数第二个push的参数RegistrationPointer

arg_8= dword ptr  10h           //ContextRecord

arg_C= dword ptr  14h           //DispatcherContext

arg_10= dword ptr  18h         //Handler

 

push   ebp

mov    ebp, esp

push    [ebp+arg_4]            // 后面的异常处理函数中用到了这个变量

push   edx                               // 这里设置edx 为异常处理程序

push   large dword ptr fs:0 // 老的异常处理结构地址,新的异常处理结构

mov    large fs:0, esp          // 构建新的异常

push   [ebp+arg_C]              // DispatcherContext

push    [ebp+arg_8]               //ContextRecord

push   [ebp+arg_4]               // RegistrationPointer

push   [ebp+arg_0]               // ExceptionRecord

mov    ecx, [ebp+arg_10]

call   ecx                                  //call handler

mov    esp, large fs:0          // 卸载异常

pop    large dword ptr fs:0         // 恢复原来的异常

mov    esp, ebp

pop    ebp

retn   14h

ExecuteHandler2@20 endp

 

所以这就是我们的异常处理函数

.text:4B3061F0 ExceptionHandler@16 proc near           ; DATA XREF: .text:4B28C2A4o

.text:4B3061F0 arg_0           = dword ptr  4                //ExceptionRecord

.text:4B3061F0 arg_4           = dword ptr  8                //RegistrationPointer

.text:4B3061F0 arg_C           = dword ptr  10h           //DispatcherContext

.text:4B3061F0

.text:4B3061F0                 mov     ecx, [esp+arg_0]  // eax = ExceptionRecord

.text:4B3061F4                 test    dword ptr [ecx+4], 6       // 是否为展开

.text:4B3061FB                 mov     eax, 1                     //返回值为ExceptionContinueSearch 

.text:4B306200                 jnz     short locret_4B306214 // 展开操作就直接返回

.text:4B306202                 mov     ecx, [esp+arg_4] ; ecx = EstablisherFrame

.text:4B306206                 mov     edx, [esp+arg_C] ; edx = DispatcherContext

.text:4B30620A                 mov     eax, [ecx+8]          ;

.text:4B30620D                mov     [edx], eax   DispatcherContext->Registration = RegistrationPointer

.text:4B30620F                 mov     eax, 2           这个就是嵌套异常的标志

.text:4B306214

.text:4B306214 locret_4B306214:

.text:4B306214                 retn    10h

.text:4B306214 ExceptionHandler@16 endp

 

ExecuteHandler2,函数内部建立一个异常处理块,该块对应的异常处理函数简单的改变异常处理结构为之前的异常处理结构,并返回ExceptionNestedException(2) ,表示产生了嵌套的异常。

另外,只有在Filter 函数中可能产生嵌套异常,Handler 函数中由于事先执行全局展开,无用的异常处理结构被清除,包括上述的结构,因此该位置的异常也被清除了。

在RtlDispatchException 中

NestedRegistration = 0;

While(1)

{

 

if (NestedRegistration ==RegistrationPointer) {

           ExceptionRecord->ExceptionFlags &= (~EXCEPTION_NESTED_CALL);

           NestedRegistration = 0;

       }// 在下面的case 语句之后立马就是产生异常的异常处理函数,调用其之后就将异常状态清除即可。仅仅在调用产生异常的异常处理函数的时候有此标志。

 

…..

 case ExceptionNestedException :

           ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;

           if (DispatcherContext.RegistrationPointer > NestedRegistration) {

                NestedRegistration =DispatcherContext.RegistrationPointer;

           }// 这里设置了异常的标志,且NestedRegistration就是产生异常的异常处理函数


调试验证分析

调试采用Win10 X64 和Ring3  vs2010 release X86,其实Ring0 和 Ring3 逻辑一致,其代码也几乎相同,为方便调试及记录,通过 Ring3调试如下

代码

EXCEPTION_DISPOSITION
	__cdecl
	except_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,
	void * EstablisherFrame,
struct _CONTEXT *ContextRecord,
	void * DispatcherContext )
{
	printf( "Home Grown handler: Exception Code: %08X Exception Flags %X",
		ExceptionRecord->ExceptionCode, ExceptionRecord->ExceptionFlags );

	if ( ExceptionRecord->ExceptionFlags & 1 )
		printf( " EH_NONCONTINUABLE" );
	if ( ExceptionRecord->ExceptionFlags & 2 )
	{

		printf( " EH_UNWINDING\r\n" );
		*(PDWORD) 0 = 0;
	}
	printf( "\n" );

	// Punt... We don't want to handle this... Let somebody else handle it
	return ExceptionContinueSearch;
}

void HomeGrownFrame( void )
{
	DWORD handler = (DWORD)except_handler;
	__asm
	{                           // Build EXCEPTION_REGISTRATION record:
		push    handler         // Address of handler function
		push    FS:[0]          // Address of previous handler
		mov     FS:[0],ESP      // Install new EXECEPTION_REGISTRATION
	}

	*(PDWORD)0 = 0;             // 非法访问导致异常

	printf( "I should never get here!\n" );

	__asm
	{                           // Remove our EXECEPTION_REGISTRATION record
		mov     eax,[ESP]       // Get pointer to previous record
		mov     FS:[0], EAX     // Install previous record
		add     esp, 8          // Clean our EXECEPTION_REGISTRATION off stack
	}
}

int main()
{
	_try
	{
		HomeGrownFrame();
	}
	_except( EXCEPTION_EXECUTE_HANDLER )
	{
		printf( "Hello World\n" );
	}
	return 0;
}

这里使用非法访问产生异常。调试之前,设置如下


运行结果如下



函数入口设置断点如下


进入该函数,获得安装的异常处理函数的地址,并在入口点设置断点如下



在编译器对应的异常处理函数对应的一个filter 中下断点如下,捕获异常处理函数的执行。



F5 运行,然后程序运行如下:



HomeGrownFrame 函数中的*(PDWORD)0= 0;             //非法访问导致异常

 

调用堆栈如下:



由于没有符号,无法看到函数,查看其代码我们发现:


我们在这里下断点,并F5,然后观察fs寄存器,查看当前为捕获嵌套异常而安装的异常处理函数。并下断点如下:



现在我们F5,应该会调转到上面介绍的filter 函数,然后我们继续执行,应该可以返回到_except_handler4函数,并执行全局展开(因为filter 返回的是1 EXCEPTION_EXECUTE_HANDLER).这里返回的是except_common,其实是一样的。





F5,应该会跳转到前面的0x777361f0,然后直接返回,然后再跳转到except_handler函数,输出显示当前为展开操作。

但是其首先到达的位置为0x777361D0.查看调用堆栈如下:



然后我们发现其调用了ntdll导出函数RtlUnwind,其内部分析如下


安装的用于捕获展开异常的异常处理函数为:0x77736220,下断点如下。


我们发现,展开函数和运行异常使用的是同样的函数,安装的异常不同。代码重用,厉害了我的微软。

F5 如下:


像我们之前想的一样。

再次F5+F5+F5如下:




跳转到我们的异常处理函数且ExceptionFlags为2。

再次F5



由于Flags为0,返回1,然后RtlExceptDispatch继续执行异常处理函数链表到我们的excep_handler如下:



返回,然后执行下一个异常处理函数,try 块安装的编译器的异常处理函数_except_handler4:



F5如下:

同样的,调用“用于捕获嵌套异常而安装的异常处理函数

然后调用“用于捕获展开异常而安装的异常处理函数”,此时ExceptionFlags为2。(* ̄▽ ̄*)o



函数内部修改了异常处理的流程,跳过了对于异常处理函数except_handler即产生展开异常的异常处理函数的调用,然后继续执行。




由于返回了展开异常,程序流程改变,且改变后当前异常处理结构的地址为“_except_handler4的地址”,然后我们查看程序执行流程如下:



我们首先了解ZwContinue 代码的工作,在wrk 查找ZwContinue得到如下内容

        page

       subttl  "Continue ExecutionSystem Service"

;++

;

; NTSTATUS

; NtContinue (

;    INPCONTEXT ContextRecord,

;    IN BOOLEANTestAlert

;    )

;

; Routine Description:

;

;    Thisroutine is called as a system service to continue execution after

;    anexception has occurred. Its function is to transfer information from

;    thespecified context record into the trap frame that was built when the

;    systemservice was executed, and then exit the system as if an exception

;    hadoccurred.

;

;   WARNING -Do not call this routine directly, always call it as

;             ZwContinue!!!  This is required because it needsthe

;            trapframe built by KiSystemService.

;

; Arguments:

;

;    KTrapFrame(ebp+0: after setup) -> base of KTrapFrame

;

;    ContextRecord(ebp+8: after setup) = Supplies a pointer to acontext rec.

;

;    TestAlert(esp+12: after setup) = Supplies a boolean value thatspecifies

;       whetheralert should be tested for the previous processor mode.

;

; Return Value:

;

;    Normallythere is no return from this routine. However, if the specified

;    contextrecord is misaligned or is not accessible, then the appropriate

;    statuscode is returned.

 

我们查看其参数说明,ContextRecord为一个CONTEXT 结构体,代表要跳转到的地方,TestAlert参数代表函数执行前是否应该测试alert,如果是的话,执行apc 队列中的函数,否则从指定位置和环境继续执行。

我们来看ContextRecord中对应的的内容。



我们看到,其实函数的ZwContinue只是函数返回而已。

我们看其源代码




在捕获了当前Context 之后将Esp 设置为弹出堆栈。然后堆栈是正确的,但是eip呢?怎么回事?我们来看其怎么获得的ContextRecord.



没有什么是一张截图解决不了的,有的话,就来两张截图


展开结束之后,函数从 handler 处执行,然后程序继续向下执行。

到此SHE X86分析结束



参考链接

http://boxcounter.com/technique/2011-10-19-seh-x86/

https://www.microsoft.com/msj/0197/exception/exception.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值