(笔记)逆向工程核心原理——SEH

整理自《逆向工程核心原理》相关章节,windows异常机制除了SEH外还有VEH,两者粒度不同,异常接管顺序不同,后续进一步整理VEH的

查看结构体可以用WinDbg

例如:`dt _PEB`、`dt _TEB` ## 简介 Windows的异常处理机制。在程序源代码中用`__try`、`__except`、`__finally`关键字实现。 与C++中的`try`、`catch`处异常处理具有不同的结构。SEH是一种从属于VC++开发工具和Windows操作系统的异常处理机制。

应用场景

使用SEH进行反调试

基本概念

1. 异常处理过程

  • 进程运行中如果发生异常,OS会委托进程处理。若进程代码中存在具体的异常处理(SEH异常处理器),则能够顺利处理异常,程序继续运行。否则OS就会启动默认的异常处理机制,终止程序运行
  • 在调试运行时,处理方法不同。被调试进程内部发生的一场首先会由OS抛给调试进程处理(被调试者的SEH依据优先顺序推给调试器)。调试器具有被调试者的所有权限(运行、终止、虚拟内存、寄存器的读写权限)。被调试者发生异常时,调试器会暂停运行,采取某种措施处理异常后继续调试。

    常用的处理方法为:

    • 直接修改异常:代码、寄存器、内存
    • 将异常抛给被调试者处理
    • OS默认的异常处理机制

2. 最常见的OS异常

EXCEPTION_DATATYPE_MISALIGNMENT		(0X80000002)
EXCEPTION_BREAKPOINT				(0X80000003)
EXCEPTION_SINGLE_STEP				(0x80000004)
EXCEPTION_ACCESS_VIOLATION			(0xC0000005)
EXCEPTION_IN_PAGE_ERROR				(0xC0000006)
EXCEPTION_ILLEGAL_INSTRUCTION		(0xC000001D)
EXCEPTION_NONCONTINUABLE_EXCEPTION 	(0xC0000025)
EXCEPTION_INVALID_DISPOSITION		(0xC0000026)
EXCEPTION_ARRAY_BOUNDS_EXCEEDED		(0xC000008C)
EXCEPTION_FLT_DENORMAL_OPERAND		(0xC000008D)
EXCEPTION_FLT_DIVIDE_BYZERO			(0xC000008E)
EXCEPTION_FLT_INEXACT_RESULT		(0xC000008F)
EXCEPTION_FLT_INVALID_OPERATION		(0xC0000090)
EXCEPTION_FLT_OVERFLOW				(0xC0000091)
EXCEPTION_FLT_STACK_CHECK			(0xC0000092)
EXCEPTION_FLT_UNDERFLOW				(0xC0000093)
EXCEPTION_INT_DIVIDE_BY_ZERO		(0xC0000094)
EXCEPTION_INT_OVERFLOW				(0xC0000095)
EXCEPTION_PRIV_INSTRUCTION			(0xC0000096)
EXCEPTION_STACK_OVERFLOW			(0xC00000FD)
  • EXCEPTION_ACCESS_VIOLATION(C0000005):非法访问异常,试图访问不存在或不具访问权限的内存区域。

  • EXCEPTION_BREAKPOINT(80000003):运行的代码被设置了断点之后,CPU尝试执行该地址处的指令时,就会发生EXCEPTION_BREAKPOINT异常。原理是,设置断点会将该处的指令修改为0xCC,但Ollydbg不会将临时断点的实际指令显示出来,可以使用PE Tools工具转储进程内存之后,使用Hex Editor打开文件,即可看到真是指令。

    如果注册表将默认调试器设置为Ollydbg,那么发生异常时,操作系统会自动运行Ollydbg,附加发生异常的进程

  • EXCEPTION_ILLEGAL_INSTRUCTION(C000001D):CPU遇到无法解析的指令时引发该异常。

  • EXCEPTION_INT_DIVIDE_BY_ZERO(C0000094):除法运算中若分母为零就会发生除零异常。程序中可能出现的情况是分母为变量,变量在某个时刻变为0,再执行除法运算时就会出现异常。

  • EXCEPTION_SINGLE_STEP(80000004):将EFLAGS寄存器的第八位TF(Trap Flag陷阱标志)设置为1之后,CPU就会进入单步工作模式。每执行一条指令之后,就会暂停,抛出异常。

