# Anti Tampering

> Anti-tampering on mobile — signature checks, runtime application self-protection (RASP), and realistic return on investment. Use to decide which integrity controls are worth shipping.

- Skill: `almasumdev/anti-tampering` (Agent Skill)
- Install (CLI): `npx skillmds@latest add almasumdev/anti-tampering`
- Raw SKILL.md: https://api.skillmd.com/api/skills/almasumdev/anti-tampering/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: almasumdev (https://skillmd.com/u/almasumdev)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/almasumdev/anti-tampering

---


# 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

| Threat | Example | Who it hurts |
| ------ | ------- | ------------ |
| Re-signed binary | Pirated version with ads injected | Revenue, brand |
| Modified resources | Free-upgrade patch for IAP | Revenue |
| Frida instrumentation | Bypass a feature flag or auth check | Business logic integrity |
| Emulator / cloud device | Automated account fraud | Trust & safety |
| Debugger attached | Reverse engineering in progress | Secrets, 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](../../network/api-abuse-protection/SKILL.md).

### 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.

