脱壳的艺术.docx
《脱壳的艺术.docx》由会员分享,可在线阅读,更多相关《脱壳的艺术.docx(41页珍藏版)》请在冰豆网上搜索。
![脱壳的艺术.docx](https://file1.bdocx.com/fileroot1/2023-2/9/1a247b73-6f72-4eda-be09-c8e2b330c999/1a247b73-6f72-4eda-be09-c8e2b330c9991.gif)
脱壳的艺术
脱壳的艺术#EzhtuH_xn
MarkVincentYason|M7C_=z='
概述:
脱壳是门艺术——脱壳既是一种心理挑战,同时也是逆向领域最为激动人心的智力游戏之一。
为了甄别或解决非常难的反逆向技巧,逆向分析人员有时不得不了解操作系统的一些底层知识,聪明和耐心也是成功脱壳的关键。
这个挑战既牵涉到壳的创建者,也牵涉到那些决心躲过这些保护的脱壳者。
jsQHg_2Vd
本文主要目的是介绍壳常用的反逆向技术,同时也探讨了可以用来躲过或禁用这些保护的技术及公开可用的工具。
这些信息将使研究人员特别是恶意代码分析人员在分析加壳的恶意代码时能识别出这些技术,当这些反逆向技术阻碍其成功分析时能决定下一步的动作。
第二个目的,这里介绍的信息也会被那些计划在软件中添加一些保护措施用来减缓逆向分析人员分析其受保护代码的速度的研究人员用到。
当然没有什么能使一个熟练的、消息灵通的、坚定的逆向分析人员止步的。
eP_,bF_c_
关键词:
逆向工程、壳、保护、反调试、反逆向ME$_J4
2_
1简介 RC/45:
_hZZ
在逆向工程领域,壳是最有趣的谜题之一。
在解谜的过程中,逆向分析人员会获得许多关于系统底层、逆向技巧等知识。
_tYXE$i
壳(这个术语在本文中既指压缩壳也包括加密壳)是用来防止程序被分析的。
它们被商业软件合法地用于防止信息披露、篡改及盗版。
可惜恶意软件也基于同样的理由在使用壳,只不过动机不良。
[/YuI@C,@
由于大量恶意软件存在加壳现象,研究人员和恶意代码分析人员为了分析代码,开始学习脱壳的技巧。
但是随着时间的推移,为防止逆向分析人员分析受保护的程序并成功脱壳,新的反逆向技术也被不断地添加到壳中。
并且战斗还在继续,新的反逆向技术被开发的同时逆向分析人员也在针锋相对地发掘技巧、研究技术并开发工具来对付它们。
R{fJ__"Q5'
本文主要关注于介绍壳所使用的反逆向技术,同时也探讨了躲过/禁用这些保护措施的工具及技术。
可能有些壳通过抓取进程映像(dump)能够轻易被搞定,这时处理反逆向技术似乎没有必要,但是有些情况下加密壳的代码需要加以跟踪和分析,例如:
^$5__0_[
需要躲过部分加密壳代码以便抓取进程映像、让输入表重建工具正确地工作。
_\XPGAuEo
深入分析加密壳代码以便在一个反病毒产品中整合进脱壳支持。
f4fBUZ^_A
此外,当反逆向技术被恶意程序直接应用,以防止跟踪并分析其恶意行为时,熟悉反逆向技术也是很有价值的。
_WFiX=@SS_
本文绝不是一个完整的反逆向技术的清单,因为它只涵盖了壳中常用的、有趣的一些技术。
建议读者参阅最后一节的链接和图书资料,以了解更多其他逆向及反逆向的技术。
I_H;sVT$M
笔者希望您觉得这些材料有用,并能应用其中的技术。
脱壳快乐!
J_wB___'B
58
Ce>_*~_
2调试器检测技术 v_!
8=B21
本节列出了壳用来确定进程是否被调试或者系统内是否有调试器正在运行的技术。
这些调试器检测技术既有非常简单(明显)的检查,也有涉及到nativeAPIs和内核对象的。
Q_J3#~GYNr
2.1PEB.BeingDebuggedFlag:
IsDebuggerPresent()g_L_B(A\yG
最基本的调试器检测技术就是检测进程环境块(PEB)1中的BeingDebugged标志。
kernel32!
IsDebuggerPresent()API检查这个标志以确定进程是否正在被用户模式的调试器调试。
Wk_<_fNHg_
下面显示了IsDebuggerPresent()API的实现代码。
首先访问线程环境块(TEB)2得到PEB的地址,然后检查PEB偏移0x02位置的BeingDebugged标志。
VD_$5Djq
mov eax,largefs:
18h_W!
w_of-1
mov eax,[eax+30h]_=3_.dgtH
movzx eax,byteptr[eax+2]3)_y1q>CQf
retnz_!
IA_]v
除了直接调用IsDebuggerPresent(),有些壳会手工检查PEB中的BeingDebugged标志以防逆向分析人员在这个API上设置断点或打补丁。
_.;]W_cC<3
示例nwU],{(Hgr
下面是调用IsDebuggerPresent()API和使用PEB.BeingDebugged标志确定调试器是否存在的示例代码。
!
fzS'pkk.
;callkernel32!
IsDebuggerPresent()*_S__a_g
call [IsDebuggerPresent]A?
H#_bR_As
test eax,eaxgx.\H_3y__
jnz .debugger_found)Ka-_vX)D@
_d____d__
;checkPEB.BeingDebuggeddirectly_"E\vd_h_k
Mov eax,dword[fs:
0x30] ;EAX= TEB.ProcessEnvironmentBlockk|/VNV(=0
movzx eax,byte[eax+0x02] ;AL = PEB.BeingDebugged5@2_Rl>B$
test eax,eax_z_R<___{z
jnz .debugger_foundN__V_*2_
由于这些检查很明显,壳一般都会用后面章节将会讨论的GHOFFICE过滤词语代码或者反—反编译技术进行混淆。
gD_9
C_A_*
对策[c3_!
xHt5O
人工将PEB.BeingDebugged标志置0可轻易躲过这个检测。
在数据窗口中Ctrl+G(前往表达式)输入fs:
[30],可以在OllyDbg中查看PEB数据。
"d_u(BZw_
另外Ollyscript命令"dbh"可以补丁这个标志。
n_nyT,e%_
dbh3N__KN__W_
最后,OllyAdvanced3插件有置BeingDebugged标志为0的选项。
%unn{92)
_
2.2 PEB.NtGlobalFlag,Heap.HeapFlags,Heap.ForceFlagsH1k)yax4_
PEB.NtGlobalFlag PEB另一个成员被称作NtGlobalFlag(偏移0x68),壳也通过它来检测程序是否用调试器加载。
通常程序没有被调试时,NtGlobalFlag成员值为0,如果进程被调试这个成员通常值为0x70(代表下述标志被设置):
GJ__B+]b-
FLG_HEAP_ENABLE_TAIL_CHECK(0X10)xRWfZ3E_#
FLG_HEAP_ENABLE_FREE_CHECK(0X20)7_KlL_%_\
FLG_HEAP_VALIDATE_PARAMETERS(0X40)__I0F[Z\U
这些标志是在ntdll!
LdrpInitializeExecutionOptions()里设置的。
请注意PEB.NtGlobalFlag的默认值可以通过gflags.exe工具或者在注册表以下位置创建条目来修改:
lf(_+]k30
HKLM\Software\Microsoft\WindowsNt\CurrentVersion\ImageFileExecutionOptionsX?
_haHM#]
HeapFlags由于NtGlobalFlag标志的设置,堆也会打开几个标志,这个变化可以在ntdll!
RtlCreateHeap()里观测到。
通常情况下为进程创建的第一个堆会将其Flags和ForceFlags4分别设为0x02(HEAP_GROWABLE)和0。
然而当进程被调试时,这两个标志通常被设为0x50000062(取决于NtGlobalFlag)和0x40000060(等于FlagsAND0x6001007D)。
默认情况下当一个被调试的进程创建堆时下列附加的堆标志将被设置:
_Nf4__@m|#
HEAP_TAIL_CHECKING_ENABLED(0X20)oc"p5Y3,Os
HEAP_FREE_CHECKING_ENABLED(0X40)w__f_]Wm
示例aUMiRm-__
下面的示例代码检查PEB.NtGlobalFlag是否等于0,为进程创建的第一个堆是否设置了附加标志(PEB.ProcessHeap):
;ebx=PEBH_2],auBY
Mov ebx,[fs:
0x30]8;qOsV)UDT
eV_"_,W_
;CheckifPEB.NtGlobalFlag!
=0=4_)8a"7#.
Cmp dword[ebx+0x68],0s{_*rBX8N
jne .debugger_found/\fR6|tJ
___k6OO\=
;eax=PEB.ProcessHeap"Xq$0~c
Mov eax,[ebx+0x18]^d>m`*p_x
m_!
INbI__h
;CheckPEB.ProcessHeap.FlagsU_ChLWf|_'
Cmp dword[eax+0x0c],2*k__\;G?
jne .debugger_foundF`;q9A__7I0_^_
;CheckPEB.ProcessHeap.ForceFlagsP/S,dh_s(
Cmp dword[eax+0x10],0_0S_wu]OE
jne .debugger_found__
U_s[F@
对策_2_*W|s7cc
可以将PEB.NtGlobalFlag和PEB.HeapProcess标志补丁为进程未被调试时的相应值。
下面是一个补丁上述标志的ollyscript示例:
\0*dKgN__
Var peb$5#DU___F/
var patch_addr_'_0Q_/oU
var process_heap(Ms0pm-#t_
Y_|#_//retrievePEBviaahardcodedTEBaddress(firstthread:
0x7ffde000)}Oe4w_EYN)
Mov peb,[7ffde000+30]_kB_?
Uw_#
CszZr>_Z
//patchPEB.NtGlobalFlag_6_G,_cc
Lea patch_addr,[peb+68]F
)aF.'$-/
mov [patch_addr],0:
VR%I;g;
{H_=<5___
//patchPEB.ProcessHeap.Flags/ForceFlagsWxJaE;`Ige
Mov process_heap,[peb+18]qP9`p4c8_i
lea patch_addr,[process_heap+0c]b-#oE{_(\'
mov [patch_addr],2_
0_2[*b__
lea patch_addr,[process_heap+10]_x45F-_w{_
mov [patch_addr],0IR8qF__WDZ
同样地OllyAdvanced插件有设置PEB.NtGlobalFlag和PEB.ProcessHeap的选项。
5IRUG_)Icr
2.3DebugPort:
CheckRemoteDebuggerPresent()/NtQueryInformationProcess()Ih5CtcE1'd
Kernel32!
CheckRemoteDebuggerPresent()是另一个可以用于确定是否有调试器被附加到进程的API。
这个API内部调用了ntdll!
NtQueryInformationProcess(),调用时ProcessInformationclass参数为ProcessDebugPort(7)。
而NtQueryInformationProcess()检索内核结构EPROCESS5的DebugPort成员。
非0的DebugPort成员意味着进程正在被用户模式的调试器调试。
如果是这样的话,ProcessInformation将被置为0xFFFFFFFF,否则ProcessInformation将被置为0。
]_0")i_Y_
Kernel32!
CheckRemoteDebuggerPresent()接受2个参数,第1个参数是进程句柄,第2个参数是一个指向boolean变量的指针,如果进程被调试,该变量将包含TRUE返回值。
+WCV"_m_
BOOLCheckRemoteDebuggerPresent(\O*W/9__+
HANDLE hProcess,_0<_R___q
PBOOL pbDebuggerPresent=)!
_~t_/_
)IRsyy\[kp8
ntdll!
NtQueryInformationProcess()有5个参数。
为了检测调试器的存在,需要将ProcessInformationclass参数设为ProcessDebugPort(7):
_fTGVG
NTSTATUS NTAPI NtQueryInformationProcess(lgefTTGX)
HANDLE ProcessHandle,cZ_,}_1?
!
PROCESSINFOCLASS ProcessInformationClass,MwD8a<_2Dg
PVOID ProcessInformation,ZkF_6AF__
ULONG ProcessInformationLength,:
!
wt/Y
PULONG ReturnLengthV+$f__h_2t
)_W_S\Ir-_B
示例X__JA];9_^
下面的例子显示了如何调用CheckRemoteDebuggerPresent()和NtQueryInformationProcess()来检测当前进程是否被调试:
whQJ_Wi=ck
;usingKernel32!
CheckRemoteDebuggerPresent()4udW__6U
lea eax,[.bDebuggerPresent][I_(
Yn_
push eax ;pbDebuggerPresent_`__c__"
push 0xffffffff ;hProcess6_P_02=
call [CheckRemoteDebuggerPresent]L_vcmp dword[.bDebuggerPresent],0e_IR_F+>
jne .debugger_found__pL__,l
IJDE{__)_
;usingntdll!
NtQueryInformationProcess(ProcessDebugPort)*eM_MfxFl
lea eax,[.dwReturnLen]EvF_[h_:
C2
push eax ;ReturnLength__=_G`g-E2
push 4 ;ProcessInformationLengths13__3N_?
_
lea eax,[.dwDebugPort]r'MA_$PiS'
push eax ;ProcessInformationLHSbc!
_Y'.
push ProcessDebugPort ;ProcessInformationClass(7)^_U__~QQ
push 0xffffffff ;ProcessHandle6i@*L\Dl
call [NtQueryInformationProcess]_)T^_x_Dx
cmp dword[.dwDebugPort],0%a!
__gN__
jne .debugger_foundSq{@4F}d
对策t8`_wO+4@_
一种方法是在NtQueryInformationProcess()返回的地方设置断点,当这个断点被断下来后,将ProcessInformation补丁为0。
下面是自动执行这个方法的ollyscript示例:
X!
ml_C5_1
var bp_NtQueryInformationProcess^
_!
gq_x__
t(jE9t|2e6
//setabreakpointhandleriU
k#hL_LC
eob bp_handler_NtQueryInformationProcessSYa_O'_c_
n#@Qd!
uzM
//setabreakpointwhereNtQueryInformationProcessreturnsILqBa_:
_J_
gpa "NtQueryInformationProcess","ntdll.dll"6@eF|_GoP
find $RESULT,#C21400# //retn14qH__vU_Bx0
mov bp_NtQueryInformationProcess,$RESULT|gIE$rt-~W
bphws bp_NtQueryInformationProcess,"X"_{`_,)}
run__mx_q__Y
Dp!
91NgBp
bp_handler_NtQueryInformationProcess:
1___G_K>&;
I2Imb9k~B
//ProcessInformationClass==ProcessDebugPort?
9
\c]I0)3p
cmp [esp+8],7_u_x__B`_
jne bp_handler_NtQueryInformationProcess_continue;S_'?
_l_0
<_fM}_Kk_
//patchProcessInformationto0=__*"8N-FU
mov patch_addr,[esp+c]l_o(_C3o_'
mov [patch_addr],0O^_j*"#_f
G_g;:
)k\
//clearbreakpoint_\2e^_x
bphwc bp_NtQueryInformationProcess;RRw-|/W_m
e6Y>Bk_
bp_handler_NtQueryInformationProcess_continue:
IEM_a/[n/
runYrB-_n___
OllyAdvanced插件有一个patchNtQueryInformationProcess()的选项,这个补丁涉及注入一段代码来操纵NtQueryInformationProcess()的返回值。
w2'q_9_pB+
2.4DebuggerInterruptsCj<_8rS4+
在调试器中步过INT3和INT1指令的时候,由于调试器通常会处理这些调试中断,所以异常处理例程默认情况下将不会被调用,DebuggerInterrupts就利用了这个事实。
这样壳可以在异常处理例程中设置标志,通过INT指令后如果这些标志没有被设置则意味着进程正在被调试。
另外,kernel32!
DebugBreak()内部是调用了INT3来实现的,有些壳也会使用这个API。
M{{kO@P"9
示例dN)8_____r
这个例子在异常处理例程中设置EAX的值为0xFFFFFFFF(通过CONTEXT6记录)以此来判断异常处理例程是否被调用:
$_E.D>5^%7
;setexceptionhandlerA*?
P_H_`bY
push .exeception_handler1o%_#kf_
push dword[fs:
0]ZBK0`7#&EH
mov [fs:
0],espd_A__x?
r|u[36NmA_
;resetflag(EAX)invokeint3#_A__z#_0=
xor eax,eax_LS_ou]{R_
int3XNWtX-[^@
X$w_e\t_
;restoreexceptionhandlerCF{_bYf^%
pop dword[fs:
0]c_Z_Ncplt8
add esp,4_Hyyb0c^