VEH&SEH异常详解

admin 2022年5月12日23:42:10评论30 views字数 6580阅读21分56秒阅读模式

首发于奇安信攻防社区:https://forum.butian.net/share/1503

前言

因为Trap_Frame结构在3环的原因,会有一个从0环临时返回3环的过程,所以在用户层的异常执行过程相比于内核层更加复杂。

VEH

首先定位到KiUserExceptionDispatcher函数,这里首先通过RtlDispatchException来找到异常处理的函数

VEH&SEH异常详解

经过异常处理之后调用ZwContinue重新进入0环,这里是因为Trap_Frame结构被修改了,这里需要重新进入0环把新的值传入

VEH&SEH异常详解


如果这里异常没有处理成功会再次分发异常

VEH&SEH异常详解

RtlDispatchException

这里RtlDispatchException是分发异常的函数,0环跟3环是共用的,但是有一些细节是不同的,我们跟进去看看

VEH&SEH异常详解


首先看一下3环的RtlDispatchException里面有一个RtlCallVectoredExceptionHandlers

VEH&SEH异常详解


但是在0环的RtlDispatchException里面是没有这个函数的

VEH&SEH异常详解


跟进RtlCallVectoredExceptionHandlers,首先找全局链表(VEH链表),存储了很多个异常处理函数,如果在全局链表里面没有找到,就会继续往下找局部链表(SEH链表)

VEH&SEH异常详解

我们尝试用代码来实现VEH

// VEH1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

typedef PVOID (NTAPI *FnAddVectoredExceptionHandler)(ULONG, _EXCEPTION_POINTERS *);

FnAddVectoredExceptionHandler MyAddVectoredExceptionHeader;

// UEH异常处理函数只能返回2个值
// EXCEPTION_CONTINUE_EXECUTION 已处理
// EXCEPTION_CONTINUE_SEARCH 未处理

LONG NTAPI VectExceptionHandler( PEXCEPTION_POINTERS pExcepInfo )
{
 ::MessageBoxA(NULL"VEH Function run""VEH error", MB_OK);

 if (pExcepInfo->ExceptionRecord->ExceptionCode == 0xC0000094)
 {
  //pExcepInfo->ContextRecord->Eip = pExcepInfo->ContextRecord->Eip + 2;
  pExcepInfo->ContextRecord->Ecx = 1;

  return EXCEPTION_CONTINUE_EXECUTION;
 }

 return EXCEPTION_CONTINUE_SEARCH;

}

VOID VEH()
{
 // 动态获取AddUectoredExceptionHandler函数地址
 HMODULE hMyModule = GetModuleHandle("kernel32.dll");
 MyAddVectoredExceptionHeader = (FnAddVectoredExceptionHandler)::GetProcAddress(hMyModule, "AddVectoredExceptionHandler");

 // 参数1表示插入VEH链的头部、0表示插入到UEH链的尾部
 MyAddVectoredExceptionHeader( 0, (_EXCEPTION_POINTERS *)&VectExceptionHandler );

 // 构造除0异常
 __asm
 {
  xor edx,edx
  xor ecx,ecx
  mov eax,0x10
  idiv ecx // EDX:EAX 除以 ECX
 }

 printf("veh_code run here again");
}

int main(int argc, char* argv[])
{
 VEH();
 getchar();

 return 0;
}

AddVectoredExceptionHandler

这里有几个注意的点,首先是注册异常处理函数,使用到AddVectoredExceptionHandler,这个函数是在kernel32.dll里面的,在xp以前的版本里面是没有的,所以需要动态获取

PVOID AddVectoredExceptionHandler(
  ULONG                       First,
  PVECTORED_EXCEPTION_HANDLER Handler
)
;

第二个要注意的点就是veh是一个链表,第一个参数表示插入异常的位置,1的话就是插入到链表的头部,0的话就是插入到链表的尾部

VEH&SEH异常详解


第三个点就是异常处理的指针指向两个结构,一个是ContextRecord,这个结构里面存储的是所有寄存器的值,另外一个就是ExceptionRecord,这个结构里面存储的就是异常的具体信息

