YinkoShield

Knowledge Center / Mobile runtime attacks / mobile runtime attacks · 2026·02

Debugger attachment and runtime introspection

On Linux-derived platforms — Android included — the kernel exposes whether a debugger is attached to a process via /proc/[pid]/status's TracerPid. On iOS, sysctl with KERN_PROC reveals the P_TRACED flag in kp_proc.p_flag. Both are reliable, well-documented, and outside the reach of most user-space cloaking. The substrate reads them and signs the result.

[ debugger attachment — platform signals ] Android · /proc/[pid]/status Name: com.bank.app TracerPid: 0 ← clean ↓ ptrace(PTRACE_ATTACH, pid) Name: com.bank.app TracerPid: 9183 ← attached also: gdbserver, lldb, frida-server all set TracerPid in /proc iOS · sysctl KERN_PROC struct kinfo_proc info; sysctl(KERN_PROC, ...); flags = info.kp_proc.p_flag; flags & P_TRACED ? if (flags & P_TRACED) { // debugger attached } substrate signal: runtime.environment { debugger.attached: bool · platform · tracer_pid }
On Android, /proc/[pid]/status's TracerPid field reveals an attached debugger. On iOS, the P_TRACED flag in kp_proc.p_flag is the equivalent.

1. Mechanism

Debugger attachment on Linux-derived systems is mediated by ptrace(2) [1]. A debugger calls ptrace(PTRACE_ATTACH, pid) or, on a child it spawned, PTRACE_TRACEME. Once attached, the debugger can read and write the target’s registers, memory, and control its execution via signals.

The same primitive underlies:

  • gdb and lldb (interactive debuggers).
  • gdbserver (remote debugger backend).
  • frida-server and Frida’s spawn-and-attach flow (uses ptrace for in-process injection, then drops the trace).
  • strace-class tracing tools.

On iOS and macOS, ptrace(2) exists in a restricted form. The canonical anti-debug primitive used by hardened apps is ptrace(PT_DENY_ATTACH, 0, 0, 0), which sets the P_LNOATTACH bit and causes any subsequent debugger attach to fail. On modern iOS, the kinfo_proc struct is treated as private API and BSD-layer process introspection is sandbox-restricted to self-introspection; production anti-debug uses PT_DENY_ATTACH plus a csops-style code-signing-flags check and Mach exception-port observation through the task port subsystem, with the P_TRACED flag recording the attached condition.

2. Where in the runtime it operates

Two platform-exposed surfaces:

  • Android — /proc/<pid>/status. The kernel exposes a per-process status file [2] with a TracerPid: line. The value is 0 when no debugger is attached and the PID of the tracer when one is. The file is readable by the process itself for /proc/self/status.
  • iOS — PT_DENY_ATTACH and csops. Hardened iOS apps call ptrace(PT_DENY_ATTACH, 0, 0, 0) early to refuse subsequent attaches; complementary csops checks read code-signing flags including CS_DEBUGGED to confirm the process state. Direct enumeration of other processes’ trace state via sysctl(KERN_PROC, ...) is sandbox-restricted on modern iOS; the kinfo_proc.kp_proc.p_flag P_TRACED bit is observable for self-introspection only.

Both signals are platform-truth: the kernel knows the trace state because the kernel mediates trace operations. On Android, user-space cloaking via libc hooks does not change what the kernel reports — though hooks installed at the syscall layer (requiring root or Zygisk) can rewrite the read.

3. Which checkpoints it bypasses

  • Play Integrity. A device with a debugger attached to an app on a non-debug build is unusual but not necessarily detected by Play Integrity verdicts. ptrace from another app to a release-build app is blocked on stock Android by ro.debuggable=0, by Yama’s kernel.yama.ptrace_scope (typically set to 1 on shipping Android, restricting ptrace to descendant processes), and by SELinux denial of ptrace for untrusted_app; cross-app attach therefore typically requires root, which itself fails MEETS_STRONG_INTEGRITY via the RootOfTrust block.
  • App Attest. Attestation is unaffected by trace state.

The attack here is not on the checkpoints. It is on the app’s runtime privacy — the debugger reads the app’s memory, including credentials, session tokens, in-flight payload values.

4. Which signals make it observable

runtime.environment. The Trusted Runtime Primitive reads /proc/self/status (Android) or executes the iOS self- introspection probe (PT_DENY_ATTACH armed at startup; csops flags read; kinfo_proc.kp_proc.p_flag for the calling process) and signs the result. On Android, the substrate also cross-checks TracerPid via direct syscall to bypass any libc-level rewrite. Execution Evidence Infrastructure (EEI) — the device-identity infrastructure layer for banking and payments — signs the pair so the operator’s verifier can compare the libc-routed and syscall-direct readings; divergence is itself a signal.

5. Evidence Token shape when observed

The following example is illustrative; field names, type values, and schema are defined in YEI-001 §4 (available through the spec-access process).

{
  "ev": [{
    "ts":   "2026-06-15T10:23:14Z",
    "class": "runtime.environment",
    "type":  "debugger.attached",
    "data": {
      "platform":         "android",
      "tracer_pid_libc":  9183,
      "tracer_pid_syscall": 9183,
      "consistent":       true
    }
  }]
}

consistent: true means the libc-level read and the direct syscall agree. A false here would indicate a hook is rewriting the read — itself a strong signal.

6. Cross-references

7. External references

[1] Linux man-pages. ptrace(2). man7.org/linux/man-pages/man2/ptrace.2.html. Cited 2026-02-04.

[2] Linux man-pages. proc(5) — /proc/[pid]/status. man7.org/linux/man-pages/man5/proc.5.html. Cited 2026-02-04.

[3] Apple Developer. ptrace(2) — PT_DENY_ATTACH and the csops code-signing flags. developer.apple.com (Mac OS X / iOS man pages: ptrace.2, csops). Cited 2026-02-04.