网络靶场实战-反调试技术(上)

前言

反调试技术,是一种防止逆向的方案。逆向人员如果遇到复杂的代码混淆,有时会使用调试器动态分析代码逻辑简化分析流程。例如恶意软件通常会被安全研究人员、反病毒厂商和其他安全专业人员分析和调试,以了解其行为和功能,并开发相应的安全措施来保护系统,这时,恶意软件开发人员就会使用反调试技术阻碍逆向人员的分析,以达到增加自己恶意代码的存活时间。此外,安全人员也需要了解反调试技术,当遇到反调试代码时,可以使用相对应的反反调试。

反调试

1. IsDebuggerPresent

IsDebuggerPresent 用于检测当前进程是否正在被调试。该函数属于 Windows 调试辅助功能,可以帮助开发人员在程序运行过程中进行调试。

IsDebuggerPresent 函数的原型如下:

复制代码
BOOL IsDebuggerPresent(void);

该函数返回一个布尔值,如果当前进程正在被调试,则返回 TRUE;否则返回 FALSE。

检查进程环境块(PEB)中是否设置了正在调试的标志。

这实际上与IsDebuggerPresent()内部执行的代码相同。

x86的PEB指针从DWORD FS:0x30中获取,x64在QWORD GS:0x60中获取。

IsDebuggerPresent 函数只能检测当前进程是否正在被调试,而不能检测其他进程的调试状态。此外,安全研究人员和反病毒厂商可以使用各种技术和工具来绕过 IsDebuggerPresent 函数的检测,因此它并不是一个绝对可靠的方法来判断系统是否正在进行调试。

2. CheckRemoteDebuggerPresent

CheckRemoteDebuggerPresent 用于检测当前进程是否被远程调试器附加。该函数可以检测当前进程是否正在被远程调试器(如远程调试器工具或调试代理程序)监视和调试,恶意软件可以使用该函数来判断自身是否处于被远程调试的环境中,并根据检测结果采取相应的措施,如崩溃、隐藏关键代码等,以防止被分析和调试。

CheckRemoteDebuggerPresent 函数的原型如下:

复制代码
BOOL CheckRemoteDebuggerPresent(
  HANDLE hProcess,
  PBOOL pbDebuggerPresent
);

该函数接受两个参数:

  • hProcess:要检查的进程的句柄。通常使用 GetCurrentProcess() 函数获取当前进程的句柄。

  • pbDebuggerPresent:一个指向 BOOL 类型的变量的指针,用于接收检测结果。如果检测到远程调试器附加,则该变量被设置为 TRUE;否则设置为 FALSE。

3. 断点检测

断点是一种调试技术,用于在特定的内存地址上设置断点,以便在程序执行到该地址时触发中断,因此可以通过判断断点的存在与否来确认程序是否被调试,断点分为硬件断点与软件断点,检测的方式不同。

硬件断点检测

  • x86架构,DR0到DR3寄存器用于设置硬件断点的地址,DR4和DR5寄存器在x86架构中没有特定的用途,DR6寄存器是一个状态寄存器,用于指示硬件断点的触发情况。因此我们需要判断DR0-DR3的值,如果有值不为0,则处于调试状态,x64架构引入了新的调试寄存器,称为DR7寄存器,用于控制硬件断点和其他调试功能,但是判断是否被调试的方式与x86架构相同。获取值的代码如下:

    BOOL HardwareBreakpoints()
    {
    BOOL bResult = FALSE;
    PCONTEXT ctx = PCONTEXT(VirtualAlloc(NULL, sizeof(CONTEXT), MEM_COMMIT, PAGE_READWRITE));

    复制代码
    if (ctx) {
    
      SecureZeroMemory(ctx, sizeof(CONTEXT));
      ctx->ContextFlags = CONTEXT_DEBUG_REGISTERS;
      if (GetThreadContext(GetCurrentThread(), ctx)) {
        if (ctx->Dr0 != 0 || ctx->Dr1 != 0 || ctx->Dr2 != 0 || ctx->Dr3 != 0)
          bResult = TRUE;
      }
    
      VirtualFree(ctx, 0, MEM_RELEASE);
    }
    
    return bResult;

    }

