反调试总结

@lzeroyuee  May 19, 2020

反调试主要利用程序在调试状态下和非调试状态下的区别

PS:以下内容均来源于各大安全论坛与博客

检查调试器

PEB

1: kd> dt _PEB
nt!_PEB
   ...
   +0x002 BeingDebugged    : UChar
   ...
   +0x030 ProcessHeap      : Ptr64 Void
   ...
   +0x0bc NtGlobalFlag     : Uint4B
   ...
  1. BeingDebugged:表示是否在调试状态

    • 相关API:IsDebuggerPresent
  2. NtGlobalFlag:如果在调试状态下,此值为0x70

NtQueryInformationProcess

利用NtQueryInformationProcess获取进程相关调试信息

__kernel_entry NTSTATUS NtQueryInformationProcess(
  IN HANDLE           ProcessHandle,                // 进程句柄
  IN PROCESSINFOCLASS ProcessInformationClass,        // 进程信息类型
  OUT PVOID           ProcessInformation,            // 进程信息,与类型相关
  IN ULONG            ProcessInformationLength,        // 进程信息块长度
  OUT PULONG          ReturnLength                    // 返回长度
);

进程信息类型填写为:ProcessDebugPort (0x7)ProcessDebugObjectHandle (0x1e)

  • 当传入ProcessDebugPort时,返回一个调试端口值,在调试状态下此值为0xffffffff,非调试状态下为0,等价API:CheckRemoteDebuggerPresent
  • 当传入ProcessDebugObjectHandle时,返回一个调试对象句柄,在调试状态下为非0,非调试状态下为0

STARTUPINFO

程序正常启动时,CreateProcess的STARTUPINFO结构中的成员一般会被填为0,而调试启动时不会

故反调试检测可以检查STARTUPINFO结构中的成员是否为0,可以多检查几个成员,GetStartupInfo可以获取STARTUPINFO结构

SedebugPrivilege

一般在未提权情况下,程序正常启动不会有调试权限,但调试器启动程序时,应用程序会继承调试器的调试权限,因此可以检查调试权限来进行反调试

在管理员启动+调试权限下,可以通过OpenProcess来打开csrss.exe进程句柄,可以通过检查是否能拿到此句柄来判断是否有调试器存在

检查调试环境

注册表Just-in-time调试器

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug

中的Debugger键值对,可以检查此键值对

窗口检查

通过查找窗口来检查是否开启了调试器,常用API有:FindWindowEnumWindows

扩展:

  • 也可以检查进程的父进程是否是调试器
  • 也可以枚举遍历进程查找调试器

查找调试内核对象

NtQueryObject查询内核对象,当调试器调试进程时,会创建一个DebugObject对象,可以遍历内核对象列表查找有没有相应的调试对象

NTSTATUS NtQueryObject(
    _In_opt_  HANDLE  Handle,
    _In_        OBJECT_INFORMATION_CLASS objectInformationClass,        // 此值要传入0x3
    _Out_opt_ PVOID   ObjectInformation,                                // 传出参数,通过其可遍历内核对象
    _In_ ULONG ObjectInformationLength,
    _Out_opt_ PULONG ReturnLength
);

NtQuerySystemInformation

NtQueryInformationProcess,当传入类型为systemKernelDebuggerInfromation (0x32)时,若在调试状态下返回的system_kernel_Debug_Information的结构中的Debugedable为1

ZwSetInformationThread

传入参数ThreadHideFromDebugger (0x11)

当程序正常运行,该函数相当于空函数,当程序处于在调试状态下,则该函数可以使当前线程脱离调试器,即调试器无法获得此线程的调试事件

动态反调试

时钟检测

在调试状态下,跟踪代码比正常运行代码的时间要长。可以通过比较运行代码之间的时间间隔来判断是否被调试了

  • TSC寄存器保存着CPU的时钟周期计数,RDTSC指令将TSC寄存器的值装入EDX:EAX
  • 也可利用相关API:QueryPerformanceCounterGetTickCountGetSystemTimeGetLocalTime

异常处理

  • SEH

    在程序中主动触发异常RaiseException,如果此时存在调试器,那么异常会被调试器接收(没有调试器时,此异常会给注册的SEH,一般会在SEH中执行敏感操作),调试器接收了异常后无法按程序原定的流程继续执行,起到了反调试的作用

  • SetUnhandledExceptionFilter

    当SEH不存在时,则会调用UnhandledExceptionFilter,该函数检查是否存在调试器,存在就给调试器,不存在就调用默认的异常处理:弹出错误对话框结束进程

    SetUnhandledExceptionFilter可以设置异常处理函数来替换默认的异常处理,可以在此异常处理中做敏感操作,如果有调试器存在,此敏感操作不会被执行也可导致无法按程序原定流程执行

  • int 2d

    int 2d是一个特殊的指令,原为内核模式中用来触发断点异常的,也可以在用户模式下正常运行时触发异常,调试器不会触发此异常,只是忽略,如果遇到Int 2d指令,调试器无法执行单步指令,直到遇到断点

0xCC探测

0xCC即int 3指令,若在关键代码位置检测到有0xCC,即可判断进程处于调试状态(0xCC也可以为其他指令的值)

  • API断点:检查API首地址处是否存在断点
  • 校验和:对敏感区域进行校验和比较,如果存在调试器且正在调试当前敏感位置,校验和会不一样
  • 硬件断点检测:Dr0~Dr3保存硬件断点的地址,检查这几个寄存器的值是否为0就知道有没有硬件断点(可利用GetThreadContext或者主动触发异常来检查)

自调试

同一个进程不允许同时被两个调试器调试,可以自己调试自己防止被其他调试器调试

程序第一次打开创建同步对象,并且在以调试方式创建自己,使得第一次打开的程序成为第二次打开的程序的调试器

相关API:CreateProcessDebugActiveProcess

检查VMware

检测VMware也是检测一些特征. 根据检测的结果进行判断.

__indword(0x5658)

使用in指令通过0x5658端口读取数据,如果程序在 VMware内运行,则ebx寄存器的值就会变为0x564D5868

#include <iostream>
#include <string>

bool check_vmware()
{
    bool is_in_vmware = false;
    __try {
        __asm {
            // 保存寄存器环境
            push ebx
            push edx
            push ecx
            
            xor ebx, ebx
            mov ecx, 0xa
            mov edx, 0x5658     // dx = "VX"
            mov eax, 0x564D5868
            in eax, dx      // 若不在VMware内,此处会抛出异常
            
            // 比较ebx是否是0x564D5868,即"VMXh"
            cmp ebx, 0x564D5868
            sete is_in_vmware
            
            // 恢复环境
            pop ecx
            pop edx
            pop ebx
        }
    } __except (1)
    {
    }
    
    return is_in_vmware;
}

int main()
{
    if (check_vmware()) {
        std::cout << "Is in VMware." << std::endl;
    } else {
        std::cout << "Is not in VMware." << std::endl;
    }
    return 0;
}

添加新评论