Android App Penetration Testing & Frida Hooks

Beyond the Basics: Advanced Frida Scripts for Automated Android Insecure Data Storage Discovery & Extraction

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Insecure Data Storage and Frida

Insecure Data Storage (IDS) remains a persistent and critical vulnerability in Android applications. It occurs when sensitive user or application data is stored in locations accessible to other applications, or without proper protection, allowing unauthorized access. According to the OWASP Mobile Application Security Verification Standard (MASVS), proper data protection is a cornerstone of secure mobile development. Manually combing through an application’s storage directories for such vulnerabilities can be a tedious and time-consuming process. This article delves into leveraging Frida, a powerful dynamic instrumentation toolkit, to automate the discovery and extraction of insecurely stored data on Android, moving beyond basic hooking to more advanced, systematic approaches.

Frida allows us to inject custom scripts into running processes, enabling us to hook into application functions, modify their behavior, and inspect data in real-time. This capability is invaluable for penetration testers and security researchers aiming to identify how, where, and what data an application is storing.

Prerequisites

Before diving into the advanced scripting, ensure you have the following setup:

  • A rooted Android device or emulator (e.g., AVD, Genymotion, Nox Player)
  • Frida server running on the Android device
  • Frida-tools installed on your host machine (`pip install frida-tools`)
  • Basic understanding of JavaScript and Android architecture

Understanding Android Data Storage Mechanisms

Android provides several storage options, each with different access permissions and typical use cases. Insecure implementations often stem from misunderstanding these mechanisms or misconfiguring their access controls.

Internal Storage (Application-specific files)

Internal storage is designed for application-specific data and is typically private to the app. Paths like `/data/data/YOUR_PACKAGE_NAME/files/` and `/data/data/YOUR_PACKAGE_NAME/cache/` are common. However, if files are created with modes like `MODE_WORLD_READABLE` or `MODE_WORLD_WRITABLE` (though deprecated, still found), or if the app’s internal directories themselves have lax permissions, other apps or even unprivileged users might access them.

External Storage (Shared Storage)

External storage (e.g., SD card or shared internal storage partition) is publicly readable and writable. While `Context.getExternalFilesDir()` provides an app-specific directory on external storage that is cleared on uninstall, legacy or poorly implemented apps might still use `Environment.getExternalStorageDirectory()` which points to a globally accessible directory. Data stored here should always be considered public and encrypted if sensitive.

Shared Preferences

Shared Preferences (`Context.getSharedPreferences()`) provide a lightweight mechanism to store key-value pairs of primitive data. These are typically stored as XML files within `/data/data/YOUR_PACKAGE_NAME/shared_prefs/`. While usually private to the app, misconfigurations (e.g., `MODE_WORLD_READABLE`) can expose these files, revealing sensitive configuration data, tokens, or user information.

SQLite Databases

Android applications commonly use SQLite databases for structured data storage. These databases are typically located in `/data/data/YOUR_PACKAGE_NAME/databases/`. Similar to Shared Preferences, if created with world-readable/writable flags or if the database directory itself is exposed, the entire database content can be compromised.

Frida for Automated Discovery of Data Storage Access

The core idea behind using Frida for IDS discovery is to hook into the Android API calls responsible for creating and interacting with various storage mechanisms. By doing so, we can log every instance an application attempts to write data, noting the path, mode, and sometimes even the content.

Hooking File I/O for Internal Storage

We can intercept calls to `openFileOutput()` to see what files an application creates in its internal storage and with what permissions.

