4.2 KiB
逆向工程过程精华
完整的探索过程见原项目笔记 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 是:
- P[i] = P_initial[i] ^ key_word(i % keylen)
- 用此 P 和初始 S-box 加密 (0,0), 用结果替换 P[0..1]
- 继续替换 P[2..3] ... 直到 P[16..17]
- 同样替换 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
# 在 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 一致) |