初识反调试 IsDebuggerPresent()
函数包含在debugapi.h
头文件中,函数原型:
1 BOOL IsDebuggerPresent () ;
该函数允许程序确定是否正在由用户模式调试器(如 OllyDbg 或 x64dbg)调试它,以便可以修改其行为。通常,该函数只检查进程环境块 (PEB) 的 BeingDebugged 标志。
Assembly:
1 2 3 4 5 6 7 call IsDebuggerPresent test al, al jne being_debugged ... being_debugged: push 1 call ExitProcess
C/C++:
1 2 if (IsDebuggerPresent()) ExitProcess(-1 );
CheckRemoteDebuggerPresent()
函数包含在kernel32中的CheckRemoteDebuggerPresent()
检查调试器(在同一台计算机上的不同进程中)是否连接到当前进程。
X86 Assembly:
1 2 3 4 5 6 7 8 9 10 lea eax, [bDebuggerPresent] push eax push -1 ; GetCurrentProcess() call CheckRemoteDebuggerPresent cmp [bDebuggerPresent], 1 jz being_debugged ... being_debugged: push -1 call ExitProcess
X64 Assembly:
1 2 3 4 5 6 7 8 9 lea rdx, [bDebuggerPresent] mov rcx, -1 ; GetCurrentProcess() call CheckRemoteDebuggerPresent cmp [bDebuggerPresent], 1 jz being_debugged ... being_debugged: mov ecx, -1 call ExitProcess
C/C++:
1 2 3 4 BOOL bDebuggerPresent; if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProces(), &bDebuggerPresent) && TRUE == bDebuggerPresent) ExitProcess(-1 );
PEB->BeingDebugged
FS标志段寄存器总是指向TEB(当前的线程环境块),其中包含一个指针指向当前PEB(进程环境块),该结构体包含一个成员BeingDebugged,它是一个标志位,用于标识当前进程是否正在被调试
该函数包含在ntdll.h
头文件中,可以从进程中检索不同类型的信息。它接受一个ProcessInformationClass
参数,该参数指定要获取的信息并定义ProcessInformation
参数的输出类型。
ProcessDebugPort
可以使用ntdll
检索进程的调试器的端口号!NtQueryInformationProcess()
的有一个记录在案的类ProcessDebugPort
,如果正在调试进程,则该类将检索等于 0xFFFFFFFF(十进制 -1) 的 DWORD 值,C/C++语法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 __kernel_entry NTSTATUS NtQueryInformationProcess ( IN HANDLE ProcessHandle, IN PROCESSINFOCLASS ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength ) ;HMODULE hNtdll = LoadLibraryA ("ntdll.dll" ); if (hNtdll){ auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress ( hNtdll, "NtQueryInformationProcess" ); if (pfnNtQueryInformationProcess) { DWORD dwProcessDebugPort, dwReturned; NTSTATUS status = pfnNtQueryInformationProcess ( GetCurrentProcess (), ProcessDebugPort, &dwProcessDebugPort, sizeof (DWORD), &dwReturned); if (NT_SUCCESS (status) && (-1 == dwProcessDebugPort)) ExitProcess (-1 ); } }
X86 Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lea eax, [dwReturned] push eax ; ReturnLength push 4 ; ProcessInformationLength lea ecx, [dwProcessDebugPort] push ecx ; ProcessInformation push 7 ; ProcessInformationClass push -1 ; ProcessHandle call NtQueryInformationProcess inc dword ptr [dwProcessDebugPort] jz being_debugged ... being_debugged: push -1 call ExitProcess
X64 Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lea rcx, [dwReturned] push rcx ; ReturnLength mov r9d, 4 ; ProcessInformationLength lea r8, [dwProcessDebugPort] ; ProcessInformation mov edx, 7 ; ProcessInformationClass mov rcx, -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [dwProcessDebugPort], -1 jz being_debugged ... being_debugged: mov ecx, -1 call ExitProcess
ProcessDebugFlags
名为EPROCESS
的内核结构(表示进程对象)包含字段 NoDebugInherit
。可以使用未记录的类 ProcessDebugFlags(0x1f)
检索此字段的反向值。因此,如果返回值为 0,则存在调试器。
C/C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 typedef NTSTATUS (NTAPI *TNtQueryInformationProcess) ( IN HANDLE ProcessHandle, IN DWORD ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength ) ;HMODULE hNtdll = LoadLibraryA ("ntdll.dll" ); if (hNtdll){ auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress ( hNtdll, "NtQueryInformationProcess" ); if (pfnNtQueryInformationProcess) { DWORD dwProcessDebugFlags, dwReturned; const DWORD ProcessDebugFlags = 0x1f ; NTSTATUS status = pfnNtQueryInformationProcess ( GetCurrentProcess (), ProcessDebugFlags, &dwProcessDebugFlags, sizeof (DWORD), &dwReturned); if (NT_SUCCESS (status) && (0 == dwProcessDebugFlags)) ExitProcess (-1 ); } }
X86 Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lea eax, [dwReturned] push eax ; ReturnLength push 4 ; ProcessInformationLength lea ecx, [dwProcessDebugFlags] push ecx ; ProcessInformation push 1Fh ; ProcessInformationClass push -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [dwProcessDebugFlags], 0 jz being_debugged ... being_debugged: push -1 call ExitProcess
X64 Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lea rcx, [dwReturned] push rcx ; ReturnLength mov r9d, 4 ; ProcessInformationLength lea r8, [dwProcessDebugFlags] ; ProcessInformation mov edx, 1Fh ; ProcessInformationClass mov rcx, -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [dwProcessDebugFlags], 0 jz being_debugged ... being_debugged: mov ecx, -1 call ExitProcess
ProcessDebugObjectHandle
调试开始时,将创建一个名为debug object
的内核对象。可以使用未记录的ProcessDebugObjectHandle(0x1e)
类来查询此句柄的值。
C/C++:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 typedef NTSTATUS (NTAPI * TNtQueryInformationProcess) ( IN HANDLE ProcessHandle, IN DWORD ProcessInformationClass, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength ) ;HMODULE hNtdll = LoadLibraryA("ntdll.dll" ); if (hNtdll){ auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress( hNtdll, "NtQueryInformationProcess" ); if (pfnNtQueryInformationProcess) { DWORD dwReturned; HANDLE hProcessDebugObject = 0 ; const DWORD ProcessDebugObjectHandle = 0x1e ; NTSTATUS status = pfnNtQueryInformationProcess( GetCurrentProcess(), ProcessDebugObjectHandle, &hProcessDebugObject, sizeof (HANDLE), &dwReturned); if (NT_SUCCESS(status) && (0 != hProcessDebugObject)) ExitProcess(-1 ); } }
X86 Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lea eax, [dwReturned] push eax ; ReturnLength push 4 ; ProcessInformationLength lea ecx, [dwProcessDebugFlags] push ecx ; ProcessInformation push 1Fh ; ProcessInformationClass push -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [dwProcessDebugFlags], 0 jz being_debugged ... being_debugged: push -1 call ExitProcess
X64 Assembly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 lea rcx, [dwReturned] push rcx ; ReturnLength mov r9d, 4 ; ProcessInformationLength lea r8, [hProcessDebugObject] ; ProcessInformation mov edx, 1Eh ; ProcessInformationClass mov rcx, -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [hProcessDebugObject], 0 jnz being_debugged ... being_debugged: mov ecx, -1 call ExitProcess
其检索一个WORD_PTR值,该值是进程的调试器端口号。非零值指示进程正在环3调试器的控制下运行,如果进程没有调试器,则返回零
EPROCESS_DebugPort 获取系统内核中标记进程信息的结构体EPROCESS,通过EPROCESS结构体中的DebugPort成员判断进程是否正在被调试,一般无法使用普通方法实现,需要内核调试
5. 异常处理检测 处理异常时,正常运行过程会将信息发给Windows的SEH异常捕获流程,而进行调试时则会发给调试器
6. 断点检测 函数断点体现为0xCC,可通过对比内存数据中的指令数据与磁盘内文件的数据进行对比,如发现存在不同,则说明有断点,即程序被调试
7. 检测调试器进程 通过枚举进程,判断进程名是否为调试器进程名,如果是,则说明程序正在被调试
还可通过查看是否存在调试器的窗口,判断程序是否正在被调试。可使用VC++中附带的Spy++查找窗口名
8. 时间检测
程序运行时,如果被调试,则由于会出现单步运行等过程,程序运行速度会变慢,因此可通过检测程序运行时间,判断程序是否正在被调试