VEH&SEH异常详解


第四个点就是因为构造的是ecx为0,那么这里异常处理函数就可以修改eip指向的地址或者修改ecx的值为1即可

VEH&SEH异常详解


看下效果,首先是执行了我们自己注册的异常处理函数里面的MessageBoxA,然后程序正常下向下执行

VEH&SEH异常详解


VEH&SEH异常详解

VEH异常流程

1.CPU捕获异常信息

2.通过KiDispatchException进行分发(EIP=KiUserExceptionDispatcher)

3.KiUserExceptionDispatcher调用RtlDispatchException

4.RtlDispatchException查找VEH处理函数链表 并调用相关处理函数

5.代码返回到KiUserExceptionDispatcher

6.调用ZwContinue再次进入0环(ZwContinue调用NtContinue,主要作用就是恢复TRAP_FRAME 然后通过KiServiceExit返回到3环)。

7.线程再次返回3环后,从修正后的位置开始执行

SEH

SEH就是一个跟0环异常处理结构类似的链表

VEH&SEH异常详解


首先看一下RtlpGetStackLimits

VEH&SEH异常详解


取出了fs:[8]fs:[4]

VEH&SEH异常详解


我们知道fs:[0]指向的是_NT_TIB结构,那么fs:[4]对应的就是StackBasefs:[8]对应的就是StackLimit,即基址和界限

然后再拿到fs:[0]

VEH&SEH异常详解

VEH&SEH异常详解

拿到一系列的参数之后,会首先进行一系列的判断

VEH&SEH异常详解

RtlpExecuteHandlerForException

最后调用RtlpExecuteHandlerForException处理异常

VEH&SEH异常详解


SEH异常的实现

// SEH1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

// ring0
/*
typedef struct _EXCEPTION_REGISTRATION_RECORD {
        struct _EXCEPTION_REGISTRATION_RECORD *Next;
        PEXCEPTION_ROUTINE Handler;
    } EXCEPTION_REGISTRATION_RECORD;
*/


struct MyException
{

 struct MyException *prev;
 DWORD Handle;
};

EXCEPTION_DISPOSITION __cdecl MyException_Handler(
 struct _EXCEPTION_RECORD *ExceptionRecord, // ExceptionRecord 存储异常信息 什么类型异常产生位置
 void* EstablisherFrame,  // MyException结构体地址
 struct _CONTEXT *ContextRecord,  // Context结构体 异常发生时的各种寄存器值堆栈位置等
 void* DispatcherContext)

{
 ::MessageBoxA(NULL"SEH Function""SEH error", MB_OK);

 if (ExceptionRecord->ExceptionCode == 0xC0000094)
 {
  ContextRecord->Eip = ContextRecord->Eip + 2;
  return ExceptionContinueExecution;
 }

 return ExceptionContinueSearch;
}

void ExceptionTest()
{
 DWORD temp;

 MyException myException;

 __asm
 {
  mov eax,FS:[0]
  mov temp,eax
  lea ecx,myException
  mov FS:[0],ecx
 }
 myException.prev = (MyException*)temp;
 myException.Handle = (DWORD)&MyException_Handler;

 __asm
 {
  xor edx,edx
  xor ecx,ecx
  mov eax,0x10
  idiv ecx
 }

    // 摘除链表
 __asm
 {
  mov eax,temp
  mov FS:[0],eax
 }

 printf("SEH run again");
}


int main(int argc, char* argv[])
{
 ExceptionTest();
 getchar();
 return 0;
}

_EXCEPTION_REGISTRATION

首先定义一个异常处理_EXCEPTION_REGISTRATION结构

VEH&SEH异常详解


然后定义异常处理函数

EXCEPTION_DISPOSITION __cdecl MyException_Handler(
 struct _EXCEPTION_RECORD *ExceptionRecord, // ExceptionRecord 存储异常信息 什么类型异常产生位置
 void* EstablisherFrame,  // MyException结构体地址
 struct _CONTEXT *ContextRecord,  // Context结构体 异常发生时的各种寄存器值堆栈位置等
 void* DispatcherContext)


