dfir_ios_enhancement.py
READY
#!/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()
# ... [Manifest Access Layer and Extraction Logic Truncated for Brevity in View] ...
# ============================================================
# MAIN ORCHESTRATION
# ============================================================
def run(cfg: AppConfig) -> int:
logger = setup_logging(cfg.output_root, cfg.verbose)
logger.info("Starting DFIR iOS backup extraction")