0x1 反调试

  1. 所有的反调试手段都是以函数的方式封装好的
  2. TRUE:表示检测到调试

0x1.1 IsDebuggerPresent

BOOL
IsDebuggerPresentAPI (
    VOID
    )
{
    return IsDebuggerPresent();
}
  • IsDebuggerPresent这是windows提供的api
  • 本质上就是返回PEB->BeingDebugged

0x1.2 PEB 检测

  • PEB 是windows准备的一个结构体
  • 用于保存当前程序的运行信息
typedef struct _PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    BYTE Reserved2[1];
    PVOID Reserved3[2];
    PPEB_LDR_DATA Ldr;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID Reserved4[3];
    PVOID AtlThunkSListPtr;
    PVOID Reserved5;
    ULONG Reserved6;
    PVOID Reserved7;
    ULONG Reserved8;
    ULONG AtlThunkSListPtr32;
    PVOID Reserved9[45];
    BYTE Reserved10[96];
    PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
    BYTE Reserved11[128];
    PVOID Reserved12[1];
    ULONG SessionId;
} PEB, *PPEB;

BOOL
IsDebuggerPresentPEB(
    VOID
)
{
#if defined (ENV64BIT)
    PPEB pPeb = (PPEB)__readgsqword(0x60);

#elif defined(ENV32BIT)
    PPEB pPeb = (PPEB)__readfsdword(0x30);

#endif

    return pPeb->BeingDebugged == 1;
}
  • __readgsqword:读取gs:[]中的数据
  • __readfsdword:读取fs:[]中的数据
  • 32位架构下的pebFS:[0x30]
  • 64位架构下的pebGS:[0x60]

0x1.3 CheckRemoteDebuggerPresent

  • windows提供的一个用于检测其他程序是否被调试的api
  • 也可以用于检测自己
BOOL
CheckRemoteDebuggerPresentAPI (
    VOID
    )
{
    BOOL bIsDbgPresent = FALSE;
    CheckRemoteDebuggerPresent(GetCurrentProcess(), &bIsDbgPresent);
    return bIsDbgPresent;
}
  • 本质上是调用NTDLL的导出函数NtQueryInformationProcess
  • NtQueryInformationProcess中设置PROCESSINFOCLASS7 (ProcessDebugPort)

0x1.4 NtSetInformationThread

  • 这是windows提供的设置程序运行信息的api
  • 可以过SharpOD
  • 如下图设置仍然可以检测到

    image-20210819145735536.png

  • 检测代码
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS *PNTSTATUS;
typedef NTSTATUS(WINAPI *pNtSetInformationThread)(HANDLE, UINT, PVOID, ULONG);

enum API_IDENTIFIER
{
    API_CsrGetProcessId,
    API_EnumSystemFirmwareTables,
    API_GetActiveProcessorCount,
    API_GetSystemFirmwareTable,
    API_GetNativeSystemInfo,
    API_GetProductInfo,
    API_EnumProcessModulesEx_Kernel,
    API_EnumProcessModulesEx_PSAPI,
    API_IsWow64Process,
    API_LdrEnumerateLoadedModules,
    API_NtClose,
    API_NtCreateDebugObject,
    API_NtDelayExecution,
    API_NtOpenDirectoryObject,
    API_NtQueryInformationThread,
    API_NtQueryInformationProcess,
    API_NtQueryLicenseValue,
    API_NtQueryDirectoryObject,
    API_NtQueryObject,
    API_NtQuerySystemInformation,
    API_NtSetInformationThread,
    API_NtWow64QueryInformationProcess64,
    API_NtWow64QueryVirtualMemory64,
    API_NtWow64ReadVirtualMemory64,
    API_NtYieldExecution,
    API_RtlGetVersion,
    API_RtlInitUnicodeString,
    API_WudfIsAnyDebuggerPresent,
    API_WudfIsKernelDebuggerPresent,
    API_WudfIsUserDebuggerPresent,
};

