Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

221 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""Agent for performing mobile device forensics.
Analyzes mobile device extractions by parsing SQLite databases
for messages, call logs, contacts, and location data from
iOS and Android file system extractions.
"""
import sqlite3
import json
import sys
import re
from pathlib import Path
from datetime import datetime
_SAFE_TABLE_RE = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
class MobileForensicsAgent:
"""Parses mobile device extraction data for forensic analysis."""
def __init__(self, extraction_dir, output_dir, platform="android"):
self.extraction_dir = Path(extraction_dir)
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.platform = platform
def _query_db(self, db_path, query, params=None):
"""Execute a query against a SQLite database."""
if not Path(db_path).exists():
return []
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute(query, params or [])
results = [dict(row) for row in cursor.fetchall()]
conn.close()
return results
except sqlite3.Error as e:
return [{"error": str(e), "db": str(db_path)}]
def extract_sms_android(self):
"""Extract SMS/MMS messages from Android mmssms.db."""
db_path = self.extraction_dir / "data/data/com.android.providers.telephony/databases/mmssms.db"
return self._query_db(str(db_path), """
SELECT address, body, type,
datetime(date/1000, 'unixepoch') AS msg_time,
read, seen
FROM sms ORDER BY date DESC LIMIT 5000
""")
def extract_sms_ios(self):
"""Extract iMessage/SMS from iOS sms.db."""
db_path = self.extraction_dir / "HomeDomain/Library/SMS/sms.db"
return self._query_db(str(db_path), """
SELECT h.id AS phone_number,
CASE WHEN m.is_from_me = 1 THEN 'SENT' ELSE 'RECEIVED' END AS direction,
m.text,
datetime(m.date/1000000000 + 978307200, 'unixepoch') AS msg_time,
m.service
FROM message m
JOIN handle h ON m.handle_id = h.ROWID
ORDER BY m.date DESC LIMIT 5000
""")
def extract_call_log_android(self):
"""Extract call logs from Android contacts2.db."""
db_path = self.extraction_dir / "data/data/com.android.providers.contacts/databases/calllog.db"
return self._query_db(str(db_path), """
SELECT number, name,
CASE type WHEN 1 THEN 'INCOMING' WHEN 2 THEN 'OUTGOING'
WHEN 3 THEN 'MISSED' ELSE 'UNKNOWN' END AS call_type,
duration,
datetime(date/1000, 'unixepoch') AS call_time
FROM calls ORDER BY date DESC LIMIT 2000
""")
def extract_contacts_android(self):
"""Extract contacts from Android contacts database."""
db_path = self.extraction_dir / "data/data/com.android.providers.contacts/databases/contacts2.db"
return self._query_db(str(db_path), """
SELECT display_name, data1 AS phone_or_email, mimetype
FROM raw_contacts rc
JOIN data d ON rc._id = d.raw_contact_id
WHERE mimetype IN (
'vnd.android.cursor.item/phone_v2',
'vnd.android.cursor.item/email_v2'
) ORDER BY display_name LIMIT 5000
""")
def extract_whatsapp_messages(self):
"""Extract WhatsApp messages from msgstore.db."""
db_path = self.extraction_dir / "data/data/com.whatsapp/databases/msgstore.db"
return self._query_db(str(db_path), """
SELECT key_remote_jid AS contact,
CASE WHEN key_from_me = 1 THEN 'SENT' ELSE 'RECEIVED' END AS direction,
data AS message_text,
datetime(timestamp/1000, 'unixepoch') AS msg_time,
media_mime_type,
media_size
FROM messages
WHERE data IS NOT NULL
ORDER BY timestamp DESC LIMIT 5000
""")
def extract_browser_history_android(self):
"""Extract Chrome browser history from Android."""
db_path = self.extraction_dir / "data/data/com.android.chrome/app_chrome/Default/History"
return self._query_db(str(db_path), """
SELECT url, title, visit_count,
datetime(last_visit_time/1000000 - 11644473600, 'unixepoch') AS visit_time
FROM urls ORDER BY last_visit_time DESC LIMIT 2000
""")
def extract_wifi_history(self):
"""Extract saved WiFi networks."""
if self.platform == "android":
wifi_conf = self.extraction_dir / "data/misc/wifi/WifiConfigStore.xml"
if wifi_conf.exists():
content = wifi_conf.read_text(errors="ignore")
import re
ssids = re.findall(r'"SSID"[^>]*>([^<]+)', content)
return [{"ssid": s} for s in ssids]
return []
def extract_installed_apps(self):
"""List installed applications."""
apps = []
if self.platform == "android":
app_dir = self.extraction_dir / "data/data"
if app_dir.exists():
for pkg in sorted(app_dir.iterdir()):
if pkg.is_dir():
apps.append({
"package": pkg.name,
"has_databases": (pkg / "databases").exists(),
})
return apps
def search_keyword(self, keyword):
"""Search across extracted databases for a keyword."""
hits = []
for db_file in self.extraction_dir.rglob("*.db"):
try:
conn = sqlite3.connect(str(db_file))
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = [row[0] for row in cursor.fetchall()]
for table in tables:
if not _SAFE_TABLE_RE.match(table):
continue
try:
cursor.execute(f"SELECT * FROM [{table}] LIMIT 1")
columns = [desc[0] for desc in cursor.description]
for col in columns:
if not _SAFE_TABLE_RE.match(col):
continue
cursor.execute(
f"SELECT [{col}] FROM [{table}] WHERE [{col}] LIKE ?",
[f"%{keyword}%"]
)
matches = cursor.fetchall()
if matches:
hits.append({
"database": str(db_file.relative_to(self.extraction_dir)),
"table": table,
"column": col,
"match_count": len(matches),
})
except sqlite3.Error:
continue
conn.close()
except sqlite3.Error:
continue
return hits
def generate_report(self):
"""Generate comprehensive mobile forensics report."""
report = {
"extraction_dir": str(self.extraction_dir),
"platform": self.platform,
"report_date": datetime.utcnow().isoformat(),
}
if self.platform == "android":
report["sms"] = {"count": len(self.extract_sms_android())}
report["call_log"] = {"count": len(self.extract_call_log_android())}
report["contacts"] = {"count": len(self.extract_contacts_android())}
report["whatsapp"] = {"count": len(self.extract_whatsapp_messages())}
report["browser_history"] = {"count": len(self.extract_browser_history_android())}
elif self.platform == "ios":
report["imessage_sms"] = {"count": len(self.extract_sms_ios())}
report["wifi_networks"] = self.extract_wifi_history()
report["installed_apps"] = {"count": len(self.extract_installed_apps())}
report_path = self.output_dir / "mobile_forensics_report.json"
with open(report_path, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 3:
print("Usage: agent.py <extraction_dir> <output_dir> [android|ios]")
sys.exit(1)
extraction_dir = sys.argv[1]
output_dir = sys.argv[2]
platform = sys.argv[3] if len(sys.argv) > 3 else "android"
agent = MobileForensicsAgent(extraction_dir, output_dir, platform)
agent.generate_report()
if __name__ == "__main__":
main()