Android App Penetration Testing & Frida Hooks

Reverse Engineering Android Apps: Pinpoint and Neutralize Certificate Pinning Logic with Frida

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction

Certificate pinning is a security mechanism implemented by mobile applications to prevent man-in-the-middle (MITM) attacks. Instead of relying solely on the device’s trust store, applications “pin” specific server certificates or public keys, only accepting connections that present one of these pre-defined certificates. While this enhances security, it presents a significant hurdle for penetration testers and security researchers who need to intercept and analyze application traffic for vulnerabilities.

Generic SSL unpinning scripts often fail when applications employ custom or sophisticated pinning implementations. This article delves into an advanced methodology to pinpoint specific certificate pinning logic within an Android application using Frida, a dynamic instrumentation toolkit, and then craft targeted hooks to neutralize it.

Prerequisites

Tools Required

  • Rooted Android Device or Emulator: Necessary for running Frida server.
  • ADB (Android Debug Bridge): For interacting with the Android device.
  • Frida & Frida-Server: The dynamic instrumentation toolkit.
  • Python 3: For running Frida client scripts.
  • Burp Suite (or similar proxy): To intercept and analyze HTTPS traffic.
  • Objection: A runtime mobile exploration toolkit built on Frida (useful for initial reconnaissance).
  • Jadx-GUI (or similar decompiler): To analyze the application’s source code if needed.

Understanding Certificate Pinning in Android

Certificate pinning can be implemented in several ways:

  • TrustManager Interface: Overriding checkServerTrusted() methods in X509TrustManager is a common approach.
  • Network Security Configuration (Android 7+): Declaring pinning rules in res/xml/network_security_config.xml.
  • Popular Libraries: Libraries like OkHttp and Retrofit offer built-in pinning capabilities (e.g., CertificatePinner).
  • Custom Implementations: Developers might write their own logic, often involving byte array comparisons of certificate hashes or public keys.

Initial Detection and Generic Bypasses

Proxy Setup and Observation

First, configure your proxy (e.g., Burp Suite) and the Android device to route traffic through it. Install Burp’s CA certificate on the device. When the application fails to connect, or shows an error message like “network error” or “secure connection failed,” it’s a strong indicator of SSL pinning.

# On your Android device, set proxy (replace IP and Port) adb shell settings put global http_proxy 192.168.1.100:8080 # To clear proxy adb shell settings put global http_proxy :0

Leveraging Generic Frida Scripts

Begin by trying well-known generic SSL unpinning scripts. Tools like Objection often bundle these. If running directly via Frida:

# Start frida-server on device adb push /path/to/frida-server /data/local/tmp/frida-server adb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &" # Run a generic unpinning script (e.g., 'frida-multiple-unpinning') frida -U -f com.example.app -l universal-android-ssl-pinning-bypass.js --no-pause

If generic scripts fail, the application likely uses a custom or less common pinning implementation, requiring a more targeted approach.

Pinpointing Custom Pinning Logic with Frida-Trace

Frida-trace is invaluable for identifying specific methods involved in certificate validation. Our goal is to find the exact method where the application decides to trust or reject a certificate.

Identifying Target Methods

Start by tracing common certificate validation methods:

  • javax.net.ssl.X509TrustManager.checkServerTrusted
  • okhttp3.CertificatePinner.check
  • java.security.cert.Certificate.getPublicKey
  • Any custom methods involving `SSLSession` or `SSLContext`.
# Trace X509TrustManager methods frida-trace -U -f com.example.app -i "*TrustManager.checkServerTrusted*" --decorate # Trace OkHttp CertificatePinner frida-trace -U -f com.example.app -i "*CertificatePinner.check*" --decorate # Trace all methods of a specific class (if you know it from decompilation) frida-trace -U -f com.example.app -i "com.example.app.security.CustomPinningClass.*" --decorate

Observe the application’s behavior when initiating a network request. Look for stack traces or method calls that immediately precede the connection failure. If `checkServerTrusted` is called and returns an exception, that’s your target.

Analyzing Trace Output

Frida-trace generates a `__handlers__` directory containing JavaScript files for each traced method. Examine these handlers. For `checkServerTrusted`, you might see a handler like:

