wmarcelod/pdf_steg
GitHub: wmarcelod/pdf_steg
PDF文本层隐写工具包,利用PDF视觉渲染与软件文本提取之间的不一致性来隐藏和提取秘密消息。
Stars: 0 | Forks: 0
# pdf_steg
[Português](README.pt-BR.md) · English


基于 [PyMuPDF](https://pymupdf.readthedocs.io/) 构建。
## 功能说明
一个 PDF 有两层可能会产生不一致:人类所看到的内容(渲染的视觉字形)
和软件提取的内容(文本层/字符对象)。本工具
专门用于操作这种不一致。
两种技术,对应两个子命令:
| 子命令 | 可见页面 | 文本层内容 |
| ---------- | ---------------------------------- | ----------------------------------------- |
| `hide` | 与原件相同(已光栅化) | 仅包含秘密消息中选定的字母,位于其原始位置 |
| `embed` | 与原件相同 | 原始文本 + 以不可见方式渲染的 `[STG::STG]` 负载 |
这两种方式生成的 PDF 在人类读者看来都与原件完全相同。
区别在于 `pdftotext` / `page.get_text()` / Ctrl+A 返回的内容。
## 存在意义
此功能具有双用途。文档记录的合法用途包括:
- **对您拥有所有权的系统进行 AI 智能体安全测试** —— 验证文档
摄取管道(RAG、摘要、OCR)是否会受到人类审阅者不可见内容的
影响。这与应用于 PDF 输入的提示注入(prompt-injection)研究理念相同。
- **水印** —— 嵌入一个能在复制粘贴后依然存在,且不会污染可见版面的属性/追踪字符串。
- **隐写术研究与 CTF 竞赛** —— 清晰展示文本层与图像层的差异。
- **教育** —— 向学生展示 PDF 文本提取与视觉渲染之间的区别。
超出范围的行为:未经授权针对第三方系统,或在对抗性部署中逃避
检测。请在您拥有或已获授权测试的基础设施上进行测试。
## 安装
```
pip install pymupdf
```
## 用法
### `analyze` —— 字母清单
```
python pdf_steg.py analyze input.pdf
```
打印 PDF 中每种字符的数量。使用此命令可了解您的
秘密消息是否可以通过 `hide` 技术进行编码(消息中的每个字母
必须至少在文档中出现一次)。
### `hide` —— 选择性光栅化
```
python pdf_steg.py hide input.pdf -m "secret message" -o out.pdf [--mode MODE] [--seed N] [--dpi N] [--strict]
```
将每一页渲染为图像,然后重新插入一个仅包含 `--message` 字符的
*不可见文本层*(`render_mode=3`),每个字符都放置在
源 PDF 中的原始位置。此操作后,复制粘贴整个 PDF 仅会返回
秘密消息;其余所有内容均为图像,因此无法被选中。
默认情况下,该工具会尝试嵌入消息中的**所有**字符 —— 字母、
数字、标点符号和空白。如果某个非字母数字字符(例如 `@`、
空格)无法被放置(在 PDF 中没有出现过,或没有可行的排列顺序),
它将自动从嵌入消息中被丢弃,并在标准错误输出(stderr)中报告遗漏情况。
字母数字是必不可少的 —— 丢失其中一个就会破坏消息 —— 因此缺少字母是一个硬性错误。
传递 `--strict` 可使**任何**缺失字符都成为硬性错误。
`--mode` 控制如何从可用的事件中选取位置:
| 模式 | 行为 |
| -------- | ------------------------------------------------------------------ |
| `greedy` | 游标后的首个匹配 —— 字符聚集在起始位置附近 |
| `spread` | **默认。** 分层随机 —— 每个字符通过抖动(jitter)定位到文档中属于自己的槽位,当理想槽位为空时向前落入下一个槽位 |
| `even` | 确定性槽位中心 —— 均匀分布,无随机性 |
如果消息是可行的(每个必不可少的字符至少有一个有序的出现),
则 `spread` 和 `even` 保证能将其插入;它们绝不会在后期失败。
`--seed N` 使 `spread` 具有可重复性。`--dpi` 控制光栅化分辨率(默认 220)。
### `reveal` —— 读取 `hide` 生成的 PDF
```
python pdf_steg.py reveal out.pdf
```
转储 PDF 文本层两次:按提取原样输出(包含因字符处于不同视觉行而产生的换行符)
以及去除了所有空白的“紧凑”变体,后者即为消息。
### `embed` —— 不可见文本负载
```
# default: 保持可见文本层完整
python pdf_steg.py embed input.pdf -m "secret message" -o out.pdf
# 同时光栅化可见文本,使 payload 成为唯一可提取的文本
python pdf_steg.py embed input.pdf -m "secret message" -o out.pdf --rasterize [--dpi N]
```
将消息编码为 `[STG::STG]`,并将其作为 1 磅(pt)的不可见文本块添加到
第一页。该页面的渲染效果与原件相同。
| 标志 | 可见页面 | `get_text()` 返回内容 |
| ---------------- | -------------------- | --------------------------------------------------- |
| (无,默认) | 与源文件相同 | 源文本 + `[STG:...:STG]` |
| `--rasterize` | 与源文件相同 | 仅 `[STG:...:STG]`(可见文本变为图像) |
base64 编码层意味着即使嵌入的字体缺少某些字形,
消息也可以包含任何 UTF-8 字符(重音符号、表情符号等)。
### `extract` —— 读取 `embed` 生成的 PDF
```
python pdf_steg.py extract out.pdf
```
在页面文本中搜索 `[STG:...:STG]` 标记,对负载进行 base64 解码,
并打印结果。
## 限制
- **源 PDF 必须具有可提取的文本层。** 未经 OCR 处理的扫描版 PDF
没有可供操作的文本 —— 请先对其进行 OCR 识别。
- **`hide` 要求消息中的每个基本(字母数字)字符都存在于
源文件中。** 重音符号会被标准化(例如将 `á` 匹配为 `a`)。
非字母数字字符(空格、标点符号、符号)则尽最大努力处理:
工具会尝试嵌入它们,但如果 PDF 中没有可用的事件,则会在丢弃它们时输出一条 stderr 警告。
添加 `--strict` 可在此情况下直接报错退出。
- **`embed`(默认模式)在文本层中保留可见文本。** 任何人
在渲染的 PDF 上执行 Ctrl+A 时,都会在剪贴板中的某处看到
`[STG:...:STG]`。请使用 `--rasterize` 以获得更强的隐蔽性。
- **无加密。** 负载是 base64 编码,而非加密的。如果您需要在
隐匿性之上保证机密性,请在传入消息之前自行对其进行加密。
## 文件
- [`pdf_steg.py`](pdf_steg.py) —— CLI 命令行工具
- [`make_sample.py`](make_sample.py) —— 生成小型测试 PDF
- `sample.pdf` / `big.pdf` —— 示例输入(运行 `make_sample.py` 后获得)
## 许可证
[MIT](LICENSE) © 2026 Marcelo Duchene
标签:AI安全测试, CTF工具, DNS 反向解析, PDF处理, PDF隐写, PyMuPDF, Python, RAG管道测试, 不可见文本, 信息隐藏, 内容篡改, 字符对象操作, 对抗性机器学习, 提示注入研究, 搜索语句(dork), 数字水印, 数据提取, 数据隐写, 文本层隐写术, 文本提取, 无后门, 漏洞搜索, 逆向工具, 隐蔽通信