### Hide Control Flow with Structured Exception Handlers (C/C++) Source: https://anti-debug.checkpoint.com/techniques/exceptions.html This C/C++ example demonstrates hiding program control flow by chaining structured exception handlers. Each handler raises a new exception, passing control to the next in the sequence, ultimately leading to a hidden procedure. ```c #include void MaliciousEntry() { // ... } void Trampoline2() { __try { __asm int 3; } __except (EXCEPTION_EXECUTE_HANDLER) { MaliciousEntry(); } } void Trampoline1() { __try { __asm int 3; } __except (EXCEPTION_EXECUTE_HANDLER) { Trampoline2(); } } int main(void) { __try { __asm int 3; } __except (EXCEPTION_EXECUTE_HANDLER) {} { Trampoline1(); } return 0; ``` -------------------------------- ### Detect Debugger using timeGetTime() Source: https://anti-debug.checkpoint.com/techniques/timing.html Measures elapsed time using the timeGetTime API, which returns the number of milliseconds since the system was started. This is a user-mode function suitable for detecting debugger-induced delays. ```C/C++ bool IsDebugged(DWORD dwNativeElapsed) { DWORD dwStart = timeGetTime(); // ... some work return (timeGetTime() - dwStart) > dwNativeElapsed; } ``` -------------------------------- ### Detect Debugger using QueryPerformanceCounter() Source: https://anti-debug.checkpoint.com/techniques/timing.html Uses the QueryPerformanceCounter API to get a high-resolution performance counter value. This is useful for precise timing measurements to detect debugger delays. ```C/C++ bool IsDebugged(DWORD64 qwNativeElapsed) { LARGE_INTEGER liStart, liEnd; QueryPerformanceCounter(&liStart); // ... some work QueryPerformanceCounter(&liEnd); return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed; } ``` -------------------------------- ### Detect Debugger using GetTickCount() Source: https://anti-debug.checkpoint.com/techniques/timing.html Measures elapsed time using the GetTickCount API, which returns the number of milliseconds since the system was started. This is a common user-mode method for detecting delays. ```C/C++ bool IsDebugged(DWORD dwNativeElapsed) { DWORD dwStart = GetTickCount(); // ... some work return (GetTickCount() - dwStart) > dwNativeElapsed; } ``` -------------------------------- ### Switch to a New Desktop Source: https://anti-debug.checkpoint.com/techniques/interactive.html Creates and switches to a new desktop, effectively hiding the current desktop's windows and making debugging impossible by isolating the process. This requires appropriate permissions to create and switch desktops. ```cpp BOOL Switch() { HDESK hNewDesktop = CreateDesktopA( m_pcszNewDesktopName, NULL, NULL, 0, DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, NULL); if (!hNewDesktop) return FALSE; return SwitchDesktop(hNewDesktop); } ``` -------------------------------- ### Query Process Heap Information (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Uses RtlQueryProcessHeapInformation to retrieve heap flags. It checks if the HEAP_GROWABLE flag is not set, indicating potential debugger presence. ```cpp bool Check() { ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if (!SUCCEEDED(ntdll::RtlQueryProcessHeapInformation((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_GROWABLE; } ``` -------------------------------- ### Check Written Pages with VirtualAlloc and GetWriteWatch (Variant 1) Source: https://anti-debug.checkpoint.com/techniques/misc.html This C/C++ snippet uses VirtualAlloc with MEM_WRITE_WATCH to allocate memory and GetWriteWatch to detect any writes to that memory. It's useful for identifying unexpected memory modifications. ```C/C++ bool Generic::CheckWrittenPages1() const { const int SIZE_TO_CHECK = 4096; PVOID* addresses = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); if (addresses == NULL) { return true; } int* buffer = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE)); if (buffer == NULL) { VirtualFree(addresses, 0, MEM_RELEASE); return true; } // Read the buffer once buffer[0] = 1234; ULONG_PTR hits = SIZE_TO_CHECK; DWORD granularity; if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0) { return true; } else { VirtualFree(addresses, 0, MEM_RELEASE); VirtualFree(buffer, 0, MEM_RELEASE); return (hits == 1) ? false : true; } } ``` -------------------------------- ### Detect Debugger using ZwGetTickCount() / KiGetTickCount() (Kernel Mode) Source: https://anti-debug.checkpoint.com/techniques/timing.html Demonstrates using kernel-mode functions ZwGetTickCount() or KiGetTickCount() to measure system tick counts. These functions read from the KUSER_SHARED_DATA page and are faster than user-mode equivalents. ```C/C++ bool IsDebugged(DWORD64 qwNativeElapsed) { ULARGE_INTEGER Start, End; __asm { int 2ah mov Start.LowPart, eax mov Start.HighPart, edx } // ... some work __asm { int 2ah mov End.LowPart, eax mov End.HighPart, edx } return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; } ``` -------------------------------- ### Query Process Debug Object Handle (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Uses NtQueryInformationProcess with class 0x1e to retrieve the process debug object handle. If the handle is valid and non-zero, a debugger is likely present. ```cpp 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); } } ``` -------------------------------- ### Patching Return Address with ReadFile Source: https://anti-debug.checkpoint.com/techniques/process-memory.html Uses kernel32!ReadFile to patch the byte at the return address with 'M'. This method can cause the process to crash if a debugger is present. ```C/C++ #include #pragma intrinsic(_ReturnAddress) void foo() { // ... PVOID pRetAddress = _ReturnAddress(); if (*(PBYTE)pRetAddress == 0xCC) // int 3 { DWORD dwOldProtect, dwRead; CHAR szFilePath[MAX_PATH]; HANDLE hFile; if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { if (GetModuleFileNameA(NULL, szFilePath, MAX_PATH)) { hFile = CreateFileA(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE != hFile) ReadFile(hFile, pRetAddress, 1, &dwRead, NULL); } VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect); } } // ... } ``` -------------------------------- ### Query Process Debug Information (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Uses RtlQueryProcessDebugInformation to read heap flags from process memory. It checks if the HEAP_GROWABLE flag is not set. ```cpp bool Check() { ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if (!SUCCEEDED(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_GROWABLE; } ``` -------------------------------- ### Query Process Debug Object Handle (x86-64 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html x86-64 assembly implementation to call NtQueryInformationProcess and check the process debug object handle. If the handle is non-zero, it jumps to the 'being_debugged' label. ```assembly 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 ``` -------------------------------- ### Check NtGlobalFlag (WOW64 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Examines the NtGlobalFlag in the PEB for specific debugger-related flags in WOW64 processes using assembly. ```assembly mov eax, fs:[30h] mov al, [eax+10BCh] and al, 70h cmp al, 70h jz being_debugged ``` -------------------------------- ### Check Process Debug Flags (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Utilize NtQueryInformationProcess with the undocumented ProcessDebugFlags class to detect a debugger. A return value of 0 signifies the presence of a debugger. ```cpp 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); } } ``` -------------------------------- ### Detect Debugger using Instruction Prefixes in C/C++ Source: https://anti-debug.checkpoint.com/techniques/assembly.html This C/C++ function uses inline assembly to execute an instruction prefix (REP) followed by an INT 1 instruction. In debuggers like OllyDbg, the prefix might be skipped, leading to immediate execution of INT 1. When run normally, an exception is raised. This behavior difference can be used to detect if the code is being debugged. ```cpp bool IsDebugged() { __try { // 0xF3 0x64 disassembles as PREFIX REP: __asm __emit 0xF3 __asm __emit 0x64 // One byte INT 1 __asm __emit 0xF1 return true; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; } } ``` -------------------------------- ### Check Written Pages with VirtualAlloc and GetWriteWatch (Variant 2) Source: https://anti-debug.checkpoint.com/techniques/misc.html This C/C++ variant attempts to trigger writes to a memory buffer using invalid API calls, then uses GetWriteWatch to detect if any writes occurred. It's designed to catch hooks or debuggers that might probe the buffer. ```C/C++ bool Generic::CheckWrittenPages2() const { BOOL result = FALSE, error = FALSE; const int SIZE_TO_CHECK = 4096; PVOID* addresses = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); if (addresses == NULL) { return true; } int* buffer = static_cast(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE)); if (buffer == NULL) { VirtualFree(addresses, 0, MEM_RELEASE); return true; } // Make some calls where a buffer *can* be written to, but isn't actually edited because we pass invalid parameters if (GlobalGetAtomName(INVALID_ATOM, (LPTSTR)buffer, 1) != FALSE || GetEnvironmentVariable("This variable does not exist", (LPSTR)buffer, 4096 * 4096) != FALSE || GetBinaryType("This name does not exist", (LPDWORD)buffer) != FALSE || HeapQueryInformation(0, (HEAP_INFORMATION_CLASS)69, buffer, 4096, NULL) != FALSE || ReadProcessMemory(INVALID_HANDLE_VALUE, (LPCVOID)0x69696969, buffer, 4096, NULL) != FALSE || GetThreadContext(INVALID_HANDLE_VALUE, (LPCONTEXT)buffer) != FALSE || GetWriteWatch(0, &result, 0, NULL, NULL, (PULONG)buffer) == 0) { result = false; error = true; } if (error == FALSE) { // A this point all calls failed as they're supposed to ULONG_PTR hits = SIZE_TO_CHECK; DWORD granularity; if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0) { result = FALSE; } else { // Should have zero reads here because GlobalGetAtomName doesn't probe the buffer until other checks have succeeded // If there's an API hook or debugger in here it'll probably try to probe the buffer, which will be caught here result = hits != 0; } } VirtualFree(addresses, 0, MEM_RELEASE); VirtualFree(buffer, 0, MEM_RELEASE); return result; } ``` -------------------------------- ### Query Process Debug Object Handle (x86 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Assembly implementation to call NtQueryInformationProcess and check the process debug object handle. If the handle is non-zero, it jumps to the 'being_debugged' label. ```assembly lea eax, [dwReturned] push eax ; ReturnLength push 4 ; ProcessInformationLength lea ecx, [hProcessDebugObject] push ecx ; ProcessInformation push 1Eh ; ProcessInformationClass push -1 ; ProcessHandle call NtQueryInformationProcess cmp dword ptr [hProcessDebugObject], 0 jnz being_debugged ... being_debugged: push -1 call ExitProcess ``` -------------------------------- ### Check Heap Protection for Debugger Presence (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html This C/C++ code checks for specific sequences appended to heap blocks, indicating debugger presence via heap protection flags. It uses HeapWalk to find busy heap entries and checks for the expected magic numbers. ```C/C++ bool Check() { PROCESS_HEAP_ENTRY HeapEntry = { 0 }; do { if (!HeapWalk(GetProcessHeap(), &HeapEntry)) return false; } while (HeapEntry.wFlags != PROCESS_HEAP_ENTRY_BUSY); PVOID pOverlapped = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; return ((DWORD)(*(PDWORD)pOverlapped) == 0xABABABAB); } ``` -------------------------------- ### Check Heap Flags for Debugger Presence (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html This C/C++ code checks the heap's Flags and ForceFlags to detect if a debugger is present. It accounts for differences between 32-bit and 64-bit Windows, and versions prior to or on/after Windows Vista. ```C/C++ bool Check() { #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); PVOID pHeapBase = !m_bIsWow64 ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x40 : 0x0C; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x44 : 0x10; #else PPEB pPeb = (PPEB)__readgsqword(0x60); PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x70 : 0x14; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x74 : 0x18; #endif // _WIN64 PDWORD pdwHeapFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset); PDWORD pdwHeapForceFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset); return (*pdwHeapFlags & ~HEAP_GROWABLE) || (*pdwHeapForceFlags != 0); } ``` -------------------------------- ### Patching Return Address with WriteProcessMemory Source: https://anti-debug.checkpoint.com/techniques/process-memory.html Employs kernel32!WriteProcessMemory to patch the code at the return address with a NOP instruction (0x90). This is a common method for code patching. ```C/C++ #include #pragma intrinsic(_ReturnAddress) void foo() { // ... BYTE Patch = 0x90; PVOID pRetAddress = _ReturnAddress(); if (*(PBYTE)pRetAddress == 0xCC) { DWORD dwOldProtect; if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { WriteProcessMemory(GetCurrentProcess(), pRetAddress, &Patch, 1, NULL); VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect); } } // ... } ``` -------------------------------- ### Detect Debugger by Parent Process ID (NtQueryInformationProcess) Source: https://anti-debug.checkpoint.com/techniques/misc.html Compares the current process's parent PID with the explorer.exe PID obtained via GetShellWindow and NtQueryInformationProcess. This method helps identify if the process was launched by a debugger instead of the shell. ```cpp bool IsDebugged() { HWND hExplorerWnd = GetShellWindow(); if (!hExplorerWnd) return false; DWORD dwExplorerProcessId; GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId); ntdll::PROCESS_BASIC_INFORMATION ProcessInfo; NTSTATUS status = ntdll::NtQueryInformationProcess( GetCurrentProcess(), ntdll::PROCESS_INFORMATION_CLASS::ProcessBasicInformation, &ProcessInfo, sizeof(ProcessInfo), NULL); if (!NT_SUCCESS(status)) return false; return (DWORD)ProcessInfo.InheritedFromUniqueProcessId != dwExplorerProcessId; } ``` -------------------------------- ### Check PEB BeingDebugged Flag (WOW64 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Checks the BeingDebugged flag in the PEB for WOW64 (32-bit on 64-bit) processes using assembly instructions. ```assembly mov eax, fs:[30h] cmp byte ptr [eax+1002h], 0 ``` -------------------------------- ### Check NtGlobalFlag (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html A C/C++ implementation to check for debugger presence by examining the NtGlobalFlag in the PEB for both 32-bit and 64-bit processes. ```c++ #define FLG_HEAP_ENABLE_TAIL_CHECK 0x10 #define FLG_HEAP_ENABLE_FREE_CHECK 0x20 #define FLG_HEAP_VALIDATE_PARAMETERS 0x40 #define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS) #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68); #else PPEB pPeb = (PPEB)__readgsqword(0x60); DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC); #endif // _WIN64 if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED) goto being_debugged; ``` -------------------------------- ### Detecting Debug Objects with NtQueryObject() in C/C++ Source: https://anti-debug.checkpoint.com/techniques/object-handles.html This C/C++ code snippet demonstrates how to use NtQueryObject() to enumerate system objects and check for the existence of 'DebugObject' types. It requires loading ntdll.dll and dynamically resolving the NtQueryObject function. ```cpp typedef struct _OBJECT_TYPE_INFORMATION { UNICODE_STRING TypeName; ULONG TotalNumberOfHandles; ULONG TotalNumberOfObjects; } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; typedef struct _OBJECT_ALL_INFORMATION { ULONG NumberOfObjects; OBJECT_TYPE_INFORMATION ObjectTypeInformation[1]; } OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION; typedef NTSTATUS (WINAPI *TNtQueryObject)( HANDLE Handle, OBJECT_INFORMATION_CLASS ObjectInformationClass, PVOID ObjectInformation, ULONG ObjectInformationLength, PULONG ReturnLength ); enum { ObjectAllTypesInformation = 3 }; #define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 bool Check() { bool bDebugged = false; NTSTATUS status; LPVOID pMem = nullptr; ULONG dwMemSize; POBJECT_ALL_INFORMATION pObjectAllInfo; PBYTE pObjInfoLocation; HMODULE hNtdll; TNtQueryObject pfnNtQueryObject; hNtdll = LoadLibraryA("ntdll.dll"); if (!hNtdll) return false; pfnNtQueryObject = (TNtQueryObject)GetProcAddress(hNtdll, "NtQueryObject"); if (!pfnNtQueryObject) return false; status = pfnNtQueryObject( NULL, (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, &dwMemSize, sizeof(dwMemSize), &dwMemSize); if (STATUS_INFO_LENGTH_MISMATCH != status) goto NtQueryObject_Cleanup; pMem = VirtualAlloc(NULL, dwMemSize, MEM_COMMIT, PAGE_READWRITE); if (!pMem) goto NtQueryObject_Cleanup; status = pfnNtQueryObject( (HANDLE)-1, (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, pMem, dwMemSize, &dwMemSize); if (!SUCCEEDED(status)) goto NtQueryObject_Cleanup; pObjectAllInfo = (POBJECT_ALL_INFORMATION)pMem; pObjInfoLocation = (PBYTE)pObjectAllInfo->ObjectTypeInformation; for(UINT i = 0; i < pObjectAllInfo->NumberOfObjects; i++) { POBJECT_TYPE_INFORMATION pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjInfoLocation; if (wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0) { if (pObjectTypeInfo->TotalNumberOfObjects > 0) bDebugged = true; break; } // Get the address of the current entries // string so we can find the end pObjInfoLocation = (PBYTE)pObjectTypeInfo->TypeName.Buffer; // Add the size pObjInfoLocation += pObjectTypeInfo->TypeName.Length; // Skip the trailing null and alignment bytes ULONG tmp = ((ULONG)pObjInfoLocation) & -4; // Not pretty but it works pObjInfoLocation = ((PBYTE)tmp) + sizeof(DWORD); } NtQueryObject_Cleanup: if (pMem) VirtualFree(pMem, 0, MEM_RELEASE); return bDebugged; } ``` -------------------------------- ### Check NtGlobalFlag (64-bit Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Examines the NtGlobalFlag in the PEB for specific debugger-related flags in 64-bit processes using assembly. ```assembly mov rax, gs:[60h] mov al, [rax+BCh] and al, 70h cmp al, 70h jz being_debugged ``` -------------------------------- ### Check Process Debug Port (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Use NtQueryInformationProcess with ProcessDebugPort to check if a process is being debugged. A return value of -1 indicates a debugger is present. ```cpp typedef NTSTATUS (NTAPI *TNtQueryInformationProcess)( 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); } } ``` -------------------------------- ### Detect Debugger by Parent Process Name (CreateToolhelp32Snapshot) Source: https://anti-debug.checkpoint.com/techniques/misc.html Retrieves the parent process ID and name using CreateToolhelp32Snapshot and Process32Next to check if the parent is explorer.exe. This is an alternative to NtQueryInformationProcess for parent process verification. ```cpp DWORD GetParentProcessId(DWORD dwCurrentProcessId) { DWORD dwParentProcessId = -1; PROCESSENTRY32W ProcessEntry = { 0 }; ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(Process32FirstW(hSnapshot, &ProcessEntry)) { do { if (ProcessEntry.th32ProcessID == dwCurrentProcessId) { dwParentProcessId = ProcessEntry.th32ParentProcessID; break; } } while(Process32NextW(hSnapshot, &ProcessEntry)); } CloseHandle(hSnapshot); return dwParentProcessId; } bool IsDebugged() { bool bDebugged = false; DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); PROCESSENTRY32 ProcessEntry = { 0 }; ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(Process32First(hSnapshot, &ProcessEntry)) { do { if ((ProcessEntry.th32ProcessID == dwParentProcessId) && (strcmp(ProcessEntry.szExeFile, "explorer.exe"))) // Note: strcmp returns 0 if strings are equal { bDebugged = true; break; } } while(Process32Next(hSnapshot, &ProcessEntry)); } CloseHandle(hSnapshot); return bDebugged; } ``` -------------------------------- ### Check Process Debug Port (x86 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html This x86 assembly snippet demonstrates checking the ProcessDebugPort using NtQueryInformationProcess. It exits if a debugger is detected. ```assembly 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 ... bing_debugged: push -1 call ExitProcess ``` -------------------------------- ### Detect Debugger using GetSystemTime() Source: https://anti-debug.checkpoint.com/techniques/timing.html Utilizes the GetSystemTime API to measure elapsed time. This function retrieves the current UTC date and time, providing another method to detect debugger delays. ```C/C++ bool IsDebugged(DWORD64 qwNativeElapsed) { SYSTEMTIME stStart, stEnd; FILETIME ftStart, ftEnd; ULARGE_INTEGER uiStart, uiEnd; GetSystemTime(&stStart); // ... some work GetSystemTime(&stEnd); if (!SystemTimeToFileTime(&stStart, &ftStart)) return false; if (!SystemTimeToFileTime(&stEnd, &ftEnd)) return false; uiStart.LowPart = ftStart.dwLowDateTime; uiStart.HighPart = ftStart.dwHighDateTime; uiEnd.LowPart = ftEnd.dwLowDateTime; uiEnd.HighPart = ftEnd.dwHighDateTime; return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed; } ``` -------------------------------- ### Check PEB BeingDebugged Flag (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html A portable C/C++ implementation to check the BeingDebugged flag in the PEB for both 32-bit and 64-bit processes. ```c++ #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); #else PPEB pPeb = (PPEB)__readgsqword(0x60); #endif // _WIN64 if (pPeb->BeingDebugged) goto being_debugged; ``` -------------------------------- ### x86-64 Assembly: Advanced Selector Handling Source: https://anti-debug.checkpoint.com/techniques/misc.html This x86-64 assembly code illustrates a more complex scenario involving FS, SS, and DS selectors, including forcing an exception with 'int 3' to observe debugger behavior. ```assembly xor eax, eax push offset l2 push d fs:[eax] mov fs:[eax], esp push fs pop ss xchg [eax], cl xchg [eax], cl l1: int 3 ;force exception to occur l2: ;looks like it would be reached ;if an exception occurs ... ``` -------------------------------- ### Query System Kernel Debugger Information (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Uses NtQuerySystemInformation with SystemKernelDebuggerInformation class (0x23) to check if a kernel debugger is enabled. It returns true if DebuggerEnabled is true and DebuggerNotPresent is false. ```cpp enum { SystemKernelDebuggerInformation = 0x23 }; typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION { BOOLEAN DebuggerEnabled; BOOLEAN DebuggerNotPresent; } SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; bool Check() { NTSTATUS status; SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInfo; status = NtQuerySystemInformation( (SYSTEM_INFORMATION_CLASS)SystemKernelDebuggerInformation, &SystemInfo, sizeof(SystemInfo), NULL); return SUCCEEDED(status) ? (SystemInfo.DebuggerEnabled && !SystemInfo.DebuggerNotPresent) : false; } ``` -------------------------------- ### Check NtGlobalFlag (32-bit Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Examines the NtGlobalFlag in the PEB for specific debugger-related flags in 32-bit processes using assembly. ```assembly mov eax, fs:[30h] mov al, [eax+68h] and al, 70h cmp al, 70h jz being_debugged ``` -------------------------------- ### Detect Debugger using INT 3 (Short Form) Source: https://anti-debug.checkpoint.com/techniques/assembly.html Uses the INT 3 instruction to trigger a breakpoint exception. If a debugger is present, it handles the exception; otherwise, the exception handler is called. ```c++ bool IsDebugged() { __try { __asm int 3; return true; } __except(EXCEPTION_EXECUTE_HANDLER) { return false; } } ``` -------------------------------- ### Check Process Debug Port (x86-64 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html This x86-64 assembly snippet shows how to check the ProcessDebugPort using NtQueryInformationProcess. It terminates the process if a debugger is found. ```assembly 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 ... bing_debugged: mov ecx, -1 call ExitProcess ``` -------------------------------- ### Detect Debugger with Exclusive File Open (CreateFile) Source: https://anti-debug.checkpoint.com/techniques/object-handles.html Checks for debugger presence by attempting to exclusively open the current process's executable file. If the call fails, it suggests a debugger is holding a handle to the file. ```c++ bool Check() { CHAR szFileName[MAX_PATH]; if (0 == GetModuleFileNameA(NULL, szFileName, sizeof(szFileName))) return false; return INVALID_HANDLE_VALUE == CreateFileA(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); } ``` -------------------------------- ### Check Process Debug Flags (x86-64 Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html An x86-64 assembly implementation for checking ProcessDebugFlags via NtQueryInformationProcess. The process exits if a debugger is detected. ```assembly 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 ... bing_debugged: mov ecx, -1 call ExitProcess ``` -------------------------------- ### Check Process Memory for Shared Pages Source: https://anti-debug.checkpoint.com/techniques/process-memory.html Queries the current process's working set to detect if code pages are shared, which can indicate the absence of software breakpoints. Requires NTDLL declarations. ```c++ bool IsDebugged() { #ifndef _WIN64 NTSTATUS status; PBYTE pMem = nullptr; DWORD dwMemSize = 0; do { dwMemSize += 0x1000; pMem = (PBYTE)_malloca(dwMemSize); if (!pMem) return false; memset(pMem, 0, dwMemSize); status = ntdll::NtQueryVirtualMemory( GetCurrentProcess(), NULL, ntdll::MemoryWorkingSetList, pMem, dwMemSize, NULL); } while (status == STATUS_INFO_LENGTH_MISMATCH); ntdll::PMEMORY_WORKING_SET_LIST pWorkingSet = (ntdll::PMEMORY_WORKING_SET_LIST)pMem; for (ULONG i = 0; i < pWorkingSet->NumberOfPages; i++) { DWORD dwAddr = pWorkingSet->WorkingSetList[i].VirtualPage << 0x0C; DWORD dwEIP = 0; __asm { push eax call $+5 pop eax mov dwEIP, eax pop eax } if (dwAddr == (dwEIP & 0xFFFFF000)) return (pWorkingSet->WorkingSetList[i].Shared == 0) || (pWorkingSet->WorkingSetList[i].ShareCount == 0); } #endif // _WIN64 return false; } ``` -------------------------------- ### x86 Assembly: Selector Manipulation Source: https://anti-debug.checkpoint.com/techniques/misc.html This x86 assembly snippet demonstrates basic selector manipulation using FS and DS. It's used to detect if a debugger alters selector values. ```assembly xor eax, eax push fs pop ds l1: xchg [eax], cl xchg [eax], cl ``` -------------------------------- ### C/C++: Detecting Debugger via RaiseException Source: https://anti-debug.checkpoint.com/techniques/misc.html This C/C++ function uses `RaiseException` with `DBG_PRINTEXCEPTION_C` to detect if a debugger is present. If the exception is handled by the debugger, the function returns false. ```cpp bool IsDebugged() { __try { RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); } __except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) { return false; } return true; } ``` -------------------------------- ### Terminate if Debugger Present (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html A simple C/C++ implementation to exit the process if IsDebuggerPresent() indicates a debugger is attached. ```C/C++ if (IsDebuggerPresent()) ExitProcess(-1); ``` -------------------------------- ### Instruction Counting with Hardware Breakpoints Source: https://anti-debug.checkpoint.com/techniques/assembly.html This C++ code implements instruction counting to detect debuggers. It uses a vectored exception handler and hardware breakpoints to count executed instructions in a sequence. If the count doesn't match the expected sequence length, it indicates debugging. ```cpp #include "hwbrk.h" static LONG WINAPI InstructionCountingExeptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { pExceptionInfo->ContextRecord->Eax += 1; pExceptionInfo->ContextRecord->Eip += 1; return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; } __declspec(naked) DWORD WINAPI InstructionCountingFunc(LPVOID lpThreadParameter) { __asm { xor eax, eax nop nop nop nop cmp al, 4 jne being_debugged } ExitThread(FALSE); being_debugged: ExitThread(TRUE); } bool IsDebugged() { PVOID hVeh = nullptr; HANDLE hThread = nullptr; bool bDebugged = false; __try { hVeh = AddVectoredExceptionHandler(TRUE, InstructionCountingExeptionHandler); if (!hVeh) __leave; hThread = CreateThread(0, 0, InstructionCountingFunc, NULL, CREATE_SUSPENDED, 0); if (!hThread) __leave; PVOID pThreadAddr = &InstructionCountingFunc; // Fix thread entry address if it is a JMP stub (E9 XX XX XX XX) if (*(PBYTE)pThreadAddr == 0xE9) pThreadAddr = (PVOID)((DWORD)pThreadAddr + 5 + *(PDWORD)((PBYTE)pThreadAddr + 1)); for (auto i = 0; i < m_nInstructionCount; i++) m_hHwBps[i] = SetHardwareBreakpoint( hThread, HWBRK_TYPE_CODE, HWBRK_SIZE_1, (PVOID)((DWORD)pThreadAddr + 2 + i)); ResumeThread(hThread); WaitForSingleObject(hThread, INFINITE); DWORD dwThreadExitCode; if (TRUE == GetExitCodeThread(hThread, &dwThreadExitCode)) bDebugged = (TRUE == dwThreadExitCode); } __finally { if (hThread) CloseHandle(hThread); for (int i = 0; i < 4; i++) { if (m_hHwBps[i]) RemoveHardwareBreakpoint(m_hHwBps[i]); } if (hVeh) RemoveVectoredExceptionHandler(hVeh); } return bDebugged; } ``` -------------------------------- ### Detect IsDebuggerPresent() Function Patch Source: https://anti-debug.checkpoint.com/techniques/process-memory.html Compares the bytes of kernel32!IsDebuggerPresent() in the current process with those in other processes to detect modifications. Relies on libraries being loaded at the same base addresses within a session. ```c++ bool IsDebuggerPresent() { HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); if (!hKernel32) return false; FARPROC pIsDebuggerPresent = GetProcAddress(hKernel32, "IsDebuggerPresent"); if (!pIsDebuggerPresent) return false; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (INVALID_HANDLE_VALUE == hSnapshot) return false; PROCESSENTRY32W ProcessEntry; ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); if (!Process32FirstW(hSnapshot, &ProcessEntry)) return false; bool bDebuggerPresent = false; HANDLE hProcess = NULL; DWORD dwFuncBytes = 0; const DWORD dwCurrentPID = GetCurrentProcessId(); do { __try { if (dwCurrentPID == ProcessEntry.th32ProcessID) continue; hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessEntry.th32ProcessID); if (NULL == hProcess) continue; if (!ReadProcessMemory(hProcess, pIsDebuggerPresent, &dwFuncBytes, sizeof(DWORD), NULL)) continue; if (dwFuncBytes != *(PDWORD)pIsDebuggerPresent) { bDebuggerPresent = true; break; } } __finally { if (hProcess) CloseHandle(hProcess); } } while (Process32NextW(hSnapshot, &ProcessEntry)); if (hSnapshot) CloseHandle(hSnapshot); return bDebuggerPresent; } ``` -------------------------------- ### Checking Memory Integrity with Toolhelp32ReadProcessMemory Source: https://anti-debug.checkpoint.com/techniques/process-memory.html Utilizes kernel32!Toolhelp32ReadProcessMemory to read the memory of the current process. It checks if the byte at the return address is an INT3 instruction (0xCC) and exits if true, indicating a potential debugger. ```C/C++ #include bool foo() { // .. PVOID pRetAddress = _ReturnAddress(); BYTE uByte; if (FALSE != Toolhelp32ReadProcessMemory(GetCurrentProcessId(), _ReturnAddress(), &uByte, sizeof(BYTE), NULL)) { if (uByte == 0xCC) ExitProcess(0); } // .. } ``` -------------------------------- ### Memory Breakpoint Detection using Guard Pages Source: https://anti-debug.checkpoint.com/techniques/process-memory.html Detects debuggers by allocating a guard page containing a RET instruction. If a debugger is present, execution is diverted to a handler; otherwise, an exception occurs. ```C/C++ bool IsDebugged() { DWORD dwOldProtect = 0; SYSTEM_INFO SysInfo = { 0 }; GetSystemInfo(&SysInfo); PVOID pPage = VirtualAlloc(NULL, SysInfo.dwPageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == pPage) return false; PBYTE pMem = (PBYTE)pPage; *pMem = 0xC3; // Make the page a guard page if (!VirtualProtect(pPage, SysInfo.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &dwOldProtect)) return false; __try { __asm { mov eax, pPage push mem_bp_being_debugged jmp eax } } __except(EXCEPTION_EXECUTE_HANDLER) { VirtualFree(pPage, NULL, MEM_RELEASE); return false; } mem_bp_being_debugged: VirtualFree(pPage, NULL, MEM_RELEASE); return true; } ``` -------------------------------- ### Detect Debugger using OutputDebugString with SetLastError (Pre-Vista) Source: https://anti-debug.checkpoint.com/techniques/interactive.html An alternative pre-Vista method to detect a debugger using `OutputDebugString`. It explicitly sets `GetLastError` to a known value before the call and checks if it changes afterward, indicating debugger presence. ```cpp bool IsDebugged() { if (IsWindowsVistaOrGreater()) return false; DWORD dwErrVal = 0x666; SetLastError(dwErrVal); OutputDebugString(L"AntiDebug_OutputDebugString_v2"); return GetLastError() != dwErrVal; } ``` -------------------------------- ### Check PEB BeingDebugged Flag (64-bit Assembly) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Directly checks the BeingDebugged flag in the PEB for 64-bit processes using assembly instructions. ```assembly mov rax, gs:[60h] cmp byte ptr [rax+2], 0 jne being_debugged ``` -------------------------------- ### Detect Debugger using OutputDebugString (Pre-Vista) Source: https://anti-debug.checkpoint.com/techniques/interactive.html Checks for the presence of a debugger by observing `OutputDebugString` behavior. This method is deprecated and only effective on Windows versions prior to Vista. It relies on `GetLastError` returning a different value after the call if a debugger is attached. ```cpp bool IsDebugged() { if (IsWindowsVistaOrGreater()) return false; DWORD dwLastError = GetLastError(); OutputDebugString(L"AntiDebug_OutputDebugString_v1"); return GetLastError() != dwLastError; } ``` -------------------------------- ### Detect Single-Stepping with NtYieldExecution/SwitchToThread Source: https://anti-debug.checkpoint.com/techniques/misc.html This C++ function detects if the application is being single-stepped in a debugger. It relies on the fact that SwitchToThread returns zero when a debugger is present and single-stepping, causing a counter to become zero if the loop executes 8 times. ```cpp bool IsDebugged() { BYTE ucCounter = 1; for (int i = 0; i < 8; i++) { Sleep(0x0F); ucCounter <<= (1 - SwitchToThread()); } return ucCounter == 0; } ``` -------------------------------- ### Check Remote Debugger Presence (C/C++) Source: https://anti-debug.checkpoint.com/techniques/debug-flags.html Use this C/C++ code to check if a debugger is attached to the current process using CheckRemoteDebuggerPresent(). Exits the process if a debugger is found. ```C/C++ BOOL bDebuggerPresent; if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && TRUE == bDebuggerPresent) ExitProcess(-1); ```