反调试——自定义异常处理器并添加调试器检测
相当于对于前面的博客: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;
}
这样处理之后,如果该程序再被调试就会报错并退出
总结