17
反跟蹤技術 15 好的軟體保護都要與反跟蹤技術結合在一起。如果沒有反跟蹤技術,軟體等 於直接裸露在解密者的面前。這裡所說的反跟蹤是泛指,包括防調試器、防監視 工具等內容。本章將討論一些常用的反跟蹤方法,讀者可以根據實際情況在自己 的軟體中採用相關的技術和程式碼。 一個壞的微小的機制,如果不加以及時地引導、調節,會給社會帶來非常大 的危害,戲稱為“龍捲風”或“風暴”;一個好的微小的機制,只要正確指引,經 過一段時間的努力,將會產生轟動效應,或稱為“革命”。 15.1.1 BeingDebugged Win32 API 為程式提供了 IsDebuggerPresent 判斷自己是否處於調試狀態,懶 惰的程式師總是用它。來看一下實作程式碼: // debug.c BOOL APIENTRY IsDebuggerPresent(VOID) { return NtCurrentPeb() BeingDebugged; } 這個函式讀取了目前行程 PEB 中的 BeingDebugged 旗標,每個執行中的行程 擁有一個名為 PEBProcess Environment Block,行程環境區塊)的結構,對它有 少許瞭解會有助於理解後面的內容。PEB 結構的內容: Offset Elements name Type 15.1 BeingDebugged 引發的蝴蝶效應 015-NEW.indd 1 2009/7/16 上午 10:40:20

15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

反跟蹤技術第15章

好的軟體保護都要與反跟蹤技術結合在一起。如果沒有反跟蹤技術,軟體等

於直接裸露在解密者的面前。這裡所說的反跟蹤是泛指,包括防調試器、防監視

工具等內容。本章將討論一些常用的反跟蹤方法,讀者可以根據實際情況在自己

的軟體中採用相關的技術和程式碼。

一個壞的微小的機制,如果不加以及時地引導、調節,會給社會帶來非常大

的危害,戲稱為“龍捲風”或“風暴”;一個好的微小的機制,只要正確指引,經

過一段時間的努力,將會產生轟動效應,或稱為“革命”。

15.1.1 BeingDebugged

Win32 API為程式提供了 IsDebuggerPresent判斷自己是否處於調試狀態,懶惰的程式師總是用它。來看一下實作程式碼:

// debug.cBOOL APIENTRY IsDebuggerPresent(VOID){ return NtCurrentPeb()→ BeingDebugged;}

這個函式讀取了目前行程 PEB中的 BeingDebugged旗標,每個執行中的行程擁有一個名為 PEB(Process Environment Block,行程環境區塊)的結構,對它有少許瞭解會有助於理解後面的內容。PEB結構的內容:

Offset Elements name Type

15.1 由 BeingDebugged引發的蝴蝶效應

015-NEW.indd 1 2009/7/16 上午 10:40:20

Page 2: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-2

+0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 SpareBool : UChar +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void +0x00c Ldr : Ptr32 _PEB_LDR_DATA +0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS +0x014 SubSystemData : Ptr32 Void +0x018 ProcessHeap : Ptr32 Void +0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION +0x020 FastPebLockRoutine : Ptr32 Void +0x024 FastPebUnlockRoutine : Ptr32 Void +0x028 EnvironmentUpdateCount : Uint4B +0x02c KernelCallbackTable : Ptr32 Void +0x030 SystemReserved : [1] Uint4B +0x034 ExecuteOptions : Pos 0, 2 Bits +0x034 SpareBits : Pos 2, 30 Bits +0x038 FreeList : Ptr32 _PEB_FREE_BLOCK +0x03c TlsExpansionCounter : Uint4B +0x040 TlsBitmap : Ptr32 Void +0x044 TlsBitmapBits : [2] Uint4B +0x04c ReadOnlySharedMemoryBase : Ptr32 Void +0x050 ReadOnlySharedMemoryHeap : Ptr32 Void +0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void +0x058 AnsiCodePageData : Ptr32 Void +0x05c OemCodePageData : Ptr32 Void +0x060 UnicodeCaseTableData : Ptr32 Void +0x064 NumberOfProcessors : Uint4B +0x068 NtGlobalFlag : Uint4B +0x070 CriticalSectionTimeout : _LARGE_INTEGER +0x078 HeapSegmentReserve : Uint4B

