反调试技术

前言

反调试技术可以被恶意代码用来识别是否被调试,或者让调试器失效,而倘若想要分析相应的包含反调试机制的恶意代码,则需要进行一些操作。我们先来看看主流的一些反调试技术。


0x1探测调试器

使用windows api

使用windows api函数探测调试器是否存在是最简单的反调试技术。下面是一些api

IsDebuggerPresent

它查询PEB中的IsDebugged标志。如果进程没有运行在调试器环境中,返回0,否则返回一个非0值。

CheckRemoteDebuggerPresent

与IsDebuggerPresent几乎一样,而且也是检查本机的某一进程中的PEB中的IsDebugged标志,要传一个句柄作为参数,返回值与上一个一样。

RtQueryInformationProcess

Ntdll.dll中的原生态API,用来提取一个给定进程的信息。将参数置为ProcessDebugPort(0x7),就可以返回句柄标识的进程是否在被调试,如果在被调试,返回调试端口,否则返回0。

OutputDebugString

这个函数的作用是在调试器中显示一个字符串。同时也可以具有探测功能。使用SetLastError函数,将当前的错误码设置为任意值,如果进程没有被调试,调用OutputDebugString函数会失败,并且返回错误码,那么错误码就不是我们之前设置的那个任意值了,反之,错误码就不会变。

通常,防止恶意代码使用API进行反调试的最简单方法就是在恶意代码运行期间修改恶意代码,使其不能调用探测函数,或者修改这些探测函数的返回值,相对复杂的方法就是使用Hook函数去影响这些函数。


手动检测数据结构

虽然API探测调试器是简单的一种方法,不过手动检查数据结构是恶意代码最常用的手段。因为很多时候通过API的方法无效。

手动检测中,PEB结构中的一些标志暴露了调试器存在的信息。

PEB结构:

typedef struct _PEB{

BYTE    Reserved1[2];

BYTE    BeingDebugged;

BYTE    Reserved2[1];

BYTE    Reserved3[2];

PPEB_LDR_DATA    Ldr;

PRTL_USER_PROCESS_PARAMETERS    ProcessParameters;

BYTE    Reserved4[104];

PVOID    Reserved5[52];

PPS_POST_PROCESS_INIT_ROUTINE    PostProcessInitRoutine;

BYTE    Reserved6[128];

PVOID    Reserved7[1];

ULONG    SessionId;

}PEB,*PPEB;

BeingDebugged属性

进程运行时,fs:[30h]指向PEB的基地址,而距基地址偏移量2的BeingDebugged标志正是标识进程是否被调试

检测方法

mov    eax,dword ptr fs:[30h]

mov    ebx,byte ptr [eax+2]

test     ebx,ebx

jz        noDebuggerDetected

因为BeingDebugged标识被保存在ebx中,若值为0,则表示没有被调试。

处理这种机制的方法便是修改跳转指令或者修改BeingDebugged的值。

ProcessHeap属性

Reserved4数组中有一个未公开的位置叫做ProcessHeap,它被设置为加载器为进程分配的第一个堆的位置。ProcessHeap在PEB结构的0x18处。第一个堆头部有一个属性字段,告诉内核这个堆是否在调试器中创建。属性叫做ForceFlags和Flags(这个属性的位置偏移量在不同的系统中是不同的,xp中是0x10)。

检测方法(xp)

mov    eax,large fs:30h

mov    eax,dword ptr [eax+18h]

cmp    dword ptr ds:[eax+10h],0

jne       DebuggerDetected

处理这种机制的方法与上一种一样。

NTGlobalFlag

系统使用的PEB结构偏移量0x68处的一个未公开的位置,决定如何创建堆结构。如果值为0x70则进程正在调试

检测方法

mov    eax,large fs:30h

cmp    dword ptr ds:[eax+68h],70h

jz         DebuggerDetected

对付这种机制就是修改ProcessHeap属性或者使用隐藏调试插件。


系统痕迹检测

当我们在调试的时候,注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug会从原始的Dr.Watson修改为OllyDbg。所以就可以被检测出来了

if(FindWindow("OLLYDBG",0)==NULL){

Debugger not found

}else{

Debugger detected

}


