antidbg is a x64 user-mode anti-debugging library for Windows, designed to protect software from debugging.
The library is:
- Very easy to use (only one function call required).
- Designed for high performance and minimal resource usage (5% CPU usage; 1MB of memory).
- Compatible with most x86_64 assemblers and compilers (
GNU AT&T,MASM;Clang,MinGW-w64,GCC,clang-cl,MSVC) and any C or C++ standard. - Free of any external dependencies.
- Fully MIT-licensed, allowing unrestricted use and distribution.
The treat model assumes a debugger may intercept software guarded by this protection system from any kind of privilege level, being detections less effective the higher the privilege level is.
This software is hardened to bypass trivial CPL > 0 interception, by:
- Avoiding any kind of API hook via syscalling with inline assembly.
- Enforcing virtual memory protection and injection mitigation policies for the current process.
- Preventing syscall spoofing in
RAXby detecting and/or overwritting non-legitimate instrumentation callbacks. - Detecting any kind of inline patch on monitored
.textsections by doing a on-disk vs in-memory comparison. - Analyzing exception handling delivery (
VEH,SEH,LPTOP_LEVEL_EXCEPTION_FILTER) and software breakpoints. - Testing
PAGE_GUARDredirection behavior. - Protecting important stubs as non-writable memory, monitoring them later with hardware-accelerated hashing on a separate thread.
- Guarding the process entrypoint with
TLS callbacksfrom debugger attachment, performing thread start address inspection. - Creating traps in debugger entrypoints, such as
DbgBreakPointandDbgUiRemoteBreakin, to crash the process or return. - Hiding all threads from debugger events and process freeze. Ensures thread priority state is not affected
- Setting a global vectored handler as soon as protections starts.
If the only way to do a check is to use a non-syscallable exported function, the function in question is manually reverse-engineered and reconstructed to run in the module address space of the protection thread. All user-mode memory structures are walked with direct memory instrospection instead of using APIs.
The protection routines explictly leaves some execution paths without protection against user-mode hooks, acting as memory honeypots. They're used for state comparison and to confuse attackers.
Protection routines runs pseudo-randomly. The entropy is decided with pure hardware-based ASLR, stack behavior and a little bit of math; without calling the kernel, using user-mode APIs, or issuing conditionally or unconditionally exiting instructions by hypervisors.
When a security violation is detected, the protection system crashes the current process by invoking INT 29h with STATUS_SXS_EARLY_DEACTIVATION, bypassing all exception handlers. In some cases, it also queues an APC to the kernel in order to terminate the current process.
Found in the main entrypoint (abdg.c) of this library, explained in order.
Summarized explanations; a specific detection may perform more extra/sub-checks than what it is explained here.
You can find more source code of other detection concepts in the antidebug\archived folder.
1.Reads the PEB’sBeingDebuggedfield using kernel32's export function.2.Calls exportIsRemoteDebuggerPresentto see whether the target process is being debugged from outside its own context.3.Executes theINT 2Dsoftware interrupt, checking if the byte following such instruction is skipped and not routed throught theEXCEPTION_BREAKPOINThandler.4.Executes breakpoint interruptINT 3D, then watches whether the exception is intercepted or passed through normally.5.Invokes ICE/0xF1, raisingEXCEPTION_SINGLE_STEPand checking whether the debugger will consider this exception as the normal exception generated by executing the instruction with the single step bit set in theRFlagsregisters6.Probes stack-segment registers by setting theTFflag and checking if the debugger clears it fromRFLAGS, as normally debuggers clear the trap flag after each debugger event is delivered.7.Uses a prefix-based instruction-flow edge case and checks whether0xF3 0x64, which disassembles asPREFIX REP, forces a0xF1skip.8.checks wether after setting a trap flag and invokingpushfd mov dword ptr [esp], 0x100 popfd nop,nopis reached instead of getting into theEXCEPTION_SINGLE_STEPhandler.9.Raises aDBG_CONTROL_Cand aDBG_RIPEXCEPTIONevent to see whether the exception is intercepted and not routed through aSEH.10.Checks for an attached debug object handle, querying forProcessDebugObjectHandlewithNtQueryInformationProcess.11.Queries for the presence of a kernel debugger usingSystemKernelDebuggerInformationwithNtQuerySystemInformation, and directly reading theKUSER_SHARED_DATAmemory page for theKdDebuggerEnabledfield.12.Reads the NT global flag for a mask ofFLG_HEAP_ENABLE_TAIL_CHECK(0x10),FLG_HEAP_ENABLE_FREE_CHECK(0x20) andFLG_HEAP_VALIDATE_PARAMETERS(0x40)13.InspectsProcessDebugFlags, to infer whether debugging is enabled or suppressed.14.Checks heap flags to infer ifHEAP_GROWABLE(0x2) is not the only flag enabled for the current process context by directly reading the process heap base (_PEB + 0x30 in x86_64) + 0x7015.Same as above, but looks for the heap force flags value at memory offset 0x74.16.Duplicates process handles and checks whether a debugger touch handles, inherit handles, or re-open / duplicate them; Can I create a protected duplicate handle, then duplicate it again cleanly?17.Examines the parent-process chain to spot debugger launchers or suspicious ancestry likevsjitdebugger,x64dbg, or similar.18.Checks the debug PEB fields without using exports, reading directly from base (__readgsqword(0x60)) at offset*(BYTE*)((uintptr_t)peb + 2.19.Queries theProcessDebugPortfor the current process.20.Checks for hardware breakpoints by inspecting thread debug registers (Dr0–Dr7).21.Checks whether virtual memory was hit by debuggers by placing honeypots and monitoring for changes.22.Performs two invalid-handle close tests with process and window handles, watches whetherERROR_INVALID_WINDOW_HANDLEandEXCEPTION_INVALID_HANDLEare not intercepted.23.Checks whether debug objects are intercepted by the debugger and if handle stripping occurs.24.Tries opening a process in a way that reveals whether access is being filtered or redirected by debuggers.25.Checks whether a mutex handle marked asHANDLE_FLAG_PROTECT_FROM_CLOSEcan be closed directly.26.CallsNtSystemDebugControlwithSysDbgGetTriageDumpand checks whether a kernel debugger blocks the call or spoofs the call but doesn't touch our memory buffer.27.Checks whether memory reads of our own stack are being instrumented or intercepted.28.Checks whether the process is inside a non whitelisted job object created by a debugger.29.Uses a memory-breakpoint style access test, usually expecting page-guard or fault behavior if watchpoints are active.30.Triggers a page-exception breakpoint scenario and inspects whether the exception chain correctly delivesSTATUS_GUARD_PAGE_VIOLATION.31.Measures execution timing to detect the overhead introduced by single-stepping, breakpoints, or dynamic binary instrumentation/JIT recompilation.32.Searches for debugger windows or UI artifacts by enumerating windows/classes/titles associated with debugger tools.33.Analyzes theDBGPACPI debug port firmware table.34.Checks whether a debugger clears previously setLBR/BTFbits inDR7to perform its own single-stepping, resulting in an emptyExceptionInformationarray, or whether kernel-mode branch addresses are detected if the debugger decides to leave LBR enabled but still interceptEXCEPTION_SINGLE_STEPinvoked byicebp.35.Walks heap directly and checks for0xABABABABand0xFEEEFEEEmagic values. Effectively the same as 12 but using hookable Heap APIs.36.Checks whether aCopy-On-Writehas occurred in virtual memory by checking whether the previously shared page was touched by a debugger.37.Sends a console event (CTRL_C_EVENT) and checks whether a debugger intecepts it and changes delively of it to our control handler, or raisesDBG_CONTROL_C.38.Checks whether the process is suspended externally for injection attempts; detects any external call toNtResumeProcesspointing to our process.39.CallsNtSetDebugFilterStatewith differentSE_DEBUG_PRIVILEGEprivilege levels and checks whether a kernel debugger incorrectly handles access.40.Analyzes device objects, also checks whether a kernel debugger intercepts the file read.41.Puts threads racing against both a kernel debugger and the kernel itself reading theContextFlagsstructure; checks whetherDEBUG_REGISTERSis stripped/ifDr0was not set.42.Freezes some debuggers by creating and mapping an extremely large view of a virtual section; detects if calls toNtMapViewOfSectionare tampered with.
- Guard mode: A thread will start running in your program and continuously monitor for attached debuggers. If a debugger is detected at any time, the program will log the attempt (if compiled in debug mode) and forcefully exit while preventing any other program from stopping the crash.
Example:
#include "adbg.h"
int main() {
StartDebugProtection();
return 0;
}- Single-run mode: A function that you can call at any time to detect if debuggers are attached to your process.
Example:
#include "adbg.h"
int main() {
if (isProgramBeingDebugged()) {
printf("Debugger detected.\n");
}
else {
printf("No debugger was detected.\n");
}
return 0;
}Copy-paste your desired main function from the section "Usage" at the end of the adbg.c file. Then:
In MSVC: Click on the .sln file at the root of this repository, select your desired mode (Debug or Release) and click on "Build".
In the rest: Append -DBUILD_EXAMPLE=ON when building with CMake. Example: cmake -S .. -B build -G Ninja -DCMAKE_C_COMPILER=clang -DBUILD_EXAMPLE=ON, then cmake --build build.
Compiling in Debug mode will enable logging to console and debuggers and won't make detections behave differently from Release mode.
MSVC
cd to the project root and run:
mkdir build && cd build
cmake .. -A x64
cmake --build . --config Release
GCC/MinGW-w64
Ensure you launch the 64-bit MinGW environment, this assumes gcc is in your PATH:
mkdir build && cd build
cmake .. -G "MinGW Makefiles"
cmake --build .
Clang
Using Ninja as generator:
mkdir build && cd build
cmake .. -G Ninja -DCMAKE_C_COMPILER=clang
cmake --build .
I am not responsible nor liable for any damage you cause through any malicious usage of this project.
License: MIT