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¶
- SQLite Database Extraction
- SharedPreferences Analysis
- File System Data Extraction
- Backup Analysis
- Encryption Testing
- 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
Related Reading¶
- Android File System - Storage fundamentals
- Content Provider Testing - Testing data providers
- Dynamic Analysis - Runtime data extraction
- Component Testing - Testing components that access storage
This guide provides comprehensive techniques for testing Android app data storage security. Combine with Static Analysis to identify storage usage patterns in code.