软件断点检测

  • 软件断点又称int3,在IA-32指令集中用操作码CC (0xCC)表示,因此有时软件点的检测也称为"0xCC"检测,调试器在对应设置断点的位置上修改该地址的字节为0xCC。若是关键位置检测到该指令,可以判断进程处于调试状态。

4. PEB

在Windows操作系统中,PEB(Process Environment Block)是一个数据结构,它存储了进程的环境信息和状态。每个运行的进程都有一个独立的PEB。

1. BeingDebugged

与IsDebuggerPresent()内部执行的代码相同,获取方式如下:

复制代码
//x86
PPEB pPeb = (PPEB)__readfsdword(0x30);
//x64
PPEB pPeb = (PPEB)__readgsqword(0x60);

2. NtGlobalFlag

NtGlobalFlag 是PEB的一个字段,通常,当进程未被调试时,NtGlobalFlag字段包含值0x0。调试进程时,该字段通常包含值0x70。

该字段在x86越x64架构中的位置不同。

x86在PEB偏移0x68的位置,x64在PEB便0xBC的位置。

Windows内核全局标记,在Windows调试方案中经常用到。这个标记定义了一组系统的调试参数,包括启用或禁用调试技术的开关、造成崩溃的错误代码和处理方式等等。通过改变这个标记,可以在运行时设置和禁用不同的调试技术和错误处理方式,比如调试器只能访问当前进程、只允许用户模式调试、启用特定的错误处理方式等等。但由于NtGlobalFlag标记是内核全局标记,其改变会影响整个系统的行为,需要谨慎处理。

5.ProcessHeap

通过PEB偏移0x18可以找到ProcessHeap,结构体如下:

复制代码
struct _PEB32
{
    UCHAR InheritedAddressSpace; //0x0
    UCHAR ReadImageFileExecOptions; //0x1
    UCHAR BeingDebugged; //0x2
    union
    {
        UCHAR BitField; //0x3
        struct
        {
            UCHAR ImageUsesLargePages:1; //0x3
            UCHAR IsProtectedProcess:1; //0x3
            UCHAR IsImageDynamicallyRelocated:1; //0x3
            UCHAR SkipPatchingUser32Forwarders:1; //0x3
            UCHAR IsPackagedProcess:1; //0x3
            UCHAR IsAppContainer:1; //0x3
            UCHAR IsProtectedProcessLight:1; //0x3
            UCHAR IsLongPathAwareProcess:1; //0x3
        };
    };
    ULONG Mutant; //0x4
    ULONG ImageBaseAddress; //0x8
    ULONG Ldr; //0xc
    ULONG ProcessParameters; //0x10
    ULONG SubSystemData; //0x14
    ULONG ProcessHeap; //0x18
    ....
  }

在ProcessHeap加上偏移可以找到HeapFlags与ForceFlags,偏移的值根据系统版本和位数会有变化,如下表:

  • HeapFlags
  • ForceFlags

如果HeapFlags的值大于2,或ForceFlags的值大于0时,说明被调试。

6. INT 2D

int 2d反调试原理很简单,正常运行时int 2d触发异常,进入程序的异常处理函数。而当调试运行时,OD会处理该异常,将eip+1继续运行,因此可以在异常处理函数中添加一些操作,如果没有执行这些代码,说明被调试。这种只能检测原版Ollydbg,x64dbg和一些带有反检测插件的调试器无效。

7. 进程列表

一般情况下,主进程在主线程中启动核心代码

  • QueryInformationJobObject这个api可以获取当前程序所有的进程列表

  • 不论是主进程还是主线程,他们的ImageFileName应该是都是源程序的文件名filename.exe

8. NtQueryInformationProcess

NtQueryInformationProcess原型如下:

复制代码
NTSTATUS NTAPI NtQueryInformationProcess(
  HANDLE ProcessHandle,// 进程句柄
  PROCESSINFOCLASS ProcessInformationClass,// 检索的进程信息类型
  PVOID ProcessInformation,// 接收进程信息的缓冲区指针
  ULONG ProcessInformationLength,// 缓冲区指针大小
  PULONG ReturnLength // 实际接收的进程信息大小
);

PROCESSINFOCLASS原型如下:

