Bypassing SSL Pinning to Intercept Mobile App APIs

Mobile API OSINT
Time it takes to read this article 6 minutes.

Introduction / Overview

Legal & Ethical Disclaimer: This article is for education and authorized security testing only. Intercepting, modifying, or analyzing traffic from applications and accounts you do not own — or do not have explicit written permission to test — is illegal in most jurisdictions. Always operate inside a documented engagement scope (bug bounty program, signed pentest contract, or your own lab).

Modern mobile applications speak to backend APIs over TLS. For a security assessor, that traffic is the most valuable surface: authentication flows, hidden endpoints, IDOR-prone object references, and business logic all flow through it. The standard approach is a man-in-the-middle (MITM) proxy such as Burp Suite. The obstacle is certificate pinning: the app refuses to trust any certificate except the one it expects, so your proxy's CA is rejected and the TLS handshake fails.

This post walks through diagnosing pinning, defeating it with Frida and objection, and routing decrypted traffic into Burp — followed by an equally weighted blue-team section on how defenders detect and harden against exactly this technique.

How it works / Background

A normal MITM works because you install Burp's CA certificate into the device trust store; the app validates the server certificate against the system store and accepts your proxy. Certificate pinning breaks this by hardcoding an expected certificate or public key (a "pin") inside the app and comparing it directly during the handshake. Common implementations:

  • OkHttp CertificatePinner (Android) — pins SHA-256 of the SubjectPublicKeyInfo.
  • Android Network Security Config<pin-set> entries in res/xml/network_security_config.xml.
  • TrustManager / X509TrustManager custom implementations.
  • iOS NSURLSession delegates validating serverTrust, or libraries like TrustKit.

Because the pin check happens inside the running process, the most reliable bypass is dynamic instrumentation: hook the validation functions at runtime and force them to return "valid." Frida is a binary instrumentation toolkit that injects a JavaScript engine into the target process; objection is a Frida-powered toolkit with pre-built pinning-bypass routines.

Prerequisites / Lab setup

  • A rooted Android device or emulator (Genymotion or a Google APIs — not Play Store — AVD image, which is rootable via adb root).
  • frida-tools on the host and frida-server matching the host version on the device.
  • objection, Burp Suite, and adb.
# Host tooling
pip install frida-tools objection

# Match frida-server to the client version
frida --version          # e.g. 16.5.6
# Download the matching frida-server-16.5.6-android-arm64.xz from GitHub releases
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
Bash

Install Burp's CA so that non-pinned TLS still works. On Android 7+ user-installed CAs are not trusted by apps by default, so push it as a system CA (requires root):

# Export Burp CA as DER, convert, and hash-rename for the system store
openssl x509 -inform DER -in cacert.der -out burp.pem
HASH=$(openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1)
cp burp.pem "${HASH}.0"
adb root && adb remount
adb push "${HASH}.0" /system/etc/security/cacerts/
adb shell "chmod 644 /system/etc/security/cacerts/${HASH}.0"
Bash

Then set the device proxy to your host IP and Burp's listening port (e.g. 8080), and make sure Burp listens on all interfaces.

Walkthrough / PoC

Step 1 — Confirm pinning exists. Configure the proxy, open the app, and watch Burp. If non-app browser traffic appears but the app's API calls do not — and the app shows connection errors — pinning is likely active. Confirm by listing the target's process and package:

frida-ps -Uai            # list installed apps with identifiers
Bash

Step 2 — Try the objection one-liner. objection wraps a battle-tested universal bypass that hooks OkHttp, TrustManager, and Network Security Config checks:

objection -g com.example.target explore \
  --startup-command "android sslpinning disable"
Bash

Inside the objection REPL you can also run it interactively:

com.example.target on (Android: 13) [usb] # android sslpinning disable
(agent) Custom SSL pinning bypass hooking enabled.
(agent) [okhttp3] CertificatePinner.check() called, overriding to allow.
Plaintext

Step 3 — Fall back to a Frida script when objection's generic hooks miss a custom implementation. The community-standard script is frida-multiple-unpinning:

frida -U -f com.example.target \
  -l frida-multiple-unpinning.js --no-pause
Bash

Step 4 — Write a targeted hook when bespoke pinning resists the generic scripts. Decompile the APK first (see Reverse Engineering Android APKs) to find the exact class and method:

