第15章 反跟踪技术①
好的软件保护都要与反跟踪技术结合在一起。如果没有反跟踪技术,软件等于直接裸露在解密者的面前。这里所说的反跟踪是泛指,包括防调试器、防监视工具等内容。本章将讨论一些常用的反跟踪方法,读者可以根据实际情况在自己的软件中采用相关的技术和代码。
15.1 由BeingDebugged引发的蝴蝶效应
一个坏的微小的机制,如果不加以及时地引导、调节,会给社会带来非常大的危害,戏称为“龙卷风”或“风暴”;一个好的微小的机制,只要正确指引,经过一段时间的努力,将会产生轰动效应,或称为“革命”。
15.1.1 BeingDebugged
Win32 API为程序提供了IsDebuggerPresent判断自己是否处于调试状态,懒惰的程序员总是用它。来看一下实现代码:
// debug.c BOOL APIENTRY IsDebuggerPresent(VOID) { return NtCurrentPeb()->BeingDebugged; } |
这个函数读取了当前进程PEB中的BeingDebugged标志,每个运行中的进程拥有一个名为PEB(Process Environment Block,进程环境块)的结构,对它有少许了解会有助于理解后面的内容。PEB结构的内容:
Offset Elements name Type +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所示。
|
| 图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 +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
|
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); 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标记了:
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虽然被消灭了,但是问题并不是这么简单。
【责任编辑:
夏书 TEL:(010)68476606】