IsDebuggerPresent的反除錯與反反除錯

人類觀察者發表於2022-04-07

一、呼叫系統的IsDebuggerPresent函式

(1)實現程式

  最簡單也是最基礎的,Windows提供的API介面:IsDebuggerPresent(),這API實際上就是訪問PEB的BeingDebugged標誌來判斷是否處於除錯狀態。

  使用vs除錯此段程式碼,彈出"檢測到偵錯程式"。

#include <stdio.h>
#include <Windows.h>

DWORD WINAPI ThreadFunctionCallBack(LPVOID lp)
{
    while(true)    
    {
        if (IsDebuggerPresent())
        {
            printf("檢測到偵錯程式\n");
        }
    }
}

int main()
{

    CreateThread(NULL, NULL, ThreadFunctionCallBack, NULL, NULL, NULL);    // 啟動一個執行緒進行實時檢測
    while(TRUE)
    {
        printf("主執行緒在執行");
    }
    system("pause");
    return 0;
}

 

(2)分析IsDebuggerPresent原理:

  通過dbg進行簡單地逆向分析,進入到 IsDebuggerPresent 函式內部。x86下 FS:[30] 指向PEB,訪問 PEB 的 BeingDebugged 標誌來判斷是否處於除錯狀態。

   

  IsDebuggerPresent:

  

 

 

 // x86下PEB結構

NTDLL_Test!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar   // (BOOL)被除錯時,被置為1
+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 SparePtr1 : Ptr32 Void
+0x024 SparePtr2 : 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   // 被除錯時,會被置為x070
+0x070 CriticalSectionTimeout : _LARGE_INTEGER
+0x078 HeapSegmentReserve : Uint4B
+0x07c HeapSegmentCommit : Uint4B
+0x080 HeapDeCommitTotalFreeThreshold : Uint4B
+0x084 HeapDeCommitFreeBlockThreshold : Uint4B
+0x088 NumberOfHeaps : Uint4B
+0x08c MaximumNumberOfHeaps : Uint4B
+0x090 ProcessHeaps : Ptr32 Ptr32 Void
+0x094 GdiSharedHandleTable : Ptr32 Void
+0x098 ProcessStarterHelper : Ptr32 Void
+0x09c GdiDCAttributeList : Uint4B
+0x0a0 LoaderLock : Ptr32 _RTL_CRITICAL_SECTION
+0x0a4 OSMajorVersion : Uint4B
+0x0a8 OSMinorVersion : Uint4B
+0x0ac OSBuildNumber : Uint2B
+0x0ae OSCSDVersion : Uint2B
+0x0b0 OSPlatformId : Uint4B
+0x0b4 ImageSubsystem : Uint4B
+0x0b8 ImageSubsystemMajorVersion : Uint4B
+0x0bc ImageSubsystemMinorVersion : Uint4B
+0x0c0 ImageProcessAffinityMask : Uint4B
+0x0c4 GdiHandleBuffer : [34] Uint4B
+0x14c PostProcessInitRoutine : Ptr32 void 
+0x150 TlsExpansionBitmap : Ptr32 Void
+0x154 TlsExpansionBitmapBits : [32] Uint4B
+0x1d4 SessionId : Uint4B
+0x1d8 AppCompatFlags : _ULARGE_INTEGER
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
+0x1e8 pShimData : Ptr32 Void
+0x1ec AppCompatInfo : Ptr32 Void
+0x1f0 CSDVersion : _UNICODE_STRING
+0x1f8 ActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA
+0x1fc ProcessAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP
+0x200 SystemDefaultActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA
+0x204 SystemAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP
+0x208 MinimumStackCommit : Uint4B
+0x20c FlsCallback : Ptr32 Ptr32 Void
+0x210 FlsListHead : _LIST_ENTRY
+0x218 FlsBitmap : Ptr32 Void
+0x21c FlsBitmapBits : [4] Uint4B
+0x22c FlsHighIndex : Uint4B

 

(3)IsDebuggerPresent函式的反反除錯

  方法1: 在dbg中直接對IsDebuggerPresent函式進行修改,讓其返回即可。  

  方法2: 對Kernel32.dll中的IsDebuggerPresent函式實現MiniHook(或InlineHook),在FakeIsDebuggerPresent函式中返回FALSE即可。編寫一個dll,在載入到程式時,執行MiniHook。這裡因為MiniHook框架太大,就不貼出相關程式了,有需要可以留言,此處只附出DLL的編寫。

#include "MiniHook.h"

typedef
BOOL
(WINAPI* LPFN_ISDEBUGGERPRESENT)();

LPFN_ISDEBUGGERPRESENT __OriginalIsDebuggerPresent = NULL;

BOOL
WINAPI
FakeIsDebuggerPresent(VOID)
{
    return FALSE;
}

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        if (SunInitialize() != STATUS_SUCCESS)
        {
            MessageBox(0,"初始化失敗","提示",0);
        }
        if (SunCreateHook(&IsDebuggerPresent, &FakeIsDebuggerPresent,
            reinterpret_cast<LPVOID*>(&__OriginalIsDebuggerPresent)) != STATUS_SUCCESS)
        {
            MessageBox(0,"初始化Hook失敗","提示",0);
        }
        if (SunEnableHook(&IsDebuggerPresent) != STATUS_SUCCESS)
        {
            MessageBox(0, "建立Hook失敗", "提示", 0);
        }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        //SunRemoveHook(&IsDebuggerPresent);
        break;
    }
    return TRUE;
}

 


 

二、自定義實現IsDebuggerPresent函式(x86)

自己用匯編實現實現IsDebuggerPresent,如何獲得PEB呢? 在應用層,fs暫存器是指向當前執行緒的TEB結構的,而在核心層,fs暫存器指向PCR(Processor Control Region)的記憶體,其資料型別是KPCR。所以可以通過當前執行緒的TEB獲得PEB,再取出BeingDebugged的值。

 

原理:

  mov   eax, fs:18h     // TEB Self指標

  mov   eax, [eax+30h]  // PEB

  movzx eax, [eax+2]    // PEB->BeingDebugged

實現:

_asm
{
    push eax;                   
    mov eax, fs:[0x30];         // PEB
    movzx eax, byte ptr[eax + 2];  //BeingDebugged
    mov dword ptr[Value], eax;   //取值
    pop eax;
}

if (Value)   //判斷
{
    MessageBox(0,L"檢測到偵錯程式",L"提示",0);
}
else
{
    MessageBox(0,L"未檢測到偵錯程式",L"提示",0);
}

 


 

 

三、深度解析除錯中各標誌位的變化

這部分轉載於:https://blog.csdn.net/qq_35713009/article/details/86603668/

 

  加密與解密中提供了從外部程式碼去除某程式BeingDebugged標誌的方式,可以作為偵錯程式的外掛去除這種反除錯方法。但BeingDebugged標記在生成時還留下了一些後患。

  那麼是否可以簡單認為將 PEB 的 BeingDebugged 標誌篡改就可以達到越過IsDebuggerPresent函式的目的呢?當然不能。因為除錯時不僅僅這一位被置值了,而是連鎖反應... ...

 

(1) PEB 的 BeingDebugged 標誌(BOOL型),被修改為1,表示正在被除錯

 

(2)NtGlobalFlag的變化

  LdrpInitialize函式是一個新程式的初始執行緒開始在使用者態執行的最早程式碼,在這個函式內部有一段這樣的程式碼:

if (Peb->BeingDebugged) 
{
    Peb->NtGlobalFlag |= FLG_HEAP_ENABLE_FREE_CHECK |

        FLG_HEAP_ENABLE_TAIL_CHECK |

        FLG_HEAP_VALIDATE_PARAMETERS;

}

這說明BeingDebugged被設定為1後,NtGlobalFlag也被設定了一個標記,通過除錯可發現這個標記為0x70。

 

(3)RtlCreateHeap中建立除錯堆時的變化

  那麼 NtGlobalFlag 有沒有向 BeingDebugged 一樣留下痕跡呢?是有的,在初始化堆的函式 RtlCreateHeap 中可以找到相應程式碼。

if (RtlpGetMode() == UserMode)
{
    /* Also check these flags if in usermode */
    if (NtGlobalFlags & FLG_HEAP_VALIDATE_ALL)
        Flags |= HEAP_VALIDATE_ALL_ENABLED;

    if (NtGlobalFlags & FLG_HEAP_VALIDATE_PARAMETERS)
        Flags |= HEAP_VALIDATE_PARAMETERS_ENABLED;

    if (NtGlobalFlags & FLG_USER_STACK_TRACE_DB)
        Flags |= HEAP_CAPTURE_STACK_BACKTRACES;
}

/* Call special heap */
if (RtlpHeapIsSpecial(Flags))
return RtlDebugCreateHeap(Flags, Addr, TotalSize, CommitSize, Lock, Parameters);

FORCEINLINE BOOLEAN
RtlpHeapIsSpecial(ULONG Flags)
{
    if (Flags & HEAP_SKIP_VALIDATION_CHECKS) return FALSE;

    if (Flags & (HEAP_FLAG_PAGE_ALLOCS |
        HEAP_VALIDATE_ALL_ENABLED |
        HEAP_VALIDATE_PARAMETERS_ENABLED |
        HEAP_CAPTURE_STACK_BACKTRACES |
        HEAP_CREATE_ENABLE_TRACING))
    {
        /* This is a special heap */
        return TRUE;
    }

    /* No need for a special treatment */
    return FALSE;
}

 

 

  RtlDebugCreateHeap內部實際上又呼叫了RtlCreateHeap,不過這次Flags引數又多了幾個屬性,分別是HEAP_SKIP_VALIDATION_CHECKS, HEAP_TAIL_CHECKING_ENABLED, HEAP_FREE_CHECKING_ENABLED。第一個屬性使得 RtlCreateHeap和RtlDebugCreateHeap不會再繼續重複的相互呼叫。