接下來的問題當然是如何找到 PEB地址。它儲存在另一個名為執行緒環境區塊(Thread Environment Block,TEB)的結構之內。

Windows在調入行程,建立執行緒時,作業系統均會為每個執行緒分配TEB,而且 FS段暫存器總是被設置成使得地址 FS:0指向目前執行緒的 TEB資料(單 CPU機器在任何時刻系統中只有一個執行緒在執行),這就為存取 TEB資料提供了途徑,如圖 15.1所示。

015-NEW.indd 2 2009/7/16 上午 10:40:22

Page 3: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-3

圖15.1 執行執行緒區塊的結構

再來瞭解一下 TEB的結構,請注意 +30h處的偏移欄位。

+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 +0x0c4 CurrentLocale : Uint4B +0x0c8 FpSoftwareStatusRegister : Uint4B +0x0cc SystemReserved1 : [54] Ptr32 Void +0x1a4 ExceptionCode : Int4B +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK +0x1bc SpareBytes1 : [24] UChar +0x1d4 GdiTebBatch : _GDI_TEB_BATCH +0x6b4 RealClientId : _CLIENT_ID +0x6bc GdiCachedProcessHandle : Ptr32 Void +0x6c0 GdiClientPID : Uint4B +0x6c4 GdiClientTID : Uint4B +0x6c8 GdiThreadLocalInfo : Ptr32 Void +0x6cc Win32ClientInfo : [62] Uint4B +0x7c4 glDispatchTable : [233] Ptr32 Void

015-NEW.indd 3 2009/7/16 上午 10:40:24

Page 4: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-4

+0xb68 glReserved1 : [29] Uint4B +0xbdc glReserved2 : Ptr32 Void +0xbe0 glSectionInfo : Ptr32 Void +0xbe4 glSection : Ptr32 Void +0xbe8 glTable : Ptr32 Void +0xbec glCurrentRC : Ptr32 Void +0xbf0 glContext : Ptr32 Void +0xbf4 LastStatusValue : Uint4B +0xbf8 StaticUnicodeString : _UNICODE_STRING +0xc00 StaticUnicodeBuffer : [261] Uint2B +0xe0c DeallocationStack : Ptr32 Void +0xe10 TlsSlots : [64] Ptr32 Void +0xf10 TlsLinks : _LIST_ENTRY +0xf18 Vdm : Ptr32 Void +0xf1c ReservedForNtRpc : Ptr32 Void +0xf20 DbgSsReserved : [2] Ptr32 Void +0xf28 HardErrorsAreDisabled : Uint4B +0xf2c Instrumentation : [16] Ptr32 Void +0xf6c WinSockData : Ptr32 Void +0xf70 GdiBatchCount : Uint4B +0xf74 InDbgPrint : UChar +0xf75 FreeStackOnTermination : UChar +0xf76 HasFiberData : UChar +0xf77 IdealProcessor : UChar +0xf78 Spare3 : Uint4B +0xf7c ReservedForPerf : Ptr32 Void +0xf80 ReservedForOle : Ptr32 Void +0xf84 WaitingOnLoaderLock : Uint4B +0xf88 Wx86Thread : _Wx86ThreadState +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void +0xf98 ImpersonationLocale : Uint4B +0xf9c IsImpersonating : Uint4B +0xfa0 NlsCache : Ptr32 Void +0xfa4 pShimData : Ptr32 Void +0xfa8 HeapVirtualAffinity : Uint4B +0xfac CurrentTransactionHandle : Ptr32 Void +0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME +0xfb4 SafeThunkCall : UChar +0xfb5 BooleanSpare : [3] UChar

015-NEW.indd 4 2009/7/16 上午 10:40:25

Page 5: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-5

00h處的 TIB(Thread Information Block,執行緒資訊區塊)結構為:

+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x00c SubSystemTib : Ptr32 Void +0x010 FiberData : Ptr32 Void +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB,指向 TEB結構的指標

每個行程都有自己的 PEB,Windows一般透過 TEB間接得到 PEB的地址。即透過以下語句獲得:

mov eax,fs:[18h] //獲得目前執行緒的 TEB地址

