← Back to writing
Mobile / Android

SSL Pinning Bypass with Frida

Jun 15, 2024
4 min read
lawbyte

SSL pinning prevents MITM interception by verifying the server’s certificate against a known value embedded in the app. This guide covers every common pinning implementation and how to bypass each one using Frida, Objection, and custom scripts.

Prerequisites

  • Rooted device or AOSP emulator
  • Frida server running on device
  • Burp Suite configured as proxy
  • Burp CA cert installed as system cert

If you can already intercept HTTPS traffic, pinning isn’t in use. If Burp shows SSL handshake aborted or the app simply shows a network error, pinning is active.


Quick Bypass — Objection

In most cases, Objection’s one-liner handles common pinning implementations:

objection -g com.target.app explore
# Inside REPL:
android sslpinning disable

This patches the most common implementations: OkHttp3 CertificatePinner, X509TrustManager, HttpsURLConnection, SSLContext, and some custom implementations.


Universal Frida Script

This script covers the majority of Android pinning implementations:

// universal-ssl-bypass.js
setTimeout(function() {
Java.perform(function() {

// ─── TrustManager ───────────────────────────────────────────
var TrustManager = Java.registerClass({
name: 'com.custom.TrustManager',
implements: [Java.use('javax.net.ssl.X509TrustManager')],
methods: {
checkClientTrusted: function(chain, authType) {},
checkServerTrusted: function(chain, authType) {},
getAcceptedIssuers: function() { return []; }
}
});

var SSLContext = Java.use('javax.net.ssl.SSLContext');
SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom')
.implementation = function(km, tm, sr) {
this.init(km, [TrustManager.$new()], sr);
};

// ─── OkHttp3 CertificatePinner ──────────────────────────────
try {
var CertificatePinner = Java.use('okhttp3.CertificatePinner');
CertificatePinner.check.overload('java.lang.String', 'java.util.List')
.implementation = function(hostname, certs) { return; };
CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;')
.implementation = function(hostname, certs) { return; };
console.log('[+] OkHttp3 CertificatePinner bypassed');
} catch(e) {}

// ─── OkHttp2 ────────────────────────────────────────────────
try {
var OkHttpClient = Java.use('com.squareup.okhttp.OkHttpClient');
OkHttpClient.setCertificatePinner.implementation = function(pinner) { return this; };
console.log('[+] OkHttp2 bypassed');
} catch(e) {}

// ─── Trustkit ───────────────────────────────────────────────
try {
var TrustKit = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier');
TrustKit.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession')
.implementation = function(host, session) { return true; };
console.log('[+] TrustKit bypassed');
} catch(e) {}

// ─── Android Network Security Config ────────────────────────
try {
var NetworkSecurityTrustManager = Java.use('android.security.net.config.NetworkSecurityTrustManager');
NetworkSecurityTrustManager.checkPins.implementation = function(chain) { return; };
console.log('[+] NetworkSecurityConfig pinning bypassed');
} catch(e) {}

// ─── HttpsURLConnection ──────────────────────────────────────
try {
var HttpsURLConnection = Java.use('javax.net.ssl.HttpsURLConnection');
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function(verifier) {
this.setDefaultHostnameVerifier(Java.use('javax.net.ssl.HostnameVerifier').$new({
verify: function(hostname, session) { return true; }
}));
};
} catch(e) {}

console.log('[*] SSL pinning bypass script loaded');
});
}, 0);
frida -U -f com.target.app -l universal-ssl-bypass.js --no-pause

Bypassing Custom TrustManager

When an app implements a custom X509TrustManager:

// Typical hardened implementation
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
// manually verify pin
for (X509Certificate cert : chain) {
String pin = getPin(cert.getPublicKey());
if (expectedPin.equals(pin)) return;
}
throw new CertificateException("Pinning failed");
}

Find the class in jadx, then hook it:

Java.perform(function() {
// Replace the specific class name from jadx
var CustomTrustManager = Java.use('com.target.app.network.CustomTrustManager');
CustomTrustManager.checkServerTrusted.implementation = function(chain, authType) {
console.log('[+] checkServerTrusted bypassed');
// Return without throwing — bypass complete
};
});

Bypassing Pinning in Native Library

Some apps implement pinning in native code (NDK). Identify with:

# Check for native pinning libraries
strings target.apk | grep -E "libssl|pinning|curl_easy"
unzip target.apk
strings lib/arm64-v8a/libapp.so | grep -iE "pin|cert|sha256"

Hook native SSL functions:

// Hook openssl SSL_CTX_set_verify
var SSL_CTX_set_verify = Module.findExportByName("libssl.so", "SSL_CTX_set_verify");
if (SSL_CTX_set_verify) {
Interceptor.replace(SSL_CTX_set_verify, new NativeCallback(function(ctx, mode, cb) {
// mode 0 = SSL_VERIFY_NONE
}, 'void', ['pointer', 'int', 'pointer']));
}

// Hook SSL_get_verify_result
var SSL_get_verify_result = Module.findExportByName("libssl.so", "SSL_get_verify_result");
if (SSL_get_verify_result) {
Interceptor.replace(SSL_get_verify_result, new NativeCallback(function(ssl) {
return 0; // X509_V_OK
}, 'long', ['pointer']));
}

Bypassing Pinning via APK Patch

When Frida doesn’t work (anti-root/anti-Frida), patch the APK statically.

Method 1 — Remove pin from OkHttp

In jadx, find the CertificatePinner.Builder().add(...) call. In the smali:

# Before — adds pin
invoke-virtual {v0, v1, v2}, Lokhttp3/CertificatePinner$Builder;->add(Ljava/lang/String;[Ljava/lang/String;)Lokhttp3/CertificatePinner$Builder;

# After — comment out (replace with nop or remove)
nop

Method 2 — Trust all via network_security_config

Replace res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="user"/>
</trust-anchors>
</base-config>
</network-security-config>

Recompile, sign, install.


Bypassing Pinning with Magisk + Module

LSPosed + TrustMeAlready — installs a Xposed module that disables SSL verification app-wide.

Magisk + MagiskTrustUserCerts — promotes user certificates to system trust store automatically.

# Install via Magisk Manager → Modules → search "MagiskTrustUserCerts"
# Then install Burp cert as user cert
# After reboot it appears as system cert

Verification

After bypass, HTTPS traffic should appear in Burp:

# Confirm Frida is running
frida-ps -Uai | grep com.target.app

# Watch for pinning errors in logcat
adb logcat | grep -iE "pin|certificate|ssl|handshake"

If traffic still doesn’t appear:

  • Check that the device proxy is correctly set to Burp.
  • The app may use a non-standard port — check with tcpdump.
  • The app may use certificate transparency checks in addition to pinning.
  • Try --debug flag in Frida to see what’s being hooked.

Anti-Frida Detection Bypass

Some apps detect Frida and refuse to run:

// Detect Frida detection
Java.perform(function() {
// Hook file reads that check for frida-server
var File = Java.use('java.io.File');
File.exists.implementation = function() {
var name = this.getAbsolutePath();
if (name.indexOf('frida') !== -1 || name.indexOf('gadget') !== -1) {
return false;
}
return this.exists();
};
});

Also try Magisk Hide (older) or Shamiko (newer) to hide root from the app process.

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.