Android App Penetration Testing & Frida Hooks

The Ultimate Toolkit: Frida Scripts & Techniques for OkHttp3 SSL Pinning Bypass

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SSL Pinning and Its Bypass

SSL Pinning is a critical security mechanism implemented in mobile applications to prevent man-in-the-middle (MITM) attacks. It ensures that an app communicates only with trusted servers by validating the server’s certificate or public key against a predefined set of trusted entities embedded within the application itself. While excellent for security, this poses a significant challenge for penetration testers and security researchers who need to intercept and analyze app traffic. OkHttp3, a popular HTTP client for Android, frequently incorporates SSL pinning using its CertificatePinner class.

This article provides an expert-level guide on leveraging Frida, a dynamic instrumentation toolkit, to bypass OkHttp3 SSL pinning in Android applications. We will explore specific scripting techniques targeting OkHttp3’s core pinning mechanisms, offering practical examples and step-by-step instructions for successful traffic interception during penetration testing.

Understanding OkHttp3’s CertificatePinner

OkHttp3 implements SSL pinning primarily through its okhttp3.CertificatePinner class. Developers can configure an OkHttpClient instance to use a CertificatePinner that specifies expected SHA-256 hashes of certificates or public keys for specific hosts. When a connection is established, OkHttp3 verifies the server’s certificate chain against these pinned hashes. If a mismatch occurs, the connection is aborted, preventing communication with an untrusted server (like a proxy’s certificate).

A typical OkHttp3 pinning implementation might look like this in Java/Kotlin code:

public class MyApp {  private OkHttpClient client;  public MyApp() {    CertificatePinner certificatePinner = new CertificatePinner.Builder()      .add("*.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")      .add("*.anothersite.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")      .build();    client = new OkHttpClient.Builder()      .certificatePinner(certificatePinner)      .build();  }}

The key method we’ll target in CertificatePinner for bypassing is the check() method, which performs the actual pinning validation.

Frida Fundamentals for Android Penetration Testing

Frida allows injecting JavaScript snippets into native apps on Android, iOS, Windows, macOS, and Linux. For Android, it enables us to hook into Java methods, modify their behavior, inspect arguments, and even replace entire implementations at runtime. This makes it an ideal tool for bypassing security controls like SSL pinning without modifying the application’s APK.

Setting up Frida

Before proceeding, ensure you have:

  • A rooted Android device or emulator.
  • Frida server running on the Android device.
  • Frida tools installed on your host machine (pip install frida-tools).
  • adb (Android Debug Bridge) configured.

To run the Frida server:

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

Frida Scripting Techniques for OkHttp3 SSL Pinning Bypass

Method 1: Hooking okhttp3.CertificatePinner.check()

This is the most direct approach. We’ll target the check() method within the okhttp3.CertificatePinner class and force it to do nothing, effectively disabling the pinning validation.

