Android App Penetration Testing & Frida Hooks

Bypass Android SSL Pinning with Frida: A Comprehensive Step-by-Step How-To

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SSL Pinning and Its Bypass

SSL (Secure Sockets Layer) Pinning is a security mechanism implemented by mobile applications to prevent man-in-the-middle (MITM) attacks. By ‘pinning’ the application to specific trusted certificates or public keys, it ensures that all communication occurs only with the legitimate server, even if the device’s trust store has been compromised or modified. While crucial for security, SSL pinning poses a significant challenge for penetration testers and security researchers who need to intercept and analyze application traffic for vulnerability assessment.

This article provides an expert-level, step-by-step guide on how to bypass SSL pinning on Android applications using Frida, a dynamic instrumentation toolkit. We will cover environment setup, common pinning implementations, and provide a comprehensive Frida script to dynamically disable these checks.

Understanding Frida and Its Role in Dynamic Instrumentation

Frida is a powerful toolkit for dynamic instrumentation, allowing you to inject custom scripts into running processes on Windows, macOS, Linux, iOS, Android, and QNX. It exposes a JavaScript API that lets you hook functions, spy on crypto APIs, or trace private application code. For Android, Frida’s ability to modify an app’s behavior at runtime makes it an invaluable tool for security research, reverse engineering, and bypassing security controls like SSL pinning without modifying the application binary.

Prerequisites and Environment Setup

Before diving into the bypass, ensure you have the following:

  • Rooted Android Device or Emulator: Frida requires root access to inject into other processes.
  • ADB (Android Debug Bridge): For interacting with your Android device.
  • Python 3: To install Frida tools on your host machine.
  • Frida-tools: Install via pip:
    pip install frida-tools

  • Frida-server: Download the appropriate version for your device’s architecture (e.g., frida-server-*-android-arm64) from Frida’s GitHub releases.

Setting Up Frida-server on Android

1. Push frida-server to your device:

adb push /path/to/frida-server /data/local/tmp/

2. Set execute permissions and run it:

adb shell "chmod 755 /data/local/tmp/frida-server"adb shell "/data/local/tmp/frida-server &"

Verify Frida-server is running and detectable:

frida-ps -U

Common SSL Pinning Implementations on Android

Android applications typically implement SSL pinning using several common libraries or custom code. The most prevalent include:

  • OkHttp: A popular HTTP client that offers CertificatePinner for pinning.
  • TrustManager: Custom implementations of X509TrustManager to override default certificate validation.
  • WebView: Pinning within WebViewClient via onReceivedSslError.
  • Conscrypt/Android’s default TrustManager: Underlying system libraries where pinning might occur.

Our Frida script will target these common points to effectively disable pinning checks.

Step-by-Step Bypass with a Generic Frida Script

Here’s a robust Frida script designed to bypass various SSL pinning implementations. Save this as ssl_bypass.js.