VEH&SEH异常详解


然后在当前线程里面声明结构体,把自己的结构体挂到链表里面,并定义Next指针指向下一个结构体

VEH&SEH异常详解


然后构造除0异常,然后将我们自己定义的结构体从链表里面摘除

VEH&SEH异常详解


运行结果如下

VEH&SEH异常详解


VEH&SEH异常详解


总结

1.FS:[0]指向SEH链表的第一个成员

2.SEH的异常处理函数必须在当前线程的堆栈中

3.只有当VEH中的异常处理函数不存在或者不处理才会到SEH链表中查找

SEH异常流程

1.RtlpGetStackLimits取出_NT_TIB结构的fs:[4]fs:[8],那么fs:[4]对应的就是StackBasefs:[8]对应的就是StackLimit,即基址和界限

2.RtlpGetRegistrationHead取出_NT_TIB结构的fs:[0],取出ExceptionList

3.然后调用RtlpExecuteHandlerForException处理异常

SEH编译器扩展

编译器可以直接帮我们进行简化挂入链表、异常过滤、执行异常处理程序的操作

VEH&SEH异常详解


在过滤表达式处只能有以下三种情况

1) EXCEPTION_EXECUTE_HANDLER(1) 执行except代码 

2) EXCEPTION_CONTINUE_SEARCH(0) 寻找下一个异常处理函数

3) EXCEPTION_CONTINUE_EXECUTION(-1) 返回出错位置重新执行 

而过滤表达式可以有3种写法

1) 直接写常量值 

2) 表达式

3) 调用函数

常量值

// SEH2.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

void ExceptionTest()
{
 _try
 {
  _asm
  {
   xor edx,edx
   xor ecx,ecx
   mov eax,0x10
   idiv ecx
  }
 }
 _except(EXCEPTION_EXECUTE_HANDLER)
 {
  printf("SEH function run");
 }
}

int main(int argc, char* argv[])
{
 ExceptionTest();

 getchar();
 return 0;
}


VEH&SEH异常详解


表达式

使用GetExceptionCode得到异常码

// SEH3.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

void ExceptionTest()
{
 _try
 {
  _asm
  {
   xor edx,edx
   xor ecx,ecx
   mov eax,0x10
   idiv ecx
  }
 }
 _except(GetExceptionCode() == 0xC0000094?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH)
 {
  printf("SEH function run");
 }
}

int main(int argc, char* argv[])
{
 ExceptionTest();

 getchar();
 return 0;
}


VEH&SEH异常详解


调用函数

使用GetExceptionInformation得到指向异常处理结构的指针

// SEH4.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo)
{
 pExceptionInfo->ContextRecord->Ecx = 1;
 return EXCEPTION_CONTINUE_EXECUTION; // -1 返回出错位置重新执行
}

void ExceptionTest()
{
 _try
 {
  _asm
  {
   xor edx,edx
   xor ecx,ecx
   mov eax,0x10
   idiv ecx
  }
 }
 _except(ExceptFilter(GetExceptionInformation()))
 {
  printf("SEH function run");
 }
}

int main(int argc, char* argv[])
{
 ExceptionTest();

 getchar();
 return 0;
}

这里先修改值之后回到异常发生的地方重新执行,没有异常了,然后就走到getchar()的地方

VEH&SEH异常详解


加下方wx,拉你一起进群学习

VEH&SEH异常详解

往期推荐

sql注入bypass最新版某狗

软件调试详解

初探windows异常处理

浅谈hook攻防

无处不在的dll劫持

通过硬件断点对抗hook检测

bypass Bitdefender

windows下的反调试探究

绕过360实现lsass转储

一次简单的内网渗透靶场实战


VEH&SEH异常详解



原文始发于微信公众号(红队蓝军):VEH&SEH异常详解

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月12日23:42:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   VEH&SEH异常详解http://cn-sec.com/archives/997683.html

发表评论

匿名网友 填写信息