Android App Penetration Testing & Frida Hooks

How to Bypass Android Shared Preferences Security with Frida Hooks

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: The Perils of Shared Preferences

Android’s Shared Preferences API provides a lightweight mechanism for applications to store private primitive data in key-value pairs. It’s designed for simple settings, user preferences, and small amounts of application state. While convenient and seemingly innocuous, developers often misuse Shared Preferences to store sensitive information such as API keys, session tokens, user IDs, or even flags indicating administrative privileges. This misjudgment frequently stems from an underestimation of the security implications, assuming that since the data is ‘private’ to the app, it is inherently secure.

The data stored via Shared Preferences is typically saved in XML files within the app’s private data directory (e.g., /data/data/<package_name>/shared_prefs/<preference_name>.xml). While these files are generally inaccessible to other unprivileged applications, a rooted device or an attacker with physical access can easily bypass these filesystem-level protections. Even without root, if an application is debuggable, an attacker can use tools like `run-as` to access these files, compromising any sensitive data within.

Why Traditional File System Access Falls Short

For penetration testers, the first instinct to examine Shared Preferences might be to directly access the XML files from the device’s filesystem. This approach involves:

adb shellsu # or run-as com.example.targetappcd /data/data/com.example.targetapp/shared_prefs/ls -lcat my_prefs.xml

However, this method has several limitations. Firstly, it requires a rooted device or a debuggable application to gain sufficient permissions. Secondly, even if you can read the files, you’re only seeing the static state of the preferences at the time of access. You miss dynamic changes, how values are used by the application, or if they are encrypted before being written to disk (though this is rare for Shared Preferences). More sophisticated applications might also employ custom `SharedPreferences` implementations or encrypt data in memory before passing it to the `Editor` methods, making direct file inspection insufficient.

This is where dynamic instrumentation shines. By hooking into the application’s runtime, we can intercept calls to the Shared Preferences API as they happen, gaining full visibility into the data being read and written, irrespective of file permissions or on-disk encryption.

Enter Frida: Dynamic Instrumentation for Android

Frida is a powerful, cross-platform dynamic instrumentation toolkit that allows you to inject scripts into running processes. For Android penetration testing, Frida enables you to hook into Java methods, native functions, and even modify their behavior on the fly. This capability is invaluable for understanding application logic, bypassing security controls, and extracting sensitive information that might otherwise be hidden.

This tutorial assumes you have a basic understanding of Frida and have it set up on your testing environment (host machine with `frida-tools` and a rooted Android device with `frida-server` running). If not, you’ll need to install `frida-tools` via pip and deploy `frida-server` to your device and run it.

# On your host machine:pip install frida-tools# On your Android device (as root):adb push frida-server-<version>-android-<arch> /data/local/tmp/frida-serveradb shell"chmod 755 /data/local/tmp/frida-server"adb shell"/data/local/tmp/frida-server &"

Identifying and Hooking Shared Preferences Access

To fully understand and manipulate Shared Preferences, we need to target several key areas:

Hooking `ContextWrapper.getSharedPreferences`

The first step is to identify when an application is requesting a Shared Preferences instance. Applications typically obtain a `SharedPreferences` object by calling `getSharedPreferences(String name, int mode)` from a `Context` or `ContextWrapper` instance. By hooking this method, we can log which preference files are being accessed and with what permissions.

Java.perform(function () {    var ContextWrapper = Java.use("android.content.ContextWrapper");    ContextWrapper.getSharedPreferences.implementation = function (name, mode) {        console.log("[+] getSharedPreferences called for: '" + name + "' with mode: " + mode);        // Call the original method to ensure the app functions correctly        return this.getSharedPreferences(name, mode);    };});

To run this script against a target application (replace `com.example.targetapp` with the actual package name):

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

This will print the name and mode every time an app retrieves a `SharedPreferences` object, giving you insight into the preference files being used.

Intercepting Shared Preferences Writes (`Editor.putString`, `Editor.apply`, `Editor.commit`)

To capture data being written to Shared Preferences, we need to hook the `Editor` interface methods. Specifically, `putString`, `putBoolean`, `putInt`, etc., and the `apply()` or `commit()` methods which persist the changes.

Java.perform(function () {    var Editor = Java.use("android.content.SharedPreferences$Editor");    // Intercept putString method    Editor.putString.implementation = function (key, value) {        console.log("[+] SharedPreferences.Editor.putString() called. Key: '" + key + "', Value: '" + value + "'");        // You can modify the value here if needed:        // if (key === "sensitive_token") {        //     console.log("[*] Modifying sensitive_token to 'BYPASSED_TOKEN_FRIDA'");        //     return this.putString(key, "BYPASSED_TOKEN_FRIDA");        // }        return this.putString(key, value); // Call original method    };    // Intercept putBoolean method for flags    Editor.putBoolean.implementation = function (key, value) {        console.log("[+] SharedPreferences.Editor.putBoolean() called. Key: '" + key + "', Value: " + value);        return this.putBoolean(key, value);    };    // Intercept apply() for asynchronous writes    Editor.apply.implementation = function () {        console.log("[+] SharedPreferences.Editor.apply() called. Changes being committed asynchronously.");        return this.apply();    };    // Intercept commit() for synchronous writes    Editor.commit.implementation = function () {        console.log("[+] SharedPreferences.Editor.commit() called. Changes being committed synchronously.");        return this.commit();    };});

This script provides comprehensive logging of all `putString` and `putBoolean` operations, along with when changes are applied or committed. This is extremely useful for identifying when and what sensitive data is being saved.

Intercepting Shared Preferences Reads (`SharedPreferences.getString`, `getBoolean`, etc.)

Equally important is intercepting data reads. By hooking the getter methods, we can see what data the application retrieves from Shared Preferences and even modify it before it’s used by the app’s logic. This allows for powerful runtime manipulation.

Java.perform(function () {    var SharedPreferences = Java.use("android.content.SharedPreferences");    // Intercept getString method    SharedPreferences.getString.implementation = function (key, defValue) {        var retrievedValue = this.getString(key, defValue);        console.log("[+] SharedPreferences.getString() called. Key: '" + key + "', Default: '" + defValue + "', Retrieved: '" + retrievedValue + "'");        // Example: Modifying a sensitive flag or token on-the-fly        if (key === "isAdminFlag" && retrievedValue === "false") {            console.log("[*] Bypassing isAdminFlag. Changing from 'false' to 'true'.");            return "true"; // Inject our desired value        }        if (key === "api_token" && retrievedValue === "some_old_token") {            console.log("[*] Intercepted old API token. Injecting a new one.");            return "NEW_INJECTED_API_TOKEN_12345";        }        return retrievedValue; // Return the original or modified value    };    // Intercept getBoolean method    SharedPreferences.getBoolean.implementation = function (key, defValue) {        var retrievedValue = this.getBoolean(key, defValue);        console.log("[+] SharedPreferences.getBoolean() called. Key: '" + key + "', Default: " + defValue + ", Retrieved: " + retrievedValue);        if (key === "premium_unlocked" && !retrievedValue) {            console.log("[*] Bypassing 'premium_unlocked'. Changing from 'false' to 'true'.");            return true;        }        return retrievedValue;    };});

This script demonstrates how to not only log the retrieved values but also actively modify them. This is crucial for bypassing license checks, elevating privileges, or injecting test data into the application’s runtime. For instance, if an app checks `getBoolean(

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