| 
 覆盖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条)] 
[返回顶部] [打印本页] 
[关闭窗口]    | 
 
 
|  
 | 
 
 
|   | 
 
  | 
 
  
 | 
 
        
  | 
  | 
推荐广告 | 
 
  | 
 
  | 
 
| 
	
		
		
 | 
 
 
 |