SEH及逆向调试

SEH

文章目录

    • SEH
  • 1. 异常处理方法
    • 正常运行的异常处理
    • 调试运行的异常处理
  • 2. 常见异常
    • EXCEPTION_ACCESS_VIOLATION
    • EXCEPTION_BREAKPOINT
    • EXCEPTION_ILLEGAL_INSTRUCTION
    • EXCEPTION_INT_DIVIDE_BY_ZERO
    • EXCEPTION_SINGLE_STEP
  • 3. SEH详细说明
    • 回调函数
      • ExceptionRecord
      • EstablisherFrame
      • ContextRecord
      • 返回一个枚举类型
  • 4. 调试
    • SEH添加和删除
    • EXCEPTION_ACCESS_VIOLATION调试示例
      • 参数一:pExceptionRecord
      • 参数二:pFrame
      • 参数三:pContext
      • 参数四
      • 异常处理如何反调试
  • 5. 设置OD选项
    • 忽略在kernel32中发生的内存非法访问异常
    • 向被调试者传递的异常
    • 忽略其它异常

SEH除了异常处理,还大量用于反调试。

SEH在代码中使用__try, __except, __finally关键字实现。

与c++的异常处理不同,不要混淆。微软SEH要早一些。

1. 异常处理方法

正常运行的异常处理

有处理代码则顺利处理,没有实现SEH则启动默认处理,终止进程。

调试运行的异常处理

调试器几乎拥有被调试者的所有权限,异常也优先由调试器处理。这时调试器会暂停,处理异常后继续调试。

下面是几种处理方法。

  • 调试器直接修改异常:代码、寄存器、内存
  • 抛给被调试者,shift+f7/f8/f9即可
  • 调试器和被调试者都不能处理,则由OS默认方式处理

2. 常见异常

EXCEPTION_RECORD结构体有一个ExceptionCode成员,存有异常种类码值。

下一部分会再说这个结构体,先说下几个重要的ExceptionCode。

EXCEPTION_ACCESS_VIOLATION

#define EXCEPTION_ACCESS_VIOLATION          STATUS_ACCESS_VIOLATION
#define STATUS_ACCESS_VIOLATION          ((DWORD   )0xC0000005L)  

触发:

mov dword ptr ds:[0], 1
add dword ptr ds:[401000], 1
xor dword ptr ds:[8000 0000], 1234h ;kernel address

EXCEPTION_BREAKPOINT

#define EXCEPTION_BREAKPOINT                STATUS_BREAKPOINT
#define STATUS_BREAKPOINT                ((DWORD   )0x80000003L)    

调试器就是利用这个异常实现断点功能的。比如f2设置cc后会触发,但反汇编窗口并不会显示cc,用PETools将进程内存dump后就能看到cc了。

有一个调试方法,在注册表中将默认调试器改为OD,可以把一个目标文件的EP处字节改为cc,一运行就直接attach。

EXCEPTION_ILLEGAL_INSTRUCTION

#define EXCEPTION_ILLEGAL_INSTRUCTION       STATUS_ILLEGAL_INSTRUCTION
#define STATUS_ILLEGAL_INSTRUCTION       ((DWORD   )0xC000001DL)    

遇到无法解析的OPCODE时会触发,比如x86没有0FFF

EXCEPTION_INT_DIVIDE_BY_ZERO

#define EXCEPTION_INT_DIVIDE_BY_ZERO        STATUS_INTEGER_DIVIDE_BY_ZERO
#define STATUS_FLOAT_DIVIDE_BY_ZERO      ((DWORD   )0xC000008EL)    
mov eax, 1
xor ebx, ebx
div ebx

EXCEPTION_SINGLE_STEP

#define EXCEPTION_SINGLE_STEP               STATUS_SINGLE_STEP
#define STATUS_SINGLE_STEP               ((DWORD   )0x80000004L)    

cpu在单步模式下,每执行一条指令就会触发该异常。将EFLAG的TF(trap,陷阱)置1,就进入单步模式。

3. SEH详细说明

SEH以链的形式存在,将异常依次传递,直到处理。

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

若Next为-1,则说明这是链表最后一个节点。

回调函数

回调处理函数原型如下:

typedef
_IRQL_requires_same_
_Function_class_(EXCEPTION_ROUTINE)
EXCEPTION_DISPOSITION
NTAPI
EXCEPTION_ROUTINE (
    _Inout_ struct _EXCEPTION_RECORD *ExceptionRecord,
    _In_ PVOID EstablisherFrame,
    _Inout_ struct _CONTEXT *ContextRecord,
    _In_ PVOID DispatcherContext
    );

typedef EXCEPTION_ROUTINE *PEXCEPTION_ROUTINE;

ExceptionRecord

第一个参数是刚刚整理异常时提到的结构体指针。

//
// Exception record definition.
//

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

