日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

动态反调试技术

發(fā)布時間:2025/3/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态反调试技术 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 異常
    • SEH
    • EXCEPTION_BREAKPOINT
    • 破解之法
    • 調(diào)試程序代碼實現(xiàn)
    • SetUnhandledExceptionFilter()
    • 程序?qū)崿F(xiàn)代碼:
  • Timing Check
    • 時間間隔測量法
    • RDTSC(Read Time Stamp Counter,讀取時間戳計算器)
    • 破解之法
    • 調(diào)試程序代碼實現(xiàn)
  • 陷阱標(biāo)志
    • 單步執(zhí)行
    • 破解之法
    • 調(diào)試程序代碼實現(xiàn)
    • INT 2D
    • 破解之法
    • 程序?qū)崿F(xiàn)代碼
  • 0xCC探測
    • API斷點
    • 代碼逆向人員常用API列表:
    • 破解之法
    • 比較和校驗
    • 破解之法
    • 程序?qū)崿F(xiàn)代碼
  • 反調(diào)試技術(shù)系列:

異常

SEH

Windows操作系統(tǒng)中的一些典型異常

EXCEPTION_DATATYPE_MISALIGNMENT (0x80000002) EXCEPTION_BREAKPOINT (0x80000003) EXCEPTION_SINGLE_STEP (0x80000004) EXCEPTION_ACCESS_VIOLATION (0xC0000005) EXCEPTION_IN_PACE_ERROR (0xC0000006) EXCEPTION_ILLEGAL_INSTRUCTION (0xC000001D) EXCEPTION_NONCONTINUABLE_EXCEPTION (0xC0000025) EXCEPTION_INVALID_DISPOSITION (0xC0000026) EXCEPTION_ARRAY_BOUNDS_EXCEPTION (0xC000008C) EXCEPTION_FLT_DENORMAL_OPERAND (0xC000008D) EXCEPTION_FLT_DIVIDE_BY_ZERO (0xC000008E) EXCEPTION_FLT_INEXACT_RESULT (0xC000008F) EXCEPTION_FLT_INVALID_OPERATION (0xC0000090) EXCEPTION_FLT_OVERFLOW (0xC0000091) EXCEPTION_FLT_STACK_CHECK (0xC0000092) EXCEPTION_FLT_UNDERFLOW (0xC0000093) EXCEPTION_INT_DIVIDE_BY_ZERO (0xC0000094) EXCEPTION_INT_OVERFLOW (0xC0000095) EXCEPTION_PRIV_INSTRUCTION (0xC0000096) EXCEPTION_STACK_OVERFLOW (0xC00000FD)

EXCEPTION_BREAKPOINT

Windows操作系統(tǒng)最具有代表性的異常是斷點異常。BREAKPOINT指令觸發(fā)異常時,若程序處于正常運行狀態(tài),則自動調(diào)用已經(jīng)注冊過的SEH;若程序處于調(diào)試運行狀態(tài),則系統(tǒng)會立刻停止運行程序,并將控制權(quán)轉(zhuǎn)給調(diào)試器。

一般而言,異常處理器都含有修改EIP的代碼。修改調(diào)試器選項可以把處在調(diào)試中的進程產(chǎn)生的相關(guān)異常轉(zhuǎn)給操作系統(tǒng),自動調(diào)用SEH處理。而我們在異常處理器中適當(dāng)運用靜態(tài)反調(diào)試技術(shù),也能輕松判斷進程是否處于調(diào)試狀態(tài)。

1.安裝SEH

PUSH 40102C PUSH DWORD PTR FS:[0]MOV DWORD PTR FS:[0],ESP

解釋:
PUSH 40102C 就是把函數(shù)的實際地址壓入棧中,
PUSH DWORD PTR FS:[0]這個也就相當(dāng)于一個NEXT指針(需要在SEH鏈頂添加一個節(jié)點,所以他指向了原來的第一個節(jié)點)
MOV DWORD PTR FS:[0],ESP 這個也就是把棧頂指針( 即這個結(jié)構(gòu)體的地址)放在SEH中,即下面這種形式:

