大疆植保无人机T40_RD2484R雷达逆向

偶尔在网上看到有人买大疆T40的雷达做桌面摆件,看起来还挺哏。搜了下发现多数人都只是硬改一路供电让电机转起来,起了让他正常工作的念头,于是前后花费了一个月(比预期长不少)的时间研究了下如何让他正常运作并且获得有意义的雷达数据。

官方的介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
有源相控阵全向雷达
型号: RD2484R
地形跟随: 山地模式最大坡度:30°
避障:
    可感知距离(水平):1.5-50m
    视角(FOV):水平360°,垂直±45°
    使用条件:飞行器飞行相对高度高于1.5m且速度不超过10m/s
    安全距离:2.5m(飞行器刹车并稳定悬停后桨尖与障碍物距离)
    避障方向:水平方向全向避障;
    可感知距离(上方):1.5-30m
    视角(FOV):45°
    使用条件:起飞、降落及爬升过程中且飞行器与上方物体相对距离大于1.5m
    安全距离:2.5m(飞行器刹车并稳定悬停后飞机顶部与障碍物距离)
    避障方向:飞行器上方

TL;DR

仓库地址 : leommxj/dji-agras-radar

雷达主结构分为上下两部分:下部(ESC)主要由一个GD32F305 MCU控制imu与电机转动;上部(Digital)由ZYNQ XC7Z020控制天线,与下部无物理连接,通过线圈无线取电,通过Wi-Fi通信。
雷达通过一个区分正反面的Type-C接口暴露了AR1021x usb DP/DNCAN(DUML协议)RS485 给对端设备。

Type-C Pinout:

供电 根据一个标题为T40&T20P维修培训PPT中的T40原理框图得知应为15V。

其中我们(作为航电板)可以使用AR1021x可以使用对应驱动启动名为radar_wifi的AP(2412MHz/5240MHz),无密码,上部会作为sta连接。通信所用ip地址固定,分别为192.168.8.1192.168.8.2。zynq在107端口开放dropbear,可以通过root/root账密登录 ssh -p 107 -oHostKeyAlgorithms=+ssh-dss root@192.168.8.2。雷达业务通过40204/udp50007/udp上的两种私有协议通信。

CAN线使用波特率为1M,其上运行DUML(DJI Universal Markup Language)协议,雷达自身id为0x18。 会不断上报当前转速、温度等信息,也可以通过xxx设置雷达转速。

RS485不清楚是我买到的损坏了还是我漏过了一些关键信息,没有找到使用的地方,连接也无数据。

除此之外下部板子上有一个空置的24Pin BTB板对板连接器,其上有两路UART,分别为DUML与正常文本shell。包含不同的调试日志与调试命令。

硬件分析

B站上有人进行过比较完整的拆解->传送门 。 我偷懒仅将能直接通过Type-C接口连接的下半部分(ESC)进行了拆解分析,上半部分(Digital)都依照视频中的图片分析。 上下部分没有物理连线,仅有无限线圈供电和Wi-FI连接。

  • ESC(下半部分)

    • AR1021x USB Wi-Fi 芯片 通过ipex连接到旋转部分两侧中间一点点作为天线

      • Type-C B7 D-
      • Type-C B6 D+
    • GD32F305RCT7 MCU,作为控制器

    • MP6536 电机驱动

    • TP75176 RS485收发器

      • Type-C A3 A

      • Type-C A2 B

    • MP66 MPU-6600

    • MAX3051EKA CAN收发器

      • Type-C B2 CAN-H

      • Type-C B3 CAN-L

    • VS3510AE MOS管

    • SMAJ18A 这是个ESD保护, 并联在正负极

  • 主板

    • ZYNQ XC7Z020
    • AFE5401 射频前端
    • W25Q512JV
    • DDR
  • 顶面天线板 4x4

    • SG24TR12 24Ghz 1T2R雷达
    • LMX2491 6.4GHz 时钟发生器
  • 侧面天线板 12x6

    • A1024
    • TW7112
  • 上层通讯小板子 (所以这个板子没有主控但有个wifi)

    • AR1021X wifi芯片
    • EEPROM
    • TPS54335ADRCR DCDC芯片

板层比较多,花了挺多时间测试各个接口/testpad/引脚的含义,最主要的还是Type-C接口。

值得注意的是过程中尝试通过直接连接来测试某些引脚对是否是USB设备,结果发现在不扣金属盖子的情况下,USB异常不稳定,导致浪费很多时间,错过多次摆在眼前的USB引脚。

对于ESC部分,我测试并标注了部分testpad与pin的含义:
B_Board_Top

B_Board_Bottom

根据网上找到的一份维修手册,我们还可以大致了解到:

  • 雷达连线到分线板,分线板再给航电板等再次分线。

  • 航电板具有一个GL850G芯片,这是个usb hub芯片。

陶瓷带通滤波器

在调试ar1021x的驱动(下文会提)过程中,偶然发现使用万用表测量发现ESC的Wi-Fi天线取下后IPEX座子内部引脚与GND短路。 作为一个完全业余的射频选手,我虽然知道有些天线(比如倒F天线)是接地的,但仍觉得这不太合理。开始怀疑是不是图中这个元件出现故障导致的。吹下来测试确实此元件的in/out与GND(两侧)都导通。

BFCN1

问了opus/gpt都不能愉快的回答出这个元件的名称,给出了RF开关/集成阻抗匹配/滤波器的推测。请教了一个相关专业的朋友,认为大概率是陶瓷滤波器,同时也告诉我这种陶瓷滤波器与地短路大概率是正常的。
opus得知我确认是陶瓷滤波器,而且拆下独立测量仍然与地短路后很坚定的给这个滤波器判了死刑,认为已经击穿了。在opus的坚定下,人类也被劝服了: “我觉得他说的有道理,拆下来导通确实可能是击穿了” 。 于是我将大疆贴心留下的bypass焊盘焊了两个0r电阻并期待解决了我的AP启动问题,结局是令人悲伤的。感觉目前大模型对射频/硬件相关知识还是没有对网安/编程那么精通
BFCN2

