Skip to content

Repository files navigation

AntiDBG

antidbg is a x64 user-mode anti-debugging library for Windows, designed to protect software from debugging.

Consider the library as a base for your anti-debugging protection, not as your only defense.

The library is:

  • Very easy to use (only one function call required).
  • Designed for high performance and minimal resource usage (1% CPU usage; <2MB of memory).
  • Free of any external dependencies.
  • Fully MIT-licensed.
  • CFG-compliant.

Structure

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 RAX by detecting and/or overwritting non-legitimate instrumentation callbacks.
  • Detecting any kind of inline patch on monitored .text sections by doing a on-disk vs in-memory comparison.
  • Analyzing exception handling delivery (VEH, SEH, LPTOP_LEVEL_EXCEPTION_FILTER) and software breakpoints.
  • Testing PAGE_GUARD redirection 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 callbacks from debugger attachment, performing thread start address inspection.
  • Creating traps in debugger entrypoints, such as DbgBreakPoint and DbgUiRemoteBreakin, 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.

Detections

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’s BeingDebugged field using kernel32's export function.
  • 2. Calls export IsRemoteDebuggerPresent to see whether the target process is being debugged from outside its own context.
  • 3. Executes the INT 2D software interrupt, checking if the byte following such instruction is skipped and not routed throught the EXCEPTION_BREAKPOINT handler.
  • 4. Executes breakpoint interrupt INT 3D, then watches whether the exception is intercepted or passed through normally.
  • 5. Invokes ICE/0xF1, raising EXCEPTION_SINGLE_STEP and checking whether the debugger will consider this exception as the normal exception generated by executing the instruction with the single step bit set in the RFlags registers
  • 6. Probes stack-segment registers by setting the TF flag and checking if the debugger clears it from RFLAGS, as normally debuggers clear the trap flag after each debugger event is delivered.
  • 7. Uses a prefix-based instruction-flow edge case and checks whether 0xF3 0x64, which disassembles as PREFIX REP, forces a 0xF1 skip.
  • 8. checks wether after setting a trap flag and invoking pushfd mov dword ptr [esp], 0x100 popfd nop, nop is reached instead of getting into the EXCEPTION_SINGLE_STEP handler.
  • 9. Raises a DBG_CONTROL_C and a DBG_RIPEXCEPTION event to see whether the exception is intercepted and not routed through a SEH.
  • 10. Checks for an attached debug object handle, querying for ProcessDebugObjectHandle with NtQueryInformationProcess.
  • 11. Queries for the presence of a kernel debugger using SystemKernelDebuggerInformation with NtQuerySystemInformation, and directly reading the KUSER_SHARED_DATA memory page for the KdDebuggerEnabled field. Additionally, check if kernel timer ISRs are ticking asynchronously.
  • 12. Reads the NT global flag for a mask of FLG_HEAP_ENABLE_TAIL_CHECK (0x10), FLG_HEAP_ENABLE_FREE_CHECK (0x20) and FLG_HEAP_VALIDATE_PARAMETERS (0x40)
  • 13. Inspects ProcessDebugFlags, to infer whether debugging is enabled or suppressed.
  • 14. 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?
  • 15. Examines the parent-process chain to spot debugger launchers or suspicious ancestry like vsjitdebugger, x64dbg, or similar.
  • 16. Checks the debug PEB fields without using exports, reading directly from base (__readgsqword(0x60)) at offset *(BYTE*)((uintptr_t)peb + 2.
  • 17. Queries the ProcessDebugPort for the current process.
  • 18. Checks for hardware breakpoints by inspecting thread debug registers (Dr0Dr7).
  • 19. Checks whether virtual memory was hit by debuggers by placing honeypots and monitoring for changes.
  • 20. Performs two invalid-handle close tests with process and window handles, watches whether ERROR_INVALID_WINDOW_HANDLE and EXCEPTION_INVALID_HANDLE are not intercepted.
  • 21. Checks whether debug objects are intercepted by the debugger and if handle stripping occurs.
  • 22. Tries opening a process in a way that reveals whether access is being filtered or redirected by debuggers.
  • 23. Checks whether a mutex handle marked as HANDLE_FLAG_PROTECT_FROM_CLOSE can be closed directly.
  • 24. Calls NtSystemDebugControl with SysDbgGetTriageDump and checks whether a kernel debugger blocks the call or spoofs the call but doesn't touch our memory buffer.
  • 25. Checks whether memory reads of our own stack are being instrumented or intercepted.
  • 26. Checks whether the process is inside a non whitelisted job object created by a debugger.
  • 27. Uses a memory-breakpoint style access test, usually expecting page-guard or fault behavior if watchpoints are active.
  • 28. Triggers a page-exception breakpoint scenario and inspects whether the exception chain correctly delives STATUS_GUARD_PAGE_VIOLATION.
  • 29. Measures execution timing to detect the overhead introduced by single-stepping, breakpoints, or dynamic binary instrumentation/JIT recompilation.
  • 30. Searches for debugger windows or UI artifacts by enumerating windows/classes/titles associated with debugger tools.
  • 31. Checks whether a debugger clears previously set LBR/BTF bits in DR7 to perform its own single-stepping, resulting in an empty ExceptionInformation array, or whether kernel-mode branch addresses are detected if the debugger decides to leave LBR enabled but still intercept EXCEPTION_SINGLE_STEP invoked by icebp.
  • 32. Walks heap directly and checks for 0xABABABAB and 0xFEEEFEEE magic values. Effectively the same as 12 but using hookable Heap APIs.
  • 33. Checks whether a Copy-On-Write has occurred in virtual memory by checking whether the previously shared page was touched by a debugger.
  • 34. Sends a console event (CTRL_C_EVENT) and checks whether a debugger intecepts it and changes delively of it to our control handler, or raises DBG_CONTROL_C.
  • 35. Checks whether the process is suspended externally for injection attempts; detects any external call to NtResumeProcess pointing to our process.
  • 36. Calls NtSetDebugFilterState with different SE_DEBUG_PRIVILEGE privilege levels and checks whether a kernel debugger incorrectly handles access.
  • 37. Analyzes device objects, also checks whether a kernel debugger intercepts the file read.
  • 38. Puts threads racing against both a kernel debugger and the kernel itself reading the ContextFlags structure; checks whether DEBUG_REGISTERS is stripped/if Dr0 was not set.
  • 39. Freezes some debuggers by creating and mapping an extremely large view of a virtual section; detects if calls to NtMapViewOfSection are tampered with.
  • 40. Check for unimplemented syscalls (common in emulators).
  • 41. Sets four hardware execution breakpoints from DR0 to DR3 on four consecutive NOPs and counts the resulting EXCEPTION_SINGLE_STEP deliveries via a VEH.
  • 42. Tests debugger involvement via OutputDebugString side effects on legacy Windows XP/2000 and via DBG_PRINTEXCEPTION_{C,WIDE_C} exceptions that a debugger can intercept.
  • 43. Checks whether the first byte of our own on-disk image is 0xCCand exploits the Windows loader file-handle behavior where LoadLibrary can leave the file non-exclusively accessible under a debugger.

Usage

  1. 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;
}
  1. 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;
}

Build

1. Binary mode

To build the test runner executable, enable -DBUILD_EXAMPLE=ON. This compiles example/main.c and links it against antidebug without polluting the core library with an entry point.

Visual Studio (GUI)

  1. Open the repository folder in Visual Studio (or open the generated .sln file).
  2. Select your desired configuration (x64-Release or x64-Debug).
  3. Set antidebug_runner as the startup project and click Build (or press F5 to run).

CLI (MSVC / Ninja / Clang)

From the project root:

cmake -B build -S . -DBUILD_EXAMPLE=ON
cmake --build build --config Release

The executable will be located at:

  • MSVC multi-config: build/Release/antidebug_runner.exe
  • Ninja single-config: build/antidebug_runner.exe

Note on Debug Builds: Compiling in Debug mode (--config Debug) enables console/debugger diagnostic logs via core/debug.c. Release mode strips logging completely.


2. Library mode (Static or Shared)

By default, CMake produces a static library (antidebug.lib or libantidebug.a). To build a dynamic link library (DLL), pass -DBUILD_SHARED_LIBS=ON.

Option A: MSVC (cl.exe)

Using the Visual Studio generator:

# Static Library (.lib)
cmake -B build -S . -A x64 -DBUILD_SHARED_LIBS=OFF
cmake --build build --config Release

# Dynamic Library (.dll + import .lib)
cmake -B build -S . -A x64 -DBUILD_SHARED_LIBS=ON
cmake --build build --config Release

Option B: Clang-CL (LLVM with MSVC Integration)

Using Ninja with clang-cl:

# Ensure clang-cl is in your PATH or run from the VS x64 Native Tools Command Prompt
cmake -B build -S . -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
cmake --build build

Option C: Clang (MinGW / LLVM-MinGW)

Launch your 64-bit LLVM-MinGW shell (x86_64-w64-mingw32-clang in your PATH) and use Ninja:

# Static Library (.a)
cmake -B build -S . -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
cmake --build build

# Shared Library (.dll)
cmake -B build -S . -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
cmake --build build

3. Installation via CMake

To install the compiled library and headers into a local prefix:

cmake --install build --prefix "C:/local/antidebug"

This produces:

C:/local/antidebug/
├── bin/
│   └── antidebug.dll           (if BUILD_SHARED_LIBS=ON)
├── lib/
│   └── antidebug.lib           (or libantidebug.a)
└── include/
    └── antidebug/
        ├── adbg.h
        └── ...

Legal & Disclaimers

I am not responsible nor liable for any damage you cause through any malicious usage of this project.

The generation of the BUILD documentation was made with AI, report any issues.

License: MIT

About

A stealthy, fully syscalled C/C++ userland anti-debugging library for Windows, designed to protect software from reverse engineering

Topics

Resources

Stars

323 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages