在調試一些病毒程序的時候,可能會碰到一些反調試技術,也就是說,被調試的程序可以檢測到自己是否被調試器附加了,如果探知自己正在被調試,肯定是有人試圖反匯編啦之類的方法破解自己。為了了解如何破解反調試技術,首先我們來看看反調試技術。
一、Windows API方法
Win32提供了兩個API, IsDebuggerPresent和CheckRemoteDebuggerPresent可以用來檢測當前進程是否正在被調試,以IsDebuggerPresent函數為例,例子如下:
BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);
破解方法很簡單,就是在系統里將這兩個函數hook掉,讓這兩個函數一直返回false就可以了,網上有很多做hook API工作的工具,也有很多工具源代碼是開放的,所以這里就不細談了
二、查詢進程PEB的BeingDebugged標志位
當進程被調試器所附加的時候,操作系統會自動設置這個標志位,因此在程序里定期查詢這個標志位就可以了,例子如下:
bool PebIsDebuggedApproach() 8 R& ]/ {7 V6 T' z, K
{
char result = 0; q" V3 N9 u5 @# q% d+ E$ J
__asm
{
// 進程的PEB地址放在fs這個寄存器位置上 " C* e( ], k7 m
mov eax, fs:[30h] 7 C/ M0 X; b- G. L' S+ x, S" V
// 查詢BeingDebugged標志位 6 J5 o, `6 U: a
mov al, BYTE PTR [eax + 2] & k5 `; M9 v( _6 [
mov result, al
}
) n& r& d8 i5 c# t$ P8 s4 K
return result != 0; , d; p" C' q, u8 I8 \
}
三、查詢進程PEB的NtGlobal標志位 6 p6 C* _ T- V/ @' l. }
2 g; ~6 k* y# S) R
" \( O( p& i' H, f" T& K
跟第二個方法一樣,當進程被調試的時候,操作系統除了修改BeingDebugged這個標志位以外,還會修改其他幾個地方,其中NtDll中一些控制堆(Heap)操作的函數的標志位就會被修改,因此也可以查詢這個標志位,例子如下:
bool PebNtGlobalFlagsApproach() I V7 g0 J& G9 P2 {2 j$ F# ]
{
int result = 0;
__asm . ]& d" Z1 D$ h. m) d
{ " v+ s- m( Z$ c2 T* z8 f
// 進程的PEB
mov eax, fs:[30h]
// 控制堆操作函數的工作方式的標志位
mov eax, [eax + 68h] " c7 F2 s2 \7 ^7 d7 O5 y; [
// 操作系統會加上這些標志位FLG_HEAP_ENABLE_TAIL_CHECK, " O7 I& t, {9 r: g. g) V {9 J
// FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
// 它們的並集就是x70 5 W5 z6 _$ a( b D" `8 u$ W
// ' z6 B) L' P1 g! |
// 下面的代碼相當於C/C++的 5 W3 w5 s$ ?! Y3 u( ^+ N4 _* _
// eax = eax & 0x70 # C* f! ^$ B' y
and eax, 0x70
mov result, eax
} ; _7 j* E4 {% y) {# u
( `1 B: r, ?8 K# O6 f/ K3 T
return result != 0; + c5 L# A! R# A' ?
} - T/ K/ B) X/ O
6 k" i* H( ?# l4 O8 D2 y, V, V: L7 {
四、查詢進程堆的一些標志位 " A) t* ^' n% E. ?. Z( F& L
# x- t; ^1 U/ d7 ?+ O4 s2 P
這個方法是第三個方法的變種,只要進程被調試,進程在堆上分配的內存,在分配的堆的頭信息里,ForceFlags這個標志位會被修改,因此可以通過判斷這個標志位的方式來反調試。因為進程可以有很多的堆,因此只要檢查任意一個堆的頭信息就可以了,所以這個方法貌似很強大,例子如下:
0 P# v: B: N+ z' n* |) f
bool HeapFlagsApproach()
{
int result = 0;
& L9 V% Z l) p) {, e9 Q
__asm
{ ' k) s' P6 p+ `& A, M9 T: f4 F5 G+ o
// 進程的PEB
mov eax, fs:[30h] ' K6 Q" {4 [! |; s: T
// 進程的堆,我們隨便訪問了一個堆,下面是默認的堆
mov eax, [eax + 18h]
// 檢查ForceFlag標志位,在沒有被調試的情況下應該是 4 z1 D s$ I: ^% q P5 _
mov eax, [eax + 10h] 7 @7 s3 o8 Y* q5 Z W
mov result, eax % H# F6 I4 z3 Y( h7 ?
}
. m4 Q7 Z3 Q; p3 B
return result != 0;
} 8 w; p9 N& P6 k1 K: P7 L+ H
S( R5 V0 X! c# r4 n' C2 ~0 L
5 p" G# F! V3 W) @" |; P: V
- G9 N+ ^4 j6 W0 u
& B; E) _/ ]. \( B: r8 @
五、使用NtQueryInformationProcess函數
% d8 A0 |& v, T$ p- }9 J: l0 T
NtQueryInformationProcess函數是一個未公開的API,它的第二個參數可以用來查詢進程的調試端口。如果進程被調試,那么返回的端口值會是-1,否則就是其他的值。由於這個函數是一個未公開的函數,因此需要使用LoadLibrary和GetProceAddress的方法獲取調用地址,示例代碼如下:
, o) s( p( Q+ [
A0 j" n9 P2 i
// 聲明一個函數指針。 7 r7 W/ w. T7 N! c
typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
HANDLE processHandle,
PROCESSINFOCLASS processInformationClass,
PVOID processInformation,
ULONG processInformationLength,
PULONG returnLength); 7 z( z/ }+ I! u6 f4 ?, s: W
( B7 _7 ]2 W; K9 a. l* S% z7 a: z
bool NtQueryInformationProcessApproach() # W5 g& c# g& Z6 Y: G8 g5 X
{ ! k9 t/ F" e4 i4 _% T8 @
int debugPort = 0;
HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll ")); 7 R, @8 S6 c q- Y+ G
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess"); & n' @! [6 D8 n$ X3 f
if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) ) 9 ]/ @& h" y) n- o" F) o" E
printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n"); 4 O# M$ r9 [4 G' K6 x
else
return debugPort == -1;
8 z6 T$ A6 M8 f& u- U8 p
return false;
}
, P; K$ f5 g5 v! n- {
! Y5 F; y9 a; E( s( t# o3 ]3 L- z: O
六、NtSetInformationThread方法
" W' Q4 a$ o3 y% R% |. C/ ]* l
這個也是使用Windows的一個未公開函數的方法,你可以在當前線程里調用NtSetInformationThread,調用這個函數時,如果在第二個參數里指定0x11這個值(意思是ThreadHideFromDebugger),等於告訴操作系統,將所有附加的調試器統統取消掉。示例代碼: 1 J" g8 F, u# \3 A
3 b2 h) Z4 P" ]) m- E" R
// 聲明一個函數指針。
typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle, 2 q7 n ?% d. U5 S
THREADINFOCLASS threadInformationClass,
PVOID threadInformation, 9 K) ]# i# c7 |2 }4 @
ULONG threadInformationLength); 6 F* o' |: S: {/ d5 f
" {; s4 Q1 q" x: _) \( C! q$ v
void NtSetInformationThreadApproach() 1 x8 s( E- n0 q# }
{
HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread"); 5 b$ k4 e7 o, Z0 }
NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0); : v; o' h2 p' W/ q) q% g* Z V7 s
}
- u& ~) T9 o6 T q$ s* e+ Y
( f3 V( |! s$ ~8 y0 R9 U
6 u/ F" t( ~( e! S5 m" f
七、觸發異常的方法 2 b' V& l& z# j" m
: \* L/ a( M) g
這個技術的原理是,首先,進程使用SetUnhandledExceptionFilter函數注冊一個未處理異常處理函數A,如果進程沒有被調試的話,那么觸發一個未處理異常,會導致操作系統將控制權交給先前注冊的函數A;而如果進程被調試的話,那么這個未處理異常會被調試器捕捉,這樣我們的函數A就沒有機會運行了。
8 c8 L# _+ a! _+ ?% v
# y% j* t5 a: P- B0 ]# I$ N% y, k
這里有一個技巧,就是觸發未處理異常的時候,如果跳轉回原來代碼繼續執行,而不是讓操作系統關閉進程。方案是在函數A里修改eip的值,因為在函數A的參數_EXCEPTION_POINTERS里,會保存當時觸發異常的指令地址,所以在函數A里根據這個指令地址修改寄存器eip的值就可以了,示例代碼如下: / |$ r: ]% X5 O
" z6 t# \6 s2 o3 E* M; k
) H# x9 U/ t$ w5 H2 `
// 進程要注冊的未處理異常處理程序A / {1 m! t/ S r( A b2 o7 z
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei) $ M. a( ~0 s# M! d7 m
{ ' B/ V" Q: D5 J) r7 G8 [5 K1 ?
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
pei->ContextRecord->Eax);
// 修改寄存器eip的值
pei->ContextRecord->Eip += 2; 5 Y7 B9 A {5 N- D1 B. \
// 告訴操作系統,繼續執行進程剩余的指令(指令保存在eip里),而不是關閉進程 + \4 i- I( [/ F9 z% H1 `0 e/ n
return EXCEPTION_CONTINUE_EXECUTION; 7 [9 s6 K. R4 x7 i6 M x3 o
}
bool UnhandledExceptionFilterApproach()
{ 5 h% s9 O! v8 }- Q
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
__asm
{ . j2 P# |9 H( b
// 將eax清零
xor eax, eax
// 觸發一個除零異常
div eax 9 q8 l3 f* v0 c4 Q
} 6 Q5 N7 g- J8 D2 h( z9 F y1 u
0 _2 j* U% D9 n" M
return false; 6 w7 ]! y1 r* p( r
} \/ z* ?( k" W
八、調用DeleteFiber函數 * o9 g' y6 r; y" o
- h, g$ t3 F4 v
如果給DeleteFiber函數傳遞一個無效的參數的話,DeleteFiber函數除了會拋出一個異常以外,還是將進程的LastError值設置為具體出錯原因的代號。然而,如果進程正在被調試的話,這個LastError值會被修改,因此如果調試器繞過了第七步里講的反調試技術的話,我們還可以通過驗證LastError值是不是被修改過來檢測調試器的存在,示例代碼:
bool DeleteFiberApproach() % _5 i% ?, d5 g
{ % Y0 y) Q+ b1 }7 o: Y& _2 O# e# o
char fib[1024] = {0};
// 會拋出一個異常並被調試器捕獲
DeleteFiber(fib);
// 0x57的意思是ERROR_INVALID_PARAMETER
return (GetLastError() != 0x57); & \9 m2 J, F" T" V
}
一、Windows API方法
Win32提供了兩個API, IsDebuggerPresent和CheckRemoteDebuggerPresent可以用來檢測當前進程是否正在被調試,以IsDebuggerPresent函數為例,例子如下:
BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);
破解方法很簡單,就是在系統里將這兩個函數hook掉,讓這兩個函數一直返回false就可以了,網上有很多做hook API工作的工具,也有很多工具源代碼是開放的,所以這里就不細談了
二、查詢進程PEB的BeingDebugged標志位
當進程被調試器所附加的時候,操作系統會自動設置這個標志位,因此在程序里定期查詢這個標志位就可以了,例子如下:
bool PebIsDebuggedApproach() 8 R& ]/ {7 V6 T' z, K
{
char result = 0; q" V3 N9 u5 @# q% d+ E$ J
__asm
{
// 進程的PEB地址放在fs這個寄存器位置上 " C* e( ], k7 m
mov eax, fs:[30h] 7 C/ M0 X; b- G. L' S+ x, S" V
// 查詢BeingDebugged標志位 6 J5 o, `6 U: a
mov al, BYTE PTR [eax + 2] & k5 `; M9 v( _6 [
mov result, al
}
) n& r& d8 i5 c# t$ P8 s4 K
return result != 0; , d; p" C' q, u8 I8 \
}
三、查詢進程PEB的NtGlobal標志位 6 p6 C* _ T- V/ @' l. }
2 g; ~6 k* y# S) R
" \( O( p& i' H, f" T& K
跟第二個方法一樣,當進程被調試的時候,操作系統除了修改BeingDebugged這個標志位以外,還會修改其他幾個地方,其中NtDll中一些控制堆(Heap)操作的函數的標志位就會被修改,因此也可以查詢這個標志位,例子如下:
bool PebNtGlobalFlagsApproach() I V7 g0 J& G9 P2 {2 j$ F# ]
{
int result = 0;
__asm . ]& d" Z1 D$ h. m) d
{ " v+ s- m( Z$ c2 T* z8 f
// 進程的PEB
mov eax, fs:[30h]
// 控制堆操作函數的工作方式的標志位
mov eax, [eax + 68h] " c7 F2 s2 \7 ^7 d7 O5 y; [
// 操作系統會加上這些標志位FLG_HEAP_ENABLE_TAIL_CHECK, " O7 I& t, {9 r: g. g) V {9 J
// FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
// 它們的並集就是x70 5 W5 z6 _$ a( b D" `8 u$ W
// ' z6 B) L' P1 g! |
// 下面的代碼相當於C/C++的 5 W3 w5 s$ ?! Y3 u( ^+ N4 _* _
// eax = eax & 0x70 # C* f! ^$ B' y
and eax, 0x70
mov result, eax
} ; _7 j* E4 {% y) {# u
( `1 B: r, ?8 K# O6 f/ K3 T
return result != 0; + c5 L# A! R# A' ?
} - T/ K/ B) X/ O
6 k" i* H( ?# l4 O8 D2 y, V, V: L7 {
四、查詢進程堆的一些標志位 " A) t* ^' n% E. ?. Z( F& L
# x- t; ^1 U/ d7 ?+ O4 s2 P
這個方法是第三個方法的變種,只要進程被調試,進程在堆上分配的內存,在分配的堆的頭信息里,ForceFlags這個標志位會被修改,因此可以通過判斷這個標志位的方式來反調試。因為進程可以有很多的堆,因此只要檢查任意一個堆的頭信息就可以了,所以這個方法貌似很強大,例子如下:
0 P# v: B: N+ z' n* |) f
bool HeapFlagsApproach()
{
int result = 0;
& L9 V% Z l) p) {, e9 Q
__asm
{ ' k) s' P6 p+ `& A, M9 T: f4 F5 G+ o
// 進程的PEB
mov eax, fs:[30h] ' K6 Q" {4 [! |; s: T
// 進程的堆,我們隨便訪問了一個堆,下面是默認的堆
mov eax, [eax + 18h]
// 檢查ForceFlag標志位,在沒有被調試的情況下應該是 4 z1 D s$ I: ^% q P5 _
mov eax, [eax + 10h] 7 @7 s3 o8 Y* q5 Z W
mov result, eax % H# F6 I4 z3 Y( h7 ?
}
. m4 Q7 Z3 Q; p3 B
return result != 0;
} 8 w; p9 N& P6 k1 K: P7 L+ H
S( R5 V0 X! c# r4 n' C2 ~0 L
5 p" G# F! V3 W) @" |; P: V
- G9 N+ ^4 j6 W0 u
& B; E) _/ ]. \( B: r8 @
五、使用NtQueryInformationProcess函數
% d8 A0 |& v, T$ p- }9 J: l0 T
NtQueryInformationProcess函數是一個未公開的API,它的第二個參數可以用來查詢進程的調試端口。如果進程被調試,那么返回的端口值會是-1,否則就是其他的值。由於這個函數是一個未公開的函數,因此需要使用LoadLibrary和GetProceAddress的方法獲取調用地址,示例代碼如下:
, o) s( p( Q+ [
A0 j" n9 P2 i
// 聲明一個函數指針。 7 r7 W/ w. T7 N! c
typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
HANDLE processHandle,
PROCESSINFOCLASS processInformationClass,
PVOID processInformation,
ULONG processInformationLength,
PULONG returnLength); 7 z( z/ }+ I! u6 f4 ?, s: W
( B7 _7 ]2 W; K9 a. l* S% z7 a: z
bool NtQueryInformationProcessApproach() # W5 g& c# g& Z6 Y: G8 g5 X
{ ! k9 t/ F" e4 i4 _% T8 @
int debugPort = 0;
HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll ")); 7 R, @8 S6 c q- Y+ G
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess"); & n' @! [6 D8 n$ X3 f
if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) ) 9 ]/ @& h" y) n- o" F) o" E
printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n"); 4 O# M$ r9 [4 G' K6 x
else
return debugPort == -1;
8 z6 T$ A6 M8 f& u- U8 p
return false;
}
, P; K$ f5 g5 v! n- {
! Y5 F; y9 a; E( s( t# o3 ]3 L- z: O
六、NtSetInformationThread方法
" W' Q4 a$ o3 y% R% |. C/ ]* l
這個也是使用Windows的一個未公開函數的方法,你可以在當前線程里調用NtSetInformationThread,調用這個函數時,如果在第二個參數里指定0x11這個值(意思是ThreadHideFromDebugger),等於告訴操作系統,將所有附加的調試器統統取消掉。示例代碼: 1 J" g8 F, u# \3 A
3 b2 h) Z4 P" ]) m- E" R
// 聲明一個函數指針。
typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle, 2 q7 n ?% d. U5 S
THREADINFOCLASS threadInformationClass,
PVOID threadInformation, 9 K) ]# i# c7 |2 }4 @
ULONG threadInformationLength); 6 F* o' |: S: {/ d5 f
" {; s4 Q1 q" x: _) \( C! q$ v
void NtSetInformationThreadApproach() 1 x8 s( E- n0 q# }
{
HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread"); 5 b$ k4 e7 o, Z0 }
NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0); : v; o' h2 p' W/ q) q% g* Z V7 s
}
- u& ~) T9 o6 T q$ s* e+ Y
( f3 V( |! s$ ~8 y0 R9 U
6 u/ F" t( ~( e! S5 m" f
七、觸發異常的方法 2 b' V& l& z# j" m
: \* L/ a( M) g
這個技術的原理是,首先,進程使用SetUnhandledExceptionFilter函數注冊一個未處理異常處理函數A,如果進程沒有被調試的話,那么觸發一個未處理異常,會導致操作系統將控制權交給先前注冊的函數A;而如果進程被調試的話,那么這個未處理異常會被調試器捕捉,這樣我們的函數A就沒有機會運行了。
8 c8 L# _+ a! _+ ?% v
# y% j* t5 a: P- B0 ]# I$ N% y, k
這里有一個技巧,就是觸發未處理異常的時候,如果跳轉回原來代碼繼續執行,而不是讓操作系統關閉進程。方案是在函數A里修改eip的值,因為在函數A的參數_EXCEPTION_POINTERS里,會保存當時觸發異常的指令地址,所以在函數A里根據這個指令地址修改寄存器eip的值就可以了,示例代碼如下: / |$ r: ]% X5 O
" z6 t# \6 s2 o3 E* M; k
) H# x9 U/ t$ w5 H2 `
// 進程要注冊的未處理異常處理程序A / {1 m! t/ S r( A b2 o7 z
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei) $ M. a( ~0 s# M! d7 m
{ ' B/ V" Q: D5 J) r7 G8 [5 K1 ?
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
pei->ContextRecord->Eax);
// 修改寄存器eip的值
pei->ContextRecord->Eip += 2; 5 Y7 B9 A {5 N- D1 B. \
// 告訴操作系統,繼續執行進程剩余的指令(指令保存在eip里),而不是關閉進程 + \4 i- I( [/ F9 z% H1 `0 e/ n
return EXCEPTION_CONTINUE_EXECUTION; 7 [9 s6 K. R4 x7 i6 M x3 o
}
bool UnhandledExceptionFilterApproach()
{ 5 h% s9 O! v8 }- Q
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
__asm
{ . j2 P# |9 H( b
// 將eax清零
xor eax, eax
// 觸發一個除零異常
div eax 9 q8 l3 f* v0 c4 Q
} 6 Q5 N7 g- J8 D2 h( z9 F y1 u
0 _2 j* U% D9 n" M
return false; 6 w7 ]! y1 r* p( r
} \/ z* ?( k" W
八、調用DeleteFiber函數 * o9 g' y6 r; y" o
- h, g$ t3 F4 v
如果給DeleteFiber函數傳遞一個無效的參數的話,DeleteFiber函數除了會拋出一個異常以外,還是將進程的LastError值設置為具體出錯原因的代號。然而,如果進程正在被調試的話,這個LastError值會被修改,因此如果調試器繞過了第七步里講的反調試技術的話,我們還可以通過驗證LastError值是不是被修改過來檢測調試器的存在,示例代碼:
bool DeleteFiberApproach() % _5 i% ?, d5 g
{ % Y0 y) Q+ b1 }7 o: Y& _2 O# e# o
char fib[1024] = {0};
// 會拋出一個異常並被調試器捕獲
DeleteFiber(fib);
// 0x57的意思是ERROR_INVALID_PARAMETER
return (GetLastError() != 0x57); & \9 m2 J, F" T" V
}
jpg改rar
