anycoder-a08b208a / streamlit_app.py
moddux's picture
Upload streamlit_app.py with huggingface_hub
7ea74aa verified
#!/usr/bin/env python3
"""
PROJECT: I.O.S. D.F.I.R. // ENHANCEMENT SKELETON
Streamlit Application for iOS DFIR Documentation
"""
import streamlit as st
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from dataclasses import dataclass, field
import hashlib
import json
import logging
# Page configuration
st.set_page_config(
page_title="DFIR iOS Enhancement",
page_icon="🔍",
layout="wide",
initial_sidebar_state="collapsed"
)
# Custom CSS for cyberpunk aesthetic
CUSTOM_CSS = """
<style>
/* Import Fonts */
@import url('https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap');
/* CSS Variables */
:root {
--bg-dark: #050608;
--bg-panel: #0e1016;
--bg-card: #14161f;
--border-subtle: #2a2f3d;
--text-main: #e0e6ed;
--text-muted: #94a3b8;
--cyan: #00f0ff;
--green: #00ff9d;
--orange: #ff9d00;
--gold: #ffd700;
--purple: #bd00ff;
--red: #ff0055;
}
/* Main background */
.stApp {
background-color: var(--bg-dark);
background-image:
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px);
background-size: 40px 40px;
}
/* Hide default Streamlit elements */
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
header {visibility: hidden;}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-family: 'Rajdhani', sans-serif;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-main);
}
.mono {
font-family: 'Share Tech Mono', monospace;
}
.meta {
font-family: 'Space Mono', monospace;
font-size: 0.85rem;
}
/* Cards */
.card {
background: var(--bg-card);
border: 1px solid var(--border-subtle);
padding: 1.5rem;
border-radius: 4px;
transition: transform 0.2s, border-color 0.2s;
}
.card:hover {
border-color: rgba(255, 255, 255, 0.1);
}
.card-cyan { border-left: 4px solid var(--cyan); }
.card-green { border-left: 4px solid var(--green); }
.card-orange { border-left: 4px solid var(--orange); }
.card-gold { border-left: 4px solid var(--gold); }
.card-purple { border-left: 4px solid var(--purple); }
.card-red { border-left: 4px solid var(--red); }
/* Badges */
.badge {
display: inline-block;
font-family: 'Share Tech Mono', monospace;
font-size: 0.7rem;
padding: 2px 8px;
border: 1px solid var(--border-subtle);
border-radius: 2px;
text-transform: uppercase;
margin-right: 0.5rem;
}
.badge-cyan { border-color: var(--cyan); color: var(--cyan); }
.badge-gold { border-color: var(--gold); color: var(--gold); }
.badge-green { border-color: var(--green); color: var(--green); }
.badge-red { border-color: var(--red); color: var(--red); }
/* Code blocks */
.code-block {
background: #000;
border: 1px solid var(--border-subtle);
padding: 1rem;
font-family: 'Space Mono', monospace;
font-size: 0.85rem;
border-radius: 4px;
overflow-x: auto;
color: #d4d4d4;
}
/* Navigation buttons */
.nav-button {
background: transparent;
border: none;
color: var(--text-muted);
font-family: 'Share Tech Mono', monospace;
font-size: 0.9rem;
padding: 0.8rem 1.2rem;
cursor: pointer;
text-transform: uppercase;
transition: all 0.2s ease;
border-bottom: 2px solid transparent;
}
.nav-button:hover {
color: var(--text-main);
background: rgba(255, 255, 255, 0.03);
}
.nav-button.active {
color: var(--cyan);
border-bottom-color: var(--cyan);
}
/* Signals */
.signal {
display: inline-block;
font-family: 'Share Tech Mono', monospace;
font-size: 0.65rem;
padding: 1px 4px;
margin-right: 4px;
border-radius: 2px;
}
.sig-ref { color: var(--cyan); border: 1px solid var(--cyan); }
.sig-block { color: var(--red); border: 1px solid var(--red); }
.sig-done { color: var(--green); border: 1px solid var(--green); }
.sig-gate { color: var(--gold); border: 1px solid var(--gold); }
/* Timeline */
.timeline-item {
padding: 1rem;
border-left: 2px solid var(--border-subtle);
margin-left: 1rem;
position: relative;
}
.timeline-item::before {
content: '';
position: absolute;
left: -6px;
top: 1.5rem;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--cyan);
}
.timeline-item.done::before { background: var(--green); }
.timeline-item.active::before { background: var(--gold); }
.timeline-item.next::before { background: var(--text-muted); }
/* Role cards */
.role-card {
overflow: hidden;
position: relative;
}
.role-watermark {
position: absolute;
bottom: -10px;
right: -10px;
font-size: 5rem;
font-weight: 700;
opacity: 0.05;
font-family: 'Share Tech Mono', monospace;
line-height: 1;
pointer-events: none;
}
.role-tag {
font-family: 'Space Mono', monospace;
font-size: 0.7rem;
color: var(--cyan);
margin-bottom: 0.5rem;
display: block;
}
/* Lists */
.role-list {
list-style: none;
margin-top: 1rem;
padding-left: 0;
}
.role-list li {
font-size: 0.9rem;
margin-bottom: 0.4rem;
padding-left: 1rem;
position: relative;
}
.role-list li::before {
content: '>';
position: absolute;
left: 0;
color: var(--text-muted);
}
/* Header styling */
.header-container {
padding: 2rem 0;
border-bottom: 1px solid var(--border-subtle);
margin-bottom: 2rem;
}
.header-title {
font-size: 3rem;
font-weight: 700;
line-height: 1;
margin-bottom: 0.5rem;
}
.header-title span {
color: var(--cyan);
}
.header-subtitle {
font-size: 1.2rem;
color: var(--text-muted);
max-width: 800px;
}
/* Diagram boxes */
.diagram-box {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 2rem;
background: rgba(0, 0, 0, 0.2);
border: 1px dashed var(--border-subtle);
border-radius: 4px;
flex-wrap: wrap;
}
.diag-node {
border: 1px solid var(--cyan);
background: rgba(0, 240, 255, 0.1);
padding: 0.5rem 1rem;
font-family: 'Share Tech Mono', monospace;
font-size: 0.8rem;
border-radius: 2px;
}
.diag-arrow {
color: var(--cyan);
font-size: 1.2rem;
}
/* Anycoder link */
.anycoder-link {
font-family: 'Space Mono', monospace;
font-size: 0.7rem;
color: var(--text-muted);
text-decoration: none;
border: 1px solid var(--border-subtle);
padding: 2px 6px;
border-radius: 4px;
transition: color 0.2s, border-color 0.2s;
}
.anycoder-link:hover {
color: var(--cyan);
border-color: var(--cyan);
}
/* Section headers */
.section-header {
margin-bottom: 2rem;
border-left: 4px solid var(--cyan);
padding-left: 1rem;
}
.section-title {
font-size: 2rem;
color: var(--text-main);
}
.section-desc {
font-family: 'Space Mono', monospace;
color: var(--text-muted);
font-size: 0.9rem;
}
/* Column styling */
.st-column {
padding: 0.5rem;
}
/* Text colors */
.text-cyan { color: var(--cyan); }
.text-green { color: var(--green); }
.text-orange { color: var(--orange); }
.text-gold { color: var(--gold); }
.text-purple { color: var(--purple); }
.text-red { color: var(--red); }
</style>
"""
# Inject custom CSS
st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
# Initialize session state for navigation
if 'current_section' not in st.session_state:
st.session_state.current_section = 'overview'
# Header
def render_header():
st.markdown("""
<div class="header-container">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span class="meta">SYS_REF: DFIR_IOS_V1.0</span>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
</div>
<div>
<h1 class="header-title">Project <span>DFIR</span> // iOS ENHANCEMENT</h1>
<p class="header-subtitle">Structured SQLite-backed iOS backup extractor pipeline. Artifact hashing, logging, and extensible parser modules.</p>
<div style="margin-top: 1rem;">
<span class="badge badge-cyan">STATUS: SKELETON</span>
<span class="badge badge-gold">LANG: PYTHON3</span>
<span class="badge badge-green">LOGGING: ENABLED</span>
<span class="badge">HASHING: SHA256</span>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# Navigation
def render_navigation():
sections = [
('overview', 'Manifesto'),
('modules', 'Modules'),
('pipeline', 'Pipeline'),
('schema', 'Data Structs'),
('source', 'Source Code')
]
cols = st.columns(len(sections))
for i, (key, label) in enumerate(sections):
with cols[i]:
active_class = 'active' if st.session_state.current_section == key else ''
if st.button(
label,
key=f"nav_{key}",
use_container_width=True,
type="secondary" if st.session_state.current_section != key else "primary"
):
st.session_state.current_section = key
st.rerun()
# Section: Overview
def render_overview():
st.markdown("""
<div class="section-header">
<h2 class="section-title">System Manifesto</h2>
<span class="section-desc">:: EXEC_SUMMARY :: Purpose and operational goals of the DFIR skeleton.</span>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
st.markdown("""
<div class="card card-cyan">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">Primary Objective</span>
<span class="signal sig-gate">CORE</span>
</div>
<p>Upgrade simple SQLite-backed iOS backup extractors into a structured DFIR pipeline. Preserve analyst visibility, logging, and artifact hashing.</p>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown("""
<div class="card card-gold">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">Current Status</span>
<span class="signal sig-ref">DEV</span>
</div>
<p>This file is intentionally verbose. Several modules are scaffolded as placeholders (Stubs) for future parser implementation.</p>
</div>
""", unsafe_allow_html=True)
st.markdown("<div style='margin-top: 2rem;'></div>", unsafe_allow_html=True)
st.markdown("""
<div class="card card-purple">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">Operational Flow</span>
<span class="signal sig-ref">REF</span>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="diagram-box">
<div class="diag-node">CLI_ARGS</div>
<div class="diag-arrow">→</div>
<div class="diag-node" style="border-color: var(--purple);">MANIFEST_DB</div>
<div class="diag-arrow">→</div>
<div class="diag-node" style="border-color: var(--green);">PARSER_MODULES</div>
<div class="diag-arrow">→</div>
<div class="diag-node">TIMELINE_CSV</div>
</div>
""", unsafe_allow_html=True)
# Section: Modules
def render_modules():
st.markdown("""
<div class="section-header">
<h2 class="section-title">Parser Modules</h2>
<span class="section-desc">:: AGENTS :: Specialized extraction subroutines.</span>
</div>
""", unsafe_allow_html=True)
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown("""
<div class="card card-cyan role-card">
<div class="role-watermark">CORE</div>
<span class="role-tag">CONTACTS</span>
<h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">AddressBook Parser</h3>
<div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">export_contacts()</div>
<ul class="role-list">
<li>Modern & Legacy Schema</li>
<li>Phone Normalization</li>
<li>CSV/JSON Export</li>
</ul>
</div>
""", unsafe_allow_html=True)
with col2:
st.markdown("""
<div class="card card-green role-card">
<div class="role-watermark">CORE</div>
<span class="role-tag">MESSAGING</span>
<h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">SMS / iMessage</h3>
<div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">extract_sms()</div>
<ul class="role-list">
<li>Handle Resolution</li>
<li>AttributedBody Stub</li>
<li>Timeline Event Gen</li>
</ul>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown("""
<div class="card card-orange role-card">
<div class="role-watermark">GEO</div>
<span class="role-tag">LOCATION</span>
<h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">LocationD / Cache</h3>
<div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">extract_locations()</div>
<ul class="role-list">
<li>RTCL Location MO</li>
<li>Visit MO Parsing</li>
<li>Cell Tower Logs</li>
</ul>
</div>
""", unsafe_allow_html=True)
with col4:
st.markdown("""
<div class="card card-red role-card">
<div class="role-watermark">STUB</div>
<span class="role-tag">FUTURE</span>
<h3 style="font-size: 1.4rem; margin-bottom: 0.2rem;">Safari / Notes</h3>
<div class="mono" style="color: var(--text-muted); font-size: 0.8rem; margin-bottom: 1rem;">extract_safari_stub()</div>
<ul class="role-list">
<li>History / Downloads</li>
<li>Rich Notes</li>
<li>App Containers</li>
</ul>
</div>
""", unsafe_allow_html=True)
# Section: Pipeline
def render_pipeline():
st.markdown("""
<div class="section-header">
<h2 class="section-title">Execution Pipeline</h2>
<span class="section-desc">:: LOGIC :: The `run()` orchestration sequence.</span>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="timeline-item done">
<div class="meta" style="margin-bottom: 0.5rem;">STEP 1</div>
<h3 style="margin-bottom: 0.5rem;">Initialization</h3>
<div class="card" style="margin-top: 0.5rem; border: none; background: rgba(255,255,255,0.02); padding: 1rem;">
<span class="signal sig-done">DONE</span> Setup Logging. Validate Backup Root. Parse CLI Arguments.
</div>
</div>
<div class="timeline-item active">
<div class="meta" style="margin-bottom: 0.5rem;">STEP 2</div>
<h3 style="margin-bottom: 0.5rem;">Manifest Indexing</h3>
<div class="card" style="margin-top: 0.5rem; border: none; background: rgba(255,255,255,0.02); padding: 1rem;">
<span class="signal sig-gate">ACTIVE</span> Load `Manifest.db`. Search for `sms.db`, `AddressBook`, and Location artifacts.
</div>
</div>
<div class="timeline-item next">
<div class="meta" style="margin-bottom: 0.5rem;">STEP 3</div>
<h3 style="margin-bottom: 0.5rem;">Extraction & Export</h3>
<div class="card" style="margin-top: 0.5rem; border: none; background: rgba(255,255,255,0.02); padding: 1rem;">
<span class="signal sig-ref">NEXT</span> Run Parser Modules. Generate CSV/JSON. Build Unified Timeline.
</div>
</div>
""", unsafe_allow_html=True)
# Section: Schema
def render_schema():
st.markdown("""
<div class="section-header">
<h2 class="section-title">Data Structures</h2>
<span class="section-desc">:: STRUCT :: Python Dataclasses used for typing.</span>
</div>
""", unsafe_allow_html=True)
st.markdown("""
<div class="card card-cyan">
<div class="mono" style="font-size: 0.85rem; text-transform: uppercase; margin-bottom: 1rem;">Config & Metadata</div>
<div class="code-block" style="max-height: 400px; overflow-y: auto;">
<span class="text-purple">@dataclass</span>
<span class="text-gold">class</span> <span class="text-cyan">CaseMetadata</span>:
case_name: <span class="text-cyan">str</span> = <span class="text-orange">"UNSPECIFIED_CASE"</span>
examiner: <span class="text-cyan">str</span> = <span class="text-orange">"UNSPECIFIED_EXAMINER"</span>
evidence_id: <span class="text-cyan">str</span> = <span class="text-orange">"UNSPECIFIED_EVIDENCE"</span>
<span class="text-purple">@dataclass</span>
<span class="text-gold">class</span> <span class="text-cyan">AppConfig</span>:
backup_root: <span class="text-cyan">Path</span>
manifest_db: <span class="text-cyan">Path</span>
output_root: <span class="text-cyan">Path</span>
query_terms: <span class="text-cyan">List</span>[<span class="text-cyan">str</span>] = field(default_factory=<span class="text-cyan">list</span>)
verbose: <span class="text-cyan">bool</span> = <span class="text-gold">False</span>
hash_exports: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
copy_raw_files: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
export_csv: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
export_jsonl: <span class="text-cyan">bool</span> = <span class="text-gold">True</span>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("<div style='margin-top: 1.5rem;'></div>", unsafe_allow_html=True)
st.markdown("""
<div class="card card-purple">
<div class="mono" style="font-size: 0.85rem; text-transform: uppercase; margin-bottom: 1rem;">Timeline Event Model</div>
<div class="code-block" style="max-height: 300px; overflow-y: auto;">
<span class="text-purple">@dataclass</span>
<span class="text-gold">class</span> <span class="text-cyan">TimelineEvent</span>:
timestamp: <span class="text-cyan">Optional</span>[<span class="text-cyan">str</span>]
artifact_type: <span class="text-cyan">str</span>
source_file: <span class="text-cyan">str</span>
summary: <span class="text-cyan">str</span>
attributes: <span class="text-cyan">Dict</span>[<span class="text-cyan">str</span>, <span class="text-cyan">Any</span>] = field(default_factory=<span class="text-cyan">dict</span>)
</div>
</div>
""", unsafe_allow_html=True)
# Section: Source Code
def render_source():
st.markdown("""
<div class="section-header">
<h2 class="section-title">Source Implementation</h2>
<span class="section-desc">:: CODE :: The complete Python script logic.</span>
</div>
""", unsafe_allow_html=True)
source_code = '''#!/usr/bin/env python3
""" DFIR iOS Backup Enhancement Skeleton """
from __future__ import annotations
import argparse, csv, dataclasses, hashlib, json, logging, os, shutil, sqlite3, sys, traceback
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
APPLE_EPOCH = datetime(2001, 1, 1, tzinfo=timezone.utc)
# ============================================================
# CONFIG / DATA MODELS
# ============================================================
@dataclass
class CaseMetadata:
case_name: str = "UNSPECIFIED_CASE"
examiner: str = "UNSPECIFIED_EXAMINER"
evidence_id: str = "UNSPECIFIED_EVIDENCE"
notes: str = ""
@dataclass
class AppConfig:
backup_root: Path
manifest_db: Path
output_root: Path
query_terms: List[str] = field(default_factory=list)
verbose: bool = False
hash_exports: bool = True
copy_raw_files: bool = True
export_csv: bool = True
export_jsonl: bool = True
export_kml: bool = False
export_geojson: bool = False
case: CaseMetadata = field(default_factory=CaseMetadata)
@dataclass
class LocatedFile:
file_id: str
relative_path: str
domain: Optional[str]
source_path: Path
@dataclass
class TimelineEvent:
timestamp: Optional[str]
artifact_type: str
source_file: str
summary: str
attributes: Dict[str, Any] = field(default_factory=dict)
# ============================================================
# LOGGING
# ============================================================
def setup_logging(output_root: Path, verbose: bool = False) -> logging.Logger:
output_root.mkdir(parents=True, exist_ok=True)
log_path = output_root / "dfir_run.log"
logger = logging.getLogger("dfir_ios")
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
logger.handlers.clear()
fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
fh = logging.FileHandler(log_path, encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(fmt)
logger.addHandler(fh)
sh = logging.StreamHandler(sys.stdout)
sh.setLevel(logging.DEBUG if verbose else logging.INFO)
sh.setFormatter(fmt)
logger.addHandler(sh)
logger.debug("Logging initialized")
return logger
# ============================================================
# UTILS
# ============================================================
def apple_time_to_datetime(ts: Any) -> Optional[datetime]:
if ts is None:
return None
try:
ts = float(ts)
if ts > 1e12: ts = ts / 1e9
return APPLE_EPOCH + timedelta(seconds=ts)
except Exception:
return None
def normalize_phone(phone: Optional[str]) -> Optional[str]:
if not phone: return phone
value = str(phone).replace("+1", "").replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
return value.strip() or None
def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk: break
h.update(chunk)
return h.hexdigest()
# ============================================================
# MAIN ORCHESTRATION
# ============================================================
def run(cfg: AppConfig) -> int:
logger = setup_logging(cfg.output_root, cfg.verbose)
logger.info("Starting DFIR iOS backup extraction")
logger.info(f"Case: {cfg.case.case_name}")
logger.info(f"Examiner: {cfg.case.examiner}")
logger.info(f"Evidence ID: {cfg.case.evidence_id}")
# Validate backup root
if not cfg.backup_root.exists():
logger.error(f"Backup root does not exist: {cfg.backup_root}")
return 1
# Load manifest database
if not cfg.manifest_db.exists():
logger.error(f"Manifest database does not exist: {cfg.manifest_db}")
return 1
logger.info("Manifest database loaded successfully")
# Create output directories
cfg.output_root.mkdir(parents=True, exist_ok=True)
(cfg.output_root / "exports").mkdir(exist_ok=True)
(cfg.output_root / "raw").mkdir(exist_ok=True)
(cfg.output_root / "timeline").mkdir(exist_ok=True)
logger.info("Output directories created")
# Run parser modules
timeline_events: List[TimelineEvent] = []
# Export contacts
try:
contacts_count = export_contacts(cfg, logger)
timeline_events.append(TimelineEvent(
timestamp=datetime.now(timezone.utc).isoformat(),
artifact_type="CONTACTS",
source_file="AddressBook.sqlitedb",
summary=f"Exported {contacts_count} contacts"
))
except Exception as e:
logger.error(f"Contact export failed: {e}")
# Export SMS/iMessage
try:
sms_count = extract_sms(cfg, logger)
timeline_events.append(TimelineEvent(
timestamp=datetime.now(timezone.utc).isoformat(),
artifact_type="MESSAGES",
source_file="sms.db",
summary=f"Exported {sms_count} messages"
))
except Exception as e:
logger.error(f"SMS export failed: {e}")
# Export locations
try:
location_count = extract_locations(cfg, logger)
timeline_events.append(TimelineEvent(
timestamp=datetime.now(timezone.utc).isoformat(),
artifact_type="LOCATIONS",
source_file="LocationD/Cache.sqlite",
summary=f"Exported {location_count} location points"
))
except Exception as e:
logger.error(f"Location export failed: {e}")
# Generate timeline
if cfg.export_csv:
generate_timeline_csv(timeline_events, cfg.output_root / "timeline" / "unified_timeline.csv")
logger.info("Timeline CSV generated")
if cfg.export_jsonl:
generate_timeline_jsonl(timeline_events, cfg.output_root / "timeline" / "unified_timeline.jsonl")
logger.info("Timeline JSONL generated")
logger.info("DFIR iOS extraction complete")
return 0
def export_contacts(cfg: AppConfig, logger: logging.Logger) -> int:
"""Export contacts from AddressBook database"""
logger.info("Exporting contacts...")
# Implementation stub
return 0
def extract_sms(cfg: AppConfig, logger: logging.Logger) -> int:
"""Extract SMS/iMessage data"""
logger.info("Extracting SMS/iMessage data...")
# Implementation stub
return 0
def extract_locations(cfg: AppConfig, logger: logging.Logger) -> int:
"""Extract location data from LocationD"""
logger.info("Extracting location data...")
# Implementation stub
return 0
def generate_timeline_csv(events: List[TimelineEvent], output_path: Path):
"""Generate unified timeline CSV"""
with output_path.open("w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["timestamp", "artifact_type", "source_file", "summary", "attributes"])
for event in events:
writer.writerow([
event.timestamp,
event.artifact_type,
event.source_file,
event.summary,
json.dumps(event.attributes)
])
def generate_timeline_jsonl(events: List[TimelineEvent], output_path: Path):
"""Generate unified timeline JSONL"""
with output_path.open("w", encoding="utf-8") as f:
for event in events:
f.write(json.dumps(dataclasses.asdict(event)) + "\\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="DFIR iOS Backup Enhancement")
parser.add_argument("--backup-root", type=Path, required=True, help="Path to iOS backup")
parser.add_argument("--output-root", type=Path, required=True, help="Output directory")
parser.add_argument("--case-name", type=str, default="UNSPECIFIED_CASE")
parser.add_argument("--examiner", type=str, default="UNSPECIFIED_EXAMINER")
parser.add_argument("--evidence-id", type=str, default="UNSPECIFIED_EVIDENCE")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
manifest_db = args.backup_root / "Manifest.db"
cfg = AppConfig(
backup_root=args.backup_root,
manifest_db=manifest_db,
output_root=args.output_root,
verbose=args.verbose,
case=CaseMetadata(
case_name=args.case_name,
examiner=args.examiner,
evidence_id=args.evidence_id
)
)
sys.exit(run(cfg))'''
st.markdown("""
<div class="card card-gold">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span class="mono" style="font-size: 0.85rem; text-transform: uppercase;">dfir_ios_enhancement.py</span>
<span class="signal sig-done">READY</span>
</div>
</div>
""", unsafe_allow_html=True)
st.code(source_code, language="python")
# Main app
def main():
render_header()
render_navigation()
st.markdown("<div style='margin: 2rem 0;'></div>", unsafe_allow_html=True)
if st.session_state.current_section == 'overview':
render_overview()
elif st.session_state.current_section == 'modules':
render_modules()
elif st.session_state.current_section == 'pipeline':
render_pipeline()
elif st.session_state.current_section == 'schema':
render_schema()
elif st.session_state.current_section == 'source':
render_source()
if __name__ == "__main__":
main()