Android App Penetration Testing & Frida Hooks

Frida Scripting Lab: Monitor & Modify Android SharedPreferences Live

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android SharedPreferences and Frida

Android applications frequently use SharedPreferences to store small amounts of primitive data in key-value pairs. This can include user settings, session tokens, feature flags, or even sensitive information (though not recommended for the latter without proper encryption). During penetration testing or security analysis, understanding how an app interacts with its preferences is crucial. Frida, a dynamic instrumentation toolkit, provides an unparalleled capability to inspect and manipulate an app’s runtime behavior, including its interaction with SharedPreferences.

This guide will walk you through setting up a Frida environment to monitor and dynamically modify SharedPreferences values in a live Android application. You’ll learn how to intercept calls, observe data flow, and even inject your own values to alter application logic.

Prerequisites

Before we begin, ensure you have the following tools set up:

  • Rooted Android Device or Emulator: A device or emulator with root access is required to run the Frida server.
  • ADB (Android Debug Bridge): For interacting with the Android device/emulator from your computer.
  • Frida-server: The Frida server running on your Android device.
  • Frida-tools: Installed on your host machine (pip install frida-tools).
  • Basic understanding of JavaScript: Frida scripts are written in JavaScript.

Make sure your Frida server is running on the device. You can typically start it with:adb push frida-server /data/local/tmp/frida-serveradb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &"

Understanding Android SharedPreferences

SharedPreferences are an interface for accessing and modifying preference data. They are backed by XML files stored in the app’s private directory (e.g., /data/data/your.package.name/shared_prefs/). The main methods we’ll focus on are:

  • Context.getSharedPreferences(name, mode): To retrieve a SharedPreferences instance.
  • SharedPreferences.getString(), getInt(), getBoolean(), etc.: To read data.
  • SharedPreferences.Editor.putString(), putInt(), putBoolean(), etc.: To write data.
  • SharedPreferences.Editor.apply() or commit(): To save changes.

Monitoring SharedPreferences Access with Frida

Hooking Context.getSharedPreferences()

First, let’s see which preference files an application is accessing. We can hook the getSharedPreferences method to log its arguments.

Java.perform(function() {    var Context = Java.use("android.content.Context");    Context.getSharedPreferences.implementation = function(name, mode) {        console.log("[*] getSharedPreferences called for file: " + name + ", mode: " + mode);        return this.getSharedPreferences(name, mode);    };    console.log("[+] Hooked Context.getSharedPreferences()");});

Run this script using frida -U -l script.js -f com.example.app --no-pause, replacing com.example.app with your target package name. You will see logs whenever the app requests a SharedPreferences instance.

Intercepting Read Operations

Next, let’s monitor when an app reads specific values. We’ll hook getString, but the same principle applies to getInt, getBoolean, etc.

Java.perform(function() {    var SharedPreferences = Java.use("android.content.SharedPreferences");    SharedPreferences.getString.implementation = function(key, defValue) {        var result = this.getString(key, defValue);        console.log("[*] SharedPreferences.getString(" + key + ") called. Default: " + defValue + ", Result: " + result);        return result;    };    console.log("[+] Hooked SharedPreferences.getString()");});

This script logs the key, default value, and the actual value retrieved. You can combine this with the previous hook to get a full picture of access patterns.

Intercepting Write Operations

To see what data is being written, we need to hook the Editor.putString() methods and then the apply() or commit() methods that finalize the write.

Java.perform(function() {    var Editor = Java.use("android.content.SharedPreferences$Editor");    Editor.putString.implementation = function(key, value) {        console.log("[*] SharedPreferences.Editor.putString(" + key + ", " + value + ") called.");        return this.putString(key, value);    };    Editor.apply.implementation = function() {        console.log("[*] SharedPreferences.Editor.apply() called.");        return this.apply();    };    Editor.commit.implementation = function() {        console.log("[*] SharedPreferences.Editor.commit() called.");        return this.commit();    };    console.log("[+] Hooked SharedPreferences.Editor methods.");});

This comprehensive set of hooks will give you deep insight into how the application reads and writes data through SharedPreferences.

Modifying SharedPreferences Live

Monitoring is useful, but the real power of Frida comes from modifying these values on the fly. This can be used to bypass client-side checks, unlock features, or test different application states.

Changing Read Values (getResult())

We can alter the return value of any get method. For instance, imagine an app checks a boolean preference for a ‘premium’ feature. We can force it to always return true.

Java.perform(function() {    var SharedPreferences = Java.use("android.content.SharedPreferences");    SharedPreferences.getBoolean.implementation = function(key, defValue) {        var originalResult = this.getBoolean(key, defValue);        if (key === "isPremiumUser") {            console.log("[*] Intercepted 'isPremiumUser' read: Original: " + originalResult + ". Forcing to true!");            return true; // Force premium status        }        return originalResult;    };    console.log("[+] Hooked SharedPreferences.getBoolean() to modify 'isPremiumUser'.");});

This script demonstrates a targeted modification. You can extend this logic to modify strings, integers, or other types based on specific keys.

Injecting New Entries or Modifying Existing Ones

Sometimes you might want to inject a new preference entry that the app doesn’t normally create, or change a value before it’s even read. You can obtain a reference to the SharedPreferences object and modify it directly.

Java.perform(function() {    var Context = Java.use("android.content.Context");    var SharedPreferences = null;    // First, get a reference to the SharedPreferences object    Context.getSharedPreferences.implementation = function(name, mode) {        var sp = this.getSharedPreferences(name, mode);        if (name === "app_settings") { // Target a specific preferences file            SharedPreferences = sp;            console.log("[+] Obtained SharedPreferences instance for 'app_settings'.");        }        return sp;    };    // After the app has initialized and hopefully accessed 'app_settings'    setTimeout(function() {        if (SharedPreferences !== null) {            console.log("[*] Modifying 'app_settings' live...");            var editor = SharedPreferences.edit();            editor.putString("injected_feature_flag", "enabled");            editor.putInt("admin_level", 5);            editor.apply();            console.log("[+] Injected 'injected_feature_flag' and 'admin_level'.");            console.log("    Current 'injected_feature_flag': " + SharedPreferences.getString("injected_feature_flag", "default"));            console.log("    Current 'admin_level': " + SharedPreferences.getInt("admin_level", 0));        } else {            console.log("[-] Could not get SharedPreferences instance for 'app_settings'.");        }    }, 5000); // Wait 5 seconds for app to initialize});

This script first hooks getSharedPreferences to capture a reference to the specific preferences file, then uses a setTimeout to wait for the app to initialize before directly editing and applying new values. This allows you to dynamically alter preferences even if the app hasn’t provided a UI for them.

Practical Applications and Next Steps

The techniques demonstrated here are fundamental for a wide range of Android security assessments:

  • Bypassing Feature Restrictions: Modify boolean flags like isProUser, adFree, etc.
  • Altering Application Flow: Change settings that control logic, such as server URLs, debug modes, or analytics opt-ins.
  • Data Leakage Analysis: Identify if sensitive information is being stored insecurely in preferences.
  • Race Condition Testing: Dynamically change preferences to test how an app handles concurrent modifications.

Experiment with different types of preferences and methods. Remember to always use these techniques ethically and on applications you have permission to test.

Conclusion

Frida provides an incredibly powerful and flexible platform for dynamic analysis of Android applications. By mastering its use for monitoring and modifying SharedPreferences, you gain deep control over an app’s internal state. This skill is invaluable for security researchers and developers looking to understand, debug, or uncover vulnerabilities in Android applications. Keep exploring Frida’s capabilities; the possibilities are vast.

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