Windows 平台反调试相关的技术方法总结—part 2
字数 1041 2025-08-05 08:19:19

Windows平台反调试技术详解(Part 2)

1. 陷阱标志(TF)检查技术

技术原理

陷阱标志(TF)位于EFLAGS寄存器内,当TF设置为1时,CPU将在每个指令执行后产生INT 01h(单步)异常。

实现代码

BOOL isDebugged = TRUE;
__try {
    __asm {
        pushfd
        or dword ptr[esp], 0x100  // 设置Trap Flag
        popfd                     // 将值加载到EFLAGS寄存器
        nop
    }
}
__except (EXCEPTION_EXECUTE_HANDLER) {
    // 如果引发异常-调试器不存在
    isDebugged = FALSE;
}
if (isDebugged) {
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

绕过方法

  • 不要单步执行pushfd指令
  • 跳过该指令并在其后设置断点
  • 断点后继续跟踪执行

2. CheckRemoteDebuggerPresent和NtQueryInformationProcess

CheckRemoteDebuggerPresent实现

int main(int argc, char *argv[]) {
    BOOL isDebuggerPresent = FALSE;
    if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &isDebuggerPresent)) {
        if (isDebuggerPresent) {
            std::cout << "Stop debugging program!" << std::endl;
            exit(-1);
        }
    }
    return 0;
}

NtQueryInformationProcess实现

typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
    _In_ HANDLE ProcessHandle,
    _In_ UINT ProcessInformationClass,
    _Out_ PVOID ProcessInformation,
    _In_ ULONG ProcessInformationLength,
    _Out_opt_ PULONG ReturnLength
);

const UINT ProcessDebugPort = 7;

int main(int argc, char *argv[]) {
    pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;
    NTSTATUS status;
    DWORD isDebuggerPresent = 0;
    
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    if (NULL != hNtDll) {
        NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
        if (NULL != NtQueryInformationProcess) {
            status = NtQueryInformationProcess(
                GetCurrentProcess(),
                ProcessDebugPort,
                &isDebuggerPresent,
                sizeof(DWORD),
                NULL);
            if (status == 0x00000000 && isDebuggerPresent != 0) {
                std::cout << "Stop debugging program!" << std::endl;
                exit(-1);
            }
        }
    }
    return 0;
}

绕过方法

使用mhook库钩子NtQueryInformationProcess函数:

#include <Windows.h>
#include "mhook.h"

typedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(
    _In_ HANDLE ProcessHandle,
    _In_ UINT ProcessInformationClass,
    _Out_ PVOID ProcessInformation,
    _In_ ULONG ProcessInformationLength,
    _Out_opt_ PULONG ReturnLength
);

const UINT ProcessDebugPort = 7;
pfnNtQueryInformationProcess g_origNtQueryInformationProcess = NULL;

NTSTATUS NTAPI HookNtQueryInformationProcess(
    _In_ HANDLE ProcessHandle,
    _In_ UINT ProcessInformationClass,
    _Out_ PVOID ProcessInformation,
    _In_ ULONG ProcessInformationLength,
    _Out_opt_ PULONG ReturnLength
) {
    NTSTATUS status = g_origNtQueryInformationProcess(
        ProcessHandle,
        ProcessInformationClass,
        ProcessInformation,
        ProcessInformationLength,
        ReturnLength);
    
    if (status == 0x00000000 && ProcessInformationClass == ProcessDebugPort) {
        *((PDWORD_PTR)ProcessInformation) = 0;
    }
    return status;
}

DWORD SetupHook(PVOID pvContext) {
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    if (NULL != hNtDll) {
        g_origNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, "NtQueryInformationProcess");
        if (NULL != g_origNtQueryInformationProcess) {
            Mhook_SetHook((PVOID*)&g_origNtQueryInformationProcess, HookNtQueryInformationProcess);
        }
    }
    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    switch (fdwReason) {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(hInstDLL);
            CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)SetupHook, NULL, NULL, NULL);
            Sleep(20);
        case DLL_PROCESS_DETACH:
            if (NULL != g_origNtQueryInformationProcess) {
                Mhook_Unhook((PVOID*)&g_origNtQueryInformationProcess);
            }
            break;
    }
    return TRUE;
}

3. 基于NtQueryInformationProcess的其他反调试技术

3.1 ProcessDebugObjectHandle (0x1E)

status = NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessDebugObjectHandle,
    &hProcessDebugObject,
    sizeof(HANDLE),
    NULL);
if (0x00000000 == status && NULL != hProcessDebugObject) {
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

3.2 ProcessDebugFlags (0x1F)

status = NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessDebugObjectHandle,
    &debugFlags,
    sizeof(ULONG),
    NULL);
if (0x00000000 == status && NULL != debugFlags) {
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

3.3 ProcessBasicInformation (0x00)

std::wstring GetProcessNameById(DWORD pid) {
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        return 0;
    }
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    std::wstring processName = L"";
    
    if (!Process32First(hProcessSnap, &pe32)) {
        CloseHandle(hProcessSnap);
        return processName;
    }
    
    do {
        if (pe32.th32ProcessID == pid) {
            processName = pe32.szExeFile;
            break;
        }
    } while (Process32Next(hProcessSnap, &pe32));
    
    CloseHandle(hProcessSnap);
    return processName;
}

status = NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessBasicInformation,
    &processBasicInformation,
    sizeof(PROCESS_BASIC_INFORMATION),
    NULL);
    
std::wstring parentProcessName = GetProcessNameById((DWORD)processBasicInformation.InheritedFromUniqueProcessId);
if (L"devenv.exe" == parentProcessName) {
    std::cout << "Stop debugging program!" << std::endl;
    exit(-1);
}

绕过方法

修改NtQueryInformationProcess返回值:

  1. 将ProcessDebugObjectHandle设置为0
  2. 将ProcessDebugFlags设置为1
  3. 对于ProcessBasicInformation,将InheritedFromUniqueProcessId值更改为另一个进程ID

4. 断点检测技术

4.1 软件断点检测

DWORD CalcFuncCrc(PUCHAR funcBegin, PUCHAR funcEnd) {
    DWORD crc = 0;
    for (; funcBegin < funcEnd; ++funcBegin) {
        crc += *funcBegin;
    }
    return crc;
}

#pragma auto_inline(off)
VOID DebuggeeFunction() {
    int calc = 0;
    calc += 2;
    calc <<= 8;
    calc -= 3;
}
VOID DebuggeeFunctionEnd() {};
#pragma auto_inline(on)

DWORD g_origCrc = 0x2bd0;

