这篇文章写于14年10月,彼时正对Windows的SEH机制感兴趣,且国庆假期时间比较充裕,便记了几篇相关的笔记。后来逐渐忙于课业便没继续分析下去,3年来多次想完成这个系列,但每次刚一开头总会被各种琐事打断,一直未能如愿,希望这一次能如愿以偿地完成分析吧。
2017.12.04补充:
当时分析这个函数的动机有以下几个方面:
Matt Pietrek在他的大作《A Crash Course on the Depths of Win32 Structured Exception Handling》中提到了退出展开,其中有这么一段话:
When an exception occurs, the system walks the list of EXCEPTION_REGISTRATION structures until it finds a handler for the exception. Once a handler is found, the system walks the list again, up to the node that will handle the exception. During this second traversal, the system calls each handler function a second time. The key distinction is that in the second call, the value 2 is set in the exception flags. This value corresponds to EH_UNWINDING.
我认为这段话是不准确的,事实上这样的操作被称为局部展开,是用户代码自愿进行的(言下之意就是这部分操作不是由Windows的用户层结构化异常处理机制来完成的)。在Matt Pietrek上面列举的例子中,由于在最外层使用了C/C++结构化异常处理中的异常处理块,因此导致内部原生异常处理程序不处理异常、执行流程转到外部的try-except块后,在其中(准确的说是except_handler4函数)执行了局部展开操作。因此,“局部展开会由系统完成”是一个很普遍的误会,网上很多文章都有类似的问题。就连《Windows核心编程》中的最后一部分也直接取名叫“结构化异常处理”,而事实上,其讲解的仅仅只是Visual Studio中C/C++的结构化异常处理机制而已,而局部展开行为尽管合理,但也需要用户手动进行,在系统的用户层结构化处理中是不会进行局部展开的。
其实局部展开的秘密,就隐藏在RtlUnwind函数的代码中。长时间以来,这个函数都处于undocument状态,虽然现在(2017年12月)可以在MSDN上查到了,但也是寥寥数语,不如自己分析来的痛快。
在漏洞利用中,SEH一直是一个热点话题。在经历了几轮魔道斗争后,SEH机制越来越成熟。而关于SEH安全机制的文章,网上一抓一大把,大多拿出的是伪代码和讲解,挖出代码进行分析的很少很少。本着“大海捞针,总不死心”和“眼见为实”的精神,咱们自己来分析一下。)
Visual C++中的结构化异常处理是构建在Windows的用户层异常处理机制之上的。因此有必要先了解后者,以便更好地分析前者。
逆向成果
RtlUnwind函数是一个用户层的Windows API函数,其作用是执行SEH的展开操作(正是由于Windows的SEH机制不自动进行展开操作,才提供了此API,供需要执行该操作的用户调用)。下面介绍其参数:
void WINAPI RtlUnwind(
_In_opt_ PVOID TargetFrame,
_In_opt_ PVOID TargetIp,
_In_opt_ PEXCEPTION_RECORD ExceptionRecord,
_In_ PVOID ReturnValue
);
TargetFrame :
- 当进行局部展开时,传入目标EXCEPTION_REGISTRATION_RECORD结构地址,当完成展开操作后,FS:[0]将指向这里。而执行该函数前的fs:[0]指向的节点到目标节点之间的前闭后开的所有合法节点中的处理函数都会被调用,被调用时传入的ExcepttionRecord.ExceptionFlags将包含EXCEPTION_UNWINDING操作。
- 当进行全局展开时,传入NULL即可。此时SEH链上所有节点的SEH处理函数都会被调用,调用时传入的ExcepttionRecord.ExceptionFlags将包含EXCEPTION_EXIT_UNWIND标志。
- 其余情况下需要谨慎传入,特别是不能传入比FS:[0]更低的地址,否则将引发异常。
TargetIp:虽然很多地方说通过这个参数可以设定函数执行完成后要执行的指令地址,但经过分析,至少在Win8.1和XP下,这个参数并没有什么用。
ExceptionRecord:在展开过程中,传递给被调用的异常处理函数的EXCEPTION_RECORD结构。如果传入NULL,将由本函数自己构造一个,其构造的那个ExceptionCode为STATUS_UNWIND,ExceptionAddress为调用本函数时压入的返回地址。
- ReturnValue:作为传给被调用的异常处理函数的CONTEXT结构中的eax值与恢复线程上下文环境时的eax值。
先贴上逆向的结果——C语言版本的RtlUnwind的代码(在IDA里按了F5然后对着3年前自己的逆向成果得来的):
void __stdcall RtlUnwind(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
{
PEXCEPTION_RECORD finalExcepttionRecord; // esi
unsigned int dwCurrentSEHNode; // ebx
unsigned int pfnHandler; // ecx
void *pExceptionRecord; // ecx
int dwRet; // eax
int *dwPrevSEHNode; // ST10_4
unsigned int StackLimit; // [esp+Ch] [ebp-384h]
unsigned int StackBase; // [esp+10h] [ebp-380h]
unsigned int DispatcherContext; // [esp+14h] [ebp-37Ch]
EXCEPTION_RECORD tempExceptionRecord; // [esp+18h] [ebp-378h]
EXCEPTION_RECORD temp2ExceptionRecord; // [esp+68h] [ebp-328h]
CONTEXT stContext; // [esp+B8h] [ebp-2D8h]
int savedregs; // [esp+390h] [ebp+0h]
void *retaddr; // [esp+394h] [ebp+4h]
finalExcepttionRecord = ExceptionRecord;
RtlpGetStackLimits((void ***)&StackLimit, (void ***)&StackBase);// 获取线程的栈底和栈顶
if ( !ExceptionRecord ) // 如果没有传入ExceptionRecord,则构造一个,作为出现异常时抛出的那个
// 否则就用传入的那个
{
finalExcepttionRecord = &temp2ExceptionRecord;
temp2ExceptionRecord.ExceptionCode = 0xC0000027;// 0xC0000027 = STATUS_UNWIND
temp2ExceptionRecord.ExceptionFlags = 0;
temp2ExceptionRecord.ExceptionRecord = 0;
temp2ExceptionRecord.ExceptionAddress = retaddr;// 这里取出的是调用本函数之前call压入的返回地址
temp2ExceptionRecord.NumberParameters = 0;
}
if ( TargetFrame )
finalExcepttionRecord->ExceptionFlags |= 2u;// #define EXCEPTION_UNWINDING 0x2
// 如果传入的TargetFrame非空,则进行局部展开
else
finalExcepttionRecord->ExceptionFlags |= 6u;// #define EXCEPTION_EXIT_UNWIND 0x4
// 如果传入的TargetFrame为NULL,则进行退出展开
RtlpCaptureContext(&savedregs, (int)&stContext);// 填写stContext结构,通用寄存器组清0,
// EIP设置为call本函数时压入的返回地址
// EBP设置为调用本函数时的EBP值(即前一帧的EBP)
// ESP设置为指向TargetFrame的值(因为从本函数ret后这里就应该是ESP该指向的位置)
stContext.Esp += 16; // 上下文结构中的ESP结构加上16,平衡了压入的4个参数
stContext.Eax = (unsigned int)ReturnValue; // 将传入的ReturnValue设置给eax作为返回值
dwCurrentSEHNode = RtlpGetRegistrationHead(); // 从fs:0处获得异常链的首节点
while ( dwCurrentSEHNode != 0xFFFFFFFF ) // 当前线程注册了自己的SEH处理函数,而非kernel32默认的那个
{
if ( (PVOID)dwCurrentSEHNode == TargetFrame )// 如果已到达指定节点,则按照之前设定的Context恢复线程的环境并运行
{
ZwContinue(&stContext, 0);
}
else if ( TargetFrame && (unsigned int)TargetFrame < dwCurrentSEHNode )// 传入的TargetFrame不为NULL且SEH链首节点高于TargetFrame,抛异常
// (因为合法的SEH节点肯定在栈中,则从SEH首节点开始,应该依次降低)
// 而这里反到首节点在目标节点下方,肯定传入的参数有问题
{
tempExceptionRecord.NumberParameters = 0;
tempExceptionRecord.ExceptionCode = 0xC0000029;
tempExceptionRecord.ExceptionFlags = 1;
tempExceptionRecord.ExceptionRecord = finalExcepttionRecord;
RtlRaiseException(&tempExceptionRecord);
}
if ( dwCurrentSEHNode < StackLimit // 异常情况:SEH首节点比栈顶极限位置还高(这里的高指的是地址更低)
|| dwCurrentSEHNode + 8 > StackBase // 异常情况:SEH首节点的最末尾成员比栈底还要低(这里的低指的是地址更高)
|| dwCurrentSEHNode & 3 // 异常情况:SEH首节点地址不能被4整除(没对齐)
|| (pfnHandler = *(_DWORD *)(dwCurrentSEHNode + 4), pfnHandler < StackBase) && pfnHandler >= StackLimit// 异常情况:异常处理节点的函数地址落在栈空间中(说明多半是被溢出了)
|| !RtlIsValidHandler(pfnHandler, 0, (int)&stContext) )// 异常情况:被SafeSEH机制给判定为非法处理函数地址
{ // 以上异常情况,抛出异常,拒绝执行处理函数
tempExceptionRecord.NumberParameters = 0;
tempExceptionRecord.ExceptionCode = 0xC0000028;
tempExceptionRecord.ExceptionFlags = 1;
tempExceptionRecord.ExceptionRecord = finalExcepttionRecord;
RtlRaiseException(&tempExceptionRecord);
}
else
{
dwRet = RtlpExecuteHandlerForUnwind( // 调用目标SEH节点的处理函数,执行退出前的清理动作
pExceptionRecord,
(int)finalExcepttionRecord,
dwCurrentSEHNode,
(int)&stContext,
(int)&DispatcherContext,
*(_DWORD *)(dwCurrentSEHNode + 4))
- 1; // 这里很顽皮,居然在原SEH函数的结果基础上减了1
// ExceptionContinueExecution = 0n0 //已处理异常,程序可继续执行
// ExceptionContinueSearch = 0n1 //函数不处理该异常,继续搜寻异常处理函数链
// ExceptionNestedException = 0n2 //在处理异常的过程中产生了新的异常
// ExceptionCollidedUnwind = 0n3 //嵌套展开
// 分别减1
if ( dwRet ) // 如果不为ExceptionContinueSearch,则表明异常处理函数没按套路出牌
{
if ( dwRet == 2 ) // 如果返回的是CollidedUnwind,则将DispatcherContext作为下一个SEH节点展开
{
dwCurrentSEHNode = DispatcherContext;
}
else // 否则如果返回ContinueExecution、NestedException都会抛异常
{
tempExceptionRecord.NumberParameters = 0;
tempExceptionRecord.ExceptionCode = 0xC0000026;
tempExceptionRecord.ExceptionFlags = 1;
tempExceptionRecord.ExceptionRecord = finalExcepttionRecord;
RtlRaiseException(&tempExceptionRecord);
}
}
dwPrevSEHNode = (int *)dwCurrentSEHNode;
dwCurrentSEHNode = *(_DWORD *)dwCurrentSEHNode;// 工作指针后移
RtlpUnlinkHandler(dwPrevSEHNode); // 将fs:0指向当前节点的下一个节点,即取消掉本节点
}
}
if ( TargetFrame == (PVOID)0xFFFFFFFF ) // 如果传入的TargetFrame为0xFFFFFFFF,也不可能把这个节点干掉了,表明也到头了,该返回了
ZwContinue(&stContext, 0);
else
ZwRaiseException(finalExcepttionRecord, &stContext, 0);// 如果传入的TargetFrame为其他值,则抛异常(如果匹配到了的话,早在while中就恢复上下文执行了)
}
以下是2014.10.08的笔记:
逆向过程
不知由于何种原因,在Win 8.1 X86平台下,试图用静态方式找出RtlUnwind函数的宿主还费了一番功夫。首先,在kernel32.dll的导出表中发现了该函数,可是立即又发现其仅是一个傀儡:
.text:6B81C83C ; void __stdcall RtlUnwindStub(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
.text:6B81C83C public _RtlUnwindStub@16
.text:6B81C83C _RtlUnwindStub@16 proc near
.text:6B81C83C
.text:6B81C83C TargetFrame= dword ptr 4
.text:6B81C83C TargetIp= dword ptr 8
.text:6B81C83C ExceptionRecord= dword ptr 0Ch
.text:6B81C83C ReturnValue= dword ptr 10h
.text:6B81C83C
.text:6B81C83C 000 jmp ds:__imp__RtlUnwind@16 ; RtlUnwind(x,x,x,x)
.text:6B81C83C _RtlUnwindStub@16 endp
于是跟着JMP跳到了:
.idata:6B880A24 extrn _api_ms_win_core_registry_l1_1_0_NULL_THUNK_DATA:byte:4
.idata:6B880A28 ;
.idata:6B880A28 ; Imports from api-ms-win-core-rtlsupport-l1-2-0.dll
.idata:6B880A28 ;
.idata:6B880A28 ; SIZE_T __stdcall RtlCompareMemory(const void *Source1, const void *Source2, SIZE_T Length)
.idata:6B880A28 extrn __imp__RtlCompareMemory@12:dword
.idata:6B880A28 ; CODE XREF: FSPErrorMessages::CMessageTagCache::LookupNodeByMessageTag(ulong,FSPErrorMessages::MessageTag *)+29p
.idata:6B880A28 ; DATA XREF: FSPErrorMessages::CMessageTagCache::LookupNodeByMessageTag(ulong,FSPErrorMessages::MessageTag *)+29r ...
.idata:6B880A2C ; __declspec(dllimport) __stdcall RtlRaiseException(x)
.idata:6B880A2C extrn __imp__RtlRaiseException@4:dword
.idata:6B880A2C ; CODE XREF: PsspValidateWin32WalkMarker+9Ep
.idata:6B880A2C ; DATA XREF: PsspValidateWin32WalkMarker+9Er
.idata:6B880A30 ; WORD __stdcall RtlCaptureStackBackTrace(DWORD FramesToSkip, DWORD FramesToCapture, PVOID *BackTrace, PDWORD BackTraceHash)
.idata:6B880A30 extrn __imp__RtlCaptureStackBackTrace@16:dword
.idata:6B880A30 ; DATA XREF: RtlCaptureStackBackTraceStub(x,x,x,x)+6r
.idata:6B880A34 ; void __stdcall RtlUnwind(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
.idata:6B880A34 extrn __imp__RtlUnwind@16:dword
.idata:6B880A34 ; DATA XREF: RtlUnwindStub(x,x,x,x)r
.idata:6B880A34 ; RtlUnwind(x,x,x,x)r
.idata:6B880A38 ; void __stdcall RtlCaptureContext(PCONTEXT ContextRecord)
.idata:6B880A38 extrn __imp__RtlCaptureContext@4:dword
.idata:6B880A38 ; CODE XREF: RaiseFailFastException(x,x,x)+86p
.idata:6B880A38 ; DATA XREF: RtlCaptureContext(x)r ...
.idata:6B880A3C extrn _api_ms_win_core_rtlsupport_l1_2_0_NULL_THUNK_DATA:byte:4
.idata:6B880A40 ;
立即找到api-ms-win-core-rtlsupport-l1-2-0.dll,结果大吃一惊,连傀儡都不如:
.text:1000109E ; void __stdcall RtlUnwind(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
.text:1000109E public _RtlUnwind@16
.text:1000109E _RtlUnwind@16 proc near ; DATA XREF: .text:off_100010D8o
.text:1000109E
.text:1000109E TargetFrame = dword ptr 4
.text:1000109E TargetIp = dword ptr 8
.text:1000109E ExceptionRecord = dword ptr 0Ch
.text:1000109E ReturnValue = dword ptr 10h
.text:1000109E
.text:1000109E retn 10h
.text:1000109E _RtlUnwind@16 endp
这里要吐槽一下IDA Pro 5.5,在设置了PDB服务器和路径后竟然没有解析出pdb文件中的变量名。害得我连蒙带猜,还开着VS2013调试,只为获取call后的函数名。
.text:6B2E755F ; //=============== S U B R O U T I N E =======================================
.text:6B2E755F
.text:6B2E755F ; //VOID _stdcall
.text:6B2E755F ; //RtlUnwind(
.text:6B2E755F ; // IN PVOID TargetFrame OPTIONAL //指向当前异常处理函数的EXCEPTION_REGISTRAION结构,
.text:6B2E755F ; // 会展开SEH链上之前所有的节点,
.text:6B2E755F ; // 并发送EXCEPTION_UNWINDING;
.text:6B2E755F ; // 若留空,则为推出展开,发送EXCEPTION_UNWINDING or EXCEPTION_UNWINDING_FOR_EXIT
.text:6B2E755F ; // IN PVOID TargetIp OPTIONAL //指定函数返回后下一条指令地址,若留空,则默认,经过分析,该参数无效!
.text:6B2E755F ; // IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL
.text:6B2E755F ; // 指向EXCEPTION_RECORD,建议留空由系统填写
.text:6B2E755F ; // IN PVOID ReturnValue
.text:6B2E755F ; // ); //
.text:6B2E755F ;
.text:6B2E755F ; //Attributes: bp-based frame
.text:6B2E755F
.text:6B2E755F public RtlUnwind
.text:6B2E755F RtlUnwind proc near ; //CODE XREF: .text:6B2BC649p
.text:6B2E755F ; //.text:6B2BCB42p ...
.text:6B2E755F
.text:6B2E755F StackLimit = dword ptr -384h
.text:6B2E755F StackBase = dword ptr -380h
.text:6B2E755F DispatcherContext= dword ptr -37Ch
.text:6B2E755F tempExceptionRecord= EXCEPTION_RECORD ptr -378h
.text:6B2E755F stExceptionRecord= EXCEPTION_RECORD ptr -328h
.text:6B2E755F stContext = CONTEXT ptr -2D8h
.text:6B2E755F CookiePointer = dword ptr -4
.text:6B2E755F TargetFrame = dword ptr 8
.text:6B2E755F ExceptionRecord = dword ptr 10h
.text:6B2E755F dwRet = dword ptr 14h
.text:6B2E755F
.text:6B2E755F ; //FUNCTION CHUNK AT .text:6B2F597C SIZE 0000000F BYTES
.text:6B2E755F ; //FUNCTION CHUNK AT .text:6B31CD49 SIZE 000000B8 BYTES
.text:6B2E755F
.text:6B2E755F 000 mov edi, edi
.text:6B2E7561 000 push ebp
.text:6B2E7562 004 mov ebp, esp
.text:6B2E7564 004 and esp, 0FFFFFFF8h ; //栈内8Byte对齐
.text:6B2E7567 004 sub esp, 384h
.text:6B2E756D 388 mov eax, ds:Security_Cookie
.text:6B2E7572 388 xor eax, esp
.text:6B2E7574 388 mov [esp+384h+CookiePointer], eax
.text:6B2E757B 388 push ebx
.text:6B2E757C 38C push esi
.text:6B2E757D 390 mov esi, [ebp+ExceptionRecord]
.text:6B2E7580 390 lea edx, [esp+38Ch+StackBase]
.text:6B2E7584 390 push edi
.text:6B2E7585 394 lea ecx, [esp+390h+StackLimit]
.text:6B2E7589 394 call _RtlpGetStackLimits@8 ; //获取当前线程栈的栈顶和栈底
.text:6B2E758E 394 xor ecx, ecx
.text:6B2E7590 394 test esi, esi
.text:6B2E7592 394 jnz short ValidExceptionRecord ; //若用户指定了ExceptionRecord,跳走
.text:6B2E7594 394 mov eax, [ebp+4] ; //函数返回地址
.text:6B2E7597 394 lea esi, [esp+390h+stExceptionRecord]
.text:6B2E759B 394 mov [esp+390h+stExceptionRecord.ExceptionCode], STATUS_UNWIND
.text:6B2E75A3 394 mov [esp+390h+stExceptionRecord.ExceptionFlags], ecx
.text:6B2E75A7 394 mov [esp+390h+stExceptionRecord.ExceptionRecord], ecx
.text:6B2E75AB 394 mov [esp+390h+stExceptionRecord.ExceptionAddress], eax
.text:6B2E75AF 394 mov [esp+390h+stExceptionRecord.NumberParameters], ecx
.text:6B2E75B3
.text:6B2E75B3 ValidExceptionRecord: ; //CODE XREF: RtlUnwind+33j
.text:6B2E75B3 394 mov edi, [ebp+TargetFrame] ; //若为空,则为退出展开,否则为局部展开
.text:6B2E75B6 394 test edi, edi
.text:6B2E75B8 394 jz NullTargetFrame
.text:6B2E75BE 394 or [esi+EXCEPTION_RECORD.ExceptionFlags], 2 ; //#define EXCEPTION_UNWINDING 0x2 // Unwind is in progress
.text:6B2E75C2
.text:6B2E75C2 loc_6B2E75C2: ; //CODE XREF: RtlUnwind+126j
.text:6B2E75C2 394 lea eax, [esp+390h+stContext]
.text:6B2E75C9 394 push eax
.text:6B2E75CA 398 call _RtlpCaptureContext@4 ;
.text:6B2E75CA ; //CONTEXT.EBP = 调用RtlUnwind前的ebp
.text:6B2E75CA ; //CONTEXT.ESP = 调用RtlUnwind传参后的ESP
.text:6B2E75CA ; //CONTEXT.EIP = RtlUnwind执行完后下一条语句的地址
.text:6B2E75CF 394 mov eax, [ebp+dwRet]
.text:6B2E75D2 394 add [esp+390h+stContext._Esp], 10h ; //ESP = 还没压参前的ESP
.text:6B2E75DA 394 mov [esp+390h+stContext._Eax], eax ; //EAX 被改为传入的返回值
.text:6B2E75E1 394 call _RtlpGetRegistrationHead@0 ; //取得首个SEH链节点地址
.text:6B2E75E6 394 mov ebx, eax
.text:6B2E75E8
.text:6B2E75E8 ProcessSEH: ; //CODE XREF: RtlUnwind+10Cj
.text:6B2E75E8 ; //RtlUnwind+3586Aj
.text:6B2E75E8 394 cmp ebx, 0FFFFFFFFh ; //SEH链上没有函数
.text:6B2E75EB 394 jz NullSEHChain
.text:6B2E75F1 394 cmp ebx, edi ; //到达局部展开的节点
.text:6B2E75F3 394 jz short IsDestSEHNode
.text:6B2E75F5 394 test edi, edi ; //是局部展开还是退出展开
.text:6B2E75F7 394 jz short CheckOutOfBound
.text:6B2E75F9 394 cmp edi, ebx ;
.text:6B2E75F9 ; //由于SEH链按栈生长,后加入的节点在SEH链的前端,
.text:6B2E75F9 ; //为小地址。此时竟然出现比SEH链首地址还低的节点,
.text:6B2E75F9 ; //一定出现了错误
.text:6B2E75F9 ;
.text:6B2E75FB 394 jb NotValidExceptionChain ;
.text:6B2E75FB ; //引发一个异常,异常号为STATUS_INVALID_UNWIND_TARGET
.text:6B2E75FB ; //异常标志为EXCEPTION_NONCONTINUABLE,并将传入RtlUnwind
.text:6B2E75FB ; //的PEXCEPTION_RECORD作为关联EXCEPTION_RECORD填入主
.text:6B2E75FB ; //EXCEPTION_RECORD
.text:6B2E7601
.text:6B2E7601 CheckOutOfBound: ; //CODE XREF: RtlUnwind+98j
.text:6B2E7601 ; //RtlUnwind+120j ...
.text:6B2E7601 394 cmp ebx, [esp+390h+StackLimit]
.text:6B2E7605 394 jb ErrorOutOfStack
.text:6B2E760B 394 lea eax, [ebx+8] ;
.text:6B2E760B ; //判断SEH链最底端((ebx+8)刚好覆盖SEH结构)
.text:6B2E760B ; //是否越过线程栈底(高地址)
.text:6B2E760E 394 cmp eax, [esp+390h+StackBase]
.text:6B2E7612 394 ja ErrorOutOfStack
.text:6B2E7618 394 test bl, 3 ; //SEH链地址不能被4整除
.text:6B2E761B 394 jnz ErrorOutOfStack
.text:6B2E7621 394 mov ecx, [ebx+4] ; //ECX = SEH_Handler
.text:6B2E7624 394 cmp ecx, [esp+390h+StackBase] ;
.text:6B2E7624 ; //函数绝不可能在栈上,函数要么位于系统Dll领空,
.text:6B2E7624 ; //要么位于程序代码段,二者地址都大于堆栈段
.text:6B2E7628 394 jb ErrorSEHFuncAddr
.text:6B2E762E
.text:6B2E762E Unwinding: ; //CODE XREF: RtlUnwind+E427j
.text:6B2E762E 394 lea eax, [esp+390h+stContext]
.text:6B2E7635 394 xor edx, edx
.text:6B2E7637 394 push eax
.text:6B2E7638 398 call RtlIsValidHandler
.text:6B2E763D 394 test al, al
.text:6B2E763F 394 jz ErrorOutOfStack
.text:6B2E7645 394 push dword ptr [ebx+4] ; //SEH_Handler入栈
.text:6B2E7648 398 lea eax, [esp+394h+DispatcherContext]
.text:6B2E764C 398 push eax
.text:6B2E764D 39C lea eax, [esp+398h+stContext]
.text:6B2E7654 39C push eax
.text:6B2E7655 3A0 push ebx
.text:6B2E7656 3A4 push esi
.text:6B2E7657 3A8 call _RtlpExecuteHandlerForUnwind@20 ;
.text:6B2E7657 ; //(PEXCEPTION_RECORD,SEH链,PCONTEXT,&dwDispatcherContext,SEH_Handler)
.text:6B2E765C 394 dec eax
.text:6B2E765D 394 jnz loc_6B31CD71
.text:6B2E7663
.text:6B2E7663 UninstMoveForward: ; //CODE XREF: RtlUnwind+35839j
.text:6B2E7663 ; //RtlUnwind+35842j
.text:6B2E7663 394 push ebx
.text:6B2E7664 398 mov ebx, [ebx] ; //ebx = NextSeh
.text:6B2E7666 398 call _RtlpUnlinkHandler@4 ;
.text:6B2E7666 ; //设置FS:[0]指向下一SEH节点
.text:6B2E766B 394 jmp ProcessSEH
.text:6B2E7670 ; //---------------------------------------------------------------------------
.text:6B2E7670
.text:6B2E7670 IsDestSEHNode: ; //CODE XREF: RtlUnwind+94j
.text:6B2E7670 394 push 0
.text:6B2E7672 398 lea eax, [esp+394h+stContext]
.text:6B2E7679 398 push eax
.text:6B2E767A 39C call ZwContinue ; //实际到了这句就不会往下走了吧?
.text:6B2E767F 394 jmp short CheckOutOfBound
.text:6B2E7681 ; //---------------------------------------------------------------------------
.text:6B2E7681
.text:6B2E7681 NullTargetFrame: ; //CODE XREF: RtlUnwind+59j
.text:6B2E7681 394 or [esi+EXCEPTION_RECORD.ExceptionFlags], 6 ; //#define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress
.text:6B2E7685 394 jmp loc_6B2E75C2
.text:6B2E7685 RtlUnwind endp ; //sp-analysis failed
.text:6B31CD49 ; //START OF FUNCTION CHUNK FOR RtlUnwind
.text:6B31CD49
.text:6B31CD49 NotValidExceptionChain: ; //CODE XREF: RtlUnwind+9Cj
.text:6B31CD49 394 and [esp+390h+tempExceptionRecord.NumberParameters], 0
.text:6B31CD4E 394 lea eax, [esp+390h+tempExceptionRecord]
.text:6B31CD52 394 push eax
.text:6B31CD53 398 mov [esp+394h+tempExceptionRecord.ExceptionCode], STATUS_INVALID_UNWIND_TARGET
.text:6B31CD5B 398 mov [esp+394h+tempExceptionRecord.ExceptionFlags], 1 ; //EXCEPTION_NONCONTINUABLE
.text:6B31CD63 398 mov [esp+394h+tempExceptionRecord.ExceptionRecord], esi ; //传入RtlUnwind的PEXCEPTION_RECORD
.text:6B31CD67 398 call RtlRaiseException
.text:6B31CD6C 394 jmp CheckOutOfBound
.text:6B31CD71 ; //---------------------------------------------------------------------------
.text:6B31CD71
.text:6B31CD71 loc_6B31CD71: ; //CODE XREF: RtlUnwind+FEj
.text:6B31CD71 394 dec eax
.text:6B31CD72 394 dec eax
.text:6B31CD73 394 jz short CollidedUnwind
.text:6B31CD75 394 and [esp+390h+tempExceptionRecord.NumberParameters], 0
.text:6B31CD7A 394 lea eax, [esp+390h+tempExceptionRecord]
.text:6B31CD7E 394 push eax
.text:6B31CD7F 398 mov [esp+394h+tempExceptionRecord.ExceptionCode], 0C0000026h
.text:6B31CD87 398 mov [esp+394h+tempExceptionRecord.ExceptionFlags], 1
.text:6B31CD8F 398 mov [esp+394h+tempExceptionRecord.ExceptionRecord], esi
.text:6B31CD93 398 call RtlRaiseException
.text:6B31CD98 394 jmp UninstMoveForward
.text:6B31CD9D ; //---------------------------------------------------------------------------
.text:6B31CD9D
.text:6B31CD9D CollidedUnwind: ; //CODE XREF: RtlUnwind+35814j
.text:6B31CD9D 394 mov ebx, [esp+390h+DispatcherContext]
.text:6B31CDA1 394 jmp UninstMoveForward
.text:6B31CDA6 ; //---------------------------------------------------------------------------
.text:6B31CDA6
.text:6B31CDA6 ErrorOutOfStack: ; //CODE XREF: RtlUnwind+A6j
.text:6B31CDA6 ; //RtlUnwind+B3j ...
.text:6B31CDA6 394 and [esp+390h+tempExceptionRecord.NumberParameters], 0
.text:6B31CDAB 394 lea eax, [esp+390h+tempExceptionRecord]
.text:6B31CDAF 394 push eax
.text:6B31CDB0 398 mov [esp+394h+tempExceptionRecord.ExceptionCode], STATUS_BAD_STACK
.text:6B31CDB8 398 mov [esp+394h+tempExceptionRecord.ExceptionFlags], 1
.text:6B31CDC0 398 mov [esp+394h+tempExceptionRecord.ExceptionRecord], esi
.text:6B31CDC4 398 call RtlRaiseException
.text:6B31CDC9 394 jmp ProcessSEH
.text:6B31CDCE ; //---------------------------------------------------------------------------
.text:6B31CDCE
.text:6B31CDCE NullSEHChain: ; //CODE XREF: RtlUnwind+8Cj
.text:6B31CDCE 394 lea eax, [esp+390h+stContext]
.text:6B31CDD5 394 push 0
.text:6B31CDD7 398 push eax
.text:6B31CDD8 39C cmp edi, 0FFFFFFFFh
.text:6B31CDDB 39C jnz short RaiseException
.text:6B31CDDD 39C call ZwContinue
.text:6B31CDE2 394 jmp short ExitFunc
.text:6B31CDE4 ; //---------------------------------------------------------------------------
.text:6B31CDE4
.text:6B31CDE4 RaiseException: ; //CODE XREF: RtlUnwind+3587Cj
.text:6B31CDE4 39C push esi
.text:6B31CDE5 3A0 call ZwRaiseException
.text:6B31CDEA
.text:6B31CDEA ExitFunc: ; //CODE XREF: RtlUnwind+35883j
.text:6B31CDEA 394 mov ecx, [esp+390h+CookiePointer]
.text:6B31CDF1 394 pop edi
.text:6B31CDF2 390 pop esi
.text:6B31CDF3 38C pop ebx
.text:6B31CDF4 388 xor ecx, esp
.text:6B31CDF6 388 call CheckCookie
.text:6B31CDFB 388 mov esp, ebp
.text:6B31CDFD 004 pop ebp
.text:6B31CDFE 000 retn 10h
.text:6B31CDFE ; //END OF FUNCTION CHUNK FOR RtlUnwind
.text:6B31CE01 ; //---------------------------------------------------------------------------
调用的子函数分析
下面是其中函数的分析:
_RtlpGetStackLimits@8:
.text:6B2E71C5 ; // =============== S U B R O U T I N E =======================================
.text:6B2E71C5
.text:6B2E71C5 ; // BOOL _fastcall RtlpGetStackLimits(lpdwStackLimit(ecx),lpdwStackBase(edx))
.text:6B2E71C5 ; // 1 = NormalStack
.text:6B2E71C5 ; // 0 = DestroyedStack
.text:6B2E71C5 ; // 将栈底和栈顶地址分别写入edx和ecx指向的变量中,
.text:6B2E71C5 ; // 并判断调用函数之前堆栈是否越界。
.text:6B2E71C5 ; // Attributes: bp-based frame
.text:6B2E71C5
.text:6B2E71C5 _RtlpGetStackLimits@8 proc near ; // CODE XREF: sub_6B2E724E+64p
.text:6B2E71C5 ; // RtlUnwind+2Ap
.text:6B2E71C5 000 mov edi, edi
.text:6B2E71C7 000 push ebp
.text:6B2E71C8 004 mov ebp, esp
.text:6B2E71CA 004 mov eax, large fs:18h ; // eax = TEB
.text:6B2E71D0 004 push esi
.text:6B2E71D1 008 push edi
.text:6B2E71D2 00C lea edi, [ebp+4]
.text:6B2E71D5 00C mov esi, [eax+4] ; // esi = StackBase
.text:6B2E71D8 00C mov eax, [eax+8] ; // eax = StackLimit
.text:6B2E71DB 00C mov [edx], esi ; // [edx] = StackBase
.text:6B2E71DD 00C mov [ecx], eax ; // [ecx] = StackLimit
.text:6B2E71DF 00C cmp eax, edi ; // 比较限制栈顶和当前栈顶(指call本函数之前的栈)
.text:6B2E71E1 00C ja short DestoryedStack
.text:6B2E71E3 00C cmp edi, esi ; // 当前栈顶是否高于限制栈底
.text:6B2E71E5 00C jnb short DestoryedStack
.text:6B2E71E7 00C mov al, 1
.text:6B2E71E9
.text:6B2E71E9 RecvyPointerReg: ; // CODE XREF: RtlpGetStackLimits(x,x)+2Aj
.text:6B2E71E9 00C pop edi
.text:6B2E71EA 008 pop esi
.text:6B2E71EB 004 pop ebp
.text:6B2E71EC 000 retn
.text:6B2E71ED ; // ---------------------------------------------------------------------------
.text:6B2E71ED
.text:6B2E71ED DestoryedStack: ; // CODE XREF: RtlpGetStackLimits(x,x)+1Cj
.text:6B2E71ED ; // RtlpGetStackLimits(x,x)+20j
.text:6B2E71ED 00C xor al, al
.text:6B2E71EF 00C jmp short RecvyPointerReg
.text:6B2E71EF _RtlpGetStackLimits@8 endp
.text:6B2E71EF
.text:6B2E71EF ; // ---------------------------------------------------------------------------
_RtlpCaptureContext@4
.text:6B2BC923 ; // // __stdcall RtlpCaptureContext(x)
.text:6B2BC923 _RtlpCaptureContext@4: ; // // CODE XREF: RtlUnwind+6Bp
.text:6B2BC923 push ebx
.text:6B2BC924 mov ebx, [esp+8] ; // // [esp+8] = eax = &stContext
.text:6B2BC928 mov [ebx+CONTEXT._Eax], 0
.text:6B2BC932 mov [ebx+CONTEXT._Ecx], 0
.text:6B2BC93C mov [ebx+CONTEXT._Edx], 0
.text:6B2BC946 mov [ebx+CONTEXT._Ebx], 0
.text:6B2BC950 mov [ebx+CONTEXT._Esi], 0
.text:6B2BC95A mov [ebx+CONTEXT._Edi], 0
.text:6B2BC964
.text:6B2BC964 loc_6B2BC964: ; // // CODE XREF: RtlCaptureContext+2Cj
.text:6B2BC964 mov word ptr [ebx+CONTEXT.SegCs], cs
.text:6B2BC96A mov word ptr [ebx+CONTEXT.SegDs], ds
.text:6B2BC970 mov word ptr [ebx+CONTEXT.SegEs], es
.text:6B2BC976 mov word ptr [ebx+CONTEXT.SegFs], fs
.text:6B2BC97C mov word ptr [ebx+CONTEXT.SegGs], gs
.text:6B2BC982 mov word ptr [ebx+CONTEXT.SegSs], ss
.text:6B2BC988 pushf
.text:6B2BC989 pop [ebx+CONTEXT.EFlags]
.text:6B2BC98F mov eax, [ebp+4] ; // // stContext.eip = RtlUnwind后的第一条指令地址
.text:6B2BC992 mov [ebx+CONTEXT._Eip], eax
.text:6B2BC998 mov eax, [ebp+0] ; // // stContext.ebp = RtlUnwind前的EBP值
.text:6B2BC99B mov [ebx+CONTEXT._Ebp], eax
.text:6B2BC9A1 lea eax, [ebp+8] ; // // stContext.esp = ebp+8 = &TargetFrame(最后一个入栈参数的地址)
.text:6B2BC9A4 mov [ebx+CONTEXT._Esp], eax
.text:6B2BC9AA mov [ebx+CONTEXT.ContextFlags], CONTEXT_FULL
.text:6B2BC9B0 pop ebx
.text:6B2BC9B1 retn 4
_RtlpGetRegistrationHead@0:
.text:6B2BC9B7 ; __stdcall RtlpGetRegistrationHead()
.text:6B2BC9B7 _RtlpGetRegistrationHead@0: ; CODE XREF: sub_6B2E724E+69p
.text:6B2BC9B7 ; RtlUnwind+82p ...
.text:6B2BC9B7 mov eax, large fs:0
.text:6B2BC9BD retn
.text:6B2BC9BE ; ---------------------------------------------------------------------------
_RtlpExecuteHandlerForUnwind@20
.text:6B2BC828 ; // =============== S U B R O U T I N E =======================================
.text:6B2BC828
.text:6B2BC828
.text:6B2BC828 ; // __stdcall RtlpExecuteHandlerForUnwind(x, x, x, x, x)
.text:6B2BC828 _RtlpExecuteHandlerForUnwind@20 proc near ; // CODE XREF: RtlUnwind+F8p
.text:6B2BC828
.text:6B2BC828 PEXCEPTION_RECORD= dword ptr 4
.text:6B2BC828 SEH = dword ptr 8
.text:6B2BC828 PCONTEXT = dword ptr 0Ch
.text:6B2BC828 dwDispatcherContext= dword ptr 10h
.text:6B2BC828 lpSEHHandlerFunc= dword ptr 14h
.text:6B2BC828
.text:6B2BC828 000 mov edx, offset TempSEHFunc
.text:6B2BC82D 000 lea ecx, [ecx]
.text:6B2BC82F
.text:6B2BC82F loc_6B2BC82F: ; // CODE XREF: sub_6B2BC820+5j
.text:6B2BC82F 000 push ebx
.text:6B2BC830 004 push esi
.text:6B2BC831 008 push edi
.text:6B2BC832 00C xor eax, eax
.text:6B2BC834 00C xor ebx, ebx
.text:6B2BC836 00C xor esi, esi
.text:6B2BC838 00C xor edi, edi
.text:6B2BC83A 00C push [esp+0Ch+lpSEHHandlerFunc]
.text:6B2BC83E 010 push [esp+10h+dwDispatcherContext]
.text:6B2BC842 014 push [esp+14h+PCONTEXT]
.text:6B2BC846 018 push [esp+18h+SEH]
.text:6B2BC84A 01C push [esp+1Ch+PEXCEPTION_RECORD]
.text:6B2BC84E 020 call CallNodeSEHFunc
.text:6B2BC853 00C pop edi
.text:6B2BC854 008 pop esi
.text:6B2BC855 004 pop ebx
.text:6B2BC856 000 retn 14h
.text:6B2BC856 _RtlpExecuteHandlerForUnwind@20 endp
.text:6B2BC856
.text:6B2BC859 ; // ---------------------------------------------------------------------------
.text:6B2BC85B ; // =============== S U B R O U T I N E =======================================
.text:6B2BC85B
.text:6B2BC85B ; // Attributes: bp-based frame
.text:6B2BC85B
.text:6B2BC85B CallNodeSEHFunc proc near ; // CODE XREF: RtlpExecuteHandlerForUnwind(x,x,x,x,x)+26p
.text:6B2BC85B
.text:6B2BC85B PEXCEPTION_RECORD= dword ptr 8
.text:6B2BC85B SEH = dword ptr 0Ch
.text:6B2BC85B PCONTEXT = dword ptr 10h
.text:6B2BC85B dwDispatcherContext= dword ptr 14h
.text:6B2BC85B lpfnSEHHandler = dword ptr 18h
.text:6B2BC85B
.text:6B2BC85B 000 push ebp
.text:6B2BC85C 004 mov ebp, esp
.text:6B2BC85E 004 push [ebp+SEH]
.text:6B2BC861 008 push edx
.text:6B2BC862 00C push large dword ptr fs:0
.text:6B2BC869 010 mov large fs:0, esp
.text:6B2BC870 010 push [ebp+dwDispatcherContext]
.text:6B2BC873 014 push [ebp+PCONTEXT]
.text:6B2BC876 018 push [ebp+SEH]
.text:6B2BC879 01C push [ebp+PEXCEPTION_RECORD]
.text:6B2BC87C 020 mov ecx, [ebp+lpfnSEHHandler]
.text:6B2BC87F 020 call ecx ; // 调用该节点的SEH处理函数
.text:6B2BC881 008 mov esp, large fs:0 ; // 利用FS:[0]维持ESP平衡
.text:6B2BC888 008 pop large dword ptr fs:0 ; // 恢复原FS:[0]
.text:6B2BC88F 004 mov esp, ebp
.text:6B2BC891 004 pop ebp
.text:6B2BC892 000 retn 14h
.text:6B2BC892 CallNodeSEHFunc endp
.text:6B2BC8BC ; // =============== S U B R O U T I N E =======================================
.text:6B2BC8BC
.text:6B2BC8BC
.text:6B2BC8BC TempSEHFunc proc near ; // DATA XREF: RtlpExecuteHandlerForUnwind(x,x,x,x,x)o
.text:6B2BC8BC
.text:6B2BC8BC ExceptionRecord = dword ptr 4
.text:6B2BC8BC lpSEH = dword ptr 8
.text:6B2BC8BC lpDispatcherContext= dword ptr 10h
.text:6B2BC8BC
.text:6B2BC8BC 000 mov ecx, [esp+ExceptionRecord]
.text:6B2BC8C0 000 test [ecx+EXCEPTION_RECORD.ExceptionFlags], 6 ; // 展开 or 退出展开
.text:6B2BC8C7 000 mov eax, ExceptionContinueSearch
.text:6B2BC8CC 000 jz short ret
.text:6B2BC8CE 000 mov ecx, [esp+lpSEH]
.text:6B2BC8D2 000 mov edx, [esp+lpDispatcherContext]
.text:6B2BC8D6 000 mov eax, [ecx+EXCEPTION_RECORD.ExceptionRecord]
.text:6B2BC8D9 000 mov [edx], eax
.text:6B2BC8DB 000 mov eax, ExceptionCollidedUnwind
.text:6B2BC8E0
.text:6B2BC8E0 ret: ; // CODE XREF: TempSEHFunc+10j
.text:6B2BC8E0 000 retn 10h
.text:6B2BC8E0 TempSEHFunc endp
_RtlpUnlinkHandler@4:
.text:6B2BC8E3 ; // =============== S U B R O U T I N E =======================================
.text:6B2BC8E3
.text:6B2BC8E3
.text:6B2BC8E3 ; // __stdcall RtlpUnlinkHandler(x)
.text:6B2BC8E3 _RtlpUnlinkHandler@4 proc near ; // CODE XREF: RtlUnwind+107p
.text:6B2BC8E3 ; // sub_6B358216+34p
.text:6B2BC8E3
.text:6B2BC8E3 SEH = dword ptr 4
.text:6B2BC8E3
.text:6B2BC8E3 000 mov ecx, [esp+SEH]
.text:6B2BC8E7 000 mov ecx, [ecx]
.text:6B2BC8E9 000 mov large fs:0, ecx
.text:6B2BC8F0 000 retn 4
.text:6B2BC8F0 _RtlpUnlinkHandler@4 endp
ZwContinue:
.text:6B2BA920 ; =============== S U B R O U T I N E =======================================
.text:6B2BA920
.text:6B2BA920
.text:6B2BA920 public ZwContinue
.text:6B2BA920 ZwContinue proc near ; CODE XREF: KiUserApcDispatcher+35p
.text:6B2BA920 ; KiUserExceptionDispatcher+18p ...
.text:6B2BA920 000 mov eax, 42h ; NtContinue(模拟中断后返回,可切上下文)
.text:6B2BA925 000 call large dword ptr fs:0C0h ;快速陷入内核的又一种姿势
.text:6B2BA92C 000 retn 8
.text:6B2BA92C ZwContinue endp
.text:6B2BA92C
剩余的函数还有RtlIsValidHandler没有分析,该函数会校验传入的SEH异常处理函数是否有效。由于最初SEH并不进行这种校验,使得SEH容易成为溢出攻击的跳板。因此,从Vista系统开始,微软启用了SafeSEH机制,我们将在后续文章中对该机制加以分析。

本文详细探讨了Windows用户层的结构化异常处理(SEH)机制,特别是RtlUnwind函数的逆向分析。作者指出,局部展开并非由系统自动完成,而是需要用户手动调用RtlUnwind。文章介绍了RtlUnwind的参数作用,并分享了逆向得到的C语言版本函数代码。同时,文章还提到了在Win8.1 X86平台上寻找RtlUnwind的挑战以及调用的相关子函数分析。
1329

被折叠的 条评论
为什么被折叠?