typedef struct _EXCEPTION_REGISTRATION_RECORD {struct _EXCEPTION_REGISTRATION_RECORD *Next; //指向下一個ERRPEXCEPTION_ROUTINE Handler; //異常處理函數(shù) }EXCEPTION_REGISTRATION_RECORD

2.發(fā)生 INT 3異常

INT 3

3.1調(diào)試運行-終止進程

MOV EAX -1 JMP EAX

若進程處于調(diào)試狀態(tài),則需要由調(diào)試器(OD)處理異常。INT3指令是CPU中斷命令,在用戶模式的 調(diào)試器中什么也不做,繼續(xù)執(zhí)行其下命令。

3.2正常運行(非調(diào)試運行)–運行SEH
若進程為非調(diào)試運行,那么執(zhí)行到INT 3指令時就會調(diào)用前面已經(jīng)注冊的SEH。

MOV EAX,DWORD PTR SS:[ESP+C] MOV EBX,0x401040 MOV DWORD PTR DS:[EAX+B8],EBX XOR EAX,EAX RETN

SS:[ESP+C]是CONTEXT *pContext結(jié)構(gòu)體的指針,而CONTEXT *pContext結(jié)構(gòu)體正是SEH的第三個參數(shù),它是一個發(fā)送異常的線程CONTEXT結(jié)構(gòu)體。DS:[EAX+B8]指向pContext---->Eip成員,所以MOV DWORD PTR DS:[EAX+B8],EBX用來將該結(jié)構(gòu)體的EIP修改為401040然后,異常處理器返回0.接下來,發(fā)送異常的線程再次從修改的EIP地址處(401040)開始運行

SEH異常處理器函數(shù)定義如下:

EXCEPTION_DISPOSITION ExceptHandler{EXCEPTION_RECORD *pRecord,EXCEPTION_REGISTRATION_RECORD *pFrame,CONTEXT * pContext,PVOID pValue }; typedef enum _EXCEPTION_DISPOSITION {ExceptionContinueExecution = 0,ExceptionContinueSearch = 1,ExceptionNestedException = 2,ExceptionCollidedUnwind = 3 } EXCEPTION_DISPOSITION;

以下是CONTEXT結(jié)構(gòu)體的定義

typedef struct _CONTEXT {DWORD ContextFlags // -| +00hDWORD Dr0 // | +04hDWORD Dr1 // | +08hDWORD Dr2 // >調(diào)試寄存器 +0ChDWORD Dr3 // | +10hDWORD Dr6 // | +14hDWORD Dr7 // -| +18hFLOATING_SAVE_AREA FloatSave; //浮點寄存器區(qū) +1Ch~~~88hDWORD SegGs //-| +8ChDWORD SegFs // |\段寄存器 +90hDWORD SegEs // |/ +94hDWORD SegDs //-| +98hDWORD Edi //________ +9ChDWORD Esi // | 通用 +A0hDWORD Ebx // | 寄 +A4hDWORD Edx // | 存 +A8hDWORD Ecx // | 器 +AChDWORD Eax //_|___組_ +B0hDWORD Ebp //++++++ +B4hDWORD Eip // |控制 +B8hDWORD SegCs // |寄存 +BChDWORD EFlag // |器組 +C0hDWORD Esp // | +C4hDWORD SegSs //++++++ +C8hBYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;typedef CONTEXT *PCONTEXT;#define MAXIMUM_SUPPORTED_EXTENSION 512

4.刪除SEH

POP DWORD PTR FS:[0] ADD ESP,4

解釋:
原來棧頂存放的是NEXT指針(也就是下一個異常處理結(jié)構(gòu)體的地址(即原來SEH鏈最頂端的結(jié)構(gòu)體)),直接把棧頂值放回去,還原了SEH鏈最頂端的結(jié)構(gòu)體,然后ESP直接+4就把棧中異常處理器函數(shù)地址直接抹殺。

破解之法

選項------>調(diào)試設(shè)置

然后點擊異常選項:

最后勾選“INT 3 Break”

選了“INT 3 Breaks”后,調(diào)試器就會忽略調(diào)試進程中發(fā)生的INT3異常,而由自身的SEH處理。

即設(shè)置好后,進程調(diào)試過程中遇到INT 3指令時,調(diào)試器不會停下來,而會自動調(diào)用執(zhí)行被調(diào)試進程的SEH(與正常運行一樣)

調(diào)試程序代碼實現(xiàn)

#include "stdio.h" #include "windows.h" #include "tchar.h"void AD_BreakPoint() {printf("SEH : BreakPoint\n");__asm {// install SEHpush handlerpush DWORD ptr fs : [0]mov DWORD ptr fs : [0] , esp//INT 3 指令是CPU中斷命令,在用戶模式的調(diào)試器啥都不做(經(jīng)過調(diào)試發(fā)現(xiàn)不會觸發(fā)ntdll.dll中的KiUserExceptionDispatcher)。// generating exceptionint 3// 1) debugging// go to terminating codemov eax, 0xFFFFFFFFjmp eax // process terminating!!!// 2) not debugging// go to normal codehandler:mov eax, dword ptr ss : [esp + 0xc]mov ebx, normal_codemov dword ptr ds : [eax + 0xb8] , ebxxor eax, eaxretnnormal_code :// remove SEHpop dword ptr fs : [0]add esp, 4}printf(" => Not debugging...\n\n"); }int _tmain(int argc, TCHAR* argv[]) {AD_BreakPoint();system("pause");return 0; }

注意:OD的異常選項 忽略int 3 中斷必須不選,strongOD插件選項最好不用,才能進入指令

SetUnhandledExceptionFilter()

進程中發(fā)生異常時,若SEH未處理或注冊的SEH根本不存在,會發(fā)生什么呢?此時會調(diào)用執(zhí)行系統(tǒng)的kernel32!UnhandledExceptionFilter()API。該函數(shù)內(nèi)部會運行系統(tǒng)的最后一個異常處理器(名為Top Level Exception Filter或Last Exception Filter)。系統(tǒng)最后的異常處理器通常會彈出消息錯誤消息框,然后終止進程運行

值得注意的是kernel32!UnhandledExceptionFilter()內(nèi)部調(diào)用了kernel32!NtQueryInformationProcess(ProcessDebugPort)API(靜態(tài)反調(diào)試技術(shù) ),以判斷是否正在調(diào)試進程。若進程正常運行(非調(diào)試運行),則運行系統(tǒng)最后的異常處理器;若進程處于調(diào)試中,則將異常派送給調(diào)試器。通過kernel32!SetUnhandledExceptionFilter()API可以修改系統(tǒng)最后的異常處理器,函數(shù)原型如下:

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter (__In LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) ;

調(diào)用該函數(shù)修改系統(tǒng)最后異常處理器時,只要將新的 Top Level Exception Filter函數(shù)地址傳遞給函數(shù)的lpTopLevelExceptionFilter參數(shù)即可(返回值為上一個 Last Exception Filter函數(shù)地址)。Top Level Exception Filter函數(shù)定義如下:

typedef struct _EXCEPTION_POINTERS{PEXCEPTION_RECORD ExceptionRecord;PCONTEXT ContextRecord; } EXCEPTION_POINTERS,*PEXCEPTION_POINTERSLONG TopLevelExceptionFilter(PEXCEPTION_POINTERS pExcept;);

基于異常的反調(diào)試技術(shù)中,通常都是先觸發(fā)異常,然后在新注冊的 Last Exception Filter 內(nèi)部判斷進程正常運行還是調(diào)試運行,并根據(jù)判斷結(jié)果修改EIP值。

程序?qū)崿F(xiàn)代碼:

#include "stdio.h" #include "windows.h" #include "tchar.h"LPVOID g_pOrgFilter = 0;LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS pExcept) {SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)g_pOrgFilter);// 8900 MOV DWORD PTR DS:[EAX], EAX// FFE0 JMP EAXpExcept->ContextRecord->Eip += 4;return EXCEPTION_CONTINUE_EXECUTION; }void AD_SetUnhandledExceptionFilter() {printf("SEH : SetUnhandledExceptionFilter()\n");//SetUnhandledExceptionFilter()來注冊新的Top Level Exception Filter回調(diào)函數(shù)。//觸發(fā)異常時,系統(tǒng)在前面沒有處理異常的情況下,會調(diào)用Kernel32.dll中的//UnhandledExceptionFilter()函數(shù)。UnhandledExceptionFilter()會利用//ntdll.dll中的NtQueryInformationProcess()來判斷是否被調(diào)試,//若判斷在被調(diào)試,異常給調(diào)試器(調(diào)試器無法處理異常,進程終止)。//若判斷未被調(diào)試,則調(diào)用Top Level Exception Filter回調(diào)函數(shù)。g_pOrgFilter = (LPVOID)SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);__asm {xor eax, eax;mov dword ptr [eax], eaxjmp eax }printf(" => Not debugging...\n\n"); }int _tmain(int argc, TCHAR* argv[]) {AD_SetUnhandledExceptionFilter();return 0; }

Timing Check

在調(diào)試器中逐行跟蹤程序代碼比程序正常(非調(diào)試運行)耗費的時間要多出很多。Timing Check技術(shù)通過計算運行時間的差異來判斷進程是否處于被調(diào)試狀態(tài)。

基于Timing Check 的反調(diào)試原理相當(dāng)簡單,只要直接操作獲取時間信息或比較時間的語句
即可。

提示:
Timing Check 技術(shù)也常常用作反模擬技術(shù)。程序在模擬器中運行時,運行速度要比程序正常運行(非模擬器運行)慢很多,所以Timing Check 技術(shù)也能用來探測程序是否在模擬器中運行。

時間間隔測量法

測量時間間隔的方法有很多種,常用方法如下所示:

1.Counter based method RDTSC kernel32!QueryPerformanceCounter()/NtQueryPermanceCounter() kernel32!GetTickCount()2.Time based method timeGetTime() _ftime()

測量時間間隔的方法大致分為兩大類:一類是利用CPU的計算器;另一類是利用系統(tǒng)但實際時間。

提示:
計數(shù)器的準(zhǔn)確程度由高到低排列如下:
RDSTC>NtQueryPerformanceCounter()>GetTickCount()
NtQueryPerformanceCounter()與GetTickCount()使用相同硬件,但二者準(zhǔn)備程度不同(NtQueryPerformanceCounter()準(zhǔn)確度更高),而RDTSC是CPU內(nèi)部的計數(shù)器,其準(zhǔn)確程度最高。基于時間的方法與基于計數(shù)器的方法在實現(xiàn)過程上比較類似,原理也差不多。

RDTSC(Read Time Stamp Counter,讀取時間戳計算器)

X86 CPU中存在一個名為TSC(Time Stamp Counter,時間戳計數(shù)器)的64位寄存器。CPU對每個Clock Cycle(時鐘周期)計數(shù),然后保存到TSC。RDTSC是一條匯編指令,用來將TSC值讀入到EDX:EAX寄存器(TSC大小為64位,其高32位保存到EDX寄存器,低32位保存至EAX寄存器)


破解之法

1.不使用跟蹤命令,直接使用RUN命令越過相關(guān)代碼。
在0xCA17CF地址 處設(shè)置斷點后運行。雖然運行速度略慢于正常運行速度,但與正常跟蹤相比要快很多。
2.操作第二個RDTSC的結(jié)果值(EDX:EAX)
操作第二個RDTSC的結(jié)果值,使之與第一個結(jié)果值相同,從而順利通過CMP語句
3.操縱條件分支(CMP/Jcc)
在調(diào)試器中強制修改Flags的值,阻止執(zhí)行跳轉(zhuǎn)至0xCA17EF地址處。大部分的Jcc指令揮手CF或ZF的影響,只要修改這些標(biāo)志即可控制Jcc指令。

CF與ZF全為0時,JA指令執(zhí)行跳轉(zhuǎn)動作,只要將CF與ZF之一的值修改為1,JA指令即失效,繼續(xù)調(diào)試即可。。
4.利用內(nèi)核模式驅(qū)動程序使RDTSC指令失效
利用內(nèi)核模式驅(qū)動程序可以從根本使基于RDTSC的動態(tài)反調(diào)試技術(shù)失效(其實,Olly Advanced PlugIn就采用了該方法)

調(diào)試程序代碼實現(xiàn)

#include "stdio.h" #include "windows.h" #include "tchar.h"void DynAD_RDTSC() { //Timing Check 技術(shù)通過計算運行時間的差異來反調(diào)試,反模擬。 //1.Counter based method // RDTSC 匯編指令(RD==READ TSC == Time Stamp Counter) // x86 CPU中存在一個名為TSC(Time Stamp Counter,時間戳計數(shù)器)的64位寄存器。 // CPU對每個Clock Cycle(時鐘周期)計數(shù),然后保存到TSC.RDTSC是一條匯編指令, // 用來將TSC值讀入EDX:EAX寄存器)。// OD中的插件Olly Advanced->options 反調(diào)試2 Anti-RDSTC(基于驅(qū)動模式可以繞過),此功能有可能造成死機。// kernel32!QueryPerformanceCounter()/ntdll!NtQueryPerformanceCounter()// kernel32!GetTickCount()//2.Time based method// timeGetTime()// _ftime()DWORD dwDelta = 0;printf("Timing Check (RDTSC method)");__asm {pushad//0F31 rdtscrdtscpush edxpush eax//用于消耗時間的循環(huán)(實際代碼相當(dāng)復(fù)雜)xor eax, eaxmov ecx, 0x3e8_LOOP_START:inc eaxloop _LOOP_STARTrdtscpop esi // eaxpop edi // edx// check high order bitscmp edx, edija _DEBUGGER_FOUND// check low order bitssub eax, esimov dwDelta, eaxcmp eax, 0xffffffjb _DEBUGGER_NOT_FOUND// debugger found -> crash!!! _DEBUGGER_FOUND:xor eax, eaxmov [eax], eax// debugger not found _DEBUGGER_NOT_FOUND:popad}printf(" : delta = %X (ticks)\n", dwDelta);printf(" => Not debugging...\n\n"); }int _tmain(int argc, TCHAR* argv[]) {DynAD_RDTSC();return 0; }

陷阱標(biāo)志

陷阱標(biāo)志指EFLAGS寄存器的第九個(index 8)比特位

單步執(zhí)行

TF值設(shè)置為1時,CPU將進入單步執(zhí)行(Single Step)模式。單步執(zhí)行模式中,CPU執(zhí)行一條指令即觸發(fā)1個EXCEPTION_SINGLE_STEP異常,然后陷阱標(biāo)志會自動清零(0),該EXCEPTION_SINGLE_STEP異常可以與SEH技法結(jié)合,在反調(diào)試技術(shù)中用于探測調(diào)試器。

可以注意一下這里如何設(shè)置TF位的手法

破解之法

首先,修改OllyDbg調(diào)試器選項(忽略EXCEPTION_SINGLE_STEP異常),讓被調(diào)試者直接處理EXCEPTION_SINGLE_STEP異常


然后,在注冊SEH的地址0x002D17E0處設(shè)置斷點。執(zhí)行0x002D17D8地址處的指令后,調(diào)試器就會停在SEH的斷點處。在新的EIP地址處再次設(shè)置斷點,接著運行跟蹤可執(zhí)行正常代碼。

調(diào)試程序代碼實現(xiàn)

#include "stdio.h" #include "windows.h" #include "tchar.h"void DynAD_SingleStep() {//陷阱標(biāo)志指EFLAGS寄存器的第9個(Index8)比特位。//TF值設(shè)置為1時,CPU將進入單步執(zhí)行(Single Step)模式。單步直行模式中,CPU執(zhí)行1條指令后即//觸發(fā)一個EXCEPTION_SINGLE_STEP(0x80000004)異常,異常地址為下一條指令。然后陷阱標(biāo)志會//自動清零。printf("Trap Flag (Single Step)\n");__asm {// install SEHpush handlerpush DWORD ptr fs:[0]mov DWORD ptr fs:[0], esp//因無法修改EFLAGS,故通過棧修改pushfdor dword ptr ss:[esp], 0x100popfd//執(zhí)行完nop指令后,才觸發(fā)EXCEPTION_SINGLE_STEP異常,OD在nop指令使用F7,F8,F9都可以。//即KiUserExceptionDispatcher的異常地址指向 mov eax,0xFFFFFFFF//1)若為正常運行,則運行前面注冊過的SEH//2)若為調(diào)試運行,則繼續(xù)執(zhí)行以下指令,不管使用F7,F8,F9都斷在mov eax,0xFFFFFFFFnop// 1) debugging// go to terminating codemov eax, 0xFFFFFFFFjmp eax // process terminating!!!// 2) not debugging// go to normal code handler:mov eax, dword ptr ss:[esp+0xc]mov ebx, normal_codemov dword ptr ds:[eax+0xb8], ebxxor eax, eaxretnnormal_code:// remove SEHpop dword ptr fs:[0]add esp, 4}printf(" => Not debugging...\n\n"); }int _tmain(int argc, TCHAR* argv[]) {DynAD_SingleStep();return 0; }

INT 2D

INT 2D 原為內(nèi)核模式中用來觸發(fā)斷點異常的指令,也可以在用戶模式下觸發(fā)異常。但程序調(diào)試運行時不會觸發(fā)異常,只是忽略。

1.忽略下條指令的第一個字節(jié)
在調(diào)試模式中執(zhí)行完 INT 2D指令后,下條指令的第一個字節(jié)將被忽略,后一個字節(jié)會被識別為新的指令繼續(xù)執(zhí)行

2.一直運行到斷點處
INT 2D 指令的另一特征,使用StepInto(F7)或StepOver(F8)命令跟蹤INT 2D指令時,程序不會停在其下條指令的地方,而是一直運行,直到遇到斷點,就像使用RUN(F9)命令運行程序一樣。


程序講解:

程序如果正常運行(非調(diào)試運行)時,執(zhí)行完0x10117D2地址處的INT 2D 指令后,發(fā)生異常,運行SEH(0x10117DE);
程序調(diào)試運行時,執(zhí)行INT 2D 指令后不會運行SEH,而是跳過1字節(jié)(90)繼續(xù)執(zhí)行0x10117D5的MOV指令

破解之法

這里呢,我換了未帶插件的OD,吾愛破解那個太強大了。。。不管什么情況都直接NO debugger,改啥都沒用。接下來逐步講解:

第一步:

這里設(shè)置了一個SEH鏈,首先,我們得在SEH這里下一個斷點,如果不下斷點,這里面直接秒執(zhí)行完。。。以至于你看到的情況是直接跳轉(zhuǎn)。。。

第二步:
在調(diào)試選項里面勾選一下單步中斷,利用單步中斷異常在調(diào)試時去跳轉(zhuǎn)到異常處理器函數(shù),

第三步:

執(zhí)行到這里的時候,修改TF位,讓它產(chǎn)生EXCEPTION_SINGLE_STEP異常,(前提是第二步和第一步已完成情況下,你才能看到接下來的跳轉(zhuǎn)。)

GAMEOVER

程序?qū)崿F(xiàn)代碼

