Android App Penetration Testing & Frida Hooks

Frida for Android Pen Testers: Intercepting & Modifying Network Traffic

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Frida for Android Network Analysis

In the realm of Android application penetration testing, dynamic analysis plays a crucial role. While static analysis can reveal vulnerabilities in code, understanding an application’s runtime behavior – especially its network interactions – often uncovers critical flaws that are otherwise hidden. Frida, a dynamic instrumentation toolkit, stands out as an indispensable tool for this purpose. It allows pen testers to inject custom JavaScript or Python scripts into running processes on Android devices, providing unparalleled control over an app’s execution flow, including the ability to inspect, intercept, and modify network traffic in real-time.

This article will guide you through using Frida to intercept and modify network traffic generated by Android applications. We’ll focus on common scenarios, specifically targeting popular networking libraries, to demonstrate how Frida empowers you to manipulate data flowing between the app and its backend servers.

Setting Up Your Frida Environment

Prerequisites

  • A rooted Android device or emulator (e.g., Genymotion, Android Studio AVD).
  • ADB (Android Debug Bridge) installed and configured on your host machine.
  • Python 3 installed on your host machine.
  • Basic familiarity with JavaScript.

Installing Frida Server on Android

First, you need to download the Frida server binary corresponding to your Android device’s architecture (ARM, ARM64, x86, x86_64). You can find these on Frida’s GitHub releases page.

  1. Identify your device’s architecture:

    adb shell getprop ro.product.cpu.abi
  2. Download the appropriate frida-server-x.x.x-android-<arch> file to your host machine.

  3. Push the server to your device and set permissions:

    adb push frida-server-x.x.x-android-<arch> /data/local/tmp/frida-server
    adb shell "chmod 755 /data/local/tmp/frida-server"
  4. Start the Frida server on your device. It’s often best to run it in the background:

    adb shell "/data/local/tmp/frida-server &"

Installing Frida Tools on Your Host Machine

Install the Frida Python tools using pip:

pip install frida-tools

Verify installation by listing running processes on your device:

frida-ps -U

Identifying Network Call Targets

Before you can intercept traffic, you need to know which methods to hook. Android apps typically use various libraries for network operations, such as OkHttp, HttpURLConnection, or sometimes custom implementations. Identifying these methods is crucial.

Static Analysis for Method Discovery

Tools like Jadx-GUI or Ghidra can decompile APKs, allowing you to examine the Java/Smali code. Search for keywords like okhttp3, java.net.HttpURLConnection, socket, connect, send, receive, request, response. Look for methods that handle the actual data transmission.

Dynamic Enumeration with Frida-Trace

Frida-trace can help dynamically identify interesting methods by tracing calls. For instance, to trace all methods in the okhttp3 package for an app:

frida-trace -U -f com.example.targetapp -i "okhttp3!*"

This command attaches to com.example.targetapp and traces all methods within the okhttp3 package. Interact with the app, and you’ll see a log of invoked methods, which can guide your specific hooking strategy.

Intercepting and Modifying OkHttp Traffic with Frida

OkHttp is one of the most widely used HTTP clients in Android. Its architecture makes it relatively straightforward to intercept traffic.

Understanding OkHttp’s Structure

OkHttp uses a concept of Interceptors to observe and rewrite requests and responses. However, we can also target core methods that execute the network calls. A common target is okhttp3.Call.execute() or methods within okhttp3.Request and okhttp3.Response.

Frida Script for Request and Response Interception

Let’s craft a Frida script to intercept and modify traffic using an OkHttp client. We’ll target the enqueue method, which is often used for asynchronous network calls, and also the Interceptor chain to gain early access to requests and late access to responses.

