反調試技術,惡意代碼會用它識別自身是否被調試,或者讓調試器失效,給反病毒工程師們制造麻煩,拉長提取特征碼的時間線,本章將具體總結常見的反調試基礎的實現原理以及如何過掉這些反調試手段,從而讓我們能夠繼續分析惡意代碼。
反調試技術的實現方式有很多,最簡單的一種實現方式莫過於直接調用Windows系統提供給我們的API函數,這些API函數中有些專門用來檢測調試器的,有些則是可被改造為用於探測調試器是否存在的工具,多數情況下,調用系統API函數實現反調試是不明智的,原因很簡單,目標主機通常會安裝主動防御系統,而作為主動防御產品默認會加載RootKit驅動掛鈎這些敏感函數的使用,如果被非法調用則會提示錯誤信息,病毒作者通常會使用匯編自行實現這些類似於系統提供給我們的反調試函數,並不會使用系統的API,這樣依附於API的主動防御的系統將會失效。
1.加載調試符號鏈接文件並放入d:/symbols
目錄下.
0:000> .sympath srv*d:\symbols*http://msdl.microsoft.com/download/symbols
0:000> .reload
Reloading current modules
2.位於fs:[0x30]
的位置就是PEB結構的指針,接着我們分析如何得到的該指針,並通過通配符找到TEB結構的名稱.
0:000> dt ntdll!*teb*
ntdll!_TEB
ntdll!_GDI_TEB_BATCH
ntdll!_TEB_ACTIVE_FRAME
ntdll!_TEB_ACTIVE_FRAME_CONTEXT
ntdll!_TEB_ACTIVE_FRAME_CONTEXT
3.接着可通過dt命令,查詢下ntdll!_TEB
結構,如下可看到0x30
處ProcessEnvironmentBlock
存放的正是PEB結構.
0:000> dt -rv ntdll!_TEB
struct _TEB, 66 elements, 0xfb8 bytes
+0x000 NtTib : struct _NT_TIB, 8 elements, 0x1c bytes # NT_TIB結構
+0x018 Self : Ptr32 to struct _NT_TIB, 8 elements, 0x1c bytes # NT_TIB結構
+0x020 ClientId : struct _CLIENT_ID, 2 elements, 0x8 bytes # 保存進程與線程ID
+0x02c ThreadLocalStoragePointer : Ptr32 to Void
+0x030 ProcessEnvironmentBlock : Ptr32 to struct _PEB, 65 elements, 0x210 bytes # PEB結構
偏移地址0x18
是_NT_TIB
結構,也就是指向自身偏移0x0
的位置.
0:000> r $teb
$teb=7ffdf000
0:000> dd $teb+0x18
7ffdf018 7ffdf000 00000000 00001320 00000c10
7ffdf028 00000000 00000000 7ffd9000 00000000
而!teb
地址加0x30
正是PEB
的位置,可以使用如下命令驗證.
0:000> dd $teb+0x30
7ffdf030 7ffd9000 00000000 00000000 00000000
7ffdf040 00000000 00000000 00000000 00000000
0:000> !teb
TEB at 7ffdf000
ExceptionList: 0012fd0c
StackBase: 00130000
StackLimit: 0012e000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdf000
EnvironmentPointer: 00000000
ClientId: 00001320 . 00000c10
RpcHandle: 00000000
Tls Storage: 00000000
PEB Address: 7ffd9000 # 此處teb地址
上方的查詢結果可得知偏移位置fs:[0x18]
正是TEB的基址TEB:7ffdf000
0:000> dd fs:[0x18]
003b:00000018 7ffdf000 00000000 000010f4 00000f6c
003b:00000028 00000000 00000000 7ffda000 00000000
0:000> dt _teb 0x7ffdf000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID # 這里保存進程與線程信息
0:000> dt _CLIENT_ID 0x7ffdf000 # 查看進程詳細結構
ntdll!_CLIENT_ID
+0x000 UniqueProcess : 0x0012fd0c Void # 獲取進程PID
+0x004 UniqueThread : 0x00130000 Void # 獲取線程PID
上方TEB首地址我們知道是fs:[0x18]
,接着我們通過以下公式計算得出本進程的進程ID.
在Windows系統中如果想要獲取到PID進程號,可以使用NtCurrentTeb()
這個系統API來實現,但這里我們手動實現該API的獲取過程.
獲取進程PID:
#include "stdafx.h"
#include <Windows.h>
DWORD GetPid(){
DWORD dwPid=0;
__asm
{
mov eax,fs:[0x18] // 獲取PEB地址
add eax,0x20 // 加0x20得到進程PID
mov eax,[eax]
mov dwPid,eax
}
return dwPid;
}
int main()
{
printf("%d\n",GetPid());
return 0;
}
獲取線程PID:
#include "stdafx.h"
#include <Windows.h>
DWORD GetPid(){
DWORD dwPid=0;
__asm
{
mov eax,fs:[0x18] // 獲取PEB地址
add eax,0x20 // 加0x20得到進程PID
add eax,0x04 // 加0x04得到線程PID
mov eax,[eax]
mov dwPid,eax
}
return dwPid;
}
int main()
{
printf("%d\n",GetPid());
return 0;
}
通過標志反調試: 下方的調試標志BeingDebugged
是Char類型,為1表示調試狀態.為0表示沒有調試.可以用於反調試.
0:000> dt _peb
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
#include "stdafx.h"
#include <Windows.h>
int main()
{
DWORD dwIsDebug = 0;
__asm
{
mov eax, fs:[0x18]; // 獲取TEB
mov eax, [eax + 0x30]; // 獲取PEB
movzx eax, [eax + 2]; // 獲取調試標志
mov dwIsDebug,eax
}
if (1 == dwIsDebug)
{
printf("正在被調試");
}
else
{
printf("沒有被調試");
}
return 0;
}
通過API反調試:
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
int main()
{
STARTUPINFO temp;
temp.cb = sizeof(temp);
GetStartupInfo(&temp);
if (temp.dwFlags != 1)
{
ExitProcess(0);
}
printf("程序沒有被反調試");
return 0;
}
反調試與繞過思路
BeingDebugged 屬性反調試: 進程運行時,位置FS:[30h]指向PEB的基地址,為了實現反調試技術,惡意代碼通過這個位置來檢查BeingDebugged標志位是否為1,如果為1則說明進程被調試。
1.首先我們可以使用 dt _teb
命令解析一下TEB的結構,如下TEB結構的起始偏移為0x0,而0x30的位置指向的是 ProcessEnvironmentBlock
也就是指向了進程環境塊。
0:000> dt _teb
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB // 此處是進程環境塊
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
只需要在進程環境塊的基礎上 +0x2
就能定位到線程環境塊TEB中 BeingDebugged
的標志,此處的標志位如果為1則說明程序正在被調試,為0則說明沒有被調試。
0:000> dt _peb
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
我們手動來驗證一下,首先線程環境塊地址是007f1000
在此基礎上加0x30
即可得到進程環境快的基地址007ee000
繼續加0x2即可得到BeingDebugged
的狀態 ffff0401
需要 byte=1
0:000> r $teb
$teb=007f1000
0:000> dd 007f1000 + 0x30
007f1030 007ee000 00000000 00000000 00000000
007f1040 00000000 00000000 00000000 00000000
0:000> r $peb
$peb=007ee000
0:000> dd 007ee000 + 0x2
007ee002 ffff0401 0000ffff 0c400112 19f0775f
007ee012 0000001b 00000000 09e0001b 0000775f
梳理一下知識點我們可以寫出一下反調試代碼,本代碼單獨運行程序不會出問題,一旦被調試器附加則會提示正在被調試。
#include <stdio.h>
#include <windows.h>
int main()
{
BYTE IsDebug = 0;
__asm{
mov eax, dword ptr fs:[0x30]
mov bl, byte ptr [eax+ 0x2]
mov IsDebug, bl
}
/* 另一種反調試實現方式
__asm{
push dword ptr fs:[0x30]
pop edx
mov al, [edx + 2]
mov IsDebug,al
}
*/
if (IsDebug != 0)
printf("本程序正在被調試. %d", IsDebug);
else
printf("程序沒有被調試.");
getchar();
return 0;
}
如果惡意代碼中使用該種技術阻礙我們正常調試,該如何繞過呢?如下我們只需要在命令行中執行dump fs:[30]+2
來定位到BeingDebugged的位置,並將其數值改為0然后運行程序,會發現反調試已經被繞過了。
ProcessHeap 屬性反調試: 該屬性是一個未公開的屬性,它被設置為加載器為進程分配的第一個堆的位置,ProcessHeap位於PEB結構的0x18處,第一個堆頭部有一個屬性字段,這個屬性叫做ForceFlags和Flags屬性偏移為10,該屬性為0說明程序沒有被調試,非0則說明被調試。
0:000> dt !_peb
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
0:000> r $peb
$peb=006e1000
0:000> dd 006e1000+18
006e1018 00ca0000 775f09e0 00000000 00000000
0:000> dd 00ca0000 + 10
00ca0010 00ca00a4 00ca00a4 00ca0000 00ca0000
要實現反反調試,只需要將 00ca0000 + 10
位置的值修改為0即可,執行dump ds:[fs:[30] + 0x18] + 0x10
定位到修改即可。