背景
为了提高开发的软件产品安全性,大部分选择的方案防护方案是,通过用成熟的加固软件进行对自己研发的软件做防护,从而达到对软件搭建一个安全防护墙。加固软件主要做的两件事,对软件中关键代码的保护以及提高对软件逆向反编译的门槛。
那么软件安全性防护墙的第一道门那就是反调试。反调试技术又细分为静态反调试和动态反调试。下面就针对window端的进行梳理反调试检测方案。
进程环境块BeingDebugged检测
通过读取进程环境块PEB中,是否设置BeingDebugged标志(其实这个标志IsDebuggerPresent跟这个函数内部执行效果是一样的),这个PEB(进程环境块)指针指向的值。
在x86环境下通过FS:[0x30]获取PEB的值;
在X64环境下通过GS:[0x60]获取PEB值;
当这个值等于0的情况下,表示没被调试,否则就处于调试状态。
进程环境块NtGlobalFlag检测
进程环境块PEB中NtGlobalFlag是一个DWORD值,这个值包含操作系统设置的许多标志,这些标志会影响进程的运行方式。这个字段在程序正常运行的情况下值默认为0,在被调试器调试的时候(如ollydbg动态调试),这个字段为0x70(注意:ollydbg附加状态下是没改变的)。
进程环境块HeapFlags检测
当程序在调试下运行,并使用调试器进程创建标志创建时,HeapFlags标志更正常程序运行的标志值是不一致的。
对于X86系统,Vista以上版本的HeapFlags位于0x40偏移, 低于Vista版本的位于0x0C
对于X64系统,Vista以上版本的HeapFlags位于0x70偏移,低于Vista版本的位于0x14偏移
如果这个HeapFlags的值大于2,那么表示处于被调试状态,如果这个值等于2,那么属于正常状态。
进程环境块ForceFlags检测
当程序在调试下运行,并使用调试器进程创建标志创建时,ForceFlags标志跟正常运行的程序是不一致的。
对于X86系统,Vista以上版本的HeapFlags位于0x44偏移, 低于Vista版本的位于0x10偏移
对于X64系统,Vista以上版本的HeapFlags位于0x74偏移,低于Vista版本的位于0x18偏移
如果这个ForceFlags的值大于0,那么表示处于被调试状态,如果这个值等于2,那么属于正常状态。
IsDebuggerPresent 检测
通过直接利用系统 IsDebuggerPresent函数,进行判断当前程序是否处于调试状态。
如果程序处于调试状态的情况下,那么这个函数的返回返回真,否则返回假。
CheckRemoteDebuggerPresent 检测
它是微软公开的系统函数,通过利用它可以用于检测,软件是否正在调试远程进程(同一机器上的不同进程中,是否附加到当前进程)。
我们还可以将其用作另一种方法来检测,软件是否处于正在调试。此函数在内部调用NTDLL模块的导出PROCESSINFOCLASS设置为NtQueryInformationProcess函数7(进程调试端口)。本质上是通过NtQueryInformationProcess函数查询是否使用调试端口。
NtQueryInformationProcess 检测
这个是微软未公开的函数,下面是这个函数的参数信息,可以利用这个函数的第三个参数值,也就是利用PROCESSINFOCLASS值,进行判断是否处于调试状态。
当程序处于调试状态时,系统会给它分配一个调试端口(Debug Port),当程序正常运行状态时ProcessDebugPort的值为0,当程序处于调试状态ProcessDebugPort的值为0xFFFFFFFF。
当程序处于调试状态时,这个PROCESSINFOCLASS指向ProcessDebugObjectHandle的值是一个句柄值,当程序处于正常状态这个ProcessDebugObjectHandle值为NULL值。
当程序处于调试状态时,这个PROCESSINFOCLASS指向ProcessDebugFlags的值为0,当程序处于正常状态 时,这个ProcessDebugFlags值为1。
SetUnhandleExceptionFilte 检测
通过利用SetUnhandledExceptionFilter,可以注册一个异常处理函数,当一个异常产生,而且我们的 try - catch(或 try - expect)异常捕获中,没有处理处理这个异常时,异常会转交给 SetUnhandledExceptionFilter 。如果程序存在调试器状态,则调试器就会接管这个异常,那么这个异常就不会走到 SetUnhandledExceptionFilter 注册的异常处理函数。
原理:通过设置一个SetUnhandledExceptionFilter。然后利用RaiseException提出一个异常交给异常处理机制 由于没有设置相应的异常处理程序, 当程序被调试时,会通知进程的调试器,而不会调用UnhandledExceptionFilter。
SetHandleInformation 检测
通过创建一个互斥体对象,利用# SetHandleInformation将互斥体对象句柄标志改为HANDLE_FLAG_PROTECT_FROM_CLOSE,然后关闭句柄,如果是在调试器状态下,它会抛出EXCEPTION_EXECUTE_HANDLER异常,只要捕获到异常那么就表示程序被调试。
CloseHandle 检测
利用异常捕获机制,给CloseHandle函数一个无效的句柄作为输入参数,在程序在没有被调试时,将会返回一个错误代码;而程序被调试器调试时,将会触发一个EXCEPTION_INVALID_HANDLE的异常。
父进程反调试检测
在window系统中explorer是程序管理器或者文件管理器,一般双击运行的进程,它的父进程就都是explorer程序,如果是被调试进程启动的话那么父进程是调速器进程。通过利用CreateToolhelp32Snapshot函数或ZwQueryInformationProcess函数进行检测进程的父进程名称。
硬件断点反调试检测
硬件断点是intel在其处理器体系结构中实现的一种技术,通过使用Dr0-Dr7的特殊寄存器进行控制。在32位寄存器中Dr0-Dr3是保存断点地址,只要识别Dr0-Dr3寄存器的值不为0,那么就属于调试状态。
软件断点反调试检测
在IA-32指令集中使用操作码0xCC,表示软件断点,也就是INT3断点。
通过Int3产生异常中断的反调试相对比较经典。Ollydbg的断点机制就是利用这个机制, 当INT3 被执行到时, 如果程序未被调试, 将会异常处理器程序继续执行。而INT3指令常被调试器用于设置软件断点,int 3会导致调试器误认为这是一个自己的断点,从而不会进入异常处理程序。
其他反调试检测
通过利用FindWindow(),GetWindowLongA(),EnumWindow()等函数,进行遍历检测调试器的的窗口及控件相关信息;
通过CreateToolhelp32Snapshot等函数遍历运行进程,检测调试器相关的进程名信息,
通过查找注册表方式,检测调试器的信息。
通过检测驱动设备名称,检测调试器的特征码相关信息。
总结
以上梳理的应用层反调试方案建议结合使用,可以同时提高对应的难点。反调试只是一定情况下提高软件安全门槛,因为虽然有反调试方案,但同时也会有过掉反调试的方案。一般过掉反调试检测方案,通过将关键的反调试检测地方给 nop掉或者hook掉关键函数。反调试和反反调试的方案都是相对的,并不是绝对的安全。反调试强度更高的方案在于驱动层去检测实现。