abdukir/hi3515-dvr-fpv
GitHub: abdukir/hi3515-dvr-fpv
将2011年报废的海思Hi3515模拟监控DVR通过逆向工程和自定义固件改造为现代化的低延迟FPV视频地面站。
Stars: 0 | Forks: 0
# hi3515-dvr-fpv
**将一台报废的 2011 年 CCTV DVR 变成现代化的 FPV 地面站——通过用从零开始编写的固件替换原有固件。**
这是一个针对 **NetDVR RR104P** 的逆向工程项目:一台基于海思 **Hi3515** SoC(ARM926EJ-S,200 MHz,43 MB 可用 RAM,Linux 2.6.24,BusyBox 1.1.2)构建的 4 通道模拟
安全录像机。它唯一的客户端是一个用于 Internet Explorer 的 ActiveX 插件,因此出厂状态下这个设备纯属电子垃圾。
它现在运行着**我们自己的固件**——一个约 75 KB 的独立 C 程序,没有 libc,也没有厂商 SDK——它可以捕获模拟视频,进行硬件 H.264 编码,录制到 SATA,
通过网络进行实时流传输,并驱动其专属的屏幕 UI。基于浏览器的控制台通过我们设计的协议与之通信。
## 它的实际功能
| | 原厂固件 | 本项目 |
|---|---|---|
| 实时预览 | 仅支持 IE 中的 ActiveX | 任何现代浏览器,基于 WebSocket + MSE 的 H.264 |
| 录制 | 专有 `.ifv` 环形缓冲区 | 包含真实 PTS 的 MPEG-TS 文件,可在任何地方播放 |
| 视频质量 | ~CIF,低码率 | 704×240(全宽 2CIF),FIXQP QP10 ≈ 17-23 Mbps |
| 控制 | 基于 TLV 协议的厂商 CMS | TCP 8090 端口上的纯文本行协议,文档齐全 |
| 屏幕 UI | 厂商菜单 | 我们自己的 OSD:HUD、菜单、鼠标/键盘、回放 |
| 端到端延迟 | — | VGA 输出上约 130 ms(主要是显示器自身的延迟) |
### 为什么要折腾
专门针对 **FPV** 而言:将模拟视频接收器插入复合输入接口,你就能得到一台真正擅长这一唯一关键任务的录像机——以高码率捕获视频链路,同时以最小延迟在显示器上呈现。这台硬件的二手价值约等于零,却拥有一个真正的 H.264 编码器、四个复合输入、一个 SATA 端口和一个 VGA 输出。就其成本而言,这是一个功能非常强大的地面站。
更广泛地说,这是一个如何接管一台锁定且过时的嵌入式 Linux 设备并使其为你所用的完整实例:Dump flash、对无文档的媒体流水线进行逆向工程、编写替代的 init/userland,并带有恢复路径地进行安全刷机。
## 硬件
- **SoC** 海思 Hi3515 v100(ARMv5TE,仅限 OABI 内核)
- **捕获** Techwell TW2866,4× 复合接口,PAL/NTSC
- **编码器/解码器** 硬件 H.264 (VEDU),在编码和回放之间共享
- **RAM** 128 MB,内核限制为 `mem=43M`
- **Flash** 8 MB NOR:`mtd0` boot (1 MB) · `mtd1` kernel (2 MB) · `mtd2` rootfs (5 MB)
- **存储** SATA,4 × vfat 分区挂载于 `/root/rec/a1..a4`
- **输出** VGA(+ 复合接口),USB host,100M 以太网,RS-485 PTZ,蜂鸣器
- **Rootfs** gzip 压缩的 ext2 **RAM disk** —— `/` 是易失性的,这使得无 flash 开发成为可能
## 两种运行方式
### A. 无需刷机(推荐——本项目就是以此方式构建的)
不会产生任何永久性更改。`/` 是一个 RAM disk,因此循环加电(重启)即可完全恢复原厂固件。你需要将 DVR 连接到局域网并拥有 telnet 访问权限(原厂固件开启了 telnetd,root 密码为空)。
```
# 1. 构建 firmware (WSL/Linux,bootlin armv5 uclibc toolchain — 见 docs/BUILD.md)
bash device/dvr/build.sh
# 2. 停止 vendor app,以便我们可以独占 MPP 硬件(仅限单一所有者)
# serial console:`closewd` 然后 `exit` (停用 watchdog,结束 app.out)
# 或通过 telnet:killall mydaemon.out; killall app.out
# 3. 将我们的 binary 推送到 SATA 磁盘并运行它
py -3 tools/dvr.py push device/dvr/dvr /root/rec/a1/dvr 755
py -3 tools/dvr.py shell "/root/rec/a1/dvr 0 0 r 30 9 1 &"
# 4. 检查它是否已启动
py -3 tools/dvr.py status
```
断电重启即可撤销所有更改。
### B. 刷机(持久化——设备可自行引导)
Flash 的更改仅需修改**一个文件**:`/root/run_app.sh` 增加了一个钩子,用于挂载 SATA 并在存在 `/root/rec/a1/boot.sh` 时 `exec` 它,否则退回到执行原厂的 `app.out`。
随后我们的所有内容都存放在 **SATA 磁盘**上,该磁盘可通过网络进行写入——因此这是一次*一次性*的刷机,此后你再也无需触碰 NOR 芯片。
```
power on
└ U-Boot (mtd0) → kernel (mtd1) → ext2 RAM disk (mtd2) → /etc/init.d/rcS
└ /root/run_app.sh
net.sh (eth0 + telnetd) ←── always runs first, so telnet is up no matter what
init.sh (drivers)
├ /root/rec/a1/boot.sh exists ──→ exec it ──→ our DVR ← the new path
└ otherwise ──────────────────→ mydaemon.out + app.out ← stock, untouched
```
**请先阅读 [`docs/FLASH.md`](docs/FLASH.md)。** 简要说明如下:
```
bash flash/build.sh # build the rootfs image (needs your own mtd2 dump, see below)
bash flash/verify.sh # 40+ offline checks; refuses to bless a bad image
py -3 tools/dvr.py deploy # stage boot.sh + dvr + config on SATA, and test them by hand
# on-device preflight:使用设备自身的 kernel 和 gunzip 对 image 进行循环检查
py -3 tools/dvr.py push flash/device/preflight.sh /root/rec/a1/preflight.sh 755
py -3 tools/dvr.py shell "sh /root/rec/a1/preflight.sh /root/rec/a1/mtd2_new.bin"
py -3 tools/dvr.py flash flash/out/mtd2_new.bin --reboot
```
`tools/dvr.py flash` 会将当前的 `mtd2` 备份到 SATA,解除硬件看门狗,执行写入,然后在**允许你重启之前读回 flash 并比对 md5**。
#### 你必须提供自己的固件 dump
本仓库刻意**不包含任何厂商固件二进制文件**(参见[法律声明](#legal-and-scope))。`flash/build.sh` 需要*你的*设备中的 `dump/mtd2_rootfs.bin`——无论如何这都是正确的工作流程,因为每台设备的 config blob 和 MAC 都不同:
```
py -3 tools/dvr.py backup dump/ # pulls mtd0/mtd1/mtd2 off the device, md5-verified
```
## 连接到设备
三个独立通道,在刷机前你希望三者都就绪:
| 通道 | 提供的功能 | 备注 |
|---|---|---|
| **网络** | telnet(root,空密码),我们的控制端口 8090,媒体端口 8091,busybox httpd 8081 | 默认 `192.168.1.108`;我们的 `boot.sh` 会设置它,可通过 `/root/rec/a1/ip` 配置 |
| **串口控制台** | root shell **以及** U-Boot —— 完全无需网络即可工作 | USB-TTL,115200 8N1。**这是恢复路径。** |
| **采集卡** | 从你的 PC 查看 DVR 的实际 VGA 输出 | 任何 HDMI/VGA 采集器;用于验证 UI 工作 |
一切操作均由一个 CLI 驱动:
```
py -3 tools/dvr.py status # alive? what's it running?
py -3 tools/dvr.py shell "ps | head" # telnet
py -3 tools/dvr.py ctl INFO # control protocol
py -3 tools/dvr.py push FILE /root/rec/a1/x # chunked, md5-verified upload
py -3 tools/dvr.py screen shot.png # grab the DVR's screen
py -3 tools/dvr.py serial send "id" # root shell over the console
py -3 tools/dvr.py backup dump/ # pull all three flash partitions
py -3 tools/dvr.py flash IMG --reboot # write mtd2, verified before reboot
py -3 tools/dvr.py restore --reboot # put the stock firmware back
py -3 tools/dvr.py recover --test # rehearse U-Boot + TFTP recovery
```
`tools/dvrlib.py` 将传输陷阱编码为具体行为而不是经验之谈——上传暂存在 SATA 上(`/` 只有约 1.9 MB 可用空间)并以 1 MB 的分块进行(busybox `nc` 在大约 2.5 MB 左右会静默停止),`nc` 监听器会获得其专属的 telnet 会话(否则它会吞掉下一条命令),telnet `exec` 超时是致命的(部分回复会导致会话永久失步),并且 Git-Bash 的 POSIX 路径篡改会被自动还原。
## Web 控制台 (`webapp2/`)
```
cd webapp2 && npm install && node server.js # → http://localhost:8092
```
兼容 Node v14+。需要 `ffmpeg` 位于 `webapp/bin/ffmpeg.exe` 以进行片段重混流(参见[`SETUP.md`](SETUP.md));它**不**参与实时视频路径。
- **实时预览** —— H.264 直接从设备通过 WebSocket 传输到 jMuxer/MSE。无转码。
- **片段** —— 从设备列出录像,在浏览器中播放(无损 TS→MP4 重混流,基于范围服务)或在 **DVR 自身的显示器上**播放,以及下载和删除。
- **监控** —— DVR VGA 输出的远程控制:通道,OSD 方向键,实时画面控制(TW2866 寄存器),回放传输。
- **文件 / Shell** —— 文件管理器以及通过 telnet 的 root shell。
- **配置** —— 设备读数,时钟同步,PAL/NTSC,蜂鸣器开关,以及 `dvr.conf` 编辑器。
后端保持**一个**持久控制连接(设备仅接受四个),并为所有浏览器标签页轮询一次 `INFO`。
## 工作原理
```
TW2866 (4× composite)
│
VI (only the channels something drains — see below)
├──────────────► VO / VGA ── the DVR's own monitor, codec-free, ~130 ms
│ plus an fb0 OSD layer + fb4 cursor overlay
│
└─► VENC ────► pump_encode() ─┬─► MPEG-TS file on SATA (recording)
│ └─► TCP 8091 fan-out (live)
VDEC ◄────┘ (on-screen playback, shares the VEDU with VENC)
control: TCP 8090 + /dev/ttyAMA1 PC: webapp2 (8090 · 8091 → WS · telnet · 8081)
```
重要的架构决策:**捕获和编码只发生一次,而磁盘 / 网络 / 显示是相互独立的消费者。** 添加一个查看器只需消耗一次 `write()`。这就是为什么 200 MHz 的 ARM9 能够在 704×240 分辨率和约 17 Mbps 码率下同时维持这三项任务。
MPP 的启动是对厂商 `app.out` 提取的 trace 进行**逐字节的 ioctl 重放**,因为唯一能获取到的海思 SDK 与此芯片不匹配(`VI_SetPubAttr` → `SYS_NOTREADY`)。每个结构体都是一个带有偏移量注释的魔数 byte array。看起来很糟糕;但这不可避免。`docs/MPP_INIT_SEQUENCE.md` 是其规范说明。
## 逆向工程的发现
重点内容——完整记录见 [`docs/`](docs/):
- **[`PROTOCOL.md`](PROTOCOL.md)** —— 厂商的 8670 TLV 协议,从 ActiveX DLL 和数据包捕获中解码得出。仅第一代设备需要;保留它是因为这是唯一的记录。
- **[`docs/MPP_INIT_SEQUENCE.md`](docs/MPP_INIT_SEQUENCE.md)** —— 使视频流转起来的确切 ioctl 序列,包括那五项让状态在“无画面”和“正常工作”之间产生本质区别的修正。
- **[`docs/IFV_FORMAT.md`](docs/IFV_FORMAT.md)** —— 厂商录像容器格式及其时间→偏移量索引。
- **[`docs/DISPLAY_PATH.md`](docs/DISPLAY_PATH.md)** —— VO/VGA 启动、fb0 OSD,以及位于专用 fb4 overlay 上的无 flash 光标(app.out 自身的技巧)。
- **[`docs/FRONT_PANEL.md`](docs/FRONT_PANEL.md)** —— 18 针面板接口:一个 5×5 GPIO 按键矩阵、LED 阵列和蜂鸣器(`0x20150000` 第 7 位,一个被动换能器,因此它能播放实际音调)。*子板本身未被使用——它的开关已经老化,会自动误触——但逆向工程的研究成果被保留了下来。*
- **[`docs/REVIEW.md`](docs/REVIEW.md)** —— 架构审查、发现的缺陷以及按优先级排列的待办事项。包括**实时视频卡死**问题:空闲的 VI 通道捕获到共享的 VB 池中,但没有任何程序去排空它,导致关键通道资源耗尽。通过针对硬件的 A/B 测试进行了诊断,并且该部分刻意保留了早期错误的分析记录。
- **[`docs/FLASH.md`](docs/FLASH.md)** —— 持久化工作,包括发现厂商自身的 `mtd2` 是一个**为适应分区而被截断的 gzip 流**,且带有错误的数据 CRC(它之所以能启动,仅仅是因为 U-Boot 设置了 `verify=n`)。
## 仓库布局
```
device/dvr/ our firmware — dvr.c + oabi.h/net.h/ts.h/fb.h/ui.h/... , build.sh
tools/ dvr.py + dvrlib.py — one CLI for telnet/control/serial/U-Boot/capture/flash
flash/ the persistence kit: build.sh, verify.sh, mkuimage.py, boot hook, SATA payload
webapp2/ the current web console (Node + browser)
webapp/ gen-1 console that spoke the vendor protocol — kept for the 8670 RE
docs/ protocol specs, RE writeups, build/flash procedures, the review
*.py RE tooling: unpack.py, ext2extract.py, deframe.py, tftp_server.py, ...
```
请从 [`CLAUDE.md`](CLAUDE.md) 开始阅读——这是一份导读文档,它准确说明了哪些部分是当前在用的,哪些是历史遗留的。
## 状态
工作正常并从 flash 运行:冷启动到网络就绪约需 20 秒,向浏览器提供实时 H.264,手动进行每通道 MPEG-TS 录制,带有逐帧步进和拖动进度条的屏幕回放,由鼠标/键盘/网络驱动的 OSD 菜单,带有旋律的蜂鸣器反馈。
**录制是通过命令手动进行的,这是刻意设计的**——仅有 `REC`/`STOP`。没有自动录制,也没有循环覆盖保留机制。编写此固件的意义就在于*不*去重新构建一台 CCTV DVR。
已知待解决的事项已如实记录在 [`docs/REVIEW.md`](docs/REVIEW.md) 第 3 节中,包括 U-Boot TFTP 恢复路径*可用但未经排练*(它需要在 PC 上允许入站 UDP 69 端口)。
## 法律与范围
- **此处不分发任何厂商固件。** 本项目构建所基于的 dump 文件——U-Boot、内核、rootfs、`app.out`、海思 SDK、ActiveX DLL、数据手册——均为制造商的版权财产,**不**包含在本仓库中。请 dump 你自己的设备(`tools/dvr.py backup`)。*发布*的内容仅为我们自己的代码以及对硬件工作原理的描述。
- 声明一个例外情况:`flash/rootfs_overlay/root/run_app.sh` 是设备的原厂启动脚本,其中插入了我们的交接钩子,将其完整保留是为了便于审计与原厂的差异。它大约有 40 行 `mount`/`tar`/`exec`,纯粹是为了实现互操作性。
- 文档中引用的原厂设备凭据(无密码的 `root`,`admin` / `000000`)是此设备系列的**出厂默认设置**,并非任何人的机密。
- 请仅在你自有的硬件上运行此项目。
- 与海思、Techwell 或该 DVR 的制造商没有任何隶属关系,也未得到其认可。
## 许可证
本仓库中的代码采用 MIT 许可证。参见 [`LICENSELICENSE)。这不会也不可能授予对其互操作的第三方固件的任何权利。
标签:FPV地面站, MITM代理, 云资产清单, 客户端加密, 嵌入式固件, 嵌入式开发, 数据可视化, 硬件黑客, 逆向工具, 逆向工程, 音视频处理