0x2识别调试器的行为

INT扫描

调试器设置断点的基本机制是软件中断指令INT3,临时替换运行程序中的一条指令,然后当程序运行到这条指令的时候,调用调试异常处理例程。INT3的机器码是0xCC,因此无论何时,使用调试器设置一个断点,都会插入一个0xCC修改代码。

所以恶意代码会在代码中查找机器码0xCC。

对抗这种机制的方法就是设置硬件断点


时钟检测

被调试时,进程的运行速度大大降低,所以利用时钟检测也是恶意代码探测调试器的一种常用方法。

记录执行一段操作前后的时间戳,然后比较两个时间戳,如果存在滞后,则可以认为存在调试器

记录触发一个异常前后的时间戳,如果不调试进程,可以很快处理完异常,因为调试器处理异常的速度非常慢。默认情况下,调试器处理异常时需要人为干预,导致大量延迟。

使用rdstc指令

rdtsc

xor    eax,eax

add   ecx,eax

rdtsc

sub    eax,ecx

cmp   eax,0xFFF

jb       NoDebuggerDetected

rdtsc

push   eax

ret


使用QueryPerformanceCount和GetTickCount

这两个函数都可以获取时间,所以计算两次获取的时间差即可,若时间差比较大,则可能进程正在被调试

应对方法是在时钟检测之后设置断点,或者修改时间差。


0x3干扰调试器的功能

使用TLS回调

程序加载到调试器后大部分调试器会从程序PE头部指定的入口点处开始。TLS回调被用来在程序入口点执行之前运行代码,所以这些代码可能被忽略,秘密的运行了。

使用IDA可以用ctrl+E明显的看到程序的入口点以及是否存在TLS回调。也可以设置调试器在TLS回调前暂停。


下面有三个实例程序,先看第一个


程序开始的时候流程图长这样,已经可以很明显的看到之前所说的手动探测数据结构的手段了

所以当打开调试器之后,我们就可以开始修改PEB了,不论是OD还是ImmunityDebug都可以用命令行改

dump fs:[30]+2,dump ds:[fs:[30]+0x18]+0x10,dump fs:[30]+0x68

数据窗口会分别显示相应的数据,分别修改便可以绕过检测了,不过修改指令应该也可以吧。


下面来看第二个程序


main函数长这样,还有个TLS回调,这是一个密码验证程序,我们下意识载入调试器,不过直接退出了


因为TLS回调长这样,所以我们要nop掉exit调用。


紧接着是回调函数中的sub_401020函数,这个函数就是之前讨论过的API探测,所以就不用说直接nop掉byte_40A968加1的这条指令。

然后我们调试器直接跟进到main函数中的strncmp函数处观察两个参数,果然一个是输入,一个是密码,不过测试发现不对。

所以我们来到解密函数处观察发现这里也有一个反调试的机制


这一段中,bl被赋值成了PEB中的BeingDebugged标识,而后又进入了解密过程的计算,所以还需要改一下PEB中的标识。

最后才是正确答案。


下面是最后一个程序


程序一开始就逐个字符的定义了两个字符串,第一个看不懂,第二个是一个可执行文件名。


接着获取自身文件名与定义的文件名比对只有对了才能执行。


不过初始化的文件名的字符串还进行了更改,并且有反调试机制,利用的便是时间戳的差值,所以我们只需要改一下变量num就可以了。不过这段汇编代码却是包含了SEH的处理。


这一段里面eax已经被赋值为自身的地址0x401228了加上2Ch也就是0x401254,下面把这个地址入栈,到SEH调用链,到div ecx时,ecx是0,无法做除法,触发SEH,执行下面的0x401254的异常处理代码,在调试状态下会花费大量时间,所以可以辨别处是否处于调试状态。


同样,后面也调用了GetTickCount()函数进行反调试处理


最后的网址解密函数同样也是利用了rdstc的时间处理机制。


反调试技术不单单有这些,还有插入中断,利用调试器漏洞等方法,这几种只是最具有代表性的,方便学习在这里做个笔记。

内容与程序选自《恶意代码分析实战》一书16章。

猜你喜欢

转载自blog.csdn.net/bj5716/article/details/80031581