首页
社区
课程
招聘
[原创]windows反调试技术-动态反调试
发表于: 2019-2-22 22:03 11667

[原创]windows反调试技术-动态反调试

2019-2-22 22:03
11667
利用动态反调试技术可以不断阻止对程序代码的跟踪调试,目的就是隐藏和保护程序代码与数据,使之无法进行逆向分析。

1.异常
        异常常用于反调试技术,正常运行进程在发生异常时,在SEH机制的作用下,OS会接受异常,然后调用进程中注册的SEH进行处理。但是,若进程在调试运行中发生异常,调试器就会接受处理。利用该特征就可以判断进程使正常运行还是调试运行,然后根据不同的结果执行不同的操作。
       1.1.SEH
        windows操作系统中常见的使断点异常。BreakPoint指令触发异常时,若程序处于正常运行状态,则自动调用已经注册过的SEH,若程序处于调试运行状态,则系统会立刻停止运行程序,并将控制权转给调试器,一般而言异常处理器中都含有修改EIP值的代码,修改调试器选项可以把处于调试中进程产生的相关异常转给操作系统,自动调用SEH处理,但即便如此,在异常处理中增加适当的静态反调试技术,也能够轻松判断进程是否处于调试状态,此外,EIP值在异常处理内部如何变化也不得而知,这意味着,必须跟进如异常处理器才能继续调试。
       EIP在context+0xB8位置处
       破解的办法:在od 调试选项exception选项中,选择int3 breaks后,调试器就会忽略被调试进程中发生的int3异常,由自身SEH处理。
       1.2 SetUnhandledExceptionFilter()
        进程中发生异常的时候,若SEH未处理或注册的SEH不存在就会调用kernel32!UnhandledExceptionFilter()API.会运行系统的最后一个异常处理器,弹出错误框,终止进程运行。值得注意的是 kernel32!UnhandledExceptionFilter() 内部调用了ntdll!NtQueryInformationProcess(ProcessDebugPort)API(静态反调试及时),来判断进程是否正在调试进程。若进程正常运行(非调试运行),则运行系统最后的异常处理器;若进程处于调试中,则将异常派送给调试器。通过kernel32!SetUnHandledException()API可以修改系统最后的异常处理。
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter ( _In_LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
) ;
    lpTopLevelExceptionFilter ,函数指针。当异常发生时,且程序不处于调试模式(在vs或者别的调试器里运行)则首先调用该函数。调用该函数修改系统最后异常处理器时,只要将新得Top Level Exception Filter函数地址传递给函数的 lp TopLecelException参数即可
     基于异常的反调试技术中,通常先特意触发异常,然后再新注册的Last Exception Filter内部判断进程正常运行还是调试运行,并根据判断结果修改EIP值,系统在此过程中自行判断调试与否。
    利用的思路如下:
     1)调用新的SetUnhandledExceptionFilter()来注册新的Top Level Exception Filter(新的Excetion Filter函数中包含异常处理代码)。
     2)然后强制触发断点,利用相应的异常来触发异常(如尝试向未定义的进程虚拟内存地址(0)写入)然后程序回调用Kernel32!UnhandledExceptionFilter()函数。
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter ( _In_LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
) ;
    lpTopLevelExceptionFilter ,函数指针。当异常发生时,且程序不处于调试模式(在vs或者别的调试器里运行)则首先调用该函数。调用该函数修改系统最后异常处理器时,只要将新得Top Level Exception Filter函数地址传递给函数的 lp TopLecelException参数即可
     基于异常的反调试技术中,通常先特意触发异常,然后再新注册的Last Exception Filter内部判断进程正常运行还是调试运行,并根据判断结果修改EIP值,系统在此过程中自行判断调试与否。
    利用的思路如下:
     1)调用新的SetUnhandledExceptionFilter()来注册新的Top Level Exception Filter(新的Excetion Filter函数中包含异常处理代码)。
     2)然后强制触发断点,利用相应的异常来触发异常(如尝试向未定义的进程虚拟内存地址(0)写入)然后程序回调用Kernel32!UnhandledExceptionFilter()函数。
     破解方法,破解的时候首先使用Kernel32!UnhandledExceptionFilter()内部使用的ntdll!NtQueryInformationProcess()API()失效,然后调用SetUnhandledExceptionFilter()API跟踪注册的Exception Filter,在正常运行的时候跳转到那个地址就可以。

2. Timing Check
      在调试器中逐跟踪程序代码比程序正常运行(非调试运行)耗费的时间要多出很多。Timing Check技术通过计算运行时间的差异来判断进程是否处于被调试状态。
      破解的方法:只要直接操作获取的时间信息获取比较时间的语句就可以。(Timing Checking技术通常用作反模拟技术,程序在模拟器中运行时,运行速度比程序正常运行慢很多,所以Timing Check技术也能用来探测程序是否在模拟器中运行。)
       2.1时间间隔测量方法
       大致方法分为两种,一种事利用CPU计数(Counter),另一类事利用系统的实际时间(Time)
       2.2 RDTSC
       x86寄存器有一个名为TSC(Time Stamp Counter,时间戳计数器的64位计数器CPU,CPU对每个Clock Cycle(时终周期)计数,然后保存到TSC.RDTSC是一条汇编指令,用来将TSC值读入EDX:EAX(高位32位保存到EDX寄存器,低32位保存到EAX寄存器)。程序判断的时候会调用两次RDTSC,通过计算两次调用的时间间隔来判断进程是否处于调试状态(只要使用一次f7或f8)命令,时间间隔就会大于0xffffffff。
       破解的方法:
       1)不使用跟踪命令,直接使用RUN命令越过相关的代码。
       2)操作第二个RDTSC的结果值(EDX:EAX)
       3) 操作判断之后的条件分支
       4)利用内核模式驱动程序使RDTSC指令无效:利用内核驱动程序可以从根本上使基于RDTSC的动态反调试技术失效(其实OllyAdvanced Plugin就采用了该方法)。

3.陷阱标志
       陷阱标志指的是EFLAGS寄存器的第9个比特位
       3.1 单步执行
       TF设置为1的时候,CPU将进入单步执行(Single Step)模式。单步执行模式中,CPU执行1条指令后即触发一个EXCEPTION_SINGLE_STEP异常,然后陷阱标志位会清零。该异常可以与SEH技法结合,在反调试技术中用于探测调试器。
PUSHFD   ;将EFLAGS寄存器的值压入栈
OR WDORD PTR SS:[ESP],100  ;将TF位置为1
POPFD   ;将修改后的TF标志值存入EFLAGS
       由于我们不能直接修改EFLAGS寄存器的值,所以使用PUSHFD/POPFD指令与or运算指令修改陷进标志的值。发生异常的时候,若进程处于非调试状态,则运行SEH运行正常的代码,若进程处于调试状态的时候,则无法转到对应的SEH,会执行异常的指令(这里在SEH里面,进程会跳转到正常的代码继续执行)
        破解方法:OD 的调试选项,option->debugging options->exception:选择忽略EXCEPTION_SINGLE_STEP异常),让被调试者直接处理EXCEPTION_SINGLE_STEP异常。
       3.2 INT 2D
       INT 2D 为内核模式中用来触发断点异常的指令,也可以在用户模式下触发异常。但程序调试运行的时候不会触发异常,只是忽略。这种在正常运行与调试运行中表现出的不同可以很好的利用于反调试技术。下面介绍几个INT2D 的几个特征
       1.忽略下一条指令的第一个字节
PUSHFD   ;将EFLAGS寄存器的值压入栈
OR WDORD PTR SS:[ESP],100  ;将TF位置为1
POPFD   ;将修改后的TF标志值存入EFLAGS
       由于我们不能直接修改EFLAGS寄存器的值,所以使用PUSHFD/POPFD指令与or运算指令修改陷进标志的值。发生异常的时候,若进程处于非调试状态,则运行SEH运行正常的代码,若进程处于调试状态的时候,则无法转到对应的SEH,会执行异常的指令(这里在SEH里面,进程会跳转到正常的代码继续执行)
        破解方法:OD 的调试选项,option->debugging options->exception:选择忽略EXCEPTION_SINGLE_STEP异常),让被调试者直接处理EXCEPTION_SINGLE_STEP异常。
       3.2 INT 2D
       INT 2D 为内核模式中用来触发断点异常的指令,也可以在用户模式下触发异常。但程序调试运行的时候不会触发异常,只是忽略。这种在正常运行与调试运行中表现出的不同可以很好的利用于反调试技术。下面介绍几个INT2D 的几个特征
       1.忽略下一条指令的第一个字节
        在调试的时候执行完INT 2D指令后,下一条指令的第一个字节将被忽略。后一个字节将会被识别为新的指令继续执行。如下图所示
       
       
      执行完INT 2D后,会重新解析指令。使用INT 2D的反调试技术能够形成较强的代码混淆效果,从而在一定程度上防止代码逆向分析人员调试程序。
      2.一直运行到断点处
      INT 2D指令的另一个特征使,使用F7或者F8,命令跟踪INT 2D指令的时候,程序不会停止在其下条指令开始的地方,而是一直运行程序,就想使用RUN(F9)命令运行程序一样。(这个特性只会在OD中才有,原因在于执行INT 2D 之后原有的代码字节序被改变了,就是说,若程序在执行的过程中改变,则进程不能单步暂停,若想停止跟踪代码,需要事先在相应的地址处设置断点。
      破解办法:只要简单修改对应的代码就可以破解这种调试技术,但是实际调试过程中,有事必须跟踪SEH逐行调试代码,这个时候就需要另一种方法使程序执行到SEH,利用陷阱标志能够使程序进入SEH标志。(TF=0的时候,跟踪INT 2D指令后,其下条指令的第一个字节会被忽略,程序继续执行;但是TF=1的时候,其后面的1一个字节不会被忽略,代码仍被正常识别)

4.0xCC 探测

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2019-2-24 17:36 被wwzzww编辑 ,原因:
收藏
免费 4
支持
分享
打赏 + 5.00雪花
打赏次数 1 雪花 + 5.00
 
赞赏  kanxue   +5.00 2019/02/25 感谢分享~
最新回复 (3)
雪    币: 47147
活跃值: (20465)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
期待文章完成
2019-2-23 17:58
3
雪    币: 82
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
mark
2019-2-25 10:05
0
雪    币: 2124
活跃值: (1507)
能力值: ( LV3,RANK:35 )
在线值:
发帖
回帖
粉丝
4
学习一下
2019-11-24 09:02
0
游客
登录 | 注册 方可回帖
返回
//