Java.perform(function () {    console.log("[*] Starting SSL Pinning Bypass");    var CertificateFactory = Java.use("java.security.cert.CertificateFactory");    var FileInputStream = Java.use("java.io.FileInputStream");    var BufferedInputStream = Java.use("java.io.BufferedInputStream");    var X509Certificate = Java.use("java.security.cert.X509Certificate");    var KeyStore = Java.use("java.security.KeyStore");    var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");    var SSLContext = Java.use("javax.net.ssl.SSLContext");    // Bypass OkHTTPv3    try {        var CertificatePinner = Java.use("okhttp3.CertificatePinner");        CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function (hostname, certificates) {            console.log("[+] Bypassing OkHTTPv3 pinning for " + hostname);            return;        };        CertificatePinner.check.overload("java.lang.String", "[Ljava.security.cert.Certificate;").implementation = function (hostname, certificates) {            console.log("[+] Bypassing OkHTTPv3 pinning for " + hostname);            return;        };        console.log("[+] OkHTTPv3 CertificatePinner bypassed");    } catch (err) {        console.log("[-] OkHTTPv3 CertificatePinner not found, skipping: " + err.message);    }    // Bypass TrustManager (various implementations)    try {        var TrustManager = Java.use("javax.net.ssl.X509TrustManager");        var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");        TrustManager.checkServerTrusted.overload("[Ljava.security.cert.X509Certificate;", "java.lang.String").implementation = function (chain, authType) {            console.log("[+] Bypassing TrustManager checkServerTrusted 1: " + authType);            return;        };        TrustManager.checkServerTrusted.overload("[Ljava.security.cert.X509Certificate;", "java.lang.String", "java.lang.String").implementation = function (chain, authType, host) {            console.log("[+] Bypassing TrustManager checkServerTrusted 2: " + authType + ", host: " + host);            return;        };        TrustManagerImpl.checkServerTrusted.implementation = function (chain, authType, host) {            console.log("[+] Bypassing TrustManagerImpl checkServerTrusted: " + authType + ", host: " + host);            return;        };        // Custom TrustManager implementations - iterate and hook commonly named methods        var Application = Java.use('android.app.Application');        Application.onCreate.implementation = function () {            this.onCreate();            var customTrustManagers = ['net.sqlcipher.database.SQLiteDatabase.CustomTrustManager', 'com.example.someapp.CustomTrustManager'];            customTrustManagers.forEach(function (className) {                try {                    var CustomTM = Java.use(className);                    CustomTM.checkServerTrusted.overload("[Ljava.security.cert.X509Certificate;", "java.lang.String").implementation = function (chain, authType) {                        console.log("[+] Bypassing custom TrustManager: " + className);                        return;                    };                    console.log("[+] Custom TrustManager " + className + " bypassed");                } catch (err) {                    // console.log("[-] Custom TrustManager " + className + " not found: " + err.message);                }            });        };        console.log("[+] X509TrustManager bypassed");    } catch (err) {        console.log("[-] X509TrustManager not found, skipping: " + err.message);    }    // Bypass SSLContext initialization (important for custom SSLSocketFactories)    try {        SSLContext.init.overload("[Ljavax.net.ssl.KeyManager;", "[Ljavax.net.ssl.TrustManager;", "java.security.SecureRandom").implementation = function (km, tm, sr) {            console.log("[+] Bypassing SSLContext.init");            var TrustManagers = Java.array("javax.net.ssl.TrustManager", [Java.cast(tm[0], TrustManager)]);            this.init(km, TrustManagers, sr);        };        console.log("[+] SSLContext init bypassed");    } catch (err) {        console.log("[-] SSLContext init not found, skipping: " + err.message);    }    // Bypass WebViewClient    try {        var WebViewClient = Java.use("android.webkit.WebViewClient");        WebViewClient.onReceivedSslError.overload("android.webkit.WebView", "android.webkit.SslErrorHandler", "android.net.http.SslError").implementation = function (view, handler, error) {            console.log("[+] Bypassing WebViewClient onReceivedSslError");            handler.proceed();        };        console.log("[+] WebViewClient onReceivedSslError bypassed");    } catch (err) {        console.log("[-] WebViewClient not found, skipping: " + err.message);    }    console.log("[*] SSL Pinning Bypass Finished");});

Executing the Bypass

1. Identify the target application’s package name. You can often find this using adb shell pm list packages | grep <app_name> or by inspecting the app’s URL on the Play Store.

2. Run Frida with your bypass script. Replace <package_name> with the actual package name:

frida -U -f <package_name> -l ssl_bypass.js --no-pause
  • -U: Target USB device.
  • -f <package_name>: Spawn the application (it will be launched by Frida).
  • -l ssl_bypass.js: Load your Frida script.
  • --no-pause: Start the application immediately after injection.

If the app is already running, use frida -U <package_name> -l ssl_bypass.js to attach to it. You may need to restart the app after attaching for hooks to take full effect.

Intercepting Traffic with a Proxy

Once SSL pinning is bypassed, you’ll need a proxy tool like Burp Suite or OWASP ZAP to intercept the traffic. Configure your Android device to route its traffic through your proxy:

  1. On your host machine, note down your IP address (e.g., ifconfig or ipconfig).
  2. Open Burp Suite/OWASP ZAP and ensure the listener is active on an accessible port (e.g., 8080) on all interfaces.
  3. On your Android device, go to Wi-Fi settings, long-press your connected network, select ‘Modify network’, then ‘Advanced options’.
  4. Set ‘Proxy’ to ‘Manual’, enter your host machine’s IP address as ‘Proxy hostname’ and the port (e.g., 8080) as ‘Proxy port’.

Now, when the application makes network requests, they should be routed through your proxy, allowing you to inspect and modify them.

Advanced Scenarios and Troubleshooting

Anti-Frida Detection

Some applications implement anti-Frida measures to detect its presence. Common techniques include checking for frida-server process, Frida specific libraries, or abnormal memory regions. Bypassing these often involves:

  • Frida Gadget: Injecting Frida as a shared library during app startup.
  • Obfuscating Frida: Modifying Frida’s binaries or signatures.
  • Hooking anti-Frida checks: Identifying and disabling the detection logic itself.

Custom SSL Pinning

Highly customized SSL pinning logic might not be caught by the generic script. In such cases, dynamic analysis and static analysis (decompiling the APK) are necessary to identify the specific methods responsible for certificate validation. You can then tailor your Frida script to target those precise functions.

Debugging Frida Scripts

Use console.log() statements liberally within your Frida script to understand execution flow and debug issues. Frida’s output in the terminal provides valuable insights into what the script is doing and where it might be failing.

Conclusion

Bypassing Android SSL pinning with Frida is an essential skill for mobile application security testing. This comprehensive guide has equipped you with the knowledge to set up your environment, understand common pinning techniques, and utilize a powerful generic Frida script to disable these security controls. Remember to always use these techniques ethically and only on applications you have explicit permission to test.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner