Android App Penetration Testing & Frida Hooks

Reverse Engineering Android Apps: Identifying & Bypassing Custom SSL Pinning with Frida

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SSL Pinning and Its Challenges

SSL (Secure Sockets Layer) pinning, also known as certificate pinning, is a security mechanism designed to prevent Man-in-the-Middle (MITM) attacks. By associating a specific certificate or public key with a host, the application ensures that it only communicates with the legitimate server, even if a compromised or untrusted Certificate Authority (CA) issues a seemingly valid certificate. While crucial for security, SSL pinning poses a significant challenge for penetration testers and security researchers who need to intercept and analyze application traffic.

Android applications commonly implement SSL pinning using various methods. Standard implementations often leverage frameworks like OkHttp’s CertificatePinner, Android’s NetworkSecurityConfig, or the default TrustManager. These are typically easier to bypass using readily available Frida scripts or tools like Objection. However, when applications implement custom SSL pinning logic – often by creating their own X509TrustManager or overriding certificate validation methods in a bespoke manner – generic bypass techniques usually fail. This article dives deep into identifying and bypassing such custom implementations using static and dynamic analysis with Frida.

Prerequisites and Tools

To follow along with this guide, you’ll need the following:

  • Rooted Android Device or Emulator: Necessary for running Frida-server.
  • ADB (Android Debug Bridge): For interacting with the Android device.
  • Frida: A dynamic instrumentation toolkit. Install Frida-tools on your host machine (pip install frida-tools) and Frida-server on your Android device (download from Frida GitHub releases, push to /data/local/tmp, set executable permissions, and run).
  • JADX-GUI: A powerful decompiler for Android applications. Essential for static analysis. Download from JADX GitHub releases.
  • Burp Suite (or similar proxy): To intercept and verify network traffic.
  • Target Android APK: An application with custom SSL pinning implemented.

Step 1: Initial Reconnaissance and Generic Bypass Attempts

Before diving into custom logic, always attempt generic bypasses. This helps confirm that pinning is indeed active and establishes a baseline. Tools like Objection, which builds upon Frida, offer quick ways to try standard bypasses.

# Start frida-server on your Android device (if not already running)z# adb shell"/data/local/tmp/frida-server" &# On your host machineadb push frida-server /data/local/tmp/adb shell "chmod 755 /data/local/tmp/frida-server"adb shell "/data/local/tmp/frida-server &"# Try Objection for a quick bypass (replace com.example.app with target package)objection -g com.example.app explore--startup-command "android sslpinning disable"# Alternatively, use a generic Frida SSL bypass script (e.g., frida-multiple-unpinning.js)frida -U -f com.example.app -l universal-ssl-bypass.js --no-pause

If, after attempting these generic methods, you still cannot intercept traffic via your proxy (e.g., Burp Suite shows connection errors like “SSLHandshakeException”), it’s a strong indicator that the application employs custom SSL pinning logic.

Step 2: Static Analysis with JADX-GUI to Identify Pinning Logic

This is where JADX-GUI becomes invaluable. Load the target APK into JADX-GUI and begin searching for keywords and patterns indicative of SSL pinning. The goal is to pinpoint the exact Java classes and methods responsible for certificate validation.

Keywords for Pinning Detection

Start your search within JADX-GUI for the following terms:

  • X509Certificate: Often used when dealing with raw certificate data.
  • PublicKey: Pinning might involve comparing public keys.
  • TrustManager: The core interface for certificate trust validation. Look for custom implementations of X509TrustManager.
  • checkServerTrusted: This is the most crucial method of X509TrustManager, responsible for validating the server’s certificate chain. Custom pinning logic almost always resides here.
  • hostnameVerifier: Used to verify the hostname against the certificate.
  • SSLContext, SSLSocketFactory: These classes are involved in creating secure connections, and custom implementations might inject pinning logic.
  • CertificatePinner: Though often standard, custom versions can exist.

Search Strategy

  1. Start broad: Search for TrustManager or X509TrustManager. Look for classes that implement this interface.
  2. Focus on checkServerTrusted: Once a custom TrustManager is found, examine its checkServerTrusted method. This method takes X509Certificate[] chain and String authType as arguments. Inside, you’ll likely find logic comparing certificate hashes, public keys, or issuer details against hardcoded values or assets. If an issue is detected, it typically throws a CertificateException.
  3. Look for custom SSLSocketFactory or HostnameVerifier: If no custom TrustManager is immediately apparent, trace how HttpsURLConnection or OkHttp clients are initialized. Custom factories or verifiers can inject pinning logic.
  4. Analyze code patterns: Pay attention to any methods that involve byte array comparisons, hashing (SHA-1, SHA-256), or string comparisons of certificate fields.

A typical custom pinning implementation in Java might look something like this (simplified):

