Android App Penetration Testing & Frida Hooks

Behind the Scenes: How Android SharedPreferences Work & How Frida Hooks Them

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Android SharedPreferences

Android’s SharedPreferences API provides a lightweight mechanism for applications to store and retrieve small amounts of primitive data. This data persists across user sessions and application restarts, making it ideal for preferences, settings, and other non-critical information. While seemingly simple, understanding its underlying mechanics and potential security pitfalls is crucial for both developers and penetration testers.

What are SharedPreferences?

At its core, SharedPreferences stores data in XML files within the application’s private data directory (typically /data/data/<package_name>/shared_prefs/). Each set of preferences corresponds to a distinct XML file. By default, these files are only accessible by the application that created them, thanks to Android’s sandbox security model. However, on a rooted device, or if the app is debuggable, these files can be directly inspected, revealing stored values.

How Apps Use SharedPreferences

Applications interact with SharedPreferences through the getSharedPreferences() method from a Context object, specifying a name for the preference file and a mode (e.g., MODE_PRIVATE). To write data, an Editor object is obtained, values are put, and then apply() or commit() is called. To read, direct getter methods like getString() or getInt() are used.

Here’s a typical pattern in Kotlin:

// Writing to SharedPreferencesfun saveUserData(context: Context, username: String, token: String) {    val sharedPrefs = context.getSharedPreferences("user_data", Context.MODE_PRIVATE)    val editor = sharedPrefs.edit()    editor.putString("username", username)    editor.putString("auth_token", token)    editor.apply() // Asynchronous write} // Reading from SharedPreferencesfun getUserData(context: Context): Pair<String?, String?> {    val sharedPrefs = context.getSharedPreferences("user_data", Context.MODE_PRIVATE)    val username = sharedPrefs.getString("username", null)    val token = sharedPrefs.getString("auth_token", null)    return Pair(username, token)}

Security Considerations of SharedPreferences

Common Misconceptions and Vulnerabilities

Despite being app-private, SharedPreferences are not inherently secure for sensitive data. They are stored unencrypted by default within the app’s sandboxed environment. This leads to several common vulnerabilities:

  • Rooted Devices: On a rooted Android device, the preference XML files can be directly accessed and modified by any user with root privileges.
  • Backup Mechanisms: If Android’s backup features are enabled, SharedPreferences can be backed up to external storage or cloud services, potentially exposing data.
  • World-Readable Mode: While less common now, historically, developers sometimes used MODE_WORLD_READABLE, making the preferences accessible to other applications (now deprecated).
  • Forensic Analysis: During forensic analysis of a device or an application’s data directory, preference files are easily extractable.

Therefore, storing sensitive information like authentication tokens, API keys, or personal identifiable information (PII) directly in SharedPreferences is highly discouraged without proper encryption at the application layer.

Introduction to Frida for Android Hooking

What is Frida?

Frida is a dynamic instrumentation toolkit that allows developers, security researchers, and reverse engineers to inject snippets of JavaScript or custom C code into running processes. For Android, this means you can attach to an application, inspect its memory, hook functions, modify behavior, and observe its runtime activities without recompiling the APK. It’s an indispensable tool for penetration testing and reverse engineering Android applications.

Frida Setup (Brief)

To use Frida, you typically need:

  1. A rooted Android device or emulator.
  2. The frida-server binary running on the Android device.
  3. The frida-tools Python package installed on your host machine.

Once frida-server is running (e.g., adb push frida-server /data/local/tmp/frida-server && adb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &"), you can interact with processes using frida or frida-trace from your host.

Frida Hooking SharedPreferences

Identifying Target Methods

To intercept SharedPreferences access, we need to target key methods within the android.content.SharedPreferences and android.content.SharedPreferences.Editor classes. The most relevant methods for read/write operations are:

  • android.content.SharedPreferences.getString(java.lang.String key, java.lang.String defValue)
  • android.content.SharedPreferences.Editor.putString(java.lang.String key, java.lang.String value)
  • android.content.SharedPreferences.Editor.apply()
  • android.content.SharedPreferences.Editor.commit()

By hooking these methods, we can observe what keys are being accessed, what values are being written, and even modify them on the fly.

Writing a Frida Script to Intercept SharedPreferences Access

The following Frida script demonstrates how to hook these critical SharedPreferences methods. It will log every attempt to read or write a string value, and every time an edit operation is applied or committed.

Java.perform(function() {    console.log("[+] Starting SharedPreferences Hooking Script");    // Hook SharedPreferences.Editor for write operations    var SharedPreferencesEditor = Java.use("android.content.SharedPreferences$Editor");    SharedPreferencesEditor.putString.implementation = function(key, value) {        console.log("[SharedPreferences WRITE] Key: " + key + ", Value: " + value);        var retval = this.putString(key, value);        return retval;    };    SharedPreferencesEditor.apply.implementation = function() {        console.log("[SharedPreferences APPLY] Changes applied.");        return this.apply();    };    SharedPreferencesEditor.commit.implementation = function() {        console.log("[SharedPreferences COMMIT] Changes committed.");        return this.commit();    };    // Hook SharedPreferences for read operations    var SharedPreferences = Java.use("android.content.SharedPreferences");    SharedPreferences.getString.implementation = function(key, defValue) {        var readValue = this.getString(key, defValue);        console.log("[SharedPreferences READ] Key: " + key + ", Read Value: " + readValue + ", Default: " + defValue);        return readValue;    };    console.log("[+] SharedPreferences Hooks Applied");});

Practical Demonstration

Setting up the Environment

Ensure your Android device is connected via ADB and frida-server is running. Save the above Frida script as sp_hook.js on your host machine.

Running the Frida Script

To attach Frida to a running application, you need its package name. Let’s assume the target app is com.example.myapp. You can then execute the Frida script using the following command:

frida -U -f com.example.myapp -l sp_hook.js --no-pause
  • -U: Connect to a USB device.
  • -f com.example.myapp: Spawn and attach to the specified package name.
  • -l sp_hook.js: Load the Frida script.
  • --no-pause: Start the application immediately after injecting the script.

Once the command runs, interact with com.example.myapp. For instance, if the app has a login screen that saves a session token or a settings screen that stores user preferences, perform actions that would trigger SharedPreferences read or write operations. You will observe output similar to this in your terminal:

[+] Starting SharedPreferences Hooking Script[+] SharedPreferences Hooks Applied[SharedPreferences WRITE] Key: username, Value: testuser[SharedPreferences WRITE] Key: auth_token, Value: d24f5a...[SharedPreferences APPLY] Changes applied.[SharedPreferences READ] Key: username, Read Value: testuser, Default: null[SharedPreferences READ] Key: app_language, Read Value: en, Default: en

This output provides real-time visibility into the application’s use of SharedPreferences, revealing the keys and values being handled. This is incredibly useful for identifying where sensitive data might be stored or simply understanding an application’s internal logic.

Conclusion

Understanding how Android SharedPreferences work is fundamental for both secure application development and effective penetration testing. While convenient, their default unencrypted storage makes them unsuitable for highly sensitive data without additional application-level encryption. Frida provides a powerful and flexible way to dynamically inspect and manipulate SharedPreferences operations, offering invaluable insights into an application’s data handling and potential vulnerabilities. By applying these hooking techniques, security researchers can efficiently uncover insecure data storage practices and advise on remediation strategies.

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