后续出于好奇心我花了25元买了个估计与当前滤波器比较类似的Mini-Circuits BFCN-5200+ 陶瓷带通滤波器,以及测试板。测试发现其RF in与GND短路, RF out 不短路。与这个两边都导通的情况倒也不太相同(DC通过性?)

由于没有能测到5G的VNA,简单用一个LMX2820的信号源和TinySA Ultra对这个滤波器进行了粗浅的测试:

  • 50M 6.6dBm -> -60dBm = 66.6dB 插入损耗
  • 1G 5.2dBm -> -36.8dBm = 42dB 插入损耗
  • 2.4G 4.7dBm -> -25.3dBm = 30dB 插入损耗
  • 5G 1.6dBm -> -3.4dBm = 5dB 插入损耗
  • 5240M(48 channel) 2.8dBm -> 0.8dBm = 2dB 插入损耗
  • 6G 0.2dBm -> -44dBm = 44.2dB 插入损耗

可以看出应该是一个5G频段的带通滤波器,很符合固件中发现其使用5240MHz通信的特点。 并且工作正常,没有损坏。

插入前/插入后的5150MHz~5450MHz对比:

tinysa_nobfcn

tinysa_bfcn

不得不说还是有点讲究的,我还买了个ar1021x的小模块,他板子上面就没有什么滤波器。

题外话: TinySA维修

在这次使用TinySA前,我测试LNB时不小心将带有13V/18V DC输入的线接到TinySA RF口上,导致TinySA损坏,表现为Level明显低于实际数值,Self Test发现Test 3/4/7/11显示 Signal Level Critical

根据wiki中 Fault Finding 手工检查可以发现符合开启30Mhz output后level明显低于-35dBm的情况 (-52dBm)。即需要更换输入开关U22。

我这个的U22丝印为S79XA17-G4K ,淘宝上两块钱买了四个。某天深夜想起来进行更换,换完之后发现测试Level更低了,-70多了,还以为是买到废品了,再拆开一看发现装反了,又换了一个,装对方向,一切正常。

在修复前其实TinySA除了Level不对之外基本做个定性分析还是可以使用的。我也在修复前对这个滤波器进行过测试,整体的频率/插入损耗变化趋势是差不多的,但在同样5240MHz时测出的插入损耗有8dB。工欲善其事必先利其器

固件逆向

感谢这位Chris Hawkins 建立 https://www.dankdronedownloader.com/ 网站让我们可以下到各种dji固件。

大疆T40/Agras T40 型号为ag601。 本次分析的固件为目前最新(好多年没更新了)的01.03.1005

固件包名称为V01.03.1005_ag601_dji_system.bin 实际上外层是一个tar包,解压开可以看到其中有一个ag601.cfg.sig,以及34个.pro.fw.sig 后缀的文件。010打开可以看到都有明文头,部分文件(比如ag601.cfg)内容也是明文,另外有部分熵值较高。

搜索发现符合 dji-firmware-tools项目中的dji_imah_fwsig.py工具解压的格式。

1
dji-firmware-tools/dji_imah_fwsig.py -vv -k PRAK-2020-01 -k UFIE-2020-04 -u -i $INPUTFILE -m $OUTPUTFILE

提取完后在ag601.cfg这个xml文件中可以找到固件中其他文件的信息,每个文件作为一个<module>标签。 module的name字段对我们比较有用处,可以直接体现出对应的部件作用。

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<dji>
<device id="ag601">
<firmware formal="01.03.1005">
<release version="01.03.1005" antirollback="1" antirollback_ext="cn:1" enforce="0" enforce_ext="cn:0" enforce_time="2024-04-28T03:18:40+00:00" from="2024/04/28" expire="2025/04/28">
...
<module id="0500" version="01.04.80.48" type="sp04" group="ac" order="4" wait="0" size="247296" name="Spray_Controler" upgrade_order="7" is_upgrade_center="false" op_lib_name="libstandard_md_up.so" com_method="V1" com_prama1="0x0500" com_prama2="null" sec_type="normal" only_check_ver="false" loader_needed="true" post_reset="true" post_check_ver="true" reboot_after_fail="true" reboot_notify="false" resp_get_ver="true" support_multi_hw="true" allow_skip="false" delay_after_1_pkg_us="0" delay_after_a_cmd_s="0" fail_repeat="2" sound_file="null" get_version_to="null" get_version_dt="null" request_upgrade_to="null" request_upgrade_dt="null" check_status_to="null" check_status_dt="null" request_accept_data_to="null" request_accept_data_dt="null" transfer_data_to="null" transfer_data_dt="null" transfer_complete_to="null" transfer_complete_dt="null" reboot_to="null" reboot_dt="null" wait_status_report_time="null" wait_status_report_time_total="null" md5_unsign="231e4df573d52067ae5aa29447805fb6" md5="0635883fa2e9abb4bd903792c9c48380">ag601_0500_v01.04.80.48_20230607_sp04.pro.fw.sig</module>
...

分析后可以发现主要与雷达有关应该是RAD_Digital(雷达上半部分zynq)、RAD_ESC(雷达下半部分GD32)、E1E(航电板,与zynq PS核通信)

RAD_Digital

binwalk能解出一些文件

1
0.zip  is_enc  mac.info  normal.bin  recover_fpga.img  recover_image.ub  verify.info

其中normal.bin高熵加密, recover_fpga.img 和 recover_image.ub非常符合zynq系列PS(Arm)+PL(fpga)的体系。 recover_image.ub中可以进一步提取出PS上跑的linux的kernel与文件系统。

分析启动流程可以发现比较关键的网络连接部分

/etc/init.d/load.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/etc/init.d/bootlogd stop &
/usr/bin/ar1021x_cfg.sh &
# gpio获取上/下雷达
ip_addr=192.168.8.2
gateway=192.168.8.1
inetd # 这里面开了个ftp

# 一些mount
# 一些upgrade逻辑,里面还有加密的事看起来

/home/root/radar_loader.sh 或 /home/root/autorun_script.sh 启动
# 看起来有几种位置
/home/root/app.elf

/usr/sbin/start_sshd.sh & # ssh有个预置的公钥
# 实际上启动的dropbear 监听 107 端口, 似乎没有限制用户登录
# root:k1I8TezZrkRB2:0:0::/home/root:/bin/sh

/bin/panic_store.sh &

以及无线网络的配置信息/etc/wpa_supplicant.conf。注意虽然配置了个psk, key_mgmt是NONE,同时特别指定了2412与5240两个scan_freq。

1
2
3
4
5
6
7
8
9
10
11
ctrl_interface=/var/run/wpa_supplicant


network={
ssid="radar_wifi"
scan_ssid=1
scan_freq=2412 5240
key_mgmt=NONE
psk="very secret passphrase"
priority=5
}

同时也可以发现雷达业务主角 -> /home/root/app.elf 。逆向分析可以发现其尝试使用40204与50007两个udp端口,与192.168.8.1进行通信。

后续大量时间花在分析app.elf到底搞的什么数据、怎么处理的数据、发的什么数据上。

E1E

推测这个是航电板的原因其实是他最大。

system.new.dat/system.patch.dat/system.transfer.list 和另外的vendor看很可能是安卓,使用xpirt/sdat2img 将这种transfer list形式的转成img后可以正常解开。

从提取出的文件系统可以发现一些关键信息:

bin/start_dji_system.sh中调用hostapd.sh启动了一个自身ip为192.168.8.1的无线网络,同时明确注释了是radar

1
2
# start hostapd daemon for ar1021x of radar
hostapd.sh &
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
#dmesg |grep ath6kl &
insmod /system/lib/modules/ath6kl_usb.ko debug_quirks=0x8200 reg_domain=0x8349 recovery_enable_mode=0x2

i=0
while [ $i -le 5 ]; do
if ifconfig wlan0
then
# modify mode to 5G ht20
ht_cap_params_path=`ls /sys/kernel/debug/ieee80211/phy*/ath6kl/ht_cap_params`
echo "1 0 0 0" > $ht_cap_params_path
echo "insmod ath6kl success!"
#logcat |grep hostapd &
hostapd -dd -B /etc/hostapd.conf
ifconfig wlan0 192.168.8.1 up
ip rule add from all lookup main pref 9999
iw wlan0 scan
echo "wlan0 scan complete !"
ps |grep hostapd
exit $?
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ctrl_interface=/tmp/run/hostapd
interface=wlan0
driver=nl80211
country_code=US
ssid=radar_wifi
channel=48
ap_max_inactivity=5
#ht_capab=[HT40-][SHORT-GI-20][SHORT-GI-40][40-INTOLERANT]
#rsn_preauth=1
#time_advertisement=2
#time_zone=CST8
macaddr_acl=0
auth_algs=1
ieee80211n=1
wmm_enabled=0
hw_mode=a
ignore_broadcast_ssid=2
#wpa=2
#wpa_passphrase=12345678
#wpa_key_mgmt=WPA-PSK
#wpa_pairwise=TKIP
#rsn_pairwise=CCMP

当然这里有个比较诡异的点是为什么设置了ignore_broadcast_ssid=2?

同时进一步分析也可以找到些app.elf对端的程序,比如radar_agentdji_perception等。

/etc/dji.json中还可以看到通信地址的信息(还是定义?)

1
2
3
4
"183": {"status": 1, "target": "rcpmu",  "index": 3, "channel": "ip",  "distance": 0, "protocol": "v1",
"ip": {"interface": "wlan0", "local_port": 50007, "remote_address": "192.168.8.2", "remote_port": 50007, "server": true, "protocol": "udp"}},
"183": {"status": 1, "target_machine":200, "target": "rcpmu", "index": 3, "channel": "ip", "distance": 0, "protocol": "v2",
"ip": {"interface": "wlan0", "local_port": 40204, "remote_address": "192.168.8.2", "remote_port": 40204, "server": true, "protocol": "udp"}},

除了system/vendor之外的normal.img/rtos.img从文件头可以看出又是之前的fwsig格式,不过按之前的提取参数无法成功提取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
./V01.03.1005_ag601_dji_system-out/E1E_0802_v10.00.09.71_0802/normal.img: Unpacking image...
OrderedDict([('magic', b'IM*H'),
('header_version', 2),
('size', 7608352),
('reserved', b'\x00\x00J\x00'),
('header_size', 352),
('signature_size', 256),
('payload_size', 7607744),
('target_size', 7608352),
('os', 0),
('arch', 0),
('compression', 0),
('anti_version', 0),
('auth_alg', 262146),
('auth_key', 'PRAK'),
('enc_key', 'TBIE'),
('scram_key', b'\x93]\xe8\xb2\xf0\x03\x95\x94?\x8735\x19\xb4/\xba'),
('name', 'normal'),
('type', 'TKRN'),
('version', 0),
('date', 539231272),
('encr_cksum', 1352457852),
('reserved2', b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'),
('userdata', b''),
('entry', b'\x00\x00`\x7f\x00\x00\x00\x00'),
('plain_cksum', 2281791023),
('chunk_num', 5),
('payload_digest', b'b\\\x89p\xcd\xa85\x88#\xb2-\xdd\xb6vw\x11\xd6hM\x81\xb8\xda\xe8!\xf4\xe6\n\xbf\x1b\x0e\x88G')])
./V01.03.1005_ag601_dji_system-out/E1E_0802_v10.00.09.71_0802/normal.img: Image file head signature verification passed.
./V01.03.1005_ag601_dji_system-out/E1E_0802_v10.00.09.71_0802/normal.img: Encrypted data checksum 0x509CDE7C matches.
./V01.03.1005_ag601_dji_system-out/E1E_0802_v10.00.09.71_0802/normal.img: Warning: 'TBIE' matches multiple keys; using first, 'TBIE-
2021-08'
./V01.03.1005_ag601_dji_system-out/E1E_0802_v10.00.09.71_0802/normal.img: Key choices: TBIE-2021-08, TBIE-2021-06, TBIE-2020-04, TBIE-
2020-02, TBIE-2019-11, TBIE-2018-07

查看日志可以发现其使用TBIE作为enc_key,同时又有多个TBIE可供选择,默认第一个失败得到了错误的数据,手工指定参数可以解出 -k PRAK-2020-01 -k TBIE-2020-02

normal.bin中的dts可以看到些有趣的外设定义,其余内容我没有进一步分析。

RAD_ESC

这个是GD32的固件,010打眼一看应该没有复杂的格式,直接加载,从字符串分析发现使用了uC/OS-III 实时操作系统。
找了个GD32移植的好的代码,here 根据正常编译出的axf文件的reset handler结构和vector table中地址推导出正确的加载地址为0x08010000

随后稍微修复并提供一些辅助信息(系统源码、GD32的库等),让claude开工。

claude成果喜人,成功找到了对外连接的链路。

CAN0后面的没有经过验证。

  • USART0 PB6/PB7 (修改过,默认是PA9/PA10)

  • UART3 PC10/PC11

  • SPI0 PA15(NSS) PB3(SCK) PB4(MISO) PB5(MOSI) MPU6500

  • CAN0 PA11/PA12

  • I2C ? PB8/PB9 CL/DA

  • PA8, PA9 PWM输出 (电机/温控相关)

  • PB13, PB14 PWM输出 (无线充电 P9242R 相关)

  • PC7 EXTI脉冲输入 (time_sync 时间同步)

同时也恢复了DUML消息的格式与指令信息(多数未经验证)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
命令处理表(28条,全局注册)

cmd_set=0x00 通用

┌────────┬─────────────────────────────┬─────────────────────┐
│ cmd_id │ Handler │ 功能 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x00 │ cmd_handler_echo │ Echo 回显 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x01 │ cmd_handler_get_device_info │ 获取设备信息 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x07 │ cmd_handler_upgrade │ 固件升级 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x0B │ cmd_handler_reboot │ 重启 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x0C │ cmd_handler_heartbeat │ 心跳 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x32 │ cmd_handler_activation │ 激活 │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x50 │ cmd_handler_push_stub │ Push 桩(未实现?) │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0x51 │ cmd_handler_product_sn_get │ 获取产品 SN │
├────────┼─────────────────────────────┼─────────────────────┤
│ 0xFF │ cmd_handler_get_cfg_info │ 获取配置信息 │
└────────┴─────────────────────────────┴─────────────────────┘

cmd_set=0x03 产品信息

┌────────┬─────────────────────────────────┬───────────────┐
│ cmd_id │ Handler │ 功能 │
├────────┼─────────────────────────────────┼───────────────┤
│ 0x73 │ cmd_handler_product_sn_set_stub │ 写入 SN(桩) │
├────────┼─────────────────────────────────┼───────────────┤
│ 0x74 │ cmd_handler_product_sn_read │ 读取 SN │
└────────┴─────────────────────────────────┴───────────────┘

cmd_set=0x10 RDTT

┌────────┬─────────────────────────────────┬───────────────┐
│ cmd_id │ Handler │ 功能 │
├────────┼─────────────────────────────────┼───────────────┤
│ 0x80 │ cmd_handler_rdtt_link_negotiate │ RDTT 链路协商 │
├────────┼─────────────────────────────────┼───────────────┤
│ 0x82 │ cmd_handler_rdtt_send_test │ RDTT 发送测试 │
└────────┴─────────────────────────────────┴───────────────┘

cmd_set=0x1E ESC/电机/雷达

┌────────┬────────────────────────────────┬──────────────────────┐
│ cmd_id │ Handler │ 功能 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x01 │ cmd_handler_motor_pwm_control │ 电机 PWM 控制 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x07 │ cmd_motor_pid_handler │ 电机 PID 参数 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x09 │ cmd_handler_motor_enable │ 电机使能 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x0A │ cmd_handler_motor_disable │ 电机禁用 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x0D │ cmd_handler_angle_sync_ack │ 角度同步应答 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x21 │ cmd_flyinfo_handler │ 飞行信息处理 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x80 │ cmd_handler_motor_speed_set │ 电机转速设定 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x81 │ cmd_motor_position_handler │ 电机位置控制 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x82 │ cmd_handler_motor_stop │ 电机停止 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0x83 │ cmd_hms_switch_from_rd │ HMS 开关(来自雷达) │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0xE5 │ cmd_handler_esc_e5_stub │ ESC E5(桩) │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0xE6 │ cmd_handler_ar1021x_flash_read │ AR1021x Flash 读取 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0xEA │ cmd_handler_esc_ea_stub │ ESC EA(桩) │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0xEB │ cmd_handler_esc_config_set │ ESC 配置写入 │
├────────┼────────────────────────────────┼──────────────────────┤
│ 0xFE │ cmd_handler_rdtt_status_req │ RDTT 状态查询 │
└────────┴────────────────────────────────┴──────────────────────┘

Get it Running

根据前面的Type-C引脚定义,给VCC供15V电后雷达启动,led亮灯,CAN线出数据,USB设备出现,缓慢旋转。

AR1021x驱动 与 radar_wifi

通过Type-C连接AR1021x到电脑上,供电后可以成功枚举新USB设备

1
Bus 002 Device 002: ID 0cf3:1022 Qualcomm Atheros Communications USBWLAN

网上关于此网卡的资料多用于无线图传,驱动非常老。 而我使用的系统为Ubuntu 24.04,kernel版本为Linux u24 6.17.0-20-generic #20~24.04.1-Ubuntu。 这系列网卡使用的ath6kl->AR6004驱动其实在目前主线中默认存在,不过驱动注册的设备ath6kl_usb_ids(drivers/net/wireless/ath/ath6kl/usb.c)中没有这个ar1021x的vid:pid。

同时由于这系列的WiFi芯片是FullMAC的,还需要加载对应的固件。 正常Ubuntu在对应固件目录(/lib/firmware/ath6k/AR6004/)下带有hw1.2 hw1.3的固件,但是此芯片需要的是hw3.0。

我最初尝试将航电板上的/lib/firmware/ath6k/AR6004/hw3.0中固件与bdata等传到自己机器上,并使用new_id功能直接让设备使用ath6kl驱动。

1
2
modprobe ath6kl_usb
echo "0cf3 1022" > /sys/bus/usb/drivers/ath6kl_usb/new_id

配置完成后再使用航电板上的hostapd配置文件启动热点。但没有人连接,而且靠近也无法搜到热点。 dmesg输出

1
2
3
[   52.837810] ath6kl: ar6004 hw 3.0 usb fw 3.5.0.601 api 1
[ 52.837815] ath6kl: firmware supports: 64bit-rates,ap-inactivity-mins
[ 52.841861] ath6kl: Firmware lacks RSN-CAP-OVERRIDE, so HT (802.11n) is disabled.

后续走了很多弯路,尝试用codex+gpt5.4通过对航电板驱动逆向+分析目前驱动代码的方式来匹配差异行为,但浪费了很多token也没有成功。

最终发现第一阻碍是这个firmware与现版本可能差异较大,从qca/ath6kl-firmware获取了另一个固件,发现部分功能正常。 但由于ignore_broadcast_ssid配置,直接用原配置不广播ssid(不清楚正常是怎么用的,bdata.bin还可以内置bssid吗?),导致zynq无法连接。

修改后遇到第二个阻碍,AP模式下启动后,STA(客户端)能搜到并且连接上,但AP侧日志毫无反应,也无法正常上网。 此时通过其他网卡靠近雷达开启热点的方式,已经让zynq成功连接到我的热点,虽然信号较差。我购买了一个同型号芯片(ar1021x)的小模块,发现现象一致,应该与魔改无关。

最终找了一个看起来靠谱的老版本驱动 sprink951/AR1021x,让Claude对着他分析差异,发现原因竟然是其中一个结构体wmi_connect_event中的前两个字段顺序反了。修改后可以正常使用。 这个顺序不对还是2015年这个commit 特意调换的,非常令人不解。

后面使用中又发现驱动会有偶尔/热插拔后hang住整个系统的情况。也指挥Claude进行了修改。详细内容可以看github。

成功连接后使用ssh -p 107 -oHostKeyAlgorithms=+ssh-dss root@192.168.8.2与root/root就可以登录zynq的PS核,其中可以看到开启了ftp/ssh,以及app.elf

1
2
3
4
5
6
tcp        0      0 0.0.0.0:107             0.0.0.0:*               LISTEN      
tcp 0 0 0.0.0.0:21 0.0.0.0:* LISTEN
tcp 0 336 192.168.8.2:107 192.168.8.1:56490 ESTABLISHED
tcp 0 0 :::107 :::* LISTEN
udp 0 704 192.168.8.2:40204 0.0.0.0:*
udp 0 0 192.168.8.2:50007 0.0.0.0:*

除此之外在ps中可以看到一些保存日志的进程在实时保存app.elf/kmsg等日志。

DUML over CAN

一开始使用h7tool连接CAN线,发现使用的1M波特率,标准帧,但是数据明显是连续拼接的,并且有比较明显的magic/length包头结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
97        , Rx, 05:13:48.011.397, STD,DATA, 0xA3,       8  , 55 39 04 25 18 78 10 00   
98 , Rx, 05:13:48.011.523, STD,DATA, 0xA3, 8 , 00 1E 0F 00 00 00 00 00
99 , Rx, 05:13:48.011.649, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
100 , Rx, 05:13:48.011.775, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
101 , Rx, 05:13:48.011.895, STD,DATA, 0xA3, 8 , CC 00 65 2C 18 00 00 00
102 , Rx, 05:13:48.012.020, STD,DATA, 0xA3, 8 , 00 00 07 00 00 00 00 00
103 , Rx, 05:13:48.012.155, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 C5
104 , Rx, 05:13:48.012.200, STD,DATA, 0xA3, 1 , 18
105 , Rx, 05:13:48.012.318, STD,DATA, 0xA3, 8 , 55 27 04 15 18 78 11 00
106 , Rx, 05:13:48.012.440, STD,DATA, 0xA3, 8 , 00 1E 0B 01 00 00 00 00
107 , Rx, 05:13:48.012.566, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
108 , Rx, 05:13:48.012.681, STD,DATA, 0xA3, 8 , 00 77 11 43 3E 94 51 58
109 , Rx, 05:13:48.012.791, STD,DATA, 0xA3, 7 , 3F 97 F3 04 BE 4A 04
110 , Rx, 05:13:48.012.907, STD,DATA, 0xA3, 8 , 55 39 04 25 18 78 12 00
111 , Rx, 05:13:48.013.032, STD,DATA, 0xA3, 8 , 00 1E 0F 00 00 00 00 00
112 , Rx, 05:13:48.013.158, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
113 , Rx, 05:13:48.013.285, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
114 , Rx, 05:13:48.013.405, STD,DATA, 0xA3, 8 , CC 00 65 2C 18 00 00 00
115 , Rx, 05:13:48.013.529, STD,DATA, 0xA3, 8 , 00 00 08 00 00 00 00 00
116 , Rx, 05:13:48.013.653, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 DF
117 , Rx, 05:13:48.013.711, STD,DATA, 0xA3, 1 , 9C
118 , Rx, 05:13:48.013.828, STD,DATA, 0xA3, 8 , 55 27 04 15 18 78 13 00
119 , Rx, 05:13:48.013.950, STD,DATA, 0xA3, 8 , 00 1E 0B 01 00 00 00 00
120 , Rx, 05:13:48.014.076, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
121 , Rx, 05:13:48.014.193, STD,DATA, 0xA3, 8 , 00 71 28 44 3E 3A 25 50
122 , Rx, 05:13:48.014.298, STD,DATA, 0xA3, 7 , 3F 0C F4 D9 BD 85 52
123 , Rx, 05:13:48.014.414, STD,DATA, 0xA3, 8 , 55 39 04 25 18 78 14 00
124 , Rx, 05:13:48.014.540, STD,DATA, 0xA3, 8 , 00 1E 0F 00 00 00 00 00
125 , Rx, 05:13:48.014.666, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
126 , Rx, 05:13:48.014.792, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 00
127 , Rx, 05:13:48.014.912, STD,DATA, 0xA3, 8 , CC 00 65 2C 18 00 00 00
128 , Rx, 05:13:48.015.037, STD,DATA, 0xA3, 8 , 00 00 09 00 00 00 00 00
129 , Rx, 05:13:48.015.164, STD,DATA, 0xA3, 8 , 00 00 00 00 00 00 00 30
130 , Rx, 05:13:48.015.220, STD,DATA, 0xA3, 1 , 42

在网上搜了一通,一开始以为是 RoboMasterS1を解析してROSで動かせるように頑張ってみる(プロトコル解析2) #can - Qiita 这种,但不完全符合。 后逆向分析官方的 dji-sdk/Onboard-SDK 中libdji-linker.a -> osdk_protocol_v1.c.o OsdkProtocol_v1Pack 函数发现了更符合的结构。

后来发现这其实就是大名鼎鼎的DUML协议,在Drone Security and the Mysterious Case of DJI’s DroneID 就有记录。 同时dji-firmware-tools里面也有成熟的wireshark解析lua ,但我们的Sender 0x18和cmdset 0x1E 等具体信息其中没有记录。

通过这个CAN可以对电机进行控制,也可以获得电机状态等。 由于h7tool对这种使用标准帧传长数据的情况不太友好,加上考虑到后续可能需要转发集成到别的脚本里,掏出一个Kvaser Leaf Light使用Linux的Socket CAN驱动进行开发。

详见仓库 duml_can_tool.py

其中motor-stop等几个指令其实并不是直接控制电机转速的,motor-speed反应直观,按GD32固件代码看上限为1800rpm。 设置为0停转,默认上电为60rpm。(不装外壳要小心高速旋转,笔者第一次尝试1800rpm时雷达飞出去差点把猫揍了)

24Pin BTB

逆向ESC固件可以看到有一路uart,明显有调试指令,并且是ascii的,很令我感兴趣,最初以为是rs485传递,但怎么也测不出来,485上也没有数据。

古法测引脚发现实际上在一个空置的板对板连接器中,明显是用来调试的。可以通过一个转接板把引脚转出来。(我买这个连接线太不稳了,很难受)

BTB

uart4 921600 8N1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<0.000>{null}INFO:----Init "gd32_uart_init" ret 0 time 0us
<0.001>{null}INFO:----Init "gd32_can_init" ret 0 time 0us
<0.001>{null}INFO:----Init "init_dummy" ret 4 time 0us
<0.001>{null}INFO:----Init "gd32_sys_ctrl_init" ret 0 time 0us
<0.001>{null}INFO:----Init "init_dummy" ret 5 time 0us
<0.001>{null}INFO:----Init "init_dummy" ret 6 time 0us
<0.001>{null}INFO:----Init "init_dummy" ret 7 time 0us
<0.001>{null}INFO:----Init "init_dummy" ret 8 time 0us
<0.002>{null}INFO:----Init "init_dummy" ret 9 time 0us
<0.002>{null}INFO:----Init "init_dummy" ret 10 time 0us
<0.002>{null}do probe(drv_boot)800a620,size = 1,step= 8
<0.002>{null}INFO:----Init "drv_boot_init" ret 0 time 0us
<0.002>{null}INFO:----Init "init_loader_config" ret 0 time 0us
<0.002>{null}INFO:----Init "drv_led_init" ret 0 time 0us
<0.002>{null}INFO:----Init "init_dummy" ret 11 time 0us
<0.002>{null}INFO:----Init "init_dummy" ret 12 time 0us
<0.003>{null}INFO:LD:loader main start[id:0xf1]...!
<0.003>{null}LD:id:0xf1,base_addr:0x8010000,size:0x2b800!
<0.003>{null}upgrade_flag[0xffffffff]!=flag[0x99669966]
<0.003>{null}LD:key:[0x18][0x0][0x0]!
<0.003>{null}LD:fw_start_offset:0x8010000,jump_addr:0x8010000!
<0.003>{null}INFO:firmware_jump: 0x8010000
<0.023>{root}INFO:----Init "gd32_uart_init" ret 0 time 0us
<0.023>{root}INFO:----Init "gd32_exti_init" ret 0 time 0us
<0.023>{root}INFO:----Init "gd32_pwm_init" ret 0 time 0us
<0.023>{root}INFO:----Init "gd32_spi_init" ret 0 time 0us
<0.023>{root}INFO:----Init "gd32_i2c_init" ret 0 time 0us
<0.023>{root}INFO:----Init "gd32_can_init" ret 0 time 0us
<0.025>{root}timer 1 add success.
<0.025>{root}INFO:----Init "gd32_bsp_timer_init" ret 0 time 0us
<0.025>{root}INFO:----Init "init_dummy" ret 4 time 0us
<0.025>{root}INFO:Register motor device#0 ...
<0.025>{root}INFO:----Init "motor_device_init" ret 0 time 0us
<0.025>{root}INFO:----Init "gd32_sys_ctrl_init" ret 0 time 0us
<0.025>{root}INFO:----Init "init_dummy" ret 5 time 0us
<0.025>{root}INFO:----Init "init_dummy" ret 6 time 0us
<0.025>{root}INFO:----Init "init_dummy" ret 7 time 0us
<0.026>{root}INFO:----Init "init_dummy" ret 8 time 0us
<0.026>{root}INFO:----Init "init_dummy" ret 9 time 0us
<0.026>{root}INFO:----Init "sys_log_exinit" ret 0 time 0us
<0.026>{root}INFO:----Init "init_dummy" ret 10 time 0us
<0.026>{root}INFO:----Init "sensor_hub_init1" ret 0 time 0us
<0.026>{root}do probe(drv_boot)80317f8,size = 1,step= 8
<0.028>{root}INFO:----Init "drv_boot_init" ret 0 time 1666us
<0.028>{root}INFO:----Init "drv_led_init" ret 0 time 0us
<0.028>{root}gpio 40 not exist
<0.028>{root}gpio 34 not exist
<0.028>{root}INFO:----Init "version_init" ret 0 time 0us
<0.030>{root}INFO:----Init "init_dummy" ret 11 time 0us
<0.030>{root}mpu6500 id ok
<0.230>{root}INFO:----Init "mpu6500_driver_init" ret 0 time 199999us
0.230>{root}INFO:----Init "sys_board_log_init" ret 0 time 0us



#help
detect 24 cmd
gpio_list gpio_set gpio_get gpio_request gpio_release gpio_test
uart_list uart_status uart_debug uart_wr uart_rd help
errno time echo showenv resetenv saveenv
setenv runenv loopenv stopenv sleep motor

可以看到有系统启动日志和一些指令。

这个连接器上除了这路明文uart之外还有一路跑DUML的UART。作用是在输出另外一部分业务相关的日志。内容可以看出很多明文。

uart3 921600 8N1

1
2
3
4
5
6
7
8
9
10
11
[Rx] 55 53 04 98 00 0A FF FF 00 00 0E 25 3C 49 4E 46 3A 35 31 39 2E 32 38 39 53 3E 66 75 6E 3A 20 74 69 6D 65 5F 73 79 6E 63 5F 66 75 6E 63 2C 20 70 75 6C 73 65 5F 70 65 72 69 6F 64 3A 20 30 2C 20 70 61 63 6B 5F 70 65 72 69 6F 64 3A 20 30 0D 0A 00 1D 1A 
[Rx] 55 2F 04 63 00 0A FF FF 00 00 0E 16 3C 44 42 47 3A 35 31 39 2E 34 38 34 53 3E 74 65 6D 70 65 72 61 74 75 72 65 20 5B 34 38 5D 0D 0A 00 97 A4
[Rx] 55 2F 04 63 00 0A FF FF 00 00 0E 16 3C 44 42 47 3A 35 31 39 2E 36 38 34 53 3E 74 65 6D 70 65 72 61 74 75 72 65 20 5B 34 38 5D 0D 0A 00 1F 12
[Rx] 55 2F 04 63 00 0A FF FF 00 00 0E 16 3C 44 42 47 3A 35 31 39 2E 38 38 34 53 3E 74 65 6D 70 65 72 61 74 75 72 65 20 5B 34 38 5D 0D 0A 00 94 0B
[Rx] 55 4D 04 A8 00 0A FF FF 00 00 0E 24 63 68 65 63 6B 5F 73 79 6E 63 3A 20 6D 73 67 20 70 75 6C 73 65 5F 70 65 72 69 6F 64 5F 6D 73 20 3A 25 64 0D 20 64 69 73 63 61 72 64 28 32 34 39 29 20 62 79 20 66 6C 6F 77 20 63 74 72 6C 00 10 8A 55 2F 04 63 00 0A FF FF 00 00 0E 16 3C 44 42 47 3A 35 32 30 2E 30 38 34 53 3E 74 65 6D 70 65 72 61 74 75 72 65 20 5B 34 38 5D 0D 0A 00 F8 A0
[Rx] 55 31 04 53 00 0A FF FF 00 00 0E 24 3C 49 4E 46 3A 35 32 30 2E 30 38 39 53 3E 70 75 6C 73 65 5F 70 65 72 69 6F 64 5F 6D 73 20 3A 30 0D 0A 00 14 30
[Rx] 55 37 04 F9 00 0A FF FF 00 00 0E 28 3C 49 4E 46 3A 35 32 30 2E 32 37 34 53 3E 67 79 72 6F 3A 20 30 2E 31 39 36 20 30 2E 30 38 38 20 2D 30 2E 32 30 34 0D 0A 00 EF 3D 55 36 04 3D 00 0A FF FF 00 00 0E 28 3C 49 4E 46 3A 35 32 30 2E 32 37 34 53 3E 61 63 63 3A 20 30 2E 30 37 36 20 2D 30 2E 30 30 34 20 38 2E 30 37 36 0D 0A 00 3C 44 55 2F 04 63 00 0A FF FF 00 00 0E 28 3C 49 4E 46 3A 35 32 30 2E 32 37 34 53 3E 74 65 6D 70 5F 64 61 74 61 3A 20 34 33 2E 38 20 0D 0A 00 87 3D
[Rx] 55 2F 04 63 00 0A FF FF 00 00 0E 16 3C 44 42 47 3A 35 32 30 2E 32 38 34 53 3E 74 65 6D 70 65 72 61 74 75 72 65 20 5B 34 38 5D 0D 0A 00 70 16 55 37 04 F9 00 0A FF FF 00 00 0E 19 3C 49 4E 46 3A 35 32 30 2E 32 38 34 53 3E 48 61 6C 6C 41 3A 31 30 34 34 2C 20 48 61 6C 6C 42 3A 2D 34 39 30 2E 20 0D 0A 00 00 22 55 34 04 AC 00 0A FF FF 00 00 0E 19 3C 49 4E 46 3A 35 32 30 2E 32 38 34 53 3E
[Rx] 73 70 65 63 69 66 69 65 64 20 73 70 65 65 64 3A 20 36 30 2E 20 0D 0A 00 2C 4C 55 2D 04 F2 00 0A FF FF 00 00 0E 19 3C 49 4E 46 3A 35 32 30 2E 32 38 34 53 3E 63 75 72 20 73 70 65 65 64 3A 20 30 2E 20 0D 0A 00 7F 1A 55 37 04 F9 00 0A FF FF 00 00 0E 19 3C 49 4E 46 3A 35 32 30 2E 32 38 34 53 3E 63 75 72 72 5F 64 3A 20 34 33 2C 20 63 75 72 72 5F 71 3A 20 31 35 2E 20 0D 0A 00 63 F1 55 30 04 97 00 0A FF FF 00 00 0E 19 3C 49 4E 46 3A 35 32 30 2E 32 38 34 53 3E 63 61 6C 69 62 5F 73 74 61 74 75 73 3A 20 39 2E 20 0D 0A 00 25 42 55 2D 04 F2 00 0A FF FF 00 00 0E 19 3C 49 4E 46 3A 35 32 30 2E 32 38 34 53 3E 63 74 72 6C 5F 6D 6F 64 65 3A 20 31 2E 20 0D 0A 00 C8 A8
[Rx] 55 53 04 98 00 0A FF FF 00 00 0E 25 3C 49 4E 46 3A 35 32 30 2E 32 38 39 53 3E 66 75 6E 3A 20 74 69 6D 65 5F 73 79 6E 63 5F 66 75 6E 63 2C 20 70 75 6C 73 65 5F 70 65 72 69 6F 64 3A 20 30 2C 20 70 61 63 6B 5F 70 65 72 69 6F 64 3A 20 30 0D 0A 00 35 34
[Rx] 55 2F 04 63 00 0A FF FF 00 00 0E 16 3C 44 42 47 3A 35 32 30 2E 34 38 34 53 3E 74 65 6D 70 65 72 61 74 75 72 65 20 5B 34 38 5D 0D 0A 00 F9 C5

Act as a radar

探明了大多数接口后就该尝试分析业务了。

在指挥codex/claude等agent不断努力后编写了radar_min_client.py脚本,转发从app.elf发来的DUML到CAN线给ESC,同时ESC来的DUML也转给app.elf。 运行后可以观测到电机转速提升至900rpm,进入工作状态。数据不断更新。

AI对两个udp端口中的数据含义的逆向得出并迭代了多轮结论, 过程中编写了radar_mem_poke.py等脚本对内存进行动态探索。 但是最终结论我认为仍可能存在一定或较大偏差,仅供参考。 可以看仓库中radar_client下面的.md文档。最终效果:

可以看到UDP协议上传输的并不是点云或频谱数据,经过了几轮处理,是避障业务强相关的。为了获取些对我更直观的数据,选择自己实现一个从内存里扣相对初阶的数据直接喂给客户端绘图的工具 radar_viewer.py 。由于我在室内,其实结果很乱,但是我自己的移动确实能对应出点云合理的变动。同时也发现数据的刷新与电机转速强相关,即使尝试独立让zynq进入工作状态,没有实际的转动,数据也不会刷新,转的越快刷新频率越高。 效果:

Mumbling

过程中很多Hard/Dirty work都是Agent做的,如果没有目前如此强大的模型能力,在最初评估工作量时我都不会开启这个业余项目。 但使用中(个人体验上)也能体会到一些目前人类还有胜率的地方:

  • 闭源模型的降智、policy严格程度变化等行为,作为用户确实无能为力。

  • 长上下文丢失记忆,虽然现在有list/memory/subagent等工程化方法来缓解这个问题,但有些小细节不可避免地会丢失,如果后续分析有大量依赖不好确认是否重要的细节,就会导致反复试错、反复丢失细节。 不过人类的记忆力又好到哪里去呢

  • 对于射频、硬件等领域明显感觉到基础模型能力没有coding/安全领域的强力。人类在驱使AI做自己不熟悉的领域(比如这里的雷达)时,也会吃力,主要难以分辨模型是不是在胡说八道。 未来驱使AI习惯后哪一天会也丢失了对自己专业领域的鉴别能力呢。:<

如果挖洞、写程序都是通过harness、做pipeline、写prompt,我到底是臭搞技术的还是成了动嘴做管理的