agentleFS
kings0527kings0527/pi-harness/skills/anti-debugging-techniques/SKILL.md

anti-debugging-techniques

Skill9 starsChanged 3 months agoMITView on GitHub

Anti-debugging detection and bypass playbook. Use when reversing protected binaries that detect debuggers via ptrace, PEB flags, timing checks, or signal/exception handlers on Linux and Windows.

4 other projects use a version of this, 4 of them edited.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

5 rules. 3 are common, 1 is distinctive, and 1 is very specific to this project.

  • 3

    Common

    Most of the 4 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    When a debugger is attached, SIGTRAP is consumed by the debugger rather than delivered to the handler.

    line 94
  2. Common:

    After this call, the thread becomes invisible to the debugger — breakpoints and single-stepping stop working silently.

    line 197
  3. Common:

    Check: PEB BeingDebugged, NtGlobalFlag, HeapFlags

    line 403
  4. Common:

    Check: NtQueryInformationProcess (all classes)

    line 404
  5. Distinctive:

    If ALL of these are true, stop trying Frida and switch to emulation:

    line 540

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

SKILL: Anti-Debugging Techniques — Detection & Bypass Playbook

AI LOAD INSTRUCTION: Expert anti-debug techniques across Linux and Windows. Covers ptrace, PEB flags, NtQueryInformationProcess, timing attacks, signal-based detection, TLS callbacks, VEH tricks, and all corresponding bypass methods. Base models often miss the distinction between user-mode and kernel-mode detection and the correct patching strategy for each.

0. RELATED ROUTING

  • code-obfuscation-deobfuscation when the binary also uses control flow flattening, VM protection, or string encryption
  • vm-and-bytecode-reverse when the anti-debug sits inside a custom VM dispatcher
  • symbolic-execution-tools when you want to symbolically skip anti-debug checks entirely

Advanced Reference

Also load ANTI_DEBUG_MATRIX.md when you need:

  • Complete cross-reference matrix of technique × OS × detection method × bypass method
  • Per-technique reliability ratings and false-positive notes
  • Tool compatibility chart (GDB, x64dbg, WinDbg, Frida, ScyllaHide)

Quick bypass picks

Detection ClassFirst BypassBackup
ptrace-based (Linux)LD_PRELOAD hook ptrace() → return 0Kernel module to hide tracer
PEB.BeingDebugged (Windows)Patch PEB byte at fs:[0x30]+0x2ScyllaHide auto-patch
Timing check (rdtsc)Conditional BP after rdtsc, fix registersFrida hook rdtsc return
IsDebuggerPresentNOP the call / hook return 0x64dbg built-in hide
INT 2D / UD2 exceptionSet VEH to handle gracefullyTitanHide driver

1. LINUX ANTI-DEBUG TECHNIQUES

1.1 ptrace(PTRACE_TRACEME)

The classic self-attach: a process calls ptrace(PTRACE_TRACEME, 0, 0, 0). If a debugger is already attached, the call fails (returns -1).

if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
    exit(1); // debugger detected
}

Bypass methods:

MethodHow
LD_PRELOAD shimCompile shared lib: long ptrace(int r, ...) { return 0; } and set LD_PRELOAD
Binary patchNOP the ptrace call or patch return value check
GDB catchcatch syscall ptrace → modify $rax to 0 on return
Kernel moduleHook sys_ptrace to allow multiple tracers

1.2 /proc/self/status — TracerPid

FILE *f = fopen("/proc/self/status", "r");
// parse TracerPid: if non-zero → debugger attached

Bypass: Mount a FUSE filesystem over /proc/self, or LD_PRELOAD hook fopen/fread to filter TracerPid to 0.

1.3 Timing Checks (rdtsc / clock_gettime)

Measures elapsed time between two points; debugger single-stepping causes noticeable delay.

rdtsc
mov ebx, eax       ; save low 32 bits
; ... protected code ...
rdtsc
sub eax, ebx
cmp eax, 0x1000    ; threshold
ja  debugger_detected

Bypass: Set hardware breakpoint after second rdtsc, modify eax to pass the comparison. Or use Frida to replace the timing function.

1.4 Signal-Based Detection (SIGTRAP)

volatile int caught = 0;
void handler(int sig) { caught = 1; }
signal(SIGTRAP, handler);
raise(SIGTRAP);
if (!caught) exit(1); // debugger swallowed the signal

When a debugger is attached, SIGTRAP is consumed by the debugger rather than delivered to the handler. Bypass: In GDB, use handle SIGTRAP nostop pass to forward the signal.

1.5 /proc/self/maps & LD_PRELOAD Detection

Checks for injected libraries or memory regions characteristic of debuggers/instrumentation.

FILE *f = fopen("/proc/self/maps", "r");
while (fgets(buf, sizeof(buf), f)) {
    if (strstr(buf, "frida") || strstr(buf, "LD_PRELOAD"))
        exit(1);
}

Bypass: Hook fopen("/proc/self/maps") to return a filtered version, or rename Frida's agent library.

1.6 Environment Variable Checks

Some protections check for LD_PRELOAD, LINES, COLUMNS (set by GDB's terminal), or debugger-specific env vars.

Bypass: Unset suspicious env vars before launch, or hook getenv().


2. WINDOWS ANTI-DEBUG TECHNIQUES

2.1 IsDebuggerPresent / CheckRemoteDebuggerPresent

if (IsDebuggerPresent()) ExitProcess(1);

BOOL debugged = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &debugged);
if (debugged) ExitProcess(1);

Bypass: Hook kernel32!IsDebuggerPresent to return 0, or patch PEB directly.

2.2 PEB Flags

FieldOffset (x64)Debugged ValueNormal Value
BeingDebuggedPEB+0x0210
NtGlobalFlagPEB+0xBC0x70 (FLG_HEAP_*)0
ProcessHeap.FlagsHeap+0x400x400000620x00000002
ProcessHeap.ForceFlagsHeap+0x440x400000600
mov rax, gs:[0x60]    ; PEB
movzx eax, byte [rax+0x02]  ; BeingDebugged
test eax, eax
jnz debugger_detected

Bypass: Zero all four fields. ScyllaHide does this automatically.

2.3 NtQueryInformationProcess

InfoClassValueDebugged Return
ProcessDebugPort0x07Non-zero port
ProcessDebugObjectHandle0x1EValid handle
ProcessDebugFlags0x1F0 (inverted!)

Bypass: Hook ntdll!NtQueryInformationProcess to return clean values per info class.

2.4 Hardware Breakpoint Detection

CONTEXT ctx;
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ctx);
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3)
    ExitProcess(1);

Bypass: Hook GetThreadContext to zero DR0–DR3, or use NtSetInformationThread(ThreadHideFromDebugger) preemptively (ironically, the anti-debug technique itself).

2.5 INT 2D / INT 3 / UD2 Exception Tricks

INT 2D is the kernel debug service interrupt. Without a debugger, it raises STATUS_BREAKPOINT; with a debugger, behavior differs (byte skipping).

xor eax, eax
int 2dh
nop          ; debugger may skip this byte
; ... divergent execution path ...

Bypass: Handle in VEH or patch the interrupt instruction.

2.6 TLS Callbacks

TLS callbacks execute before main() / WinMain(). Anti-debug checks placed here run before the debugger's initial break.

Bypass: In x64dbg, set "Break on TLS Callbacks" option. In WinDbg, use sxe ld to break on module load.

2.7 NtSetInformationThread(ThreadHideFromDebugger)

NtSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);

After this call, the thread becomes invisible to the debugger — breakpoints and single-stepping stop working silently.

Bypass: Hook NtSetInformationThread to NOP when ThreadInfoClass == 0x11.

2.8 VEH-Based Detection

Registers a Vectored Exception Handler that checks EXCEPTION_RECORD for debugger-specific behavior (single-step flag, guard page violations with debugger semantics).

Bypass: Understand the VEH logic and ensure the exception chain behaves identically to non-debugged execution.


3. ADVANCED MULTI-LAYER TECHNIQUES

3.1 Self-Debugging (fork + ptrace)

The process forks a child that attaches to the parent via ptrace. If an external debugger is already attached, the child's ptrace fails.

pid_t child = fork();
if (child == 0) {
    if (ptrace(PTRACE_ATTACH, getppid(), 0, 0) == -1)
        kill(getppid(), SIGKILL);
    else
        ptrace(PTRACE_DETACH, getppid(), 0, 0);
    _exit(0);
}
wait(NULL);

Bypass: Patch the fork() return or kill/detach the watchdog child.

3.2 Multi-Process Debugging Detection

Parent and child cooperatively check each other's debug state, creating a mutual-watch pattern.

Bypass: Attach to both processes (GDB follow-fork-mode, or two debugger instances).

3.3 Timing-Based with Multiple Checkpoints

Distributes timing checks across multiple functions, comparing cumulative drift. Single patches fail because the total still exceeds threshold.

