npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
MITRE ATLAS
When to Use
Use this skill when:
- Assessing whether mobile applications store sensitive data securely on the device filesystem
- Testing for credential leakage through SharedPreferences, SQLite databases, or plists
- Evaluating keychain/keystore implementation for proper access control attributes
- Performing data-at-rest security assessment during mobile penetration tests
Do not use this skill on production user devices without authorization -- data extraction techniques require physical access or root/jailbreak privileges.
Prerequisites
- Rooted Android device or emulator with ADB access
- Jailbroken iOS device with SSH access or Objection-patched IPA
- ADB (Android Debug Bridge) for Android filesystem access
- SQLite3 CLI for database inspection
- Frida/Objection for runtime data extraction
- Target application installed and exercised (logged in, data cached)
Legal Notice: This skill is for authorized security testing and educational purposes only. Unauthorized use against systems you do not own or have written permission to test is illegal and may violate computer fraud laws.
Workflow
Step 1: Map Application Data Storage Locations
Android storage paths:
# Internal storage (app-private, requires root)
/data/data/<package_name>/
├── shared_prefs/ # SharedPreferences XML files
├── databases/ # SQLite databases
├── files/ # General files
├── cache/ # Cached data
├── lib/ # Native libraries
└── app_webview/ # WebView data
# External storage (world-readable on older Android)
/sdcard/Android/data/<package_name>/
# Check for world-readable files
adb shell run-as <package_name> ls -la /data/data/<package_name>/iOS storage paths:
# App sandbox (accessible via SSH on jailbroken device)
/var/mobile/Containers/Data/Application/<UUID>/
├── Documents/ # User data, backed up by default
├── Library/
│ ├── Preferences/ # NSUserDefaults plists
│ ├── Caches/ # Cache data
│ └── Application Support/
└── tmp/ # Temporary filesStep 2: Extract and Analyze SharedPreferences (Android)
# Pull SharedPreferences files
adb shell run-as <package_name> cat shared_prefs/*.xml
# Or on rooted device
adb pull /data/data/<package_name>/shared_prefs/ ./shared_prefs/
# Search for sensitive data
grep -ri "password\|token\|secret\|key\|session\|auth\|cookie" shared_prefs/Common insecure storage patterns:
<!-- Plaintext credentials -->
<string name="user_password">mysecretpass123</string>
<string name="auth_token">eyJhbGciOiJIUzI1NiIs...</string>
<string name="api_key">sk-live-abc123def456</string>
<!-- Sensitive PII -->
<string name="user_ssn">123-45-6789</string>
<string name="credit_card">4111111111111111</string>Step 3: Analyze SQLite Databases
# Pull databases
adb pull /data/data/<package_name>/databases/ ./databases/
# Open and inspect
sqlite3 databases/app.db
.tables
.schema users
SELECT * FROM users;
SELECT * FROM sessions;
SELECT * FROM tokens;
# Search all tables for sensitive columns
sqlite3 databases/app.db ".dump" | grep -i "password\|token\|secret\|credit"Check for unencrypted SQLCipher databases:
# If database opens without password, it's unencrypted
sqlite3 databases/app.db "SELECT count(*) FROM sqlite_master;"
# Success = unencrypted (vulnerability)Step 4: Inspect iOS Keychain Storage
# Using Objection
objection --gadget com.target.app explore
ios keychain dump
# Check protection class attributes
# kSecAttrAccessibleWhenUnlocked - OK for most data
# kSecAttrAccessibleAlways - VULNERABLE: accessible even when locked
# kSecAttrAccessibleAfterFirstUnlock - acceptable for background appsStep 5: Assess External Storage and Backup Exposure
Android:
# Check if backup is enabled
aapt dump badging target.apk | grep -i "allowBackup"
# android:allowBackup="true" = vulnerability
# Extract backup data
adb backup -f backup.ab -apk <package_name>
java -jar abe.jar unpack backup.ab backup.tar
tar xvf backup.tar
# Inspect extracted data for sensitive information
# Check external storage
adb shell ls -la /sdcard/Android/data/<package_name>/iOS:
# Check backup exclusion
# Files in Documents/ are backed up by default
# Check NSURLIsExcludedFromBackupKey attribute
objection --gadget com.target.app explore
ios plist cat Info.plistStep 6: Runtime Memory Analysis
# Dump process memory for sensitive data
objection --gadget com.target.app explore
memory search "password" --string
memory search "BEGIN RSA PRIVATE KEY" --string
memory dump all /tmp/memdump/
# Android: Check for sensitive data in logs
adb logcat -d | grep -i "password\|token\|key\|secret"Key Concepts
| Term | Definition |
|---|---|
| SharedPreferences | Android key-value storage in XML format; often misused for storing credentials in plaintext |
| Keychain Services | iOS secure credential storage backed by Secure Enclave hardware on modern devices |
| Android Keystore | Hardware-backed cryptographic key storage on Android; keys cannot be extracted from the device |
| SQLCipher | Transparent encryption extension for SQLite databases; prevents data extraction without password |
| Data Protection API | iOS file-level encryption tied to device passcode; controlled via protection class attributes |
Tools & Systems
- ADB (Android Debug Bridge): Command-line tool for Android device interaction and filesystem access
- Objection: Frida-powered runtime exploration for keychain dumping and memory inspection
- SQLite3: Command-line interface for inspecting unencrypted SQLite databases
- Android Backup Extractor (ABE): Tool for unpacking ADB backup files to inspect stored data
- iExplorer: GUI tool for browsing iOS app sandbox filesystem
Common Pitfalls
- Encrypted but key in code: Some apps encrypt databases but store the encryption key in SharedPreferences or hardcoded in the binary. Always check for key storage alongside encryption.
- MODE_WORLD_READABLE deprecation: This flag was deprecated in API 17, but legacy apps may still use it, making SharedPreferences readable by other apps.
- iOS backup scope: By default, all files in the Documents directory are included in iTunes/iCloud backups. Verify that sensitive files have the backup exclusion attribute set.
- Clipboard exposure: Data copied to clipboard is accessible to all apps. Check if the app copies sensitive data (passwords, tokens) to the clipboard.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.6 KB
API Reference: Insecure Mobile Data Storage Detection
OWASP Mobile Top 10 — M9: Insecure Data Storage
Risk Areas
| Storage Type | Platform | Risk |
|---|---|---|
| SharedPreferences | Android | HIGH (plaintext XML) |
| SQLite databases | Both | CRITICAL if unencrypted |
| Keychain (improper) | iOS | MEDIUM |
| External storage | Android | HIGH (world-readable) |
| Plist files | iOS | HIGH (plaintext) |
Android Data Locations
App Private Storage
/data/data/<package>/shared_prefs/ # SharedPreferences XML
/data/data/<package>/databases/ # SQLite databases
/data/data/<package>/files/ # App files
/data/data/<package>/cache/ # Cache dataExternal Storage (World-Readable)
/sdcard/Android/data/<package>/ADB Commands
Pull App Data
adb pull /data/data/com.target.app/ ./extracted/List SharedPreferences
adb shell run-as com.target.app ls /data/data/com.target.app/shared_prefs/Read SharedPreferences
adb shell run-as com.target.app cat shared_prefs/credentials.xmlSQLite Analysis
Python sqlite3
import sqlite3
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
for table in cursor.fetchall():
cursor.execute(f"PRAGMA table_info({table[0]})")
print(cursor.fetchall())iOS Data Locations
App Sandbox
/var/mobile/Containers/Data/Application/<UUID>/
Documents/
Library/Preferences/ # NSUserDefaults (plist)
Library/Caches/
tmp/Keychain
# Using keychain-dumper
./keychain-dumper -aFrida Scripts for Data Storage Audit
Hook SharedPreferences (Android)
Java.perform(function() {
var sp = Java.use("android.app.SharedPreferencesImpl$EditorImpl");
sp.putString.implementation = function(key, value) {
console.log("SharedPrefs PUT: " + key + " = " + value);
return this.putString(key, value);
};
});Hook NSUserDefaults (iOS)
var NSUserDefaults = ObjC.classes.NSUserDefaults;
var orig = NSUserDefaults["- setObject:forKey:"];
Interceptor.attach(orig.implementation, {
onEnter: function(args) {
console.log("NSUserDefaults: " + ObjC.Object(args[3]) + " = " + ObjC.Object(args[2]));
}
});Secure Storage Alternatives
| Platform | Secure Method |
|---|---|
| Android | EncryptedSharedPreferences, Android Keystore |
| iOS | Keychain Services with kSecAttrAccessible |
| Both | SQLCipher for encrypted databases |
standards.md2.8 KB
Standards Reference: Insecure Data Storage in Mobile
OWASP Mobile Top 10 2024 Mapping
| OWASP ID | Risk | Data Storage Relevance |
|---|---|---|
| M1 | Improper Credential Usage | Hardcoded credentials in SharedPreferences, plists, databases |
| M6 | Inadequate Privacy Controls | PII stored unencrypted, accessible via backup extraction |
| M8 | Security Misconfiguration | allowBackup=true, world-readable files, missing encryption |
| M9 | Insecure Data Storage | Primary focus: all local storage vulnerabilities |
| M10 | Insufficient Cryptography | Weak encryption of local databases, hardcoded keys |
OWASP MASVS v2.0 - MASVS-STORAGE Controls
| Control | Description | Test Method |
|---|---|---|
| MASVS-STORAGE-1 | App securely stores sensitive data | Inspect SharedPreferences, keychain, databases |
| MASVS-STORAGE-2 | App prevents sensitive data leakage | Check logs, clipboard, backups, screenshots |
NIST SP 800-163 Rev 1 - Mobile App Vetting
- Section 4.3.1: Data storage analysis for sensitive information at rest
- Section 4.3.2: Verification of encryption implementation for stored data
- Section 5.2: Data protection requirements for enterprise mobile apps
CWE Mappings
| CWE ID | Title | Storage Type |
|---|---|---|
| CWE-312 | Cleartext Storage of Sensitive Information | SharedPreferences, plists, SQLite |
| CWE-316 | Cleartext Storage in Memory | Process memory, clipboard |
| CWE-359 | Exposure of Private Personal Information | PII in unencrypted databases |
| CWE-522 | Insufficiently Protected Credentials | Passwords in SharedPreferences |
| CWE-532 | Information Exposure Through Log Files | Sensitive data in logcat/syslog |
| CWE-921 | Storage of Sensitive Data in Unprotected Mechanism | External storage, world-readable |
| CWE-922 | Insecure Storage of Sensitive Information | General insecure storage |
Android Keystore Best Practices
| Practice | Secure | Insecure |
|---|---|---|
| Key storage | Android Keystore (hardware-backed) | Hardcoded in APK or SharedPreferences |
| Database encryption | SQLCipher with Keystore-derived key | Unencrypted SQLite |
| Shared Preferences | EncryptedSharedPreferences (Jetpack) | MODE_PRIVATE without encryption |
| File encryption | AES-256-GCM with Keystore key | Plaintext files in internal storage |
iOS Data Protection Classes
| Class | When Accessible | Use Case |
|---|---|---|
| NSFileProtectionComplete | Only when unlocked | Highly sensitive data |
| NSFileProtectionCompleteUnlessOpen | While open/unlocked | Files written in background |
| NSFileProtectionCompleteUntilFirstUserAuthentication | After first unlock | Background-accessible data |
| NSFileProtectionNone | Always | Non-sensitive cached data |
workflows.md3.2 KB
Workflows: Exploiting Insecure Data Storage in Mobile
Workflow 1: Android Data Storage Assessment
[Install & exercise app] --> [Root/ADB access] --> [Extract internal storage]
|
+------------------+------------------+
| | |
[SharedPreferences] [SQLite DBs] [File system]
[Grep for secrets] [Open & query] [Check permissions]
[Check encryption] [Check SQLCipher] [External storage]
| | |
+------------------+------------------+
|
[Backup extraction]
[ADB backup test]
|
[Memory analysis]
[Logcat review]
|
[Report findings]Workflow 2: iOS Data Storage Assessment
[Install & exercise app] --> [Jailbreak/Objection] --> [Extract sandbox data]
|
+------------------+------------------+
| | |
[Keychain dump] [Plist analysis] [SQLite DBs]
[Protection class] [NSUserDefaults] [Core Data]
[Access control] [Sensitive values] [Encryption check]
| | |
+------------------+------------------+
|
[Backup inclusion check]
[Memory string search]
[Clipboard monitoring]
|
[Report findings]Decision Matrix: Data Storage Risk
| Storage Mechanism | Encrypted | Access Restricted | Backup Excluded | Risk Level |
|---|---|---|---|---|
| SharedPreferences (plaintext) | No | App-only | No | CRITICAL |
| EncryptedSharedPreferences | Yes | App-only | Depends | LOW |
| SQLite (no SQLCipher) | No | App-only | No | HIGH |
| SQLCipher (key in code) | Yes* | App-only | No | MEDIUM |
| Android Keystore | Yes | Hardware-backed | N/A | LOW |
| iOS Keychain (kSecAttrAccessibleAlways) | Yes | Always accessible | N/A | MEDIUM |
| iOS Keychain (complete protection) | Yes | When unlocked only | N/A | LOW |
| External storage | No | World-readable | N/A | CRITICAL |
Scripts 2
agent.py6.5 KB
#!/usr/bin/env python3
"""Agent for detecting insecure data storage in mobile applications."""
import argparse
import json
import os
import re
import subprocess
import sqlite3
from datetime import datetime, timezone
ANDROID_SENSITIVE_PATHS = [
"/data/data/{package}/shared_prefs/",
"/data/data/{package}/databases/",
"/data/data/{package}/files/",
"/data/data/{package}/cache/",
"/sdcard/Android/data/{package}/",
]
_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
SENSITIVE_PATTERNS = {
"api_key": re.compile(r'["\']?api[_-]?key["\']?\s*[:=]\s*["\']([^"\']+)', re.I),
"token": re.compile(r'["\']?(?:access|auth|bearer)[_-]?token["\']?\s*[:=]\s*["\']([^"\']+)', re.I),
"password": re.compile(r'["\']?password["\']?\s*[:=]\s*["\']([^"\']+)', re.I),
"private_key": re.compile(r'-----BEGIN (?:RSA )?PRIVATE KEY-----'),
"base64_cred": re.compile(r'["\']?(?:auth|credential)["\']?\s*[:=]\s*["\']([A-Za-z0-9+/=]{20,})', re.I),
}
def scan_shared_prefs(prefs_dir):
"""Scan Android SharedPreferences XML files for sensitive data."""
findings = []
if not os.path.isdir(prefs_dir):
return findings
for fname in os.listdir(prefs_dir):
if not fname.endswith(".xml"):
continue
fpath = os.path.join(prefs_dir, fname)
try:
with open(fpath, "r", errors="replace") as f:
content = f.read()
for pattern_name, pattern in SENSITIVE_PATTERNS.items():
matches = pattern.findall(content)
if matches:
findings.append({
"file": fpath,
"type": "shared_prefs",
"pattern": pattern_name,
"match_count": len(matches),
"severity": "HIGH",
})
except PermissionError:
pass
return findings
def scan_sqlite_databases(db_dir):
"""Scan SQLite databases for unencrypted sensitive data."""
findings = []
if not os.path.isdir(db_dir):
return findings
for fname in os.listdir(db_dir):
if not fname.endswith((".db", ".sqlite", ".sqlite3")):
continue
fpath = os.path.join(db_dir, fname)
try:
conn = sqlite3.connect(fpath)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
for (table_name,) in tables:
if not _SAFE_TABLE_RE.match(table_name):
continue
cursor.execute(f"PRAGMA table_info([{table_name}])")
columns = cursor.fetchall()
sensitive_cols = []
for col in columns:
col_name = col[1].lower()
for sf in ["password", "token", "secret", "key", "ssn", "credit"]:
if sf in col_name:
sensitive_cols.append(col[1])
if sensitive_cols:
cursor.execute(f"SELECT COUNT(*) FROM [{table_name}]")
row_count = cursor.fetchone()[0]
findings.append({
"file": fpath,
"table": table_name,
"sensitive_columns": sensitive_cols,
"row_count": row_count,
"encrypted": False,
"severity": "CRITICAL",
})
conn.close()
except (sqlite3.Error, PermissionError):
pass
return findings
def scan_file_storage(files_dir):
"""Scan app file storage for sensitive data."""
findings = []
if not os.path.isdir(files_dir):
return findings
for root, _, files in os.walk(files_dir):
for fname in files:
fpath = os.path.join(root, fname)
try:
with open(fpath, "r", errors="replace") as f:
content = f.read(4096)
for pattern_name, pattern in SENSITIVE_PATTERNS.items():
if pattern.search(content):
findings.append({
"file": fpath,
"pattern": pattern_name,
"severity": "HIGH",
})
except (PermissionError, UnicodeDecodeError):
pass
return findings
def adb_pull_app_data(package_name, output_dir):
"""Pull application data via ADB for analysis."""
os.makedirs(output_dir, exist_ok=True)
paths = [p.format(package=package_name) for p in ANDROID_SENSITIVE_PATHS]
results = []
for path in paths:
try:
subprocess.check_output(
["adb", "pull", path, output_dir],
text=True, errors="replace", timeout=15
)
results.append({"path": path, "status": "pulled"})
except subprocess.SubprocessError:
results.append({"path": path, "status": "failed"})
return results
def main():
parser = argparse.ArgumentParser(
description="Detect insecure data storage in mobile apps (authorized testing only)"
)
parser.add_argument("--scan-dir", help="Directory containing app data to scan")
parser.add_argument("--package", help="Android package name for ADB pull")
parser.add_argument("--pull-dir", default=os.environ.get("MOBILE_AUDIT_DIR", "/tmp/mobile_audit"))
parser.add_argument("--output", "-o", help="Output JSON report")
args = parser.parse_args()
print("[*] Insecure Mobile Data Storage Detection Agent")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}
scan_dir = args.scan_dir or args.pull_dir
if args.package:
adb_pull_app_data(args.package, args.pull_dir)
if os.path.isdir(scan_dir):
report["findings"].extend(scan_shared_prefs(scan_dir))
report["findings"].extend(scan_sqlite_databases(scan_dir))
report["findings"].extend(scan_file_storage(scan_dir))
critical = sum(1 for f in report["findings"] if f.get("severity") == "CRITICAL")
report["risk_level"] = "CRITICAL" if critical else "HIGH" if report["findings"] else "LOW"
print(f"[*] Findings: {len(report['findings'])} (CRITICAL: {critical})")
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[*] Report saved to {args.output}")
else:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py10.7 KB
#!/usr/bin/env python3
"""
Mobile Data Storage Security Scanner
Analyzes extracted mobile app data directories for insecure storage patterns.
Scans SharedPreferences, SQLite databases, plists, and files for sensitive data.
Usage:
python process.py --data-dir ./extracted_app_data [--platform android|ios] [--output report.json]
"""
import argparse
import json
import os
import re
import sqlite3
import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from pathlib import Path
SENSITIVE_PATTERNS = {
"password": re.compile(r"(?:password|passwd|pwd)\s*[:=]\s*['\"]?([^\s'\"<]+)", re.IGNORECASE),
"api_key": re.compile(r"(?:api[_-]?key|apikey)\s*[:=]\s*['\"]?([a-zA-Z0-9_-]{16,})", re.IGNORECASE),
"token": re.compile(r"(?:auth[_-]?token|access[_-]?token|bearer)\s*[:=]\s*['\"]?([a-zA-Z0-9_.-]{16,})", re.IGNORECASE),
"secret": re.compile(r"(?:secret|private[_-]?key)\s*[:=]\s*['\"]?([a-zA-Z0-9_/+=]{16,})", re.IGNORECASE),
"jwt": re.compile(r"eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+"),
"email": re.compile(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"),
"credit_card": re.compile(r"\b(?:4\d{3}|5[1-5]\d{2}|6011|3[47]\d)\d{8,12}\b"),
"ssn": re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
"private_key": re.compile(r"BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY"),
}
def scan_shared_preferences(prefs_dir: str) -> list:
"""Scan Android SharedPreferences XML files for sensitive data."""
findings = []
prefs_path = Path(prefs_dir)
if not prefs_path.exists():
return findings
for xml_file in prefs_path.glob("*.xml"):
try:
tree = ET.parse(xml_file)
root = tree.getroot()
for element in root.iter():
name = element.get("name", "")
value = element.text or element.get("value", "")
for pattern_name, pattern in SENSITIVE_PATTERNS.items():
if pattern.search(name) or pattern.search(str(value)):
findings.append({
"source": f"SharedPreferences/{xml_file.name}",
"key": name,
"value_preview": str(value)[:50] + "..." if len(str(value)) > 50 else str(value),
"pattern_matched": pattern_name,
"severity": "CRITICAL" if pattern_name in ("password", "private_key", "credit_card") else "HIGH",
})
except ET.ParseError:
findings.append({
"source": f"SharedPreferences/{xml_file.name}",
"key": "PARSE_ERROR",
"pattern_matched": "error",
"severity": "INFO",
})
return findings
def scan_sqlite_databases(db_dir: str) -> list:
"""Scan SQLite databases for sensitive data."""
findings = []
db_path = Path(db_dir)
if not db_path.exists():
return findings
for db_file in list(db_path.glob("*.db")) + list(db_path.glob("*.sqlite")):
try:
conn = sqlite3.connect(str(db_file))
cursor = conn.cursor()
# Check if database is encrypted
try:
cursor.execute("SELECT count(*) FROM sqlite_master;")
findings.append({
"source": f"Database/{db_file.name}",
"key": "encryption_status",
"value_preview": "Database is NOT encrypted (SQLCipher not used)",
"pattern_matched": "unencrypted_database",
"severity": "HIGH",
})
except sqlite3.DatabaseError:
findings.append({
"source": f"Database/{db_file.name}",
"key": "encryption_status",
"value_preview": "Database appears to be encrypted",
"pattern_matched": "encrypted_database",
"severity": "INFO",
})
continue
# Get all tables
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [row[0] for row in cursor.fetchall()]
for table in tables:
# Get column names
cursor.execute(f"PRAGMA table_info('{table}');")
columns = [col[1] for col in cursor.fetchall()]
# Check column names for sensitive patterns
sensitive_cols = []
for col in columns:
col_lower = col.lower()
if any(s in col_lower for s in ["password", "token", "secret", "key", "auth", "credential", "ssn", "credit"]):
sensitive_cols.append(col)
if sensitive_cols:
# Sample data from sensitive columns
cols_str = ", ".join(sensitive_cols)
cursor.execute(f"SELECT {cols_str} FROM '{table}' LIMIT 3;")
samples = cursor.fetchall()
findings.append({
"source": f"Database/{db_file.name}/{table}",
"key": f"sensitive_columns: {', '.join(sensitive_cols)}",
"value_preview": str(samples[:2]) if samples else "empty",
"pattern_matched": "sensitive_database_columns",
"severity": "CRITICAL",
})
# Full text search in all columns
try:
cursor.execute(f"SELECT * FROM '{table}' LIMIT 100;")
rows = cursor.fetchall()
for row in rows:
row_text = " ".join(str(cell) for cell in row if cell)
for pattern_name, pattern in SENSITIVE_PATTERNS.items():
if pattern.search(row_text):
findings.append({
"source": f"Database/{db_file.name}/{table}",
"key": f"row_data_match",
"value_preview": row_text[:100],
"pattern_matched": pattern_name,
"severity": "HIGH",
})
break
except sqlite3.OperationalError:
pass
conn.close()
except sqlite3.Error as e:
findings.append({
"source": f"Database/{db_file.name}",
"key": "error",
"value_preview": str(e),
"pattern_matched": "error",
"severity": "INFO",
})
return findings
def scan_plist_files(plist_dir: str) -> list:
"""Scan iOS plist files for sensitive data."""
findings = []
plist_path = Path(plist_dir)
if not plist_path.exists():
return findings
for plist_file in plist_path.rglob("*.plist"):
try:
with open(plist_file, "r", errors="replace") as f:
content = f.read()
for pattern_name, pattern in SENSITIVE_PATTERNS.items():
matches = pattern.findall(content)
if matches:
findings.append({
"source": f"Plist/{plist_file.name}",
"key": pattern_name,
"value_preview": str(matches[0])[:50],
"pattern_matched": pattern_name,
"severity": "HIGH",
})
except (OSError, UnicodeDecodeError):
pass
return findings
def scan_general_files(data_dir: str) -> list:
"""Scan general files for sensitive data and permission issues."""
findings = []
data_path = Path(data_dir)
for file_path in data_path.rglob("*"):
if not file_path.is_file():
continue
if file_path.suffix in (".so", ".dylib", ".png", ".jpg", ".gif", ".mp3", ".mp4"):
continue
if file_path.stat().st_size > 5 * 1024 * 1024:
continue
try:
with open(file_path, "r", errors="replace") as f:
content = f.read(10000)
for pattern_name, pattern in SENSITIVE_PATTERNS.items():
matches = pattern.findall(content)
if matches:
findings.append({
"source": f"File/{file_path.relative_to(data_path)}",
"key": pattern_name,
"value_preview": str(matches[0])[:50],
"pattern_matched": pattern_name,
"severity": "HIGH" if pattern_name in ("password", "private_key") else "MEDIUM",
})
except (OSError, UnicodeDecodeError):
pass
return findings
def main():
parser = argparse.ArgumentParser(description="Mobile Data Storage Security Scanner")
parser.add_argument("--data-dir", required=True, help="Extracted app data directory")
parser.add_argument("--platform", choices=["android", "ios"], default="android")
parser.add_argument("--output", default="storage_scan.json", help="Output report path")
args = parser.parse_args()
data_dir = Path(args.data_dir)
if not data_dir.exists():
print(f"[-] Directory not found: {args.data_dir}")
sys.exit(1)
all_findings = []
if args.platform == "android":
print("[*] Scanning SharedPreferences...")
all_findings.extend(scan_shared_preferences(str(data_dir / "shared_prefs")))
print("[*] Scanning SQLite databases...")
all_findings.extend(scan_sqlite_databases(str(data_dir / "databases")))
else:
print("[*] Scanning plist files...")
all_findings.extend(scan_plist_files(str(data_dir / "Library" / "Preferences")))
print("[*] Scanning SQLite databases...")
all_findings.extend(scan_sqlite_databases(str(data_dir)))
print("[*] Scanning general files...")
all_findings.extend(scan_general_files(str(data_dir)))
# Generate report
severity_counts = {}
for f in all_findings:
sev = f["severity"]
severity_counts[sev] = severity_counts.get(sev, 0) + 1
report = {
"scan": {
"data_directory": str(data_dir),
"platform": args.platform,
"date": datetime.now().isoformat(),
},
"summary": {
"total_findings": len(all_findings),
"by_severity": severity_counts,
},
"findings": all_findings,
}
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Scan complete: {len(all_findings)} findings")
print(f"[+] Report saved: {args.output}")
for sev, count in sorted(severity_counts.items()):
print(f" {sev}: {count}")
if __name__ == "__main__":
main()