# MASForensics 系统改造设计 > 目标:把当前「单台 Windows 磁盘取证」系统改造为能处理**多设备、多行为人、 > 异构证据、需跨源关联**的复杂取证系统。本文是唯一的权威设计文档 > (已合并早先的 `REFIT_PLAN.md` / `RESEARCH_DESIGN.md` 两份草稿)。 > > 触发本次改造的实际案件:2025 美亚杯资格赛 Individual —— 5 份证据 > (1 USB E01、1 安卓整盘 `blk0_sda.bin`、3 份 iOS 提取、1 组交易截图), > 跨 LEUNG YL / CHAN MH / FUNG CC 至少 3 人。 --- ## 1. 设计原则(贯穿全文的不变式) 1. **LLM 提议,代码裁决**。LLM 负责语言/分类/感知;它**不持有案件状态、 不产出数值、不写入未经核验的事实**。所有「真相」在符号层。 2. **每条记录的事实都可从一次工具调用重新推导**。结论可被独立复核。 3. **推理核心与设备类型无关**。设备特定逻辑全部位于「能力插件」中; 支持一种新设备 = 写插件,绝不改核心。 4. **看似不可逆的操作(如实体归并)实为可逆、带证据的论断**,可被推翻。 这四条不是口号——下文每个设计决策都对应其中一条。 --- ## 2. 现状问题诊断 | # | 问题 | 位置 | 后果 | |---|---|---|---| | P1 | **单镜像假设深植**:工具是闭包绑死 `image_path`,图是单源,主程序只选一个镜像 | `tool_registry.py:148` `register_all_tools`、`main.py:91-153` | 无法摄取多份证据,无法跨设备关联 | | P2 | **反幻觉只写在提示词里** | `base_agent.py` system prompt | LLM 一旦不听话,错误事实进入案件记录且**事后无法识别** | | P3 | **置信度公式无统计含义且有序依赖缺陷**:`delta=weight*(1-conf)`(正)/`weight*conf`(负),正负边混合时更新结果与边的到达顺序有关 | `evidence_graph.py:26-33` | 置信度不可校准、不可辩护 | | P4 | **工件分类是 Windows 专属**:靠 hive 名 / `.pf` / `mirc` 关键词 | `tool_registry.py:80-107` `_auto_categorize` | iOS/安卓工件全部落入 `other` | | P5 | **案件信息硬编码** `cfreds_hacking_case` | `config.yaml:35-50` | 换案即需改代码 | | P6 | **镜像发现靠扩展名 glob**,`.bin` 不在列表 | `main.py:28` `_IMAGE_GLOBS` | `blk0_sda.bin` 不被发现 | | P7 | **Phenomenon 无来源标注** | `evidence_graph.py:85` `Phenomenon` | 不知道某发现出自哪台设备,跨源关联无锚点 | 改造同时解决「接入新证据」与「修掉 P1-P7 这些固有缺陷」。 --- ## 3. 目标架构 ``` case.yaml ──► Case ──► N × EvidenceSource ├ id / type / owner / path └ access_mode: image | tree │ ┌──────────────┴───────────────┐ image-backed tree-backed (TSK, inode 寻址) (路径寻址:已挂载/已解包) │ │ └────────────┬─────────────────┘ ▼ SourceRegistry ── source_id → SourceHandle(解析 path/offset/mode) │ ToolRegistry ── 工具按 access_mode 注册,调用时绑定 source_id │ ┌──────────────────────┼───────────────────────┐ ▼ ▼ ▼ Knowledge-Source Graph Write Gateway ToolInvocationLog Agents (LLM) ──► (唯一写入口,强制 (每次工具调用留痕: 只能经网关写图 前置条件 = grounding) args / 输出 / sha256) │ │ └──────────────────────┴──► Grounded Evidence Graph (GEG) Phenomenon / Hypothesis / Entity 置信度 = 对数几率累加 ``` **保留**现有的五阶段流水线、断连恢复、运行归档、工具结果缓存、 `AgentFactory` 动态组合——这些设计是好的,不重写,只适配。 --- ## 4. 核心设计 ### 4.1 证据源抽象(解决 P1/P5/P6/P7,地基) 新增 `case.py`: - **`EvidenceSource`** 数据类:`id`、`label`、`type`、`owner`(关联人)、 `path`、`access_mode`、`meta`(类型特定,如分区 offset / 解包后根目录)。 - **`Case`**:持有 `list[EvidenceSource]` + 案件元数据,从 `case.yaml` 加载。 - **`access_mode` 是关键设计区分**: - `image`:块设备/磁盘镜像,用 TSK 按 inode 寻址(USB E01、安卓 `blk0_sda` 各分区)。 - `tree`:已挂载文件系统或已解包目录,按路径寻址(iOS 提取解压后、归档展开后)。 - 工具按 access_mode 分族注册(见 4.2)。一份证据可经「准备」从 image 变为 tree (如分区 mount、zip 解包)。 `main.py` 的 `select_image_interactive`(:91-153)改为加载/构造 `Case`; `_IMAGE_GLOBS` 改为类型探测(`mmls` 试探 + 文件头嗅探),不再靠扩展名。 `config.yaml` 删除 `cfreds_hacking_case`,案件信息移入 `case.yaml`。 ### 4.2 工具注册按源参数化(解决 P1) 现状:`register_all_tools(image_path, offset, ...)` 把单一镜像闭包进每个工具 (`tool_registry.py:159+`)。改造: - 工具执行器签名增加 `source_id`;执行时经 `SourceRegistry` 解析出真实 path/offset/mode。 - `TOOL_CATALOG` 按 `access_mode` 标注工具适用性;agent 拿到的工具集由其 负责的源类型决定。 - **「当前源」上下文**:编排器为 agent 设置 current source(类比现有 `graph._current_agent`),工具默认作用于它——LLM 不必每次传 `source_id` (减少出错)。跨源工具(时间线合并、实体查询)显式跨源。 - 缓存键 `_cache_key`(`tool_registry.py:41`)纳入 `source_id`,防止跨源串味。 ### 4.3 图写入网关(解决 P2,落实原则 1) 现状:agent 通过 `add_phenomenon` 等工具直接写图,约束只在 prompt。改造: - 所有图变更(`add_phenomenon` / `add_hypothesis` / `link` / `observe_identity` …) 收敛到**一个写入网关**。网关在代码层强制前置条件。 - 现有 prompt 里的「反幻觉规则」下沉为网关的硬校验。LLM agent 的四阶段工作流 (INVESTIGATE→RECORD→LINK→ANSWER)不变——变的是 RECORD 这一步底下的网关变严。 - `base_agent.py` 的 `mandatory_record_tools` 机制保留(它保证 agent 真的记录了东西)。 ### 4.4 证据落地约束 Grounding(解决 P2,落实原则 2) 这是系统可靠性的核心机制。 **ToolInvocationLog**:每次工具调用留痕一条记录 `{invocation_id, source_id, tool, args, output, output_sha256, agent, ts}`。 现有结果缓存(`tool_registry.py:29`)已存确定性输出,扩展为完整留痕即可。 **Phenomenon 一分为二**——把「事实」和「解读」分开: - `verified_facts`: `list[{type, value, invocation_id}]`, `type ∈ {path, timestamp, inode, hash, identifier, count, ...}`。 - `interpretation`: 自由文本,agent 的分析叙述。 **`add_phenomenon` 网关前置条件**: 1. 每个 fact 必须引用一次**本 agent 本任务内真实发生过的** `invocation_id`。 2. 代码校验 `fact.value` 命中该次调用的输出: - 文本输出 → 逐字 substring 匹配; - 结构化/二进制工具输出 → 与解析后的字段匹配。 3. 任一 fact 不通过 → **整条拒绝写入**,返回失败的 fact,agent 须修正重试。 4. 通过 → 写入;`verified_facts` 每条带 `invocation_id`(可重跑复核), `interpretation` 标记为「未核验分析」。 **效果**:在系统里「记录一条工具输出未支撑的路径/时间戳/哈希/标识符」 **结构性地不可能**。LLM 仍可能写错 `interpretation`,但报告会把 verified facts(带重跑指令的引证)与 interpretation(明确标注的分析) **分开渲染**,人类调查员一眼可辨。这是诚实划定边界的可靠性保证。 > 现有 `_make_auto_record`(`tool_registry.py:126`)把工具输出直接转 phenomenon—— > 那是「平凡落地」的特例(描述即输出),新设计是它的一般化与形式化。 ### 4.5 假设置信度:似然比 / 对数几率(解决 P3) 把 `evidence_graph.py:26` 的 `_DEFAULT_EDGE_WEIGHTS` 从「拍脑袋的 delta」 换成基于**似然比(LR)**的对数几率累加: - 每条 `Phenomenon → Hypothesis` 边代表一个似然比。LLM 仍只做**离散分类** (这条证据对这条假设是 direct_evidence / supports / weakens / contradicts …), 数值 `log₁₀(LR)` 由标定表查得——**LLM 绝不吐数字**(延续现有「LLM 选类型、 代码算数值」哲学并赋予统计基础)。 - 置信度更新: ``` L_post = L_prior + Σ log₁₀(LR_i) # 对数几率,可交换 → 无序依赖 confidence = 1 / (1 + 10^(−L_post)) ``` - 边类型 → `log₁₀(LR)` 标定表(初值,后续可由标注案例校准): | 边类型 | log₁₀LR | |---|---:| | `direct_evidence` | +2.0 | | `supports` / `consequence_observed` | +1.0 | | `prerequisite_met` | +0.5 | | `weakens` | −0.5 | | `contradicts` | −2.0 | - 阈值不变(≥0.8 supported / ≤0.2 refuted),只是改由 `L_post` 推出。 - `prior_prob` 成为可配置量(默认 0.5 → `L_prior=0`)。 - **同类证据调和衰减**(2026-05 落地):同 `(hypothesis, edge_type)` 的第 k 条边 贡献 `log_lr_base / k`。累计 = `log_lr_base · H_N`(调和级数,~ ln N)。 解决朴素贝叶斯独立性破产 + 同一发现被多 agent 重复入图导致 L=+31 的失控 (2026-05-20 实战数据)。单条边不变(k=1, 衰减=1.0)。**结构信号**比绝对值 更重要:strategist 看 `distinct_sources` 比看 confidence 数值更能判断证据厚度。 附带产出一个 **假设 × 证据矩阵**视图,供报告与线索选择使用。 ### 4.6 跨源实体解析(解决「复杂场景」的关联难题,落实原则 4) 复杂取证的核心难题:iPhone keychain 里的 Apple ID、安卓短信库里的号码、 USB 文件作者、交易截图里的钱包地址——**哪些指向同一行为人?** **关键设计:「身份共指」本身就是一条假设**——于是实体解析不是独立子系统, 而是 4.5 假设机制的复用: - agent 观察到标识符即经网关 `observe_identity`,记一条**类型化**的标识符 (强标识符:IMEI / 钱包地址 / email / 电话号;弱标识符:昵称 / 显示名), 挂到暂定 `Entity`。 - 「Entity A ≡ Entity B」登记为一条 `Hypothesis`;共享强标识符 = 强 +LR 边, 共享弱标识符 = 弱 +LR 边,冲突的强标识符 = 强 −LR 边——用 4.5 同一套计算打分。 - **不做破坏性归并**:跨阈值时在两个 Entity 间加一条 `same_as` 边(由该 coref 假设背书)。查询时把 `same_as` 连通分量视作同一行为人。**完全可逆、可审计、 可被后续 contradicts 证据推翻**(落实原则 4)。 - **Blocking**:只在「至少共享一个标识符或名称高相似」的实体对间建 coref 假设, 避免 O(n²)。 跨设备时间线、「谁在何时做了什么」由 `same_as` 连通后的实体图自然涌现。 ### 4.7 能力插件层(接入 5 类证据) 每类证据 = 一个 `(摄取 handler, 工具集, 知识源 agent)` 三元组。推理核心不动。 | 插件 | 摄取 | 新工具 | 知识源 agent | |---|---|---|---| | **iOS 提取** | `unzip` 解包为 `tree` 源 | `parse_plist`(含二进制 plist)、`sqlite_tables`/`sqlite_query`(sms.db、WhatsApp `ChatStorage.sqlite`、通讯录)、`parse_ios_keychain`、`read_idevice_info` | `iOSArtifactAgent` | | **安卓整盘** | `mmls` 分区→各分区 `image` 源;可 mount 为 `tree` | 复用 TSK;ext4/F2FS 读取;`fsstat` 探明加密 | 复用 filesystem + `AndroidArtifactAgent` | | **磁盘镜像(E01)** | 已支持(TSK 含 ewf) | 现有 TSK 工具链 | 现有 filesystem/registry | | **归档** | `unzip_archive` 通用解包 | —— | —— | | **媒体/截图** | —— | `ocr_image`(tesseract;注意 DeepSeek 无视觉能力,必须走 OCR) | `MediaAgent` | **安卓风险**:`blk0_sda` 的 `userdata` 分区大概率 FBE 加密。先 `fsstat` 各分区 探明:未加密→TSK 直接用;加密且无密钥→只能分析 `EFS`/`PARAM`/`system` 等非加密区。 `tool_registry.py:80` 的 `_auto_categorize` 改为可扩展:分类由源插件提供自己的 工件分类表,而非全局 Windows 关键词表(解决 P4)。 ### 4.8 Agent 体系重组 现有 7 个 agent 按 Windows 工件命名(registry、communication=邮件/IRC、 network=浏览器/PCAP)。改为按**调查职能**组织,并增加平台特定 agent: - `agent_factory.py` 的 `_AGENT_CLASSES`(:34-40)扩充:新增 `ios_artifact`、 `android_artifact`、`financial`(钱包/交易)、`media`。 - `communication` 泛化:邮件 + IM + 短信,跨平台。 - 新增 **源类型 → 适任 agent** 映射,供 Phase 1 逐源派 triage agent。 - `create_specialized_agent`(:69)的动态组合机制保留——它本就是应对能力缺口的 正确手段,只是工具目录变大后选择空间更丰富。 ### 4.9 编排器多源流水线 | 阶段 | 改造 | |---|---| | Phase 1 | 「单镜像初勘」→ **逐源并行 triage**,每源派类型适配的 agent | | Phase 2 | 假设跨源生成;身份共指假设在此首次登记 | | Phase 3 | **Strategist 循环**:LLM 元 agent 每轮看图决定 propose_lead 或 declare_complete;workers 执行 lead;hypothesis 边重判 — 详见 `DESIGN_STRATEGIST.md` | | Phase 4 | 跨源时间线合并,**按源做时区归一**(iOS UTC vs 安卓本地时间) | | Phase 5 | 一案一份综合报告:含假设结论、实体关联图、每条结论的 provenance 引证 | **Phase 3 的"LLM 决定深度"**(2026-05 实战暴露 Phase 3 单轮触发 + log-odds 通胀致使 8 个 pending leads 一个未派发后落地):调度层从代码硬决策("max_rounds=N, converged→stop")转为 LLM 元 agent 驱动。 - 新 agent `InvestigationStrategist`(`agents/strategist.py`)每轮取一个动作:propose 1-3 lead,或 declare_investigation_complete - 4 个只读视图工具:`graph_overview` / `source_coverage` / `marginal_yield` / `budget_status`(`tools/strategy.py`)让 LLM 看到调度信号 - 2 个写入决策工具:`propose_lead` / `declare_investigation_complete` 是 strategist 的 mandatory_record - 编排器读 `config.yaml:strategist.*` + `config.yaml:budgets.*` 控制 max_rounds 和 hard caps - 看 `[[DESIGN_STRATEGIST]]` 获取完整数据模型、prompt 设计、断连恢复、风险/缓解 断连恢复、运行归档逻辑保留;`graph_state.json` 新增 `investigation_rounds[]` 数组持久化 strategist 每轮决策。 --- ## 5. 数据模型变更汇总 | 节点/结构 | 变更 | |---|---| | `EvidenceSource` | **新增**一等节点(`src-*`) | | `ToolInvocation` | **新增**留痕记录(`inv-*`),随 graph 持久化 | | `Phenomenon` | + `source_id`;description 拆为 `verified_facts[]` + `interpretation`;澄清/移除语义含混的 `confidence`(默认 1.0),观测的可靠性由 grounding 表达 | | `Hypothesis` | + `prior_prob`、`log_odds`(累加量);`confidence` 改为派生值 | | `Entity` | + 类型化标识符集合;通过 `same_as` 边跨源连通 | | Phenomenon→Hypothesis 边 | 携带 `edge_type`,映射到 `log₁₀(LR)`(替换 `_DEFAULT_EDGE_WEIGHTS`);同 `(hyp, edge_type)` 的第 k 条边按 `1/k` 调和衰减 | | Entity→Entity 边 | **新增** `same_as`(由 coref 假设背书,可逆) | | `Lead` | + `proposed_by` / `motivating_hypothesis` / `expected_evidence_type` / `round_number`(strategist 注解) | | `InvestigationRound` | **新增**:strategist 每轮决策的 provenance + before/after 快照 + 收益指标 | `evidence_graph.py` 的 `VALID_EDGE_TYPES`、序列化/反序列化、Jaccard 去重相应适配。 --- ## 6. 组件改动清单 | 文件 | 改动 | |---|---| | `case.py` | **新建**:`Case` / `EvidenceSource` / `SourceRegistry` | | `main.py` | 选源逻辑改为加载 `Case`;类型探测替代扩展名 glob | | `tool_registry.py` | 工具按 `source_id` 参数化;缓存键含 source;`_auto_categorize` 改可扩展;`ToolInvocationLog` | | `evidence_graph.py` | 数据模型变更(第 5 节);LR/对数几率置信度;写入网关 + grounding 校验 | | `base_agent.py` | RECORD 走网关;`add_phenomenon` 改为 `verified_facts`+`interpretation` 接口 | | `agent_factory.py` | `_AGENT_CLASSES` 扩充;源类型→agent 映射 | | `orchestrator.py` | Phase 1 逐源;Phase 4 跨源时区归一;Phase 5 综合报告 | | `agents/` | 新增 `ios_artifact.py` / `android_artifact.py` / `financial.py` / `media.py`;`communication.py` 泛化 | | `tools/` | 新增 `mobile_ios.py`(plist/sqlite/keychain)、`media.py`(OCR)、`archive.py`(解包) | | `config.yaml` / `case.yaml` | 删除 `cfreds_hacking_case`;新建 `case.yaml` 证据清单 | --- ## 7. 构建顺序(按依赖排序) | 阶段 | 内容 | 依赖 | 价值 | |---|---|---|---| | **S1** | 4.1 证据源抽象 + 4.2 工具参数化 + 修 P6 | —— | 地基;先只在 USB E01 上跑通验证不破坏现有逻辑 | | **S2** | 4.3 写入网关 + 4.4 grounding + ToolInvocationLog | S1 | 可靠性核心;可量化「零幻觉录入」 | | **S3** | 4.5 LR/对数几率置信度 | 独立(可与 S2 并行) | 修 P3;置信度可辩护 | | **S4** | 4.7 iOS 插件 + 4.8 agent 重组 | S1 | 覆盖率 1/5 → 4/5 | | **S5** | 4.6 跨源实体解析 | S1+S3 | 跨设备关联,复杂场景能力成型 | | **S6** | 4.7 安卓 + 媒体插件 + 4.9 编排器适配 | S1+S4 | 全 5 份证据接入 | S1+S2+S3 是「把系统改对」;S4-S6 是「把能力铺全」。建议严格按序—— S1 不稳,后面全是空中楼阁。 --- ## 8. 设计取舍与未决问题 1. **grounding 对自由文本的边界**:只硬核验 `verified_facts` 里的结构化原子, `interpretation` 不做逐字核验(诚实划界)。可加一个二级 lint:扫描 interpretation 中形似路径/时间戳/哈希但未被任何引用调用覆盖的串并告警。 2. **LR 标定表初值人定**:先用第 4.5 节的初值跑通;「从标注案例学习 LR」是后续工作。 3. **安卓 userdata 加密**:能否取得解密密钥决定 4.7 安卓插件的证据深度——需尽早探明。 4. **实体解析的破坏性 vs 可逆**:本设计选**可逆的 `same_as` 边**而非破坏性归并—— 牺牲一点查询效率换取完全可审计可回滚,符合原则 4。 5. **报告粒度**:定为「一案一份综合报告」,内嵌每证据小节 + 跨源关联, 而非每证据独立成篇。