// hook-okhttp.js — neutralize OkHttp CertificatePinner
Java.perform(function () {
  var CertificatePinner = Java.use('okhttp3.CertificatePinner');
  CertificatePinner.check.overload('java.lang.String', 'java.util.List')
    .implementation = function (hostname, peerCertificates) {
      console.log('[+] Bypassing pinning for: ' + hostname);
      return; // skip the check entirely
    };
});
JavaScript
frida -U -f com.example.target -l hook-okhttp.js --no-pause
Bash

Step 5 — Intercept and analyze. Once a hook lands, the app's API requests appear in Burp's HTTP history. From there you can use Repeater to fuzz endpoints, test authorization on object references, and map the API. For broader endpoint discovery, pair this with passive Mobile App OSINT and Endpoint Discovery.

A frequent gotcha: some apps use Flutter, which bundles its own BoringSSL and ignores the system proxy entirely. For those, use Frida to hook ssl_verify_peer_cert in libflutter.so and force the proxy via iptables/ProxyDroid.

Mermaid diagram

Bypassing SSL Pinning to Intercept Mobile App APIs diagram 1

The diagram shows the decision path from a failed handshake to a successful interception: try generic bypasses first, and only reverse-engineer a custom hook when those fail.

Detection & Defense (Blue Team)

Pinning bypass is a runtime attack on a rooted, instrumented device. Defenders should assume a motivated attacker can defeat any single client-side control, so layer the following.

1. Detect the instrumentation environment.

  • Root/jailbreak detection: check for su, Magisk, common root paths, and writable system partitions.
  • Frida detection: scan loaded libraries for frida-agent, look for the default frida-server port 27042, and detect the D-Bus AUTH handshake string in process memory. Frida's named pipes and thread names (gum-js-loop, gmain) are also signals.
  • Debugger/emulator detection: check ro.build.fingerprint, QEMU artifacts, and ptrace attachment.
// Minimal Frida named-pipe / library check (illustrative)
for (String lib : getLoadedLibraries()) {
    if (lib.contains("frida") || lib.contains("gum")) {
        reportTamperAndExit();
    }
}
Java

2. Harden the pinning itself. Use two pins (leaf + backup intermediate) so certificate rotation does not brick the app, and prefer Android's declarative network_security_config.xml <pin-set> over hand-rolled TrustManagers, which are easy to mis-hook:

<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.example.com</domain>
    <pin-set expiration="2026-01-01">
      <pin digest="SHA-256">k3XnEYQCK9...leaf...=</pin>
      <pin digest="SHA-256">backuppinABC...=</pin>
    </pin-set>
  </domain-config>
</network-security-config>
XML

3. Move trust server-side. Client-side pinning only protects confidentiality of the channel; it does not authenticate the client. Enforce strong server-side authorization, short-lived tokens, mutual TLS (mTLS) with client certificates stored in hardware-backed keystores (Android Keystore / iOS Secure Enclave), and per-request signing (e.g., HMAC over the body with a key derived at runtime) so a proxied request still fails integrity checks.

4. Use attestation. Google Play Integrity API and Apple App Attest / DeviceCheck let the backend reject requests from rooted, emulated, or repackaged clients. Combined with RASP (runtime application self-protection) and code obfuscation (DexGuard, R8 with aggressive shrinking), the cost of a bypass rises sharply.

5. Monitor at the backend. Watch for anomalies that indicate interception/replay: requests from data-center IPs, missing or stale attestation tokens, unusual TLS fingerprints (JA3/JA4 not matching the official app), and high-velocity endpoint enumeration.

Mapping to MITRE ATT&CK for Mobile: this technique relates to T1626/T1404 (privilege escalation via rooting) and the broader "Network Traffic Capture / Manipulation" tradecraft; attestation and integrity checks counter T1633 (Virtualization/Sandbox Evasion) weaknesses.

Conclusion

Certificate pinning is a speed bump, not a wall. With Frida and objection a tester can neutralize generic pinning in seconds and write a targeted hook for custom implementations after a short reverse-engineering pass. Defenders should treat pinning as one layer in a defense-in-depth stack — paired with root/Frida detection, server-side authorization, mTLS, and platform attestation — rather than a single control. The most resilient APIs are those that never trust the client in the first place.

References

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL