本期作者/牛杰
前言
反调试技术,是一种防止逆向的方案。逆向人员如果遇到复杂的代码混淆,有时会使用调试器动态分析代码逻辑简化分析流程。例如恶意软件通常会被安全研究人员、反病毒厂商和其他安全专业人员分析和调试,以了解其行为和功能,并开发相应的安全措施来保护系统,这时,恶意软件开发人员就会使用反调试技术阻碍逆向人员的分析,以达到增加自己恶意代码的存活时间。此外,安全人员也需要了解反调试技术,当遇到反调试代码时,可以使用相对应的反反调试。
反调试
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); } returnbResult; }
软件断点检测
软件断点又称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原型如下:
typedefenum_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说明被调试。
2. ProcessDebugObjectHandle
通过NTDLL导出NtQueryInformationProcess函数,PROCESSINFOCLASS设置为0x1E,该值是进程的调试对象句柄(ProcessDebugObjectHandle),当该值存在且函数返回值不为NULL,说明进程处于调试状态,当返回值为NULL,或该值不存在,说明处于非调试状态。
3. 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;
测试结果如下
总结
本篇介绍了部分反调试的方法,在自己的代码中使用反调试技术,可以增加逆向人员的分析难度,或是通过了解这些技术的原理,在分析恶意代码时进行反反调试,在后续的文章中,将会介绍更多的反调试方法。
-
WINDOWS
+关注
关注
3文章
3541浏览量
88624 -
调试
+关注
关注
7文章
578浏览量
33923 -
调试技术
+关注
关注
0文章
7浏览量
6627 -
代码
+关注
关注
30文章
4779浏览量
68524 -
调试器
+关注
关注
1文章
303浏览量
23716
原文标题:反调试技术-上
文章出处:【微信号:蛇矛实验室,微信公众号:蛇矛实验室】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论