#include "stdio.h" #include "windows.h" #include "tchar.h"void DynAD_INT2D() {//INT 2D 原為內(nèi)核模式中用來觸發(fā)異常的指令,也可以在用戶模式下觸發(fā)異常。但程序//調(diào)試運行時不會觸發(fā)異常,只是忽略。//INT 2D指令會造成兩個有趣的現(xiàn)象//1.在調(diào)試模式中執(zhí)行INT2D指令后(F7,F8),下條指令的第一個字節(jié)將被忽略,后一個字節(jié)會被識別為新的指令繼續(xù)執(zhí)行。此特性,可用于代碼混淆。//2.在調(diào)試模式中執(zhí)行INT2D指令后(F7,F8),程序不會停在其下條指令開始的地方,而是一直運行,直到遇到斷點(原有的代碼字節(jié)順序被打亂,OD的BUG)。BOOL bDebugging = FALSE;__asm {// install SEHpush handlerpush DWORD ptr fs:[0]mov DWORD ptr fs:[0], esp//可以在執(zhí)行到此條指令時,修改EFlags寄存器TF=1,然后就能進入SEH處理函數(shù)int 0x2dnopmov bDebugging, 1jmp normal_codehandler:mov eax, dword ptr ss:[esp+0xc]mov dword ptr ds:[eax+0xb8], offset normal_codemov bDebugging, 0xor eax, eaxretnnormal_code:// remove SEHpop dword ptr fs:[0]add esp, 4}printf("Trap Flag (INT 2D)\n");if( bDebugging ) printf(" => Debugging!!!\n\n");else printf(" => Not debugging...\n\n"); }int _tmain(int argc, TCHAR* argv[]) {DynAD_INT2D();return 0; }

