Android Software Reverse Engineering & Decompilation

Understanding & Exploiting Android Network Security Configs with Frida for SSL Bypass

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to SSL Pinning and Network Security Configs

In the landscape of mobile application security, SSL (Secure Sockets Layer) pinning is a critical defense mechanism employed by developers to prevent man-in-the-middle (MitM) attacks. By ensuring that an application only communicates with a server presenting a pre-defined, trusted certificate, SSL pinning significantly enhances data integrity and confidentiality. However, for security researchers, penetration testers, or reverse engineers, this robust protection often becomes a barrier. Intercepting network traffic is crucial for analyzing application behavior, identifying vulnerabilities, or understanding underlying APIs. This article delves into Android’s Network Security Configuration (NSC) feature, a modern approach to implementing SSL pinning, and demonstrates how to effectively bypass it using Frida, a powerful dynamic instrumentation toolkit.

Understanding NSC is paramount as it provides a declarative, XML-based approach for configuring network security settings without modifying application code. This includes custom trust anchors, debug-only overrides, and most importantly for our purposes, certificate pinning.

What are Android Network Security Configs?

Introduced in Android 7.0 (API level 24), Network Security Configuration allows apps to customize their network security settings in a declarative XML file. This eliminates the need for complex, programmatic SSL/TLS setup and provides a more secure and robust way to manage trust. Key features of NSC include:

  • Custom Trust Anchors: Defining which Certificate Authorities (CAs) are trusted for secure connections.
  • Debug Overrides: Allowing different trust settings for debug builds, simplifying development and testing.
  • Cleartext Traffic Opt-Out: Preventing accidental use of cleartext HTTP traffic.
  • Certificate Pinning: Restricting connections to specific certificates or public keys.

For SSL pinning, developers typically include a “ element within their `network_security_config.xml` file, specifying the hashes of trusted public keys. An application configured with such a pin-set will refuse to connect to any server whose certificate chain does not include one of the specified public keys, even if the certificate is issued by a globally trusted CA.

Example: Network Security Config for Pinning

Here’s how a typical `network_security_config.xml` file might look when implementing certificate pinning:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <pin-set expiration="2025-01-01">
            <pin digest="sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" />
            <pin digest="sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" />
        </pin-set>
    </domain-config>
</network-security-config>

This configuration specifies that all connections to `example.com` and its subdomains must present a certificate whose public key hash matches one of the provided SHA256 digests. This is what we aim to bypass.

Setting Up Your Environment for Frida

Before diving into the bypass, ensure you have the necessary tools set up:

  1. Rooted Android Device or Emulator: Frida requires root access on the target device.
  2. ADB (Android Debug Bridge): For interacting with your Android device.
  3. Frida-server: The Frida agent running on the Android device.
  4. Frida-tools: Python tools for interacting with `frida-server` from your host machine.
  5. Proxy Tool (e.g., Burp Suite, OWASP ZAP): To intercept and analyze traffic.

Installation Steps:

# On your host machine
pip install frida-tools

# Download frida-server matching your device's architecture
# Example for ARM64
wget https://github.com/frida/frida/releases/download/16.1.4/frida-server-16.1.4-android-arm64.xz
xz -d frida-server-16.1.4-android-arm64.xz

# Push frida-server to your device and start it
adb push frida-server-16.1.4-android-arm64 /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server-16.1.4-android-arm64"
adb shell "/data/local/tmp/frida-server-16.1.4-android-arm64 &"

# Forward the Frida port
adb forward tcp:27042 tcp:27042

Verify Frida is running by executing `frida-ps -U` on your host. It should list processes on your device.

The Frida Approach: Overriding Trust Managers

Android’s Network Security Configs ultimately influence the behavior of the underlying `javax.net.ssl.X509TrustManager` instances. To bypass SSL pinning, we need to inject a custom `X509TrustManager` that accepts all certificates, effectively nullifying the pinning logic. Frida’s Java interoperability allows us to hook into Java methods, register new classes, and replace existing objects at runtime.

