"""Shared paths and seed helpers for coruna-lab tooling.""" from __future__ import annotations import hashlib import sys from pathlib import Path LAB_ROOT = Path(__file__).resolve().parents[1] SOURCE_ROOT = LAB_ROOT / "source" ONLINE_ROOT = LAB_ROOT.parent / "coruna-online" MODULE_HUNT = ONLINE_ROOT / "module_hunt" if str(MODULE_HUNT) not in sys.path: sys.path.insert(0, str(MODULE_HUNT)) CAMPAIGN_HASH = "34f5121f572d6742703eb84ec2f866a6" # Campaign originals (current seeds embedded in type-0x01 + core) ORIGINAL_DEPLOYMENT_SEED = "09d0b8d58a71653cd1c89c64c866f2e6" ORIGINAL_REPORTING_SEED = "2d2aebba0bf3d7d694194a7ab93b0a96" OLD_DEP = ORIGINAL_DEPLOYMENT_SEED.encode("ascii") OLD_REP = ORIGINAL_REPORTING_SEED.encode("ascii") # Active tree root for --apply targets (project dir with web/ + sync/). # Defaults to SOURCE_ROOT so accidental --apply without --root does not invent paths; # new_project / patch_all --root override this before applying. _TREE_ROOT = SOURCE_ROOT CAMPAIGN_DIR = _TREE_ROOT / "web" / CAMPAIGN_HASH SYNC_DIR = _TREE_ROOT / "sync" C2_FETCH = ONLINE_ROOT / "c2_fetch" CORE_DYLIB = ( ONLINE_ROOT / "evidence/cases/2026-08-02-coruna-all-26/downloads/extracted" / "80fa600e2486e588bb7991766c6b6bada5a5de0a3351a83b46b6920ee4b55ac1" / "tmp.dylib" ) DAILY_BODY = ( ONLINE_ROOT / "evidence/cases/2026-08-02-coruna-har/responses" / "har-36_6b5f8ad2b8e41bf097b4811c9fb082523a32f72dd83eed49ed09c5a7f9b04027.body" ) SECONDARY_KEYS = Path(__file__).resolve().parent / "secondary_keys.json" # Representative dylib per group (same bytes as the other stems in that group) GROUP_DYLIBS = { "A": C2_FETCH / "65704c0722165a7bdedad3f3f61258b2f95470f6_type0x01.dylib", "B": C2_FETCH / "7f208248c748f97956fe4a7cf246c91235852e67_type0x01.dylib", } def tree_root() -> Path: return _TREE_ROOT def set_tree_root(root: Path) -> Path: """Point CAMPAIGN_DIR / SYNC_DIR at root/web/... and root/sync.""" global _TREE_ROOT, CAMPAIGN_DIR, SYNC_DIR root = root.resolve() _TREE_ROOT = root CAMPAIGN_DIR = root / "web" / CAMPAIGN_HASH SYNC_DIR = root / "sync" return root def ensure_tree_layout(root: Path) -> None: camp = root / "web" / CAMPAIGN_HASH sync = root / "sync" if not camp.is_dir() or not sync.is_dir(): raise SystemExit( f"missing working tree under {root} (need web/{CAMPAIGN_HASH}/ and sync/).\n" f"Run first:\n" f" python3 tools/new_project.py --skip-patch\n" f"or directly:\n" f" python3 tools/new_project.py --deployment-domains '...' --reporting-domains '...'" ) def pack_seed(value: str) -> bytes: data = value.encode("ascii") if len(data) > 32: raise SystemExit( f"seed longer than 32 bytes ({len(data)}): {value!r}\n" "Provide at most 32 ASCII characters (recommend exactly 32 hex chars)." ) try: data.decode("ascii") except UnicodeDecodeError as exc: raise SystemExit("seed must be ASCII") from exc return data + b"\x00" * (32 - len(data)) def validate_seed_arg(name: str, value: str) -> str: if not value: raise SystemExit(f"{name} must be non-empty") pack_seed(value) # length check return value def replace_seed(blob: bytearray, old: bytes, new32: bytes) -> int: count = 0 start = 0 while True: index = blob.find(old, start) if index < 0: break blob[index : index + 32] = new32 count += 1 start = index + 32 return count def patch_seeds_in_dylib( data: bytes, deployment_seed: str, reporting_seed: str, *, expect_dep: int, expect_rep: int, label: str, ) -> bytes: buf = bytearray(data) nd = replace_seed(buf, OLD_DEP, pack_seed(deployment_seed)) nr = replace_seed(buf, OLD_REP, pack_seed(reporting_seed)) if nd != expect_dep or nr != expect_rep: raise SystemExit( f"{label}: unexpected seed hits dep={nd} rep={nr} " f"(want {expect_dep}/{expect_rep}). Already patched?" ) return bytes(buf) def sha256_hex(data: bytes) -> str: return hashlib.sha256(data).hexdigest()