mov eax,[eax+30h] //在 TEB偏移 30h處獲得 PEB地址

TIB+18h處為 Self,是 TIB的反身指標,指向 PEB首地址,因此可以省略而直接使用 fs:[30h]得到自己行程的 PEB。為了免去繁冗的定義,這裡給出一個內嵌組語的簡化版 IsDebuggerPresent:

BOOL MyIsDebuggerPresent(VOID){ __asm { mov eax, fs:[0x30] //在位於 TEB偏移 30h處獲得 PEB地址

movzx eax, byte ptr [eax+2] //獲得 PEB偏移 2h處 BeingDebugged的值

}}

根據這個原理,OllyDbg可以用外掛清除 BeingDebugged以隱藏調試器。雖然在 Windows 2000/NT 系統中 PEB 本身在大多數情況下被映射到

7FFDF000h處,不過值得注意的是,從Windows XP SP2後系統引入了一個特性:PEB地址隨機化。每個行程的 PEB位址不固定,大概有 14種可能。系統建立行程時設置 PEB的位址,呼叫 NtCreateProcess/NtCreateProcessEx,

依次轉向 PspCreateProcess/MmCreatePeb/MiCreatePebOrTeb,在MiCreatePebOrTeb函式中根據目前時間計算隨機值:

PVOID HighestVadAddress;LARGE_INTEGER CurrentTime;HighestVadAddress = (PVOID) ((PCHAR)MM_HIGHEST_VAD_ADDRESS + 1);KeQueryTickCount (&CurrentTime);

015-NEW.indd 5 2009/7/16 上午 10:40:26

Page 6: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-6

CurrentTime.LowPart &= ((X64K >> PAGE_SHIFT) - 1);if (CurrentTime.LowPart <= 1) { CurrentTime.LowPart = 2; }HighestVadAddress = (PVOID) ((PCHAR)HighestVadAddress - (CurrentTime.LowPart<< PAGE_SHIFT));

所以不能認為 PEB就在 7FFDF000h,不同的行程 PEB位址會不一樣。當然也就不能用本行程 FS:[18h]的指標去讀寫其他行程的內容。正確的方法是使用下面的函式,取得某個執行緒段選擇子的線性位址:

BOOL GetThreadSelectorEntry( HANDLE hThread, DWORD dwSelector, LPLDT_ENTRY lpSelectorEntry);

如果喜歡 Native API,也可以透過 NtQueryInformationProcess獲得 PEB:

ULONG GetPebBase(ULONG ProcessId){ HANDLE hProcess = NULL; PROCESS_BASIC_INFORMATION pbi = {0} ULONG peb = 0; ULONG cnt = 0; hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessId); if (hProcess != NULL) { if (NtQueryInformationProcess( hProcess, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), &cnt) == 0) { PebBase = (ULONG)pbi.PebBaseAddress; } CloseHandle(hProcess); }}

這裡採用 GetThreadSelectorEntry,下面這段程式碼就可以清除 BeingDebugged旗標了:

015-NEW.indd 6 2009/7/16 上午 10:40:27

Page 7: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-7

BOOL HideDebugger( HANDLE hThread,HANDLE hProcess){ CONTEXT ctx; LDT_ENTRY sel; DWORD fs; DWORD peb; SIZE_T bytesrw; WORD flag; ctx.ContextFlags = CONTEXT_SEGMENTS; if (!GetThreadContext(hThread, &ctx)) return FALSE; if (!GetThreadSelectorEntry(hThread, ctx.SegFs, &sel)) return FALSE; fs = (sel.HighWord.Bytes.BaseHi << 8 | sel.HighWord.Bytes.BaseMid) << 16 | sel.BaseLow; if (!ReadProcessMemory(hProcess, (LPCVOID)(fs + 0x30), &peb, 4, &bytesrw) || bytesrw != 4) return FALSE; if (!ReadProcessMemory(hProcess, (LPCVOID)(peb + 0x2), &flag, 2, &bytesrw) || bytesrw != 2) return FALSE; flag = 0; if (!WriteProcessMemory(hProcess, (LPCVOID)(peb + 0x2), &flag, 2, &bytesrw) || bytesrw != 2) return FALSE; return TRUE;}

