offsecnotes

Insecure data storage

by frankheat

Overview data storage

Internal storage (app-private internal storage)

This is private device storage where apps save data that only they can access. It’s used to store private app data such as sensitive information, configurations, databases, temporary cache files, or anything that should not be accessed by the user or other apps.

Location: /data/data/package-name/ or /data/user/0/package-name/

Permissions needed: None

Access:

Lifecycle: when the user uninstalls your app, the system automatically deletes all files in this directory

External storage

In Android terminology, “external storage” does not necessarily mean a physical SD card. It means: “storage that’s accessible to both apps and the user (shared, public area)”.

Location:

Note: the “/0” represents the user ID. Android has a multi-user system. The primary user of the phone is user 0

Note: the /sdcard path is an alias (shortcut) that points to your device’s primary external storage.

ls -l /
[...]
lrw-r--r--   1 root   root         21 2023-05-04 18:16 sdcard -> /storage/self/primary
[...]

ls -l /storage/self/primary
lrwxrwxrwx 1 root root 19 2025-10-10 10:00 /storage/self/primary -> /storage/emulated/0

App-Specific External Storage

Location:

Permissions needed: None

Access from Android 11:

Lifecycle: when the user uninstalls your app, these directories and all their contents are also deleted

Tip: if you want to manage files in these folder you can use the system file picker app. To open it you can:

# First try
am start -a android.intent.action.VIEW -n com.google.android.documentsui/com.android.documentsui.files.FilesActivity

# Second try
am start -a android.intent.action.VIEW -n com.android.documentsui/com.android.documentsui.files.FilesActivity

# Third try
am start -a android.intent.action.VIEW -n com.android.documentsui/com.android.documentsui.FilesActivity

Shared Storage

This is the rest of external storage. It’s the public space intended for files that the user expects to be able to access directly and share between apps.

Location (from Android 11):

Keychain

Here we can store cryptographic keys like private keys. On most devices, the keychain is protected in hardware by special security chips. It does not store password, but only cryptographic keys.


Test

Local storage

You need to analyze both internal and external storage.

Storing sensitive data in external storage can expose it to users or malicious actors. On Android versions below 11, any app with the appropriate storage permission could freely read data stored in external storage, including other apps’ files. Although Android 11 and higher introduced scoped storage to restrict this access, sensitive data stored externally can still be exposed if an attacker gains physical access to the device (for example, by connecting it to a computer).

# External storage
<EXTERNAL_STORAGE>/Android/data/com.package.name/
<EXTERNAL_STORAGE>/Android/obb/com.package.name/

# Data app location folder
/data/data/<package_name>

To monitor the storage you can use fsmon or frida by using the following script.

Storage APIs Tracing

Credits: OWASP

If you need to monitor changes in internal storage, update the corresponding value in external_paths.

Note: the script monitor even ContentResolver.insert() because files created via this method are managed by MediaStore as content:// URIs (not file paths), so they can’t be opened with libc open(). []

function printBacktrace(maxLines = 8) {
    Java.perform(() => {
        let Exception = Java.use("java.lang.Exception");
        let stackTrace = Exception.$new().getStackTrace().toString().split(",");
        console.log("\nBacktrace:");
        for (let i = 0; i < Math.min(maxLines, stackTrace.length); i++) {
            console.log(stackTrace[i]);
        }
    });
};

// Intercept libc's open to make sure we cover all Java I/O APIs
Interceptor.attach(
    Process.getModuleByName('libc.so').getExportByName('open'),
    {
        onEnter: function(args) {
            const external_paths = ['/sdcard', '/storage/emulated'];
            const path = args[0].readCString();
            external_paths.forEach(external_path => {
                if (path.indexOf(external_path) === 0) {
                    console.log(`\n[*] open called to open a file from external storage at: ${path}`);
                    printBacktrace(15);
                }
            });
        }
    }
);

// Hook ContentResolver.insert to log ContentValues (including keys like _display_name, mime_type, and relative_path) and returned URI
Java.perform(() => {
    let ContentResolver = Java.use("android.content.ContentResolver");
    ContentResolver.insert.overload('android.net.Uri', 'android.content.ContentValues').implementation = function(uri, values) {
        console.log(`\n[*] ContentResolver.insert called with ContentValues:`);

        console.log(`\t_display_name: ${values.get("_display_name").toString()}`);
        console.log(`\tmime_type: ${values.get("mime_type").toString()}`);
        console.log(`\trelative_path: ${values.get("relative_path").toString()}`);

        let result = this.insert(uri, values);
        console.log(`\n[*] ContentResolver.insert returned URI: ${result.toString()}`);
        printBacktrace();
        return result;
    };
});

Logs

On Android, logging APIs like can accidentally expose sensitive data. Logs go to logcat, which since Android 4.1 is accessible only to system apps with READ_LOGS. However, many pre-installed apps hold this privilege, creating a risk of data leaks. For this reason, directly logging sensitive information to logcat is discouraged. []

# Open the app and then run this command
adb logcat --pid <PID>

It is often better to run adb --clear beforehand to ensure a cleaner environment.


Application memory

# Start objection
objection -g 'exampleapp' explore

# Search a specific string
memory search <input_string> --string

# Dump all and then extract strings
memory dump all appMemoryDump
strings appMemoryDump > appMemoryDump.txt