Skip to content

Android Storage Security Testing - Finding Data That Shouldn't Be There

Overview - The Hunt for Sensitive Data

Look, here's the reality: most Android apps store sensitive data somewhere. Passwords, tokens, API keys, user data - it's all on the device in one form or another. The question isn't "if" - it's "where" and "how securely."

Storage security testing is about finding where apps store sensitive information and determining whether that storage is actually secure or just security theater. We're going to tear apart databases, dig through XML files, and extract backups to see what secrets apps are hiding.

What We're Looking For:

  • Credentials: Passwords, tokens, API keys stored in plaintext
  • Personal Data: User information, contacts, messages stored insecurely
  • Application Secrets: Hardcoded keys, encryption passwords, session tokens
  • Cached Data: Temporary files containing sensitive information
  • Backup Data: Full backups that can be extracted and analyzed

The Tools We'll Use:

  • ADB: Our trusty Swiss Army knife
  • SQLite: For database extraction and analysis
  • Python Scripts: For automated extraction and parsing
  • File System Access: Root or run-as for data directories
  • Backup Tools: For extracting full app backups

Prerequisites: Understanding of Android File System and ADB Essentials. If you're not comfortable with ADB and Android's file system, go back and read those first. Trust me, it'll save you time.

Table of Contents

  1. SQLite Database Extraction
  2. SharedPreferences Analysis
  3. File System Data Extraction
  4. Backup Analysis
  5. Encryption Testing
  6. Keystore Analysis

SQLite Database Extraction - Where Your Data Actually Lives

SQLite is Android's default database engine, and most apps use it extensively. User accounts, settings, cached content, authentication tokens - if it's structured data, it's probably in a SQLite database. The good news? SQLite databases are relatively easy to extract and analyze. The bad news? Many developers don't encrypt them.

Locating Databases - The Treasure Hunt Begins

Why This Matters:

Databases often contain the crown jewels: - User credentials (hopefully hashed, but often not) - Authentication tokens - Personal information - Cached API responses - Session data - User preferences and settings

Find All Databases:

# Using ADB (requires root)
adb shell find /data/data -name "*.db" -type f
# This finds EVERY database on the device
# Warning: Can be slow on devices with many apps

# For specific app (faster)
adb shell find /data/data/com.target.app -name "*.db" -type f

# List databases directory (if you know it exists)
adb shell ls -la /data/data/com.target.app/databases/
# Shows all .db files, plus .db-wal and .db-journal files

Common Database Locations:

Main Database Directory: - /data/data/<package>/databases/ - Primary location for app databases - Most apps store their main database here - Typical names: main.db, app.db, <package_name>.db, database.db

Database Files to Look For: - .db files - The actual database - .db-wal files - Write-Ahead Log (contains uncommitted transactions!) - .db-journal files - Journal files (legacy, older SQLite versions) - .db-shm files - Shared memory files

Pro Tip:

The .db-wal file is often overlooked but can contain: - Recent transactions not yet merged into main DB - Deleted data that hasn't been cleaned up - Recently inserted sensitive information - Always extract both .db and .db-wal files for complete data

Finding Databases the Smart Way:

# Get package name first
PACKAGE="com.target.app"

# Check if databases directory exists
adb shell ls /data/data/$PACKAGE/databases/ 2>/dev/null

# Find all database-related files
adb shell find /data/data/$PACKAGE -type f \( -name "*.db" -o -name "*.db-wal" -o -name "*.db-journal" \)

# Get file sizes (helps identify which databases are important)
adb shell find /data/data/$PACKAGE -name "*.db" -exec ls -lh {} \;

What Each Database Type Contains:

User Data Databases: - Authentication tokens - User profiles - Preferences - Session data

Cache Databases: - Cached API responses - Temporary data - May contain sensitive info developers forgot about

Analytics Databases: - User behavior tracking - Events and logs - Sometimes contains PII

The Reality:

Most apps have multiple databases. One for user data, one for cache, one for analytics, maybe one for offline sync. Check them all. The analytics database might have user emails. The cache database might have API tokens. The "offline" database might have everything synced from the server.

Extracting Databases - Getting the Data Out

The Challenge:

App data directories are protected by UID-based permissions. Other apps can't read them. Even you can't read them without special access. That's good security. But for testing, we need to get in.

Method 1: Root Access (The Easy Way)

If you have root access, this is straightforward:

# Copy entire databases directory
adb shell "su -c 'cp -r /data/data/com.target.app/databases/ /sdcard/db_backup/'"
adb pull /sdcard/db_backup ./db_analysis/

# Or tar everything (includes databases, shared_prefs, files, cache)
adb shell "su -c 'tar -czf /sdcard/appdata.tgz /data/data/com.target.app'"
adb pull /sdcard/appdata.tgz .
tar -xzf appdata.tgz

Why Root Works: - Root (UID 0) can read any file - No permission checks - Can access all app data directories - Fastest and most reliable method

Method 2: run-as (The Clever Way)

