Android App Penetration Testing & Frida Hooks

Your Frida Toolkit: Essential Scripts and Workflow for Android SSL Pinning Defeat

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SSL Pinning and its Challenges for Testers

SSL Pinning, or Certificate Pinning, is a security mechanism employed by mobile applications to prevent man-in-the-middle (MITM) attacks. Instead of relying on the device’s trust store for validating server certificates, the application hardcodes or ‘pins’ the expected server certificate or public key. This ensures that the app only communicates with known, legitimate servers, even if a compromised CA issues a seemingly valid certificate.

What is SSL Pinning?

At its core, SSL pinning restricts the set of trusted certificates an application will accept when establishing a TLS connection. If a certificate presented by a server during the SSL/TLS handshake does not match the pinned certificate or public key, the connection is aborted, typically resulting in a network error within the application.

Why Bypass SSL Pinning?

For penetration testers and security researchers, SSL pinning presents a significant hurdle. It prevents the interception and inspection of application traffic using tools like Burp Suite or OWASP ZAP, which rely on injecting their own CA certificate into the trust chain. Bypassing SSL pinning is crucial for understanding an app’s network communication, identifying API vulnerabilities, and assessing data transmission security.

Setting Up Your Frida Environment for Android

Frida is a dynamic instrumentation toolkit that allows you to inject scripts into running processes on Android, iOS, Windows, macOS, and Linux. Its powerful API lets you hook into functions, modify behavior, and inspect data, making it an invaluable tool for bypassing SSL pinning.

Prerequisites

  • A rooted Android device or an Android emulator (e.g., Android Studio AVD, Genymotion).
  • Android Debug Bridge (ADB) installed on your host machine.
  • Frida-server compatible with your Android device’s architecture and OS version.
  • Frida-tools installed on your host machine (pip install frida-tools).
  • A proxy tool like Burp Suite for traffic interception.

Installation Steps: Frida-server on Android

  1. Download Frida-server: Visit the Frida releases page and download the frida-server-*-android-ARCH.xz file matching your Android device’s architecture (e.g., arm64, x86_64). Unpack it.

  2. Push to Device: Transfer the Frida-server binary to your Android device via ADB.

    adb push /path/to/frida-server /data/local/tmp/
  3. Set Permissions and Execute: Grant execute permissions and run Frida-server.

    adb shellsu -c "chmod 755 /data/local/tmp/frida-server"su -c "/data/local/tmp/frida-server &"

    You can verify it’s running by checking for listening ports or by running frida-ps -U on your host. If Frida-server fails to start, ensure your device is properly rooted and you have granted root permissions to the adb shell session.

The Universal SSL Pinning Bypass Script

Many Android applications implement SSL pinning using common APIs provided by Android’s Java ecosystem (e.g., X509TrustManager, OkHttp’s CertificatePinner). A well-crafted generic Frida script can target these common implementations.

Understanding the Generic Bypass

The universal bypass script primarily works by hooking critical methods responsible for certificate validation and forcing them to always return true or by replacing their default behavior with a permissive one. This often involves targeting checkServerTrusted methods within TrustManager implementations and methods related to certificate pinning in popular networking libraries.

The Script (frida_android_ssl_bypass.js)