int main() {
    DWORD crc = CalcFuncCrc((PUCHAR)DebuggeeFunction, (PUCHAR)DebuggeeFunctionEnd);
    if (g_origCrc != crc) {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    return 0;
}

4.2 硬件断点检测

CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if (GetThreadContext(GetCurrentThread(), &ctx)) {
    if (ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0) {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
}

硬件断点重置

CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
SetThreadContext(GetCurrentThread(), &ctx);

绕过方法

钩子NtGetContextThread和NtSetContextThread函数:

typedef NTSTATUS(NTAPI *pfnNtGetContextThread)(
    _In_ HANDLE ThreadHandle,
    _Out_ PCONTEXT pContext
);

typedef NTSTATUS(NTAPI *pfnNtSetContextThread)(
    _In_ HANDLE ThreadHandle,
    _In_ PCONTEXT pContext
);

pfnNtGetContextThread g_origNtGetContextThread = NULL;
pfnNtSetContextThread g_origNtSetContextThread = NULL;

NTSTATUS NTAPI HookNtGetContextThread(
    _In_ HANDLE ThreadHandle,
    _Out_ PCONTEXT pContext
) {
    DWORD backupContextFlags = pContext->ContextFlags;
    pContext->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
    NTSTATUS status = g_origNtGetContextThread(ThreadHandle, pContext);
    pContext->ContextFlags = backupContextFlags;
    return status;
}

NTSTATUS NTAPI HookNtSetContextThread(
    _In_ HANDLE ThreadHandle,
    _In_ PCONTEXT pContext
) {
    DWORD backupContextFlags = pContext->ContextFlags;
    pContext->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
    NTSTATUS status = g_origNtSetContextThread(ThreadHandle, pContext);
    pContext->ContextFlags = backupContextFlags;
    return status;
}

void HookThreadContext() {
    HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
    g_origNtGetContextThread = (pfnNtGetContextThread)GetProcAddress(hNtDll, "NtGetContextThread");
    g_origNtSetContextThread = (pfnNtSetContextThread)GetProcAddress(hNtDll, "NtSetContextThread");
    Mhook_SetHook((PVOID*)&g_origNtGetContextThread, HookNtGetContextThread);
    Mhook_SetHook((PVOID*)&g_origNtSetContextThread, HookNtSetContextThread);
}

5. SEH(结构化异常处理)反调试

实现代码

BOOL g_isDebuggerPresent = TRUE;

EXCEPTION_DISPOSITION ExceptionRoutine(
    PEXCEPTION_RECORD ExceptionRecord,
    PVOID EstablisherFrame,
    PCONTEXT ContextRecord,
    PVOID DispatcherContext
) {
    g_isDebuggerPresent = FALSE;
    ContextRecord->Eip += 1;
    return ExceptionContinueExecution;
}

int main() {
    __asm {
        // 设置SEH处理程序
        push ExceptionRoutine
        push dword ptr fs:[0]
        mov dword ptr fs:[0], esp
        
        // 生成中断
        int 3h
        
        // 恢复原始SEH处理程序
        mov eax, [esp]
        mov dword ptr fs:[0], eax
        add esp, 8
    }
    
    if (g_isDebuggerPresent) {
        std::cout << "Stop debugging program!" << std::endl;
        exit(-1);
    }
    return 0;
}

总结

本文详细介绍了Windows平台上的多种反调试技术,包括:

  1. 陷阱标志(TF)检查
  2. CheckRemoteDebuggerPresent和NtQueryInformationProcess检测
  3. 基于NtQueryInformationProcess的其他检测方法
    • ProcessDebugObjectHandle
    • ProcessDebugFlags
    • ProcessBasicInformation
  4. 断点检测技术
    • 软件断点检测
    • 硬件断点检测和重置
  5. SEH(结构化异常处理)反调试

对于每种技术,都提供了详细的实现代码和相应的绕过方法。这些技术可以单独使用,也可以组合使用以提高反调试效果。在实际应用中,开发者应根据具体需求选择合适的技术组合。

Windows平台反调试技术详解(Part 2) 1. 陷阱标志(TF)检查技术 技术原理 陷阱标志(TF)位于EFLAGS寄存器内,当TF设置为1时,CPU将在每个指令执行后产生INT 01h(单步)异常。 实现代码 绕过方法 不要单步执行pushfd指令 跳过该指令并在其后设置断点 断点后继续跟踪执行 2. CheckRemoteDebuggerPresent和NtQueryInformationProcess CheckRemoteDebuggerPresent实现 NtQueryInformationProcess实现 绕过方法 使用mhook库钩子NtQueryInformationProcess函数: 3. 基于NtQueryInformationProcess的其他反调试技术 3.1 ProcessDebugObjectHandle (0x1E) 3.2 ProcessDebugFlags (0x1F) 3.3 ProcessBasicInformation (0x00) 绕过方法 修改NtQueryInformationProcess返回值: 将ProcessDebugObjectHandle设置为0 将ProcessDebugFlags设置为1 对于ProcessBasicInformation,将InheritedFromUniqueProcessId值更改为另一个进程ID 4. 断点检测技术 4.1 软件断点检测 4.2 硬件断点检测 硬件断点重置 绕过方法 钩子NtGetContextThread和NtSetContextThread函数: 5. SEH(结构化异常处理)反调试 实现代码 总结 本文详细介绍了Windows平台上的多种反调试技术,包括: 陷阱标志(TF)检查 CheckRemoteDebuggerPresent和NtQueryInformationProcess检测 基于NtQueryInformationProcess的其他检测方法 ProcessDebugObjectHandle ProcessDebugFlags ProcessBasicInformation 断点检测技术 软件断点检测 硬件断点检测和重置 SEH(结构化异常处理)反调试 对于每种技术,都提供了详细的实现代码和相应的绕过方法。这些技术可以单独使用,也可以组合使用以提高反调试效果。在实际应用中,开发者应根据具体需求选择合适的技术组合。