# 逆向工程过程精华 完整的探索过程见原项目笔记 `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 一致) |