3. SEH链和相关的结构体

  • SEH以链的形式存在(由_EXCEPTION_REGISTRATION_RECORD结构体组成的链表),第一个异常处理器如果未处理相关异常,它就会被传递到下一个异常处理器,直到得到处理。

    typedef struct _EXCEPTION_REGISTRATION_RECORD
    {
    	PEXCEPTION_REGISTRATION_RECORD Next;
    	PEXCEPTION_DISPOSITION Handler;
    } EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
    //链表以Next成员为FFFFFFFF的结构体结束,表示链表的最后一个结点
    //异常顺着链传递,直到有异常处理器处理
    
  • 异常处理函数的定义:

    EXCEPTION_DISPOSITION _except_handler
    (
    	EXCEPTION_RECORD *pRecord,
    	EXCEPTION_REGISTRATIOIN_RECORD *pFrame,
    	CONTEXT *pContext,
    	PVOID pValue
    );
    

    函数接收四个参数,返回EXCEPTION_DISPOSITION枚举类型。异常处理函数是一个回调函数,系统调用它时会传入四个参数,参数保存着异常相关的信息
    第一个参数:

    typedef struct _EXCEPTION_RECORD {
    	DWORD ExceptionCode;						//异常代码
    	DWORD ExceptionFlags;
    	struct _EXCEPTION_RECORD * ExceptionRecord;	
    	PVOID ExceptionAddress;						//异常发生的地址
    	DWORD NumberParameters;
    	ULONG_PTR ExceptionInformation[EXCEPTION_AMXIMUM_PARAMETERS];//15
    } EXCEPTION_RECORD, *PEXCEPTION_RECORD
    

    重要成员为ExceptionCodeExceptionAddress

    第三个参数:

    struct CONTEXT
    {
    	DWORD ContextFlags;
    	DWORD Dr0;
    	DWORD Dr1;
    	DWORD Dr2;
    	DWORD Dr3;
    	DWORD Dr6;
    	DWORD Dr7;
    	FLOATING_SAVE_AREA FloatSave;
    	DWORD SegGs;
    	DWORD SegFs;
    	DWORD SegEs;
    	DWORD SegDs;
    	DWORD Edi;
    	DWORD Esi;
    	DWORD Ebx;
    	DWORD Edx;
    	DWORD Ecx;
    	DWORD Eax;
    	DWORD Ebp;
    	DWORD Eip;
    	DWORD SegCs;
    	DWORD EFlags;
    	DWORD Esp;
    	DWORD SegSs;
    	BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];//512
    }
    

    CONTEXT结构体用来备份CPU寄存器的值,为的是多线程环境中在线程切换时,CPU寄存器的值就会被保存到CONTEXT以便下次从之前暂停的状态继续执行。
    重要成员为Eip成员,在异常处理器中可以修改为其他地址,暂停的线程就会执行新设置的Eip地址处的代码。

    返回值:

    typedef enum _EXCEPTION_DISPOSITION
    {
    	ExceptionContinueExecution = 0,	//继续执行异常代码
    	ExceptionContinueSearch = 1,	//运行下一个异常处理器
    	ExceptionNestedException = 2,	//在OS内部使用
    	ExceptionCollidedUnwind = 3		//在OS内部使用
    } EXCEPTION_DISPOSITION;
    

    异常处理器处理异常后会返回ExceptionContinueExecution(0),从发生异常的代码出继续运行。若无法处理异常,则返回ExceptionContinueSearch(1),将一场派送到SEH链的下一个异常处理器。

  • 访问进程的SEH链的方法——通过TEB结构体的NtTib成员
    TEB.NtTib.ExceptionList = FS:[0]

  • SEH的安装方法

    在C语言中,使用__try__except__finally关键字可以向代码添加SEH。

    汇编

    PUSH @MyHandler
    PUSH DWORD PTR FS:[0]
    MOV DWORD PTR FS:[0], ESP
    

    原理是FS:[0]保存了SEH链第一个_EXCEPTION_REGISTRATION_RECORD结构体的地址(FS:[0]存储了TEB结构体,TEB结构体第一个成员是NtTibNtTib第一个成员是ExceptionListExceptionList_EXCEPTION_REGISTRATION_RECORD结构体组成的链表)。
    先将自定义的异常处理器压入栈中,然后将FS:[0]压入栈中,再结合栈空间向低地址增长的特点,栈中保存的两个地址即可形成一个新的_EXCEPTION_REGISTRATION_RECORD结构体,分别对应了Next成员和Handler成员。
    将栈顶地址存回到FS:[0]之后,此时的SEH链第一个异常处理器即自定义的异常处理器,第二个异常处理器是原SEH链的第一个异常处理器(由VC++等编译器生成PE文件时默认添加到其启动函数的),最后一个_EXCEPTION_REGISTRATION_RECORD结构体的Next成员为FFFFFFFF,Handler成员处于内核空间,是OS的默认异常处理器(创建进程时,OS自动产生默认的SEH)。

利用参数,编写SEH回调函数

在回调函数设置断点之后,系统调用回调函数是,会将四个参数放在栈中。分别是
[ESP+4]:指向EXCEPTION_RECORD,重点成员ExceptionCodeExceptionAddress
[ESP+8]:指向EXCEPTION_REGISTRATION_RECORD,SEH链的起始地址。
[ESP+C]:指向CONTEXT结构体,重点成员为偏移为B8处的Eip寄存器的值。
[ESP+10]

其他细节

  • 当进程中发生异常时,若SEH未处理注册的SEH不存在。此时会调用系统的kernel32!UnhandledExceptionFIlter()API。
  • 该API会运行系统的最后一个异常处理器——Top Level Exception Filter或Last Exception Filter通常行为是弹出错误消息框、终止进程)。
  • kernel32!UnhandledExceptionFilter()调用了ntdll!QueryInformationProcess(ProcessDebugPort)。来判断是否正在调试进程。如果正在进行调试,则将异常传递给调试器。否则系统异常处理器终止进程。
  • 通过kernel32!SetUnhandledExceptionFilter()可以修改系统最后的异常处理器。函数定义如下:
    //返回的是上一个Top Level Exception Filter的地址,方便恢复
    LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
    	__in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
    );
    
  • Top Level Exception Filter的函数定义:
    typedef struct _EXCEPTION_POINTERS{
    	PEXCEPTION_RECORD ExceptionRecord;
    	PCONTEXT ContextRecord;
    } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
    
    LONG TopLevelExceptionFilter(
    	PEXCEPTION_POINTER pExcept;
    );
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值