// _X509TrustManager.checkServerTrusted_.js onEnter: function (log, args, state) { log('checkServerTrusted called with: ' + args[0] + ', ' + args[1]); }, onLeave: function (log, retval, state) { log('checkServerTrusted returned: ' + retval); }

By running `frida-trace` and interacting with the app, you’ll see a console output revealing when these methods are invoked. If `checkServerTrusted` is called and then an exception is thrown, it’s a strong candidate. You might need to examine the arguments (e.g., the certificates being evaluated) to understand the pinning logic.

Crafting a Targeted Frida Hook

Once the specific pinning method is identified, a custom Frida script can be written to bypass it.

Example: Bypassing TrustManager.checkServerTrusted()

If `checkServerTrusted` is the culprit, we can hook it to simply return without throwing an exception.

// bypass_trustmanager.js Java.perform(function () { console.log("[*] Starting custom TrustManager bypass..."); try { var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); var SSLContext = Java.use('javax.net.ssl.SSLContext'); // Hooking all implementations of checkServerTrusted X509TrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String').implementation = function (chain, authType) { console.log('[+] checkServerTrusted hooked: ' + authType); // Bypass implementation this.checkServerTrusted(chain, authType); // Original call (if needed, but usually we just return) }; X509TrustManager.checkServerTrusted.overload('[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String').implementation = function (chain, authType, host) { console.log('[+] checkServerTrusted (with host) hooked: ' + authType + ' on ' + host); // Bypass implementation this.checkServerTrusted(chain, authType, host); // Original call }; // If the app creates its own custom TrustManager, we might need to target the constructor // and replace it with a dummy one. // Example: Replacing the default TrustManager in SSLContext.init() var TrustManagerFactory = Java.use('javax.net.ssl.TrustManagerFactory'); TrustManagerFactory.getTrustManagers.implementation = function () { console.log('[+] TrustManagerFactory.getTrustManagers hooked!'); var trustManagers = this.getTrustManagers(); return Java.array('javax.net.ssl.TrustManager', [Java.cast(getDummyTrustManager(), X509TrustManager)]); }; function getDummyTrustManager() { return Java.implement('javax.net.ssl.X509TrustManager', { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return Java.array('java.security.cert.X509Certificate', []); } }); } console.log("[*] TrustManager bypass setup complete."); } catch (e) { console.error("[!!!] TrustManager bypass error: " + e.message); } });

Example: OkHttp CertificatePinner Bypass

If the app uses OkHttp’s `CertificatePinner`, you can target its `check` method or even prevent its creation.

// bypass_okhttp_pinner.js Java.perform(function() { console.log("[*] Starting custom OkHttp CertificatePinner bypass..."); try { var CertificatePinner = Java.use('okhttp3.CertificatePinner'); // Hooking the check method to prevent pinning checks CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function (hostname, certificates) { console.log('[+] OkHttp CertificatePinner.check hooked for: ' + hostname); // Simply do nothing, effectively bypassing the check // this.check(hostname, certificates); // Do not call original method }; // Alternative: Prevent CertificatePinner from being added to OkHttpClient.Builder var OkHttpClientBuilder = Java.use('okhttp3.OkHttpClient$Builder'); OkHttpClientBuilder.certificatePinner.implementation = function (certificatePinner) { console.log('[+] OkHttpClient.Builder.certificatePinner called. Removing pinner!'); return this; // Return self without setting the pinner }; console.log("[*] OkHttp CertificatePinner bypass setup complete."); } catch (e) { console.error("[!!!] OkHttp CertificatePinner bypass error: " + e.message); } });

Deployment and Verification

Deploy your custom Frida script using:

frida -U -f com.example.app -l bypass_trustmanager.js --no-pause # Or frida -U -f com.example.app -l bypass_okhttp_pinner.js --no-pause

After running the script, try to make the network request again within the application. Monitor your Burp Suite proxy. If traffic now flows through, you have successfully bypassed the specific certificate pinning implementation.

Conclusion

Bypassing custom certificate pinning logic in Android applications demands a methodical approach. While generic scripts are a good starting point, Frida-trace and targeted scripting are essential tools for tackling more robust implementations. By understanding the underlying pinning mechanisms and leveraging Frida’s powerful instrumentation capabilities, security researchers can effectively neutralize these defenses, enabling comprehensive security assessments of mobile applications.

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