Reverse:SEH

1. 什么是SEH

程序中可能包含有 SEH 链处理机制,在 Window 中异常处理是通过 SEH 来实现的,相关的资料描述可以查看这些文档:Windows SEHIDA Manual about SEH 中较为详细的描述,SEHWindows 系统的底层异常处理方式,C++中的异常处理形式的实现是依赖于这套机制的 in Windows(from IDA Manual about SEH)。

在编写 C/C++ 源码时的应用层面很简单,只需要使用如下结构就可以实现:

int main()
{
    __try
    {
        TestExceptions();
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        printf("Executing SEH __except block\n");
    }

    return 0;
}

那么可以这么简单的理解异常处理就是 __try__except 语句构成的模块(C++中是trycatch)。

2.逆向层面的SEH

2.1 SEH

2.1.1 SEH数据结构

  1. 首先来看一下相关的数据结构,感谢 Reverse SEH 提供的相应结构体,我这里就直接引用了。

异常处理函数 _except_handler3 是一个经常使用的 SEH 函数,用来处理 SEH3 相关的内容。它的参数只有 arg0agr1 是有效的,后面两个参数是无效参数

int _except_handler3(
   PEXCEPTION_RECORD exception_record,
   PEXCEPTION_REGISTRATION registration,
   PCONTEXT context,
   PEXCEPTION_REGISTRATION dispatcher
);

_EXCEPTION_RECORD 是传入的第0个参数,较为有用的是两个成员,ExceptionCodeExceptionAddress 其余成员在逆向过程中的价值不大。

typedef struct _EXCEPTION_RECORD {
    DWORD ExceptionCode;   //exception code
    DWORD ExceptionFlags;
    struct _EXCEPTION_RECORD *ExceptionRecord;
    PVOID ExceptionAddress;   // exception address
    DWORD NumberParameters;
    ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

_EXCEPTION_REGISTRATION_RECORD 是传入的第1个参数,实际使用中它的大小是不确定的,可能会发生变化,但最开始的两个成员是确定的,分别为 NextHandler 记录着下一个 node 的位置和当前异常的处理函数。

struct _EXCEPTION_REGISTRATION_RECORD
   _EXCEPTION_REGISTRATION_RECORD* Next
   _EXCEPTION_DISPOSITION* Handler
   ...
}

2.1.2 SEH注册

SEH 是一个链表,这个链表的的结构日下图所示:
在这里插入图片描述
第一个字段是 Next 指针,第二字段则是异常处理函数。而链表的起始位置记录在了 fs 段寄存器的第一个字段,所以一个添加 SEH 常规操作就是下面所示的汇编代码。

push    offset sub_403E53	;exception handler function
mov     eax, large fs:0		;get the list header
push    eax					;save the Next node to the Next member
mov     large fs:0, esp ;	;change the header to current

上面的操作过程就是链表的头插法,而这里的 header 就是 fs:0,首先在栈上创建一个 node 并将 Next 字段的值进行设置,记录上一个的位置,最后将 fs:0 中的值修改。

2.2 __SEH_prolog

A Reverse ExampleReverse SEH 对于 SEH3 有一些描述,我自己对其进行了一些参考。首先要说明的是 __SEH_prolog 不是一个能够被用户调用的 API 函数,它是在使用了__try__except 语句之后由 Compiler 生成的,旨在实现该 Statement 功能的函数。

逆向过程中这个函数就变得可见了,IDA对其的命名是 __SEH_prolog 这个是注册函数,对应还有注销函数 __SEH_epilog,最主要的是识别注册函数,这个注销函数我就不进行分析了。

2.2.1 函数声明

下面是 __SEH_prolog 的函数接口在 IDA 中的体现。他具有两个参数,参数0是一个异常处理的 ScopeTable 参数1是分配栈的大小。
在这里插入图片描述
那么可以从上面的截图中推导出 __SEH_prolog 的函数接口方式。

void __SEH_prolog(_SCOPETABLE_ENTRY* scopetable, unsigned int StackSize);

2.2.2 函数定义

__SEH_prolog 的函数体在 IDA 是这样显示的,它首先完成对于 SEH 节点的添加,该节点的异常处理函数是 _except_handler3,然后按照第1个参数的大小分配数据栈。
在这里插入图片描述

2.2.3 SEH3运行流程

认识流程之前我们先来看一下 _SCOPETABLE_ENTRY 这个结构体。它的定义如下:

struct _SCOPETABLE_ENTRY {
	DWORD EnclosingLevel;
	PVOID FilterFunc;
	PVOID HandlerFunc;
}

主要关注它的第三个成员 HandlerFunc 这个是在调用 _except_handler3 后会跳转到的位置。

