转自看雪
ZD?LsD 3 原文地址 TuwSJS7 ZQ\O|
n8 5Yk| [1]引言
o(i?_4E ;,KT+!H$ 本文对基于Windows NT的操作系统上使用的几种反调试技术进行了分类和介绍。
1OK~*=/4 FSZ :}Q 反调试技术是程序检测它是否在调试器(Debugger)下运行的方法。反调试技术被用于商业可执行程序的保护,加壳工具以及病毒等,以杜绝或减缓软件被逆(向工程)
\rbvlO?} L6=5]?B= 我们假设程序是在r3 debugger下进行分析的,例如Windows平台上的OD。本文面向逆向工程师和恶意软件分析师。
Q^lgtb 请注意,我们将纯粹谈论通用的反调试和反跟踪技术。此文不会涉及特定的调试器检测,例如窗口或进程的枚举,注册表扫描等。
cR6#$-a
(H|^Ow5 [2]反调试和反跟踪技术
]INt9Pvqm RBeQT=B8~ - 利用内存差
wD pL9 q |' kC9H[> (1) kernel32!IsDebuggerPresent
:N4?W}r. 如果正在调试进程,则IsDebuggerPresent返回1,否则返回0。此API只读取PEB!BeingDebugged 的byte-flag(位于PEB结构中的偏移 2)。
un
N*L 绕过它就只需要 PEB!BeingDebugged 设置为 0
Zqg
AgN@ 例:
TPKm>5g - call IsDebuggerPresent
- test eax, eax
- jne @DebuggerDetected
- ...
!1+!;R@&H> srU*1jD) (2) PEB!IsDebugged
,UT :wpc^i y8di-d3_ 该字段引用进程的Process Environment Block(进程环境块/区间?)中的第二个字节. 被调试期间会由系统设置为非0, 该字节可以被重置为0,而不会对程序的执行过程产生影响(因为这是一个信息标志).
hZO=$Mm4p @A%\;oo 例:
i[e-dT:*R - mov eax, fs:[30h]
- mov eax, byte [eax+2]
- test eax, eax
- jne @DebuggerDetected
- ...
K;g6V!U y
0fI7:e3 (3) PEB!NtGlobalFlags
"?
5@j/
e` 当有进程被创建时,系统会设置一些标志,这些标志将定义各种API对此进程的行为。这些标志可以在PEB中被读取,位于偏移量0x68的DWORD中.
eGrxS;NY 默认情况下,根据是否在Debugger下创建了进程而设置不同的NtGlobalFlags标志。如果调试了该进程,将出现一些控制ntdll中的常规堆栈操作的flags:
Zr!CT5C5 F(去)L(掉)G_HEAP_ENABLE_TAIL_CHECK,
6bpO#&T F(去)L(掉)G_HEAP_ENABLE_FREE_CHECK,
!V0)eC50 F(去)L(掉)G_HEAP_VALIDATE_PARAMETERS.
q+67Wc= 可以通过重置NtGlobalFlags字段来绕过这种反调试技术。
0ARj3 例:
.uo:fxbd2 - mov eax, fs:[30h]
- mov eax, [eax+68h]
- and eax, 0x70
- test eax, eax
- jne @DebuggerDetected
- ...
&nn+X%m9g s(Fxi|v; (4) Heap flags(堆栈标志)
d6e]aO=g v kW2& 如前所述,NtGlobalFlags标注常规堆栈将会出现什么样的行为。虽然很容易修改PEB字段,但如果堆栈的行为与程序未被调试的时候的行为方式不同,则可能会出现问题。这是一个挺厉害的反调试机制,因为进程堆很多,并且它们的chunks可以单独受F(去)L(掉)G_HEAP_* flags(例如chunk尾端)的影响。堆栈头部也会受到影响。例如,检查堆栈头中的字段ForceFlags(偏移量0x10)可用于检测Debugger的存在与否。
Ymcc|u6 $" 2\xEMec 有两种简单的方法来避开这种检测机制:
M ~6k[ew KJ6:ZTbW - 1.创建一个未被调试的进程,并在创建进程后附加调试器(一个简单的解决方案是创建进程挂起,直至运行达到Entry-Point,将Entry-Point跳转至无限循环,恢复进程,附加调试器,并恢复原始Entry-Point)。
VSc)0eyn MOP#to)k& - 2.通过注册表项“HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options”强制NtGlobalFlags为我们要调试的进程赋值:创建一个名为你要运行的进程名称的子键(非值),并在这个子键,String值“GlobalFlags”设置为空。
$olITe"$g 7/c9azmC 例:
>D$NEO^ - mov eax, fs:[30h]
- mov eax, [eax+18h] ;process heap
- mov eax, [eax+10h] ;heap flags
- test eax, eax
- jne @DebuggerDetected
- ...
='.G,aJ9 lz0'E'%{P (5) Vista anti-debug (没起名字)
NK(; -~{P lH
oV>k 我估计没人再用Vista了,再加上我对Vista机制也不熟悉,就偷了个懒。原文如下:
c6F8z75U }z wHUf9q1 Here's an anti-debug specific to Windows Vista that I found by comparing memory dumps of a program running with and without control of a debugger. I'm not sure of its realiability, but it's worth mentionning (tested on Windows Vista 32 bits, SP0, English version).
6:Zd,N= b@N*W] When a process is debugged, its main thread TEB, at offset 0xBFC, contains a pointer to a unicode string referencing a system dll. Moreover, the string follows this pointer (therefore, located at offset 0xC00 in the TEB). If the process is not debugged, the pointer is set to NULL and the string is not present.
nTXM/ :P\RiaZAT Example:
nc>Ae`"( - call GetVersion
- cmp al, 6
- jne @NotVista
- push offset _seh
- push dword fs:[0]
- mov fs:[0], esp
- mov eax, fs:[18h] ; teb
- add eax, 0BFCh
- mov ebx, [eax] ; pointer to a unicode string
- test ebx, ebx ; (ntdll.dll, gdi32.dll,...)
- je @DebuggerNotFound
- sub ebx, eax ; the unicode string follows the
- sub ebx, 4 ; pointer
- jne @DebuggerNotFound
- ;debugger detected if it reaches this point
- ;...
7|7sA'1cM kD4J{\ - 利用系统差
q;qY#wD@ - z"D_5 (1) NtQueryInformationProcess
\]p[DYBY# G2_l}q~ ntdll!NtQueryInformationProcess 是ZwQueryInformationProcess系统调用的打包
7`t[|o %VzCeS9 它的原型如下:
~_yz\;# - NTSYSAPI NTSTATUS NTAPI NtQueryInformationProcess(
- IN HANDLE ProcessHandle,
- IN PROCESS_INFORMATION_CLASS ProcessInformationClass,
- OUT PVOID ProcessInformation,
- IN ULONG ProcessInformationLength,
- OUT PULONG ReturnLength
- );
F,$ypGr ]`n6H[6O 当被call时的ProcessInformationClass被设置为7(ProcessDebugPort constant)时,进程被调试的话,系统会将ProcessInformation 设置为 -1.
lJXihr y{uN+QS 这是一个强大的反调试机制,没有很简单的方法来绕开。但是,如果跟踪程序,则可以在syscall返回时修改ProcessInformation。
VTJ,;p_UH F5EsaF'e4 另一种解决方案是使用系统驱动Hook ZwNtQueryInformationProcess
c9xc@G! zF=E5TL-,4 绕过NtQueryInformationProcess将能绕过许多反调试机制的检测(例如CheckRemoteDebuggerPresent或UnhandledExceptionFilter)
RVe UQ% 4>8'.8S 例:
?Kf?Z`9 *Y ^U@Erc#d - push 0
- push 4
- push offset isdebugged
- push 7 ;ProcessDebugPort
- push -1
- call NtQueryInformationProcess
- test eax, eax
- jne @ExitError
- cmp isdebugged, 0
- jne @DebuggerDetected
- ...
08 aZU 0RGSv!w (2) kernel32!CheckRemoteDebuggerPresent
b+ v!3| ejPK-jxCa/ 此API有两个参数:Process Handle,Ptr* DWORD. 如果调用成功,则在调试进程时,DWORD值将设置为1。
K.2l)aRd D4CiB"g3* 内部来说,此API将ProcessInformationClass设置为ProcessDebugPort (7)后调用ntdll!NtQueryInformationProcess
x4bj?=+ 79Si^n1\ 例:
_,9/g^< - push offset isdebugged
- push -1
- call CheckRemoteDebuggerPresent
- test eax, eax
- jne @DebuggerDetected
- ...
i_qR&X }c0EGoU}? (3) UnhandledExceptionFilter
Wfyap)y n42XqR 当发生异常时,使用Windows XP SP>=2,Windows 2003和Windows Vista时,操作系统处理异常的常用方法是:
q6
Rr? l?LP:;S - 如果有异常,将控制权传递给每个进程的Vectored Exception Handlers。
_0]{kB.$_ Snf1vH - 如果异常未被处理,则将控制权传递给每个线程顶部SEH handler,由生成异常的线程中的FS:[0]指向。如果异常未被SEH中的前一个SEH handler所处理,则SEH形成SEH链并且依次调用SEH。
\ HZ]=B#0 OBmmOswg~ - 如果任何先前的处理方法尚未成功处理异常,则最终的SEH处理程序(由系统设置)将调用kernel32!UnhandledExceptionFilter。该函数将决定它应该做什么,具体取决于是否调试过程。
i\u m;\ h4dT N} - 如果未被调试,它将调用用户定义的过滤器函数(通过kernel32!SetUnhandledExceptionFilter设置)。
V
9i[dF 3XomnL{ - 如果已被调试,程序将被终止。
LTc=D h$y0>eMWs UnhandledExceptionFilter中的调试器检测是使用ntdll!NtQueryInformationProcess进行的。
MfNguh Z3]ut#` 例:
~Uw
<E:?v - push @not_debugged
- call SetUnhandledExceptionFilter
- xor eax, eax
- mov eax, dword [eax] ; trigger exception
- ;program terminated if debugged
- ;...
- @not_debugged:
- ;process the exception
- ;continue the execution
- ;...
00)=3@D Yv [j5\:x 6H_7M(f -----------------------------------------------分割线-----------------------------------------------------------------------
yzQ^KqLH an9k2F.) 今天实在太晚了,明天还得早起,就先翻译这些,如果觉得还需要继续翻译的请留言咯。:D
/SDDCZ`;|c /iekww^54