反调试技术总结: 动态

静态反调试

上一篇博客



动态反调试

隐藏和保护程序代码与数据, 阻碍逆向分析. 动态反调试技术会干扰调试器, 使之无法正常跟踪查找源程序的核心代码(OEP).

异常

在SEH机制下, 如果程序发生异常OS通常会交给进程中的SEH处理, 但是被调试的进程异常被调试器接收, 所以根据这点可以判断进程是否被调试.
通常SEH会通过INT3指令系统断点触发, 如果是调试状态, 进程会因为没有得到异常处理而终止, 非调试状态则会在SEH代码执行之后恢复正常, 继续运行, 为了破解这个反调试技术, 可以在调试器中设置INT3 breaks忽略的选项, 让调试器碰到INT3中断时自觉交个SEH处理, 这样就能继续进入正常代码调试. 下图是OllyDbg的设置.
在这里插入图片描述

SetUnhandledExceptionFilter()

当异常不被SEH处理时, 还有一个API会接收异常, 就是SetUnhandledExceptionFilter(). 这个API会调用系统最后的异常处理器, 称为Last Exception Filter. 不过异常处理器只是弹出错误信息框, 终止进程运行. 但是需要注意的是SetUnhandledExceptionFilter()这个API, 而不是最后的异常处理器, kernel32!SetUnhandledExceptionFilter()中会调用ntdll!NtQueryInformationProcess(ProcessDebugPort)API (静态反调试)来判断是否处于调试状态. 如果知道是调试状态则将异常抛给调试器, 如果不是则调用最后的异常处理器.
反调试技术中会通过kernel32!SetUnhandledExceptionFilter()修改最后的异常处理器, 在最后的异常处理器中判断调试状态, 所以是结合了静态和动态两种反调试方法.

破解方法, 钩取ntdll!NtQueryInformationProcess(ProcessDebugPort)先破解静态反调试, 然后跟踪SetUnhandledExceptionFilter()注册的Exception Filter找到正常运行的地址.

Timing Check

代码调试过程是比正常运行的耗时要多得多的, 根据这点可以根据时间信息来判断是否在调试.
破解方法, 就是修改时间信息, 欺骗反调试代码. 不过Timing Check常常是和其他反调试技术并用的, 所以破解难度直线上升.
另外, Timing Check常常用作反模拟技术, 比如在模拟器中运行的程序, 速度要慢很多, 所以可以判断是否是在模拟器中运行.
技术分类

Counter based methodTime based method
RDTSCtimeGetTime()
kernel32!QueryPerformanceCounter() / ntdll!NtQueryPerformanceCounter()_ftime()
kernel32!GetTickCount()

RDTSC(Read Time Stamp Counter)
x86 CPU中有一个Time Stamp Counter时间戳计数器, 对每个时钟周期计数然后保存到TSC
RDTSC是将TSC的值读取到EDX:EAX的汇编指令

破解方法

  • 不用单步跟踪, 直接run越过检测代码
  • 修改第二次比较的EDX:EAX的值使之符合比较条件.
  • 控制jcc比较指令的flags值, 比如修改CF或ZF的值即可控制条件跳转
  • 利用内核模式驱动程序, RDTSC在内核模式下无效.
陷阱标志

TF
EFLAGS寄存器的第9个比特位
在这里插入图片描述
Trap Flag(TF)设置为1时, CPU进入单步模式, 执行一条指令后自动触发EXCEPTION_SINGLE_STEP异常, 然后TF清零.(所以是设置TF = 1, 就只在下次执行时执行一条指令, 再执行单步时需要再设置TF = 1)
利用TF来反调试, 通常结合SEH技术, 探测调试状态. 基本思想是主动将TF设置为1后继续执行会抛出EXCEPTION_SINGLE_STEP异常这一行为, 来区分调试和非调试, 如果是正常运行, 则可以执行SEH处理异常, 然后转到正常代码. 如果是调试状态, 因为调试器截获了EXCEPTION_SINGLE_STEP异常, 则不会进入SEH然后程序终止.

破解方法, 设置调试器忽略EXCEPTION_SINGLE_STEP异常, 即可绕过TF检测.
在这里插入图片描述
INT 2D
一个内核模式的断点异常指令, 也可在用户模式下触发异常, 但是有趣的是调试时会忽略这个异常, 所以可以用于反调试. 调试模式下, INT 2D之后的代码会被忽略一个字节, 从第二个字节开始重新识别为新的指令, 所以可以造成很强的混淆效果. 另一个特征是, 碰到INT 2D时单步指令会失效, 因为0xCC被忽略了, 所以不能被系统断下, 而相当于变成了run指令.

破解方法, 利用TF标志绕过INT 2D指令. 在INT 2D处下断点, 设置TF = 1, 一个很巧妙的想法, 用异常冲掉内核指令INT 2D的效果, TF == 1时, 执行INT 2D不会让CPU忽略下一个字节的指令代码, 所以INT 2D无效化了. 用异常打败异常

0xCC探测

最本质的反调试思想, 因为对于x86CPU, 所有调试都是基于把指令第一个字节设置成0xCC来中断程序运行的, 所以检测作为断点的0xCC就能判断调试状态. 注意不是单纯扫描0xCC, 因为0xCC可以是立即数, 地址, 偏移量等, 不一定就是调试器下的断点.
反调试思想, 预判破解者通常在API开始的第一个指令处下断点, 所以专门检测API的第一个字节是否是0xCC来判断调试状态.

常用API
在这里插入图片描述
在这里插入图片描述
破解方法, 预判对方的预判(这波在第三层), 在API代码中间设置断点就能绕开检测. 还有一种降维打击, 设置硬件断点, 也可以绕开0xCC检测.

比较校验和
特定代码区域的校验和, 如果下了断点某个字节改成了0xCC, 则校验和也会随之改变. 所以发现校验和不一致, 就可判断调试状态.
破解, 避开校验区域设置断点, 或者修改程序控制流强制转到正常代码运行. 不过比较校验和的反调试技术会用于多个区域, 数十上百个, 因此采用了该技术的程序破解难度大大增加.





总结

动态反调试技术重要是下面这些

  • SEH异常处理机制
  • SetUnhandledExceptionFilter()
  • Timing Check
  • 陷阱标志
  • 0xCC探测

反调试技术是逆向技术中的高级技术, 浓缩了很多低中级的逆向技术, 还另有扩展, 静态和动态反调试是实际软件保护技术的重要部分, 也是逆向技术中的精彩一环, 目前暂时了解了一点点Windows系统的反调试技术, 未来在纵向上还需要继续实践打磨, 横向上还需要继续深入学习Linux, Android, IOS等系统的反调试技术.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值