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 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:
setTimeout (function ( ) { Java .perform (function ( ) { 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); }; 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) {} try { var OkHttpClient = Java .use ('com.squareup.okhttp.OkHttpClient' ); OkHttpClient .setCertificatePinner .implementation = function (pinner ) { return this ; }; console .log ('[+] OkHttp2 bypassed' ); } catch (e) {} 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) {} try { var NetworkSecurityTrustManager = Java .use ('android.security.net.config.NetworkSecurityTrustManager' ); NetworkSecurityTrustManager .checkPins .implementation = function (chain ) { return ; }; console .log ('[+] NetworkSecurityConfig pinning bypassed' ); } catch (e) {} 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:
public void checkServerTrusted (X509Certificate[] chain, String authType) throws CertificateException { 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 ( ) { var CustomTrustManager = Java .use ('com.target.app.network.CustomTrustManager' ); CustomTrustManager .checkServerTrusted .implementation = function (chain, authType ) { console .log ('[+] checkServerTrusted bypassed' ); }; });
Bypassing Pinning in Native Library Some apps implement pinning in native code (NDK). Identify with:
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:
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 ) { }, 'void' , ['pointer' , 'int' , 'pointer' ])); }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 ; }, '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:
invoke-virtual {v0, v1, v2}, Lokhttp3/CertificatePinner$Builder; ->add(Ljava/lang/String; [Ljava/lang/String; )Lokhttp3/CertificatePinner$Builder; 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.
Verification After bypass, HTTPS traffic should appear in Burp:
frida-ps -Uai | grep com.target.app 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:
Java .perform (function ( ) { 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.
No comments yet. Be the first.