静态反调试技术

@author: dlive

0x01 PEB

PEB中与反调试技术密切相关的成员有如下几个

+0x002 BeingDebugged; UChar
+0x00c Ldr 			; Ptr32 _PEB_LDR_DATA
+0X018 ProcessHeap	; Ptr32 Void
+0x068 NtGlobalFlag	; Uint4B

BeingDebugged成员是一个标志,用来表示进程是否处于被调试状态

Ldr, ProcessHeap, NtGloabFlag成员与被调试进程的堆内存特性相关

获取PEB结构体的地址

# 第一种方法
MOV EAX, DWORD PTR FS:[0x30]
# 第二种方法
MOV EAX, DWORD PTR FS:[0x18]
MOV EAX, DWORD PTR DS:[EAX+0x30]

BeingDebugged(+0x2)

通过IsDebuggerPresent() API可以获取PEB.BeingDebugged的值,若为1则表示进程处于被调试状态

破解方法:只要借助OD,将PEB.BeingDebugged的值修改为0即可

Ldr(+0xC)

调试进程时其堆内存就会出现一些特殊标识,表示它正处于被调试状态

其中最醒目的是,未使用的堆内存区域全部填充着0xFEEEFEEE,这证明正在调试进程

PEB.Ldr成员是一个指向_PEB_LDR_DATA结构体的指针,而_PEB_LDR_DATA结构体恰好是在堆内存区域中创建的,所以扫描该区域即可轻松查找是否存在0xFEEEFEEE区域

破解方法:只要将填充着0xFEEEFEEE的区域全部覆写为NULL即可

注意:该方法只适用于WindowsXP,而在Windows Vista以后的系统中则无法使用。另外,利用附加功能将运行中的进程附加到调试器时,堆内存中并不出现上述标识。

ProcessHeap(+0x18)

PEB.ProcessHeap成员是指向HEAP结构体的指针

GetProcessHeap() API可以获取PEB.ProcessHeap结构体的地址

HEAP结构体中Flags(+0xC)和ForceFlags(+0x10)和反调试相关,进程正常运行时,Heap.Flags的值为0x2,Heap.ForceFlags的值为0x0,进程处于调试状态时这些值会改变。

破解方法:修改HEAP.Flags =2, HEAP.ForceFlags=0

注意:该方法只适用于WindowsXP,而在Windows Vista以后的系统中则无法使用。另外,利用附加功能将运行中的进程附加到调试器时,也不会有上述特征。

NtGlobalFlag(0x68)

调试进程时,PEB.NtGlobalFlag会被设置为0x70

注意:利用附加功能将运行中的进程附加到调试器时,不会有上述特征。

0x02 NtQueryInformationProcess API

https://msdn.microsoft.com/en-us/library/ms684280(VS.85).aspx

NTSTATUS WINAPI NtQueryInformationProcess(
  _In_      HANDLE           ProcessHandle,
  _In_      PROCESSINFOCLASS ProcessInformationClass,
  _Out_     PVOID            ProcessInformation,
  _In_      ULONG            ProcessInformationLength,
  _Out_opt_ PULONG           ReturnLength
);

为该函数第二个参数ProcessInformationClass设定特定的值,函数执行结果会保存在第三个参数ProcessInformation中

第二个参数为枚举类型,其中与反调试相关的参数有ProcessDebugPort(0x07),ProcessDebugObject-Handle(0x1E),ProcessDebugFlags(0x1F)

ProcessDebugPort(0x7)

非调试状态下debugport == 0

// ProcessDebugPort (0x7)
    DWORD dwDebugPort = 0;
    pNtQueryInformationProcess(GetCurrentProcess(),
                               ProcessDebugPort,
                               &dwDebugPort,
                               sizeof(dwDebugPort),
                               NULL);
    printf("NtQueryInformationProcess(ProcessDebugPort) = 0x%X\n", dwDebugPort);
    if( dwDebugPort != 0x0  )  printf("  => Debugging!!!\n\n");
    else                       printf("  => Not debugging...\n\n");

checkRemoteDebuggerPresent()API可以用来检测进程是否处于被调试状态,与IsDebuggerPresent API不同的是,它不仅可以检测当前进程是否处于被调试状态,也可检测其他进程。该函数的实现中使用了NtQueryInformationProcess(ProcessDebugPort) API

ProcessDebugObjectHandle(0x1E)

// ProcessDebugObjectHandle (0x1E)
    HANDLE hDebugObject = NULL;
    pNtQueryInformationProcess(GetCurrentProcess(),
                               ProcessDebugObjectHandle,
                               &hDebugObject,
                               sizeof(hDebugObject),
                               NULL);
    printf("NtQueryInformationProcess(ProcessDebugObjectHandle) = 0x%X\n", hDebugObject);
    if( hDebugObject != 0x0  )  printf("  => Debugging!!!\n\n");
    else                        printf("  => Not debugging...\n\n");

第二个参数为ProcessDebugObjectHandle时,第三个参数返回为被调试对象句柄,当返回NULL时说明进程处于非调试状态

ProcessDebugFlags(0x1F)

    // ProcessDebugFlags (0x1F)
    BOOL bDebugFlag = TRUE;
    pNtQueryInformationProcess(GetCurrentProcess(),
                               ProcessDebugFlags,
                               &bDebugFlag,
                               sizeof(bDebugFlag),
                               NULL);
    printf("NtQueryInformationProcess(ProcessDebugFlags) = 0x%X\n", bDebugFlag);
    if( bDebugFlag == 0x0  )  printf("  => Debugging!!!\n\n");
    else                      printf("  => Not debugging...\n\n");

