IsDebuggerPresent

image

函数体

image

检测原理

TEB结构体
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB

fs:[30]对应的是PEB

PEB结构体
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
偏移0x2处即为BeingDebugged字段,为1则表示正在被调试

对策

  1. 打断点然后更改eax寄存器的值(不推荐)
  2. 由于IsDebuggerPresent后面往往会跟随关键je跳转,直接把je改为jmp,
  3. 把BeingDebugged字段处的值置0

进程名检测

CreateToolHelp32Snapshot

实现方法

bool CheckandKillDebugger(const wchar_t* name)
{
    HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
    if(snap == INVALID_HANDLE_VALUE) return false;
    PROCESSENTRY32W pe;
    pe.dwSize = sizeof(pe);
    BOOL killed = FALSE;
    if(Process32FirstW(snap,&pe))
    {
        do
        {
            if(_wcsicmp(pe.szExeFile,name) == 0)
            {
                killed = true;
            }
        } while (Process32NextW(snap,&pe));
    }
    CloseHandle(snap);
    return killed;
}

CreateToolhelp32Snapshot创建一个所有进程的快照,然后用Process32FristW配合Process32NextW遍历快照中的进程,存到PROCESSENTRY32W结构体。再使用针对wchar_t的字符串比较函数_wcsicmp,检测xdbg进程名

image

对策

pe.szExeFile是用宽字符存储的,所以对应的字符串比较函数也必须跟宽字符有关,因此可以找到_wcsicmp函数。
这个函数在字符串相等时返回0,我尝试给下面的eax设断点,但还没看到eax是0的时候就已经被检测到了。
所以将目光转向返回值所在内存地址,我这里是rbp + 1EC,而它后面跟的正好是1,所以把1改成0就可以了。
或者把下面的setne改成sete

逻辑:非目标进程 =》rax != 0 =》killed是0 =》cmp比较结果为0 =》zf = 1 =》al被置0 (不写出来真的很乱)

GetProcAddress

实现方法

bool CheckandKillDebugger(const wchar_t* name)
{
    HMODULE hPsapi = LoadLibraryW(L"psapi.dll");
    if (!hPsapi) return false;

    typedef BOOL (WINAPI* pEnumProcesses)(DWORD*,DWORD,DWORD*);
    typedef BOOL (WINAPI* pEnumProcessModules)(HANDLE,HMODULE*,DWORD,LPDWORD);
    typedef DWORD (WINAPI* pGetModuleBaseNameW)(HANDLE,HMODULE,LPWSTR,DWORD);

    pEnumProcesses mEnumProcesses = (pEnumProcesses)GetProcAddress(hPsapi,"EnumProcesses");
    pEnumProcessModules mEnumProcessModules = (pEnumProcessModules)GetProcAddress(hPsapi,"EnumProcessModules");
    pGetModuleBaseNameW mGetModuleBaseNameW = (pGetModuleBaseNameW)GetProcAddress(hPsapi,"GetModuleBaseNameW");
    if(!mEnumProcesses || !mEnumProcessModules || !mGetModuleBaseNameW)
    {
        FreeLibrary(hPsapi);
        return false;
    }

    DWORD pids[1024],cbNeeded;

    if(!mEnumProcesses(pids,sizeof(pids),&cbNeeded))
    {
        FreeLibrary(hPsapi);
        return false;
    }

    int cnt = cbNeeded / sizeof(DWORD);

    for(int i = 0; i < cnt; ++i)
    {
        DWORD pid = pids[i];
        if (pid == 0) continue;

        HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,pid);
        if (!hProcess) continue;

        HMODULE hMod;
        DWORD cbMod;
        if(mEnumProcessModules(hProcess,&hMod,sizeof(hMod),&cbMod))
        {
            
            wchar_t modName[MAX_PATH] = {0};
            if (mGetModuleBaseNameW(hProcess,hMod,modName,MAX_PATH))
            {
                if (_wcsicmp(modName, name) == 0) 
                {
                    CloseHandle(hProcess);
                    FreeLibrary(hPsapi);
                    return true;
                }
            }
        }
        CloseHandle(hProcess);
    }
    FreeLibrary(hPsapi);
    return false;
}

LoadLibrary函数动态加载psapi.dll,提高检测api隐蔽性。然后需要用GetProcAddress获取EnumProcessEnumProcessModulesGetModuleBaseNameW这3个api的地址,并保存在各自的指针中。先用mEnumProcess枚举进程,再用OpenProcess打开相应进程。然后通过mEnumProcessModules获取进程的exe模块句柄(大部分进程第一个是exe,后面是一堆dll,所以这里没用数组接收),把句柄传入GetModuleBaseNameW就可以得到进程名,最后与x64dbg.exe比对一下就行了