异常触发之后的运行流程是这样的:

  1. 首先会进入调用 SEH 中记录中的第一个异常处理函数,在 SEH3 中的就是在函数开头通过 __SEH_prolog 函数注册的 _except_handler3 函数。
  2. _except_handler3 中的第一个参数 会传入一个 _EXCEPTION_REGISTRATION_RECORD 的结构体指针。
struct _EXCEPTION_REGISTRATION_RECORD {
   struct _EXCEPTION_REGISTRATION_RECORD* Next;
   struct _EXCEPTION_DISPOSITION* Handler;
   struct _SCOPETABLE_ENTRY* ScopeTable;
}
  1. 最后经过 _except_handler3 函数后会跳转到 ScopeTable->HandlerFunc 处重新运行。需要注意的是:ScopeTable 所在的内存区域必须是 read only

3.逆向层面的EH

前面介绍的 SEH 对应的是 Windows__try__except,而这里将记录下的是 C++trycatch

3.1 EH数据结构

In EH 主要涉及到两个数据结构 FuncInfoV1UnwindMapEntry

1.在 FuncInfoV1 中我们主要关注两个成员 maxStatepUnwindMap

struct FuncInfoV1 {
  int magicNumber;
  int maxState;
  void *pUnwindMap;
  int nTryBlocks;
  void *pTryBlockMap;
  int nIPMapEntries;
  void *pIPtoStateMap;
};

  • maxState: 最大的状态数量。也就是该 EH 中的 UnwindMapEntry数组中元素的个数。
  • pUnwindMap:指向 UnwindMapEntry 结构体数组的指针。

在这里插入图片描述

2.在 UnwindMapEntry 中有两个成员 toStateaction 这两个都是我们需要关注的。

struct UnwindMapEntry {
  int toState;
  void *action;
};

  • toState:下一个状态的序号,这个就是数组中的下标,当其为 -1 表示处理结束没有下一个阶段。
  • action:处理的 Code,将该异常进行处理的代码。

在这里插入图片描述

3.2 EH 注册

EH 的注册流程是将处理函数的一部分作为内容处理函数进行注册,也就是上图中的跳转到 __CxxFrameHandler 函数的那部分代码。
在这里插入图片描述
而在 __EH_prolog 内部则是直接将存入到 eax 中的地址作为 SEH 链的一部分进行注册。
在这里插入图片描述

3.3 EH 运行流程

When 程序发生异常时,通过 SEH 链 will jump 到 address,which 传递一个 FuncInfoV1 结构体后 will 跳转到 __CxxFrameHandler 函数执行。
在这里插入图片描述
该函数的执行流程大致如下图所示:
在这里插入图片描述
通过 pUnwindMap 指针获取到相应的 UnwindMapEntry 数组。然后按照状态表依次执行。完成异常处理。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码是一个 Linux 驱动程序中的函数,用于控制嵌入式系统中的 LED 灯的亮灭。以下是代码注释: ```c // 函数名:keyled_ioctl // 参数:file - 文件指针,cmd - 控制命令,arg - 参数 long keyled_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { unsigned int regVal; // 定义一个无符号整型变量 regVal switch(cmd) { // 根据控制命令进行不同的操作 case LED1_REVERSE: // 如果控制命令为 LED1_REVERSE regVal = ioread32(gpj2dat); // 从 gpj2dat 寄存器中读取当前值 regVal ^= (0x1<<0); // 对第 0 位进行异或操作,即将 LED1 灯的状态取反 iowrite32(regVal, gpj2dat); // 将新的值写入 gpj2dat 寄存器中 break; case LED2_REVERSE: // 如果控制命令为 LED2_REVERSE regVal = ioread32(gpj2dat); // 从 gpj2dat 寄存器中读取当前值 regVal ^= (0x1<<1); // 对第 1 位进行异或操作,即将 LED2 灯的状态取反 iowrite32(regVal, gpj2dat); // 将新的值写入 gpj2dat 寄存器中 break; case LED3_REVERSE: // 如果控制命令为 LED3_REVERSE regVal = ioread32(gpj2dat); // 从 gpj2dat 寄存器中读取当前值 regVal |= 0x0f; // 将低四位设置为全 1,即将 LED3、4 灯都点亮 iowrite32(regVal, gpj2dat); // 将新的值写入 gpj2dat 寄存器中 break; case LED4_REVERSE: // 如果控制命令为 LED4_REVERSE regVal = ioread32(gpj2dat); // 从 gpj2dat 寄存器中读取当前值 regVal &= 0xf0; // 将低四位设置为全 0,即将 LED1、2 灯都熄灭 iowrite32(regVal, gpj2dat); // 将新的值写入 gpj2dat 寄存器中 break; } return 0; // 返回操作结果 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值