0xCC探測

API斷點

若只調(diào)試程序中的某個局部功能,一個比較快的方法是先在程序要調(diào)試的API處設(shè)置斷點,再運行程序。運行暫停在相應(yīng)斷點后,再查看存儲在棧中的返回地址。“跟蹤返回地址調(diào)試相應(yīng)部分”的方式能夠大幅縮小代碼調(diào)試范圍。反調(diào)試技術(shù)中,探測這些位置在API上的斷點就能準(zhǔn)確判斷當(dāng)前進程是否處于調(diào)試狀態(tài)。一般而言,斷點都是設(shè)置在API代碼的開始部分,所以,只需要檢測API代碼的第一個字節(jié)是否是0xCC即可判斷出當(dāng)前進程是否處于調(diào)試之中

代碼逆向人員常用API列表:

進程:

CreatProcess CreateProcessAsUser CreateRemoteThread CreatThread GetThreadContext SetThreadContext EnumProcesses EnumProcessModules OpenProcess CreateToolhelp32Snapshot Process32First Process32Next ShellExecuteA WinExec TerminateProcess

內(nèi)存:

ReadProcessMemory WriteProcessMemory VirtualAlloc VirtualAllocEx VirtualProtect VirtualProtectEx VirtualQuery VirtualQueryEx

文件:

CreateFile ReadFile WriteFile CopyFile CreateDirectory DeleteFile MoveFile MoveFileEx FindFirstFile FindNextFile GetFileSize GetWindowsDirectory GetSystemDirectory GetFileAttributes SetFileAttributes SetFilePointer CreateFileMapping MapViewOfFile MapViewOfFileEx UnmapViewOfFile _open _write _read _lseek _tell

寄存器:

RegCreateKeyEx RegDeleteKey RegDeleteValue RegEnumKeyEx RegQueryValueEx RegSetValue RegSetValueEx

網(wǎng)絡(luò):

WSAStartup socket inet_addr closesocket getservbyname gethostbybname htons connect inet_htoa recv send HttpOpenRequest HttpSendRequest HttpQueryInfo InternetCloseHandle InternetConnect InternetGetConnectedState InternetOpen InternetOpenUrl InternetReadFile URLDownloadToFile

其它:

OpenProcessToken LookupPrivilegeVaule AdjustTokenPrivileges OpenSCManager CreateService OpenService ControlService DeleteService RegisterServiceCtrlHandler SetServiceStatus QueryServiceStatusEx CreateMutex OpenMutex FindWindow LoadLibrary GetProAddress GetModuleFileNameA GetCommandLine OutputDebugString ………………………………………………

