From 5fce78d798fe798447f300b885f48a7850b7c632 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:10:11 +0800 Subject: [PATCH 01/16] Add synthetic detector-evasion benchmark --- research/evasion_benchmark.py | 155 ++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 research/evasion_benchmark.py diff --git a/research/evasion_benchmark.py b/research/evasion_benchmark.py new file mode 100644 index 0000000..fb6a9f9 --- /dev/null +++ b/research/evasion_benchmark.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Synthetic detector-evasion benchmark for defensive research. + +This module never loads, mutates, executes, or emits real payloads/binaries. +It operates only on abstract feature vectors so experiments are reproducible +without providing a path to bypass production AV/EDR. +""" +from __future__ import annotations +import argparse +import json +import math +import random +from dataclasses import dataclass, asdict +from pathlib import Path + +FEATURES = ("exec_surface", "io_surface", "network_surface", "persistence_surface", "entropy", "token_overlap") +WEIGHTS = { + "exec_surface": 1.25, + "io_surface": 0.75, + "network_surface": 1.10, + "persistence_surface": 1.50, + "entropy": 0.55, + "token_overlap": 1.35, +} +BIAS = -2.65 +THRESHOLD = 0.50 + +@dataclass +class Sample: + sample_id: str + label: int + features: dict[str, float] + +def sigmoid(x: float) -> float: + return 1.0 / (1.0 + math.exp(-x)) + +def score(features: dict[str, float]) -> float: + z = BIAS + sum(WEIGHTS[k] * features[k] for k in FEATURES) + return sigmoid(z) + +def detected(features: dict[str, float]) -> bool: + return score(features) >= THRESHOLD + +def clamp(v: float) -> float: + return max(0.0, min(1.0, v)) + +def generate(seed: int = 2026, n: int = 240) -> list[Sample]: + rng = random.Random(seed) + rows: list[Sample] = [] + for i in range(n): + label = 1 if i < n // 2 else 0 + center = 0.68 if label else 0.28 + f = {} + for name in FEATURES: + jitter = rng.uniform(-0.22, 0.22) + feature_center = center + if name == "entropy": + feature_center += 0.08 if label else -0.02 + if name == "persistence_surface": + feature_center += 0.10 if label else -0.06 + f[name] = clamp(feature_center + jitter) + rows.append(Sample(f"S{i:04d}", label, f)) + return rows + +def perturb(features: dict[str, float], profile: str) -> dict[str, float]: + """Apply abstract, non-operational feature-space perturbations only.""" + f = dict(features) + if profile == "P1": + f["token_overlap"] = clamp(f["token_overlap"] - 0.18) + f["entropy"] = clamp(f["entropy"] + 0.07) + elif profile == "P2": + f["exec_surface"] = clamp(f["exec_surface"] - 0.12) + f["io_surface"] = clamp(f["io_surface"] - 0.10) + f["entropy"] = clamp(f["entropy"] + 0.10) + elif profile == "P3": + f["token_overlap"] = clamp(f["token_overlap"] - 0.16) + f["network_surface"] = clamp(f["network_surface"] - 0.10) + f["persistence_surface"] = clamp(f["persistence_surface"] - 0.08) + elif profile != "baseline": + raise ValueError(f"unknown profile: {profile}") + return f + +def metrics(samples: list[Sample], profile: str) -> dict: + tp = fp = tn = fn = 0 + scores: list[float] = [] + for s in samples: + f = perturb(s.features, profile) + pred = detected(f) + scores.append(score(f)) + if s.label and pred: tp += 1 + elif s.label and not pred: fn += 1 + elif not s.label and pred: fp += 1 + else: tn += 1 + recall = tp / (tp + fn) if tp + fn else 0.0 + fpr = fp / (fp + tn) if fp + tn else 0.0 + return { + "profile": profile, + "tp": tp, "fn": fn, "fp": fp, "tn": tn, + "recall": round(recall, 4), + "false_positive_rate": round(fpr, 4), + "mean_score": round(sum(scores) / len(scores), 4), + } + +def sensitivity(samples: list[Sample]) -> list[dict]: + out = [] + positives = [s for s in samples if s.label == 1] + for name in FEATURES: + changed = 0 + delta_sum = 0.0 + for s in positives: + base = score(s.features) + f = dict(s.features) + f[name] = clamp(f[name] - 0.15) + after = score(f) + delta_sum += base - after + changed += int(detected(s.features) and not detected(f)) + out.append({ + "feature": name, + "mean_score_drop": round(delta_sum / max(1, len(positives)), 4), + "decision_flips": changed, + }) + return sorted(out, key=lambda x: (x["decision_flips"], x["mean_score_drop"]), reverse=True) + +def run(seed: int, n: int) -> dict: + samples = generate(seed, n) + profiles = ["baseline", "P1", "P2", "P3"] + results = [metrics(samples, p) for p in profiles] + baseline_recall = results[0]["recall"] + for row in results: + row["recall_delta_vs_baseline"] = round(row["recall"] - baseline_recall, 4) + return { + "schema": "lr-payload-lab/synthetic-evasion-benchmark-v1", + "seed": seed, + "samples": n, + "scope": "synthetic feature vectors only; no executable payloads or production AV bypass", + "detector": {"threshold": THRESHOLD, "weights": WEIGHTS, "bias": BIAS}, + "results": results, + "sensitivity": sensitivity(samples), + } + +def main() -> int: + ap = argparse.ArgumentParser(description="Run the synthetic detector-evasion benchmark") + ap.add_argument("--seed", type=int, default=2026) + ap.add_argument("--samples", type=int, default=240) + ap.add_argument("--json", type=Path) + args = ap.parse_args() + report = run(args.seed, args.samples) + text = json.dumps(report, ensure_ascii=False, indent=2) + if args.json: + args.json.write_text(text + "\n", encoding="utf-8") + print(text) + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) From 4601e2ee590f4d9a1e7eb148a65c694b650ac89b Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:10:13 +0800 Subject: [PATCH 02/16] Document safe detection-evasion research track --- docs/detection-evasion-study.md | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/detection-evasion-study.md diff --git a/docs/detection-evasion-study.md b/docs/detection-evasion-study.md new file mode 100644 index 0000000..81d2c6b --- /dev/null +++ b/docs/detection-evasion-study.md @@ -0,0 +1,59 @@ +# Detection Evasion Study — Safe Research Track + +这个实验用于展示 **“检测器在输入特征发生扰动后是否仍然稳定”**,而不是提供真实杀软绕过能力。 + +## 研究问题 + +1. 一个检测器过度依赖哪些特征? +2. 当这些特征发生小幅变化时,召回率下降多少? +3. 哪些扰动最容易造成 decision flip? +4. 如何通过特征多样化、阈值校准与规则组合提高鲁棒性? + +## 实验设计 + +`research/evasion_benchmark.py` 只生成合成特征向量,不读取 PE/ELF,不执行载荷,也不调用 Defender/EDR。 + +每个样本由 6 个抽象维度组成: + +- `exec_surface` +- `io_surface` +- `network_surface` +- `persistence_surface` +- `entropy` +- `token_overlap` + +检测器是固定权重的可解释模型。P1/P2/P3 是 **纯特征空间扰动**,不对应真实混淆、注入、打包或规避技术。 + +## 运行 + +```bash +python research/evasion_benchmark.py +python research/evasion_benchmark.py --samples 1000 --json report.json +``` + +输出包括: + +- baseline / P1 / P2 / P3 的 TP、FN、FP、TN +- Recall 与 False Positive Rate +- 相比 baseline 的 recall delta +- 每个特征的平均分数下降和 decision flips + +## 导师展示建议 + +把重点放在 **“为什么检测器漏掉了”** 和 **“怎么把它修好”**: + +```text +Synthetic Sample + ↓ +Explainable Detector + ↓ +Feature-space Perturbation + ↓ +Recall / FPR / Decision Flip + ↓ +Sensitivity Analysis + ↓ +Detector Hardening +``` + +这条路线能体现检测对抗、实验设计、指标评估和可解释性,同时不会把仓库变成真实 AV/EDR 规避工具。 From 0d50b4bd9d3988f60098c398124eb1f482975746 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:10:18 +0800 Subject: [PATCH 03/16] Test synthetic evasion benchmark --- tests/test_evasion_benchmark.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/test_evasion_benchmark.py diff --git a/tests/test_evasion_benchmark.py b/tests/test_evasion_benchmark.py new file mode 100644 index 0000000..d299b4c --- /dev/null +++ b/tests/test_evasion_benchmark.py @@ -0,0 +1,27 @@ +import unittest +from pathlib import Path +import importlib.util + +MODULE = Path(__file__).resolve().parents[1] / "research" / "evasion_benchmark.py" +spec = importlib.util.spec_from_file_location("evasion_benchmark", MODULE) +m = importlib.util.module_from_spec(spec) +assert spec and spec.loader +spec.loader.exec_module(m) + +class EvasionBenchmarkTests(unittest.TestCase): + def test_reproducible(self): + self.assertEqual(m.run(2026, 120), m.run(2026, 120)) + + def test_profiles_present(self): + r = m.run(2026, 120) + self.assertEqual([x["profile"] for x in r["results"]], ["baseline", "P1", "P2", "P3"]) + self.assertEqual(len(r["sensitivity"]), 6) + + def test_feature_bounds(self): + for s in m.generate(1, 20): + for value in s.features.values(): + self.assertGreaterEqual(value, 0.0) + self.assertLessEqual(value, 1.0) + +if __name__ == "__main__": + unittest.main() From 80876fcb8a70389666a78dafbe68ded82df139f4 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:11:05 +0800 Subject: [PATCH 04/16] Present detector-evasion research workflow --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 53e7126..f08b7b1 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,26 @@

LR-PayloadLab

LR-PayloadLab reproducible demo

-

Bounded payloads. Verifiable research.

-

Auditable endpoint telemetry scenarios with receipts and rollback.

-

Test Python 3.10+ MIT Version

+

Payload telemetry × Detection evasion research × Reproducible experiments.

+

把“载荷是否能被检测、检测为什么失效、怎样加固检测器”做成一套可复现实验链。

+

Test Python 3.10+ MIT Research

## 30 秒看懂 -用 JSON 描述研究行为,平台先检查动作白名单、路径边界和资源上限,再执行并生成逐动作回执。`rollback` 根据回执清理实验文件。 +LR-PayloadLab 现在包含两条研究链: ```text +Track A Manifest → Policy validation → Bounded execution → Receipt → Rollback + +Track B +Synthetic Sample → Explainable Detector → Feature Perturbation + → Recall / FPR / Decision Flip → Sensitivity Analysis ``` -## 5 分钟 Demo +Track A 用来生成可审计、可回滚的无害遥测场景;Track B 用来研究“检测器面对载荷特征变化时为什么会漏报”。 + +## 载荷实验 ```bash python -m pip install -e . @@ -24,17 +31,45 @@ payload-lab rollback receipt.json payload-lab build examples/telemetry-demo.json --output generated-payload.py ``` -示例只包含临时标记、无害子进程、文件哈希、50ms 计算与延迟。生成文件的 SHA-256 会写入输出,便于实验复现。 +示例只产生临时标记、无害子进程、文件哈希、短时计算与延迟,并记录 SHA-256 和逐动作回执。 + +## Detection Evasion Benchmark + +```bash +python research/evasion_benchmark.py +python research/evasion_benchmark.py --samples 1000 --json report.json +``` + +这部分用于导师展示“免杀/检测对抗”研究方法,但实验对象是 **合成特征向量**,不会生成或修改真实恶意程序,也不会调用生产环境 AV/EDR。 + +输出包括: + +- baseline / P1 / P2 / P3 的 TP、FN、FP、TN +- Recall / False Positive Rate +- 相比 baseline 的 recall delta +- 特征敏感度与 decision flips +- 固定 seed 的可复现实验结果 + +详细设计见 [Detection Evasion Study](./docs/detection-evasion-study.md)。 ## 研究价值 -- **可审计载荷**:行为在 Manifest 中完整列出,生成脚本不隐藏动作。 -- **遥测验证**:可观察文件、进程、计算和时间行为是否被采集。 -- **安全约束**:拒绝未知动作、目录逃逸、长时间计算;无网络、持久化、凭据访问与规避模块。 -- **回滚回执**:实验产生的文件记录在回执中,按工作区边界清理。 +- **可审计 Payload**:Manifest 完整描述实验行为,执行结果有回执。 +- **检测对抗评估**:不只展示“能不能检出”,还量化检测率随特征扰动如何变化。 +- **可解释性**:直接输出哪些特征最容易导致 decision flip。 +- **可复现性**:固定模型、固定随机种子、自动化测试。 +- **安全边界**:不包含真实 AV/EDR 绕过、注入、持久化、凭据访问或隐蔽远控能力。 + +## 导师展示路线 -## 研究路线 +建议现场只演示三步: + +```text +1. 运行 benign payload,展示 receipt / rollback +2. 运行 detection-evasion benchmark,展示 recall 下降 +3. 打开 sensitivity 结果,解释“检测器为什么漏、应该怎么加固” +``` -下一步计划加入跨平台遥测适配器、Sigma 规则回放、实验签名、容器隔离和检测覆盖矩阵。欢迎提交新的**无害、可回滚、可观测**场景。 +这比单纯做一个“免杀程序”更适合研究型项目:既能体现攻防思维,也能留下完整实验方法、指标和代码证据。 作者:LLR6 · MIT License From 3b975be15d01a2cde55d0f91503880e1128467cd Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:11:07 +0800 Subject: [PATCH 05/16] Fix benchmark test module loading --- tests/test_evasion_benchmark.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_evasion_benchmark.py b/tests/test_evasion_benchmark.py index d299b4c..d4e08a4 100644 --- a/tests/test_evasion_benchmark.py +++ b/tests/test_evasion_benchmark.py @@ -1,11 +1,13 @@ import unittest from pathlib import Path import importlib.util +import sys MODULE = Path(__file__).resolve().parents[1] / "research" / "evasion_benchmark.py" spec = importlib.util.spec_from_file_location("evasion_benchmark", MODULE) -m = importlib.util.module_from_spec(spec) assert spec and spec.loader +m = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = m spec.loader.exec_module(m) class EvasionBenchmarkTests(unittest.TestCase): From 0f2f22371f51be3ee135fb123c1625910951c497 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:22:40 +0800 Subject: [PATCH 06/16] Add IcedID ATT&CK research manifest --- research/icedid_case_study.json | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 research/icedid_case_study.json diff --git a/research/icedid_case_study.json b/research/icedid_case_study.json new file mode 100644 index 0000000..ff99ea6 --- /dev/null +++ b/research/icedid_case_study.json @@ -0,0 +1,68 @@ +{ + "family": "IcedID", + "attack_id": "S0483", + "platform": "Windows", + "scope": "public ATT&CK mapping + safe adversary-emulation coverage research", + "techniques": [ + [ + "T1071.001", + "Application Layer Protocol: Web Protocols", + "C2 over HTTPS" + ], + [ + "T1547.001", + "Registry Run Keys / Startup Folder", + "persistence" + ], + [ + "T1055.004", + "Process Injection: Asynchronous Procedure Call", + "remote process injection" + ], + [ + "T1055.012", + "Process Injection: Process Hollowing", + "process hollowing" + ], + [ + "T1082", + "System Information Discovery", + "host discovery" + ], + [ + "T1016", + "System Network Configuration Discovery", + "network discovery" + ], + [ + "T1518.001", + "Security Software Discovery", + "AV discovery" + ], + [ + "T1218.007", + "System Binary Proxy Execution: Msiexec", + "proxy execution" + ], + [ + "T1218.011", + "System Binary Proxy Execution: Rundll32", + "proxy execution" + ], + [ + "T1053.005", + "Scheduled Task/Job: Scheduled Task", + "persistence" + ], + [ + "T1497", + "Virtualization/Sandbox Evasion", + "sandbox filtering" + ], + [ + "T1047", + "Windows Management Instrumentation", + "execution" + ] + ] +} From c739104d7f43688f25d58aa03ae53b1184080094 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:22:42 +0800 Subject: [PATCH 07/16] Add IcedID coverage analyzer --- research/icedid_coverage.py | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 research/icedid_coverage.py diff --git a/research/icedid_coverage.py b/research/icedid_coverage.py new file mode 100644 index 0000000..8815d19 --- /dev/null +++ b/research/icedid_coverage.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import json +from pathlib import Path + +DATA = Path(__file__).with_name("icedid_case_study.json") + +TACTIC_HINTS = { + "T1071.001": "command-and-control", + "T1547.001": "persistence", + "T1055.004": "defense-evasion", + "T1055.012": "defense-evasion", + "T1082": "discovery", + "T1016": "discovery", + "T1518.001": "discovery", + "T1218.007": "defense-evasion", + "T1218.011": "defense-evasion", + "T1053.005": "persistence", + "T1497": "defense-evasion", + "T1047": "execution", +} + +def load(): + return json.loads(DATA.read_text(encoding="utf-8")) + +def summarize(doc): + by_tactic = {} + for tid, name, note in doc["techniques"]: + tactic = TACTIC_HINTS.get(tid, "other") + by_tactic.setdefault(tactic, []).append({"id": tid, "name": name, "note": note}) + return { + "family": doc["family"], + "attack_id": doc["attack_id"], + "technique_count": len(doc["techniques"]), + "by_tactic": by_tactic, + "research_questions": [ + "Which IcedID behaviors are observable with endpoint telemetry?", + "Which ATT&CK behaviors have safe Atomic Red Team equivalents?", + "Which detections remain stable when benign telemetry changes?", + "Where are coverage gaps between behavior, telemetry, and detection?" + ] + } + +if __name__ == "__main__": + print(json.dumps(summarize(load()), ensure_ascii=False, indent=2)) From 266841c188f8051677e1c4c70aed97eab1196b64 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:22:45 +0800 Subject: [PATCH 08/16] Document IcedID adversary-emulation case study --- docs/icedid-case-study.md | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/icedid-case-study.md diff --git a/docs/icedid-case-study.md b/docs/icedid-case-study.md new file mode 100644 index 0000000..ffe7440 --- /dev/null +++ b/docs/icedid-case-study.md @@ -0,0 +1,87 @@ +# IcedID(S0483)行为研究 Case Study + +## 为什么选 IcedID + +IcedID 是 MITRE ATT&CK 收录的真实 Windows 恶意软件家族(S0483)。本项目不保存或分发 IcedID 恶意二进制,而是把公开威胁情报里的行为映射成可复现的检测研究对象。 + +研究链: + +```text +IcedID public threat intelligence + ↓ +MITRE ATT&CK technique mapping + ↓ +Atomic Red Team / safe emulation coverage + ↓ +endpoint telemetry + ↓ +detection rules + ↓ +robustness / false-negative analysis +``` + +## 重点行为 + +当前 case study 选择 12 个适合做检测研究的行为: + +| ATT&CK | 行为 | 研究重点 | +|---|---|---| +| T1071.001 | Web Protocols | HTTPS C2 的网络遥测 | +| T1547.001 | Registry Run Keys / Startup Folder | 启动持久化检测 | +| T1055.004 | APC Injection | 注入行为的事件关联 | +| T1055.012 | Process Hollowing | 父子进程与内存遥测 | +| T1082 | System Information Discovery | 主机信息发现 | +| T1016 | System Network Configuration Discovery | 网络配置发现 | +| T1518.001 | Security Software Discovery | 安全软件枚举 | +| T1218.007 | Msiexec | 系统二进制代理执行 | +| T1218.011 | Rundll32 | DLL 代理执行 | +| T1053.005 | Scheduled Task | 任务计划持久化 | +| T1497 | Virtualization/Sandbox Evasion | 沙箱/分析环境识别 | +| T1047 | WMI | WMI 执行行为 | + +## 和 Atomic Red Team 怎么结合 + +Atomic Red Team 的测试按 ATT&CK technique 组织,每个 technique 都有结构化 YAML/Markdown 定义,并可由 Invoke-AtomicRedTeam 执行。 + +这里的研究原则是: + +1. 先从 IcedID 的 ATT&CK technique 列表出发。 +2. 找到相同 technique 的 Atomic Test。 +3. 阅读 prerequisite / attack / cleanup 定义。 +4. 只在授权、隔离测试机运行。 +5. 保存 EDR/Sysmon/Windows Event Log 遥测。 +6. 把测试结果喂给 LR-PayloadLab 的 detection-resilience 分析。 + +## 导师演示 + +```text +IcedID (S0483) + │ + ├── T1082 System Information Discovery + ├── T1016 Network Configuration Discovery + ├── T1218.011 Rundll32 + ├── T1053.005 Scheduled Task + └── ... + ↓ +Atomic Red Team equivalent + ↓ +Windows telemetry + ↓ +Detection / missed detection + ↓ +Feature sensitivity + detector hardening +``` + +重点不是声称“仓库里放了 IcedID 木马”,而是说明: + +> 我选取真实恶意软件家族 IcedID 作为威胁模型,根据 ATT&CK 的公开行为证据建立可复现实验,并研究不同检测器对这些行为的覆盖和鲁棒性。 + +## 数据文件 + +- `research/icedid_case_study.json`:研究行为清单 +- `research/icedid_coverage.py`:按研究战术聚合行为 +- `research/evasion_benchmark.py`:检测鲁棒性实验 + +## 安全边界 + +仓库不收录真实 IcedID 二进制、不实现凭据窃取/C2/持久化/注入载荷,也不实现 AV/EDR 绕过;实验聚焦公开行为证据、授权对手模拟、遥测与检测工程。 From b73a1fdf2e65bd29eab292d804c0eda2ae6a5304 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:22:47 +0800 Subject: [PATCH 09/16] Test IcedID research mapping --- tests/test_icedid_coverage.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/test_icedid_coverage.py diff --git a/tests/test_icedid_coverage.py b/tests/test_icedid_coverage.py new file mode 100644 index 0000000..3f3d160 --- /dev/null +++ b/tests/test_icedid_coverage.py @@ -0,0 +1,27 @@ +import importlib.util +import sys +import unittest +from pathlib import Path + +P = Path(__file__).resolve().parents[1] / "research" / "icedid_coverage.py" +spec = importlib.util.spec_from_file_location("icedid_coverage", P) +assert spec and spec.loader +m = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = m +spec.loader.exec_module(m) + +class IcedIDCoverageTests(unittest.TestCase): + def test_case_study_shape(self): + d = m.load() + self.assertEqual(d["attack_id"], "S0483") + self.assertEqual(d["platform"], "Windows") + self.assertGreaterEqual(len(d["techniques"]), 10) + + def test_summary_groups(self): + s = m.summarize(m.load()) + self.assertIn("discovery", s["by_tactic"]) + self.assertIn("defense-evasion", s["by_tactic"]) + self.assertEqual(s["technique_count"], 12) + +if __name__ == "__main__": + unittest.main() From 95a2c340ba5eea67c3f5fec7b697aca73afc2ad6 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:23:18 +0800 Subject: [PATCH 10/16] Add IcedID real-threat-model research track --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index f08b7b1..a2057e2 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,50 @@ python research/evasion_benchmark.py --samples 1000 --json report.json 详细设计见 [Detection Evasion Study](./docs/detection-evasion-study.md)。 + +## Real Threat Model: IcedID (S0483) + +为了让实验不只停留在“自造样本”,项目现在加入 **IcedID** 作为真实威胁模型。研究数据来自公开 ATT&CK 行为证据,并映射到可安全复现的 Atomic Red Team / adversary-emulation 流程。 + +当前覆盖 12 个重点技术,包括: + +- T1071.001 Web Protocols +- T1547.001 Registry Run Keys / Startup Folder +- T1055.004 APC Injection +- T1055.012 Process Hollowing +- T1082 System Information Discovery +- T1016 System Network Configuration Discovery +- T1518.001 Security Software Discovery +- T1218.007 Msiexec +- T1218.011 Rundll32 +- T1053.005 Scheduled Task +- T1497 Virtualization/Sandbox Evasion +- T1047 Windows Management Instrumentation + +运行: + +```bash +python research/icedid_coverage.py +``` + +详细案例见 [IcedID Case Study](./docs/icedid-case-study.md)。 + +这条链用于回答一个更接近实战的问题: + +```text +真实恶意软件家族 + ↓ +公开 ATT&CK 行为证据 + ↓ +授权对手模拟 + ↓ +真实终端遥测 + ↓ +检测覆盖 / 漏报 + ↓ +检测器加固 +``` + ## 研究价值 - **可审计 Payload**:Manifest 完整描述实验行为,执行结果有回执。 From fc6009e44a11a2774895245a6b69ea46904bae63 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:30:04 +0800 Subject: [PATCH 11/16] Add Atomic Red Team coverage metadata --- research/atomic_coverage_map.json | 120 ++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 research/atomic_coverage_map.json diff --git a/research/atomic_coverage_map.json b/research/atomic_coverage_map.json new file mode 100644 index 0000000..d2307ab --- /dev/null +++ b/research/atomic_coverage_map.json @@ -0,0 +1,120 @@ +{ + "schema": "lr-payload-lab/atomic-coverage-map-v1", + "threat_model": { + "family": "IcedID", + "attack_id": "S0483", + "platform": "Windows" + }, + "generated_from": "redcanaryco/atomic-red-team public technique YAML metadata", + "mappings": [ + { + "technique_id": "T1071.001", + "technique_name": "Application Layer Protocol: Web Protocols", + "exact_atomic": true, + "atomic_guid": "81c13829-f6c9-45b8-85a6-053366d55297", + "atomic_name": "Malicious User Agents - Powershell", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1071.001/T1071.001.yaml" + }, + { + "technique_id": "T1547.001", + "technique_name": "Registry Run Keys / Startup Folder", + "exact_atomic": true, + "atomic_guid": "e55be3fd-3521-4610-9d1a-e210e42dcf05", + "atomic_name": "Reg Key Run", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1547.001/T1547.001.yaml" + }, + { + "technique_id": "T1055.004", + "technique_name": "Process Injection: Asynchronous Procedure Call", + "exact_atomic": true, + "atomic_guid": "611b39b7-e243-4c81-87a4-7145a90358b1", + "atomic_name": "Process Injection via C#", + "execution_policy": "metadata-only", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1055.004/T1055.004.yaml" + }, + { + "technique_id": "T1055.012", + "technique_name": "Process Injection: Process Hollowing", + "exact_atomic": true, + "atomic_guid": "562427b4-39ef-4e8c-af88-463a78e70b9c", + "atomic_name": "Process Hollowing using PowerShell", + "execution_policy": "metadata-only", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1055.012/T1055.012.yaml" + }, + { + "technique_id": "T1082", + "technique_name": "System Information Discovery", + "exact_atomic": true, + "atomic_guid": "66703791-c902-4560-8770-42b8a91f7667", + "atomic_name": "System Information Discovery", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1082/T1082.yaml" + }, + { + "technique_id": "T1016", + "technique_name": "System Network Configuration Discovery", + "exact_atomic": true, + "atomic_guid": "970ab6a1-0157-4f3f-9a73-ec4166754b23", + "atomic_name": "System Network Configuration Discovery on Windows", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1016/T1016.yaml" + }, + { + "technique_id": "T1518.001", + "technique_name": "Security Software Discovery", + "exact_atomic": true, + "atomic_guid": "f92a380f-ced9-491f-b338-95a991418ce2", + "atomic_name": "Security Software Discovery", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1518.001/T1518.001.yaml" + }, + { + "technique_id": "T1218.007", + "technique_name": "Signed Binary Proxy Execution: Msiexec", + "exact_atomic": true, + "atomic_guid": "a059b6c4-e7d6-4b2e-bcd7-9b2b33191a04", + "atomic_name": "Msiexec.exe - Execute Local MSI file with embedded JScript", + "execution_policy": "metadata-only", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1218.007/T1218.007.yaml" + }, + { + "technique_id": "T1218.011", + "technique_name": "Signed Binary Proxy Execution: Rundll32", + "exact_atomic": true, + "atomic_guid": "57ba4ce9-ee7a-4f27-9928-3c70c489b59d", + "atomic_name": "Rundll32 execute JavaScript Remote Payload With GetObject", + "execution_policy": "metadata-only", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1218.011/T1218.011.yaml" + }, + { + "technique_id": "T1053.005", + "technique_name": "Scheduled Task/Job: Scheduled Task", + "exact_atomic": true, + "atomic_guid": "fec27f65-db86-4c2d-b66c-61945aee87c2", + "atomic_name": "Scheduled Task Startup Script", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1053.005/T1053.005.yaml" + }, + { + "technique_id": "T1497", + "technique_name": "Virtualization/Sandbox Evasion", + "exact_atomic": false, + "atomic_guid": null, + "atomic_name": null, + "execution_policy": "needs-representative-subtechnique", + "source_url": "https://attack.mitre.org/techniques/T1497/", + "note": "Atomic Red Team has no exact parent-level T1497 YAML; do not count this as covered until a representative sub-technique is selected and justified." + }, + { + "technique_id": "T1047", + "technique_name": "Windows Management Instrumentation", + "exact_atomic": true, + "atomic_guid": "c107778c-dcf5-47c5-af2e-1d058a3df3ea", + "atomic_name": "WMI Reconnaissance Users", + "execution_policy": "isolated-lab", + "source_url": "https://github.com/redcanaryco/atomic-red-team/blob/master/atomics/T1047/T1047.yaml" + } + ] +} From e38f3fdbaf2d3e799805fae40a98f8f938dc9a8c Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:30:06 +0800 Subject: [PATCH 12/16] Add IcedID Atomic coverage matrix generator --- research/atomic_coverage_matrix.py | 127 +++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 research/atomic_coverage_matrix.py diff --git a/research/atomic_coverage_matrix.py b/research/atomic_coverage_matrix.py new file mode 100644 index 0000000..64f0cb7 --- /dev/null +++ b/research/atomic_coverage_matrix.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +import argparse +import json +from pathlib import Path + +HERE = Path(__file__).resolve().parent +DEFAULT_MAP = HERE / "atomic_coverage_map.json" +RUN_STATES = {"not_run", "passed", "failed", "skipped"} +DETECTION_STATES = {"not_evaluated", "detected", "missed", "partial"} + +def load_json(path: Path): + return json.loads(path.read_text(encoding="utf-8")) + +def validate_results(doc, mapping): + if doc.get("schema") != "lr-payload-lab/atomic-results-v1": + raise ValueError("unsupported results schema") + known = {(x["technique_id"], x.get("atomic_guid")) for x in mapping["mappings"]} + seen = set() + for i, row in enumerate(doc.get("results", [])): + key = (row.get("technique_id"), row.get("atomic_guid")) + if key in seen: + raise ValueError(f"duplicate result at index {i}: {key}") + seen.add(key) + if key not in known: + raise ValueError(f"unknown technique/guid pair at index {i}: {key}") + if row.get("run_status", "not_run") not in RUN_STATES: + raise ValueError(f"invalid run_status at index {i}") + if row.get("detection", "not_evaluated") not in DETECTION_STATES: + raise ValueError(f"invalid detection at index {i}") + return doc + +def build_report(mapping, results=None): + result_by_key = {} + if results: + validate_results(results, mapping) + result_by_key = {(x["technique_id"], x.get("atomic_guid")): x for x in results.get("results", [])} + + rows = [] + for item in mapping["mappings"]: + key = (item["technique_id"], item.get("atomic_guid")) + observed = result_by_key.get(key, {}) + rows.append({ + **item, + "run_status": observed.get("run_status", "not_run"), + "detection": observed.get("detection", "not_evaluated"), + "telemetry": observed.get("telemetry", []), + "evidence": observed.get("evidence", ""), + "notes": observed.get("notes", item.get("note", "")), + }) + + exact = sum(1 for x in rows if x["exact_atomic"]) + run = sum(1 for x in rows if x["run_status"] != "not_run") + evaluated = [x for x in rows if x["detection"] != "not_evaluated"] + detected = sum(1 for x in evaluated if x["detection"] == "detected") + missed = sum(1 for x in evaluated if x["detection"] == "missed") + partial = sum(1 for x in evaluated if x["detection"] == "partial") + + return { + "schema": "lr-payload-lab/atomic-coverage-report-v1", + "threat_model": mapping["threat_model"], + "summary": { + "ttp_total": len(rows), + "exact_atomic_mappings": exact, + "no_exact_atomic": len(rows) - exact, + "experiments_recorded": run, + "detections_evaluated": len(evaluated), + "detected": detected, + "missed": missed, + "partial": partial, + }, + "rows": rows, + } + +def render_markdown(report): + s = report["summary"] + lines = [ + "# IcedID Atomic Coverage Matrix", + "", + f"- Threat model: **{report['threat_model']['family']} ({report['threat_model']['attack_id']})**", + f"- TTPs in study: **{s['ttp_total']}**", + f"- Exact Atomic mappings: **{s['exact_atomic_mappings']}**", + f"- No exact Atomic mapping: **{s['no_exact_atomic']}**", + f"- Experiments recorded: **{s['experiments_recorded']}**", + f"- Detections evaluated: **{s['detections_evaluated']}**", + "", + "| ATT&CK | Atomic test | Policy | Run | Detection | Telemetry |", + "|---|---|---|---|---|---|", + ] + for row in report["rows"]: + atomic = row["atomic_name"] or "No exact parent-level Atomic" + telemetry = ", ".join(row["telemetry"]) if row["telemetry"] else "-" + lines.append( + f"| {row['technique_id']} | {atomic} | {row['execution_policy']} | " + f"{row['run_status']} | {row['detection']} | {telemetry} |" + ) + lines += [ + "", + "## Reading the matrix", + "", + "- metadata-only: mapped for research and coverage analysis, but this project does not provide an execution wrapper.", + "- isolated-lab: suitable for an authorized isolated test host after reviewing upstream prerequisites and cleanup.", + "- needs-representative-subtechnique: parent ATT&CK technique without an exact parent-level Atomic test.", + "- not_run / not_evaluated: no experimental claim is being made yet.", + "", + "This report records evidence; it does not execute Atomic Red Team tests.", + ] + return "\n".join(lines) + "\n" + +def main(): + ap = argparse.ArgumentParser(description="Build an IcedID ATT&CK / Atomic Red Team coverage matrix") + ap.add_argument("--map", type=Path, default=DEFAULT_MAP) + ap.add_argument("--results", type=Path) + ap.add_argument("--markdown", type=Path) + ap.add_argument("--json", type=Path) + args = ap.parse_args() + mapping = load_json(args.map) + results = load_json(args.results) if args.results else None + report = build_report(mapping, results) + if args.markdown: + args.markdown.write_text(render_markdown(report), encoding="utf-8") + if args.json: + args.json.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps(report["summary"], ensure_ascii=False, indent=2)) + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) From 27b265fff2ae4b239ac054d6810f1a70cc8939c1 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:30:08 +0800 Subject: [PATCH 13/16] Add honest lab results template --- examples/icedid-lab-results.template.json | 119 ++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 examples/icedid-lab-results.template.json diff --git a/examples/icedid-lab-results.template.json b/examples/icedid-lab-results.template.json new file mode 100644 index 0000000..4c5bb98 --- /dev/null +++ b/examples/icedid-lab-results.template.json @@ -0,0 +1,119 @@ +{ + "schema": "lr-payload-lab/atomic-results-v1", + "lab": { + "name": "replace-with-lab-name", + "host": "isolated-windows-test-host", + "edr": "replace-with-product", + "date": "YYYY-MM-DD" + }, + "results": [ + { + "technique_id": "T1071.001", + "atomic_guid": "81c13829-f6c9-45b8-85a6-053366d55297", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1547.001", + "atomic_guid": "e55be3fd-3521-4610-9d1a-e210e42dcf05", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1055.004", + "atomic_guid": "611b39b7-e243-4c81-87a4-7145a90358b1", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1055.012", + "atomic_guid": "562427b4-39ef-4e8c-af88-463a78e70b9c", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1082", + "atomic_guid": "66703791-c902-4560-8770-42b8a91f7667", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1016", + "atomic_guid": "970ab6a1-0157-4f3f-9a73-ec4166754b23", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1518.001", + "atomic_guid": "f92a380f-ced9-491f-b338-95a991418ce2", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1218.007", + "atomic_guid": "a059b6c4-e7d6-4b2e-bcd7-9b2b33191a04", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1218.011", + "atomic_guid": "57ba4ce9-ee7a-4f27-9928-3c70c489b59d", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1053.005", + "atomic_guid": "fec27f65-db86-4c2d-b66c-61945aee87c2", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + }, + { + "technique_id": "T1497", + "atomic_guid": null, + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "No exact parent-level Atomic mapping; select and justify a representative sub-technique before running." + }, + { + "technique_id": "T1047", + "atomic_guid": "c107778c-dcf5-47c5-af2e-1d058a3df3ea", + "run_status": "not_run", + "detection": "not_evaluated", + "telemetry": [], + "evidence": "", + "notes": "" + } + ] +} From a8f7133293e20f6c947a5b02cfaac16e51f29395 Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:30:10 +0800 Subject: [PATCH 14/16] Document IcedID Atomic coverage workflow --- docs/icedid-atomic-coverage.md | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/icedid-atomic-coverage.md diff --git a/docs/icedid-atomic-coverage.md b/docs/icedid-atomic-coverage.md new file mode 100644 index 0000000..36f588b --- /dev/null +++ b/docs/icedid-atomic-coverage.md @@ -0,0 +1,46 @@ +# IcedID × Atomic Red Team Coverage Matrix + +这个模块把真实威胁模型、Atomic Red Team 元数据和实验结果统一成一张 Coverage Matrix。 + +## 当前映射 + +- IcedID 研究 TTP:12 +- 可找到同 ID Atomic YAML:11 +- 无 exact parent-level Atomic:1(T1497) +- 高风险/容易被误用的 Atomic 只记录 metadata,不在 LR-PayloadLab 中封装执行命令。 + +## 生成矩阵 + +先复制 examples/icedid-lab-results.template.json 并填写真实实验结果。 + +然后运行: + + python research/atomic_coverage_matrix.py --results icedid-lab-results.json --markdown icedid-coverage.md --json icedid-coverage.json + +## 实验结果字段 + +- run_status: not_run / passed / failed / skipped +- detection: not_evaluated / detected / missed / partial +- telemetry: 实际拿到的日志源,例如 Sysmon EID 1、Windows Security、EDR alert +- evidence: 截图、日志文件、事件编号或报告路径 +- notes: 失败原因、前置条件、清理情况等 + +## 为什么不直接执行所有 Atomic + +Process Injection、Process Hollowing、Msiexec/Rundll32 等测试可能产生高风险行为。这个仓库负责研究映射、证据记录和检测覆盖分析,不会再封装一层一键执行器。 + +对于 discovery、WMI、Run Key、Scheduled Task 等项目,也应只在有授权的隔离 Windows 测试主机上,阅读 upstream prerequisite / cleanup 后运行。 + +导师展示链路: + + IcedID TTP + ↓ + Exact Atomic metadata + ↓ + Authorized isolated execution + ↓ + Telemetry evidence + ↓ + Detection result + ↓ + Coverage Matrix From 8a5f6cacaba3e80e3ad7341f87848065632da0ce Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:30:12 +0800 Subject: [PATCH 15/16] Test Atomic coverage matrix --- tests/test_atomic_coverage_matrix.py | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/test_atomic_coverage_matrix.py diff --git a/tests/test_atomic_coverage_matrix.py b/tests/test_atomic_coverage_matrix.py new file mode 100644 index 0000000..0ae2335 --- /dev/null +++ b/tests/test_atomic_coverage_matrix.py @@ -0,0 +1,59 @@ +import importlib.util +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +P = ROOT / "research" / "atomic_coverage_matrix.py" +spec = importlib.util.spec_from_file_location("atomic_coverage_matrix", P) +assert spec and spec.loader +m = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = m +spec.loader.exec_module(m) + +class AtomicCoverageMatrixTests(unittest.TestCase): + def setUp(self): + self.mapping = m.load_json(ROOT / "research" / "atomic_coverage_map.json") + + def test_mapping_counts(self): + report = m.build_report(self.mapping) + self.assertEqual(report["summary"]["ttp_total"], 12) + self.assertEqual(report["summary"]["exact_atomic_mappings"], 11) + self.assertEqual(report["summary"]["no_exact_atomic"], 1) + + def test_default_is_not_run(self): + report = m.build_report(self.mapping) + self.assertTrue(all(x["run_status"] == "not_run" for x in report["rows"])) + self.assertEqual(report["summary"]["detections_evaluated"], 0) + + def test_real_result_merge(self): + first = self.mapping["mappings"][0] + results = { + "schema": "lr-payload-lab/atomic-results-v1", + "results": [{ + "technique_id": first["technique_id"], + "atomic_guid": first["atomic_guid"], + "run_status": "passed", + "detection": "detected", + "telemetry": ["example-log"] + }] + } + report = m.build_report(self.mapping, results) + self.assertEqual(report["summary"]["experiments_recorded"], 1) + self.assertEqual(report["summary"]["detected"], 1) + + def test_unknown_result_rejected(self): + results = { + "schema": "lr-payload-lab/atomic-results-v1", + "results": [{ + "technique_id": "T0000", + "atomic_guid": None, + "run_status": "passed", + "detection": "detected" + }] + } + with self.assertRaises(ValueError): + m.build_report(self.mapping, results) + +if __name__ == "__main__": + unittest.main() From ec0c71dc4ef13b5b9f44b561737440414685c8ad Mon Sep 17 00:00:00 2001 From: LR <2830244143@qq.com> Date: Sun, 27 Sep 2026 16:30:26 +0800 Subject: [PATCH 16/16] Expose IcedID Atomic coverage matrix workflow --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index a2057e2..e7c2765 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,23 @@ python research/icedid_coverage.py 检测器加固 ``` + +## IcedID × Atomic Coverage Matrix + +项目已经把 12 个 IcedID 研究 TTP 与 Atomic Red Team 元数据接起来: + +- 11 个 TTP 存在同 ID 的 Atomic YAML +- T1497 是父级技术,当前没有 exact parent-level Atomic,因此不会虚报为“已覆盖” +- Process Injection / Process Hollowing / Msiexec / Rundll32 等高风险项只保留 metadata,不提供二次封装执行器 +- 实验结果默认全部 not_run,只有填入真实遥测和检测证据后才计入覆盖率 + +生成矩阵: + + python research/atomic_coverage_matrix.py --results examples/icedid-lab-results.template.json --markdown icedid-coverage.md --json icedid-coverage.json + +详情见 [IcedID × Atomic Coverage](./docs/icedid-atomic-coverage.md)。 + + ## 研究价值 - **可审计 Payload**:Manifest 完整描述实验行为,执行结果有回执。