SEH

runwu2204 Lv6

参考第64章 传递参数的方法 - 68.3 Windows SEH - 《逆向工程权威指南》 - 书栈网 · BookStack

Windows-SEH学习笔记(2) - 云之君’s Blog (yunzh1jun.com)

windows SEH分析 - 知乎 (zhihu.com)

基本结构为

1
2
3
4
5
6
7
8
__try
{
//普通代码区域
}
__except(filter)//filter表示不同的处理形式
{
//发生异常后进行处理
}

__try 子句后的复合语句是主体或受保护节。 __except 表达式也称为筛选表达式。 它的值确定了异常的处理方式。 __except 子句后的复合语句是异常处理程序。 处理程序指定在执行主体期间引发异常时要采取的操作。 执行过程如下所示:

  1. 执行受保护节(try语句)。
  2. 如果在受保护节执行过程中未发生异常,则继续执行 __except 子句之后的语句。
  3. 如果在受保护节的执行过程中或受保护节调用的任何例程中发生异常,则会计算 __except 表达式。 有三种可能的值:
    • EXCEPTION_CONTINUE_EXECUTION (-1) 异常已消除。 从出现异常的点继续执行。
    • EXCEPTION_CONTINUE_SEARCH (0) 无法识别异常。 继续向上搜索堆栈查找处理程序,首先是所在的 try-except 语句,然后是具有下一个最高优先级的处理程序。
    • EXCEPTION_EXECUTE_HANDLER (1) 异常可识别。 通过执行 __except 复合语句将控制权转移到异常处理程序,然后在 __except 块后继续执行。

在ida中filter作为函数指针或函数地址存在,(32位中)eax作为其返回值对filter进行操纵

例如

1
2
3
4
5
6
7
.text:004179E3 ;   __except filter // owned by 4179AC ; EXCEPTION_EXECUTE_HANDLER
.text:004179E3 mov eax, 1 ; 从异常发生处下一条开始执行
.text:004179E8 retn ; Return Near from Procedure
----------------
.text:004179E9 ; __except(loc_4179E3) // owned by 4179AC
.text:004179E9 mov esp, [ebp+ms_exc.old_esp]
.text:004179EC jmp far ptr loc_4142A7 ; Jump

这里的except的filter恒为1所以处理完即跳转

重要变量

[ebp+ms_exc.registration.TryLevel] 用于设置try等级,一般用于多层try嵌套情况,跳转到对应的except,如果值为-1表示不开启或者try域结束

[ebp+ExceptionNum] 异常标识符,用于标识不同异常

FS寄存器

fs:[0]

此处存放SEH链表,用于进行异常处理,当第一个不行就会跳转到next的第二个

1
2
3
4
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
}

出现异常就会遍历这个SEH链表,直到

fs:[0x18]

TEB(Thread environment Block)起始地址

fs:[0x30]

PEB(Process environment Block)起始地址

基本处理流程

example

DubheCTF2024-Destination

image-20240320202054288

通过frida插桩

1
2
3
4
5
6
except=function(addr,str1){Interceptor.attach(ptr(addr),{onEnter(args){console.log("\n"+str1+":"
+JSON.stringify(this.context))}})}
except(0x4140D7,"reg_excption")// try语句内注册的exception
except(0x4179E9,"old_excption")// 原有的except语句
except(0x4179E3,"filter")//filter 语句
except(0x4166BD,"my_except")//自己插入的恒返回1的exception处理函数

保留注册操作

image-20240322012221404

删去注册操作

image-20240321181600029

输出为

image-20240322011210069

说明seh链表中只有old_exception

手动注册逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<stdio.h>
#include<stdlib.h>
#include<tchar.h>
#include <Windows.h>

int __stdcall CatchProc(EXCEPTION_RECORD* pExcept, void* pErr, void* pContext, void* p_Dis)
{
// if (pExcept->ExceptionCode == EXCEPTION_BREAKPOINT)
// {
// printf("BREAKPOINT catched");
// return 0;
// }
printf("catched 0x%08X\n", pExcept->ExceptionCode);
return 1;
}


