在分析恶意代码的过程中,免不了需要动态分析,所以掌握常见反调试技术是必要的,否则就要出师未捷身先死了。
IsDebuggerPresent
CheckRemoteDebuggerPresent
NtQueryInformationProcess
OutputDebugString
在PEB结构中存储很多信息可以用来检测调试器。
BeingDebugged属性
ProcessHeap属性
NTGlobalFlag属性
注册表
文件遍历
进程遍历
窗口遍历
断点扫描
CRC校验
时钟检测
TLS回调会在程序执行OEP之前执行,因此若一个样本使用TLS回调,在OD分析断在OEP处时,实际上TLS回调函数已经执行完毕。
一个样本具备TLS回调的特征是区段中包含一个.tls节。正常程序很少使用该技术,因此当样本中出现.tls区段则很可能具备反调试手段。
若想分析TLS函数,在OD中将调试选项中的入口断点改为系统断点即可
在IDA中按下Ctrl+E可以查看所有入口函数,若包含TLS回调,则会显示
使用OD的忽略异常可以让调试器不接收异常从而将异常处理权限还给进程自身。
由于调试器使用int3(0xCC)或int 3(0xCD 03)指令产生异常从而接管异常处理来达到“断下来”的效果。因此若在程序中人为的添加中断指令就会被调试器认为是自己下的断点从而接管程序的执行。(现在的调试器都具备了区分断点归属性的能力,很少被这种技术欺骗)
而这种技术在一些调试器上会恰巧更改程序的执行流程,如下图:
这段代码在面对WinDbg调试器时,会展现它的反调试功能。当程序执行到int 3指令时,会触发3号中断,从而被WinDbg捕获,在捕获后,WinDbg会将中断指令的地址+1存入EIP,这就导致了原本利用SEH技术将要执行的continue代码被WinDbg强行更改为int 3后面的指令。从而错过了真正的代码流。
指令icebp(0xF1)会产生一个单步异常,调试器会误认为是自身单步导致的异常,从而接管异常处理。这时如果恶意作者使用icebp+SEH技术的话,调试器就会由于接管了异常处理而越过恶意代码作者的SEH函数。因此当代码中出现icebp,不要使用单步执行走过去。而是要在SEH处理函数下断进行监测。
该漏洞是由于调试器在读取PE数据时过于规则化,从而导致当恶意代码作者在不损坏文件的前提下修改PE头数据时,会被调试器视为一个损坏的PE文件从而无法加载。对于这种情况,我们只需观察PE数据对其进行手动修正即可。
此漏洞在目前版本的调试器已不存在,只针对OD1.0版本。漏洞内容如下:
通过本章的知识,可以发现反调试没有一个固定的规则,任何能对分析师对调试器造成欺骗的技术都可以作为反调试。小到一个API大到一个调试器的漏洞都可以被用来检测调试器。因此需要分析师与时俱进。
另外大多数反调试行为会涉及PEB结构(fs:[30]),因此需要对这种指令十分敏感。
这个恶意代码使用了哪些反调试技术?
当每种反调试技术成功执行时,有什么现象?
如何应对这些反调试技术
如何在调试过程中手动修改检测的数据结构?
哪一种 OllyDbg 插件可以帮你逃避恶意代码的反调试技术?
在命令行中运行Lab16-02.exe时,会发生什么?
当使用猜测的命令参数运行Lab16-02.exe时,会发生什么?
命令行密码是什么?
使用IDA Pro加载Lab16-02.exe。在main函数的何处可以找到strncmp函数?
在默认设置下,将这个恶意代码加载到OllyDbg中会发生什么?
Lab16-02.exe中PE结构的独特之处是什么?
回调(callback)发生在哪些位置?(提示:在 IDA Pro中使用Ctrl+E组合键。)
恶意代码使用哪一种反调试技术使它在调试器中立即终止运行?如何避免这种检查?
当你禁用反调试技术后,你在调试器中看到的命令行密码是什么?
调试器中找到的密码在命令行中运行有效吗?
哪种反调试技术为调试器和命令行设置不同的密码?如何防御它们?
当使用静态分析法分析这个二进制文件时,你看到了哪些字符串?
当运行这个二进制文件时会发生什么?
如何重命名它,才能使这个二进制文件正常运行?
这个恶意代码使用了哪些反调试技术?
对每一种反调试技术而言,如果恶意代码确定它运行在调试器中,它将做什么?
为什么反调试技术在这个恶意代码中能够成功?
恶意代码使用了什么域名?
该API查询PEB结构中的IsDebugger标志位, 未调试返回
0
调试状态返回
1
该API查询PEB结构中的IsDebugger标志位, 未调试返回
0
调试状态返回
1
与IsDebuggerPresent原理一致,此API可以检测其他进程的调试状态,只需传入进程句柄。
通过传入自身进程句柄可检测自身调试状态 未调试返回
0
调试状态返回
1
与IsDebuggerPresent原理一致,此API可以检测其他进程的调试状态,只需传入进程句柄。
通过传入自身进程句柄可检测自身调试状态 未调试返回
0
调试状态返回
1
该API用于提取一个进程的状态信息,第一个参数为进程句柄,第二个参数为信息类型,当传入类型为
0x7
(ProcessBebugPort)时会获取调试端口。 未调试返回
0
调试状态返回端调试口号
该API用于提取一个进程的状态信息,第一个参数为进程句柄,第二个参数为信息类型,当传入类型为
0x7
(ProcessBebugPort)时会获取调试端口。 未调试返回
0
调试状态返回端调试口号
此API含义: 输出一个字符串,当未处于调试状态时调用该API会设置错误码,处于调试状态时会正常输出。通过对比手动设置的错误码可以检测是否处于调试状态。 报错未调试,未报错则处于调试状态。
此API含义: 输出一个字符串,当未处于调试状态时调用该API会设置错误码,处于调试状态时会正常输出。通过对比手动设置的错误码可以检测是否处于调试状态。 报错未调试,未报错则处于调试状态。
PEB结构偏移为
2
的位置为BeingDebugger属性,宽度
1
字节, 未调试
0
调试状态为
1
[fs:[
0x30
]
+
2
]
-
> byte
PEB结构偏移为
2
的位置为BeingDebugger属性,宽度
1
字节, 未调试
0
调试状态为
1
[fs:[
0x30
]
+
2
]
-
> byte
ProcessHeap是PEB结构中Reserved4数组中一个未公开的属性,偏移为
0x18
,第一个堆头部有一个属性指明了是否为调试器创建。(ForceFlags
/
Flags)
XP: Flags位于堆头部偏移
0x10
处 [[fs:[
0x30
]
+
0x18
]
+
0x10
]
-
> dword
WIN7:Flags位于堆头部偏移
0x44
处 [[fs:[
0x30
]
+
0x18
]
+
0x44
]
-
> dword
0
未调试 不为
0
处于调试状态
ProcessHeap是PEB结构中Reserved4数组中一个未公开的属性,偏移为
0x18
,第一个堆头部有一个属性指明了是否为调试器创建。(ForceFlags
/
Flags)
XP: Flags位于堆头部偏移
0x10
处 [[fs:[
0x30
]
+
0x18
]
+
0x10
]
-
> dword
WIN7:Flags位于堆头部偏移
0x44
处 [[fs:[
0x30
]
+
0x18
]
+
0x44
]
-
> dword
0
未调试 不为
0
处于调试状态
NTGlobalFlag是PEB结构中另一个未公开位置,偏移
0x68
,这个值指明了如何创建堆结构。如果此处值为
0x70
则处于调试状态
0x70
含义: FLG_HEAP_ENABLE_TAIL_CHECK|FLG_HEAP_ENABLE_FREE_CHECK|FLG_HEAP_VALIDATE_PARAMETERS
[fs:[
30
]
+
0x68
]
-
> dword
=
=
0x70
?调试:非调试
NTGlobalFlag是PEB结构中另一个未公开位置,偏移
0x68
,这个值指明了如何创建堆结构。如果此处值为
0x70
则处于调试状态
0x70
含义: FLG_HEAP_ENABLE_TAIL_CHECK|FLG_HEAP_ENABLE_FREE_CHECK|FLG_HEAP_VALIDATE_PARAMETERS
[fs:[
30
]
+
0x68
]
-
> dword
=
=
0x70
?调试:非调试
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
该注册表项指定当应用程序发生错误时,触发哪一个调试器。默认情况下它被设置为“Dr.Watson”,若是变成了“OllyDbg”,则恶意程序可以确定他处于被调试的状态。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
该注册表项指定当应用程序发生错误时,触发哪一个调试器。默认情况下它被设置为“Dr.Watson”,若是变成了“OllyDbg”,则恶意程序可以确定他处于被调试的状态。
恶意代码还可能通过遍历磁盘文件寻找调试器文件名。
通过遍历进程寻找是否有黑名单中的进程来监测调试器,如od ida x32dbg之类的进程名
通过遍历进程寻找是否有黑名单中的进程来监测调试器,如od ida x32dbg之类的进程名
通过FindWindow可以遍历窗口寻找是否有疑似调试器的程序在运行
通过FindWindow可以遍历窗口寻找是否有疑似调试器的程序在运行
调试器断点常使用int3指令(
0xCC
)触发
3
号中断,因此在下断时会临时修改指令字节,程序可以通过开启线程循环扫描自身代码段数据查找是否存在
0xCC
字节数据。
调试器断点常使用int3指令(
0xCC
)触发
3
号中断,因此在下断时会临时修改指令字节,程序可以通过开启线程循环扫描自身代码段数据查找是否存在
0xCC
字节数据。
原理同断点扫描类似,程序首先计算自身代码段的CRC校验值,当运行时开启新线程循环计算自身进程代码段的CRC校验值并与预先定义好的值进行比较,若不同则说明代码段某一处或多出字节被修改,这时程序可以停止执行,转而执行伪装代码。
原理同断点扫描类似,程序首先计算自身代码段的CRC校验值,当运行时开启新线程循环计算自身进程代码段的CRC校验值并与预先定义好的值进行比较,若不同则说明代码段某一处或多出字节被修改,这时程序可以停止执行,转而执行伪装代码。
在调试时程序执行速度会大幅降低,通过比对两处代码的执行间隔可以推断出是否处于调试状态。
所需指令: rdstc 从开机到此刻的时钟数,存到EDX:EAX中
所需API: QueryPerformanceCounter 与 GetTickCount
在调试时程序执行速度会大幅降低,通过比对两处代码的执行间隔可以推断出是否处于调试状态。
所需指令: rdstc 从开机到此刻的时钟数,存到EDX:EAX中
所需API: QueryPerformanceCounter 与 GetTickCount
OutputDebugString参数为“
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s”使会使调试器崩溃。
OutputDebugString参数为“
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s
%
s”使会使调试器崩溃。
使用了BeingDebugged ProcessHeap NTGlobalFlag, 通过搜索fs:
30
可以发现有几十处代码,可见这个样本反调试很多
使用了BeingDebugged ProcessHeap NTGlobalFlag, 通过搜索fs:
30
可以发现有几十处代码,可见这个样本反调试很多
当检测到调试器时会调用
401000
处代码进行自删除
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!