Skip to content

MASTG-BEST-0074: Implementing Anti-Debugging Checks on iOS

Implement anti-debugging checks in iOS apps that handle high-risk flows, and run those checks at startup and before or during sensitive operations instead of relying on a single startup check.

Use anti-debugging as a defense-in-depth control. A local attacker who controls the device or app package can eventually bypass client-side checks through patching, instrumentation, or a modified runtime. The goal is to raise attacker effort, make bypasses harder to maintain, and feed risk signals into broader app and backend policy.

Use Layered Signals

Combine multiple signal types from Anti-Debugging Detection instead of depending on one API. For example:

  • Use ptrace and PT_DENY_ATTACH where this is acceptable for the app's distribution model.
  • Use process-state checks such as Apple's archived sysctl debugger detection example as one reactive signal.
  • Add parent-process or Mach exception port checks where they fit the app's threat model.

Validate the implementation against the app's distribution requirements. Some low-level APIs used by anti-debugging implementations are not part of the public iOS SDK, and apps distributed through the App Store must comply with Apple's public API and review requirements.

Check Sensitive Flows

Run anti-debugging checks close to security-relevant operations, such as:

  • key unwrapping or signing
  • payment approval
  • authentication or step-up authorization
  • secrets access
  • premium feature enforcement
  • high-risk transaction submission

Avoid tight polling loops that waste battery or degrade performance. Prefer checkpoint-based checks before sensitive actions, with short periodic checks only while sensitive flows are active.

Harden the Check and Response

Obfuscate anti-debugging logic and avoid centralizing every check behind a single named function. A single dispatch point is easier to hook or patch than several distributed checks.

When the app detects a debugger, use a response that fits the risk of the protected flow. For example, terminate the sensitive operation, clear transient secrets from memory, require reauthentication, reduce functionality, or send a generic risk signal to the backend. Avoid exposing detailed detection reasons to the client because they help attackers tune bypasses.

Test the checks in release builds. Debug-only code paths, checks that run only once at startup, or responses that can be bypassed by hooking one function provide limited resilience.

Tests

MASTG-TEST-0401: References to Debugging Detection APIs MASTG-TEST-0402: Runtime Use of Debugging Detection APIs