almasumdev/awesome-mobile-security-agent-skills

anti-tampering

Anti-tampering on mobile — signature checks, runtime application self-protection (RASP), and realistic return on investment.

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

Anti-Tampering on Mobile

Instructions

Anti-tampering controls are a spectrum. Pick the layers that match your actual threat model and compliance needs — not a generic checklist.

1. Tampering We Care About

ThreatExampleWho it hurts
Re-signed binaryPirated version with ads injectedRevenue, brand
Modified resourcesFree-upgrade patch for IAPRevenue
Frida instrumentationBypass a feature flag or auth checkBusiness logic integrity
Emulator / cloud deviceAutomated account fraudTrust & safety
Debugger attachedReverse engineering in progressSecrets, IP

You probably don't care about all of these. Pick two or three.

2. Signature / Bundle ID Checks

Cheap, high ROI. The app verifies at runtime that it's still signed by your key / has your bundle ID.

kotlin
fun isGenuine(ctx: Context): Boolean {
    val info = ctx.packageManager.getPackageInfo(
        ctx.packageName,
        PackageManager.GET_SIGNING_CERTIFICATES,
    )
    val expectedSha256 = "AA:BB:CC:..." // pinned at build time
    val signers = info.signingInfo?.apkContentsSigners ?: return false
    return signers.any { sha256Hex(it.toByteArray()) == expectedSha256 }
}
swift
func isGenuine() -> Bool {
    guard let bundleId = Bundle.main.bundleIdentifier else { return false }
    guard bundleId == "com.example.app" else { return false }
    // Optionally validate the embedded provisioning profile / code signature
    return hasValidCodeSignature()
}

Don't just early-return false — it tells the attacker exactly where to patch. Feed the signal into a server-side anomaly score instead.

3. Resource / Asset Integrity

For apps where specific files must not be swapped (game balance JSON, model weights, license files):

  • Ship a manifest of sha256 digests signed with a key you control.
  • At startup, recompute and compare.
  • Store only the expected digests in R8-obfuscated code, not the full JSON.

4. RASP — Where It Pays, Where It Doesn't

RASP (Runtime Application Self-Protection) libraries attempt to detect Frida, debuggers, Magisk modules, Xposed, and various hooking frameworks.

  • Worth it for: high-value financial, gambling, trading, DRM, and some compliance-driven (PSD2) apps.
  • Rarely worth it for: most SaaS, productivity, content, and social apps. RASP is noisy, adds startup cost, blocks legitimate users (niche ROMs, custom kernels), and is an arms race.
  • Never a substitute for: server-side authorization.

If you ship RASP:

  • Treat detections as signals, not kill switches. Feed them into risk scoring.
  • Test against a representative device lab before shipping — false positives on Samsung One UI / Xiaomi MIUI / Huawei EMUI are common.
  • Review the vendor's CVE history and support responsiveness.

5. Debugger Detection

Cheap to add, cheap to bypass. Still worth it as one signal.

Android:

kotlin
fun debuggerAttached(): Boolean =
    Debug.isDebuggerConnected() || Debug.waitingForDebugger() ||
    (ApplicationInfo.FLAG_DEBUGGABLE and context.applicationInfo.flags) != 0

iOS:

swift
func isBeingDebugged() -> Bool {
    var info = kinfo_proc()
    var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
    var size = MemoryLayout.stride(ofValue: info)
    sysctl(&mib, UInt32(mib.count), &info, &size, nil, 0)
    return (info.kp_proc.p_flag & P_TRACED) != 0
}

Ship these disabled in debug builds and only in release binaries.

6. Emulator / Cloud Device Signals

Signals to combine (none are definitive):

  • Build.FINGERPRINT contains "generic" / "emulator" / "vbox".
  • /proc/cpuinfo, /dev/socket/qemud.
  • Sensor availability and realism (a perfect, unchanging accelerometer reading screams emulator).
  • iOS: simulator-only paths under /Applications/Xcode.app/.

Prefer Play Integrity (deviceRecognitionVerdict) / App Attest — they already do this with better fidelity. See api-abuse-protection.

7. Server-Side Is Where It Matters

Whatever the client detects, it reports. The server decides:

  • If a binary reports "signature invalid" and pins fail and attestation is missing, raise risk score → require step-up auth, block the transaction, or silently flag for review.
  • Never trust a single client-side boolean. Assume the client can lie about any of these signals.

8. What Not to Do

  • Do not crash the app on tamper detection. A crash is a stack trace that points to the check.
  • Do not log "tamper detected" in plain text. Use an opaque error code.
  • Do not spread checks across 200 places "to make it harder" if you never test them — they'll rot and produce false positives in production.

Checklist

  • [ ] Signature / bundle ID is checked and the result is reported, not used as a kill switch.
  • [ ] Asset integrity manifest exists for files whose content must not change.
  • [ ] RASP, if used, is justified by threat model and tested on a real device lab.
  • [ ] Debugger detection is release-only and feeds a signal, not a crash.
  • [ ] Emulator detection uses Play Integrity / App Attest as the primary source.
  • [ ] Server-side risk scoring exists to weigh these signals together.
  • [ ] No anti-tamper code relies on string literals visible in the binary.
from this repository

More skills

All skills