class API
{
private:
    static bool ShouldFunctionExistOnCurrentPlatform(API_OS_BITS bits, API_OS_VERSION minVersion, API_OS_VERSION removedInVersion);
public:
    static void Init();
    static void PrintAvailabilityReport();
    static bool IsAvailable(API_IDENTIFIER api);
    static void* GetAPI(API_IDENTIFIER api);
};

#ifndef STATUS_INFO_LENGTH_MISMATCH
#define STATUS_INFO_LENGTH_MISMATCH ((DWORD)0xC0000004L)
#endif
#ifndef STATUS_DATATYPE_MISALIGNMENT
#define STATUS_DATATYPE_MISALIGNMENT ((DWORD)0x80000002L)
#endif

BOOL NtSetInformationThread_ThreadHideFromDebugger()
{
    struct AlignedBool
    {
        alignas(4) bool Value;
    };

    const int ThreadHideFromDebugger =  0x11;

    auto NtSetInformationThread = static_cast<pNtSetInformationThread>(API::GetAPI(API_IDENTIFIER::API_NtSetInformationThread));
    auto NtQueryInformationThread = static_cast<pNtQueryInformationThread>(API::GetAPI(API_IDENTIFIER::API_NtQueryInformationThread));
    
    NTSTATUS Status;
    bool doQITcheck = false;

    if (API::IsAvailable(API_IDENTIFIER::API_NtQueryInformationThread))
    {
        doQITcheck = IsWindowsVistaOrGreater();
    }

    AlignedBool isThreadHidden;
    isThreadHidden.Value = false;
    
    Status = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &isThreadHidden, 12345);
    if (Status == 0)
        return TRUE;

    Status = NtSetInformationThread((HANDLE)0xFFFF, ThreadHideFromDebugger, NULL, 0);
    if (Status == 0)
        return TRUE;
    
    Status = NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);

    if (Status == 0)
    {
        if (doQITcheck)
        {
            Status = NtQueryInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &isThreadHidden.Value, sizeof(bool), NULL);

            if (Status == STATUS_INFO_LENGTH_MISMATCH)
            {
                return TRUE;
            }

            if (Status == 0)
            {
                AlignedBool bogusIsThreadHidden;
                bogusIsThreadHidden.Value = false;

                Status = NtQueryInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &bogusIsThreadHidden.Value, sizeof(BOOL), NULL);
                if (Status != STATUS_INFO_LENGTH_MISMATCH)
                {
                    return TRUE;
                }

                const size_t UnalignedCheckCount = 8;
                bool bogusUnalignedValues[UnalignedCheckCount];
                int alignmentErrorCount = 0;
#if _WIN64
                const size_t MaxAlignmentCheckSuccessCount = 2;
#else
                const size_t MaxAlignmentCheckSuccessCount = 4;
#endif
                for (size_t i = 0; i < UnalignedCheckCount; i++)
                {
                    Status = NtQueryInformationThread(GetCurrentThread(), ThreadHideFromDebugger, &(bogusUnalignedValues[i]), sizeof(BOOL), NULL);
                    if (Status == STATUS_DATATYPE_MISALIGNMENT)
                    {
                        alignmentErrorCount++;
                    }
                }
                if (UnalignedCheckCount - MaxAlignmentCheckSuccessCount > alignmentErrorCount)
                {
                    return TRUE;
                }

                return isThreadHidden.Value ? FALSE : TRUE;
            }
        }
    }
    else
    {
        return TRUE;
    }

    return FALSE;
}

原理:

  1. 调用NtSetInformationThread会导致ThreadInformationClass=0x11(ThreadHideFromDebugger)
  2. hThread=NULL传递给NtSetInformationThread会导致调用的线程被隐藏,同时导致调试器接收不到调试事件
  3. 这个程序同样会检查ThreadHideFromDebugger是否被封锁

0x1.5 JobObject

  • 核心:一般情况下,主进程在主线程中启动核心代码
  • QueryInformationJobObject这个api可以获取当前程序所有的进程列表
  • 不论是主进程还是主线程,他们的ImageFileName应该是都是源程序的文件名filename.exe

    控制台程序会有一个特殊的父进程\\Windows\\System32\\conhost.exe
  • 原理:查询当前程序的进程列表,通过进程id反查程序文件名.只要有进程id不匹配就记为异常

核心代码


BOOL ProcessJob()
{
    BOOL foundProblem = FALSE;

    DWORD jobProcessStructSize = sizeof(JOBOBJECT_BASIC_PROCESS_ID_LIST) + sizeof(ULONG_PTR) * 1024;
    JOBOBJECT_BASIC_PROCESS_ID_LIST* jobProcessIdList = static_cast<JOBOBJECT_BASIC_PROCESS_ID_LIST*>(malloc(jobProcessStructSize));

    if (jobProcessIdList) {

        SecureZeroMemory(jobProcessIdList, jobProcessStructSize);

        jobProcessIdList->NumberOfProcessIdsInList = 1024;
        //获取进程列表数组
        if (QueryInformationJobObject(NULL, JobObjectBasicProcessIdList, jobProcessIdList, jobProcessStructSize, NULL))
        {
            int ok_processes = 0;
            //穷举进程列表
            for (DWORD i = 0; i < jobProcessIdList->NumberOfAssignedProcesses; i++)
            {
                ULONG_PTR processId = jobProcessIdList->ProcessIdList[i];

                // 匹配到主进程,合法
                if (processId == (ULONG_PTR)GetCurrentProcessId())
                {
                    ok_processes++;
                }
                // 检查其他的进程是否是 白名单程序
                else
                {

                    
                    HANDLE hJobProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, (DWORD)processId);
                    if (hJobProcess != NULL)
                    {
                        const int processNameBufferSize = 4096;
                        LPTSTR processName = static_cast<LPTSTR>(malloc(sizeof(TCHAR) * processNameBufferSize));
                        if (processName) {
                            SecureZeroMemory(processName, sizeof(TCHAR) * processNameBufferSize);
                            // 根据进程id查询进程名称,一般是exe的文件名
                            if (GetProcessImageFileName(hJobProcess, processName, processNameBufferSize) > 0)
                            {
                                String pnStr(processName);

                                // 忽略控制台进程
                                if (pnStr.find(String(L"\\Windows\\System32\\conhost.exe")) != std::string::npos)
                                {
                                    ok_processes++;
                                }
                            }

                            free(processName);
                        }
                        CloseHandle(hJobProcess);
                    }
                }
            }

            // 进程列表中只要有一个不合法就记为异常
            foundProblem = ok_processes != jobProcessIdList->NumberOfAssignedProcesses;
        }

        free(jobProcessIdList);
    }
    return foundProblem;
}
  • 可以过SharpOD
  • 如下图设置仍然可以检测到

    image-20210819145735536.png

0x1.6 检查硬件断点

  • 直接检查寄存器,不多说
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;
}

0x1.7 检查软件断点

  • int3int 3都是软件断点
  • int3->0xcc 是处理器的硬件指令
  • int 3->0x cd 03 是windows的中断实现
  • 扫描有没有这些特殊指令即可
// 模拟被保护的函数
VOID My_Critical_Function()
{
    int a = 1;
    int b = 2;
    int c = a + b;
    _tprintf(_T("I am critical function, you should protect against int3 bps %d"), c);
}

/*
这个函数需要跟在 被保护函数的后面
用来计算扫描长度
*/
VOID Myfunction_Adresss_Next()
{
    My_Critical_Function();
};


BOOL SoftwareBreakpoints()
{
    // 计算长度
    size_t sSizeToCheck = (size_t)(Myfunction_Adresss_Next)-(size_t)(My_Critical_Function);
    // 获取检查的起始地址
    PUCHAR Critical_Function = (PUCHAR)My_Critical_Function;

    for (size_t i = 0; i < sSizeToCheck; i++) {
        // 这里可以用 亦或 检查
        // 0xCC xor 0x55 = 0x99
        if (Critical_Function[i] == 0xCC)
            return TRUE;
    }
    return FALSE;
}
Last modification:August 20, 2021
如果觉得我的文章对你有用,请随意赞赏