Bypass: Frida Interceptor.replace all timing sources (rdtsc, clock_gettime, QueryPerformanceCounter) to return controlled values.

3.4 Nanomite / INT3 Patching

Original conditional jumps are replaced with INT3 (0xCC). A parent debugger process handles each INT3, evaluates the condition, and sets the child's EIP accordingly.

Bypass: Reconstruct the original jump table by tracing all INT3 handlers, then patch the binary.


4. COUNTERMEASURE TOOLS

ToolPlatformCapability
ScyllaHideWindows (x64dbg/IDA/OllyDbg)Auto-patches PEB, hooks NtQuery*, hides threads, fixes timing
TitanHideWindows (kernel driver)Kernel-level hiding for all user-mode checks
FridaCross-platformScript-based hooking of any function, timing spoofing
LD_PRELOAD shimsLinuxReplace ptrace, getenv, fopen at load time
GDB scriptsLinuxcatch syscall, conditional BP, register fixup
QilingCross-platformFull-system emulation, bypass all hardware checks

5. SYSTEMATIC BYPASS METHODOLOGY

Step 1: Static analysis — identify anti-debug calls
  └─ Search for: ptrace, IsDebuggerPresent, NtQuery, rdtsc,
     GetTickCount, SIGTRAP, INT 2D, TLS directory entries

Step 2: Classify each check
  ├─ API-based → hook or patch the call
  ├─ Flag-based → patch PEB/proc fields
  ├─ Timing-based → spoof time source
  ├─ Exception-based → forward/handle exception correctly
  └─ Multi-process → handle both processes

Step 3: Apply bypass (order matters)
  1. Load ScyllaHide / set LD_PRELOAD (covers 80% of checks)
  2. Handle TLS callbacks (break before main)
  3. Patch remaining custom checks (Frida or binary patch)
  4. Verify: run with breakpoints, confirm no premature exit

Step 4: Validate bypass completeness
  └─ Set BP on ExitProcess/exit/_exit — if hit unexpectedly,
     a check was missed → trace back from exit call

6. DECISION TREE

Binary exits/crashes under debugger?
│
├─ Crashes immediately before main?
│  └─ TLS callback anti-debug
│     └─ Enable TLS callback breaking in debugger
│
├─ Crashes at startup?
│  ├─ Linux: check for ptrace(TRACEME)
│  │  └─ LD_PRELOAD hook or NOP patch
│  └─ Windows: check IsDebuggerPresent / PEB
│     └─ ScyllaHide or manual PEB patch
│
├─ Crashes after some execution?
│  ├─ Consistent crash point → API-based check
│  │  ├─ NtQueryInformationProcess → hook return values
│  │  ├─ /proc/self/status → filter TracerPid
│  │  └─ Hardware BP detection → hook GetThreadContext
│  │
│  ├─ Variable crash point → timing-based check
│  │  └─ Hook rdtsc / QueryPerformanceCounter
│  │
│  └─ Crash on breakpoint hit → exception-based check
│     ├─ INT 2D / INT 3 trick → handle in VEH
│     └─ SIGTRAP handler → GDB: handle SIGTRAP pass
│
├─ Debugger loses control silently?
│  └─ ThreadHideFromDebugger
│     └─ Hook NtSetInformationThread
│
├─ Child process detects and kills parent?
│  └─ Self-debugging (fork+ptrace)
│     └─ Patch fork() or handle both processes
│
└─ All basic bypasses applied but still detected?
   └─ Multi-layer / custom checks
      ├─ Use Frida for comprehensive API hooking
      ├─ Full emulation with Qiling
      └─ Trace all calls to exit/abort to find remaining checks

7. CTF & REAL-WORLD PATTERNS

Common CTF Anti-Debug Patterns

PatternFrequencyQuick Bypass
Single ptrace(TRACEME)Very commonLD_PRELOAD one-liner
IsDebuggerPresent + NtGlobalFlagCommonScyllaHide
rdtsc timing in loopModeratePatch comparison threshold
signal(SIGTRAP) + raiseModerateGDB signal forwarding
fork + ptrace watchdogRare but trickyKill child or patch fork
Nanomite INT3 replacementRare (advanced)Reconstruct jump table

Real-World Protections

ProtectorPrimary Anti-DebugRecommended Tool
VMProtectPEB + timing + driver-levelTitanHide + ScyllaHide
ThemidaMulti-layer PEB + SEH + timingScyllaHide + manual patches
Enigma ProtectorIsDebuggerPresent + CRC checksx64dbg + ScyllaHide
UPX (custom)Usually none (just packing)Standard unpack
Custom (malware)Varies widelyFrida + Qiling for analysis

8. QUICK REFERENCE — BYPASS CHEAT SHEET

Linux One-Liners

# LD_PRELOAD anti-ptrace
echo 'long ptrace(int r, ...) { return 0; }' > /tmp/ap.c
gcc -shared -o /tmp/ap.so /tmp/ap.c
LD_PRELOAD=/tmp/ap.so ./target

# GDB: catch and bypass ptrace
(gdb) catch syscall ptrace
(gdb) commands
> set $rax = 0
> continue
> end

Frida Anti-Debug Bypass (Cross-Platform)

// Hook IsDebuggerPresent (Windows)
Interceptor.replace(
  Module.getExportByName('kernel32.dll', 'IsDebuggerPresent'),
  new NativeCallback(() => 0, 'int', [])
);

// Hook ptrace (Linux)
Interceptor.replace(
  Module.getExportByName(null, 'ptrace'),
  new NativeCallback(() => 0, 'long', ['int', 'int', 'pointer', 'pointer'])
);

// Timing spoof
Interceptor.attach(Module.getExportByName(null, 'clock_gettime'), {
  onLeave(retval) {
    // manipulate timespec to hide debugger delay
  }
});

x64dbg ScyllaHide Quick Setup

  1. Plugins → ScyllaHide → Options
  2. Check: PEB BeingDebugged, NtGlobalFlag, HeapFlags
  3. Check: NtQueryInformationProcess (all classes)
  4. Check: NtSetInformationThread (HideFromDebugger)
  5. Check: GetTickCount, QueryPerformanceCounter
  6. Apply → restart debugging session

9. iOS ANTI-DEBUG & ANTI-JAILBREAK DETECTION

Detection Vectors

#Detection MethodAPI/MechanismWhat It ChecksReliability
1sysctl P_TRACEDsysctl(CTL_KERN, KERN_PROC, KERN_PROC_PID, pid)kp_proc.p_flag & P_TRACEDHigh
2ptrace denyptrace(PT_DENY_ATTACH, 0, 0, 0)Prevents debugger attachmentHigh
3File existenceaccess() / stat() / NSFileManagerCydia.app, sshd, apt, bash, etc.Medium (easy to hook)
4URL schemeUIApplication canOpenURL:cydia://, sileo://Low (easy to hook)
5Dylib enumeration_dyld_image_count() + _dyld_get_image_name()Substrate, Frida dylibs in load listHigh
6Environment varsgetenv("DYLD_INSERT_LIBRARIES")Injected library pathsMedium
7Sandbox integritysandbox_check(pid, "process-fork", ...)Fork allowed = jailbrokenMedium
8Symbol resolutiondlsym(RTLD_DEFAULT, "MSHookFunction")Substrate/Frida symbols in memoryHigh
9Integrity checkCode hash validation at runtimeDetects inline hooks / patchesVery High
10Port scanConnect to localhost:27042 (Frida default)Frida server runningMedium
11Exception-basedSet invalid instruction, catch signalDebugger alters signal deliveryMedium
12Timing checkmach_absolute_time() between instructionsDebugger slows executionLow (unstable)

Bypass Quick Reference

DetectionBypass MethodFrida Script Pattern
sysctl P_TRACEDHook sysctl, clear P_TRACED flag in resultInterceptor.attach(Module.findExportByName(null, "sysctl"), ...)
ptrace denyHook ptrace, return 0 when PT_DENY_ATTACHInterceptor.attach(Module.findExportByName(null, "ptrace"), { onEnter: if(args[0]==31) ... })
File checksHook access/stat/NSFileManager, return ENOENT for blacklisted pathsSee mobile-dynamic-re §4.1
Dylib enumHook _dyld_get_image_name, filter suspicious namesFilter strings containing "frida", "substrate", "substitute"
getenvHook getenv, return NULL for DYLD_* keysInterceptor.attach(Module.findExportByName(null, "getenv"), ...)
dlsymHook dlsym, return NULL for hook-framework symbolsReturn NULL for "MSHookFunction", "frida_agent_main"
Integrity checkAvoid inline hooks (use Stalker), or hook the check function itselfLoad re-escalation if integrity check is comprehensive
Port scanUse non-default Frida port (-l 0.0.0.0:12345)Configuration change, not code

iOS Bypass Priority (覆盖率分析)

PriorityChecks to BypassEffortCovers
1st (do these first)File existence + URL scheme + getenv5 min~60% of apps
2ndDylib enum + dlsym + sysctl15 min~85% of apps
3rdIntegrity checks + timing30-60 min~95% of apps
4thCustom/proprietary detectionHoursRemaining 5%

