patel5d2/Stagy
GitHub: patel5d2/Stagy
一款支持多种载体格式和统计分析检测的全功能隐写术套件,提供从数据隐藏、加密植入到隐写分析检测的完整闭环。
Stars: 0 | Forks: 0
# Stagy
这是一款隐写术套件,能够跨载体隐藏、提取并**检测**隐藏数据。它拥有一个核心库,并由 CLI 和 Web 应用进行封装。红队负责植入;蓝队负责寻找。下一步计划见:[ROADMAP.md](ROADMAP.md)。
## 状态
所有路线图阶段均已实现。包含容器格式和加密层;广泛的载体支持——用于 **PNG/BMP 图像**和 **16位 PCM WAV 音频**的带密钥 LSB,**JPEG DCT (JSteg)**,**扩频音频**,**附加数据/多义文件**,JPEG **EXIF**,文档载体(**零宽文本**,**PDF**,**DOCX**),以及仅限实验室环境的**网络隐蔽通道**。检测部分(阶段 6)已完成:针对 LSB 的卡方检验、RS 分析和样本对分析,加上针对附加数据的熵分析和文件雕刻,融合为一个经过校准的裁定结果,并由基准测试/校准框架提供支持。攻击/检测闭环已完成。**Web 应用**(阶段 7)提供了 FastAPI 后端和 React/Tailwind 前端(隐藏/提取/检测),**Docker** 文件(阶段 9)则通过 nginx 实现二者的自托管部署。请参阅下方的**功能矩阵**,以及 [docs/advanced-techniques.md](docs/advanced-techniques.md),了解 JPEG DCT、扩频和网络通道及其真实的权衡。
## 功能矩阵
| 技术 | CLI | 载体 | 容量 | 鲁棒性 | 可检测性 | 备注 |
|---|---|---|---|---|---|---|
| 图像 LSB (带密钥) | `image` | PNG/BMP | 高 | 重新编码时会损坏 | RS / 样本对可捕获 | — |
| JPEG DCT (JSteg) | `jpeg` | JPEG | 中等 | 可复制,重编码会损坏 | 直方图攻击 (顺序式) | `[jpeg]` |
| 音频 LSB | `audio` | WAV PCM16 | 高 | 重量化时会损坏 | 卡方检验 / RS | — |
| 音频扩频 | `audio --method spread` | WAV PCM16 | 低 | 抗噪声/滤波 | 困难 (带密钥,次噪声) | — |
| 附加/多义文件 | `meta --technique appended` | 任意 | 无限 | 可复制,重新解析会损坏 | 文件雕刻 (瞬间) | — |
| EXIF 字段 | `meta --technique exif` | JPEG | 小 | 剥离元数据时会损坏 | `meta scan` (瞬间) | `[docs-fmt]` |
| 零宽 / PDF / DOCX | `meta --technique …` | 文本/PDF/DOCX | 低–中 | 脆弱 | 不定 | `[docs-fmt]` |
| 网络隐蔽通道 | `net` **(仅限实验环境)** | IP/TCP 头 | 极小 | NAT/防火墙重写 | 时序/字段分析 | `[network]` + root |
## 安装
```
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
```
## 设计概览
`明文 → [压缩] → 加密 (AES-256-GCM) → 封装 (Stagy 容器) → 带密钥分散进入载体 LSB`
- **编解码器只是无状态的位载体。** 它们存储/检索封装的字节;对加密一无所知。加密和封装位于其上一层 (`container.py`)。
- **先加密,后植入。** 被检测到 ≠ 被解密。
- **密码短语具备两项职责。** 它既用于派生 AES 密钥,*又*用于为伪随机的 LSB 选择提供种子,从而使 payload 分散开来,而不是从左到右填充(这种方式会在几秒钟内被卡方检验识别)。
- **切勿将无损载体重新编码为有损格式。** 图像编解码器会拒绝保存为 `.jpg`/`.jpeg`。
## CLI
```
stagy image capacity -c cover.png --bits 1 --channels RGB
stagy image embed -c cover.png -p secret.txt -o stego.png --key "passphrase"
stagy image extract -i stego.png -o recovered.txt --key "passphrase"
stagy audio capacity -c cover.wav --bits 1
stagy audio embed -c cover.wav -p secret.txt -o stego.wav --key "passphrase"
stagy audio extract -i stego.wav -o recovered.txt --key "passphrase"
# JPEG DCT (JSteg) — 能够在复制中存活,无法在重新编码中存活。需要:pip install 'stagy[jpeg]'
stagy jpeg embed -c cover.jpg -p secret.txt -o stego.jpg --key "passphrase"
stagy jpeg extract -i stego.jpg -o recovered.txt --key "passphrase"
# 扩频音频 — 抗噪声,容量低,仅限 keyed。
stagy audio embed -c cover.wav -p secret.txt -o stego.wav --method spread --key "passphrase"
stagy audio extract -i stego.wav -o recovered.txt --method spread --key "passphrase"
# metadata / document 技术:appended | exif | zerowidth | pdf | docx
stagy meta embed --technique exif -c cover.jpg -p secret.txt -o stego.jpg --key "passphrase"
stagy meta extract --technique exif -i stego.jpg -o recovered.txt --key "passphrase"
stagy meta scan -i suspect.jpg # dump EXIF/XMP — the tell a hidden payload leaves
# 网络隐蔽通道 — 仅限 LAB(需要 root + 您控制的接收器):
# pip install 'stagy[network]'
# stagy net send -p secret.txt --dst 10.0.0.2 --field ip_id --key "passphrase"
# stagy net recv --count 86 --field ip_id -o recovered.txt --key "passphrase"
```
密码短语来源于 `--key`、`STAGY_KEY` 环境变量或交互式提示(绝不回显)。提取出的字节会输出至 `-o`(或显式使用 `--stdout`),默认情况下绝不直接打印。
## 检测
```
stagy detect -i suspect.png # calibrated verdict + posterior
stagy detect -i suspect.png --reference orig.png # near-conclusive with the original
stagy detect -i ./share # bulk-scan a tree, ranked most-suspicious first
stagy detect -i ./share --fail-on-flag # exit 2 if anything is flagged (cron/CI)
stagy bench --covers ./photos --fit # measure every detector, recalibrate
```
将 `detect` 指向某个目录会递归扫描该目录,并按 **P(隐藏数据)** 对文件进行排序打印——这是整个检测器设计所优化的低误报率分流顺序——使用 `--all` 可列出每个文件,使用 `--report json` 可获取机器可读的数组。校准参数在扫描过程中仅加载一次,若遇到单个无法读取的文件会生成报告,而不会导致程序致命中断。
`detect` 报告的是**后验概率**,而不仅仅是一个标签,裁定阈值来源于实测的操作点——即在留出数据上,检测器产生 1%(疑似隐写)或 10%(可疑)误报率时的得分。这对应于 MITRE ATT&CK [T1027.003](https://attack.mitre.org/techniques/T1027/003/)。
解读后验概率时需考虑基准率:在默认的 1/1000 先验概率下,即使是强信号通常也只会有几个百分点。这是数学运算的结果,而不是 bug。可使用 `--prior` 来针对定向搜寻与批量扫描进行建模。
`stagy bench` 是确保蓝队功能真实可靠的关键——它会构建带有标签的真实数据集,在留出数据上评估每个分析器的得分,并报告**在固定的低误报预算下的召回率**,而非单纯追求准确率。当前实测的基准线(合成载体,仅作为回归防护):
| 检测器 | AUC | 召回率 @1% FPR |
|---|---|---|
| reference-diff (已知纯净原图) | 1.000 | 100.0% |
| sample-pairs (Dumitrescu–Wu–Wang) | 0.998 | 96.0% |
| rs-analysis (Fridrich) | 0.997 | 95.5% |
| chi-square (Westfeld–Pfitzmann) | 0.947 | 76.5% |
| **FUSED (盲测,无参考图)** | **1.000** | **99.5%** |
这两种结构化估计器——RS 和样本对——能够读取 LSB 平面的空间相关性,因此*带密钥*的分散植入无法像骗过基于直方图的卡方检验那样躲过它们的检测。在 5% 填充率下,对带密钥植入的盲测召回率从 9%(仅用卡方检验)提升到了 **96%**,这得益于两种估计器的融合,并且它们独立的相互印证使得裁定结果值得信赖,而不是单凭一次嘈杂的猜测。详情请参见 [docs/detection-benchmark.md](docs/detection-benchmark.md)。
一组独立的分析器专门用于捕获与 LSB 平面*正交*(无关)的隐藏技术,每一个都能提供确定性的证据——在 PNG 的 `IEND` 标志后隐藏的 ZIP 是一个既定事实,而不是概率——因此它们被直接赋予权重而非进行校准,且特征缺失被视为中立,绝不代表最终结论是安全的:
- **文件雕刻** 会扫描载体 EOF 标记之后的区域,以寻找已知的签名(ZIP、次要的 PNG/JPEG、PDF 等)。
- **熵分析** 能够标记出高信息熵的附加数据块,即使它没有任何签名(如加密的 payload)。
- **PNG 文本块**(`tEXt`/`zTXt`/`iTXt`)是图像查看器永远不会展示的藏匿点;该分析器会对那些能解码出真实文件/容器,或是体积庞大且不透明的高熵数据块进行标记,同时保留普通的元数据和 XMP 不受影响。
因为这些技术与 LSB 植入是正交的,一旦触发信号,就会为最终的裁定结果设定一个*下限*,而不会被纯净的像素平面平均化稀释掉。
## Web 应用
```
pip install -e ".[web]"
uvicorn web.backend.app:app --reload # API on :8000, docs at /docs
npm --prefix web/frontend install
npm --prefix web/frontend run dev # UI on :5173
```
包含三大流程:**隐藏**(载体 + payload + 密码短语,带实时容量计),**提取**(隐写文件 + 密码短语 → payload),以及**检测**(裁定结果、各检测器的证据、渲染后的 LSB 位平面,以及让植入签名清晰可见的数值对直方图)。
上传的文件**仅在内存中处理,绝不持久化存储**——每个请求都会分配一个临时目录,在处理完成(包括发生错误时)后即被删除。文件类型通过文件魔术头进行白名单校验,而不是依据文件扩展名。上传大小限制在 16 MB 以内,且请求按 IP 进行速率限制。植入操作和隐写分析在 threadpool 中运行,因此大型载体文件不会阻塞事件循环。
网络隐写术被刻意**不**通过 HTTP 暴露——原始的数据包重写操作并不适合放在网页表单背后执行。
密码短语会通过请求体传输,因此请部署在 TLS 之后。设计理念及其基本原理详见:[web/frontend/DESIGN.md](web/frontend/DESIGN.md)。
## 自托管 (Docker)
```
docker compose up --build # UI on http://localhost:8080
```
包含两个容器:`api` (uvicorn) 和 `web` (由 nginx 提供已构建的前端服务,通过 compose 网络将 `/api` 代理至 API)。API **不会**直接暴露给宿主机——只有 Web UI 会——因此整个应用是同源的。它可以无缝接入 Proxmox 家庭实验室,并隐藏在 Traefik + Cloudflare Tunnel 背后:只需将隧道指向 `web` 服务即可获得演示 URL。
## 库
```
import stagy
stagy.hide("cover.png", b"secret bytes", "stego.png", passphrase="pw", compress=True)
result = stagy.reveal("stego.png", passphrase="pw")
result.payload # -> b"secret bytes"
```
## 许可证
MIT
标签:DNS 反向解析, Python, React, Syscalls, 信息隐藏, 密码学, 手动系统调用, 无后门, 请求拦截, 逆向工具, 隐写分析, 隐写术