复制代码
typedef enum _PROCESSINFOCLASS
{
    ProcessBasicInformation,
    ProcessQuotaLimits,
    ProcessIoCounters,
    ProcessVmCounters,
    ProcessTimes,
    ProcessBasePriority,
    ProcessRaisePriority,
    ProcessDebugPort, //0x7
    ProcessExceptionPort,
    ProcessAccessToken,
    ProcessLdtInformation,
    ProcessLdtSize,
    ProcessDefaultHardErrorMode,
    ProcessIoPortHandlers,
    ProcessPooledUsageAndLimits,
    ProcessWorkingSetWatch,
    ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup,
    ProcessPriorityClass,
    ProcessWx86Information,
    ProcessHandleCount,
    ProcessAffinityMask,
    ProcessPriorityBoost,
    ProcessDeviceMap,
    ProcessSessionInformation,
    ProcessForegroundInformation,
    ProcessWow64Information,
    ProcessImageFileName,
    ProcessLUIDDeviceMapsEnabled,
    ProcessBreakOnTermination,
    ProcessDebugObjectHandle, // 0x1E
    ProcessDebugFlags, // 0x1F
    ProcessHandleTracing,
    ProcessIoPriority,
    ProcessExecuteFlags,
    ProcessResourceManagement,
    ProcessCookie,
    ProcessImageInformation,
    ProcessCycleTime,
    ProcessPagePriority,
    ProcessInstrumentationCallback,
    ProcessThreadStackAllocation,
    ProcessWorkingSetWatchEx,
    ProcessImageFileNameWin32,
    ProcessImageFileMapping,
    ProcessAffinityUpdateMode,
    ProcessMemoryAllocationMode,
    ProcessGroupInformation,
    ProcessTokenVirtualizationEnabled,
    ProcessConsoleHostProcess,
    ProcessWindowInformation,
    ProcessHandleInformation,
    ProcessMitigationPolicy,
    ProcessDynamicFunctionTableInformation,
    ProcessHandleCheckingMode,
    ProcessKeepAliveCount,
    ProcessRevokeFileHandles,
    ProcessWorkingSetControl,
    ProcessHandleTable,
    ProcessCheckStackExtentsMode,
    ProcessCommandLineInformation,
    ProcessProtectionInformation,
    ProcessMemoryExhaustion,
    ProcessFaultInformation,
    ProcessTelemetryIdInformation,
    ProcessCommitReleaseInformation,
    ProcessDefaultCpuSetsInformation,
    ProcessAllowedCpuSetsInformation,
    ProcessSubsystemProcess,
    ProcessJobMemoryInformation,
    ProcessInPrivate,
    ProcessRaiseUMExceptionOnInvalidHandleClose,
    ProcessIumChallengeResponse,
    ProcessChildProcessInformation,
    ProcessHighGraphicsPriorityInformation,
    ProcessSubsystemInformation,
    ProcessEnergyValues,
    ProcessActivityThrottleState,
    ProcessActivityThrottlePolicy,
    ProcessWin32kSyscallFilterInformation,
    ProcessDisableSystemAllowedCpuSets,
    ProcessWakeInformation,
    ProcessEnergyTrackingState,
    ProcessManageWritesToExecutableMemory,REDSTONE3
    ProcessCaptureTrustletLiveDump,
    ProcessTelemetryCoverage,
    ProcessEnclaveInformation,
    ProcessEnableReadWriteVmLogging,
    ProcessUptimeInformation,
    ProcessImageSection,
    ProcessDebugAuthInformation,
    ProcessSystemResourceManagement,
    ProcessSequenceNumber,
    ProcessLoaderDetour,
    ProcessSecurityDomainInformation,
    ProcessCombineSecurityDomainsInformation,
    ProcessEnableLogging,
    ProcessLeapSecondInformation,
    ProcessFiberShadowStackAllocation,
    ProcessFreeFiberShadowStackAllocation,
    MaxProcessInfoClass
} PROCESSINFOCLASS;
  1. ProcessDbgPort

该方式是CheckRemoteDebuggerPresent的另一种调用方式。

通过NTDLL导出NtQueryInformationProcess函数,PROCESSINFOCLASS设置为7,该值是进程调试端口(ProcessDebugPort),该值不为0说明被调试。

  1. ProcessDebugObjectHandle