ProcessDebugFlags参数用于获取调试标识,DebugFlag的值若为0则处于调试状态,若为1则处于非调试状态

破解方法

patch函数/修改函数返回值/API Hook

OD中advanced olly提供了对该API Hook的功能

0x03 NtQuerySytemInformation API

检测系统是否以调试模式运行(WinDbg调试需要)

之前进程隐藏的章节中使用过ZwQuerySytemInformation API来获取进程列表,用户层Zw和Nt没什么区别,所以这里使用Zw也是可以的

NTSTATUS WINAPI NtQuerySystemInformation(
  _In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
  _Inout_   PVOID                    SystemInformation,
  _In_      ULONG                    SystemInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

SystemInformationClass参数中指定需要的系统信息类型,将某结构体的地址传递给SystemInformation参数,API结束时,该结构体中就填充着相关的信息

SystemKernelDebuggerInformation(0x23)

NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)  
                                GetProcAddress(GetModuleHandle(L"ntdll"), 
                                               "NtQuerySystemInformation");

    ULONG SystemKernelDebuggerInformation = 0x23;
    ULONG ulReturnedLength = 0;
    SYSTEM_KERNEL_DEBUGGER_INFORMATION DebuggerInfo = {0,};

    NtQuerySystemInformation(SystemKernelDebuggerInformation, 
                             (PVOID) &DebuggerInfo, 
                             sizeof(DebuggerInfo),      // 2 bytes
                             &ulReturnedLength);

    printf("NtQuerySystemInformation(SystemKernelDebuggerInformation) = 0x%X 0x%X\n", 
           DebuggerInfo.DebuggerEnabled, DebuggerInfo.DebuggerNotPresent);
    if( DebuggerInfo.DebuggerEnabled )  printf("  => Debugging!!!\n\n");
    else                                printf("  => Not debugging...\n\n");

DebuggerInfo.DebuggerEnabled==1时为调试状态

破解方法:Win XP: 编辑boot.ini 删除/debugport=com1 /baudrate=115200 /Debug

​ Win7: cmd执行 bcdedit /debug off

0x04 NtQueryObject API

系统中某个调试器调试进程时,会创建一个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试

ntdll!NtQueryObject可获得系统各种内核对象信息

NTSTATUS NtQueryObject(
  _In_opt_  HANDLE                   Handle,
  _In_      OBJECT_INFORMATION_CLASS ObjectInformationClass,
  _Out_opt_ PVOID                    ObjectInformation,
  _In_      ULONG                    ObjectInformationLength,
  _Out_opt_ PULONG                   ReturnLength
);

该API与上面讲过的API使用方法类似

typedef enum _OBJECT_INFORMATION_CLASS { 
  ObjectBasicInformation,
  ObjectTypeInformation,
  ObjectNameInformation,
  ObjectAllInformation, //3
  ObjectHandleInformation
} OBJECT_INFORMATION_CLASS;

ObjectAllInformation(0x3)

使用ObjectAllInformation可获得系统所有对象信息,然后从中检测是否存在调试对象

0x05 ZwSetInformationThread API

强制分离被调试者与调试者的技术,使用该API被调试者可将自身从调试器中分离出来(使调试进程终止运行,同时终止自身进程),该API不会对正常运行的程序(非调试运行)产生任何影响。

NTSTATUS ZwSetInformationThread(
        HANDLE ThreadHandle,
        THREAD_INFORMATION_CLASS ThreadInformationClass,
        PVOID ThreadInformation,
        ULONG ThreadInformationLength
);

ThreadHideFromDebugger(0x11)

API第二个参数设置为ThreadHideFromDebugger即可达到分离调试进程的目的

void DetachDebugger()
{
    typedef enum _THREAD_INFORMATION_CLASS {
        ThreadBasicInformation,
        ThreadTimes,
        ThreadPriority,
        ThreadBasePriority,
        ThreadAffinityMask,
        ThreadImpersonationToken,
        ThreadDescriptorTableEntry,
        ThreadEnableAlignmentFaultFixup,
        ThreadEventPair,
        ThreadQuerySetWin32StartAddress,
        ThreadZeroTlsCell,
        ThreadPerformanceCount,
        ThreadAmILastThread,
        ThreadIdealProcessor,
        ThreadPriorityBoost,
        ThreadSetTlsArrayAddress,
        ThreadIsIoPending,
        ThreadHideFromDebugger           // 17 (0x11)
    } THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;

    typedef NTSTATUS (WINAPI* ZWSETINFORMATIONTHREAD)(
        HANDLE ThreadHandle,
        THREAD_INFORMATION_CLASS ThreadInformationClass,
        PVOID ThreadInformation,
        ULONG ThreadInformationLength
    );

    ZWSETINFORMATIONTHREAD pZwSetInformationThread = NULL;
    pZwSetInformationThread = (ZWSETINFORMATIONTHREAD)
                              GetProcAddress(GetModuleHandle(L"ntdll.dll"), 
                                             "ZwSetInformationThread");

    pZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);

    printf("ZwSetInformationThread() -> Debugger detached!!!\n\n");
}

0x06 其他

检测当前运行环境是否处于一个逆向分析专用环境/系统

  1. 检测OD窗口 => FindWindow()
  2. 检测OD进程 => CreateToolhelp32Snapshot()
  3. 检查计算机名是否为TEST, ANALYSIS => GetComputerName()
  4. 检测程序运行路径是否存在TEST, SAMPLE => GetCommandLine()
  5. 检测是否为虚拟机环境 => VMWareService.exe , VMWareTray.exe, VMWareUser.exe
posted @ 2017-03-05 09:34  dlive  阅读(1172)  评论(0编辑  收藏  举报