Binary Tech

SSL Pinning Bypass Using Frida

SSL Pinning Bypass Using Frida

When performing mobile application penetration testing, one of the most common obstacles is SSL pinning. Even after installing Burp’s certificate, traffic still doesn’t appear – because the application explicitly validates the server certificate.

In this guide, I’ll walk you through a practical and industry-standard method to bypass SSL pinning using:

  • Frida
  • Android Studio Emulator
  • Burp Suite
  • Windows host machine

This method uses dynamic instrumentation, meaning we modify app behavior at runtime – no APK patching required.

Table of Contents

What is SSL Pinning?

SSL pinning is a security mechanism where an app hardcodes or validates a specific certificate/public key. Even if a trusted CA certificate is installed on the device, the app rejects it.

That’s why just installing Burp’s CA certificate is not enough.

Step 0: One-Time Setup (Prerequisites)

Before starting, ensure you have:

  • Windows system
  • Android Studio Emulator (Android 10+ recommended)
  • Burp Suite
  • Python 3.9+
  • Frida tools

Install Python

Verify:

python --version

Install Frida (Host Machine)

  • Install Frida:
pip install frida frida-tools
  • Verify installation:
frida --version

Make sure this version matches the frida-server version you download later.

Step 1: Start Android Studio Emulator

  1. Open Android Studio
  2. Launch your emulator (Pixel device recommended)
  3. Wait until Android fully boots
  4. Verify device connection:
adb devices

Expected output:

emulator-5554   device

Step 2: Check Emulator CPU Architecture

This is very important.

Run:

adb shell getprop ro.product.cpu.abi

Typical output:

x86_64

Remember this architecture – you must download the matching frida-server.

Step 3: Download the correct frida-server

Download from the official Frida releases page:

https://github.com/frida/frida/releases

Example:

  • If your Frida version is: 17.2.17
  • Download: frida-server-17.2.17-android-x86_64.xz
  • Extract the file and rename it to: Fridaserver

Step 4: Push fridaserver to Emulator

  • Push the server:
adb push fridaserver /data/local/tmp/
  • Open the adb shell and inside the shell:
cd /data/local/tmp
chmod 755 fridaserver
./fridaserver &
  • Verify from the host:
frida-ps -U

If you see Android processes → Frida is running successfully

Step 5: Configure Burp Proxy and Android Emulator Network

Now we configure Burp Suite and route Android Emulator traffic through it.

5.1 Configure Burp Proxy
  1. Open Burp Suite
  2. Navigate to: Proxy → Settings → Proxy Listeners
    • Bind address: All interfaces
    • Port: 8080
5.2 Configure Proxy in Android Emulator:
Method A – Emulator Proxy Tab
  1. Click the three dots (⋮) on the emulator toolbar
  2. Go to: Settings → Proxy
  3. Select Manual
  4. Enter:
Host: 10.0.2.2  
Port: 8080

Save settings in the emulator.

Method B – Android Wi-Fi Settings
  1. Inside emulator: Settings → Network & Internet → Wi-Fi
  2. Long-press connected network
  3. Tap Modify network
  4. Enable Advanced options
  5. Set: Proxy: Manual
    • Proxy hostname: 10.0.2.2 
    • Proxy port: 8080
5.3 Install Burp CA Certificate
  1. Export Burp certificate as cacert.der
  2. Drag and drop to the emulator
  3. Install as User Certificate

Important: Android 7+ does not trust user-installed certificates for apps by default. That’s exactly why we need SSL pinning bypass.

Step 6: Identify Target App Package Name

adb shell pm list packages | findstr yourapp

Example:

com.example.targetapp

You’ll use this package name in the Frida command.

Step 7: SSL Pinning Bypass Script

This blog uses a Frida script:

// Universal-ish Android SSL pinning bypass (TrustManager, Conscrypt, OkHttp3, WebView)
Java.perform(function () {
  function log(msg) { console.log("[SSL BYPASS] " + msg); }
  // ---- X509TrustManager: accept all ----
  var X509TM = Java.use('javax.net.ssl.X509TrustManager');
  var TrustManager = Java.registerClass({
    name: 'com.frida.CustomTrustManager',
    implements: [X509TM],
    methods: {
      checkClientTrusted: function (chain, authType) {},
      checkServerTrusted: function (chain, authType) {},
      getAcceptedIssuers: function () { return []; }
    }
  });
  // SSLContext.init -> install our TrustManager
  try {
    var SSLContext = Java.use('javax.net.ssl.SSLContext');
    SSLContext.init.overload(
      '[Ljavax.net.ssl.KeyManager;',
      '[Ljavax.net.ssl.TrustManager;',
      'java.security.SecureRandom'
    ).implementation = function (kms, tms, sr) {
      log('Hooking SSLContext.init() -> injecting permissive TrustManager');
      var tm = [ TrustManager.$new() ];
      return this.init(kms, tm, sr);
    };
  } catch (e) { log('SSLContext hook err: ' + e); }
  // ---- Android 7+ Conscrypt TrustManagerImpl ----
  var tmImplClasses = [
    'com.android.org.conscrypt.TrustManagerImpl',
    'org.conscrypt.TrustManagerImpl'  // some ROMs
  ];
  tmImplClasses.forEach(function (cls) {
    try {
      var TMI = Java.use(cls);
      // verifyChain (newer Androids)
      if (TMI.verifyChain) {
        TMI.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
          log(cls + '.verifyChain() bypass');
          return untrustedChain;
        };
      }
      // checkTrusted (older variants)
      ['checkTrusted','checkServerTrusted','checkClientTrusted'].forEach(function (m) {
        if (TMI[m]) {
          TMI[m].overloads.forEach(function (ov) {
            ov.implementation = function () {
              log(cls + '.' + m + '() bypass');
              try { return ov.apply(this, arguments); } catch (e) { return []; }
            };
          });
        }
      });
    } catch (e) { /* class may not exist on this build */ }
  });
  // ---- OkHttp3 CertificatePinner ----
  try {
    var CertPinner = Java.use('okhttp3.CertificatePinner');
    // OkHttp 3.x: check(String, List)
    if (CertPinner.check.overloads.length) {
      CertPinner.check.overloads.forEach(function (ov) {
        if (ov.argumentTypes.length === 2) {
          ov.implementation = function (hostname, peerCerts) {
            log('OkHttp3 CertificatePinner.check(' + hostname + ') bypass');
            return; // do nothing -> bypass pinning
          };
        }
      });
    }
    // Some builds have synthetic method name:
    if (CertPinner['check$okhttp']) {
      CertPinner['check$okhttp'].implementation = function () {
        log('OkHttp3 CertificatePinner.check$okhttp() bypass');
        return;
      };
    }
  } catch (e) { /* OkHttp may not be used */ }
  // ---- WebViewClient onReceivedSslError ----
  try {
    var WebViewClient = Java.use('android.webkit.WebViewClient');
    WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
      log('WebViewClient.onReceivedSslError() -> proceeding');
      handler.proceed();
    };
  } catch (e) {}
  // ---- HostnameVerifier ----
  try {
    var HV = Java.use('javax.net.ssl.HostnameVerifier');
    var AllHostsHV = Java.registerClass({
      name: 'com.frida.AllHostsHV',
      implements: [HV],
      methods: { verify: function (h, s) { return true; } }
    });
    var HUC = Java.use('javax.net.ssl.HttpsURLConnection');
    HUC.setDefaultHostnameVerifier.implementation = function (v) {
      log('HttpsURLConnection.setDefaultHostnameVerifier() -> forcing always-true');
      return this.setDefaultHostnameVerifier(AllHostsHV.$new());
    };
    HUC.setHostnameVerifier.implementation = function (v) {
      log('HttpsURLConnection.setHostnameVerifier() -> forcing always-true');
      return this.setHostnameVerifier(AllHostsHV.$new());
    };
  } catch (e) {}
  log('Installed hooks.');
});

Reference (Community Script)

The implementation is inspired by the widely used universal SSL pinning bypass script from Frida CodeShare:

https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida

If the above custom script doesn’t work for a specific target, you can directly use the CodeShare script to modify the script according to your target.

  • Always use spawn mode:
frida -U -f com.example.targetapp -l ssl_bypass.js 
  • Expected logs:
[+] SSLContext bypass
[+] OkHttp SSL Pinning bypass

If you see these logs → pinning is disabled.

Step 9: Verify in Burp Suite

  1. Open the target app
  2. Trigger API requests
  3. Check Burp

If HTTPS traffic appears → Success

Important: After Emulator Restart

Frida bypass is not persistent.

After every emulator reboot:

  1. Start the emulator
  2. Start fridaserver
  3. Run the Frida injection command again

Final Thoughts

SSL pinning is one of the first defensive mechanisms you’ll encounter in Android app testing. Mastering dynamic bypass techniques using Frida is an essential skill for:

  • Mobile Pentesters
  • Security Researchers
  • Bug Bounty Hunters
  • Red Teamers

This approach is widely used in professional security assessments.

Authors: Vinith Kalikar, Atharva Inamdar

Disclaimer: This guide is for educational and authorized security testing purposes only. Do not test applications without proper permission.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top