IsDebuggerPresent的反调试与反反调试

2022/4/7 23:22:49

本文主要是介绍IsDebuggerPresent的反调试与反反调试,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、调用系统的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



这篇关于IsDebuggerPresent的反调试与反反调试的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程