If the app is debuggable, you can use run-as to execute commands as the app's UID:

# First, check if app is debuggable
adb shell dumpsys package com.target.app | grep debuggable
# If output shows debuggable=true, you can use run-as

# List databases using app's UID
adb shell run-as com.target.app ls /data/data/com.target.app/databases/

# Copy database via run-as (one file at a time)
adb shell run-as com.target.app \
  "cat /data/data/com.target.app/databases/main.db" > main.db

# Copy WAL file too (important!)
adb shell run-as com.target.app \
  "cat /data/data/com.target.app/databases/main.db-wal" > main.db-wal

Limitations of run-as: - Only works if app is debuggable - Can't write files as the app (read only) - Must extract files one at a time - Some apps disable debuggable in release builds

Method 3: Backup API (The Sneaky Way)

Android's backup API can sometimes extract app data without root:

# Create backup (may prompt on device)
adb backup -f app_backup.ab -apk com.target.app

# Extract backup (see Backup Analysis section for details)
# Backup format is tricky - it's a custom format with zlib compression

Pro Tip - Extract Everything:

Don't just extract the main database. Get everything:

PACKAGE="com.target.app"
OUT_DIR="./extracted_${PACKAGE}"

mkdir -p "$OUT_DIR"/databases

# Extract all .db files
for db in $(adb shell "su -c 'find /data/data/$PACKAGE -name \"*.db\"'"); do
    db_name=$(basename "$db")
    adb shell "su -c 'cat $db'" > "$OUT_DIR/databases/$db_name"
done

# Extract WAL files too
for wal in $(adb shell "su -c 'find /data/data/$PACKAGE -name \"*.db-wal\"'"); do
    wal_name=$(basename "$wal")
    adb shell "su -c 'cat $wal'" > "$OUT_DIR/databases/$wal_name"
done

The Reality:

Most testing happens with root access because: - It's faster - More reliable - Can access all apps - No need to worry about debuggable flags

But knowing alternative methods is valuable when root isn't available.

Analyzing Databases

Interactive SQLite Shell:

# On device (if sqlite3 available)
adb shell sqlite3 /data/data/com.target.app/databases/main.db

# Locally after extraction
sqlite3 main.db

Common SQLite Queries:

-- List all tables
.tables

-- View table schema
.schema users

-- Query data
SELECT * FROM users;
SELECT * FROM users WHERE email='admin@example.com';

-- Export to CSV
.mode csv
.output users.csv
SELECT * FROM users;

-- Find sensitive columns
SELECT sql FROM sqlite_master WHERE sql LIKE '%password%' OR sql LIKE '%token%';

Automated Analysis Script:

#!/usr/bin/env python3
import sqlite3
import os

def analyze_database(db_path):
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    # Get all tables
    cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
    tables = cursor.fetchall()

    print(f"Database: {db_path}")
    print(f"Tables: {len(tables)}")

    sensitive_keywords = ['password', 'token', 'secret', 'key', 'credential', 'auth']

    for table in tables:
        table_name = table[0]
        print(f"\n=== Table: {table_name} ===")

        # Get schema
        cursor.execute(f"PRAGMA table_info({table_name})")
        columns = cursor.fetchall()

        # Check for sensitive column names
        for col in columns:
            if any(keyword in col[1].lower() for keyword in sensitive_keywords):
                print(f"[!] Sensitive column found: {col[1]}")
                # Extract sample data
                cursor.execute(f"SELECT {col[1]} FROM {table_name} LIMIT 5")
                samples = cursor.fetchall()
                print(f"    Samples: {samples}")

        # Count rows
        cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
        count = cursor.fetchone()[0]
        print(f"    Rows: {count}")

    conn.close()

# Usage
for db_file in os.listdir('./db_analysis/'):
    if db_file.endswith('.db'):
        analyze_database(f'./db_analysis/{db_file}')

Related reading: See Android Basics - SQLite Databases for database fundamentals.


SharedPreferences Analysis

Locating SharedPreferences

# List all preference files
adb shell ls -la /data/data/com.target.app/shared_prefs/

# Find preference files
adb shell find /data/data/com.target.app -name "*.xml" | grep shared_prefs

Extracting SharedPreferences

Root Method:

adb shell "su -c 'cp /data/data/com.target.app/shared_prefs/*.xml /sdcard/'"
adb pull /sdcard/*.xml ./prefs/

Non-Root Method:

adb shell run-as com.target.app \
  cat /data/data/com.target.app/shared_prefs/default.xml > default.xml

Analyzing Preferences

View XML Content:

cat ./prefs/*.xml | xmllint --format -

Search for Sensitive Data:

grep -iE "token|password|key|secret|auth|credential" ./prefs/*.xml
grep -E "(https?://[^\s]+)" ./prefs/*.xml  # URLs

Parse Preferences Script:

#!/usr/bin/env python3
import xml.etree.ElementTree as ET
import glob

def parse_preferences(xml_file):
    tree = ET.parse(xml_file)
    root = tree.getroot()

    sensitive_keywords = ['token', 'password', 'key', 'secret', 'auth']

    print(f"\n=== File: {xml_file} ===")
    for child in root:
        key = child.get('name')
        value = child.text if child.text else child.get('value')

        if any(keyword in key.lower() for keyword in sensitive_keywords):
            print(f"[!] Sensitive: {key} = {value}")
        else:
            print(f"    {key} = {value}")

# Analyze all preference files
for xml_file in glob.glob('./prefs/*.xml'):
    parse_preferences(xml_file)

Related reading: See Android Basics - Shared Preferences for format details.


File System Data Extraction

App Files Directory

List Files:

adb shell ls -laR /data/data/com.target.app/files/

Extract Files:

# Entire files directory
adb shell "su -c 'tar -czf /sdcard/files.tgz /data/data/com.target.app/files/'"
adb pull /sdcard/files.tgz .
tar -xzf files.tgz

Cache Analysis

Extract Cache:

adb shell ls -la /data/data/com.target.app/cache/
adb shell "su -c 'cp -r /data/data/com.target.app/cache/ /sdcard/cache_backup/'"
adb pull /sdcard/cache_backup ./cache_analysis/

External Storage

SD Card Analysis:

# App-specific external storage
adb shell ls -la /sdcard/Android/data/com.target.app/

# General external files
adb shell find /sdcard -name "*target*" -o -name "*app*"


Backup Analysis

ADB Backup Extraction

Create Backup:

adb backup -f backup.ab -apk -shared -all
# OR for specific app
adb backup -f app_backup.ab -apk com.target.app

Extract Backup (Unencrypted):

# Backup format: 24-byte header + zlib-compressed tar
dd if=backup.ab bs=1 skip=24 | python3 -c "import zlib,sys;sys.stdout.buffer.write(zlib.decompress(sys.stdin.buffer.read()))" | tar -xvf -

Extract Backup Script:

#!/usr/bin/env python3
import zlib
import tarfile
import sys

def extract_backup(backup_file, output_dir):
    with open(backup_file, 'rb') as f:
        # Skip 24-byte header
        f.seek(24)

        # Decompress zlib
        data = zlib.decompress(f.read())

        # Extract tar
        with tarfile.open(fileobj=open(backup_file, 'rb')) as tar:
            # Actually we need to write decompressed data first
            with open('temp.tar', 'wb') as temp:
                temp.write(data)

            with tarfile.open('temp.tar', 'r') as tar:
                tar.extractall(output_dir)

extract_backup('backup.ab', './extracted_backup/')

Analyzing Backup Contents

# Find databases in backup
find extracted_backup/ -name "*.db"

# Find preferences
find extracted_backup/ -name "*.xml" | grep shared_prefs

# Find sensitive files
find extracted_backup/ -type f -exec grep -l "password\|token\|secret" {} \;

Encryption Testing

Identifying Encryption

Search for Encryption Usage:

jadx target.apk -d output
grep -r "Cipher\|AES\|DES\|RSA" output/sources/
grep -r "KeyStore\|SecretKey\|KeyGenerator" output/sources/

Testing Encryption Implementation

Common Issues: - Hardcoded encryption keys - Weak encryption algorithms (DES, ECB mode) - Keys stored in SharedPreferences or code - Improper IV generation

Frida Hook for Encryption:

Java.perform(function() {
    var Cipher = Java.use('javax.crypto.Cipher');

    Cipher.doFinal.overload('[B').implementation = function(input) {
        var result = this.doFinal(input);
        console.log('[Cipher] Algorithm: ' + this.getAlgorithm());
        console.log('[Cipher] Input: ' + input);
        console.log('[Cipher] Output: ' + result);
        return result;
    };
});

Related reading: See Cryptography for encryption concepts.


Keystore Analysis

Android Keystore System

Access Keystore (Requires Root):

# Keystore files location
adb shell find /data/misc/keystore -name "*"
adb shell find /data/system/users/0/ -name "*.keystore"

Analyze Keystore (if accessible):

# Note: Keystore files are typically encrypted and inaccessible
# Testing focuses on app's usage of Keystore API

Keystore Usage Testing

Frida Hook for Keystore:

Java.perform(function() {
    var KeyStore = Java.use('java.security.KeyStore');

    KeyStore.getKey.implementation = function(alias, password) {
        console.log('[KeyStore] Getting key: ' + alias);
        var key = this.getKey(alias, password);
        if (key) {
            console.log('[KeyStore] Key type: ' + key.getClass().getName());
        }
        return key;
    };
});


Storage Testing Checklist

  • Databases extracted and analyzed for sensitive data
  • SharedPreferences examined for tokens and credentials
  • Files directory searched for sensitive information
  • Cache files analyzed for leaked data
  • Backups extracted and analyzed
  • External storage checked for app data
  • Encryption implementation reviewed
  • Hardcoded secrets identified in code
  • Keystore usage verified


This guide provides comprehensive techniques for testing Android app data storage security. Combine with Static Analysis to identify storage usage patterns in code.