Anti Debug专题

11、遍历进程名检测调试器

一、课程目标

本节课主要学习如何通过遍历系统进程列表并检查进程名称来检测常见的调试器进程。这是反调试技术中一种直观且常用的方法。通过本课的学习,你将能够:

  1. 掌握遍历系统进程列表的基本方法
  2. 学会识别常见的调试器进程名称
  3. 理解黑名单检测技术的原理和应用
  4. 掌握多种进程遍历技术(Toolhelp32、PSAPI、NT API)
  5. 了解该技术的局限性和改进方法

二、名词解释表

名词 解释
进程遍历 枚举系统中所有正在运行的进程
Toolhelp32 Windows API提供的进程和线程信息查询接口
PSAPI Process Status Helper API,用于获取进程状态信息
CreateToolhelp32Snapshot 创建系统信息快照的API函数
PROCESSENTRY32 包含进程信息的结构体
进程黑名单 已知调试器或分析工具的进程名称列表

三、技术原理

3.1 进程遍历概述

进程遍历是反调试技术中的一种常见方法,通过检查系统中运行的进程列表,查找是否存在已知的调试器进程。这种方法基于一个简单的假设:如果系统中有调试器在运行,那么很可能是用于调试当前程序的。

3.2 常见调试器进程名称

以下是常见的调试器和分析工具进程名称:

经典调试器:

  • ollydbg.exe - OllyDbg
  • x32dbg.exe - x32dbg
  • x64dbg.exe - x64dbg
  • windbg.exe - WinDbg
  • idaq.exe - IDA Pro 32位
  • idaq64.exe - IDA Pro 64位

现代调试器:

  • cheatengine-x86_64.exe - Cheat Engine
  • cheatengine-i386.exe - Cheat Engine
  • cheatengine.exe - Cheat Engine
  • immunitydebugger.exe - Immunity Debugger
  • radare2.exe - Radare2

沙箱和分析工具:

  • sandboxie.exe - Sandboxie
  • joeboxserver.exe - Joe Sandbox
  • joeboxcontrol.exe - Joe Sandbox
  • apimonitor.exe - API Monitor
  • processhacker.exe - Process Hacker

3.3 检测原理

通过定期遍历系统进程列表并检查进程名称,可以发现是否有调试器在运行。这种方法的优点是实现简单,但缺点是容易被绕过(通过重命名进程)。

四、代码实现

4.1 基于Toolhelp32的进程遍历

#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <stdio.h>
#include <vector>
#include <string>

// 常见调试器进程名称列表
const char* g_debuggerNames[] = {
    "ollydbg.exe",
    "x32dbg.exe",
    "x64dbg.exe",
    "windbg.exe",
    "idaq.exe",
    "idaq64.exe",
    "cheatengine-x86_64.exe",
    "cheatengine-i386.exe",
    "cheatengine.exe",
    "immunitydebugger.exe",
    "radare2.exe",
    "sandboxie.exe",
    "joeboxserver.exe",
    "joeboxcontrol.exe",
    "apimonitor.exe",
    "processhacker.exe",
    "procmon.exe",
    "tcpview.exe",
    "autoruns.exe",
    "procexp.exe",
    "wireshark.exe",
    "fiddler.exe",
    "scyllahide.exe",
    "scylla_hide.exe"
};

const int g_debuggerCount = sizeof(g_debuggerNames) / sizeof(g_debuggerNames[0]);

// 字符串转小写
void ToLower(char* str) {
    while (*str) {
        *str = tolower(*str);
        str++;
    }
}

// 检查进程名是否在黑名单中
BOOL IsDebuggerProcess(const char* processName) {
    char lowerName[MAX_PATH];
    strcpy_s(lowerName, processName);
    ToLower(lowerName);
    
    for (int i = 0; i < g_debuggerCount; i++) {
        char lowerDebugger[MAX_PATH];
        strcpy_s(lowerDebugger, g_debuggerNames[i]);
        ToLower(lowerDebugger);
        
        if (strstr(lowerName, lowerDebugger) != NULL) {
            return TRUE;
        }
    }
    
    return FALSE;
}

