反调试——自定义异常处理器并添加调试器检测

相当于对于前面的博客:https://www.cnblogs.com/Sna1lGo/p/14732048.html

自己写代码来实现异常处理。

异常处理机制:

出现异常,首先看有没有调试器有的话交给调试器处理,没有就传一堆信息给异常函数来处理

异常处理函数原型:

EXCEPTION_DISPOSITION myExceptHandler(
struct _EXCEPTION_RECORD* ExceptionRecord,
PVOID EstablisherFrame,
PCONTEXT pcontext,
PVOID DispatcherContext
)
{
}

返回值

该函数的类型返回值是一个枚举类型

typedef enum _EXCEPTION_DISPOSITION
{
  ExceptionContinueExecution,//继续执行
  ExceptionContinueSearch, //搜索下一个异常处理器
  ExceptionNestedException,
  ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;

参数:

    struct _EXCEPTION_RECORD* ExceptionRecord,//异常的记录
PVOID EstablisherFrame,
PCONTEXT pcontext,
PVOID DispatcherContext

重点关注pcontext参数

pcontext

查看PCONTEXT定义:

typedef CONTEXT *PCONTEXT;

继续查看CONTEXT定义:

typedef struct DECLSPEC_NOINITALL _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;

这里的注释表明了,该pcontext参数保存的是每个寄存器的内容,包括各种各样的寄存器。

例子:

#include<Windows.h>
#include<iostream>
EXCEPTION_DISPOSITION myExceptHandler(
struct _EXCEPTION_RECORD* ExceptionRecord,
PVOID EstablisherFrame,
PCONTEXT pcontext,
PVOID DispatcherContext
)
{

MessageBoxA(0, "出现异常了", "异常报错", MB_OK);
return ExceptionContinueExecution;
}
int main()
{
DWORD execptionFuncAddr = (DWORD)myExceptHandler;//异常处理的函数地址
__asm
{
push execptionFuncAddr;
mov eax, fs: [0] ;
push eax;
mov fs : [0] , esp;
//添加异常,并且把异常放到异常链里面,并且把异常的头指针指向新的头结点
}
char* str = NULL;
str[0] = 'a';
printf("Sna1lGo\n");
system("pause");
return 0;
}

这个程序按理说,我们是手动把异常处理函数添加进SEH链了,然后也触发异常了,但是程序却跑不起来。原因是VS2019的有一个定义没有关闭

 

 

把这里的3关闭掉就好了。

 

 

然后再调用就可以了。

分析一下例子的流程:

操作系统在执行到有问题的语句的时候,就会在发生异常的位置的一些环境保存下来,然后传给异常处理的回调函数,再处理完之后再回到异常的位置继续执行,但是我们这个例子的异常处理器里面并没有解决这个异常,而只是弹出一个框而已,所以就会一直重复该操作。

例子修改

通过修改EIP,来跳过有异常的代码位置:在异常处理函数里面添加指令使得跳过使程序发生异常的指令,这里有一个str[0] = 'a'才导致的异常,通过od反调试可以知道str[0]='a'这条指令在硬编码中占4个字节,所以只需要将eip的地址+4就好了,跳过该有异常的指令就好了。

#include<Windows.h>
#include<iostream>
EXCEPTION_DISPOSITION myExceptHandler(
struct _EXCEPTION_RECORD* ExceptionRecord,
PVOID EstablisherFrame,
PCONTEXT pcontext,
PVOID DispatcherContext
)
{

MessageBoxA(0, "出现异常了", "异常报错", MB_OK);
pcontext->Eip += 4;
return ExceptionContinueExecution;
}
int main()
{
DWORD execptionFuncAddr = (DWORD)myExceptHandler;//异常处理的函数地址
__asm
{
push execptionFuncAddr;
mov eax, fs: [0] ;
push eax;
mov fs : [0] , esp;
//添加异常,并且把异常放到异常链里面,并且把异常的头指针指向新的头结点
}
char* str = NULL;
str[0] = 'a';
printf("Sna1lGo\n");
system("pause");
return 0;
}

这样就好了,异常处理函数里面跳过了异常的指令,并且返回值是ExceptionContinueExecution,表示继续执行

添加调试器检测处理

因为调试器添加断点的原理就是通过异常处理,如果我们在异常处理里面添加一个验证,来验证有没有调试器在调试该程序的话,那么就可以防止被调试了。

在teb-peb核心模块中的peb进程环境块里面,具体可以查看该博客:

https://www.cnblogs.com/Sna1lGo/p/14590794.html

在peb进程核心模块里面,有一个字段是专门用来表示是否被调试器所调试,如果为1就表示1,如果为0就表示不是。

#include<Windows.h>
#include<iostream>
EXCEPTION_DISPOSITION myExceptHandler(
struct _EXCEPTION_RECORD* ExceptionRecord,
PVOID EstablisherFrame,
PCONTEXT pcontext,
PVOID DispatcherContext
)
{

MessageBoxA(0, "出现异常了", "异常报错", MB_OK);

DWORD IsDebugerFlag = 0;
__asm
{
mov eax, fs: [0x18] ;//teb模块
mov eax, [eax + 0x30];//peb模块
movzx eax, byte ptr[eax + 2];//这里有一个标志位表示是否在被调试
mov IsDebugerFlag, eax;
}
if (IsDebugerFlag)
{
MessageBoxA(0, "检查到调试器", "警告", MB_OK);
}
exit(0);

pcontext->Eip = pcontext->Eip+ 4;
return ExceptionContinueExecution;
}
int main()
{
DWORD execptionFuncAddr = (DWORD)myExceptHandler;//异常处理的函数地址
__asm
{
push execptionFuncAddr;
mov eax, fs: [0] ;
push eax;
mov fs : [0] , esp;
//添加异常,并且把异常放到异常链里面,并且把异常的头指针指向新的头结点
}
char* str = NULL;
str[0] = 'a';
printf("Sna1lGo\n");
system("pause");
return 0;
}

这样处理之后,如果该程序再被调试就会报错并退出

 

 

总结

异常处理机制很重要,可以通过利用该机制来限制程序被别人调试,例子采用的是触发一个调试器捕获不了的异常,然后再在异常处理函数里面验证是否被调试,如果被调试就退出这样来实现。