typedef EXCEPTION_RECORD *PEXCEPTION_RECORD;

EstablisherFrame

指向第一个节点(低地址)的位置。

ContextRecord

ContextRecord指向Context结构体,用来备份寄存器的值,也就是书上常说的上下文环境。

每个线程都有这么一个结构体。

typedef struct _CONTEXT {

    //
    // The flags values within this flag control the contents of
    // a CONTEXT record.
    //
    // If the context record is used as an input parameter, then
    // for each portion of the context record controlled by a flag
    // whose value is set, it is assumed that that portion of the
    // context record contains valid context. If the context record
    // is being used to modify a threads context, then only that
    // portion of the threads context will be modified.
    //
    // If the context record is used as an IN OUT parameter to capture
    // the context of a thread, then only those portions of the thread's
    // context corresponding to set flags will be returned.
    //
    // The context record is never used as an OUT only parameter.
    //

    DWORD ContextFlags;

    //
    // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    // set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
    // included in CONTEXT_FULL.
    //

    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
    //

    FLOATING_SAVE_AREA FloatSave;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_SEGMENTS.
    //

    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_INTEGER.
    //

    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;

    //
    // This section is specified/returned if the
    // ContextFlags word contians the flag CONTEXT_CONTROL.
    //

    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;              // MUST BE SANITIZED
    DWORD   EFlags;             // MUST BE SANITIZED
    DWORD   Esp;
    DWORD   SegSs;

    //
    // This section is specified/returned if the ContextFlags word
    // contains the flag CONTEXT_EXTENDED_REGISTERS.
    // The format and contexts are processor specific
    //

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];

} CONTEXT;

typedef CONTEXT *PCONTEXT;

返回一个枚举类型

// Exception disposition return values
typedef enum _EXCEPTION_DISPOSITION
{
    ExceptionContinueExecution, 
    ExceptionContinueSearch,
    ExceptionNestedException,
    ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;

从0到3,依次为:
0. 继续执行异常代码;

  1. 传给下一个异常处理器;

2和3是在OS内部使用。

4. 调试

FS寄存器指向当前活动线程的TEB结构,0偏移处就是SEH第一个节点的地址,所以可以这样获得SEH链:

mov eax, fs:[0]

然后可以打开SEH chain窗口对比。

SEH添加和删除

添加:

push @MyHandler
push dword ptr fs:[0]
mov dword ptr fs:[0], esp

删除:

pop dword ptr fs:[0]
add esp, 4

这里不太好理解,调试一下就懂了。

EXCEPTION_ACCESS_VIOLATION调试示例

查看SEH Chain,在第一个节点地址772086D0下断点。

008FF97C    ntdll.772086D0
008FF994    ntdll.772151B2

执行下面的指令:

011B2080 >  33C0            xor     eax, eax
011B2082    C700 01000000   mov     dword ptr [eax], 1

引发中断后,观察栈:

ESP ==>  > 77215062  返回到 ntdll.77215062
ESP+4    > 008FF3F4
ESP+8    > 008FF97C
ESP+C    > 008FF444
ESP+10   > 008FF37C

参数一:pExceptionRecord

和普通函数调用不同,esp+4是第一个参数ExceptionRecord指针。

008FF3F4  05 00 00 C0 00 00 00 00 00 00 00 00 82 20 1B 01  

c0000005就是EXCEPTION_ACCESS_VIOLATION的code值。

011b2082就是异常代码的地址。

参数二:pFrame

008FF97C其实就是上面SEH Chain中的起始地址。

参数三:pContext

特别注意eip,是ContextRecord偏移B8的地方。

008FF4FC  82 20 1B 01

参数四

最后一个参数供系统内部使用,可忽略。

异常处理如何反调试

mov esi, dword ptr ss:[esp+c]
mov eax, dword ptr fs:[30]
cmp byte ptr ds:[eax+2], 1
jnz ...
mov dword ptr ds:[esi + B8], xxxxxxxx
xor eax, eax
retn

伪代码:

esi = &context
eax = &PEB
if(eax.BeingDebugged)
...

next_eip = xxxxxxxx;

return 0;

最后返回的0,就是ExceptionContinueExecution

5. 设置OD选项

下面介绍Debugging options - exception的几个选项。

合理配置可以在不暂停调试器的情况下基于规避SEH实现的反调试招数。

忽略在kernel32中发生的内存非法访问异常

默认选中。也就是说,碰到的时候不会暂停。

向被调试者传递的异常

这些复选框中有:

  • int 3
  • single-step break
  • mem access violation
  • 除0
  • 无效或特权指令
  • All FPU exceptions

最后一个,是Float point unit,浮点运算单元,它有自己的一套指令,与普通x86结构不同。

勾选的异常会传递给被调试者处理。

忽略其它异常

这里添加的异常会发送给被调试者。

你可能感兴趣的:(#,re,windows)