// 基于Toolhelp32的进程遍历检测
BOOL DetectDebuggersViaToolhelp32() {
    // 创建进程快照
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
    
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    
    // 遍历进程
    if (Process32First(hSnapshot, &pe32)) {
        do {
            // 检查进程名
            if (IsDebuggerProcess(pe32.szExeFile)) {
                printf("检测到调试器进程: %s (PID: %lu)\n", pe32.szExeFile, pe32.th32ProcessID);
                CloseHandle(hSnapshot);
                return TRUE;
            }
        } while (Process32Next(hSnapshot, &pe32));
    }
    
    CloseHandle(hSnapshot);
    return FALSE;
}

4.2 基于PSAPI的进程遍历

// 基于PSAPI的进程遍历检测
BOOL DetectDebuggersViaPSAPI() {
    // 获取进程列表
    DWORD processIds[1024];
    DWORD bytesReturned;
    
    if (!EnumProcesses(processIds, sizeof(processIds), &bytesReturned)) {
        return FALSE;
    }
    
    DWORD processCount = bytesReturned / sizeof(DWORD);
    
    // 遍历进程
    for (DWORD i = 0; i < processCount; i++) {
        if (processIds[i] != 0) {
            // 打开进程
            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIds[i]);
            if (hProcess != NULL) {
                // 获取进程名称
                char processName[MAX_PATH];
                if (GetProcessImageFileNameA(hProcess, processName, sizeof(processName))) {
                    // 提取文件名
                    char* fileName = strrchr(processName, '\\');
                    if (fileName != NULL) {
                        fileName++;  // 跳过反斜杠
                        
                        // 检查是否为调试器
                        if (IsDebuggerProcess(fileName)) {
                            printf("检测到调试器进程: %s (PID: %lu)\n", fileName, processIds[i]);
                            CloseHandle(hProcess);
                            return TRUE;
                        }
                    }
                }
                CloseHandle(hProcess);
            }
        }
    }
    
    return FALSE;
}

4.3 基于NT API的进程遍历

// NT API相关定义
typedef enum _SYSTEM_INFORMATION_CLASS {
    SystemBasicInformation = 0,
    SystemProcessInformation = 5,
} SYSTEM_INFORMATION_CLASS;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    LARGE_INTEGER WorkingSetPrivateSize;
    ULONG HardFaultCount;
    ULONG NumberOfThreadsHighWatermark;
    ULONGLONG CycleTime;
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
    ULONG HandleCount;
    ULONG SessionId;
    ULONG_PTR UniqueProcessKey;
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG PageFaultCount;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    SIZE_T QuotaPeakPagedPoolUsage;
    SIZE_T QuotaPagedPoolUsage;
    SIZE_T QuotaPeakNonPagedPoolUsage;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER ReadOperationCount;
    LARGE_INTEGER WriteOperationCount;
    LARGE_INTEGER OtherOperationCount;
    LARGE_INTEGER ReadTransferCount;
    LARGE_INTEGER WriteTransferCount;
    LARGE_INTEGER OtherTransferCount;
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;

typedef NTSTATUS (NTAPI *PNtQuerySystemInformation)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

// 基于NT API的进程遍历检测
BOOL DetectDebuggersViaNTAPI() {
    // 获取NT API函数地址
    HMODULE hNtdll = GetModuleHandle(L"ntdll.dll");
    if (hNtdll == NULL) {
        return FALSE;
    }
    
    PNtQuerySystemInformation NtQuerySystemInformation = 
        (PNtQuerySystemInformation)GetProcAddress(hNtdll, "NtQuerySystemInformation");
    
    if (NtQuerySystemInformation == NULL) {
        return FALSE;
    }
    
    // 分配缓冲区
    ULONG bufferSize = 0x10000;
    PVOID buffer = malloc(bufferSize);
    if (buffer == NULL) {
        return FALSE;
    }
    
    // 查询系统进程信息
    NTSTATUS status;
    while ((status = NtQuerySystemInformation(SystemProcessInformation, buffer, bufferSize, &bufferSize)) 
           == 0xC0000004) {  // STATUS_INFO_LENGTH_MISMATCH
        free(buffer);
        buffer = malloc(bufferSize);
        if (buffer == NULL) {
            return FALSE;
        }
    }
    
    if (status != 0) {  // STATUS_SUCCESS
        free(buffer);
        return FALSE;
    }
    
    // 遍历进程信息
    PSYSTEM_PROCESS_INFORMATION spi = (PSYSTEM_PROCESS_INFORMATION)buffer;
    
    while (spi->NextEntryOffset != 0) {
        if (spi->ImageName.Buffer != NULL && spi->ImageName.Length > 0) {
            // 将Unicode字符串转换为ANSI
            char processName[MAX_PATH];
            WideCharToMultiByte(CP_ACP, 0, spi->ImageName.Buffer, spi->ImageName.Length / sizeof(WCHAR), 
                               processName, sizeof(processName), NULL, NULL);
            processName[spi->ImageName.Length / sizeof(WCHAR)] = '\0';
            
            // 检查是否为调试器
            if (IsDebuggerProcess(processName)) {
                printf("检测到调试器进程: %s (PID: %p)\n", processName, spi->UniqueProcessId);
                free(buffer);
                return TRUE;
            }
        }
        
        spi = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)spi + spi->NextEntryOffset);
    }
    
    free(buffer);
    return FALSE;
}

