
동글·루트 불필요 방식의 무선 이어버드 블루투스 보안 평가 도구로, Airoha SDK 취약점 체인(CVE-2025-20700/20701/20702)의 영향을 받는 기기를 대상으로 합니다.
버전 1.0.0
Airoha SDK 취약점 체인(CVE-2025-20700 / CVE-2025-20701 /
CVE-2025-20702)의 영향을 받는 무선 이어버드를 위한 블루투스 보안 평가 도구입니다.
주변 기기를 스캔하고, 영향을 받는 것으로 알려진 Airoha 기반 칩셋을 핑거프린팅하며,
인증되지 않은 GATT 액세스와 RACE 프로토콜 도달 가능성을 탐지합니다. 이 모든 작업은
bleak을 통해 OS 블루투스 스택(BlueZ)으로만 수행됩니다. 외부 블루투스 동글이 필요
없고 루트 권한도 필요하지 않습니다. 결과는 기술적 세부 정보와 함께 평이한 언어로
보고되므로, 깊은 블루투스 지식 없이도 조치를 취할 수 있습니다.
이 도구는 사용자가 소유하거나 명시적 승인을 받은 기기를 평가하기 위한 것입니다.
GATT 및 RACE 프로빙은 능동적 작업으로, 대상 기기에 연결하고 명령을 전송합니다.
--gatt, --race, --firmware, --bd-address, --assess, --baseline,
--check-drift, --memory-read를 자신의 기기가 아니거나 테스트 허가를 받지 않은
기기에 대해 실행하지 마십시오. --scan은 수동적이며 공개적으로 브로드캐스트되는
광고만 수신하므로, 범위 내의 어떤 기기에 대해 실행해도 안전합니다.
--memory-read는 다른 능동적 프로브보다 한 단계 더 나아갑니다. 고정 주소에서
기기 실제 플래시 콘텐츠의 실제 읽기 전용 페이지(256바이트) 하나를 검색하여,
도달 가능성만 확인하는 --race 프로브가 응답을 받지 못한 경우 CVE-2025-20702를
확정적으로 확인합니다. 이는 읽기 전용이며(플래시 읽기는 이 도구가 절대 전송하지
않는 쓰기/삭제/FOTA 명령과 달리 마모나 벽돌화 위험이 없습니다), 옵트인 방식이며,
표준 소유권 확인 프롬프트 외에 무엇을 하는지 정확히 설명하는 별도의 확인 절차를
요구합니다.
주변의 임의 기기를 프로빙하는 것은 단순히 정책상의 문제가 아닙니다. 실제 부작용이
발생할 수 있습니다. --gatt는 발견하는 모든 특성(characteristic)에 대해 읽기 또는
알림 구독을 시도합니다. 일부 소비자 기기는 프로비저닝 스타일 서비스(예: Google
Fast Pair 서비스)를 노출하는데, 이 서비스는 이 도구가 명시적으로 요청한 것과
무관하게 대상 기기에서 실제 페어링 핸드셰이크를 시작하여 반응합니다. 암호화가
필요한 특성은 자신의 기기에서도 동일한 현상을 유발할 수 있습니다. BlueZ는 해당
인증 요청을 데스크톱에 등록된 에이전트(예: KDE의 페어링 프롬프트)로 조용히
라우팅할 수 있기 때문입니다. 따라서 모든 능동적 명령은 프로브가 진행되는 동안
그러한 요청을 자동으로 거부하는 자체 임시 BlueZ 에이전트도 등록하므로, 페어링
프롬프트가 전혀 나타나지 않습니다. 또한 모든 능동적 명령은 무선 작업을 수행하기
전에 대상 주소가 자신의 것인지 확인을 요청합니다. 스크립트 사용을 위해 이미
자신의 기기임을 확인했다면 --yes를 전달하여 프롬프트를 건너뛸 수 있습니다:
buds_audit.py --assess --target AA:BB:CC:DD:EE:FF --yes
--watch는 --scan과 마찬가지로 수동적입니다. 이미 브로드캐스트 중인 광고만
수신하고 어떤 기기에도 연결하지 않으므로 확인을 요청하지 않습니다.
python3 -m venv venv
venv/bin/pip install -r requirements.txt
Python 3.10+ (3.14로 개발됨)와 블루투스 어댑터가 켜져 있고 BlueZ가 실행 중인 Linux 시스템이 필요합니다.
Linux 전용이며, 모든 Linux 시스템에서 자동으로 동작하지는 않습니다:
bleak 자체에는 Windows 백엔드가 있지만,
이 도구는 bleak만 사용하지 않습니다. Bluetooth Classic 검색(core/scanner.py)과
본딩 상태 확인(core/gatt.py)은 둘 다 bluetoothctl(Windows에는 없는 BlueZ 전용
CLI 도구)을 직접 실행합니다. 해당 코드 경로는 단순히 "command not found" 오류로
실패할 것입니다.bluetoothctl이 있는 BlueZ가 필요합니다. 단순히 어떤 Linux
커널이든 되는 것은 아닙니다. 대부분의 데스크톱 배포판에는 포함되어 있지만,
bluez 패키지가 설치되지 않은 최소 이미지나 서버 이미지에는 기본적으로 없을 수
있습니다. BlueZ 5.86에서 루트 권한 없이 검증되었습니다. bleak가 BlueZ의 표준
D-Bus API를 대상으로 하므로 다른 버전도 동일하게 동작해야 하지만, 이는 독립적으로
재검증된 것은 아닙니다.usbipd-win을 통해 Windows에서 전달해야 하며, 이는 USB로 연결된 어댑터만
전달할 수 있습니다. 대부분의 노트북 내장 블루투스는 Wi-Fi 옆의 비 USB 버스
(SDIO/PCIe)로 연결되어 있어 usbipd-win이 일반적으로 전달할 수 없습니다.
따라서 이는 전적으로 특정 하드웨어에 따라 달라집니다.자신의 Linux 머신이 필요하지 않습니다. 블루투스 라디오에 실제로 접근할 수 있는 Linux만 있으면 됩니다. 이를 위한 두 가지 실용적인 방법이 있습니다:
어느 쪽이든 규칙은 동일합니다. 도구 자체는 변경되지 않으며, BlueZ가 실제로 도달할 수 있는 블루투스 어댑터가 있는 Linux만 있으면 됩니다.
많은 TWS 이어버드는 전원을 절약하기 위해 일정 시간 사용하지 않으면 광고를 중단하고(활성 연결도 끊고) 일부는 스스로 완전히 꺼집니다. 1분 전에 찾은 기기를 스캔으로 찾지 못하거나 프로브가 도중에 실패한다면, 이는 버그가 아니라 이어버드가 유휴 상태가 된 것입니다. 케이스에서 꺼내거나 페어링 버튼을 다시 눌러 재시도하세요.
이는 주소 안정성에도 영향을 줍니다. 이 프로젝트의 확인된 테스트 장치(소니 WF-1000XM3)는 테스트한 모든 전원 사이클에서 동일한 BLE 주소를 유지했습니다. 이는 컴패니언 앱 재연결을 위해 설계된 이어버드에서 예상되는 동작입니다. 일반적으로 회전하는 주소 대신 고정/공용 BLE 주소를 사용합니다(전화기는 개인 주소를 회전시키므로 이러한 이유로 이 도구의 대상으로 적합하지 않습니다). 그러나 모든 이어버드 모델에서 보장되는 것은 아닙니다. 일부 제조업체는 사전 페어링/재연결 모드에서도 해석 가능한 개인 주소(resolvable private address)를 사용하는데, 이 경우 이 도구처럼 페어링되지 않은 스캐너에는 전원을 켤 때마다 다른 주소로 보입니다.
GATT 프로브(--gatt 및 --assess의 GATT 단계)는 기기에 페어링이 필요한 특성이
있는 경우 여러 번의 재연결이 필요할 수 있습니다. 각 재연결은 BlueZ가 실제 페어링
협상을 시도하고(이 도구 자체 에이전트가 거부) 재연결 후 스윕을 재개하기 전에
이루어지며, 각 시도 전에 한 줄 상태 메시지를 출력하므로 느린 스윕이 멈춘 것처럼
보이지 않습니다. 이러한 구독이 거부되면 BlueZ는 해당 의도를 유지하고 이후 해당
기기에 대한 모든 연결에서 다시 발행합니다. 이후 재연결을 방해하지 않도록 프로브는
각 재연결 전에 BlueZ의 기기 캐시 기록을 지우고(bluetoothctl remove와 동일) 모든
시도가 깨끗한 상태에서 시작되도록 합니다. 이렇게 하면 확인된 테스트 기기에 대해
반복적으로 연속 스윕을 수행해도 매번 동일한 전체 결과가 반환됩니다. 이전에 관찰된
현상(과도한 테스트 세션 중 완전성이 저하되고 휴식 후 회복되는 것)은 이후 재발하지
않았으며, 동일한 유지 상태 누적이지 기기 피로는 아닌 것으로 여겨집니다.
buds_audit.py
플래그 없이 실행하면 BLE 주소나 각 플래그의 용도를 미리 알 필요 없이 번호가 매겨진 메뉴가 시작됩니다:
1) Full analysis (scan, run the full CVE audit, and save a baseline)
2) Check current state against a saved baseline
3) Scan for spoofed/impersonating devices
4) Exit
옵션 1은 주변의 알려진 영향 기기를 스캔하여 MAC 주소를 입력하는 대신 번호로 선택할
수 있도록 목록을 표시하고, 전체 CVE 감사(--assess와 동일, BD 주소 조회 포함)를
실행한 다음 기준선을 저장하여(--baseline과 동일) 이후 실행에서 변경 사항을 감지할
수 있게 합니다. 또한 --assess --memory-read가 확인 프롬프트를 통해 답하는 것과
동일한 메모리 읽기 질문을 합니다. 여기서 예라고 답하면 위에서 설명한 것과 동일한
실제 읽기 전용 RACE 플래시 페이지 읽기가 포함되고, 아니요라고 답하면 감사가 그 없이
실행될 뿐 전체 분석이 취소되지는 않습니다. 옵션 2는 이미 기준선을 저장한 기기 목록을
표시하고 선택한 기기의 드리프트를 다시 확인합니다(--check-drift와 동일). 옵션 3은
--watch입니다. 모든 옵션은 무선에 영향을 미치기 전에 플래그 기반 인터페이스와
동일한 소유권 확인을 거칩니다. 마법사는 정확히 동일한 기본 검사 위에 더 친숙한
프런트 엔드를 제공할 뿐이며, 덜 신중한 별도 경로가 아닙니다.
아래의 플래그 기반 인터페이스는 스크립트 사용 또는 대상 주소를 이미 알고 있는 사람을 위해 여전히 제공됩니다.
모든 명령은 venv/bin/python buds_audit.py를 통해 실행됩니다.
buds_audit.py --help
venv가 없고 의존성이 설치되지 않은 상태에서도 python3 buds_audit.py --help만으로
동작합니다. 실제로 라디오가 필요한 명령이 실행되기 전까지는 bleak를 임포트하지
않습니다.
buds_audit.py --scan
buds_audit.py --scan --flags-only # only show devices matching the known-affected catalog
buds_audit.py --scan --target AA:BB:CC:DD:EE:FF
주변 BLE 및 Bluetooth Classic 기기를 수동적으로 스캔하고, 제조업체 데이터와 주소
접두사에서 Airoha 칩셋을 핑거프린팅하며, data/affected_devices.json과 대조합니다.
각각 --target ADDR이 필요하며, 해당 단일 기기에 대한 능동적 작업입니다:
buds_audit.py --gatt --target AA:BB:CC:DD:EE:FF # CVE-2025-20700: unauthenticated GATT access
buds_audit.py --race --target AA:BB:CC:DD:EE:FF # CVE-2025-20702: RACE channel reachability
buds_audit.py --firmware --target AA:BB:CC:DD:EE:FF # CVE-2025-20701: passive firmware/pairing-bypass check
buds_audit.py --bd-address --target AA:BB:CC:DD:EE:FF # Classic BD address via RACE, informational
네 가지 모두 기기가 이미 페어링된 경우 오류 없이 깔끔히 건너뜁니다. "인증되지 않은 액세스" 결과는 본딩된 기기에서는 의미가 없기 때문입니다.
--gatt는 이제 각 성공적인 비페어링 읽기 또는 알림에서 반환된 실제 값(16진수
인코딩)을 표시합니다. 단순히 읽기가 성공했다는 사실만 보여주는 것이 아닙니다. 값은
이미 가져오고 있었으므로 추가 위험은 없으며, 더 이상 버려지지 않을 뿐입니다.
--race는 도달 가능성만 테스트합니다(무해한 SDK 정보 조회, 메모리 액세스 없음).
RACE 서비스가 존재하고 쓰기를 깔끔하게 수락해도 응답하지 않을 수 있는데, 이는
진정으로 결정적이지 않은 결과이지 무엇인가 수정되었다는 증거가 아닙니다. 확정적인
답을 원한다면 아래의 --memory-read를 참조하세요.
--bd-address는 정보 제공용이며 그 자체로는 취약점 발견이 아닙니다. 동일한
인증되지 않은 RACE 채널을 통해 기기의 실제 Bluetooth Classic(BR/EDR) 주소를
조회하며, --firmware의 빌드버전 조회와 동일한 위험 형태(페이로드가 없는
메타데이터 명령)입니다. 이 도구에는 자체 Classic 전송이 없으므로, Classic 지원
라디오/동글로 CVE-2025-20701 능동 테스트를 직접 수행하려는 경우 유용합니다. 아래의
하드웨어 요구 사항 섹션을 참조하세요.
buds_audit.py --memory-read --target AA:BB:CC:DD:EE:FF
CVE-2025-20702를 확정적으로 확인하기 위해 실제 읽기 전용 RACE 플래시 페이지 읽기
(고정 주소에서 256바이트)를 한 번 시도합니다. --race가 RACE 서비스가 존재하지만
무해한 쿼리에 응답하지 않는 것을 발견한 경우 유용합니다. 이는 의도적으로 --race와
분리된 옵트인 기능입니다. 여기서 성공하면 채널 도달 가능 여부에 대한 예/아니오
신호만이 아니라 실제 기기 펌웨어 콘텐츠를 검색합니다. 이 명령은 쓰기, 삭제, 링크 키
추출, RAM/레지스터 읽기를 절대 수행하지 않습니다(읽기 부작용이 없는 플래시만 읽음).
전체 근거는 ROADMAP.md의 Phase 8 및 Out of Scope 섹션을 참조하세요. 표준 소유권
프롬프트 외에 무엇을 수행하는지 정확히 설명하는 자체 별도 확인이 필요합니다.
buds_audit.py --assess --target AA:BB:CC:DD:EE:FF
buds_audit.py --assess --target AA:BB:CC:DD:EE:FF --json result.json
buds_audit.py --assess --target AA:BB:CC:DD:EE:FF --memory-read
위의 GATT, RACE, 펌웨어, BD 주소 프로브를 단일 대상에 대해 실행하고 단일 판정을
생성합니다: PASS, PARTIAL, VULNERABLE, SUSPECTED_COMPROMISE. 판정과 각
개별 발견 사항은 기술적 세부 정보 옆에 평이한 언어 해석과 함께 출력되므로 깊은
블루투스 지식 없이도 결과를 읽을 수 있습니다. 이 도구는 보안 전문가뿐만 아니라
자신의 기기를 확인하려는 모든 사람을 위한 것입니다. --json은 전체 결과(기기 정보,
판정 및 평이한 설명, 증거가 있는 플래그와 평이한 해설, 수정 권고 사항)를 파일에
추가로 기록합니다. --memory-read를 추가하면 메모리 읽기 확인이 동일한 감사 및
판정에 통합되며, 먼저 자체 별도 확인 프롬프트가 표시됩니다. BD 주소 조회는 펌웨어
확인과 동일한 저위험 메타데이터 조회 형태이므로 --assess의 일부로 자동
실행됩니다(별도 플래그나 추가 확인 프롬프트 불필요).
--assess는 개별 프로브와 마찬가지로 의도적으로 단일 대상 전용입니다. "범위 내 모든
기기 평가" 모드는 없습니다. 그렇게 하면 자신의 기기가 아닐 수 있는 기기를 능동적으로
프로빙하게 되기 때문입니다.
buds_audit.py --baseline --target AA:BB:CC:DD:EE:FF
buds_audit.py --check-drift --target AA:BB:CC:DD:EE:FF
--baseline은 기기를 처음 평가할 때 신뢰할 수 있는 스냅샷을 캡처합니다. 여기에는
신원(이름 및 제조업체 데이터), GATT 테이블, RACE 펌웨어 빌드, 로컬 본딩 상태
(페어됨/신뢰됨/본딩됨 불리언만 해당, 키 자료는 절대 아님)가 포함되며
data/device_baselines.json에 저장됩니다. 자동으로 캡처되지 않으며 명시적으로
요청해야 합니다. 다시 실행하면 기존 기준선을 덮어씁니다.
--check-drift는 동일한 스냅샷을 다시 캡처하여 저장된 기준선과 비교하고, 발견된
드리프트에 따라 판정을 생성합니다: IDENTITY_DRIFT, GATT_TABLE_DRIFT,
FIRMWARE_DOWNGRADE, BOND_STATE_DRIFT. 이는 "마지막으로 이 기기를 신뢰한 이후
변경된 것이 있는가"라는 질문에 답하는 것이지 "이 기기가 취약한가"가 아닙니다. 이는
휴리스틱 손상 신호이지 포렌식 증거가 아닙니다. 드리프트 플래그가 있는 기기는 다른
모든 것을 대체하는 SUSPECTED_COMPROMISE 판정을 받습니다.
buds_audit.py --watch
고정 길이 창으로 연속 스캔하고(Ctrl+C로 중지) 이름과 제조업체 데이터로 모든 광고를
상호 연관시킵니다. 두 개의 서로 다른 주소가 동일한 신원을 겹치는 관찰 창에서
브로드캐스트하는 경우(즉, 둘 다 동일한 신원으로 동시에 방송 중인 경우)
POSSIBLE_IMPERSONATION으로 표시합니다. 단일 물리적 기기가 시간이 지나면서 BLE
주소를 회전시키는 경우(동시가 아닌 순차적으로 관찰됨) 표시되지 않습니다. 진짜 두
번째 송신기만 표시됩니다. 이는 위협 모델의 마지막 단계인 피해자 휴대폰에 이어버드를
사칭하는 것에 해당합니다.
data/affected_devices.json은 선별된 카탈로그이지 완전한 목록이 아닙니다. 현재
확인된 기기:
| 브랜드 | 모델 | Airoha SoC | CVE | 패치된 펌웨어 |
|---|---|---|---|---|
| Sony | WF-1000XM3 | AB1562 | CVE-2025-20700, CVE-2025-20701, CVE-2025-20702 | 출시된 패치 없음 |
ERNW 공개에 따르면 Airoha AB1562/AB1565/AB1568 시리즈 SoC를 사용하는 다른
브랜드(Bose, Jabra, JBL, Marshall 및 패치 이전 Beats 모델 포함)도 영향을 받는
것으로 보고되었지만, 이 프로젝트에서 정확한 주소 접두사와 칩셋 세부 정보가 실제
하드웨어로 확인되지 않았으므로 아직 카탈로그에 없습니다. 카탈로그 외부의 기기도
--gatt/--race/--firmware/--assess로 능동적으로 프로빙할 수 있습니다.
카탈로그는 수동적 --scan 일치 및 판정 가중치에만 영향을 미칠 뿐, 프로브 자체가
테스트하는 내용에는 영향을 주지 않습니다.
이 도구는 CVE-2025-20701(Bluetooth Classic 페어링 강제 누락)을 RACE 펌웨어 빌드 버전 확인을 통해 수동적으로만 평가합니다. 자동 페어링 핸드셰이크가 완료될 수 있는지 능동적으로 테스트하려면 Bumble을 통한 원시 HCI 액세스와 전용 Bumble 호환 USB 블루투스 동글이 필요합니다. BlueZ/bleak로는 달성할 수 없으며, 이것이 이 도구가 시도하지 않는 이유입니다. 세 가지 CVE를 모두 다루는 대화형 동글 기반 참조 구현은 ERNW의 race-toolkit을 참조하세요.
Airoha SDK 취약점 체인(CVE-2025-20700 / CVE-2025-20701 / CVE-2025-20702)은
ERNW의 Dennis Heinze와 Frieder Steinmetz가 발견하고 공개했습니다. 그들의
race-toolkit은 이 프로젝트가
동글 없는 공백을 메우는 기준 구현이며, 여기 사용된 정확한 RACE 프로토콜 GATT
UUID와 패킷 프레임은 추측이 아니라 소스에서 직접 읽은 것입니다. 자세한 내용은
core/race.py를 참조하세요. race-toolkit은 라이선스가 없습니다(LICENSE 파일
없음, 저장소에서 직접 확인). 여기서 소스에서 재사용된 것은 기본 프로토콜 사실
(UUID, 구조체 레이아웃, 명령 코드)뿐이며, 이는 Airoha 자체 프로토콜을 설명하는
것이지 저자들의 원래 표현이 아니므로 라이선스 대상이 아닙니다.
MIT - LICENSE를 참조하세요.
venv/bin/ruff check . --fix && venv/bin/ruff format .
venv/bin/python -m pytest tests/