現在讀者一定認為這個旗標太愚蠢了,事實上它比你想象的要複雜一些。

BeingDebugged雖然被消滅了,但是問題並不是這麼簡單。

15.1.2 NtGlobalFlag

先查看一下Windows 2000中的原始碼,BeingDebugged被清除之前發生了什麼事情。相關程式碼如下:

VOID LdrpInitialize ( IN PCONTEXT Context, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) // Routine Description:……

//#if DBG if (TRUE)//#else

015-NEW.indd 7 2009/7/16 上午 10:40:28

Page 8: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-8

// if (Peb→ BeingDebugged || Peb→ ReadImageFileExecOptions)//#endif { PWSTR pw; pw = (PWSTR)Peb→ ProcessParameters→ ImagePathName.Buffer; if (!(Peb→ ProcessParameters→ Flags & RTL_USER_PROC_PARAMS_NORMALIZED)) { pw = (PWSTR)((PCHAR)pw + (ULONG_PTR)(Peb→ ProcessParameters)); } UnicodeImageName.Buffer = pw; UnicodeImageName.Length = Peb→ ProcessParameters→ ImagePathName.Length; UnicodeImageName.MaximumLength = UnicodeImageName.Length;

LdrQueryImageFileExecutionOptions( &UnicodeImageName, L"DisableHeapLookaside", REG_DWORD, &RtlpDisableHeapLookaside, sizeof( RtlpDisableHeapLookaside ), NULL ); st = LdrQueryImageFileExecutionOptions( &UnicodeImageName, L"GlobalFlag", REG_DWORD, &Peb→ NtGlobalFlag, sizeof( Peb→ NtGlobalFlag ), NULL ); if (!NT_SUCCESS( st )) { if (Peb→ BeingDebugged) {// 這裡改寫了 NtGlobalFlag Peb→ NtGlobalFlag |= FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_VALIDATE_PARAMETERS; } }

可以看到如果 BeingDebugged被設為 TRUE,NtGlobalFlag中也會因此被設置這些旗標:

FLG_HEAP_ENABLE_FREE_CHECK,FLG_HEAP_ENABLE_TAIL_CHECK,FLG_HEAP_VALIDATE_PARAMETERS…

015-NEW.indd 8 2009/7/16 上午 10:40:30

Page 9: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-9

回顧一下 PEB的結構,+68h處就是 NtGlobalFlag,用WinHex比較記憶體可以發現,被調試時程式的 NtGlobalFlag=70h,正常情況下卻不是。因此得到一個改進的 IsDebuggerPresent函式:

BOOL MyIsDebuggerPresentEx(VOID){ __asm { mov eax, fs:[0x30] mov eax, [eax+0x68] and eax, 0x70 }}

ExeCryptor比較早用到 NtGlobalFlag做檢測,剛開始讓許多人莫名其妙了一段時間。注意原始碼中那個 LdrQueryImageFileExecutionOptions函式如果成功的話,就不會改寫 NtGlobalFlag了,這個函式事實上讀取註冊表了。註冊表內容:

HKLM\Software\Microsoft\Windows Nt\CurrentVersion\Image File Execution Options

如果在這裡建一個名為行程名、值為空的子鍵,那麼 NtGlobalFlag(及其引發的)的檢測都變得無效了。

現在深呼吸一下,逐漸清醒了,這個所謂的新發現也不過是一個旗標而已,

還是可以像 BeingDebugged一樣被清除掉,可惜就如引發它的 BeingDebugged一樣,雖然被毀滅,它留下的痕跡仍然存在。

15.1.3 Heap Magic

為了掌握 NtGlobalFlag所留下的痕跡,在WRK中找找線索。相關程式碼 如下:

PVOIDRtlCreateHeap ( IN ULONG Flags, IN PVOID HeapBase OPTIONAL, IN SIZE_T ReserveSize OPTIONAL, IN SIZE_T CommitSize OPTIONAL, IN PVOID Lock OPTIONAL, IN PRTL_HEAP_PARAMETERS Parameters OPTIONAL ) ……

015-NEW.indd 9 2009/7/16 上午 10:40:31

Page 10: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-10

if (NtGlobalFlag & FLG_HEAP_ENABLE_TAIL_CHECK) { Flags |= HEAP_TAIL_CHECKING_ENABLED; }if (NtGlobalFlag & FLG_HEAP_ENABLE_FREE_CHECK) { Flags |= HEAP_FREE_CHECKING_ENABLED; }if (NtGlobalFlag & FLG_HEAP_DISABLE_COALESCING) { Flags |= HEAP_DISABLE_COALESCE_ON_FREE; } Peb = NtCurrentPeb(); if (NtGlobalFlag & FLG_HEAP_VALIDATE_PARAMETERS) { Flags |= HEAP_VALIDATE_PARAMETERS_ENABLED; } if (NtGlobalFlag & FLG_HEAP_VALIDATE_ALL) { Flags |= HEAP_VALIDATE_ALL_ENABLED; }if (NtGlobalFlag & FLG_USER_STACK_TRACE_DB) { Flags |= HEAP_CAPTURE_STACK_BACKTRACES;} ……

#ifndef NTOS_KERNEL_RUNTIME // // In the non kernel case check if we are creating a debug heap // the test checks that skip validation checks is false.if (DEBUG_HEAP( Flags )) { return RtlDebugCreateHeap( Flags, HeapBase, ReserveSize, CommitSize, Lock, Parameters );}#endif // NTOS_KERNEL_RUNTIME

其中用到了 DEBUG_HEAP定義:

//heappriv.h#define HEAP_DEBUG_FLAGS (HEAP_VALIDATE_PARAMETERS_ENABLED | \ HEAP_VALIDATE_ALL_ENABLED | \ HEAP_CAPTURE_STACK_BACKTRACES | \ HEAP_CREATE_ENABLE_TRACING | \ HEAP_FLAG_PAGE_ALLOCS)#define DEBUG_HEAP(F) ((F & HEAP_DEBUG_FLAGS) && !(F & \HEAP_SKIP_VALIDATION_CHECKS))

015-NEW.indd 10 2009/7/16 上午 10:40:32

Page 11: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-11

NtGlobalFlag因為 BeingDebugged為 TRUE的緣故設置了 FLG_HEAP_VALIDATE_PARAMETERS,因此 RtlCreateHeap選擇用 RtlDebugCreateHeap建立調試堆積。再去翻一翻 RtlDebugCreateHeap函式中的片段:

PVOIDRtlDebugCreateHeap ( IN ULONG Flags, IN PVOID HeapBase OPTIONAL, IN SIZE_T ReserveSize OPTIONAL, IN SIZE_T CommitSize OPTIONAL, IN PVOID Lock OPTIONAL, IN PRTL_HEAP_PARAMETERS Parameters )……

Heap = RtlCreateHeap( Flags | HEAP_SKIP_VALIDATION_CHECKS | HEAP_TAIL_CHECKING_ENABLED | HEAP_FREE_CHECKING_ENABLED, HeapBase, ReserveSize, CommitSize, Lock, Parameters );

原來還是呼叫 RtlCreateHeap,看來關鍵起作用的旗標是:

HEAP_SKIP_VALIDATION_CHECKSHEAP_TAIL_CHECKING_ENABLEDHEAP_FREE_CHECKING_ENABLED

回到 RtlCreateHeap搜尋這些文字,第一個旗標看起來是為了防止從RtlCreateHeap到RtlDebugCreate Heap又回到RtlCreateHeap而做過多的重複工作。不過後面兩個周圍卻有一些有趣的內容:

// Otherwise if the flags indicate that we should fill heap then it it now.} else if (Heap→ Flags & HEAP_FREE_CHECKING_ENABLED) { RtlFillMemoryUlong( (PCHAR)(BusyBlock + 1), Size & ~0x3, ALLOC_HEAP_FILL );}// If the flags indicate that we should do tail checking then copy// the fill pattern right after the heap block.if (Heap→ Flags & HEAP_TAIL_CHECKING_ENABLED) { RtlFillMemory( (PCHAR)ReturnValue + Size,

015-NEW.indd 11 2009/7/16 上午 10:40:33

Page 12: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-12

CHECK_HEAP_TAIL_SIZE, CHECK_HEAP_TAIL_FILL ); BusyBlock→ Flags |= HEAP_ENTRY_FILL_PATTERN;}

沒想到調試堆積裡面還會被填充一些奇怪的東西,相關的定義也很容易找到:

//heap.h#define CHECK_HEAP_TAIL_SIZE HEAP_GRANULARITY#define CHECK_HEAP_TAIL_FILL 0xAB#define FREE_HEAP_FILL 0xFEEEFEEE#define ALLOC_HEAP_FILL 0xBAADF00D

既然堆積裡面被填充了東西,就可以編寫以下程式碼測試一下:

LPVOID GetHeap(VOID){ return HeapAlloc(GetProcessHeap(), NULL, 0x10);}

然後用 OllyDbg看看回傳的指標裡面都是什麼:

00153660 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA00153670 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA00153680 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA00153690 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA001536A0 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA……

00153770 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA 0D F0 AD BA00153780 EE FE EE AB AB AB AB AB AB AB AB FE EE FE EE FE00153790 00 00 00 00 00 00 00 00 0D 01 28 00 EE 14 EE 00001537A0 78 01 15 00 78 01 15 00 EE FE EE FE EE FE EE FE001537B0 EE FE EE FE EE FE EE FE EE FE EE FE EE FE EE FE001537C0 EE FE EE FE EE FE EE FE EE FE EE FE EE FE EE FE001537D0 EE FE EE FE EE FE EE FE EE FE EE FE EE FE EE FE001537E0 EE FE EE FE EE FE EE FE EE FE EE FE EE FE EE FE001537F0 EE FE EE FE EE FE EE FE EE FE EE FE EE FE EE FE

