常用的静态反调试技术及其规避方法

静态反调试虽然容易绕过,但是由于种类繁多,如果病毒结合了很多种静态反调试而对其了解不多的话,也不好办,所以了解更多的

方法不至于束手无策.

1.利用PEB的BeingDebugged 字段

  已知fs:[0x30]指向PEB, PEB地址+0x2 处的一个字节的数据表示是否在被调试,如果是1则是,0则不是

  当进程被附加时,系统会将该值置为1

IsDebuggerPresent() API就是利用这个原理工作的:

IsDebuggerPresent的反汇编代码:

751E9E0A  mov         eax,dword ptr fs:[00000030h]  
751E9E10  movzx       eax,byte ptr [eax+2]  
751E9E14  ret  

所以通过调试器启动后或附加后将该值修改为0即可规避

2.Ldr的内存区域特征

启动方式调试程序时,peb中的Ldr的内存是从堆中分配的,未使用的区域被大量填充0xfeeefeee.但附加方式却不会出现这种情况.

例如来到Ldr处(peb+0xc):这里测试Ldr地址是0x00241ea0

 下拉:

通过附加的方式则不会出现这种特征. 此外,这种特征在xp以上的系统就没有了

3.Process Heap (peb+0x18)

_heap结构节选:(xp sp3)

其中Flags和ForceFlags字段被调试时将不同

当正常运行时前者为0x2,后者为0x0, 启动调试时将变成其他值:

附加后查看:

 此外该特征在xp以上系统将不存在.附加方式调试也不会出现. 规避方法就是修改这2个值

4.NtGlobalFlag (peb+0x68)

该字段正常运行和被附加调试时为0,被启动调试时为其他值

5.NtQueryInformationProcess 

原型:

NTSTATUS NtQueryInformationProcess(

HANDLE ProcessHandle,

PROCESSINFOCLASS ProcessInformationClass,

PVOID ProcessInformation,

ULONG ProcessInformationLength,

PULONG ReturnLength

);

 第2个参数是个枚举类型. 主要有3个值与调试有关:

ProcessDebugPort = 7

ProcessDebugObjectHandle = 30

ProcessDebugFlags  = 31

ProcessInformationClass 参数为7时,ProcessInformation返回调试状态.

 即当处于非调试状态将传入变量置0,否则置0xffffffff

 同理,当ProcessInformationClass 参数为30时,ProcessInformation返回调试对象句柄

 即当调试状态时将传入变量置句柄值,否则置0

 此外,API CheckRemoteDebuggerPresent就是通过这个方式来检测是否存在调试器

ProcessInformationClass 参数为31时,ProcessInformation返回调试标记

 即当调试状态时将传入变量置0,否则置1

规避方法:在该API开头处直接改为ret  (后面可能还要加数字平衡栈),让它获取不到任何信息.如:

ntdll中的代码:

773DD300 mov eax,18h
773DD305 call dword ptr fs:[0C0h]
773DD30C ret 14h

改为:

773DD300 C2 1400 retn 0x14
773DD303 90 nop
773DD304 90 nop
773DD305 64:FF15 C000000>call dword ptr fs:[0xC0]
773DD30C C2 1400 retn 0x14

6.基于调试环境: NtQuerySystemInformation

当操作系统开启内核调试模式时,会有一定特征,可以通过NtQuerySystemInformation API来获取

NTSTATUS NtQuerySystemInformation(
  SYSTEM_INFORMATION_CLASS SystemInformationClass,
  PVOID SystemInformation,
  ULONG SystemInformationLength,
  PULONG ReturnLength);
SYSTEM_INFORMATION_CLASS是个枚举类型:

当为0x23时为获取系统是否在被调试信息存放到第2个参数指向的结构中,该结构是2个1字节的结构,当处于调试时这2字节都会被写入1.

规避方法同样是调试时修改API,让其直接返回

7.NtQueryObject

该API获取系统内核对象信息,原型:

NTSTATUS NtQueryObject(
  _In_opt_  HANDLE                   Handle,
  _In_      OBJECT_INFORMATION_CLASS ObjectInformationClass,
  _Out_opt_ PVOID                    ObjectInformation,
  _In_      ULONG                    ObjectInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

第2个参数是个枚举类型:

typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation = 0,

ObjectNameInformation = 1,
ObjectTypeInformation = 2,

ObjectAllTypesInformation =3,

ObjectHandleInformation 
} OBJECT_INFORMATION_CLASS;

传入3即获取所有对象信息,然后检测是否存在调试对象

规避方法:修改API开头代码直接返回.

8. ZwSetInformationThread

NTSTATUS ZwSetInformationThread(
  _In_ HANDLE          ThreadHandle,
  _In_ THREADINFOCLASS ThreadInformationClass,
  _In_ PVOID           ThreadInformation,
  _In_ ULONG           ThreadInformationLength
);

 当第一个参数传入本线程句柄,第2个参数传入0x11,后2个参数传入0即可将本线程设置为隐藏,从而使调试器收不到调试事件而无法调试.

9.DebugActiveProcessStop 

BOOL DebugActiveProcessStop(
  DWORD dwProcessId);

该API使进程从调试器中detach出来,最后调试和被调试进程都终止.

10.TLS回调函数

利用tls回调函数可以将以上方法利用起来.

11.其他静态检测思路

比如探测od窗口名,进程名, 是否运行在虚拟机中等等方式

原文地址:https://www.cnblogs.com/freesec/p/6576647.html