4.4 完整的进程检测实现

// 进程检测工具类
class ProcessDetector {
public:
    static std::vector<std::string> GetRunningProcesses() {
        std::vector<std::string> processes;
        
        HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        if (hSnapshot == INVALID_HANDLE_VALUE) {
            return processes;
        }
        
        PROCESSENTRY32 pe32;
        pe32.dwSize = sizeof(PROCESSENTRY32);
        
        if (Process32First(hSnapshot, &pe32)) {
            do {
                processes.push_back(std::string(pe32.szExeFile));
            } while (Process32Next(hSnapshot, &pe32));
        }
        
        CloseHandle(hSnapshot);
        return processes;
    }
    
    static void PrintRunningProcesses() {
        printf("=== 当前运行的进程列表 ===\n");
        auto processes = GetRunningProcesses();
        
        for (const auto& process : processes) {
            printf("%s\n", process.c_str());
        }
        printf("总计: %zu 个进程\n\n", processes.size());
    }
    
    static BOOL DetectKnownDebuggers() {
        printf("=== 检测已知调试器进程 ===\n");
        
        // 使用三种方法检测
        BOOL detected = FALSE;
        
        // 方法1: Toolhelp32
        if (DetectDebuggersViaToolhelp32()) {
            detected = TRUE;
        }
        
        // 方法2: PSAPI
        if (DetectDebuggersViaPSAPI()) {
            detected = TRUE;
        }
        
        // 方法3: NT API
        if (DetectDebuggersViaNTAPI()) {
            detected = TRUE;
        }
        
        if (!detected) {
            printf("未检测到已知调试器进程。\n");
        }
        
        return detected;
    }
};

// 高级进程名检测
BOOL AdvancedProcessNameDetection() {
    printf("=== 高级进程名检测 ===\n");
    
    // 检测模糊匹配的进程名
    const char* fuzzyPatterns[] = {
        "dbg",      // 包含dbg的进程
        "debug",    // 包含debug的进程
        "analyze",  // 包含analyze的进程
        "monitor",  // 包含monitor的进程
        "trace",    // 包含trace的进程
        "hook"      // 包含hook的进程
    };
    
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return FALSE;
    }
    
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32);
    
    BOOL detected = FALSE;
    
    if (Process32First(hSnapshot, &pe32)) {
        do {
            char lowerName[MAX_PATH];
            strcpy_s(lowerName, pe32.szExeFile);
            ToLower(lowerName);
            
            // 检查模糊匹配模式
            for (int i = 0; i < sizeof(fuzzyPatterns)/sizeof(fuzzyPatterns[0]); i++) {
                if (strstr(lowerName, fuzzyPatterns[i]) != NULL) {
                    printf("发现可疑进程: %s (PID: %lu)\n", pe32.szExeFile, pe32.th32ProcessID);
                    detected = TRUE;
                }
            }
        } while (Process32Next(hSnapshot, &pe32));
    }
    
    CloseHandle(hSnapshot);
    return detected;
}

4.5 反调试实现

// 简单的进程名反调试
VOID SimpleProcessNameAntiDebug() {
    if (ProcessDetector::DetectKnownDebuggers()) {
        printf("检测到调试器进程存在!程序即将退出。\n");
        ExitProcess(1);
    }
}

// 多层次进程检测
BOOL MultiLayerProcessDetection() {
    // 第一层:精确匹配检测
    if (ProcessDetector::DetectKnownDebuggers()) {
        return TRUE;
    }
    
    // 第二层:模糊匹配检测
    if (AdvancedProcessNameDetection()) {
        return TRUE;
    }
    
    // 第三层:定期检测
    static DWORD lastCheck = 0;
    DWORD currentTime = GetTickCount();
    
    if (currentTime - lastCheck > 5000) {  // 每5秒检测一次
        lastCheck = currentTime;
        if (ProcessDetector::DetectKnownDebuggers()) {
            return TRUE;
        }
    }
    
    return FALSE;
}

// 增强版反调试
VOID EnhancedProcessNameAntiDebug() {
    // 多次检测
    for (int i = 0; i < 5; i++) {
        if (MultiLayerProcessDetection()) {
            printf("第%d次进程检测发现调试环境!\n", i + 1);
            
            // 随机化响应
            int response = rand() % 4;
            switch (response) {
            case 0:
                ExitProcess(0);
            case 1:
                printf("发生未知错误。\n");
                Sleep(5000);
                exit(1);
            case 2:
                // 执行错误指令
                __debugbreak();
            case 3:
                // 进入无限循环
                while (1) {
                    Sleep(1000);
                }
            }
        }
        
        // 随机延迟
        Sleep(rand() % 100 + 50);
    }
    
    printf("进程名反调试检测通过。\n");
}

4.6 绕过进程名检测的方法

// 进程名伪装技术
class ProcessNameObfuscator {
public:
    // 重命名进程(需要管理员权限)
    static BOOL RenameCurrentProcess(const char* newName) {
        // 注意:这种方法在现代Windows系统中受到限制
        // 需要特殊权限并且可能不总是有效
        
        printf("尝试将进程重命名为: %s\n", newName);
        
        // 实际实现需要使用更高级的技术
        // 这里仅作概念演示
        
        return FALSE;
    }
    
    // 创建虚假进程
    static BOOL CreateFakeProcesses() {
        // 创建一些看似正常的进程来混淆检测
        const char* fakeProcesses[] = {
            "notepad.exe",
            "calc.exe",
            "mspaint.exe",
            "charmap.exe"
        };
        
        // 实际实现需要创建真实进程
        // 这里仅作概念演示
        
        printf("创建虚假进程以混淆检测...\n");
        return TRUE;
    }
};

// 综合绕过方法
VOID ComprehensiveProcessNameBypass() {
    // 重命名调试器进程(概念演示)
    ProcessNameObfuscator::RenameCurrentProcess("normal_process.exe");
    
    // 创建虚假进程
    ProcessNameObfuscator::CreateFakeProcesses();
    
    printf("进程名检测绕过完成。\n");
}

4.7 完整测试程序

#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <vector>
#include <string>

// 前面实现的函数声明
BOOL DetectDebuggersViaToolhelp32();
BOOL DetectDebuggersViaPSAPI();
BOOL DetectDebuggersViaNTAPI();
BOOL MultiLayerProcessDetection();
VOID ComprehensiveProcessNameBypass();

// 显示系统信息
VOID DisplaySystemInfo() {
    printf("=== 系统信息 ===\n");
    
    // 获取系统信息
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo);
    
    printf("处理器数量: %d\n", sysInfo.dwNumberOfProcessors);
    printf("页面大小: %d 字节\n", sysInfo.dwPageSize);
    
    // 获取内存状态
    MEMORYSTATUSEX memStatus;
    memStatus.dwLength = sizeof(memStatus);
    GlobalMemoryStatusEx(&memStatus);
    
    printf("总物理内存: %llu MB\n", memStatus.ullTotalPhys / (1024 * 1024));
    printf("可用物理内存: %llu MB\n", memStatus.ullAvailPhys / (1024 * 1024));
    
    printf("\n");
}

// 性能测试
VOID PerformanceTest() {
    const int iterations = 100;
    
    printf("=== 性能测试 (%d次调用) ===\n", iterations);
    
    // 测试Toolhelp32方法
    DWORD start = GetTickCount();
    for (int i = 0; i < iterations; i++) {
        DetectDebuggersViaToolhelp32();
    }
    DWORD toolhelpTime = GetTickCount() - start;
    
    // 测试PSAPI方法
    start = GetTickCount();
    for (int i = 0; i < iterations; i++) {
        DetectDebuggersViaPSAPI();
    }
    DWORD psapiTime = GetTickCount() - start;
    
    printf("Toolhelp32方法耗时: %lu ms\n", toolhelpTime);
    printf("PSAPI方法耗时: %lu ms\n", psapiTime);
    printf("性能比率: %.2f\n", (float)psapiTime / toolhelpTime);
    
    printf("\n");
}