Java.perform(function() {Java.use('android.app.ContextImpl').openFileOutput.implementation = function(name, mode) {console.log("[Frida] openFileOutput called: File = " + name + ", Mode = " + mode);var result = this.openFileOutput(name, mode);if (mode === 1 || mode === 2) { // MODE_WORLD_READABLE (1) or MODE_WORLD_WRITABLE (2)console.warn("[Frida-IDS] Insecure File Creation Detected: " + name + " with mode: " + mode);}return result;};console.log("[*] Hooked openFileOutput");});

To run this script:

frida -U -f com.example.insecureapp -l file_io_hook.js --no-pause

This script will print details every time `openFileOutput` is invoked, highlighting potentially insecure modes.

Intercepting Shared Preferences Access

Monitoring `getSharedPreferences()` helps us identify the names of preference files. Furthermore, we can hook `putString()`, `putInt()`, etc., on the `SharedPreferences.Editor` object to capture the actual key-value pairs being stored.

Java.perform(function() {var SharedPreferencesImpl = Java.use('android.app.SharedPreferencesImpl');SharedPreferencesImpl.constructor.overload('java.io.File', 'int').implementation = function(file, mode) {console.log("[Frida] SharedPreferences created: File = " + file.getAbsolutePath() + ", Mode = " + mode);if (mode === 1 || mode === 2) { // MODE_WORLD_READABLE (1) or MODE_WORLD_WRITABLE (2)console.warn("[Frida-IDS] Insecure SharedPreferences Creation: " + file.getAbsolutePath() + " with mode: " + mode);}return this.constructor.overload('java.io.File', 'int').call(this, file, mode);};var EditorImpl = Java.use('android.app.SharedPreferencesImpl$EditorImpl');EditorImpl.putString.overload('java.lang.String', 'java.lang.String').implementation = function(key, value) {console.log("[Frida] SharedPreferences putString: Key = " + key + ", Value = " + value);return this.putString(key, value);};EditorImpl.putInt.overload('java.lang.String', 'int').implementation = function(key, value) {console.log("[Frida] SharedPreferences putInt: Key = " + key + ", Value = " + value);return this.putInt(key, value);};console.log("[*] Hooked SharedPreferences");});

This script captures both the creation of Shared Preferences files and the individual key-value pairs being written, offering a comprehensive view of sensitive data storage in preferences.

Monitoring SQLite Database Operations

To detect insecure database storage, we can hook `openOrCreateDatabase()`. This reveals the database name and its creation mode.

Java.perform(function() {Java.use('android.app.ContextImpl').openOrCreateDatabase.overload('java.lang.String', 'int', 'android.database.sqlite.SQLiteDatabase$CursorFactory').implementation = function(name, mode, factory) {console.log("[Frida] openOrCreateDatabase called: DB Name = " + name + ", Mode = " + mode);if (mode === 1 || mode === 2) { // MODE_WORLD_READABLE (1) or MODE_WORLD_WRITABLE (2)console.warn("[Frida-IDS] Insecure Database Creation: " + name + " with mode: " + mode);}return this.openOrCreateDatabase(name, mode, factory);};Java.use('android.app.ContextImpl').openOrCreateDatabase.overload('java.lang.String', 'int', 'android.database.sqlite.SQLiteDatabase$CursorFactory', 'android.database.DatabaseErrorHandler').implementation = function(name, mode, factory, errorHandler) {console.log("[Frida] openOrCreateDatabase called: DB Name = " + name + ", Mode = " + mode);if (mode === 1 || mode === 2) { // MODE_WORLD_READABLE (1) or MODE_WORLD_WRITABLE (2)console.warn("[Frida-IDS] Insecure Database Creation: " + name + " with mode: " + mode);}return this.openOrCreateDatabase(name, mode, factory, errorHandler);};console.log("[*] Hooked openOrCreateDatabase");});

This script will log the database file names and their associated modes when they are created or opened, allowing you to quickly identify databases with potentially weak permissions.

Automated Extraction and Further Analysis

Once you’ve discovered paths to sensitive files or databases, the next step is extraction and analysis. Frida can aid in identifying these paths, which you can then pull using `adb`.

Pulling Files and Databases via ADB

After Frida logs a suspicious file path, you can use `adb pull` to extract it. For files in `/data/data/APP_PACKAGE/`, direct `adb pull` often works on rooted devices. If not, you may need to copy it to a world-readable location first.

# If direct pull works:adb pull /data/data/com.example.insecureapp/shared_prefs/my_prefs.xml. # If direct pull fails due to permissions (e.g., on some devices or non-root context):adb shell su -c "cp /data/data/com.example.insecureapp/files/sensitive.txt /sdcard/Download/sensitive.txt"adb pull /sdcard/Download/sensitive.txtrm /sdcard/Download/sensitive.txt

For SQLite databases, you can use tools like `sqlitebrowser` (DB Browser for SQLite) to inspect their contents after pulling.

Enumerating All Storage Artifacts (Advanced)

Beyond hooking specific I/O calls, you can use Frida to programmatically enumerate an application’s entire private storage directory. This provides a holistic view of all files and directories created by the app, irrespective of whether they were explicitly hooked.

Java.perform(function() {var File = Java.use('java.io.File');var Application = Java.use('android.app.Application');var currentApplication = Application.currentApplication();if (currentApplication) {var filesDir = currentApplication.getFilesDir();var cacheDir = currentApplication.getCacheDir();var dataDir = filesDir.getParentFile(); // Typically /data/data/PACKAGE_NAMEconsole.log("[Frida] Listing contents of application data directory: " + dataDir.getAbsolutePath());function listDirectoryRecursive(directory) {if (directory === null || !directory.exists()) {return;}var files = directory.listFiles();if (files) {for (var i = 0; i < files.length; i++) {var file = files[i];if (file.isDirectory()) {console.log("[Frida-Dir] " + file.getAbsolutePath());listDirectoryRecursive(file);} else {console.log("[Frida-File] " + file.getAbsolutePath() + " (Size: " + file.length() + " bytes)");}}}}listDirectoryRecursive(dataDir);} else {console.error("[*] Could not get current application context.");}});

This script obtains the application’s root data directory (e.g., `/data/data/com.example.app`) and then recursively lists all files and subdirectories within it. This is extremely powerful for identifying unexpected files or directories that might contain sensitive data, even if they weren’t created via standard Android API calls or were missed by specific I/O hooks. It gives you a complete inventory of an app’s internal storage footprint.

Conclusion

Frida provides an unparalleled level of introspection into Android applications, making it an indispensable tool for identifying insecure data storage vulnerabilities. By combining targeted API hooking with programmatic directory enumeration, security researchers can automate a significant portion of the discovery process, uncovering sensitive files, databases, and Shared Preferences that might otherwise be overlooked. Remember to always conduct such tests ethically and with proper authorization, reporting any findings responsibly to the application developers. Mastering these advanced Frida techniques will significantly enhance your Android penetration testing capabilities.

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