Java.perform(function () {    console.log("[*] Frida script loaded: OkHttp Interception");    // Target OkHttpClient.Builder.build() to get a reference to the client    var OkHttpClientBuilder = Java.use('okhttp3.OkHttpClient$Builder');    OkHttpClientBuilder.build.implementation = function () {        var client = this.build();        console.log("[*] OkHttpClient built. Adding custom interceptor.");        // Create a new Interceptor        var Interceptor = Java.use('okhttp3.Interceptor');        var CustomInterceptor = Java.registerClass({            name: 'com.example.frida.CustomInterceptor',            implements: [Interceptor],            methods: {                intercept: function (chain) {                    var request = chain.request();                    console.log("n[+] Intercepting Request:");                    console.log("    URL: " + request.url().toString());                    console.log("    Method: " + request.method());                    console.log("    Headers:");                    var requestHeaders = request.headers();                    for (var i = 0; i < requestHeaders.size(); i++) {                        console.log("        " + requestHeaders.name(i) + ": " + requestHeaders.value(i));                    }                    if (request.body()) {                        // To read the body, we need to buffer it.                        // Note: Reading body consumes it, so we need to reconstruct if modifying.                        var requestBody = request.body();                        var Buffer = Java.use('okio.Buffer');                        var buffer = Buffer.$new();                        requestBody.writeTo(buffer);                        var bodyString = buffer.readUtf8();                        console.log("    Body: " + bodyString);                        // Example modification: Add a custom header if a specific body is found                        if (bodyString.includes("frida_test_data")) {                            console.log("[*] Modifying request: Adding X-Frida-Modified-Request header.");                            request = request.newBuilder()                                       .addHeader('X-Frida-Modified-Request', 'true')                                       .build();                        }                    }                    var response = chain.proceed(request);                    console.log("n[+] Intercepting Response:");                    console.log("    Code: " + response.code());                    console.log("    Message: " + response.message());                    console.log("    Headers:");                    var responseHeaders = response.headers();                    for (var i = 0; i < responseHeaders.size(); i++) {                        console.log("        " + responseHeaders.name(i) + ": " + responseHeaders.value(i));                    }                    if (response.body()) {                        var responseBody = response.body();                        var Buffer = Java.use('okio.Buffer');                        var buffer = Buffer.$new();                        responseBody.source().readAll(buffer);                        var bodyString = buffer.readUtf8();                        console.log("    Body: " + bodyString);                        // Example modification: Modify response body on the fly                        if (response.code() === 200 && bodyString.includes("success")) {                            console.log("[*] Modifying response: Changing 'success' to 'FRIDA_SUCCESS'.");                            var newBodyString = bodyString.replace(/success/g, "FRIDA_SUCCESS");                            var ResponseBody = Java.use('okhttp3.ResponseBody');                            var MediaType = Java.use('okhttp3.MediaType');                            return response.newBuilder()                                           .body(ResponseBody.create(MediaType.parse(response.body().contentType().toString()), newBodyString))                                           .build();                        }                    }                    return response;                }            }        });        // Add our custom interceptor to the client        var newClientBuilder = client.newBuilder();        newClientBuilder.addInterceptor(CustomInterceptor.$new());        return newClientBuilder.build();    };});

Step-by-Step Execution

Let’s assume your target application’s package name is com.example.targetapp.

  1. Save the above JavaScript code as okhttp_hook.js.

  2. Run Frida, attaching it to the target application. The -l flag specifies the script to load.

    frida -U -l okhttp_hook.js -f com.example.targetapp --no-pause

    The --no-pause flag allows the app to start immediately without waiting for your input.

  3. Interact with the Android application to trigger network requests. You will see detailed request and response information, along with any modifications, printed in your terminal where Frida is running.

Modifying Network Traffic

Altering Request Headers and Body

In the provided script, we demonstrate how to modify a request:

  • We add a custom header X-Frida-Modified-Request: true if the request body contains the string “frida_test_data”.

To implement more complex modifications, you can use conditional logic within the intercept method. For instance, you could change the URL, alter specific headers, or completely rewrite the request body before it’s sent to the server. Remember that modifying the request body often requires re-creating the request object using its builder pattern, as the original body might be consumed.

Manipulating Response Data

The script also showcases response modification:

  • If a 200 OK response contains the string “success” in its body, we replace it with “FRIDA_SUCCESS”.

Similar to requests, you can change response codes, headers, or entire response bodies. This is incredibly powerful for testing how an application handles unexpected server responses, forging API responses to bypass client-side checks, or escalating privileges by faking success messages.

Beyond OkHttp: Generalizing Interception Techniques

While we focused on OkHttp, the principles apply to other networking libraries:

  • HttpURLConnection: Hook methods like connect(), getOutputStream(), getInputStream(), setRequestProperty(), and getResponseCode().
  • Retrofit: Since Retrofit builds upon OkHttp, hooking OkHttp is often sufficient. Otherwise, target Retrofit’s internal invocation handlers.
  • Custom Sockets: For raw socket communication, you might need to hook lower-level Java Socket methods (e.g., java.net.Socket.getOutputStream().write()) or even native C/C++ network calls if the application uses NDK for networking.

Conclusion

Frida is an unparalleled tool for dynamic analysis of Android applications, particularly when it comes to network traffic manipulation. By leveraging its powerful instrumentation capabilities, pen testers can go beyond simple observation, actively altering requests and responses to uncover vulnerabilities that might otherwise remain hidden. Mastering these techniques transforms an auditor’s ability to assess the true security posture of an Android application, making Frida an essential component of any advanced Android penetration testing toolkit.

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