覆盖SEH的溢出利用检测思路
|
来源:kruglinski_at_sohu.com 作者:kruglinski 发布时间:2007-08-22
|
|
看到安焦上的一篇《基于栈指纹检测缓冲区溢出的一点思路》,这是在ShellCode已经运行时在它的调用堆栈(被Hook的下级调用函数 LoadLibrary)里进行检测,有些利用溢出覆盖SEH Handler,然后任程序运行,因为溢出破坏了堆或栈,肯定会出现异常,这时指向ShellCode的Handler被运行,我在想这一类的溢出利用,既然它想运行,那首先要过操作系统的异常派遣这一关,如果在分派异常时我们就对SEH Handler进行一下检测,或许能在ShellCode运行前就发现它。
我简单看了一下SEH处理流程,一直跟到这两个函数,因为wrk代码不全,所以我选取ReactOS的代码,但并不影响理解。
以下代码来自ReactOS,版权归原作者
VOID NTAPI KiUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context) { EXCEPTION_RECORD NestedExceptionRecord; NTSTATUS Status;
/* call the vectored exception handlers */ if(RtlpExecuteVectoredExceptionHandlers(ExceptionRecord, Context) != ExceptionContinueExecution) {//VEH??? ReactOS也太强了吧,实现了XP的VEH,兼容度很高啊! goto ContinueExecution; } else { /* Dispatch the exception and check the result */ if(RtlDispatchException(ExceptionRecord, Context)) { ContinueExecution: /* Continue executing */ Status = NtContinue(Context, FALSE); } else { /* Raise an exception */ Status = NtRaiseException(ExceptionRecord, Context, FALSE); } }
/* Setup the Exception record */ NestedExceptionRecord.ExceptionCode = Status; NestedExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE; NestedExceptionRecord.ExceptionRecord = ExceptionRecord; NestedExceptionRecord.NumberParameters = Status;
/* Raise the exception */ RtlRaiseException(&NestedExceptionRecord); }
BOOLEAN NTAPI RtlDispatchException(IN PEXCEPTION_RECORD ExceptionRecord, IN PCONTEXT Context) { PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, NestedFrame = NULL; DISPATCHER_CONTEXT DispatcherContext; EXCEPTION_RECORD ExceptionRecord2; EXCEPTION_DISPOSITION Disposition; ULONG_PTR StackLow, StackHigh; ULONG_PTR RegistrationFrameEnd;
/* Get the current stack limits and registration frame */ RtlpGetStackLimits(&StackLow, &StackHigh); RegistrationFrame = RtlpGetExceptionList();
/* Now loop every frame */ while (RegistrationFrame != EXCEPTION_CHAIN_END) { /* Find out where it ends */ RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame + sizeof(EXCEPTION_REGISTRATION_RECORD);
/* Make sure the registration frame is located within the stack */ if ((RegistrationFrameEnd > StackHigh) || ((ULONG_PTR)RegistrationFrame < StackLow) || ((ULONG_PTR)RegistrationFrame & 0x3)) { /* Check if this happened in the DPC Stack */ if (RtlpHandleDpcStackException(RegistrationFrame, RegistrationFrameEnd, &StackLow, &StackHigh)) { /* Use DPC Stack Limits and restart */ continue; }
/* Set invalid stack and return false */ ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID; return FALSE; }
/* Check if logging is enabled */ RtlpCheckLogException(ExceptionRecord, Context, RegistrationFrame, sizeof(*RegistrationFrame));
/* Call the handler */ Disposition = RtlpExecuteHandlerForException(ExceptionRecord, RegistrationFrame, Context, &DispatcherContext, RegistrationFrame-> Handler);
/* Check if this is a nested frame */ if (RegistrationFrame == NestedFrame) { /* Mask out the flag and the nested frame */ ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL; NestedFrame = NULL; }
/* Handle the dispositions */ switch (Disposition) { /* Continue searching */ case ExceptionContinueExecution:
/* Check if it was non-continuable */ if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) { /* Set up the exception record */ ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.NumberParameters = 0;
/* Raise the exception */ RtlRaiseException(&ExceptionRecord2); } else { /* Return to caller */ return TRUE; }
/* Continue searching */ case ExceptionContinueSearch: break;
/* Nested exception */ case ExceptionNestedException:
/* Turn the nested flag on */ ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
/* Update the current nested frame */ if (DispatcherContext.RegistrationPointer > NestedFrame) { /* Get the frame from the dispatcher context */ NestedFrame = DispatcherContext.RegistrationPointer; } break;
/* Anything else */ default:
/* Set up the exception record */ ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.NumberParameters = 0;
/* Raise the exception */ RtlRaiseException(&ExceptionRecord2); break; }
/* Go to the next frame */ RegistrationFrame = RegistrationFrame->Next; }
/* Unhandled, return false */ return FALSE; }
然后我们可以为需要保护的进程Hook KiUserExceptionDispatcher,在这里面检测Handler是否安全,我能想到的可能不太安全的Handler有四种情况,也许有更多,我只简单的实现了第一个策略(就是遍历一下SEH链),下面是相关的代码片段。
//SEHChecker.cpp
inline DWORD __fastcall GetFsDword(DWORD dwOffset) { __asm mov eax,DWORD PTR fs:[ecx] }
//策略: //1. Handler在栈区域 //2. Handler在堆区域 //3. Handler在全局数据区 //4. Handler在正常的代码页中,但第一条指令是jmp xxx,或者Handler前一段是不影响ShellCode的指令,后面带有一个jmp xxx,跳到Handler中,怎么检测?
BOOL AnyUnsafeHandler(void) { struct SEHChain{ SEHChain *pNext; void *pHandler; };
SEHChain* pChain=(SEHChain*)GetFsDword(0); DWORD dwStackBase=GetFsDword(4); DWORD dwStackLimit=GetFsDword(8);
BOOL bRet=FALSE;
do { bRet=((DWORD)(pChain->pHandler)>=dwStackLimit)&&((DWORD)(pChain->pHandler)<=dwStackBase); pChain=pChain->pNext; }while(!bRet&&(pChain!=(SEHChain*)-1));
return bRet; }
VOID WINAPI HookedUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT Context) { if(AnyUnsafeHandler()) { ExitThread(0); }
TrampolineUserExceptionDispatcher(ExceptionRecord,Context); }
在检测到非安全的Handler时我为什么要用ExitThread呢,因为基于TIB的Seh Chain是线程相关的,它不是Final型的SEH Handler(不懂的参考一下Hume大侠的经典文章<<SEH in ASM>>),所以直接用ExitThread把可能出现危险的线程给退掉,如果是主线程则进程会退出,需要的话同时记录一下日志,以供管理员分析受攻击情况。
|
|
|
[推荐]
[评论(0条)]
[返回顶部] [打印本页]
[关闭窗口] |
|
|
|
|
|
|
推荐广告 |
|
|
|
|