10. ANDROID ANTI-DEBUG & ANTI-ROOT DETECTION

Detection Vectors

#Detection MethodMechanismWhat It ChecksReliability
1TracerPidRead /proc/self/statusTracerPid != 0 means being tracedHigh
2/proc/self/mapsRead process memory mapLook for frida-agent, xposed entriesHigh
3su binaryaccess("/system/bin/su") / which susu exists = rootedMedium
4Root packagesCheck installed packagesSuperSU, Magisk Manager, KernelSUMedium
5Build propsandroid.os.Build fieldstest-keys, userdebugLow
6SELinux statusgetenforcePermissive = likely rootedMedium
7Mount pointsParse /proc/mountsMagisk overlay mountsHigh (hard to hide)
8Native fork detectionfork() + ptrace(PTRACE_TRACEME)If ptrace fails, debugger attachedHigh
9Thread enumeration/proc/self/task/Unexpected threads (Frida agent)Medium
10Socket scanEnumerate /proc/net/tcpFrida default port 27042Medium
11Java debuggerandroid.os.Debug.isDebuggerConnected()JDWP attachedHigh
12Signal handlingSIGTRAP delivery timingDebugger intercepts signalsMedium

Bypass Quick Reference

DetectionBypass MethodImplementation
TracerPidHook open("/proc/self/status"), replace TracerPid lineReturn "TracerPid:\t0" in hooked read
/proc/self/mapsHook file read, filter frida/xposed linesString filtering in read() result
su binaryHook access(), return -1 for su pathsInterceptor on access syscall
Root packagesHook PackageManager.getPackageInfoJava.use → return PackageNotFoundException
Build propsHook SystemProperties.getReturn "release-keys" for ro.build.tags
MagiskUse MagiskHide / Zygisk DenyListConfiguration, not code
isDebuggerConnectedHook method, return falseJava.use("android.os.Debug").isDebuggerConnected.implementation = () => false
Fork+ptraceHook ptrace, return 0Native Interceptor

Android Bypass Priority

PriorityChecksEffortCovers
1stsu/root packages + Build props5 min~50% of apps
2ndTracerPid + /proc/self/maps10 min~75% of apps
3rdMagisk detection + mount points15 min~90% (use Zygisk)
4thCustom native checks30+ minRemaining

11. FRIDA DETECTION & COUNTERMEASURES

How Apps Detect Frida

#Detection VectorMechanismDifficulty to Bypass
1Default port (27042)TCP connect to localhost:27042Easy (change port)
2frida-server process nameEnumerate /proc, check cmdlineEasy (rename binary)
3D-Bus protocolSend D-Bus AUTH message on connectionsMedium
4Library mapping/proc/self/maps contains "frida-agent"Medium (hook read)
5Thread namingfrida threads have distinctive namesMedium (hook pthread)
6Inline hook detectionCheck function prologues for hook trampolinesHard
7Memory scanningScan for known Frida gadget strings/signaturesHard
8Code page permissionsRWX pages indicate JIT (Frida Stalker)Medium
9Exception port (iOS)Mach exception ports registered by FridaHard
10Symbol scanningdladdr/dlsym for Frida internal symbolsMedium

Stealth Configuration

# Basic stealth (defeats 70% of detection)
frida -l script.js -H device:12345 --runtime=v8   # non-default port

# Rename frida-server (defeats process name scan)
cp frida-server /usr/bin/fs-daemon
/usr/bin/fs-daemon -l 0.0.0.0:12345

# Use frida-gadget (inject into app, no server process)
# Most stealthy — no external process, no default port

Advanced: Avoid Hook Detection

TechniqueWhen to UseTrade-off
Stalker (code tracing)When target checks for inline hooksSlower, but zero code modification
Spawn + early hookWhen timing-based detection runs at initMust hook before detection code runs
LD_PRELOAD injectionWhen Frida itself is detectedDifferent injection vector
Kernel-level (kprobes)Last resort, all userland methods failedRequires root + kernel module
Emulation (→ emulation-re)When all dynamic approaches blockedDifferent paradigm entirely

Decision: When to Switch from Dynamic to Emulation

If ALL of these are true, stop trying Frida and switch to emulation:

  1. App uses integrity checks (can't modify code)
  2. App detects Frida by memory scanning (can't inject)
  3. App uses anti-tamper on detection functions themselves (can't bypass)

→ Load emulation-re for offline approach → Load re-escalation for systematic upgrade

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.