动态加载api的效果

xdbg无法直接查到相关api,只能看到GetProcAddress。即使通过GetProcAddress打断点,有些api也会失效,比如上述这三个。

对策

给GetProcAddress返回值改成0,改不了就在调用处把rax改成0,异常就nop,这样就不会触发检测了
image

NtQueryInformationProcess

实现方法

typedef NTSTATUS (NTAPI* _NtQueryInformationProcess)(
    HANDLE           ProcessHandle,
    DWORD ProcessInformationClass,
    PVOID            ProcessInformation,
    ULONG            ProcessInformationLength,
    PULONG           ReturnLength
    );

BOOL testNtPort(_NtQueryInformationProcess NtQueryInformationProcess)
{
    HANDLE hProcess = GetCurrentProcess();
    DWORD DebugPort;
    NtQueryInformationProcess(hProcess,7,&DebugPort,sizeof(DWORD),NULL);
    if (DebugPort != 0)
    {
        MessageBoxW(NULL,L"NtPort",NULL,MB_OK);
        return TRUE;
    }
    return FALSE;
}

//VScode gdb xdbg
BOOL testNtObjectHandle(_NtQueryInformationProcess NtQueryInformationProcess)
{
    HANDLE hProcess = GetCurrentProcess();
    HANDLE ObjectHandle;
    NtQueryInformationProcess(hProcess, 30, &ObjectHandle, sizeof(ObjectHandle), NULL);
    if (ObjectHandle != NULL)
    {
        MessageBoxW(NULL,L"NtObjectHandle",NULL,MB_OK);
        return TRUE;
    }
    return FALSE;
}

BOOL testFlag(_NtQueryInformationProcess NtQueryInformationProcess)
{
    HANDLE hProcess = GetCurrentProcess();
    BOOL Flags;
    NtQueryInformationProcess(hProcess, 31, &Flags, sizeof(Flags), NULL);
    if (Flags != 1)
    {
        MessageBoxW(NULL,L"Flag",NULL,MB_OK);
        return TRUE;
    }
    return FALSE;
}

BOOL CheckandKillDebugger()
{
    HMODULE  hDll = LoadLibraryW(L"Ntdll.dll");
    _NtQueryInformationProcess NtQueryInformationProcess = 
    (_NtQueryInformationProcess)GetProcAddress(hDll, "NtQueryInformationProcess");
    
    if( testNtPort(NtQueryInformationProcess) == TRUE 
        || testNtObjectHandle(NtQueryInformationProcess) ==TRUE
        || testFlag(NtQueryInformationProcess) == TRUE)
        {
            return TRUE;
        }
    return FALSE;
}

对策:给函数下断点,找第二个参数(缓冲区数据的含义)和缓冲区地址,修改缓冲区数据,比如0、NULL以及Flags对应的1。

此外NtQueryInformationProcess可配合SetUnhandledExceptionFilter使用。

SetUnhandledExceptionFilter

这个函数是用来注册UnhandledExceptionFilter API的。该API会先调用上边的NtQueryInformationProcess检查是否存在调试器,如果不存在,就执行程序自己的异常处理API。存在调试器的情况下,异常会被移交给调试器,如果调试器也不处理,就调用系统的UnhandledExceptionFilter去处理

RtlGetNtGlobalFlags

实现方法

#define FLG_HEAP_ENABLE_TAIL_CHECK    0x10
#define FLG_HEAP_ENABLE_FREE_CHECK    0x20
#define FLG_HEAP_VALIDATE_PARAMETERS  0x40

typedef ULONG (NTAPI* RtlGetNtGlobalFlags_t)(void);

BOOL CheckandKillDebugger()
{
    HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
    if(!hNtdll) return FALSE;
    RtlGetNtGlobalFlags_t RtlGetNtGlobalFlags =
    (RtlGetNtGlobalFlags_t)GetProcAddress(hNtdll,"RtlGetNtGlobalFlags");
    if(!RtlGetNtGlobalFlags) return FALSE;

    ULONG flags = RtlGetNtGlobalFlags();
    ULONG heapFlags = flags & (FLG_HEAP_ENABLE_FREE_CHECK
                             | FLG_HEAP_ENABLE_TAIL_CHECK
                             | FLG_HEAP_VALIDATE_PARAMETERS);
	//表达式 flags & (A | B | C) 用位运算把 flags 中除了 A、B、C 所代表的那几位以外的位都清零,结果 heapFlags 只保留这三个位的状态。
    
    return (heapFlags != 0);
}
posted on 2026-05-07 14:04  %HuTao%  阅读(4)  评论(0)    收藏  举报