1.基于异常的反调试
(1) 基本原理:
注册SEH后, 正常情况下发生异常会转入SEH处理流程, 但是如果这时处于被调试状态则异常事件会先发给调试器. 基于这个原理就能探测到进程是否是
被调试运行.
(2) 基于int 3 断点异常反调试
故意隔一段代码就调用一个会触发int 3异常的函数, 而且这个函数最后会退出进程或者故意运行到非法地址或者进入一段循环的长的垃圾代码.
如果正在被单步调试, 那么就会直接进入该call中的int 3代码处,继续运行后将会退出,或者一直在垃圾代码处循环.
如果正在被单步步入该函数,结果将和上面差不多
如果正在被调试运行时调用了该函数,将直接断在int 3处, 然后骚扰调试者的进度,最后还是要面对退出或者垃圾代码循环
如果没有被调试, 那么就会转到事先写好的SEH 处理函数中,然后修改eip,继续正常运行.
规避方法: 在调试器中设置忽略int 3断点即可,并且如果是在单步调试时,不能单步步入,而是要单步跳过这个call
测试代码: (debug模式编译后正常,但是release模式编译后运行不正常)
#include "stdio.h" #include "windows.h" #include "tchar.h" void int3AntiDebugger() { __asm { push se_handler push DWORD ptr fs:[0] mov DWORD ptr fs:[0], esp int 3 mov eax, 0 jmp eax se_handler: mov eax, [esp + 0xc] //获取context指针 lea eax, [eax + 0xb8] //获取context.eip地址 mov ebx, normal mov DWORD ptr [eax], ebx xor eax, eax ret normal: pop DWORD ptr fs:[0] add esp, 4 } } int _tmain(int argc, TCHAR* argv[]) { int3AntiDebugger(); return 0; }
(3).利用SetUnhandledExceptionFilter
当进程中发生异常时,如果没有注册任何seh,将会调用kernel32.dll!UnhandledExceptionFilter()函数, 该函数内部会执行系统最后一个异常
处理器,默认这个异常处理器会弹框提示程序已停止工作.
此外,API kernel32.dll!UnhandledExceptionFilter() 内部会调用ntdll!NtQueryInformationProcess来确定是否被调试,如果是则将异常发送给
调试器否则才会调用最后一个异常处理器.
通过调用SetUnhandledExceptionFilter():
LPTOP_LEVEL_EXCEPTION_FILTER
WINAPI
SetUnhandledExceptionFilter(
_In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
来设置最后的异常处理器,返回原来的异常处理器.
该处理器函数原型:
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
_In_ struct _EXCEPTION_POINTERS *ExceptionInfo
);
即返回long类型的数据,接受一个_EXCEPTION_POINTERS 结构体:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
因此可以故意触发异常,并设置最后的异常处理器. 在这个异常处理器中修改上下文的eip,结束异常处理后将到被设置的eip处执行
(4) 时间检测
获取2次时间值,如果差值大于某个值就认为正在被调试,因为调试时代码执行速度变慢.
这里利用RDTSC指令. 在x86cpu中存在一个名为TSC的64bit寄出器.cpu对每个时钟周期计数并保存到TSC中.
RDTSC读取TSC的值并保存到edx和eax中(高32位存在edx中).
(5)陷阱标志 结合SEH
flags寄存器中的第8bit(从0开始)为TF标志,当TF标志位为1时, 再执行一条执行后将触发ExCEPTION_SINGLE_STEP异常,然后清0TF标志.
此时如果没有调试器自然就转入SEH执行代码,然后再运行到正常代码处,如果有调试器就继续运行下去并进入垃圾代码循环或者退出进程等
(6) int 2D 指令
该指令是内核模式下的一条指令, 机器码为CD2D. 但是在用户模式下,如果是非调试状态下相当于int 3, 如果是在调试状态下却不会产生异常且会忽略掉
下一条指令的第一个字节,从下一条指令的第2个字节开始执行代码,这样将会导致代码混乱.
(7)验证校验和
(8) 垃圾代码
执行同样效果的大量代码,在大量代码中,只有插入在其中的少量的代码是有意义的.
根据IA-32 指令规则,编写的代码扰乱反汇编器的解析结果
加密与解密代码
复制API代码到新的位置,调用时不再调用原来的API.而是到新的位置执行代码