反調試技巧總結-原理和實現 收藏
標 題: 【原創】反調試技巧總結-原理和實現(1)(2)(3)(4)(5)(6)......
作 者: shellwolf
時 間: 2008-08-10,22:40
鏈 接: http://bbs.pediy.com/showthread.php?t=70470
反調試技巧總結-原理和實現
-------------------------------------------------------------------------------------------------------
2008.8.7? shellwolf
一、 前言
??? 前段學習反調試和vc,寫了antidebug-tester,經常會收到message希望交流或索要實現代碼,我都沒有回復。其實代碼已經在編程版提供了1個版本,另其多是vc內嵌asm寫的,對cracker而言,只要反下就知道了。我想代碼其實意義不是很大,重要的是理解和運用。
??? 做個簡單的總結,說明下實現原理和實現方法。也算回復了那些給我發Message的朋友。
??? 部分代碼和參考資料來源:
1、<<脫殼的藝術>> hawking
2、<<windows anti-debugger reference>> Angeljyt
3、http://bbs.pediy.com
4、<<軟件加密技術內幕>> 看雪學院
5、<<ANTI-UNPACKER TRICKS>> Peter Ferrie
我將反調試技巧按行為分為兩大類,一類為檢測,另一類為攻擊,每類中按操作對象又分了五個小類:
1、 通用調試器???? 包括所有調試器的通用檢測方法
2、 特定調試器???? 包括OD、IDA等調試器,也包括相關插件,也包括虛擬環境
3、 斷點?????????? 包括內存斷點、普通斷點、硬件斷點檢測
4、 單步和跟蹤???? 主要針對單步跟蹤調試
5、 補丁?????????? 包括文件補丁和內存補丁
反調試函數前綴
????????????? 檢測??????? 攻擊
通用調試器???? FD_??????? AD_
特定調試器???? FS_??????? AS_
斷點?????????? FB_??????? AB_
單步和跟蹤???? FT_??????? AT_
補丁?????????? FP_??????? AP_
聲明:
1、本文多數都是摘錄和翻譯,我只是重新組合并翻譯,不會有人告侵權吧。里面多是按自己的理解來說明,可能有理解錯誤,或有更好的實現方法,希望大家幫忙指出錯誤。
2、我并沒有總結完全,上面的部分分類目前還只有很少的函數甚至空白,等待大家和我一起來完善和補充。我堅信如果有扎實的基礎知識,豐富的想像力,靈活的運用,就會創造出更多的屬于自己的反調試。而最強的反調試,通常都是自己創造的,而不是來自別人的代碼。
二、 查找-通用調試器(FD_)
函數列表如下,后面會依次說明,需事先說明的是,這些反調試手段多數已家喻戶曉,目前有效的不多,多數已可以通過OD的插件順利通過,如果你想驗證它們的有效性,請關閉OD的所有反反調試插件:
bool FD_IsDebuggerPresent();
bool FD_PEB_BeingDebuggedFlag();
bool FD_PEB_NtGlobalFlags();
bool FD_Heap_HeapFlags();
bool FD_Heap_ForceFlags();
bool FD_Heap_Tail();
bool FD_CheckRemoteDebuggerPresent();
bool FD_NtQueryInfoProc_DbgPort();
bool FD_NtQueryInfoProc_DbgObjHandle();
bool FD_NtQueryInfoProc_DbgFlags();
bool FD_NtQueryInfoProc_SysKrlDbgInfo();
bool FD_SeDebugPrivilege();
bool FD_Parent_Process();
bool FD_DebugObject_NtQueryObject();
bool FD_Find_Debugger_Window();
bool FD_Find_Debugger_Process();
bool FD_Find_Device_Driver();
bool FD_Exception_Closehandle();
bool FD_Exception_Int3();
bool FD_Exception_Popf();
bool FD_OutputDebugString();
bool FD_TEB_check_in_Vista();
bool FD_check_StartupInfo();
bool FD_Parent_Process1();
bool FD_Exception_Instruction_count();
bool FD_INT_2d();
2.1 FD_IsDebuggerPresent()
對調試器來說,IsDebuggerPresent是臭名昭著的惡意函數。不多說了,它是個檢測調試的api函數。實現更簡單,只要調用IsDebuggerPresent就可以了。在調用它之前,可以加如下代碼,以用來檢測是否在函數頭有普通斷點,或是否被鉤掛。
? //check softbreak
? if(*(BYTE*)Func_addr==0xcc)
??? return true;
? //check hook
? if(*(BYTE*)Func_addr!=0x64)
??? return true;
2.2 FD_PEB_BeingDebuggedFlag
我們知道,如果程序處于調試器中,那么在PEB結構中有個beingDegug標志會被設置,直接讀取它就可判斷是否在調試器中。實際上IsDebuggerPresent就是這么干的。
? __asm
? {
??? mov eax, fs:[30h] ;EAX =? TEB.ProcessEnvironmentBlock
??? inc eax
??? inc eax
??? mov eax, [eax]
??? and eax,0x000000ff? ;AL? =? PEB.BeingDebugged
??? test eax, eax
??? jne rt_label
??? jmp rf_label
? }
2.3 FD_PEB_NtGlobalFlags
PEB中還有其它FLAG表明了調試器的存在,如NtGlobalFlags。它位于PEB環境中偏移為0x68的位置,默認情況下該值為0,在win2k和其后的windows平臺下,如果在調試中,它會被設置為一個特定的值。使用該標志來判斷是否被調試并不可靠(如在winnt中),但這種方法卻也很常用。這個標志由下面幾個標志組成:
FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
FLG_HEAP_ENABLE_FREE_CHECK (0x20)
FLG_HEAP_VALIDATE_PARAMETERS (0x40)
檢測NtGlobalFlags的方法如下,這個方法在ExeCryptor中使用過。
__asm
? {
??? mov eax, fs:[30h]
??? mov eax, [eax+68h]
??? and eax, 0x70
??? test eax, eax
??? jne rt_label
??? jmp rf_label
? }
2.4 FD_Heap_HeapFlags()
同樣,調試器也會在堆中留下痕跡,你可以使用kernel32_GetProcessHeap()函數,如果你不希望使用api函數(以免暴露),則可以直接在PEB中尋找。同樣的,使用HeapFlags和后面提到的ForceFlags來檢測調試器也不是非常可靠,但卻很常用。
這個域由一組標志組成,正常情況下,該值應為2。
? __asm
? {
??? mov eax, fs:[30h]
??? mov eax, [eax+18h] ;PEB.ProcessHeap
??? mov eax, [eax+0ch] ;PEB.ProcessHeap.Flags
??? cmp eax, 2
??? jne rt_label
??? jmp rf_label
? }
2.5 FD_Heap_ForceFlags
進程堆里另外一個標志,ForceFlags,它也由一組標志組成,正常情況下,該值應為0。
? __asm
? {
??? mov eax, fs:[30h]
??? mov eax, [eax+18h] ;PEB.ProcessHeap
??? mov eax, [eax+10h] ;PEB.ProcessHeap.ForceFlags
??? test eax, eax
??? jne rt_label
??? jmp rf_label
? }
2.6 FD_Heap_Tail
如果處于調試中,堆尾部也會留下痕跡。標志HEAP_TAIL_CHECKING_ENABLED 將會在分配的堆塊尾部生成兩個0xABABABAB。如果需要額外的字節來填充堆尾,HEAP_FREE_CHECKING_ENABLED標志則會生成0xFEEEFEEE。
據說Themida使用過這個反調試
? __asm
? {
??? mov eax, buff
??? ;get unused_bytes
??? movzx ecx, byte ptr [eax-2]
??? movzx edx, word ptr [eax-8] ;size
??? sub eax, ecx
??? lea edi, [edx*8+eax]
??? mov al, 0abh
??? mov cl, 8
??? repe sca**
??? je rt_label
??? jmp rf_label
? }
2.7 FD_CheckRemoteDebuggerPresent
CheckRemoteDebuggerPresent是另一個檢測調試的api,只是可惜它似乎只能在winxp sp1版本以后使用。它主要是用來查詢一個在winnt時就有的一個數值,其內部會調用NtQueryInformationProcess(),我是這樣實現的:
? FARPROC Func_addr ;
? HMODULE hModule = GetModuleHandle("kernel32.dll");
? if (hModule==INVALID_HANDLE_VALUE)
??? return false;
? (FARPROC&) Func_addr =GetProcAddress(hModule, "CheckRemoteDebuggerPresent");
? if (Func_addr != NULL)
? {
??? __asm
??? {
????? push? eax;
????? push? esp;
????? push? 0xffffffff;
????? call? Func_addr;
????? test? eax,eax;
????? je??? rf_label;
????? pop??? eax;
????? test? eax,eax
????? je??? rf_label;
????? jmp??? rt_label;
??? }
? }
2.8 FD_NtQueryInfoProc_DbgPort
使用ntdll_NtQueryInformationProcess()來查詢ProcessDebugPort可以用來檢測反調試。如果進程被調試,其返回值應為0xffffffff。
下面的代碼應該是從pediy里copy過來的,時間太長,不記得是哪位兄弟的代碼了。
? HMODULE hModule = GetModuleHandle("ntdll.dll");
? ZW_QUERY_INFORMATION_PROCESS ZwQueryInformationProcess;
??? ZwQueryInformationProcess = (ZW_QUERY_INFORMATION_PROCESS)GetProcAddress(hModule, "ZwQueryInformationProcess");
??? if (ZwQueryInformationProcess == NULL)
??? return false;
? PROCESS_DEBUG_PORT_INFO ProcessInfo;
? if (STATUS_SUCCESS != ZwQueryInformationProcess(GetCurrentProcess( ), ProcessDebugPort, &ProcessInfo, sizeof(ProcessInfo), NULL))
??? return false;
? else
??? if(ProcessInfo.DebugPort)
????? return true;
??? else
????? return false;
?????
2.9 FD_NtQueryInfoProc_DbgObjHandle
? 在winxp中引入了"debug object".當一個調試活動開始,一個"debug object"被創建,同也相應產生了一個句柄。使用為公開的ProcessDebugObjectHandle類,可以查詢這個句柄的數值。
? 代碼可能還是從pediy里復制的,不記得了。
? HMODULE hModule = GetModuleHandle("ntdll.dll");
? ZW_QUERY_INFORMATION_PROCESS ZwQueryInformationProcess;
??? ZwQueryInformationProcess = (ZW_QUERY_INFORMATION_PROCESS)GetProcAddress(hModule, "ZwQueryInformationProcess");
??? if (ZwQueryInformationProcess == NULL)
??? return false;
? _PROCESS_DEBUG_OBJECTHANDLE_INFO ProcessInfo;
? if (STATUS_SUCCESS != ZwQueryInformationProcess(GetCurrentProcess( ), (PROCESS_INFO_CLASS)0x0000001e, &ProcessInfo, sizeof(ProcessInfo), NULL))
??? return false;
? else
??? if(ProcessInfo.ObjectHandle)
????? return true;
??? else
????? return false;
2.10 FD_NtQueryInfoProc_DbgFlags();
同樣的未公開的ProcessDebugFlags類,當調試器存在時,它會返回false。
? HMODULE hModule = GetModuleHandle("ntdll.dll");
? ZW_QUERY_INFORMATION_PROCESS ZwQueryInformationProcess;
??? ZwQueryInformationProcess = (ZW_QUERY_INFORMATION_PROCESS)GetProcAddress(hModule, "ZwQueryInformationProcess");
??? if (ZwQueryInformationProcess == NULL)
??? return false;
? _PROCESS_DEBUG_FLAGS_INFO ProcessInfo;
? if (STATUS_SUCCESS != ZwQueryInformationProcess(GetCurrentProcess( ), (PROCESS_INFO_CLASS)0x0000001f, &ProcessInfo, sizeof(ProcessInfo), NULL))
??? return false;
? else
??? if(ProcessInfo.Debugflags)
????? return false;
??? else
????? return true;
2.11 FD_NtQueryInfoProc_SysKrlDbgInfo()
這個方法估計對大家用處不大,SystemKernelDebuggerInformation類同樣可以用來識別調試器,只是可惜在windows下無效,據稱可以用在reactOS中。
?? HMODULE hModule = GetModuleHandle("ntdll.dll");
??? ZW_QUERY_SYSTEM_INFORMATION ZwQuerySystemInformation;
??? ZwQuerySystemInformation = (ZW_QUERY_SYSTEM_INFORMATION)GetProcAddress(hModule, "ZwQuerySystemInformation");
??? if (ZwQuerySystemInformation == NULL)
??????? return false;
??? SYSTEM_KERNEL_DEBUGGER_INFORMATION Info;
??? if (STATUS_SUCCESS == ZwQuerySystemInformation(SystemKernelDebuggerInformation, &Info, sizeof(Info), NULL))
??? {
??????? if (Info.DebuggerEnabled)
??????? {
??????????? if (Info.DebuggerNotPresent)
??????????????? return false;
??????????? else
??????????????? return true;
??????? }
??????? else
??????????? return false;
??? }
??? else
?????? return true;
?
2.12 FD_SeDebugPrivilege()
? 當一個進程獲得SeDebugPrivilege,它就獲得了對CSRSS.EXE的完全控制,這種特權也會被子進程繼承,也就是說一個被調試的程序如果獲得了CSRSS.EXE的進程ID,它就可以使用openprocess操作CSRSS.EXE。獲得其進程ID有很多中方法,如Process32Next,或NtQuerySystemInformation,在winxp下可以使用CsrGetProcessId。
??? hTmp=OpenProcess(PROCESS_ALL_ACCESS,false,PID_csrss);
??? if(hTmp!=NULL)
??? {
????? CloseHandle(hProcessSnap );
????? return true;
??? }
2.13 FD_Parent_Process()
通常我們都直接在windows界面下運行應用程序,這樣的結果就是它的父進程為"explorer.exe",這個反調試就是檢測應用程序的父進程是否為"explorer.exe",如不是則判定為處于調試器中,這也不是百分百可靠,因為有的時候你的程序是在命令行提示符下運行的。
Yoda使用了這個反調試,它使用Process32Next檢測父進程,目前很多插件已經通過使Process32Next始終返回false來越過這個反調試(比如HideOD)。不過可以對代碼做些簡單的修正來處理這個反反調試。
2.14 FD_DebugObject_NtQueryObject();
? 如前面所描述的,當一個調試活動開始,一個"debug object"被創建,同也相應產生了一個句柄。我們可以查詢這個調試對象列表,并檢查調試對象的數量,以實現調試器的檢測。
? HMODULE hModule = GetModuleHandle("ntdll.dll");
? PNtQueryObject NtQueryObject;
? NtQueryObject = (PNtQueryObject)GetProcAddress(hModule,"NtQueryObject");
? if(NtQueryObject==NULL)
??? return false;
? unsigned char szdbgobj[25]=
? "\x44\x00\x65\x00\x62\x00\x75\x00\x67\x00\x4f\x00\x62\x00\x6a\x00\x65\x00\x63\x00\x74\x00\x00\x00";
? unsigned char *psz=&szdbgobj[0];
? __asm
? {
??? xor??? ebx,ebx;
??? push? ebx;
??? push? esp;
??? push? ebx;
??? push? ebx;
??? push? 3;
??? push? ebx;
??? Call? dword ptr [NtQueryObject];
??? pop? edi;
??? push? 4;
??? push? 1000h;
??? push? edi;
??? push? ebx;
????? call? dword ptr [VirtualAlloc];
??? push? ebx;
??? push? edi;
??? push? eax;
??? push? 3;
??? push? ebx;
??? xchg? esi,eax;
??? Call? dword ptr [NtQueryObject];
??? lodsd;
??? xchg? ecx,eax;
lable1:? lodsd;
??? movzx? edx,ax;
??? lodsd;
??? xchg? esi,eax;
??? cmp??? edx,16h;
??? jne??? label2;
??? xchg? ecx,edx;
??? mov??? edi,psz;
??? repe? cmp**;
??? xchg? ecx,edx;
??? jne??? label2;
??? cmp??? dword ptr [eax],edx
??? jne??? rt_label;
lable2:? add??? esi,edx
??? and??? esi,-4;
??? lodsd
??? loop? label1;
? }
? return false;
rt_label:
? return true;
2.15 FD_Find_Debugger_Window();
通過列舉運行的應用程序的窗口,并于常用調試相關工具比對的方法,應該很常用了,就不多說了。這個也是個可以自行增加項目的函數,你可以將一些常用的調試工具歸入其中,比如OD,IDA,WindBG,SoftICE等,你也可以添加任何你需要的,比如"Import REConstructor v1.6 FINAL (C) 2001-2003 MackT/uCF","Registry Monitor - Sysinternals: ? //ollyice
??? hWnd=CWnd::FindWindow(_T("1212121"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //ollydbg v1.1
??? hWnd=CWnd::FindWindow(_T("icu_dbg"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //ollyice pe--diy
??? hWnd=CWnd::FindWindow(_T("pe--diy"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //ollydbg ?-°?
??? hWnd=CWnd::FindWindow(_T("ollydbg"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //ollydbg ?-°?
??? hWnd=CWnd::FindWindow(_T("odbydyk"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //windbg
??? hWnd=CWnd::FindWindow(_T("WinDbgFrameClass"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //dede3.50
??? hWnd=CWnd::FindWindow(_T("TDeDeMainForm"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //IDA5.20
??? hWnd=CWnd::FindWindow(_T("TIdaWindow"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //others
??? hWnd=CWnd::FindWindow(_T("TESTDBG"),NULL);
??? if (hWnd!=NULL)
??? return true;
??? hWnd=CWnd::FindWindow(_T("kk1"),NULL);
??? if (hWnd!=NULL)
??? return true;
??? hWnd=CWnd::FindWindow(_T("Eew75"),NULL);
??? if (hWnd!=NULL)
??? return true;
??? hWnd=CWnd::FindWindow(_T("Shadow"),NULL);
??? if (hWnd!=NULL)
??? return true;
? //PEiD v0.94
??? hWnd=CWnd::FindWindow(NULL,"PEiD v0.94");
??? if (hWnd!=NULL)
??? return true;
? //RegMON
??? hWnd=CWnd::FindWindow(NULL,"Registry Monitor - Sysinternals: ??? if (hWnd!=NULL)
??? return true;
? //File Monitor
??? hWnd=CWnd::FindWindow(NULL,"File Monitor - Sysinternals: ??? if (hWnd!=NULL)
??? return true;
? //Import Rec v1.6
??? hWnd=CWnd::FindWindow(NULL,"Import REConstructor v1.6 FINAL (C) 2001-2003 MackT/uCF");
??? if (hWnd!=NULL)
??? return true;
? return false;
?
2.16 FD_Find_Debugger_Process();
? 與上面的方法類似,區別是這個反調試用通過查詢進程名字與已知的常用調試器應用程序名字進行比對,以確定是否有調試器處于運行狀態。
??? if(strcmp(pe32.szExeFile,"OLLYICE.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"IDAG.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"OLLYDBG.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"PEID.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"SOFTICE.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"LORDPE.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"IMPORTREC.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"W32DSM89.EXE")==0)
??????? return true;
??? if(strcmp(pe32.szExeFile,"WINDBG.EXE")==0)
??????? return true;
2.17 FD_Find_Device_Driver()
? 調試工具通常會使用內核驅動,因此如果嘗試是否可以打開一些調試器所用到的設備,就可判斷是否存在調試器。常用的設備名稱如下:
\\.\SICE? (SoftICE)
\\.\SIWVID(SoftICE)???
\\.\NTICE? (SoftICE)???
\\.\REGVXG(RegMON)
\\.\REGVXD(RegMON)
\\.\REGSYS(RegMON)
\\.\REGSYS(RegMON)
\\.\FILEVXG(FileMON)
\\.\FILEM(FileMON)
\\.\TRW(TRW2000)
2.18 FD_Exception_Closehandle()
? 如果給CloseHandle()函數一個無效句柄作為輸入參數,在無調試器時,將會返回一個錯誤代碼,而有調試器存在時,將會觸發一個EXCEPTION_INVALID_HANDLE (0xc0000008)的異常。
? __try?
? {
??? CloseHandle(HANDLE(0x00001234));
??? return false;
? }
? __except(1)
? {
??? return true;
? }
?
2.19 FD_Exception_Int3()
? 通過Int3產生異常中斷的反調試比較經典。當INT3 被執行到時, 如果程序未被調試, 將會異常處理器程序繼續執行。而INT3指令常被調試器用于設置軟件斷點,int 3會導致調試器誤認為這是一個自己的斷點,從而不會進入異常處理程序。
? __asm
? {
??? push?? offset exception_handler; set exception handler
??? push? dword ptr fs:[0h]
??? mov??? dword ptr fs:[0h],esp?
??? xor?? eax,eax;reset EAX invoke int3
??? int??? 3h
??? pop??? dword ptr fs:[0h];restore exception handler
??? add?? esp,4
??? test?? eax,eax; check the flag
??? je??? rt_label
??? jmp??? rf_label
exception_handler:
??? mov?? eax,dword ptr [esp+0xc];EAX = ContextRecord
??? mov??? dword ptr [eax+0xb0],0xffffffff;set flag (ContextRecord.EAX)
??? inc?? dword ptr [eax+0xb8];set ContextRecord.EIP
??? xor?? eax,eax
??? retn
rt_label:
??? xor eax,eax
??? inc eax
??? mov esp,ebp
??? pop ebp
??? retn
rf_label:
??? xor eax,eax
??? mov esp,ebp
??? pop ebp
??? retn
? }
2.20 FD_Exception_Popf()
我們都知道標志寄存器中的陷阱標志,當該標志被設置時,將產生一個單步異常。在程序中動態設置這給標志,如果處于調試器中,該異常將會被調試器捕獲。
可通過下面的代碼設置標志寄存器。
??? pushf
??? mov dword ptr [esp], 0x100
??? popf
2.21 FD_OutputDebugString()
? 在有調試器存在和沒有調試器存在時,OutputDebugString函數表現會有所不同。最明顯的不同是, 如果有調試器存在,其后的GetLastError()的返回值為零。
? OutputDebugString("");
? tmpD=GetLastError();
? if(tmpD==0)
??? return true;
? return false;
2.22 FD_TEB_check_in_Vista();
? 這是從windows anti-debug reference里拷貝出來的,據說是適用于vista系統下檢測調試器。我沒有vista所以也沒有測試。有條件的可以試下,有問題幫忙反饋給我。多謝。
??? //vista
??? __asm
??? {
????? push?? offset exception_handler; set exception handler
????? push? dword ptr fs:[0h]
????? mov??? dword ptr fs:[0h],esp?
????? xor?? eax,eax;reset EAX invoke int3
????? int??? 3h
????? pop??? dword ptr fs:[0h];restore exception handler
????? add?? esp,4
????? mov eax, fs:[18h] ; teb
????? add eax, 0BFCh
????? mov ebx, [eax] ; pointer to a unicode string
????? test ebx, ebx ; (ntdll.dll, gdi32.dll,...)
????? je????? rf_label
????? jmp??? rt_label
? exception_handler:
????? mov?? eax,dword ptr [esp+0xc];EAX = ContextRecord
????? inc?? dword ptr [eax+0xb8];set ContextRecord.EIP
????? xor?? eax,eax
????? retn
??? }
2.23 FD_check_StartupInfo();
? 這是從pediy上拷貝來的。Window創建進程的時候會把STARTUPINFO結構中的值設為0,而通過調試器創建進程的時候會忽略這個結構中的值,也就是結構中的值不為0,所以可以利用這個來判斷是否在調試程序。
? STARTUPINFO si;
? ZeroMemory( &si, sizeof(si) );
? si.cb = sizeof(si);
? GetStartupInfo(&si);
? if ( (si.dwX != 0) || (si.dwY !=0)
??? || (si.dwXCountChars != 0) || (si.dwYCountChars !=0 )
??? || (si.dwFillAttribute != 0) || (si.dwXSize != 0)
??? || (si.dwYSize != 0) )
??? return true;
? else?
??? return false;
???
2.24 FD_Parent_Process1()
與前面的FD_Parent_Process原理一樣,唯一不同的是使用ZwQueryInformationProcess檢測父進程,而沒有使用Process32Next,這有一個好處是可以繞過OD的HideOD插件。
2.25 FD_Exception_Instruction_count()
? 好像《軟件加解密技術》中有提到這個反調試。
? 通過注冊一個異常句柄,在特定地址設置一些硬件斷點,當通過這些地址時都會觸發EXCEPTION_SINGLE_STEP (0x80000004)的異常,在異常處理程序中,將會調整指令指針到一條新指令,然后恢復運行。可以通過進入進程context結構來設置這些斷點,有些調試器不能處理那些不是自己設置的硬件斷點,從而導致一些指令將會被漏掉計數,這就形成了一個反調試。
? __asm
? {
??? xor??? eax,eax;
??? cdq;
??? push? e_handler;
??? push? dword ptr fs:[eax];
??? mov??? fs:[eax],esp;
??? int 3;
hwbp1:? nop
hwbp2:? nop
hwbp3:? nop
hwbp4:? nop
??? div??? edx
??? nop
??? pop??? dword ptr fs:[0]
??? add??? esp,4
??? cmp??? al,4;
??? jne??? rt_label;
??? jmp??? rf_label;
e_handler:
??? xor??? eax,eax;
??? ;ExceptionRecord
??? mov??? ecx,dword ptr[esp+0x04]
??? ;Contextrecord
??? mov??? edx,dword ptr[esp+0x0c]
??? ;ContextEIP
??? inc??? byte ptr[edx+0xb8];
???
??? ;ExceptionCode
??? mov??? ecx,dword ptr[ecx];
??? ;1.EXCEPTION_INT_DIVIDE_BY_ZERO
??? cmp??? ecx,0xc0000094;
??? jne??? Ex_next2;
??? ;Context_eip
??? inc??? byte ptr[edx+0xb8];
??? mov??? dword ptr[edx+0x04],eax;dr0
??? mov??? dword ptr[edx+0x08],eax;dr1
??? mov??? dword ptr[edx+0x0c],eax;dr2
??? mov??? dword ptr[edx+0x10],eax;dr3
??? mov??? dword ptr[edx+0x14],eax;dr6
??? mov??? dword ptr[edx+0x18],eax;dr7
??? ret
??? ;2.EXCEPTION_BREAKPOINT
Ex_next2:
??? cmp??? ecx,0x80000003;
??? jne??? Ex_next3;
??? mov??? dword ptr[edx+0x04],offset hwbp1;dr0
??? mov??? dword ptr[edx+0x08],offset hwbp2;dr1
??? mov??? dword ptr[edx+0x0c],offset hwbp3;dr2
??? mov??? dword ptr[edx+0x10],offset hwbp4;dr3
??? mov??? dword ptr[edx+0x18],0x155;dr7
??? ret
??? ;3.EXCEPTION_SINGLE_STEP
Ex_next3:
??? cmp? ecx,0x80000004
??? jne??? rt_label
??? ;CONTEXT_Eax
??? inc??? byte ptr[edx+0xb0]
??? ret
? }
2.26 FD_INT_2d()
在windows anti-debug reference中指出,如果程序未被調試這個中斷將會生產一個斷點異常. 被調試并且未使用跟蹤標志執行這個指令, 將不會有異常產生程序正常執行. 如果被調試并且指令被跟蹤, 尾隨的字節將被跳過并且執行繼續. 因此, 使用 INT 2Dh 能作為一個強有力的反調試和反跟蹤機制。
? __try
? {
??? __asm
??? {
??????? int 2dh
????? inc eax;any opcode of singlebyte.
????? ;or u can put some junkcode,"0xc8"..."0xc2"..."0xe8"..."0xe9"
??? }
? return true;
? }
? __except(1)
? {
??? return false;
? }
?
三、? 檢測-專用調試器(FS_)
??? 這一部分是我比較喜歡的,但內容還不是很豐富,比如:
1、? 針對SoftIce的檢測方法有很多,但由于我從沒使用過Softice,也沒有條件去測試,所以沒有給出太多,有興趣的可以自己查閱資料進行補充,針對softice網上資料較多,或查閱《軟件加解密技術》。
2、? 同樣,這里也沒有給出windbg等等其它調試器的檢測方法。
3、? 而針對Odplugin,也只給了幾種HideOD的檢測。事實上,目前OD的使用者通常都使用眾多的強大插件,當OD的反調試越來越普遍時,自己設計幾款常用的OD插件的反調試,將會是非常有效的反調試手段。
4、? 對VME的檢測也只給出了兩種,如想豐富這一部分可以參考Peter Ferrie的一篇anti-vme的文章(http://bbs.pediy.com/showthread.php?t=68411)。里面有非常多的anti-vme方法。
??? 針對專用調試器的函數列表如下:
//find specific debugger
bool FS_OD_Exception_GuardPages();
bool FS_OD_Int3_Pushfd();
bool FS_SI_UnhandledExceptionFilter();
bool FS_ODP_Process32NextW();
bool FS_ODP_OutputDebugStringA();
bool FS_ODP_OpenProcess();
bool FS_ODP_CheckRemoteDebuggerPresent();
bool FS_ODP_ZwSetInformationThread();
bool FS_SI_Exception_Int1();
bool IsInsideVMWare_();
bool FV_VMWare_VMX();
bool FV_VPC_Exception();
int FV_VME_RedPill();//0:none,1:vmvare;2:vpc;3:others
3.1 FS_OD_Exception_GuardPages
??? “保護頁異常”是一個簡單的反調試技巧。當應用程序嘗試執行保護頁內的代碼時,將會產生一個EXCEPTION_GUARD_PAGE(0x80000001)異常,但如果存在調試器,調試器有可能接收這個異常,并允許該程序繼續運行,事實上,在OD中就是這樣處理的,OD使用保護頁來實現內存斷點。
最開始實現時忘記了free申請的空間,多謝sessiondiy提醒。
? SYSTEM_INFO sSysInfo;
? LPVOID lpvBase;
? BYTE * lptmpB;
? GetSystemInfo(&sSysInfo);
? DWORD dwPageSize=sSysInfo.dwPageSize;
? DWORD flOldProtect;
? DWORD dwErrorcode;
? lpvBase=VirtualAlloc(NULL,dwPageSize,MEM_COMMIT,PAGE_READWRITE);
? if(lpvBase==NULL)
??? return false;
?
? lptmpB=(BYTE *)lpvBase;
? *lptmpB=0xc3;//retn
? VirtualProtect(lpvBase,dwPageSize,PAGE_EXECUTE_READ | PAGE_GUARD,&flOldProtect);
?
? __try
? {
??? __asm? call dword ptr[lpvBase];
??? VirtualFree(lpvBase,0,MEM_RELEASE);
??? return true;
? }
? __except(1)
? {
??? VirtualFree(lpvBase,0,MEM_RELEASE);
??? return false;
? }
3.2 FS_OD_Int3_Pushfd
??? 這是個最近比較牛X的反調試,據稱是vmp1.64里發現的,好像ttprotect里面也有使用,我沒有驗證。Pediy里有帖子詳細討論,我是看到gkend的分析,才搞懂一些。下面摘自gkend分析
代碼:
??? int3,pushfd和int3,popfd一樣的效果。只要修改int3后面的popfd為其他值,OD都能通過。老掉牙的技術又重新被用了。SEH異常機制的運用而已。
??? 原理:在SEH異常處理中設置了硬件斷點DR0=EIP+2,并把EIP的值加2,那么應該在int3,popfd后面的指令執行時會產生單步異常。但是OD遇到前面是popfd/pushfd時,OD會自動在popfd后一指令處設置硬件斷點,而VMP的seh異常處理會判斷是否已經設置硬件斷點,如果已經有硬件斷點就不產生單步異常,所以不能正常執行。
??? http://bbs.pediy.com/showthread.php?t=67737
??? 大家也可以仔細研究下OD下的pushfd,popfd等指令,相信利用它們可以構造很多反調試,下面是我實現的一個,不過現在看起來有點沒看懂,不知當時為什么用了兩個int3。
? __asm
? {
??? push?? offset e_handler; set exception handler
??? push? dword ptr fs:[0h]
??? mov??? dword ptr fs:[0h],esp?
??? xor?? eax,eax;reset EAX invoke int3
??? int??? 3h
??? pushfd
??? nop
??? nop
??? nop
??? nop
??? pop??? dword ptr fs:[0h];restore exception handler
??? add?? esp,4
??? test?? eax,eax; check the flag
??? je??? rf_label
??? jmp??? rt_label
e_handler:
??? push?? offset e_handler1; set exception handler
??? push? dword ptr fs:[0h]
??? mov??? dword ptr fs:[0h],esp?
??? xor?? eax,eax;reset EAX invoke int3
??? int??? 3h
??? nop
??? pop??? dword ptr fs:[0h];restore exception handler
??? add?? esp,4
??? ;EAX = ContextRecord
??? mov??? ebx,eax;dr0=>ebx
??? mov?? eax,dword ptr [esp+0xc]
??? ;set ContextRecord.EIP
??? inc?? dword ptr [eax+0xb8];
??? mov??? dword ptr [eax+0xb0],ebx;dr0=>eax
??? xor??? eax,eax
??? retn
e_handler1:
??? ;EAX = ContextRecord
??? mov?? eax,dword ptr [esp+0xc]
??? ;set ContextRecord.EIP
??? inc?? dword ptr [eax+0xb8];
??? mov??? ebx,dword ptr[eax+0x04]
??? mov??? dword ptr [eax+0xb0],ebx;dr0=>eax
??? xor??? eax,eax
??? retn
rt_label:
??? xor? eax,eax
??? inc eax
??? mov esp,ebp
??? pop? ebp
??? retn
rf_label:
??? xor eax,eax
??? mov esp,ebp
??? pop ebp
??? retn
? }
3.3 FS_SI_UnhandledExceptionFilter
??? 這個針對SoftIce的反調試很簡單,好像是SoftIce會修改UnhandledExceptionFilter這個函數的第一個字節為CC。因此判斷這個字節是否為cc,就是一種檢查softice的簡便方法。
FARPROC Uaddr ;
BYTE tmpB = 0;
(FARPROC&) Uaddr =GetProcAddress ( GetModuleHandle("kernel32.dll"),"UnhandledExceptionFilter");
tmpB = *((BYTE*)Uaddr);?? // 取UnhandledExceptionFilter函數第一字節
tmpB=tmpB^0x55;
if(tmpB ==0x99)?????????? // 如該字節為CC,則SoftICE己加載
? return true;
else?
? return false;
3.4 FS_ODP_Process32NextW
??? 當我在調試FD_parentprocess時,感覺總是怪怪的,使用OD時運行Process32NextW總是返回失敗,搞了一個晚上,才搞懂原來是OD的插件HideOD在作怪。當HideOD的Process32NextW的選項被選中時,它會更改Process32NextW的返回值,使其始終返回false,這主要是HideOD針對FD_parentprocess這個反調試的一個反反調試。但也正是這一點暴露的它的存在。
? int OSVersion;
? FARPROC Func_addr;
? WORD tmpW;
? //1.Process32Next
? HMODULE hModule = GetModuleHandle("kernel32.dll");
? (FARPROC&) Func_addr =GetProcAddress ( hModule,"Process32NextW");
? if (Func_addr != NULL)
? {
??? tmpW=*(WORD*)Func_addr;
??? OSVersion=myGetOSVersion();
??? switch(OSVersion)
??? {
??? case OS_winxp:
????? if(tmpW!=0xFF8B)//maybe option of Process32Next is selected.
??????? return true;
????? break;
??? default:
????? if(tmpW==0xC033)
??????? return true;
????? break;
??? }
? }
??? 但上面的代碼并不完美,因為有跨平臺問題,所以要先獲得當前操作系統版本。目前只在win2k和winxp下進行了測試。
3.5 FS_ODP_OutputDebugStringA
??? 同樣,HIDEOD的OutputDebugStringA選項,也對OutputDebugStringA這個api做了處理,具體修改內容我記不得了,大家可以自己比對一下。我的代碼如下:
? int OSVersion;
? FARPROC Func_addr;
? WORD tmpW;
? //2.OutputDebugStringA
? HMODULE hModule = GetModuleHandle("kernel32.dll");
? (FARPROC&) Func_addr =GetProcAddress ( hModule,"OutputDebugStringA");
? if (Func_addr != NULL)
? {
??? tmpW=*(WORD*)Func_addr;
??? OSVersion=myGetOSVersion();
??? switch(OSVersion)
??? {
??? case OS_winxp:
????? if(tmpW!=0x3468)//maybe option of OutputDebugStringAt is selected.
??????? return true;
????? break;
??? default:
????? if(tmpW==0x01e8)
??????? return true;
????? break;
??? }
? }
? return false;
3.6 FS_ODP_OpenProcess
??? 這個據稱這個是針對HideDebugger這個插件的,當這個插件開啟時,它會掛鉤OpenProcess這個函數,它修改了OpenProcess的前幾個字節。因此檢測這幾個字節就可實現這個反調試。
? FARPROC Func_addr;
? BYTE tmpB;
? //OpenProcess
? HMODULE hModule = GetModuleHandle("kernel32.dll");
? (FARPROC&) Func_addr =GetProcAddress ( hModule,"OpenProcess");
? if (Func_addr != NULL)
? {
??? tmpB=*((BYTE*)Func_addr+6);
??? if(tmpB==0xea)//HideDebugger Plugin of OD is present
??????? return true;
? }
? return false;
3.7 FS_ODP_CheckRemoteDebuggerPresent
??? 和前面提到的兩個HideOD的反調試類似,不多說了。大家可以自行比對一下開啟和不開啟HideOD時,CheckRemoteDebuggerPresent函數的異同,就可以設計反這個插件的反調試了。
? int OSVersion;
? FARPROC Func_addr;
? BYTE tmpB;
? //2.CheckRemoteDebuggerPresent
? HMODULE hModule = GetModuleHandle("kernel32.dll");
? (FARPROC&) Func_addr =GetProcAddress ( hModule,"CheckRemoteDebuggerPresent");
? if (Func_addr != NULL)
? {
??? tmpB=*((BYTE*)Func_addr+10);
??? OSVersion=myGetOSVersion();
??? switch(OSVersion)
??? {
??? case OS_winxp:
????? if(tmpB!=0x74)//HideOD is present
??????? return true;
????? break;
??? default:
????? break;
??? }
? }
? return false;
3.8 FS_ODP_ZwSetInformationThread
??? 和前面提到的幾個HideOD的反調試類似,大家可以自行比對一下開啟和不開啟HideOD時,ZwSetInformationThread函數的異同,就可以設計反這個插件的反調試了。
? int OSVersion;
? FARPROC Func_addr;
? WORD tmpW;
? BYTE tmpB0,tmpB1;
? //2.CheckRemoteDebuggerPresent
? HMODULE hModule = GetModuleHandle("ntdll.dll");
? (FARPROC&) Func_addr =GetProcAddress ( hModule,"ZwSetInformationThread");
? if (Func_addr != NULL)
? {
??? tmpW=*((WORD*)Func_addr+3);
??? tmpB0=*((BYTE*)Func_addr+9);
??? tmpB1=*((BYTE*)Func_addr+10);
??? OSVersion=myGetOSVersion();
??? switch(OSVersion)
??? {
??? case OS_winxp:
????? if(tmpW!=0x0300)//HideOD is present
??????? return true;
????? break;
??? case OS_win2k:
????? if((tmpB0!=0xcd)&&(tmpB1!=0x2e))
??????? return true;
????? break;
??? default:
????? break;
??? }
? }
? return false;
3.9 FS_SI_Exception_Int1
??? 通常int1的DPL為0,這表示"cd 01"機器碼不能在3環下執行。如果直接執行這個中斷將會產生一個保護錯誤,windows會產生一個EXCEPTION_ACCESS_VIOLATION (0xc0000005)異常。然而,如果SOFTICE正在運行,它掛鉤了int1,并調整其DPL為3。這樣SoftICE就可以在用戶模式執行單步操作了。
??? 當int 1發生時,SoftICE不檢查它是由于陷阱標志位還是由軟件中斷產生,SoftICE總是去調用原始中斷1的句柄,此時將會產生一個EXCEPTION_SINGLE_STEP (0x80000004)而不是EXCEPTION_ACCESS_VIOLATION (0xc0000005)異常,這就形成了一個簡單的反調試方法。
? __asm
? {
??? push?? offset eh_int1; set exception handler
??? push? dword ptr fs:[0h]
??? mov??? dword ptr fs:[0h],esp?
??? xor?? eax,eax;reset flag(EAX) invoke int3
??? int??? 1h
??? pop??? dword ptr fs:[0h];restore exception handler
??? add?? esp,4
??? cmp??? eax,0x80000004; check the flag
??? je??? rt_label_int1
??? jmp??? rf_label_int1
eh_int1:
??? mov??? eax,[esp+0x4];
??? mov??? ebx,dword ptr [eax];
??? mov?? eax,dword ptr [esp+0xc];EAX = ContextRecord
??? mov??? dword ptr [eax+0xb0],ebx;set flag (ContextRecord.EAX)
??? inc?? dword ptr [eax+0xb8];set ContextRecord.EIP
??? inc?? dword ptr [eax+0xb8];set ContextRecord.EIP
??? xor?? eax,eax
??? retn
? }
3.10 FV_VMWare_VMX
??? 這是一個針對VMWare虛擬機仿真環境的反調試,我從網上直接拷貝的代碼。
??? VMWARE提供一種主機和客戶機之間的通信方法,這可以被用做一種VMWare的反調試。Vmware將會處理IN (端口為0x5658/’VX’)指令,它會返回一個majic數值“VMXh”到EBX中。
??? 當在保護模式操作系統的3環下運行時,IN指令的執行將會產生一個異常,除非我們修改了I/O的優先級等級。然而,如果在VMWare下運行,將不會產生任何異常,同時EBX寄存器將會包含’VMXh’,ECX寄存器也會被修改為Vmware的產品ID。
??? 這種技巧在一些病毒中比較常用。
??? 針對VME的反調試,在peter Ferrie的另一篇文章<<Attacks on More Virtual Machine Emulators>>中有大量的描述,有興趣的可以根據這篇文章,將FV_反調試好好豐富一下。
bool IsInsideVMWare_()
{
? bool r;
? _asm
? {
??? push?? edx
??? push?? ecx
??? push?? ebx
??? mov??? eax, 'VMXh'
??? mov??? ebx, 0 // any value but MAGIC VALUE
??? mov??? ecx, 10 // get VMWare version
??? mov??? edx, 'VX' // port number
??? in???? eax, dx // read port
?????????????????? // on return EAX returns the VERSION
??? cmp??? ebx, 'VMXh' // is it a reply from VMWare?
??? setz?? [r] // set return value
??? pop??? ebx
??? pop??? ecx
??? pop??? edx
? }
? return r;
}
bool FV_VMWare_VMX()
{
? __try
? {
??? return IsInsideVMWare_();
? }
? __except(1) // 1 = EXCEPTION_EXECUTE_HANDLER
? {
??? return false;
? }
}
3.11 FV_VPC_Exception
??? 這個代碼我也是完整從網上拷貝下來的,具體原理在<<Attacks on More Virtual Machine Emulators>>這篇文章里也有詳細描述。與VMWare使用一個特殊端口完成主機和客戶機間通信的方法類似的是,VirtualPC靠執行非法指令產生一個異常供內核捕獲。這個代碼如下:
代碼:
0F 3F x1 x2
0F C7 C8 y1 y2
??? 由這兩個非法指令引起的異常將會被應用程序捕獲,然而,如果VirtualPC正在運行,將不會產生異常。X1,x2的允許的數值還不知道,但有一部分已知可以使用,如0A 00,11 00…等等。
__declspec(naked) bool FV_VPC_Exception()
{
? _asm
? {
??? push ebp
??? mov? ebp, esp
??? mov? ecx, offset exception_handler
??? push ebx
??? push ecx
??? push dword ptr fs:[0]
??? mov? dword ptr fs:[0], esp
??? mov? ebx, 0 // Flag
??? mov? eax, 1 // VPC function number
? }
??? // call VPC
?? _asm __emit 0Fh
?? _asm __emit 3Fh
?? _asm __emit 07h
?? _asm __emit 0Bh
? _asm
? {
??? mov eax, dword ptr ss:[esp]
??? mov dword ptr fs:[0], eax
??? add esp, 8
??? test ebx, ebx
???
??? setz al
??? lea esp, dword ptr ss:[ebp-4]
??? mov ebx, dword ptr ss:[esp]
??? mov ebp, dword ptr ss:[esp+4]
??? add esp, 8
??? jmp ret1
exception_handler:
??? mov ecx, [esp+0Ch]
??? mov dword ptr [ecx+0A4h], -1 // EBX = -1 -> not running, ebx = 0 -> running
??? add dword ptr [ecx+0B8h], 4 // -> skip past the call to VPC
??? xor eax, eax // exception is handled
??? ret
?ret1:
??? ret
? }
}
3.12 FV_VME_RedPill
??? 這個方法似乎是檢測虛擬機的一個簡單有效的方法,雖然還不能確定它是否是100%有效。名字很有意思,紅色藥丸(為什么不是bluepill,哈哈)。我在網上找到了個ppt專門介紹這個方法,可惜現在翻不到了。記憶中原理是這樣的,主要檢測IDT的數值,如果這個數值超過了某個數值,我們就可以認為應用程序處于虛擬環境中,似乎這個方法在多CPU的機器中并不可靠。據稱ScoobyDoo方法是RedPill的升級版。代碼也是在網上找的,做了點小改動。有四種返回結果,可以確認是VMWare,還是VirtualPC,還是其它VME,或是沒有處于VME中。
?? //return value:? 0:none,1:vmvare;2:vpc;3:others
?? unsigned char matrix[6];
??? unsigned char redpill[] =
??????? "\x0f\x01\x0d\x00\x00\x00\x00\xc3";
??? HANDLE hProcess = GetCurrentProcess();
??? LPVOID lpAddress = NULL;
??? PDWORD lpflOldProtect = NULL;
??? __try
??? {
??????? *((unsigned*)&redpill[3]) = (unsigned)matrix;
??????? lpAddress = VirtualAllocEx(hProcess, NULL, 6, MEM_RESERVE|MEM_COMMIT , PAGE_EXECUTE_READWRITE);
???????
??????? if(lpAddress == NULL)
??????????? return 0;
??????? BOOL success = VirtualProtectEx(hProcess, lpAddress, 6, PAGE_EXECUTE_READWRITE , lpflOldProtect);
??????? if(success != 0)
???????????? return 0;
??
??????? memcpy(lpAddress, redpill, 8);
??????? ((void(*)())lpAddress)();
??????? if (matrix[5]>0xd0)
??????? {
????????? if(matrix[5]==0xff)//vmvare
??????????? return 1;
????????? else if(matrix[5]==0xe8)//vitualpc
??????????? return 2;
????????? else
??????????? return 3;
??????? }
??????? else
??????????? return 0;
??? }
??? __finally
??? {
??????? VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);
??? }
四、? 檢測-斷點(FB_)
這一部分內容較少,但實際上可用的方法也比較多,我沒有深入研究,不敢亂寫,照抄了幾個常用的方法:
//find breakpoint
bool FB_HWBP_Exception();
DWORD FB_SWBP_Memory_CRC();
bool FB_SWBP_ScanCC(BYTE * addr,int len);
bool FB_SWBP_CheckSum_Thread(BYTE *addr_begin,BYTE *addr_end,DWORD sumValue);
4.1 FB_HWBP_Exception
? 在異常處理程序中檢測硬件斷點,是比較常用的硬件斷點檢測方法。在很多地方都有提到。
? __asm
? {
??? push?? offset exeception_handler; set exception handler
??? push?? dword ptr fs:[0h]
??? mov??? dword ptr fs:[0h],esp?
??? xor??? eax,eax;reset EAX invoke int3
??? int??? 1h
??? pop??? dword ptr fs:[0h];restore exception handler
??? add??? esp,4
??? ;test if EAX was updated (breakpoint identified)
??? test?? eax,eax
??? jnz???? rt_label
??? jmp??? rf_label
exeception_handler:
??? ;EAX = CONTEXT record
??? mov???? eax,dword ptr [esp+0xc]
??? ;check if Debug Registers Context.Dr0-Dr3 is not zero
??? cmp???? dword ptr [eax+0x04],0
??? jne???? hardware_bp_found
??? cmp???? dword ptr [eax+0x08],0
??? jne???? hardware_bp_found
??? cmp???? dword ptr [eax+0x0c],0
??? jne???? hardware_bp_found
??? cmp???? dword ptr [eax+0x10],0
??? jne???? hardware_bp_found
??? jmp???? exception_ret
hardware_bp_found:
??? ;set Context.EAX to signal breakpoint found
??? mov???? dword ptr [eax+0xb0],0xFFFFFFFF
exception_ret:
??? ;set Context.EIP upon return
??? inc?????? dword ptr [eax+0xb8];set ContextRecord.EIP
??? inc?????? dword ptr [eax+0xb8];set ContextRecord.EIP
??? xor???? eax,eax
??? retn
? }
4.2 FB_SWBP_Memory_CRC()
? 由于在一些常用調試器中,比如OD,其是將代碼設置為0xcc來實現普通斷點,因此當一段代碼被設置了普通斷點,則其中必定有代碼的修改。因此對關鍵代碼進行CRC校驗則可以實現偵測普通斷點。但麻煩的是每次代碼修改,或更換編譯環境,都要重新設置CRC校驗值。
? 下面的代碼拷貝自《軟件加解密技術》,里面完成的是對整個代碼段的CRC校驗,CRC校驗值保存在數據段。CRC32算法實現代碼網上有很多,就不列出來了。
DWORD FB_SWBP_Memory_CRC()
{
? //打開文件以獲得文件的大小
? DWORD fileSize,NumberOfBytesRW;
? DWORD CodeSectionRVA,CodeSectionSize,NumberOfRvaAndSizes,DataDirectorySize,ImageBase;
? BYTE* pMZheader;
? DWORD pPEheaderRVA;
? TCHAR? *pBuffer ;
? TCHAR szFileName[MAX_PATH];
? GetModuleFileName(NULL,szFileName,MAX_PATH);
? //打開文件
? HANDLE hFile = CreateFile(
??? szFileName,
??? GENERIC_READ,
??? FILE_SHARE_READ,
??? NULL,
??? OPEN_EXISTING,
??? FILE_ATTRIBUTE_NORMAL,
??? NULL);
?? if ( hFile != INVALID_HANDLE_VALUE )
?? {
??? //獲得文件長度 :
??? fileSize = GetFileSize(hFile,NULL);
??? if (fileSize == 0xFFFFFFFF) return 0;
??? pBuffer = new TCHAR [fileSize];???? //// 申請內存,也可用VirtualAlloc等函數申請內存
??? ReadFile(hFile,pBuffer, fileSize, &NumberOfBytesRW, NULL);//讀取文件內容
??? CloseHandle(hFile);? //關閉文件
?? }
?? else
???? return 0;
? pMZheader=(BYTE*)pBuffer; //此時pMZheader指向文件頭
? pPEheaderRVA = *(DWORD *)(pMZheader+0x3c);//讀3ch處的PE文件頭指針
? ///定位到PE文件頭(即字串“PE\0\0”處)前4個字節處,并讀出儲存在這里的CRC-32值:
? NumberOfRvaAndSizes=*((DWORD *)(pMZheader+pPEheaderRVA+0x74));//得到數據目錄結構數量
? DataDirectorySize=NumberOfRvaAndSizes*0x8;//得到數據目錄結構大小
? ImageBase=*((DWORD *)(pMZheader+pPEheaderRVA+0x34));//得到基地址
? //假設第一個區塊就是代碼區塊
? CodeSectionRVA=*((DWORD *)(pMZheader+pPEheaderRVA+0x78+DataDirectorySize+0xc));//得到代碼塊的RVA值
? CodeSectionSize=*((DWORD *)(pMZheader+pPEheaderRVA+0x78+DataDirectorySize+0x8));///得到代碼塊的內存大小
? delete pBuffer;? // 釋放內存
? return CRC32((BYTE*)(CodeSectionRVA+ImageBase),CodeSectionSize);
}
4.3 FB_SWBP_ScanCC
掃描CC的方法,比照前面校驗代碼CRC數值的方法更直接一些,它直接在所要檢測的代碼區域內檢測是否有代碼被更改為0xCC,0xcc對應匯編指令為int3 ,對一些常用的調試器(如OD)其普通斷點就是通過修改代碼為int3來實現的。但使用時要注意是否正常代碼中就包含CC。通常這個方法用于掃描API函數的前幾個字節,比如檢測常用的MessageBoxA、GetDlgItemTextA等。
bool FB_SWBP_ScanCC(BYTE * addr,int len)
{
? FARPROC Func_addr ;
? HMODULE hModule = GetModuleHandle("USER32.dll");
? (FARPROC&) Func_addr =GetProcAddress ( hModule, "MessageBoxA");
? if (addr==NULL)
??? addr=(BYTE *)Func_addr;//for test
? BYTE tmpB;
? int i;
? __try
? {
??? for(i=0;i<len;i++,addr++)
??? {
????? tmpB=*addr;
????? tmpB=tmpB^0x55;
????? if(tmpB==0x99)// cmp 0xcc
??????? return true;
??? }
? }
? __except(1)
??? return false;
? return false;
}
?
4.4 FB_SWBP_CheckSum_Thread(BYTE *addr_begin,BYTE *addr_end,DWORD sumValue);
此方法類似CRC的方法,只是這里是檢測累加和。它與CRC的方法有同樣的問題,就是要在編譯后,計算累加和的數值,再將該值保存到數據區,重新編譯。在這里創建了一個單獨的線程用來監視代碼段。
DWORD WINAPI CheckSum_ThreadFunc( LPVOID lpParam )
{
? DWORD dwThrdParam[3];
? BYTE tmpB;
? DWORD Value=0;
? dwThrdParam[0]=* ((DWORD *)lpParam);
???? dwThrdParam[1]=* ((DWORD *)lpParam+1);
????? dwThrdParam[2]=* ((DWORD *)lpParam+2);
? BYTE *addr_begin=(BYTE *)dwThrdParam[0];
? BYTE *addr_end=(BYTE *)dwThrdParam[1];
? DWORD sumValue=dwThrdParam[2];
? for(int i=0;i<(addr_end-addr_begin);i++)
??? Value=Value+*(addr_begin+i);
? /* //if sumvalue is const,it should be substract.
? DWORD tmpValue;
? Value=Value-(sumValue&0x000000FF);
? tmpValue=(sumValue&0x0000FF00)>>8;
? Value=Value-tmpValue;
? tmpValue=(sumValue&0x0000FF00)>>16;
? Value=Value-tmpValue;
? tmpValue=(sumValue&0x0000FF00)>>24;
? Value=Value-tmpValue;*/
? if (Value!=sumValue)
??? MessageBox(NULL,"SWBP is found by CheckSum_ThreadFunc","CheckSum_ThreadFunc",MB_OK|MB_ICONSTOP);
??? return 1;
}
bool FB_SWBP_CheckSum_Thread(BYTE *addr_begin,BYTE *addr_end,DWORD sumValue)
{
??? DWORD dwThreadId;
? DWORD dwThrdParam[3];
? dwThrdParam[0]=(DWORD)addr_begin;
? dwThrdParam[1]=(DWORD)addr_end;
? dwThrdParam[2]=sumValue;
??? HANDLE hThread;
??? hThread = CreateThread(
??????? NULL,??????????????????????? // default security attributes
??????? 0,?????????????????????????? // use default stack size?
??????? CheckSum_ThreadFunc,???????? // thread function
??????? &dwThrdParam[0],??????????????? // argument to thread function
??????? 0,?????????????????????????? // use default creation flags
??????? &dwThreadId);??????????????? // returns the thread identifier
??? // Check the return value for success.
?
?? if (hThread == NULL)
????? return false;
?? else
?? {
????? Sleep(1000);
????? CloseHandle( hThread );
??? return true;
?? }
}
五、? 檢測-跟蹤(FT_)
個人認為,反跟蹤的一些技巧,多數不會非常有效,因為在調試時,多數不會被跟蹤經過,除非用高超的技巧將關鍵代碼和垃圾代碼及這些反跟蹤技巧融合在一起,否則很容易被發現或被無意中跳過。
函數列表如下:
//Find Single-Step or Trace
bool FT_PushSS_PopSS();
void FT_RDTSC(unsigned int * time);
DWORD FT_GetTickCount();
DWORD FT_SharedUserData_TickCount();
DWORD FT_timeGetTime();
LONGLONG FT_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);
bool FT_F1_IceBreakpoint();
bool FT_Prefetch_queue_nop1();
bool FT_Prefetch_queue_nop2();
5.1 FT_PushSS_PopSS
這個反調試在<<windows anti-debug reference>>里有描述,如果調試器跟蹤經過下面的指令序列:
? __asm
? {
??? push ss??? //反跟蹤指令序列
??? ;junk
??? pop? ss??? //反跟蹤指令序列
??? pushf??? //反跟蹤指令序列
??? ;junk
??? pop eax??? //反跟蹤指令序列
}
Pushf將會被執行,同時調試器無法設置壓進堆棧的陷阱標志,應用程序通過檢測陷阱標志就可以判斷處是否被跟蹤調試。
? __asm
? {
??? push ebp
??? mov ebp,esp
??? push ss??? //反跟蹤指令序列
??? ;junk
??? pop? ss??? //反跟蹤指令序列
??? pushf??? //反跟蹤指令序列
??? ;junk
??? pop eax??? //反跟蹤指令序列
??? and? eax,0x00000100
??? jnz? rt_label
??? xor eax,eax
??? mov esp,ebp
??? pop ebp
??? retn
rt_label:
??? xor eax,eax
??? inc eax
??? mov esp,ebp
??? pop ebp
??? retn
? }
5.2 FT_RDTSC
通過檢測某段程序執行的時間間隔,可以判斷出程序是否被跟蹤調試,被跟蹤調試的代碼通常都有較大的時間延遲,檢測時間間隔的方法有很多種。比如RDTSC指令,kernel32_GetTickCount函數,winmm_ timeGetTime 函數等等。
下面為RDTSC的實現代碼。
? int time_low,time_high;
? __asm
? {
??? rdtsc
??? mov??? time_low,eax
??? mov??? time_high,edx
? }
5.3 FT_GetTickCount
? GetTickCount函數檢測時間間隔簡單且常用。直接調用即可。具體可查MSDN。
5.4 FT_SharedUserData_TickCount
? 直接調用GetTickCount函數來檢測時間間隔的方法,雖然簡單卻容易被發現。而使用GetTickCount的內部實現代碼,直接讀取SharedUserData數據結構里的數據的方法,更隱蔽些。下面的代碼是直接從GetTickCount里扣出來的,其應該是在位于0x7FFE0000地址處的SharedUserData數據接口里面直接取數據,不過這個代碼應該有跨平臺的問題,我這里沒有處理。大家可以完善下。
? DWORD tmpD;
? __asm
? {
??? mov???? edx, 0x7FFE0000
??? mov???? eax, dword ptr [edx]
??? mul???? dword ptr [edx+4]
??? shrd??? eax, edx, 0x18
??? mov??? tmpD,eax
? }
? return tmpD;
5.5 FT_timeGetTime
? 使用winmm里的timeGetTime的方法也可以用來檢測時間間隔。直接調用這個函數即可。具體可查MSDN。
5.6 FT_QueryPerformanceCounter
? 這是一種高精度時間計數器的方法,它的檢測刻度最小,更精確。
? if(QueryPerformanceCounter(lpPerformanceCount))
??????? return lpPerformanceCount->QuadPart;
? else
???? return 0;
5.7 FT_F1_IceBreakpoint
? 在<<Windows anti-debug reference>>中有講述這個反跟蹤技巧。這個所謂的"Ice breakpoint" 是Intel 未公開的指令之一, 機器碼為0xF1.執行這個指令將產生單步異常.,如果程序已經被跟蹤, 調試器將會以為它是通過設置標志寄存器中的單步標志位生成的正常異常. 相關的異常處理器將不會被執行到.下面是我的實現代碼:
__asm
? {
? push?? offset eh_f1; set exception handler
???? push? dword ptr fs:[0h]
???? mov??? dword ptr fs:[0h],esp?
???? xor?? eax,eax;reset EAX invoke int3
???? _emit 0xf1
???? pop??? dword ptr fs:[0h];restore exception handler
???? add??? esp,4
? test? eax,eax
? jz??? rt_label_f1
? jmp??? rf_label_f1
eh_f1:
???? mov eax,dword ptr[esp+0xc]
? mov??? dword ptr [eax+0xb0],0x00000001;set flag (ContextRecord.EAX)
???? inc dword ptr [eax+0xb8]
???? xor eax,eax
???? retn
rt_label_f1:
? inc??? eax
? mov??? esp,ebp
???? pop??? ebp
???? retn
rf_label_f1:
? xor??? eax,eax
? mov??? esp,ebp
???? pop??? ebp
???? retn
? }
?
5.8 FT_Prefetch_queue_nop1
這個反調試是在<<ANTI-UNPACKER TRICKS>>中給出的,它主要是基于REP指令,通過REP指令來修改自身代碼,在非調試態下,計算機會將該指令完整取過來,因此可以正確的執行REP這個指令,將自身代碼完整修改,但在調試態下,則在修改自身的時候立即跳出。
這個反跟蹤技巧個人覺得用處不大,因為只有在REP指令上使用F7單步時,才會觸發這個反跟蹤,而我個人在碰到REP時,通常都是F8步過。下面是利用這個CPU預取指令的特性的實現反跟蹤的一種方法,正常情況下,REP指令會修改其后的跳轉指令,進入正常的程序流程,但在調試態下,其無法完成對其后代碼的修改,從而實現反調試。
?? DWORD oldProtect;
?? DWORD tmpProtect;
?? __asm
?? {
??? lea eax,dword ptr[oldProtect]
??? push eax
??? push 0x40
??? push 0x10
??? push offset label3;
??? call dword ptr [VirtualProtect];
??? nop
label3:
??? mov al,0x90
??? push 0x10
??? pop ecx
??? mov edi,offset label3
??? rep stosb
??? jmp rt_label
??? nop
??? nop
??? nop
??? nop
??? nop
rf_label:
??? ;write back
??? mov dword ptr[label3],0x106a90b0
??? mov dword ptr[label3+0x4],0x205CBF59
??? mov dword ptr[label3+0x8],0xAAF30040
??? mov dword ptr[label3+0xc],0x90909090
??? mov dword ptr[label3+0x6],offset label3
??? lea eax, dword ptr[tmpProtect];
??? ;restore protect
??? push eax
??? push oldProtect
??? push 0x10
??? push offset label3;
??? call dword ptr [VirtualProtect];
??? xor eax,eax
??? mov esp,ebp
??? pop ebp
??? retn
rt_label:
??? ;write back
??? mov dword ptr[label3],0x106a90b0
??? mov dword ptr[label3+0x4],0x205CBF59
??? mov dword ptr[label3+0x8],0xAAF30040
??? mov dword ptr[label3+0xc],0x90909090
??? mov dword ptr[label3+0x6],offset label3
??? lea eax, dword ptr[tmpProtect];
??? ;restore protect
??? push eax
??? push oldProtect
??? push 0x10
??? push offset label3;
??? call dword ptr [VirtualProtect];
??? xor eax,eax
??? inc eax
??? mov esp,ebp
??? pop ebp
??? retn
? }
5.9 FT_Prefetch_queue_nop2
? 與5.8節類似,這是根據CPU預取指令的這個特性實現的另一種反跟蹤技巧。原理是通過檢測REP指令后的ECX值,來判斷REP指令是否被完整執行。在正常情況下,REP指令完整執行后,ECX值應為0;但在調試態下,由于REP指令沒有完整執行,ECX值為非0值。通過檢測ECX值,實現反跟蹤。
? DWORD oldProtect;
? DWORD tmpProtect;
? __asm
? {
??? lea eax,dword ptr[oldProtect]
??? push eax
??? push 0x40
??? push 0x10
??? push offset label3;
??? call dword ptr [VirtualProtect];
??? mov ecx,0
label3:
??? mov al,0x90
??? push 0x10
??? pop ecx
??? mov edi,offset label3
??? rep stosb
??? nop
??? nop
??? nop
??? nop
??? nop
??? nop
??? push ecx
??? ;write back
??? mov dword ptr[label3],0x106a90b0
??? mov dword ptr[label3+0x4],0x201CBF59
??? mov dword ptr[label3+0x8],0xAAF30040
??? mov dword ptr[label3+0xc],0x90909090
??? mov dword ptr[label3+0x6],offset label3
??? lea eax, dword ptr[tmpProtect];
??? ;restore protect
??? push eax
??? push oldProtect
??? push 0x10
??? push offset label3;
??? call dword ptr [VirtualProtect];
??? pop ecx
??? test ecx,ecx
??? jne rt_label
? }
rf_label:
? return false;
rt_label:
? return true;
六、? 檢測-補丁(FP_)
這部分內容也較少,方法當然也有很多種,原理都差不多,我只選了下面三種。這幾種方法通常在一些殼中較常用,用于檢驗文件是否被脫殼或被惡意修改。
函數列表如下:
//find Patch
bool FP_Check_FileSize(DWORD Size);
bool FP_Check_FileHashValue_CRC(DWORD CRCVALUE_origin);
bool FP_Check_FileHashValue_MD5(DWORD MD5VALUE_origin);
6.1 FP_Check_FileSize(DWORD Size)
? 通過檢驗文件自身的大小的方法,是一種比較簡單的文件校驗方法,通常如果被脫殼,或被惡意修改,就可能影響到文件的大小。我用下面的代碼實現。需注意的是,文件的大小要先編譯一次,將首次編譯得到的數值寫入代碼,再重新編譯完成。
? DWORD Current_Size;
? TCHAR szPath[MAX_PATH];
? HANDLE hFile;
? if( !GetModuleFileName( NULL,szPath, MAX_PATH ) )
??????? return FALSE;
? hFile = CreateFile(szPath,
??? GENERIC_READ ,
??? FILE_SHARE_READ,
??? NULL,
??? OPEN_ALWAYS,
??? FILE_ATTRIBUTE_NORMAL,
??? NULL);
? if (hFile == INVALID_HANDLE_VALUE)
??? return false;
? Current_Size=GetFileSize(hFile,NULL);
? CloseHandle(hFile);
? if(Current_Size!=Size)
??? return true;
? return false;
6.2 FP_Check_FileHashValue_CRC
? 檢驗文件的CRC數值,是比較常用的文件校驗方法,相信很多人都碰到過了,我是在《軟件加解密技術》中了解到的。需注意的是文件原始CRC值的獲得,及其放置位置,代碼編寫完成后,通常先運行一遍程序,使用調試工具獲得計算得到的數值,在將這個數值寫入文件中,通常這個數值不參加校驗,可以放置在文件的尾部作為附加數據,也可以放在PE頭中不用的域中。
? 下面的代碼只是個演示,沒有保存CRC的真實數值,也沒有單獨存放。
? DWORD fileSize,NumberOfBytesRW;
? DWORD CRCVALUE_current;
? TCHAR szFileName[MAX_PATH];
? TCHAR? *pBuffer ;
? GetModuleFileName(NULL,szFileName,MAX_PATH);
? HANDLE hFile = CreateFile(
??? szFileName,
??? GENERIC_READ,
??? FILE_SHARE_READ,
??? NULL,
??? OPEN_EXISTING,
??? FILE_ATTRIBUTE_NORMAL,
??? NULL);
? if (hFile != INVALID_HANDLE_VALUE )
? {
??? fileSize = GetFileSize(hFile,NULL);
??? if (fileSize == 0xFFFFFFFF) return false;
??? pBuffer = new TCHAR [fileSize];?
??? ReadFile(hFile,pBuffer, fileSize, &NumberOfBytesRW, NULL);
??? CloseHandle(hFile);
? }
? CRCVALUE_current=CRC32((BYTE *)pBuffer,fileSize);
? if(CRCVALUE_origin!=CRCVALUE_current)
??? return true;
? return false;
6.3 FP_Check_FileHashValue_MD5
與6.2節的原理相同,只是計算的是文件的MD5數值。仍要注意6.2節中同樣的MD5真實數值的獲得和存放問題。
未完。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/hack8/archive/2009/01/18/3826322.aspx