// 主程序
int main() {
    srand((unsigned int)time(NULL));
    
    printf("遍历进程名检测调试器演示程序\n");
    printf("=========================\n\n");
    
    // 显示系统信息
    DisplaySystemInfo();
    
    // 显示当前运行的进程
    ProcessDetector::PrintRunningProcesses();
    
    // 检测已知调试器
    ProcessDetector::DetectKnownDebuggers();
    
    // 高级检测
    AdvancedProcessNameDetection();
    
    // 性能测试
    PerformanceTest();
    
    // 实际应用示例
    printf("=== 反调试检测 ===\n");
    if (MultiLayerProcessDetection()) {
        printf("检测到调试环境,执行反调试措施。\n");
        
        // 这里可以执行各种反调试措施
        // 为演示目的,我们只是显示信息而不真正退出
        printf("(演示模式:不实际退出程序)\n");
    } else {
        printf("未检测到调试环境,程序正常运行。\n");
        MessageBoxW(NULL, L"进程名检测通过,程序正常运行", L"提示", MB_OK);
    }
    
    // 演示绕过方法
    printf("\n=== 绕过演示 ===\n");
    printf("执行进程名绕过...\n");
    ComprehensiveProcessNameBypass();
    
    printf("绕过完成后再次检测:\n");
    if (MultiLayerProcessDetection()) {
        printf("仍然检测到调试环境。\n");
    } else {
        printf("检测结果显示未发现调试器。\n");
    }
    
    return 0;
}

4.8 高级技巧和注意事项

// 抗干扰版本(防止简单的Hook)
BOOL AntiTamperProcessDetection() {
    // 多次调用并验证
    BOOL results[5];
    
    for (int i = 0; i < 5; i++) {
        results[i] = MultiLayerProcessDetection();
        Sleep(10);  // 简短延迟
    }
    
    // 检查结果一致性
    for (int i = 1; i < 5; i++) {
        if (results[i] != results[0]) {
            // 结果不一致,可能是被干扰了
            return TRUE;  // 假设存在调试环境
        }
    }
    
    return results[0];
}

// 时间差检测增强版
BOOL TimeBasedProcessDetection() {
    DWORD start = GetTickCount();
    
    // 执行多次进程检测
    for (int i = 0; i < 10; i++) {
        if (MultiLayerProcessDetection()) {
            return TRUE;
        }
    }
    
    DWORD end = GetTickCount();
    
    // 如果执行时间过长,可能是被调试
    if ((end - start) > 1000) {  // 超过1秒
        return TRUE;
    }
    
    return FALSE;
}

// 综合检测函数
BOOL ComprehensiveProcessDetection() {
    // 抗干扰检测
    if (AntiTamperProcessDetection()) {
        return TRUE;
    }
    
    // 时间差检测
    if (TimeBasedProcessDetection()) {
        return TRUE;
    }
    
    // 其他进程检测
    if (AdvancedProcessNameDetection()) {
        return TRUE;
    }
    
    return FALSE;
}

// 动态获取API地址(避免静态导入)
FARPROC GetDynamicAPIAddress(LPCSTR moduleName, LPCSTR functionName) {
    // 动态加载模块
    HMODULE hModule = LoadLibraryA(moduleName);
    if (hModule == NULL) {
        return NULL;
    }
    
    // 获取函数地址
    FARPROC pfn = GetProcAddress(hModule, functionName);
    
    return pfn;
}

// 检测API调用的完整性
BOOL ValidateAPICall() {
    // 可以通过检查API函数代码的完整性来验证未被修改
    // 这需要更高级的技术,如代码校验和检查
    
    return TRUE;
}

五、课后作业

  1. 基础练习

    • 在不同Windows版本下测试上述代码的兼容性
    • 扩展调试器进程名称列表,添加更多现代调试器
    • 实现对进程路径的检测而不仅仅是进程名
  2. 进阶练习

    • 实现一个进程监控器,实时监控调试器进程的启动和关闭
    • 研究如何检测通过重命名绕过检测的调试器
    • 设计一个多层检测机制,结合多种进程检测技术
  3. 思考题

    • 进程名检测方法有哪些明显的局限性?
    • 如何提高进程检测的准确性和隐蔽性?
    • 现代调试器采用了哪些技术来对抗进程名检测?
  4. 扩展阅读

    • 研究Windows进程管理机制
    • 了解进程隐藏技术
    • 学习现代反反调试技术