bool __stdcall SetCatchProc(void* pCProc)
{
char* p_ExpProcEntry;
__asm
{
mov eax, fs: [0]
add eax, 4
mov eax, dword ptr[eax]
mov p_ExpProcEntry, eax
}
DWORD p_oldProtect = 0;
if (VirtualProtect(p_ExpProcEntry, 5, PAGE_EXECUTE_READWRITE, &p_oldProtect))
{
*(BYTE*)p_ExpProcEntry = 0xE9;
*(DWORD*)(p_ExpProcEntry + 1) = (char*)pCProc - p_ExpProcEntry - 5;
return true;
}
return false;
}
int _tmain(int argc, _TCHAR* argv[])
{
SetCatchProc(CatchProc);
int i = 0;
int sum = 5 / i;
char p1[255];
gets_s(p1);
return 0;
}

它的输出就是

image-20240715151146044

第一次是触发的除零异常,第二次则是在展开(unwind)时用于清理处理异常的一些参数

正常的异常处理会对这两个进行判断,而我们手动注册的没有对这两个进行判断,就相当于执行了两次

异常代码 异常的原因 CODE
STATUS_ACCESS_VIOLATION 读取或写入不可访问的内存位置。
STATUS_BREAKPOINT 遇到硬件定义的断点;仅由调试器使用。
STATUS_DATATYPE_MISALIGNMENT 在没有正确对齐的地址上读取或写入数据;例如,16 位实体必须在 2 字节边界对齐。 (不适用于 Intel 80 x 86 处理器。)
STATUS_FLOAT_DIVIDE_BY_ZERO 将浮点类型除以 0.0。
STATUS_FLOAT_OVERFLOW 超过浮点类型的最大正指数。
STATUS_FLOAT_UNDERFLOW 超过浮点类型的最小负指数的大小。
STATUS_FLOATING_RESEVERED_OPERAND 使用保留的浮点格式(无效的使用格式)。
STATUS_ILLEGAL_INSTRUCTION 尝试执行处理器未定义的指令代码。
STATUS_PRIVILEGED_INSTRUCTION 执行当前计算机模式下不允许的指令。
STATUS_INTEGER_DIVIDE_BY_ZERO 将整数类型除以 0。
STATUS_INTEGER_OVERFLOW 尝试超出整数的范围的操作。
STATUS_SINGLE_STEP 以单步模式执行一条指令;仅由调试器使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#ifndef WIN32_NO_STATUS 
/*lint -save -e767 */
#define STATUS_WAIT_0 ((DWORD )0x00000000L)
#define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L)
#define STATUS_USER_APC ((DWORD )0x000000C0L)
#define STATUS_TIMEOUT ((DWORD )0x00000102L)
#define STATUS_PENDING ((DWORD )0x00000103L)
#define DBG_EXCEPTION_HANDLED ((DWORD )0x00010001L)
#define DBG_CONTINUE ((DWORD )0x00010002L)
#define STATUS_SEGMENT_NOTIFICATION ((DWORD )0x40000005L)
#define STATUS_FATAL_APP_EXIT ((DWORD )0x40000015L)
#define DBG_REPLY_LATER ((DWORD )0x40010001L)
#define DBG_TERMINATE_THREAD ((DWORD )0x40010003L)
#define DBG_TERMINATE_PROCESS ((DWORD )0x40010004L)
#define DBG_CONTROL_C ((DWORD )0x40010005L)
#define DBG_PRINTEXCEPTION_C ((DWORD )0x40010006L)
#define DBG_RIPEXCEPTION ((DWORD )0x40010007L)
#define DBG_CONTROL_BREAK ((DWORD )0x40010008L)
#define DBG_COMMAND_EXCEPTION ((DWORD )0x40010009L)
#define DBG_PRINTEXCEPTION_WIDE_C ((DWORD )0x4001000AL)
#define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L)
#define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L)
#define STATUS_BREAKPOINT ((DWORD )0x80000003L)
#define STATUS_SINGLE_STEP ((DWORD )0x80000004L)
#define STATUS_LONGJUMP ((DWORD )0x80000026L)
#define STATUS_UNWIND_CONSOLIDATE ((DWORD )0x80000029L)
#define DBG_EXCEPTION_NOT_HANDLED ((DWORD )0x80010001L)
#define STATUS_ACCESS_VIOLATION ((DWORD )0xC0000005L)
#define STATUS_IN_PAGE_ERROR ((DWORD )0xC0000006L)
#define STATUS_INVALID_HANDLE ((DWORD )0xC0000008L)
#define STATUS_INVALID_PARAMETER ((DWORD )0xC000000DL)
#define STATUS_NO_MEMORY ((DWORD )0xC0000017L)
#define STATUS_ILLEGAL_INSTRUCTION ((DWORD )0xC000001DL)
#define STATUS_NONCONTINUABLE_EXCEPTION ((DWORD )0xC0000025L)
#define STATUS_INVALID_DISPOSITION ((DWORD )0xC0000026L)
#define STATUS_ARRAY_BOUNDS_EXCEEDED ((DWORD )0xC000008CL)
#define STATUS_FLOAT_DENORMAL_OPERAND ((DWORD )0xC000008DL)
#define STATUS_FLOAT_DIVIDE_BY_ZERO ((DWORD )0xC000008EL)
#define STATUS_FLOAT_INEXACT_RESULT ((DWORD )0xC000008FL)
#define STATUS_FLOAT_INVALID_OPERATION ((DWORD )0xC0000090L)
#define STATUS_FLOAT_OVERFLOW ((DWORD )0xC0000091L)
#define STATUS_FLOAT_STACK_CHECK ((DWORD )0xC0000092L)
#define STATUS_FLOAT_UNDERFLOW ((DWORD )0xC0000093L)
#define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L)
#define STATUS_INTEGER_OVERFLOW ((DWORD )0xC0000095L)
#define STATUS_PRIVILEGED_INSTRUCTION ((DWORD )0xC0000096L)
#define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL)
#define STATUS_DLL_NOT_FOUND ((DWORD )0xC0000135L)
#define STATUS_ORDINAL_NOT_FOUND ((DWORD )0xC0000138L)
#define STATUS_ENTRYPOINT_NOT_FOUND ((DWORD )0xC0000139L)
#define STATUS_CONTROL_C_EXIT ((DWORD )0xC000013AL)
#define STATUS_DLL_INIT_FAILED ((DWORD )0xC0000142L)
#define STATUS_CONTROL_STACK_VIOLATION ((DWORD )0xC00001B2L)
#define STATUS_FLOAT_MULTIPLE_FAULTS ((DWORD )0xC00002B4L)
#define STATUS_FLOAT_MULTIPLE_TRAPS ((DWORD )0xC00002B5L)
#define STATUS_REG_NAT_CONSUMPTION ((DWORD )0xC00002C9L)
#define STATUS_HEAP_CORRUPTION ((DWORD )0xC0000374L)
#define STATUS_STACK_BUFFER_OVERRUN ((DWORD )0xC0000409L)
#define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD )0xC0000417L)
#define STATUS_ASSERTION_FAILURE ((DWORD )0xC0000420L)
#define STATUS_ENCLAVE_VIOLATION ((DWORD )0xC00004A2L)
#define STATUS_INTERRUPTED ((DWORD )0xC0000515L)
#define STATUS_THREAD_NOT_RUNNING ((DWORD )0xC0000516L)
#define STATUS_ALREADY_REGISTERED ((DWORD )0xC0000718L)
#if defined(STATUS_SUCCESS) || (_WIN32_WINNT > 0x0500) || (_WIN32_FUSION >= 0x0100)
#define STATUS_SXS_EARLY_DEACTIVATION ((DWORD )0xC015000FL)
#define STATUS_SXS_INVALID_DEACTIVATION ((DWORD )0xC0150010L)
#endif

写对应的结构可以参考miniLCTF_2021/Challenges/Reverse/0ooooops/源码.cpp at main · XDSEC/miniLCTF_2021 (github.com)

GetExceptionInformation()返回的PEXCEPTION_POINTERS链表存放着异常处的很多信息

  • 标题: SEH
  • 作者: runwu2204
  • 创建于 : 2024-03-17 16:24:25
  • 更新于 : 2024-07-31 01:41:37
  • 链接: https://runwu2204.github.io/2024/03/17/Re/windows/pe/SEH/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论