Java.perform(function () {    console.log("[*] Starting OkHttp3 CertificatePinner Bypass...");    var CertificatePinner = Java.use('okhttp3.CertificatePinner');    CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (hostname, peerCertificates) {        console.log("[+] Bypassing CertificatePinner.check() for hostname: " + hostname);        // Do nothing, effectively bypassing the pinning check        // You can optionally call the original method if you want to inspect arguments        // this.check(hostname, peerCertificates);    };    console.log("[*] OkHttp3 CertificatePinner Bypass script loaded.");});

Explanation:

  1. Java.perform(function () { ... });: Ensures our script runs in the context of the target Java VM.
  2. Java.use('okhttp3.CertificatePinner');: Obtains a JavaScript wrapper for the okhttp3.CertificatePinner class.
  3. .check.overload('java.lang.String', 'java.util.List'): Specifies the exact overload of the check() method we want to hook. It’s crucial to match the argument types correctly.
  4. .implementation = function (...) { ... };: Replaces the original implementation of the check() method with our custom function, which simply logs a message and returns, thus skipping the actual pinning logic.

Method 2: Hooking javax.net.ssl.X509TrustManager.checkServerTrusted()

While Method 1 targets OkHttp3 specifically, many SSL pinning implementations (including those relying on default system trust managers) eventually call methods within the X509TrustManager interface. Bypassing checkServerTrusted() offers a more generic approach that can work even if the app uses custom trust managers or other libraries for pinning, as long as they ultimately delegate to an X509TrustManager.

Java.perform(function () {    console.log("[*] Starting X509TrustManager Bypass...");    var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');    var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); // For newer Android versions    var checkServerTrusted = X509TrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String');    var checkServerTrustedArgs = checkServerTrusted.argumentTypes;    checkServerTrusted.implementation = function (chain, authType) {        console.log("[+] Bypassing X509TrustManager.checkServerTrusted()");        // Call original method only if needed, for simplicity we bypass        // this.checkServerTrusted(chain, authType);    };    // Also hook TrustManagerImpl.checkServerTrusted if TrustManagerImpl is used    if (TrustManagerImpl) {        var checkServerTrusted_TrustManagerImpl = TrustManagerImpl.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String');        if (checkServerTrusted_TrustManagerImpl) {            checkServerTrusted_TrustManagerImpl.implementation = function (chain, authType, host) {                console.log("[+] Bypassing TrustManagerImpl.checkServerTrusted() for host: " + host);                // this.checkServerTrusted(chain, authType, host);            };        }        var checkServerTrusted_TrustManagerImpl_sslEngine = TrustManagerImpl.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'javax.net.ssl.SSLEngine');        if (checkServerTrusted_TrustManagerImpl_sslEngine) {            checkServerTrusted_TrustManagerImpl_sslEngine.implementation = function (chain, authType, sslEngine) {                console.log("[+] Bypassing TrustManagerImpl.checkServerTrusted() for SSLEngine");                // this.checkServerTrusted(chain, authType, sslEngine);            };        }    }    console.log("[*] X509TrustManager Bypass script loaded.");});

Explanation:

  1. This script targets multiple overloads of checkServerTrusted() in both the standard X509TrustManager and the Android-specific com.android.org.conscrypt.TrustManagerImpl. This comprehensive approach increases the chances of successful bypass across different Android versions and app implementations.
  2. Similar to Method 1, we replace the original implementation with an empty function, preventing certificate validation errors.

Execution Guide and Observing Results

To apply these Frida scripts:

  1. Save your chosen script (e.g., bypass.js).
  2. Identify the target Android application’s package name (e.g., com.example.app).
  3. Run Frida, attaching to the application:
frida -U -f com.example.app -l bypass.js --no-pause

The -U flag targets a USB-connected device, -f spawns and attaches to the app, -l loads your script, and --no-pause starts the app immediately without waiting. After running, interact with the app. If successful, you should see the log messages from your Frida script in the console.

To observe the intercepted traffic:

  1. Configure your Android device’s Wi-Fi proxy settings to point to your Burp Suite (or any other HTTP proxy) instance running on your host machine.
  2. Ensure Burp Suite is set up to listen on the specified port and is configured to generate an SSL CA certificate that your device trusts (typically by installing Burp’s CA certificate on the Android device).
  3. With the Frida script active and the proxy configured, all network traffic from the target app should now flow through Burp Suite, allowing you to inspect requests and responses.

Advanced Considerations and Troubleshooting

  • Obfuscation: Apps using ProGuard or DexGuard might obfuscate class and method names. In such cases, you might need to use techniques like Java.enumerateLoadedClasses() to find the real class names at runtime, or use wildcard patterns in Java.use() (e.g., Java.use('/.*.CertificatePinner/') if the package name is unknown but the class name is unique).
  • Multiple Pinning Mechanisms: Some applications might combine OkHttp3 pinning with other forms of pinning (e.g., using Conscrypt’s PinningTrustManager or custom network security configurations). A multi-pronged approach combining various Frida scripts might be necessary.
  • Timing Issues: Ensure your script is injected and active before the application initializes its network stack. Using --no-pause with -f helps ensure early injection.
  • Error Handling: Always add try...catch blocks in your Frida scripts to gracefully handle cases where a class or method might not exist or has a different signature than expected.

Conclusion

Bypassing SSL pinning is a fundamental skill for Android application penetration testers. Frida provides an incredibly flexible and powerful platform to achieve this without modifying the application binary. By understanding how OkHttp3 implements its CertificatePinner and leveraging the techniques discussed—particularly hooking CertificatePinner.check() and X509TrustManager.checkServerTrusted()—you can effectively neutralize this security control and gain full visibility into an app’s network communications. Always remember to apply 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