破解之法

向系統(tǒng)API設(shè)置斷點時盡量避開第一個字節(jié),將之設(shè)置在代碼的中間部分。此外,設(shè)置硬件斷點也能避開上述所說

比較和校驗

檢測代碼中設(shè)置的軟件斷點的另一個方法是,比較特定代碼區(qū)域的校驗和值。比如,假定程序中0x401000~0x401070地址區(qū)域的校驗和值為0x12345678,在代碼調(diào)試時,必然會設(shè)置一些斷點(0xCC),這樣一來,新的校驗和值就與原值不一樣了。像這樣的話,比較校驗和即可判斷是否處于調(diào)試狀態(tài)

未設(shè)置任何斷點時,計算出來的校驗值保存在內(nèi)存單元0xF9A000中,在程序運行利用循環(huán)重新計算一遍校驗值,然后再和原來對比。這里計算完后,直接修改je跳轉(zhuǎn)即可跳轉(zhuǎn)

如果不跳轉(zhuǎn),直接把它賦值為1;所以這里只需要讓它跳過即可。

破解之法

從理論上講,只要不在計算CRC的代碼區(qū)域中設(shè)置斷點或修改其中代碼,基于校驗和的反調(diào)試技術(shù)就會失效。但是最好的破解辦法是直接修改CRC比較語句

程序?qū)崿F(xiàn)代碼

#include "stdio.h" #include "windows.h" #include "tchar.h"DWORD g_dwOrgChecksum = 0xF5934986;int _tmain(int argc, TCHAR* argv[]);void DynAD_Checksum() {BOOL bDebugging = FALSE;DWORD dwSize = 0;printf("Checksum\n");__asm {mov ecx, offset _tmainmov esi, offset DynAD_Checksumsub ecx, esi // ecx : loop count (buf size)xor eax, eax // eax : checksumxor ebx, ebx_CALC_CHECKSUM:movzx ebx, byte ptr ds:[esi]add eax, ebxrol eax, 1inc esiloop _CALC_CHECKSUMcmp eax, g_dwOrgChecksumje _NOT_DEBUGGINGmov bDebugging, 1_NOT_DEBUGGING:}if( bDebugging ) printf(" => Debugging!!!\n\n");else printf(" => Not debugging...\n\n"); }int _tmain(int argc, TCHAR* argv[]) {DynAD_Checksum();return 0; }

總結(jié):

  • CC int3 指令(注意兩者之間沒有空格)。
  • 2.CD 2D int 2d 指令

  • pushfdor [esp],100 popfd

    使EFlags寄存器TF==1.

    此3種方法,都是利用了調(diào)試器特有的性質(zhì)(1.忽略 2.一直運行,直到斷點。 3.斷在執(zhí)行指令后的一條指令,都沒有觸發(fā)ntdll!KiUserExceptionDispatcher())

    反調(diào)試技術(shù)系列:

    靜態(tài)反調(diào)試技術(shù)(1)https://blog.csdn.net/CSNN2019/article/details/113105292
    靜態(tài)反調(diào)試技術(shù)(2)https://blog.csdn.net/CSNN2019/article/details/113147820
    靜態(tài)反調(diào)試技術(shù)(3)https://blog.csdn.net/CSNN2019/article/details/113178232
    動態(tài)反調(diào)試技術(shù) https://blog.csdn.net/CSNN2019/article/details/113181558
    高級反調(diào)試技術(shù) https://blog.csdn.net/CSNN2019/article/details/113263215

    總結(jié)

    以上是生活随笔為你收集整理的动态反调试技术的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。