【反調試】常用反調試方法匯總


前言

常用反調試(Anti-Debug)檢測思路:

檢測PEB結構特定標志位,例如:"BeingDebugged"。
使用系統API,例如:"IsDebuggerPresent"等。
檢測指定調試器特征,例如:檢測進程,窗口標題等。

索引

// PEB_檢測
BeingDebugged 
NtGlobalFlag 
// Heap_檢測
HeapFlags 
ForceFlags 
// API_檢測
IsDebuggerPresent
CheckRemoteDebuggerPresent
NtQueryInformationProcess
NtQuerySystemInformation
// 時間_檢測
rdtsc
GetTickCount
// 硬件斷點_檢測
GetThreadContext
// 異常_檢測
SEH
// 內存CRC_檢測
---
// 分離調試器
NtSetInformationThread

示例

示例中的檢測代碼,僅用於效果演示,實際分析時檢測代碼並不會與示例代碼完全相同(作用或原理相同),另外某些數據結構在32/64系統中的偏移也不相同,需要讀者靈活應用后自行識別反調試!

PEB_檢測

// 如果被調試,BeingDebugged標志位 == 1
__asm
{
    mov   eax, fs:18h       // TEB
    mov   eax, [eax + 30h]  // PEB
    movzx eax, [eax + 2]    // PEB->BeingDebugged        
}

// PEB偏移0x68,默認值==0,如果被調試系統會設置特定值(0x70)
__asm
{
    mov   eax, fs:18h       // TEB
    mov   eax, [eax + 30h]  // PEB
    movzx eax, [eax + 68h]  // PEB->NtGlobalFlags
    and   eax, 0x70            
}
View Code

Heap_檢測

// 使用HeapFlags和ForceFlags檢測調試器並不可靠,但卻很常用
__asm
{
    mov eax, fs:[30h]
    mov eax, [eax + 18h]    // PEB.ProcessHeap
    mov eax, [eax + 0ch]    // PEB.ProcessHeap.Flags
    cmp eax, 2              // 正常情況下,該值==2
}

__asm
{
    mov eax, fs:[30h]
    mov eax, [eax + 18h]    // PEB.ProcessHeap
    mov eax, [eax + 10h]    // PEB.ProcessHeap.ForceFlags
    test eax, eax           // 正常情況下,該值 == 0
}
View Code

API_檢測

// 內部原理同PEB->BeingDebugged一樣
if (IsDebuggerPresent())
{
    // 調試
}
else
{
    // 未調試
}
View Code
// 檢測指定進程是否被調試,內部會調用NtQueryInformationProcess()
BOOL    result;
CheckRemoteDebuggerPresent(NtCurrentProcess, &result);
if (result)
{
    // 調試
}
else
{
    // 未調試
}
View Code
PVOID        pInfo;
NTSTATUS    status;
ULONG        returnlen;

//如果進程被調試,其返回值應為0xffffffff
status = NtQueryInformationProcess(NtCurrentProcess,
    ProcessDebugPort,
    &pInfo,
    sizeof(pInfo),
    &returnlen);
if (NT_SUCCESS(status))
{
    if(pInfo != NULL)
    {
        //調試
    }
}

// 
status = NtQueryInformationProcess(NtCurrentProcess,
    ProcessDebugObjectHandle,
    &pInfo,
    sizeof(pInfo),
    &returnlen);
if (status == 0xC0000353)
{
    //未調試
}
else
{
    //調試
}

// 當調試器存在時,返回0
status = NtQueryInformationProcess(NtCurrentProcess,
    ProcessDebugFlags,
    &pInfo,
    sizeof(pInfo),
    &returnlen);
if (NT_SUCCESS(status))
{
    if((ULONG_PTR)pInfo == 0))
    {
        //調試
    }
}
View Code

時間_檢測

//利用rdtsc指令(操作碼0x0F31),它返回至系統重新啟動以來的時鍾數,並且將其作為一個64位的值存入EDX:EAX中
//惡意代碼運行兩次rdtsc指令,然后比較兩次讀取之間的差值
BOOL CheckDebug()  
{  
    DWORD time1, time2;  
    __asm  
    {  
        rdtsc  
        mov time1, eax  
        rdtsc  
        mov time2, eax  
    }  
    if (time2 - time1 < 0xff)  
    {  
        //調試 
    }  
    else  
    {  
        //未調試 
    }  
} 
View Code
//該方法適合放在關鍵代碼處檢測,如果F9運行一般檢測不到
DWORD    t1, t2;
t1 = GetTickCount();
for (int i = 0; i < 0x100000; i++)
{
    __asm{
        nop;
        nop;
        nop;
        nop;
    }
}

t2 = GetTickCount();
if (t2 - t1 > 1000)
{
    //調試
}
else
{
    //未調試
}
View Code

硬件斷點_檢測

//獲取線程上下文,檢測硬件斷點
CONTEXT        ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(NtCurrentThread,&ctx);

if (ctx.Dr0 || ctx.Dr1 || ctx.Dr3 || ctx.Dr2)
{
    //調試
}
else
{
    //未調試
}
View Code

異常_檢測

//======設置veh異常回調
LONG NTAPI VEHandle(PEXCEPTION_POINTERS ExceptionInfo)
{
    if (ExceptionInfo->ExceptionRecord->ExceptionCode == 0xC0000005)
    {
        //檢測硬件斷點 
        if (ExceptionInfo->ContextRecord->Dr0 ||
            ExceptionInfo->ContextRecord->Dr1 ||
            ExceptionInfo->ContextRecord->Dr2 ||
            ExceptionInfo->ContextRecord->Dr3)
        {
            //調試
        }
        else
        {
            //未調試
        }

        ExceptionInfo->ContextRecord->Eip += 3; 
        return EXCEPTION_CONTINUE_EXECUTION;
    }

    return EXCEPTION_CONTINUE_SEARCH;
}

//======手動構造異常,在VEH中檢測硬件斷點調試
void SetVeh()
{
    AddVectoredExceptionHandler(0, VEHandle);

    PUCHAR    p = NULL;
    *p = NULL;

    RemoveVectoredExceptionHandler(VEHandle);
}
View Code

內存CRC_檢測

//======內存檢測除了CRC也可以使用其它HASH函數,只要保證內存區段數據不被修改即可
void _CRCCheck()
{
    DWORD    start, size;
    
    //1.更早的 計算一遍頁面CRC
    PVOID        imagebase;
    PIMAGE_DOS_HEADER    pDosHead;
    PIMAGE_NT_HEADERS    pNtHead;
    PIMAGE_SECTION_HEADER    pSec;
    CRCPAGE_HASH    ctx;
    
    //獲取DOS頭
    imagebase = GetModuleHandle(NULL);
    pDosHead = (PIMAGE_DOS_HEADER)imagebase;
    if (pDosHead->e_magic != IMAGE_DOS_SIGNATURE)
    {
        return;
    }
    //獲取NT頭
    pNtHead = (PIMAGE_NT_HEADERS)((ULONG_PTR)imagebase + pDosHead->e_lfanew);
    if (pNtHead->Signature != IMAGE_NT_SIGNATURE)
    {
        return;
    }
    //獲取區段頭
    pSec = IMAGE_FIRST_SECTION(pNtHead);
    
    //遍歷所有區段,計算CRC
    g_crcPage.clear();
    for (int i = 0; i < pNtHead->FileHeader.NumberOfSections; i++)
    {
        if (pSec->Characteristics & IMAGE_SCN_MEM_EXECUTE)
        {
            start = (ULONG_PTR)imagebase + pSec->VirtualAddress;
            size = (ULONG_PTR)pSec->Misc.VirtualSize;
    
            ctx.m_startAddr = (PVOID)start;
            ctx.m_size = size;
            ctx.m_hash = crc32((const void*)start, size);
    
            g_crcPage.push_back(ctx);
        }
        pSec++;
    }
    
    //計算完CRC后,在后面的某段代碼中再次計算,比較2次計算結果,如果不相同程序既有可能被調試或補丁
}
View Code

分離調試器

//該函數不會對正常運行的程序產生任何影響,但若運行的是調試器程序,
//因為該函數隱藏了當前線程,調試器無法再收到該線程的調試事件,最終停止調試
void _ThreadHideFromDebugger()
{
    NtSetInformationThread(
        NtCurrentThread,
        ThreadHideFromDebugger, //隱藏調試器線程
        NULL,
        NULL);
}
View Code

參考

網上有很多文章總結的都比較詳細,這里就不在做重復勞動了,具體可以參考以下文章。

//看雪
https://bbs.pediy.com/thread-70470.htm
https://bbs.pediy.com/thread-225740.htm

//比較全面,有反調試和虛擬機
https://github.com/LordNoteworthy/al-khaser

//轉載的前面的文章,格式比較好,方便查看
https://www.cnblogs.com/huhu0013/archive/2011/07/05/2098358.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM