Files
go1_pro_sdk/docs/REVERSE_ENGINEERING.md
2026-06-20 20:02:36 +08:00

104 lines
4.2 KiB
Markdown

# 逆向工程过程精华
完整的探索过程见原项目笔记 `free-dog-sdk/REVERSE_ENGINEERING_NOTES.md` (1700+ 行)。这里是事后整理的精华。
## 起点
Unitree Go1 **PRO** 版机器人, 跑 sportMode 系统:
- 树莓派 4 (192.168.123.161): MQTT broker + Legged_sport 控制循环 + appTransit
- 3 台 Jetson Nano (192.168.123.13/14/15): 相机/AI/SLAM
- MCU (192.168.123.10): 电机控制器 (UDP :8007)
PRO 跟 EDU 的关键区别 (Unitree 故意做的限制):
- MCU 用 Blowfish 加密 LowState 回包 (EDU 无加密)
- MCU 要求 LowCmd 也是 Blowfish 加密 (EDU 接受明文)
- Legged_sport 二进制 UPX 压缩 + 符号剥离, 拿不到密钥
## 走过的弯路 (记录, 避免再踩)
### 弯路 1: 误判加密算法是 TEA
看到 Feistel 结构 + 32 字节常数, 第一反应是 TEA. 花了几天用 Z3 求解 128-bit key, 全部超时.
**纠正**: TEA 的特征是 `lsl #4` + `lsr #5` 在同一函数. Blowfish 是 T-table 查表 + 加法 + 异或, 没有移位混合常数. 反汇编时区分这两点是关键.
### 弯路 2: 试图静态反推密钥
Blowfish 的 key schedule 是:
1. P[i] = P_initial[i] ^ key_word(i % keylen)
2. 用此 P 和初始 S-box 加密 (0,0), 用结果替换 P[0..1]
3. 继续替换 P[2..3] ... 直到 P[16..17]
4. 同样替换 S-box
**纠正**: 步骤 1 的中间值在步骤 2 中**立刻被覆盖**, 从最终 P-array 无法反推 key. 但**有 state 就够了**, 不需要原始 key. 运行时 state = 4168B = P[18] + S0..S3.
### 弯路 3: 误以为 LowCmd 不加密
heap 搜索没找到 `feefff00` 密文 → 错误结论 "LowCmd 是明文". 实际上加密发生在 sendto 缓冲区里, 加密后立刻被消费掉, 没留在 heap.
**纠正**: 用 `strace -e sendto` 抓到 Legged_sport 真实发送的字节, 解密后是 LowCmd 头, 确认必须加密.
### 弯路 4: 误以为 MCU 锁源端口
之前认为 MCU 锁 Legged_sport 的源端口 8008. 改 sin 测试用 :8008 还是不动.
**纠正**: MCU 实际是按"**最近发包者的 IP:port**"切换. 任意端口都行, 关键是要先停掉 Pi 上的抢占者.
### 弯路 5: 没杀干净 Legged_sport
`killall -9 Legged_sport` 杀完, 进程立刻又活. 看 ps 才发现父进程是 `keep_sport_alive.sh` (PID 970), 每 0.2 秒检查并重启 Legged_sport.
**纠正**: 必须**先杀看门狗**, 再杀 Legged_sport. 顺序错了等于没杀.
## 关键突破
### 突破 1: 内存提取 Blowfish state
```bash
# 在 Legged_sport 运行时 attach GDB (只读, 不设断点不写内存)
sudo bash extract_blowfish_key.sh # < 30 秒, 不影响运动控制
# 拉回 Mac, 在 heap dump 里搜索 4168B 候选, 用已知明密文对验证
python analyze_blowfish_dump.py blowfish_dump.tar.gz
# 命中: rw_7f86a1f000.bin offset 0x232190
```
### 突破 2: PRO 格式跟 EDU 不一样
用 tcpdump 抓 Legged_sport 真实发送的 616B 包, 解密后跟 free-dog-sdk `lowCmd.buildCmd()` 输出做字节级 diff:
| 字段 | EDU | PRO |
|------|-----|-----|
| 总长 | 614B | **616B** (多 2B 填充) |
| CRC 偏移 | 610..614 | **612..616** |
| CRC 算法 | `encryptCrc(genCrc(cmd[:608]))` XOR 0xedcab9de | **裸 `gen_crc(cmd[:612])`** |
| bandWidth 字节序 | LE | **BE** |
| SN/version | 填实际值 | 全 0 |
### 突破 3: 内存里的 wirelessRemote 数据
LowState 的 759..799 是 40 字节遥控器数据, 跟 EDU `unitree_legged_sdk/comm.h` 公开的 `xRockerBtnDataStruct` 完全一致. 直接 parse 就能拿到按键 + 摇杆 + L2 模拟值.
## 最终性能 (Mac 直连)
| 指标 | 实测 |
|------|------|
| 控制频率 | **480 Hz** (96% 目标) |
| 单步 p50 | 1.74 ms |
| Blowfish 加密 | 1.02 ms |
| Blowfish 解密 | 0.67 ms |
| sendto | 0.02 ms |
| Blowfish 解码率 | 100% |
## 工具集
| 工具 | 用途 |
|------|------|
| `tools/extract_blowfish_key.sh` | 狗端内存 dump |
| `tools/analyze_blowfish_dump.py` | Mac 端搜索 4168B state |
| `tools/calibrate_joints.py` | 手动转动法测真实关节限位 |
| `tools/capture_lowcmd.sh` | 抓 Legged_sport 真实发送的包 |
| `tools/diff_real_lowcmd.sh` | 抓包 + 字段级 diff |
| `tools/stop_sportmode.sh` | SSH 杀抢占源 |
| `tools/run_on_mac.sh` | Mac 端跑测试 (自动停抢占) |
| `tools/run_on_pi.sh` | Pi 端跑测试 (源 IP 跟 Legged_sport 一致) |