/* All validation performed, now call the real routine with skip validation check flag */
Flags |= HEAP_SKIP_VALIDATION_CHECKS |
HEAP_TAIL_CHECKING_ENABLED |
HEAP_FREE_CHECKING_ENABLED;

Heap = RtlCreateHeap(Flags, Addr, ReserveSize, CommitSize, Lock, Parameters);

 

與後兩個標記有關的有這樣一段程式碼,

if (Heap->Flags & HEAP_FREE_CHECKING_ENABLED) {
    RtlFillMemoryUlong((PCHAR)(BusyBlock + 1), Size & ~0x3, ALLOC_HEAP_FILL);

}

if (Heap->Flags & HEAP_TAIL_CHECKING_ENABLED) {
    RtlFillMemory((PCHAR)ReturnValue + Size,

        CHECK_HEAP_TAIL_SIZE,

        CHECK_HEAP_TAIL_FILL)

        BusyBlock->Flags |= HEAP_ENTRY_FILL_PATTERN;

}

#define ALLOC_HEAP_FILL 0xBAADF00D

#define FREE_HEAP_FILL 0xFEEEFEEE

#define CHECK_HEAP_TAIL_FILL 0xAB

意思是會用這3種資料來填堆,即當堆中多次重複出現這3種資料時,就表示在除錯狀態中。這些東西通常被稱為HeapMagic。

 

除此之外,Flags在RltCreateHeap函式後面還有一些感染操作,使得 Heap->Flags 和 Heap->ForceFlags 都附帶了除錯資訊,通常在程式正常執行的情況下,Flags為2,ForceFlags為0;而在除錯狀態下,Flags為0x50000062,ForceFlags為0x40000060。

 

 

// x86下PEB結構
NTDLL_Test!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar // (BOOL)被除錯時,被置為1
+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 SparePtr1 : Ptr32 Void
+0x024 SparePtr2 : 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 // 被除錯時,會被置為x070
+0x070 CriticalSectionTimeout : _LARGE_INTEGER
+0x078 HeapSegmentReserve : Uint4B
+0x07c HeapSegmentCommit : Uint4B
+0x080 HeapDeCommitTotalFreeThreshold : Uint4B
+0x084 HeapDeCommitFreeBlockThreshold : Uint4B
+0x088 NumberOfHeaps : Uint4B
+0x08c MaximumNumberOfHeaps : Uint4B
+0x090 ProcessHeaps : Ptr32 Ptr32 Void
+0x094 GdiSharedHandleTable : Ptr32 Void
+0x098 ProcessStarterHelper : Ptr32 Void
+0x09c GdiDCAttributeList : Uint4B
+0x0a0 LoaderLock : Ptr32 _RTL_CRITICAL_SECTION
+0x0a4 OSMajorVersion : Uint4B
+0x0a8 OSMinorVersion : Uint4B
+0x0ac OSBuildNumber : Uint2B
+0x0ae OSCSDVersion : Uint2B
+0x0b0 OSPlatformId : Uint4B
+0x0b4 ImageSubsystem : Uint4B
+0x0b8 ImageSubsystemMajorVersion : Uint4B
+0x0bc ImageSubsystemMinorVersion : Uint4B
+0x0c0 ImageProcessAffinityMask : Uint4B
+0x0c4 GdiHandleBuffer : [34] Uint4B
+0x14c PostProcessInitRoutine : Ptr32 void 
+0x150 TlsExpansionBitmap : Ptr32 Void
+0x154 TlsExpansionBitmapBits : [32] Uint4B
+0x1d4 SessionId : Uint4B
+0x1d8 AppCompatFlags : _ULARGE_INTEGER
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
+0x1e8 pShimData : Ptr32 Void
+0x1ec AppCompatInfo : Ptr32 Void
+0x1f0 CSDVersion : _UNICODE_STRING
+0x1f8 ActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA
+0x1fc ProcessAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP
+0x200 SystemDefaultActivationContextData : Ptr32 _ACTIVATION_CONTEXT_DATA
+0x204 SystemAssemblyStorageMap : Ptr32 _ASSEMBLY_STORAGE_MAP
+0x208 MinimumStackCommit : Uint4B
+0x20c FlsCallback : Ptr32 Ptr32 Void
+0x210 FlsListHead : _LIST_ENTRY
+0x218 FlsBitmap : Ptr32 Void
+0x21c FlsBitmapBits : [4] Uint4B
+0x22c FlsHighIndex : Uint4B

相關文章