Java.perform(function() {    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");    // Universal TrustManager bypass    var TrustManager = Java.use('javax.net.ssl.X509TrustManager');    var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');    TrustManager.checkServerTrusted.implementation = function (chain, authType) {        console.log('Bypassing TrustManager.checkServerTrusted (Universal)');    };    TrustManagerImpl.checkTrustedRecursive.implementation = function(a, b, c, d, e, f) {        console.log('Bypassing TrustManagerImpl.checkTrustedRecursive (Universal)');        return Java.array('java.security.cert.X509Certificate', []);    };    // OkHTTPv3 bypass    try {        var CertificatePinner = Java.use('okhttp3.CertificatePinner');        CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (p0, p1) {            console.log('Bypassing OkHTTPv3 pinning (List): ' + p0);            return;        };        CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function (p0, p1) {            console.log('Bypassing OkHTTPv3 pinning (Array): ' + p0);            return;        };        console.log('OkHTTPv3 CertificatePinner hooks applied');    } catch (err) {        console.log('OkHTTPv3 CertificatePinner not found, skipping');    }    // TrustKit bypass (if applicable)    try {        var TrustKit = Java.use('com.datatheorem.android.trustkit.TrustKit');        TrustKit.is      // Incomplete - placeholder to indicate more complex hooks are possible    } catch (err) {        console.log('TrustKit not found, skipping');    }    // WebView bypass (for some cases)    try {        var WebViewClient = Java.use('android.webkit.WebViewClient');        WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {            console.log('Bypassing WebViewClient.onReceivedSslError');            handler.proceed();        };    } catch (err) {        console.log('WebViewClient not found, skipping');    }    console.log('SSL pinning bypass scripts loaded!');});

Execution Workflow

To use this script, ensure your proxy (e.g., Burp Suite) is configured correctly on your Android device (manual proxy settings or Burp’s invisible proxy mode, and Burp’s CA certificate installed on the device’s user/system trust store).

  1. Start Frida: Execute the application with Frida and inject the script.

    frida -U -f com.example.app --no-pause -l frida_android_ssl_bypass.js

    -U specifies a USB device. -f com.example.app spawns and attaches to the app’s package name. --no-pause ensures the app starts immediately after script injection. -l loads your JavaScript file.

  2. Interact with the App: Use the application normally. You should observe ‘Bypassing…’ messages in your console output and be able to intercept traffic in Burp Suite.

Advanced Pinning Scenarios and Targeted Bypasses

While the universal script covers many cases, some applications implement more robust or custom pinning. Identifying these requires deeper analysis.

Identifying Pinning Mechanisms with Frida-Trace

When the generic script fails, frida-trace can help identify which SSL/TLS related functions are being called. This allows you to pinpoint the exact methods responsible for validation.

frida-trace -U -f com.example.app -i "*ssl*" -i "*trust*" -i "*cert*"

Analyze the output to see which methods are frequently called during network requests. Look for calls related to X509TrustManager, SSLSocketFactory, HostnameVerifier, or specific methods within libraries like OkHttp or Volley.

OkHttp3 CertificatePinner Bypass

Many modern Android apps use OkHttp. The universal script attempts to hook CertificatePinner.check. If this fails, investigate if the app is building its OkHttpClient instance in a way that bypasses standard pinning configuration or uses a custom Interceptor.

For example, if pinning is configured via a custom SSLSocketFactory or an Interceptor that adds a CertificatePinner, you might need to hook the methods responsible for building the OkHttpClient and modify its builder arguments.

Custom TrustManager Implementations

Some applications implement their own custom X509TrustManager. You’ll need to locate this custom class (e.g., via static analysis or frida-trace) and then hook its checkServerTrusted method specifically.

Java.perform(function() {    var CustomTrustManager = Java.use('com.example.app.security.MyCustomTrustManager'); // Replace with actual class name    CustomTrustManager.checkServerTrusted.implementation = function (chain, authType) {        console.log('Bypassing MyCustomTrustManager.checkServerTrusted!');        // Return without throwing exception        return;    };});

React Native/Flutter Challenges

Frameworks like React Native and Flutter often use their own networking stacks or native modules for network requests. For React Native, native Java hooks might still be effective if network calls route through standard Android APIs. For Flutter, you might need to target the Dart VM directly using Frida’s Interceptor.attach() on native library functions or by loading a specific Flutter SSL bypass script.

Developing Your Own Targeted Frida Scripts

When generic scripts fall short, the ability to write targeted Frida scripts is paramount.

  1. Static Analysis: Decompile the APK (e.g., with Jadx-GUI) to understand its networking code. Look for keywords like X509TrustManager, CertificatePinner, SSLSocketFactory, HostnameVerifier, and custom security classes.

  2. Dynamic Enumeration: Use Frida to enumerate classes and methods at runtime. This helps confirm class paths and method signatures.

    Java.perform(function () {    Java.enumerateLoadedClasses({        onMatch: function(className) {            if (className.includes("trust") || className.includes("ssl")) {                console.log(className);            }        },        onComplete: function() {            console.log("Enumeration complete!");        }    });});
  3. Targeted Hooking: Once you identify the specific class and method, write a precise hook. Remember to handle method overloads correctly (.overload('param1_type', 'param2_type')).

Troubleshooting Common Issues

  • Frida-server not running: Ensure root access and correct architecture. Check ADB logs.
  • App crashes after injection: Your script might have a syntax error or be hooking a method incorrectly. Use console.log liberally for debugging. Start with minimal hooks and gradually add more.
  • Still unable to intercept traffic: Verify your proxy settings on the device and Burp Suite. Ensure Burp’s CA certificate is correctly installed as a user or system trusted certificate.
  • Permissions issues: For Frida-server, make sure /data/local/tmp/frida-server has execute permissions and is run as root.

Conclusion

Defeating SSL pinning on Android is a critical skill for mobile application penetration testers. By mastering Frida’s capabilities, from deploying universal bypass scripts to crafting highly targeted hooks, you gain the ability to inspect and manipulate application network traffic. This enables thorough security assessments and discovery of vulnerabilities that would otherwise remain hidden behind robust security controls. Always ensure you have proper authorization before testing applications that are not your own.

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