Back to Cheat Sheets

🔬 Reverse EngineeringMobile 2016

OWASP Mobile Top 10 2016 - M9

MEDIUM RISK

📋 What Is It?

Reverse Engineering is the analysis of a shipped application binary to understand how it works from the inside. An attacker who never saw your source takes the .apk, .aab, or .ipa you published and recovers source-equivalent code, strings, resources, algorithms, keys, endpoints, and business logic. The app you distribute is the documentation of how it works - reverse engineering is just reading it.

This is not an exotic exploit; it is the default state of any binary shipped to a device you do not control. The core truth of M9: any secret, key, algorithm, or logic inside the client can be recovered. Obfuscation raises the cost and time of that recovery - it never makes it impossible. The only reliable protection for a secret is not putting it in the client at all.

M9 OWASP Rank 2016
Always Possible
Cost Is The Only Lever

⚠️ Common Exploits

  • Decompilation: Dalvik/ART bytecode back to readable Java/Kotlin
  • Native Disassembly: .so/dylib decompiled to C-like pseudo-code
  • String Mining: API keys, URLs, feature flags, and messages pulled from resources
  • Endpoint Mapping: the full back-end attack surface, including "hidden" routes
  • Algorithm Recovery: signing, DRM, and anti-fraud logic understood then reimplemented
  • Runtime Introspection: hooking to read values that are only assembled in memory
  • Clone Building: a functional third-party client that speaks your API

🔴 Attack Flow

1. Download the app package from the store or the device
↓
2. Unpack resources and decompile bytecode; disassemble native libraries
↓
3. Grep for keys, endpoints, and interesting identifiers
↓
4. Hook the running app to fill in whatever static analysis missed
↓
5. IMPACT: Secrets recovered, logic understood, API abused directly!

❌ Vulnerable Code

// Bad: secrets compiled into the app - recoverable in minutes const val API_KEY = "sk_live_51H8xQ2eZvKYlo2C…" const val HMAC_SECRET = "prod-signing-secret-2024" <!-- Bad: same problem, different file --> <string name="maps_key">AIzaSy…</string> // Bad: "hidden" endpoints documented by the binary itself val ADMIN_URL = "https://internal-admin.example.com/api/v1" // Bad: proprietary logic assumed to be private because it is compiled fun calculateFraudScore(tx: Tx): Double { /* full model, in the client */ } // Bad: obfuscation treated as the security control // proguard-rules.pro: -dontobfuscate ← and even with it on, secrets remain extractable

✅ Secure Code

// Good: the client holds no long-lived secret val token = auth.accessToken() // short-lived, user-scoped, server-issued, revocable api.call(token) // Good: signing and scoring happen where the code is not published // client sends the transaction; the SERVER computes the score and decides val decision = api.evaluate(tx) // Good: third-party keys are proxied, scoped, and restricted // app → your backend → vendor API (key never ships; usage is attributable and revocable) // where a key must ship, restrict it by package name + signing certificate + referrer // Good: raise the cost - as defence in depth, not as the control // build.gradle buildTypes { release { isMinifyEnabled = true // R8: shrink + obfuscate isDebuggable = false proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt")) } } // strip native symbols; keep sensitive routines server-side rather than "hidden" in C

✓ Prevention Checklist

  • Assume the binary is fully readable - design as if the source were published
  • Ship no API keys, signing secrets, or credentials in the app
  • Keep proprietary algorithms and risk logic on the server
  • Issue short-lived, user-scoped, revocable tokens instead of static keys
  • Proxy third-party APIs through your own back end
  • Restrict any key that must ship (package name, signing certificate, referrer, scope)
  • Enable R8/ProGuard shrinking and obfuscation on release builds
  • Strip symbols and disable debugging in release artifacts
  • Scan built artifacts for secrets in CI, before publishing
  • Rotate any credential that ever shipped in a client
  • Assume every endpoint the app can reach will be called directly - secure it accordingly

🔍 Detection & Tools

Analysis (use it on yourself):

jadx apktool Ghidra / IDA class-dump Frida

Hardening & Scanning:

R8 / ProGuard MobSF gitleaks / trufflehog Play Integrity

How to Test:

  • Decompile your own release build and read it as an attacker would
  • Grep strings and resources for keys, tokens, and internal hostnames
  • Check whether class and method names survived obfuscation
  • Hook the app and dump values assembled at runtime
  • Call every discovered endpoint directly, without the app

🌍 Real-World

These are documented incident classes - recurring patterns rather than single CVEs.

  • Cloud keys extracted from apps: long-lived provider credentials found in shipped binaries and used to access back-end storage directly.
  • Internal endpoints discovered: "hidden" admin or partner hosts named in the client and then attacked directly.
  • Anti-fraud logic reimplemented: client-side scoring recovered and replicated so an attacker can predict and avoid detection.
  • Third-party clients: unofficial apps built by reverse-engineering the private API, bypassing rate limits and terms.
  • Signing secrets recovered: HMAC keys in the binary let attackers forge requests that look authentic.

📌 Quick Tips

  • DO NOT ship secrets in a mobile app
  • DO NOT treat obfuscation as protection
  • DO NOT assume compiled means private
  • DO keep valuable logic on the server
  • DO use short-lived, revocable tokens
  • DO decompile your own build before shipping

📜 Compliance

Related Standards:

  • OWASP Mobile Top 10 2016 - M9
  • Maps to M7:2024 Insufficient Binary Protections
  • OWASP MASVS RESILIENCE-1, RESILIENCE-3
  • CWE-798 Use of Hard-coded Credentials
  • CWE-656 Reliance on Security Through Obscurity
  • CWE-1300 Improper Protection of Physical Side Channels (related resilience concerns)