All posts

Reverse Engineering a Mobile App's Private API

Introduction

A huge amount of the useful data and functionality in the world lives behind a mobile app and only a mobile app. No public API, no partner program — just an Android or iOS binary talking to a private backend over endpoints nobody documented on purpose.

Reverse engineering that private API is one of the most common jobs we do. The goal is narrow and concrete: recover exactly what the app sends, understand the parts that aren't plain HTTP (signatures, tokens, device attestation), and rebuild a client that the backend accepts as legitimate — running from your own infrastructure instead of a phone.

This post is the method, in order. It assumes you have the legal right to interoperate with the target; see the note at the end.

Step 0 — See the traffic at all

You cannot reverse what you cannot observe. The first task is putting a proxy — mitmproxy, Burp, or Charles — between the app and its backend, so every request and response is laid out in front of you.

The mechanics: run the proxy on your machine, point the device's Wi-Fi at it, and install the proxy's CA certificate on the device so it trusts the intercepting cert. On a stock setup, that's enough to read a browser's HTTPS. On a real app, it usually isn't — because of the next wall.

Step 1 — Certificate pinning, and how to get past it

Most serious apps pin their certificate: they ship a copy of the exact server cert (or its public key) inside the binary and refuse to talk to anything that presents a different one. Your proxy's CA is trusted by the OS, but the app doesn't care about the OS trust store — it compares against its own pinned copy. Result: a dead connection and no traffic.

The standard, non-destructive way through is Frida — a dynamic instrumentation toolkit that hooks into a running process and lets you replace functions at runtime. You don't patch the binary on disk; you intercept the pinning check in memory and force it to succeed.

On Android that means hooking the methods that validate the chain — javax.net.ssl.X509TrustManager, OkHttp's CertificatePinner.check(), or the native SSL_CTX_set_custom_verify for apps pinning in C. On iOS it's the SecTrustEvaluate family. The community-maintained "universal" unpinning scripts cover the common libraries in one shot:

# Android: spawn the app with a pinning-bypass script attached
frida -U -f com.target.app -l frida-universal-unpinning.js

When the app uses a bespoke or native pinning implementation, the universal script misses it and you drop into a debugger to find the exact check yourself — hook it, log its arguments, and return the value the app wants. That "find the one function that says no and make it say yes" loop is the recurring shape of the whole discipline.

Emulator vs. real device matters. Some apps detect the emulator (or root/jailbreak) and refuse to run. Physical devices with careful root hiding are more reliable for stubborn targets — another reason this is specialist work rather than a five-minute task.

Step 2 — Read the flow, isolate the hard parts

With pinning defeated, the traffic pours into your proxy. Now the same triage we run on every engagement: 90% of it is boring, 10% is the whole job.

The boring 90% is plain REST/GraphQL — you can read it and replay it. The 10% that matters is the fields your own HTTP client can't just copy:

  • Auth tokens — OAuth bearer tokens, refresh flows, session cookies. Where do they come from, and how do they rotate?
  • Request signatures — an X-Signature, X-Sign, or HMAC header computed over the request. Replaying it verbatim works for about five minutes; you need to generate it.
  • Device attestation — Play Integrity, DeviceCheck / App Attest, or a vendor SDK asserting "a real device and a real app sent this." The hard ceiling on some targets.
  • Timestamps and nonces — anti-replay values folded into the signature.

List these first. They decide feasibility and effort.

Step 3 — Reconstruct the signature

The signed header is where most private-API work concentrates. The header is easy to see and useless to copy, because it's a function of the request:

POST /api/v3/order HTTP/2
authorization: Bearer eyJhbGci...
x-timestamp: 1754870400
x-nonce: 7f3c9a12
x-signature: 5b2e...af91

To automate this, you have to find the code that produces x-signature and reimplement it. Two paths:

Static analysis. Decompile the app — jadx for Android turns the APK back into readable Java; for iOS you're in Ghidra/Hopper on the Mach-O. Search for the header name, the word sign, hmac, or the crypto imports, and trace backward to what gets hashed and with what key.

Dynamic analysis. Often faster: use Frida to hook the crypto primitive itself and print its inputs and outputs live while you use the app.

// Frida: watch every HMAC the app computes and dump what it signs
Java.perform(() => {
  const Mac = Java.use('javax.crypto.Mac')
  Mac.doFinal.overload('[B').implementation = function (input) {
    const out = this.doFinal(input)
    console.log('[HMAC] message =', bytesToUtf8(input))
    console.log('[HMAC] digest  =', bytesToHex(out))
    return out
  }
})

Once you can see the exact byte string being signed — usually some canonical concatenation like method + path + timestamp + nonce + body — and you've recovered the key, you reimplement it in your language of choice and prove it against ground truth: feed a captured request through your implementation and confirm it reproduces the captured signature byte-for-byte. If it matches, you understand it. If it doesn't, you don't yet — no guessing.

Step 4 — Match the transport, not just the payload

A perfect signature still gets blocked if the connection looks wrong. Mobile backends increasingly fingerprint the client at the TLS and HTTP/2 layer, exactly like web anti-bots do. An app built on OkHttp or NSURLSession has a specific ClientHello, cipher order, and HTTP/2 SETTINGS — and your stock HTTP library has a different one that screams "not this app."

This is the same problem we covered for browsers in TLS fingerprinting vs anti-bots, and the same tooling solves it: tls-client ships mobile profiles like Okhttp4Android13 that reproduce the app's handshake and header order. The rule holds — your signature, your TLS fingerprint, and your User-Agent all have to describe the same client, or the mismatch is the tell.

Step 5 — Assemble a client that lasts

Now it comes together into code you run: acquire and refresh tokens, generate signatures per request, present the right transport fingerprint, and handle the failure modes — token expiry, rotation, the backend reshaping a payload. As with everything we ship, the difference between a demo and a deliverable is the hardening: clean reconnects, rotation, and failing loudly instead of silently returning garbage.

The wall you might not get past

Be realistic about hardware-backed attestation. Play Integrity's strong verdict and iOS App Attest can tie a request to a genuine device and untampered app via keys in secure hardware. When a backend hard-requires a valid attestation on every call, there's no clean userland reconstruction — the signal originates in a place you don't control. Part of the job is telling you early when a target sits behind that wall, instead of billing weeks to discover it.

Key takeaways

  • A private mobile API is recovered in a fixed order: intercept traffic → defeat pinning → isolate the non-trivial fields → reconstruct them → match the transport → assemble.
  • Certificate pinning is beaten at runtime with Frida, not by patching the binary — hook the check and make it pass; fall back to manual hooks for native/bespoke pinning.
  • The real work is the signed header: find the signing code (jadx/Ghidra statically, Frida dynamically), recover the canonical message and key, and prove your reimplementation reproduces it byte-for-byte.
  • Match the app's TLS/HTTP-2 fingerprint too — a correct payload over a wrong-looking connection still gets blocked.
  • Hardware attestation (Play Integrity / App Attest) is the honest ceiling; a good specialist flags it up front.

Have a mobile app whose private API you need to integrate with? Get in touch.

Need the collection system built around this? Explore our web scraping and automation service.

Need a closed system understood and implemented? Explore our reverse engineering service.

About the authors

  • Co-Founder & Engineer at status403

    Builds and reverse engineers software systems, from low-level protocols and high-throughput network automation to production applications and infrastructure.

  • @blik2bankomatGitHub

    Co-Founder & Engineer at status403

    Builds and reverse engineers software systems, from low-level protocols and high-throughput network automation to production applications and infrastructure.