反调试技术

First Post:

Last Update:

Word Count:
1.3k

Read Time:
4 min

反调试技术

ref: https://km.woa.com/articles/show/424765?kmref=search&from_page=1&no=2

一、基于PEB的调试器检测

备注:mov eax, 0x31;dec eax;mov eax, fs:[eax]; 这么冗余的写法主要是因为

mov eax,fs:[0x30]太显眼,一看到这条指令就很容易猜到是操作PEB的关键代码,甚至通过特征码暴力定位关键逻辑。所以对代码做了简单变形,防代码特征码搜索。

A、BeingDebugged标记位检测调试器

反调函数:GetBeingDebuggedFlag()

反调表现:存在调试器则返回true,没有调试器则返回false;

绕过方式:对应标记位清零

B、NtGlobalFlag标记位检测调试器

反调函数:FindDebuggerByNtGlobalFlags()

反调表现:存在调试器则返回true,没有调试器则返回false;

绕过方式:对应标记位复位

C、Heap.Flags 标记位检测调试器

反调函数:FindDebuggerByHeapFlags()

反调表现:存在调试器则返回true,没有调试器则返回false;

绕过方式:对应标记位清零

D、HEAP.ForceFlags标记位检测调试器

反调函数:FindDebuggerByForceFlags()

反调表现:存在调试器则返回true,没有调试器则返回false;

绕过方式:对应标记位置2

二、通过NtQueryInformationProcess函数检测调试器

四、通过ThreadHideFromDebuger干扰调试

A、ZwSetInformationThread反调

ThreadHideFromDebuger是线程的一个属性值,当线程具备ThreadHideFromDebuger特性时则该线程对“调试器”隐藏,线程触发的所有异常均不会通知调试器处理。所以如果线程设置了ThreadHideFromDebuger那么当断点触发时调试器表现为卡死。当线程启动后可以通过ZwSetInformationThread函数来设置,相关代码如下:

反调函数:SetThreadHideFromDebugger()

反调表现:调用该函数后调用线程触发的异常不会在发送到调试器,触发断点后调试器卡死;

绕过方法:对ZwSetInformationThread进行hook过滤,ThreadHideFromDebugger参数则直接返回。

B、NtCreateThreadEx反调

除了通过ZwSetInformationThread来设置已有线程的ThreadHideFromDebuger标记位外,还可以创建一个新的、创建开始就自动具备ThreadHideFromDebuger属性的线程。ZwCreateThreadEx函数的声明如下:

其中CreateFlags参数如果设置了THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER位的话则创建的新线程直接具有反调试功能。

绕过方法:对ZwCreateThreadEx进行hook过滤,去掉线程ThreadHideFromDebugger属性。

C、通用ThreadHideFromDebuger保护绕过方式

不管是ZwSetInformationThread方式还是ZwCreateThreadEx方式的ThreadHideFromDebuger线程保护,其绕过方式基本都是通过HOOK函数调用需改函数参数或者修改系统内核代码直接修改内核执行逻辑的方式绕过。理论上通过代码校验都能够发现这类修改,甚至会触发PageGuard导致系统稳定性问题。但有大咖们发现了新的、不用修改代码即可绕过ThreadHideFromDebuger保护的方式。

首先我们来看看ZwSetInformationThread函数对ThreadHideFromDebuger操作的处理逻辑,代码如下:

五、基于异常原理的反调试

操作系统异常处理逻辑比较复杂,包括VEH\SEH\UEH\VCH(自己临时命名,不一定权威,具体涉及函数见代码)等等。利用好异常处理功能可以很好的提升程序稳定性,但很多时候也被用于反调试功能。为了便于后面反调试逻辑的理解,这里先给出一份包含4种异常处理的demo例子,便于大家更好的开发包含异常处理的程序。

利用异常进行反调试技术可分为2大类,一大类时利用调试器过滤处理常见调试异常的BUG来发现调试器的存在。另外还有一类则是利用操作系统在有调试器和无调试器时逻辑处理差异来发现调试器的存在。这两大类反调试思路又根据实现细节划分位不同子类型。

A、利用异常结合断点检测调试器

很多时候执行特定的代码会触发特定的异常,并且这类异常一般用于调试器种,如果程序没有被调试则会被异常处理函数所捕获。由于这类异常经常用于调试器种,所以很多调试器见到这类异常就直接拦截处理了,导致程序自己注册的异常处理函数捕获不到异常。常见的这类指令主要包含:int 3h; int 2ch; int 2dh;IceBp;单步异常等;网上还有说利用0xCD03断点的方法,但由于本人没测试通过所以这里就不包含该方式。

对程序开发者来讲虽然各异常使用场景方面稍有差异,但大致用法都还是一致的。所以利用SEH异常结合断点能够检测调试器,利用UEH\VEH等异常也能实现类似工作。SEH使用相对更便捷,所以本章节大部分异常都采用SEH异常的方式,其它异常类似功能实现可以自己摸索。

1) 利用int 3断点检测调试器

利用int 3断点检测调试器代码如下(VS调试器上测试可用):

2) 利用int 2C断点检测调试器

利用int 2C断点检测调试器代码如下(VS调试器上测试可用):

打赏点小钱
支付宝 | Alipay
微信 | WeChat