可以看到果然有很多的 BAADF00D和 FEEEFEEE,還有 ABABABAB。參考前面的內容,在 Image File Execution Options中建立一個鍵,看看正常情況下的堆積內容:

00153CA8 F0 03 15 00 F0 03 15 00 43 00 45 00 5C 00 3B 00 ? .? .C.E.\.;.00153CB8 4C 00 03 00 5C 00 57 00 D8 03 15 00 D8 03 15 00 L. .\.W.? .? .

015-NEW.indd 12 2009/7/16 上午 10:40:35

Page 13: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-13

00153CC8 57 00 53 00 5C 00 4D 00 69 00 63 00 72 00 6F 00 W.S.\.M.i.c.r.o.00153CD8 73 00 6F 00 66 00 74 00 2E 00 4E 00 45 00 54 00 s.o.f.t...N.E.T.00153CE8 5C 00 46 00 72 00 61 00 6D 00 65 00 77 00 6F 00 \.F.r.a.m.e.w.o.……

00153D78 63 00 72 00 6F 00 73 00 6F 00 66 00 74 00 20 00 c.r.o.s.o.f.t. .00153D88 56 00 69 00 73 00 75 00 61 00 6C 00 20 00 53 00 V.i.s.u.a.l. .S.00153D98 74 00 75 00 64 00 69 00 6F 00 5C 00 43 00 6F 00 t.u.d.i.o.\.C.o.00153DA8 6D 00 6D 00 6F 00 6E 00 5C 00 54 00 6F 00 6F 00 m.m.o.n.\.T.o.o.00153DB8 6C 00 73 00 5C 00 57 00 69 00 6E 00 4E 00 54 00 l.s.\.W.i.n.N.T.00153DC8 3B 00 44 00 3A 00 5C 00 50 00 72 00 6F 00 67 00 ;.D.:.\.P.r.o.g.

只是一堆沒有初始化的資料而已,沒有那些特殊的Magic旗標。寫一個程式,在堆積中搜索那些奇怪的旗標,如果出現了很多的話(比如 10次以上),那麼程式就被調試了:

LPVOID GetHeap(SIZE_T nSize){ return HeapAlloc(GetProcessHeap(), NULL, nSize);}

BOOL IsDebugHeap(VOID){ LPVOID HeapPtr; PDWORD ScanPtr; ULONG nMagic = 0;

HeapPtr = GetHeap(0x100);

ScanPtr = (PDWORD)HeapPtr; try { for(;;) { switch (*ScanPtr++) { case 0xABABABAB: case 0xBAADF00D: case 0xFEEEFEEE: nMagic++; break; } } } catch(...) { return (nMagic > 10) ? TRUE : FALSE;

015-NEW.indd 13 2009/7/16 上午 10:40:36

Page 14: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-14

}}

這裡用到了一個小伎倆,為了儘量完整地對堆積進行掃描,此處用無窮迴圈

一直向下搜尋記憶體,直到記憶體位址已經無效時,SEH會攔截錯誤,此時回傳統計的結果。那些奇怪的數字習慣上被稱為Magic,所以這個檢測技巧也被稱為HeapMagic。除了自己從堆積配置記憶體,ap0x在他的站點公佈了一段程式碼,從被調

試程式 PEB的 LDR_MODULE中也能搜索到那些用來填坑的旗標,事實上,Themida中發現了一字不差的檢測程式碼。

; MASM32 antiRing3Debugger example ; coded by ap0x; Reversing Labs: http://ap0x.headcoders.net

ASSUME FS:NOTHINGPUSH offset _SehExitPUSH DWORD PTR FS:[0]MOV FS:[0],ESP

; Get NtGlobalFlag 這裡 ap0x出現 bug,這裡獲取的是 PEBMOV EAX,DWORD PTR FS:[30h]

; Get LDR_MODULEMOV EAX,DWORD PTR[EAX+12]

; Note: This code works only on NT systems!

_loop:INC EAXCMP DWORD PTR[EAX],0FEEEFEEEhJNE _loopDEC [Tries]JNE _loop

PUSH 30hPUSH offset DbgFoundTitlePUSH offset DbgFoundTextPUSH 0CALL MessageBoxPUSH 0

015-NEW.indd 14 2009/7/16 上午 10:40:37

Page 15: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-15

CALL ExitProcessRET_Exit:PUSH 40hPUSH offset DbgNotFoundTitlePUSH offset DbgNotFoundTextPUSH 0CALL MessageBoxPUSH 0CALL ExitProcessRET

_SehExit:POP FS:[0]ADD ESP,4JMP _Exit

拋開那些 BAADF00D,原先發現的 Flags其實還有利用價值,往下翻翻,RtlCreateHeap下面還有一些不起眼的操作。程式碼如下:

// Fill in the heap header fields // Heap→ Entry.Size = (USHORT)(SizeOfHeapHeader >> HEAP_GRANULARITY_SHIFT); Heap→ Entry.Flags = HEAP_ENTRY_BUSY; Heap→ Signature = HEAP_SIGNATURE; Heap→ Flags = Flags; Heap→ ForceFlags = (Flags & (HEAP_NO_SERIALIZE | HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY | HEAP_REALLOC_IN_PLACE_ONLY | HEAP_VALIDATE_PARAMETERS_ENABLED | HEAP_VALIDATE_ALL_ENABLED | HEAP_TAIL_CHECKING_ENABLED | HEAP_CREATE_ALIGN_16 | HEAP_FREE_CHECKING_ENABLED));

這裡的 Flags在前面已經被 NtGlobalFlag影響了,看來行程堆積的 Flags和ForceFlags也不會倖免,它們也會感染上那些旗標。事實上,正常情況下系統為行程建立第一個堆積時,會將它的 Flags和

ForceFlags分別設為 2(HEAP_GROWABLE)和 0,在調試狀態下,這兩個旗標通常被設成 50000062h(取決於 NtGlobalFlag)和 40000060h。下面提供的是HEAP結構,一會兒就會需要它。

015-NEW.indd 15 2009/7/16 上午 10:40:38

Page 16: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

加密與解密(第三版)

15-16

+0x000 Entry : _HEAP_ENTRY+0x008 Signature : Uint4B+0x00c Flags : Uint4B+0x010 ForceFlags : Uint4B+0x014 VirtualMemoryThreshold : Uint4B+0x018 SegmentReserve : Uint4B+0x01c SegmentCommit : Uint4B+0x020 DeCommitFreeBlockThreshold : Uint4B+0x024 DeCommitTotalFreeThreshold : Uint4B+0x028 TotalFreeSize : Uint4B+0x02c MaximumAllocationSize : Uint4B+0x030 ProcessHeapsListIndex : Uint2B+0x032 HeaderValidateLength : Uint2B+0x034 HeaderValidateCopy : Ptr32 Void+0x038 NextAvailableTagIndex : Uint2B+0x03a MaximumTagIndex : Uint2B+0x03c TagEntries : Ptr32 _HEAP_TAG_ENTRY+0x040 UCRSegments : Ptr32 _HEAP_UCR_SEGMENT+0x044 UnusedUnCommittedRanges : Ptr32 _HEAP_UNCOMMMTTED_RANGE+0x048 AlignRound : Uint4B+0x04c AlignMask : Uint4B+0x050 VirtualAllocdBlocks : _LIST_ENTRY+0x058 Segments : [64] Ptr32 _HEAP_SEGMENT+0x158 u : __unnamed+0x168 u2 : __unnamed+0x16a AllocatorBackTraceIndex : Uint2B+0x16c NonDedicatedListLength : Uint4B+0x170 LargeBlocksIndex : Ptr32 Void+0x174 PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY+0x178 FreeLists : [128] _LIST_ENTRY+0x578 LockVariable : Ptr32 _HEAP_LOCK+0x57c CommitRoutine : Ptr32 long +0x580 FrontEndHeap : Ptr32 Void+0x584 FrontHeapLockCount : Uint2B+0x586 FrontEndHeapType : UChar+0x587 LastSegmentIndex : UChar

請再次回顧 PEB結構,並且注意 +18h處的 ProcessHeap,又寫出一個似乎很隱蔽的旗標檢測程式碼:

BOOL CheckHeapFlags(VOID){ __asm {

015-NEW.indd 16 2009/7/16 上午 10:40:39

Page 17: 15.1.1 BeingDebuggedepaper.gotop.com.tw/pdf/ACN022800.pdf · 這個函式讀取了目前行程PEB 中的BeingDebugged 旗標,每個執行中的行程 擁有一個名為PEB(Process

第 15章 反跟蹤技術

15-17

mov eax, fs:[0x30] mov eax, [eax+0x18] cmp dword ptr [eax+0x0C], 2 jne __debugger_detected cmp dword ptr [eax+0x10], 0 jne __debugger_detected xor eax, eax__debugger_detected: }}

15.1.4 從源頭消滅 BeingDebugged

首先系統建立行程的時候設置 BeingDebugged = TRUE,後來 NtGlobalFlag根據這個旗標設置 FLG_HEAP_VALIDATE_PARAMETERS等旗標。在為行程建立堆積時,又由於 NtGlobalFlag的作用,堆積的 Flags被設置了一些旗標,這個Flags隨即被填充到 ProcessHeap的 Flags和 ForceFlags當中,同時堆積的記憶體也因而被填充了很多 BAADF00D之類的東西。這樣調試器就會被檢測到。分析這個流程會發現,一切禍根只不過是很久以前系統設置了一個

BeingDebugged。猶如某地上空一隻小小的蝴蝶扇動翅膀而擾動了空氣,長時間後可能導致遙遠的彼地發生一場暴風。因此要從源頭制止這一切,在後面的事情沒

有發生之前改寫這個值,那麼所有的歷史都會改寫了。

系統確實給了這個時機,在編寫調試器(或調試器外掛)時,建立行程並呼

叫 WaitForDebugEvent後,在第一次 LOAD_DLL_DEBUG_EVENT發生的時候置 BeingDebugged = FALSE就可以了。但是會發現這樣沒法中斷在系統中斷點了,所以在第二次 LOAD_DLL_DEBUG_EVENT的時候要將 BeingDebugged置為TRUE,之後會停在系統中斷點,此時可以安全地清除 BeingDebugged了。筆者畫了一個小表格來總結這一段程式碼,程式碼如下:

DebugEventCode Count PEB.BeingDebugged Note

LOAD_DLL_DEBUG_EVENT 0 FALSE

LOAD_DLL_DEBUG_EVENT 1 TRUE

EXCEPTION_DEBUG_EVENT 0 FALSE EXCEPTION_BREAKPOINT

至 此, 一 次 性 地 解 決 了 BeingDebugged、NtGlobalFlag、HeapFlags、HeapForceFlags和 HeapMagic,這種感覺真好。

015-NEW.indd 17 2009/7/16 上午 10:40:40