Our strategy involves:

  1. Creating a custom `X509TrustManager` that has empty `checkClientTrusted` and `checkServerTrusted` methods (i.e., it trusts everything).
  2. Hooking `javax.net.ssl.SSLContext.init` to replace the application’s default `TrustManager[]` array with an array containing our custom, permissive `TrustManager`.
  3. Optionally, hooking `okhttp3.OkHttpClient.Builder` and `TrustManagerFactory.getTrustManagers` for broader compatibility, as many Android apps use OkHttp for networking and `TrustManagerFactory` to obtain `TrustManager` instances.

Frida Script for Universal SSL Unpinning

Below is a comprehensive Frida script designed to bypass most forms of SSL pinning, including those enforced by Network Security Configs:

Java.perform(function() {
    console.log("[*] Starting Frida SSL unpinning script...");

    var TrustManager = Java.use('javax.net.ssl.X509TrustManager');
    var SSLContext = Java.use('javax.net.ssl.SSLContext');

    // Create a custom TrustManager that trusts all certificates
    var TrustManagerImpl = Java.registerClass({
        name: 'com.r00t.SSLTrustManager',
        implements: [TrustManager],
        methods: {
            checkClientTrusted: function(chain, authType) {
                console.log("[+] Client trusted: " + authType);
            },
            checkServerTrusted: function(chain, authType) {
                console.log("[+] Server trusted: " + authType);
            },
            getAcceptedIssuers: function() {
                return [];
            }
        }
    });

    // Hook SSLContext.init to replace the TrustManager
    SSLContext.init.overload('[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom').implementation = function(keyManagers, trustManagers, secureRandom) {
        console.log('[*] Overriding TrustManager[] in SSLContext.init');
        var myTrustManagerArray = Java.array('javax.net.ssl.TrustManager', [TrustManagerImpl.$new()]);
        this.init(keyManagers, myTrustManagerArray, secureRandom);
    };

    // Attempt to hook OkHttp's SSL setup
    try {
        var OkHttpClient = Java.use('okhttp3.OkHttpClient');
        OkHttpClient.newBuilder.implementation = function() {
            console.log('[*] OkHttpClient.newBuilder() called. Bypassing certificate pinning.');
            var builder = this.newBuilder();
            builder.sslSocketFactory(SSLContext.getInstance("TLS").getSocketFactory(), TrustManagerImpl.$new());
            builder.hostnameVerifier(Java.use('javax.net.ssl.HostnameVerifier').$new({
                verify: function(hostname, session) {
                    return true;
                }
            }));
            return builder;
        };
    } catch (e) {
        console.log('[-] OkHttpClient not found, skipping OkHttp bypass: ' + e.message);
    }

    // Attempt to hook TrustManagerFactory if possible (less common for direct pinning but good for completeness)
    try {
        var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
        TrustManagerFactory.getTrustManagers.implementation = function() {
            console.log("[*] Intercepting TrustManagerFactory.getTrustManagers()");
            return Java.array("javax.net.ssl.TrustManager", [TrustManagerImpl.$new()]);
        };
    } catch (e) {
        console.log("[-] TrustManagerFactory not found or not accessible, skipping TrustManagerFactory bypass: " + e.message);
    }

    console.log("[*] Frida SSL unpinning script loaded successfully.");
});

Executing the Bypass and Verification

Once you have the script (e.g., named `unpinning.js`), you can inject it into a running Android application using Frida. First, ensure your proxy tool (e.g., Burp Suite) is correctly configured on your Android device (either globally or per-app for newer Android versions) and its CA certificate is installed and trusted.

Frida Command to Inject:

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

Replace `com.example.app` with the package name of your target application. The `–no-pause` flag ensures the application starts immediately with the script injected.

After running this command, launch your proxy tool and attempt to use the target application. You should now observe its HTTPS traffic being successfully intercepted and decrypted by your proxy. The console output from Frida will also provide feedback on which hooks were successfully triggered, indicating the bypass is active.

Conclusion

Android Network Security Configs represent a robust and developer-friendly way to enhance app security, especially concerning SSL pinning. However, as demonstrated, powerful dynamic instrumentation frameworks like Frida can effectively circumvent these protections for legitimate security research and testing purposes. By understanding the underlying Java APIs that NSC ultimately relies upon, and leveraging Frida’s ability to modify runtime behavior, we can successfully bypass SSL pinning and gain valuable insight into an application’s network communications. Always remember to 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