unprovable/ShadowCat
GitHub: unprovable/ShadowCat
ShadowCat 是一个离线单页 HTML 工具,通过摄像头扫描二维码序列在两台设备之间实现无网络环境下的文件传输。
Stars: 354 | Forks: 27
[](https://github.com/unprovable/ShadowCat/actions/workflows/tests.yml)
# ShadowCat
这是一个完全离线的单文件 HTML 页面,旨在通过二维码在两台设备之间传输数据——专为那些无线电模块(BLE、NFC 等)已损坏,但摄像头和浏览器仍能正常使用的旧手机而设计。
点击此处使用:https://shadowcat.online/
## 选项卡
- **生成** — 将文本编码为单个二维码。
- **扫描** — 通过摄像头解码单个二维码。
- **发送文件** — 选择文件,设置 chunk 大小 / FPS / ECC,可选择是否**压缩**,然后点击开始。当 gzip 压缩后的 payload 没有变小时(例如 PDF/JPG/MP4),会自动回退到不压缩。以选定的 FPS 不断循环播放 `[header, chunk1…chunkN]`。支持暂停 / 恢复 / 停止。
- **从此处开始** — 从选定的帧索引开始循环;随后会继续向前播放,并在正常情况下循环回到 header。
- **显示帧** + **显示** / **−** / **+** — 静态显示确切的某一帧,用于重新发送特定的缺失 chunk。该数字与接收端缺失 chunk 网格中显示的 chunk 索引相对应(0 = header)。
- **接收文件** — 启动摄像头并对准发送端。会自动检测 Header,进度条会逐步填满,缺失 chunk 网格会显示尚未接收到的部分。接收完成后,会验证文件的 CRC 并出现下载按钮。
## 协议
- Header: `QRX1|H|||||`
- 数据: `QRX1|D||` (索引从 1 开始)
- `` 是一个以逗号分隔的列表,默认为空。已定义的 flag 包括:`gz` = payload 已进行 gzip 压缩。
- `` 是传输时的体积(设置了 `gz` 时为压缩后的大小);`` 始终基于原始字节计算。
- Base64 字符表不包含 `|`,因此只需使用 `split('|')` 即可完成解析。
- 接收端根据索引追踪 chunk,忽略重复项,并通过 CRC 对 header 进行去重。未知的 flag 将被拒绝。
有关完整的传输格式、帧图和
不变式,请参阅 [SPEC.md](SPEC.md)。参考的编码器和解码器位于 [`tools/`](tools/) 中:
```
pip install qrcode[pil] pyzbar pillow opencv-python
python tools/qrx1_encode.py myfile.bin --out frames/
python tools/qrx1_encode.py myfile.bin --gzip --out frames/ # gzip the payload
python tools/qrx1_decode.py frames/ -o out/
```
## 测试
收发双方都会基于共享的 golden fixture(已知输入字节 → 已知
CRC 和 header)进行断言校验。如果 JS 或 Python 实现偏离了规范,
其对应的测试就会报错。
```
# Python — pytest,zero deps;如果未安装
# qrcode/pyzbar/PIL,将跳过可选的 QR-image roundtrip。
python -m pytest tests/test_qrx1.py
# HTML — Node 内置 runner;直接从
# shadowcat.html 中提取 protocol helpers,并在 vm 中执行它们。无需 npm install。
node --test tests/test_shadowcat.mjs
```
## 针对旧手机的实用提示
- 摄像头需要在 HTTPS 或 localhost 环境下运行 —— `file://` 无法获取 `getUserMedia` 权限。请使用 `python3 -m http.server 8000` 启动服务,并通过局域网访问 `http://:8000/qrcode.html`。iOS Safari 在进行跨设备访问时还要求必须使用 HTTPS —— 对于局域网部署,可以使用 `caddy` 或自签名证书来解决。
- 如果在渲染某帧时失败(提示 "code length overflow"),请减小 chunk 大小或降低 ECC 级别。
- 500 字符 × 3 fps ≈ 1.1 KB/s base64 数据 ≈ 0.83 KB/s 原始数据。一个 100 KB 的文件每个循环大约需要 2 分钟;接收端通常需要 1 到 2 个循环。
- 如果旧设备解码困难:请降低 FPS,将 ECC 提升至 Q 级,将 chunk 大小缩减至约 300 个字符 —— 这样会生成更小、密度更低的二维码。
## 相关项目
使用二维码进行数据传输是一个被广泛探索的想法。以下是一些其他项目的参考资料,可能对您的学习或功能扩展有所帮助:
* IP-Over-QR Codes - https://hackaday.com/2016/11/22/ip-over-qr-codes/
标签:HTML, MITM代理, 二维码, 后端开发, 多模态安全, 数据传输, 数据可视化, 文件传输, 离线工具, 逆向工具