通过NTDLL导出NtQueryInformationProcess函数,PROCESSINFOCLASS设置为0x1E,该值是进程的调试对象句柄(ProcessDebugObjectHandle),当该值存在且函数返回值不为NULL,说明进程处于调试状态,当返回值为NULL,或该值不存在,说明处于非调试状态。

  1. ProcessDebugFlags

通过NTDLL导出NtQueryInformationProcess函数,PROCESSINFOCLASS设置为0x1f,该值获取了EPROCESS中的成员NoDebugInherit,该值为0说明被调试。

9. WUDFPlatform.dll模块

WUDFPlatform.dll模块中,有三个导出函数 WudfIsAnyDebuggerPresent,WudfIsKernelDebuggerPresent,WudfIsUserDebuggerPresent,分别为任何调试器、0环调试器和3环调试器,该模块只有x64。

通过调用这三个函数,如果返回值不为0,则正在被调试。

代码如下:

复制代码
HMODULE h_wudf = LoadLibrary(L"WUDFPlatform.dll");
    if (h_wudf == NULL) {
        cout << "fail" << endl;

    }

    // WudfIsAnyDebuggerPresent
    pWudfIsAnyDebuggerPresent WudfIsAnyDebuggerPresent = (pWudfIsAnyDebuggerPresent)GetProcAddress(h_wudf, "WudfIsAnyDebuggerPresent");
    if (WudfIsAnyDebuggerPresent == NULL) {
        cout << "未发现调试器" << endl;

    }
    if (WudfIsAnyDebuggerPresent() != 0) {
        cout << "发现调试器" << endl;
    }

    // WudfIsKernelDebuggerPresent
    pWudfIsKernelDebuggerPresent WudfIsKernelDebuggerPresent = (pWudfIsKernelDebuggerPresent)GetProcAddress(h_wudf, "WudfIsKernelDebuggerPresent");
    if (WudfIsKernelDebuggerPresent == NULL) {
        cout << "未发现3环调试器"" << endl;
    }
    if (WudfIsKernelDebuggerPresent() != 0) {
        cout << "发现0环调试器" << endl;
    }

    // pWudfIsUserDebuggerPresent
    pWudfIsUserDebuggerPresent WudfIsUserDebuggerPresent = (pWudfIsUserDebuggerPresent)GetProcAddress(h_wudf, "WudfIsUserDebuggerPresent");
    if (WudfIsUserDebuggerPresent == NULL) {
        cout << "未发现3环调试器" << endl;
    }
    if (WudfIsUserDebuggerPresent() != 0) {
        cout << "发现3环调试器" << endl;

测试结果如下

总结

本篇介绍了部分反调试的方法,在自己的代码中使用反调试技术,可以增加逆向人员的分析难度,或是通过了解这些技术的原理,在分析恶意代码时进行反反调试,在后续的文章中,将会介绍更多的反调试方法。

相关推荐
Eloudy2 小时前
全文 - IMEX 第01节 -IMEX overview
运维·服务器·网络
物联网软硬件开发-轨物科技3 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
Escalating_xu3 小时前
【Linux 网络编程】TCP 跨平台实战:用 Windows 客户端访问 Linux 服务器
linux·服务器·网络
是真的小外套4 小时前
2024年软考中级信息安全工程师|选择题考点+案例分析真题解析(含答案)
android·网络
小天源6 小时前
让访问数据可解释:IP 地球大屏、地区库更新与运行维护设计
网络·网络协议·tcp/ip
老李IT笔记6 小时前
连不上不一定是网不好:新系统不认的服务端配置卡在哪一档
运维·服务器·网络
小此方7 小时前
Linux网络(二十八):从NAT转换到正反向代理:图解内网通信、NAPT端口映射
linux·运维·网络
隐擎fox8 小时前
深入 Android 移动端风控与协议层对抗:内核级虚拟环境检测、网络堆栈特征伪装与住宅 IP 协同实战
android·网络·python·网络协议·tcp/ip
Da Da 泓8 小时前
[项目] --- 基于websocket的网页五子棋(可多用户)
网络·websocket·网络协议·多线程·项目
lazy H8 小时前
【计算机网络】第 2 篇:一条数据如何穿过网络?彻底理解数据封装与解封装
网络·经验分享·后端·学习·计算机网络