package com.example.app.security;import java.security.cert.CertificateException;import java.security.cert.X509Certificate;import javax.net.ssl.X509TrustManager;public class CustomTrustManager implements X509TrustManager {    private static final String PINNED_SHA256 = "AA:BB:CC:DD:EE:FF..."; // Hardcoded hash    @Override    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {        // Not usually relevant for server pinning    }    @Override    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {        if (chain == null || chain.length == 0) {            throw new IllegalArgumentException("Certificate chain is empty or null");        }        // Iterate through the chain to find the certificate to pin (e.g., the leaf certificate)        X509Certificate leafCert = chain[0];        try {            // Calculate the SHA256 hash of the certificate's public key            // (Actual implementation is more complex, involving MessageDigest)            String calculatedHash = calculateSha256(leafCert.getPublicKey().getEncoded());            if (!PINNED_SHA256.equals(calculatedHash)) {                throw new CertificateException("Pinning failure: Certificate hash mismatch!");            }        } catch (Exception e) {            throw new CertificateException("Failed to verify certificate: " + e.getMessage(), e);        }    }    @Override    public X509Certificate[] getAcceptedIssuers() {        return new X509Certificate[0];    }    private String calculateSha256(byte[] data) {        // Placeholder for actual hash calculation logic        return "";    }}

Identify the fully qualified name of the class (e.g., com.example.app.security.CustomTrustManager) and the method (e.g., checkServerTrusted).

Step 3: Dynamic Analysis and Crafting the Frida Hook

Once you’ve identified potential pinning methods from static analysis, confirm them dynamically with Frida and then craft a bypass script.

Tracing Methods with Frida

Before writing the bypass, it’s good practice to trace the suspicious method. This confirms that the application indeed calls it during network communication.

Java.perform(function() {    var CustomTrustManager = Java.use("com.example.app.security.CustomTrustManager");    CustomTrustManager.checkServerTrusted.implementation = function(chain, authType) {        console.log("[+] CustomTrustManager.checkServerTrusted called! authType: " + authType);        for (var i = 0; i < chain.length; i++) {            console.log("    Cert " + i + ": " + chain[i].getSubjectDN().getName());        }        // Call the original method to observe its behavior and confirm it throws an exception        this.checkServerTrusted(chain, authType);    };    console.log("[+] Hooked CustomTrustManager.checkServerTrusted for tracing.");});

Save this as trace_pinning.js and run it: frida -U -f com.example.app -l trace_pinning.js --no-pause. Observe the console output while the app makes network requests. If you see the log messages, you’ve found your target.

Bypassing the Custom Logic

Now, modify the Frida script to bypass the identified method. The goal for checkServerTrusted is to prevent it from throwing a CertificateException by making it return gracefully.

Java.perform(function() {    console.log("Frida: Custom SSL pinning bypass loaded!");    try {        // Target the custom TrustManager identified via JADX        var CustomTrustManager = Java.use("com.example.app.security.CustomTrustManager");        CustomTrustManager.checkServerTrusted.implementation = function(chain, authType) {            console.log("[+] Bypassing custom checkServerTrusted for " + this.$className + "." + this.$methodName);            // This is the core of the bypass: simply return without calling the original            // or performing any checks. This prevents the CertificateException.            return;        };        console.log("[+] CustomTrustManager.checkServerTrusted hooked successfully!");    } catch (e) {        console.error("[-] Error hooking CustomTrustManager: " + e.message);    }    // Add other common pinning bypasses as a fallback or for comprehensive coverage    // This part can be extended to include other common pinning mechanisms if needed,    // but for focused custom pinning bypass, the above is primary.});

Save this script as custom_ssl_bypass.js. The key here is to replace the method’s implementation with an empty function that simply returns. For methods designed to throw exceptions on failure, an early return effectively bypasses the validation.

Step 4: Execution and Verification

With your custom bypass script ready, execute it against the target application:

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

The -f flag spawns the application and immediately attaches Frida. The --no-pause flag ensures the application starts without waiting for a script to finish loading, which is useful for catching early network requests. Observe the Frida console for your script’s output, indicating successful hooking.

Now, configure your Android device to proxy traffic through Burp Suite. Navigate through the application and verify that you can successfully intercept and inspect the application’s HTTPS traffic in Burp Suite without any SSL errors. If successful, you have bypassed the custom SSL pinning implementation.

Conclusion

Bypassing custom SSL pinning requires a methodical approach combining static analysis (with tools like JADX-GUI) to identify the unique pinning logic and dynamic instrumentation (with Frida) to subvert it. While generic Frida scripts are excellent for common pinning mechanisms, understanding how to reverse engineer and craft targeted hooks is essential for tackling more robust and bespoke implementations. This expert-level approach empowers penetration testers to gain full visibility into application network traffic, a critical step in comprehensive security assessments.

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