一款无需加密狗、无需root权限的蓝牙安全评估工具,适用于受Airoha SDK漏洞链影响的无线耳机(CVE-2025-20700/20701/20702)
版本 1.0.0
针对受 Airoha SDK 漏洞链(CVE-2025-20700 / CVE-2025-20701 / CVE-2025-20702)影响的无线耳机的蓝牙安全评估工具。扫描附近设备,识别已知受影响的 Airoha 芯片组,并探测未认证的 GATT 访问和 RACE 协议可达性——完全通过操作系统蓝牙协议栈(BlueZ)经由 bleak 实现。无需外部蓝牙加密狗,也无需 root 权限。结果以通俗语言连同技术细节一同报告,因此即使没有深入的蓝牙知识,您也可以据此采取行动。
本工具仅用于评估您拥有的或经明确授权测试的设备。GATT 和 RACE 探测是主动操作:它们会连接并发送命令到目标设备。请勿对不属于您或未经许可测试的设备运行 --gatt、--race、--firmware、--bd-address、--assess、--baseline、--check-drift 或 --memory-read。--scan 是被动的,仅监听已公开广播的广告,因此在范围内对任何设备运行都是安全的。
--memory-read 比其他主动探测更进一步:它从设备实际闪存中检索一个真实的只读页面(256 字节),位于固定地址,作为当仅测试可达性的 --race 探测得不到响应时对 CVE-2025-20702 的确定性确认。它是只读的(闪存读取没有磨损或变砖风险,不像本工具从不发送的写入/擦除/FOTA 命令),需要主动选择,并且在运行任何操作之前,除了标准的设备所有权提示之外,还需要单独确认,详细描述其具体功能。
探测任意附近的设备不仅仅是策略问题——它可能产生实际的副作用。--gatt 会尝试对找到的每一个特征执行读取或通知订阅,而某些消费设备会暴露配置类服务(例如 Google 的快速配对服务),这些服务会因此触发目标设备上的真实配对握手,独立于本工具显式请求的任何操作。即使针对您自己的设备,需要加密的特征也可能触发同样的情况,因为 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——蓝牙经典发现(core/scanner.py)和绑定状态检查(core/gatt.py)都直接调用 bluetoothctl,这是一个仅 BlueZ 的 CLI 工具,在 Windows 上不存在。这些代码路径只会以“命令未找到”失败。bluetoothctl 的 BlueZ,而不仅仅是任何 Linux 内核。大多数桌面发行版都带有该工具;没有 bluez 包的最小或服务器映像不会预装它。已在 BlueZ 5.86 上验证无需 root 即可工作——其他版本应该以相同方式工作,因为 bleak 针对 BlueZ 的标准 D-Bus API,但未独立重新验证。usbipd-win 从 Windows 转发,这仅适用于 USB 连接的适配器。大多数笔记本电脑的内置蓝牙通过非 USB 总线(SDIO/PCIe,与 Wi-Fi 一起)连接,usbipd-win 一般无法转发——因此这完全取决于具体硬件。您不需要自己的 Linux 机器——您只需要一个能实际访问蓝牙无线电的 Linux 系统。两种实用方法:
无论哪种方式,规则相同:工具本身不变——它只需要 Linux 和一个 BlueZ 能够实际访问的蓝牙适配器。
许多 TWS 耳机会在闲置一段时间后停止广播(并断开任何活动连接)以节省电量,有些甚至会自行完全关机。如果扫描无法找到一分钟前还找到的设备,或者探测中途失败,这通常是耳机进入空闲状态,而不是错误——取出耳机或再次按下配对按钮重试。
这一点也影响地址稳定性:本项目已确认的测试单元(索尼 WF-1000XM3)在所有测试的电源周期中保持相同的 BLE 地址,这对于设计用于配套应用重新连接的耳机来说是预期的——它们通常使用固定的/公共 BLE 地址,而不是旋转地址(与手机不同,手机会旋转私有地址,因此不适合作为本工具的目标)。然而,这并非每个耳机型号都能保证——一些供应商即使在预配对/重新连接模式下也使用可解析的私有地址,对于像本工具这样的未配对扫描器来说,这将在每次电源周期后显示为不同的地址。
GATT 探测(--gatt 以及 --assess 的 GATT 阶段)可能需要多次重新连接,如果设备具有需要配对的特性——每次都会使 BlueZ 尝试(并且本工具自己的代理拒绝)真实的配对协商,然后重新连接继续扫描,并且在每次尝试之前会打印一行状态,以便慢速扫描不会看起来卡住。当这样的订阅被拒绝时,BlueZ 保留该意图,并在每次后续与设备的连接中重新发出;为了防止这干扰后续的重新连接,探测器会在每次重新连接之前清除 BlueZ 缓存的设备记录(相当于 bluetoothctl remove),因此每次尝试都从干净状态开始。有了这个机制,针对已确认的测试设备反复背靠背扫描每次都返回相同的完整结果。早期的观察结果——在大量测试过程中完整性似乎下降并在休息后恢复——自那以后没有再次出现,被认为也是相同积累状态的问题,而不是设备疲劳。
buds_audit.py
不带任何标志运行时会启动一个编号菜单,而不是要求您已经知道 BLE 地址或每个标志的作用:
1) 完整分析(扫描,运行完整的 CVE 审计,并保存基线)
2) 根据已保存的基线检查当前状态
3) 扫描伪造/冒充设备
4) 退出
选项 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 # 仅显示与已知受影响目录匹配的设备
buds_audit.py --scan --target AA:BB:CC:DD:EE:FF
被动扫描附近的 BLE 和蓝牙经典设备,从制造商数据和地址前缀识别 Airoha 芯片组,并与 data/affected_devices.json 交叉引用。
每个都需要 --target ADDR 并且是针对该单个设备的主动操作:
buds_audit.py --gatt --target AA:BB:CC:DD:EE:FF # CVE-2025-20700:未认证的 GATT 访问
buds_audit.py --race --target AA:BB:CC:DD:EE:FF # CVE-2025-20702:RACE 通道可达性
buds_audit.py --firmware --target AA:BB:CC:DD:EE:FF # CVE-2025-20701:被动固件/配对绕过检查
buds_audit.py --bd-address --target AA:BB:CC:DD:EE:FF # 通过 RACE 获取经典 BD 地址,信息性
如果设备已配对,所有四个探测都会干净地跳过(无错误)——“未认证访问”的发现对于已绑定的设备没有意义。
--gatt 现在会显示每次成功的未配对读取或通知返回的实际值(十六进制编码),而不仅仅是读取成功——该值已经被检索,因此没有额外的风险,只是不再被丢弃。
--race 仅测试可达性(一个良性的 SDK 信息查询,无内存访问)——RACE 服务可能存在并干净地接受写入,但仍然不回复,这是一个真正不确定的结果,而不是任何已修复的证据。如需确定性答案,请参见下面的 --memory-read。
--bd-address 是信息性的,本身不是漏洞发现:它通过相同的未认证 RACE 通道查询设备的真实蓝牙经典(BR/EDR)地址,风险形状与 --firmware 的构建版本查询相同(一个零负载元数据命令)。如果您想自行使用经典无线电/加密狗进行 CVE-2025-20701 主动测试,这将很有用,因为本工具本身没有经典传输——请参见下面的硬件要求部分。
buds_audit.py --memory-read --target AA:BB:CC:DD:EE:FF
尝试一次真实的只读 RACE 闪存页面读取(256 字节,从固定地址),用于确定性 CVE-2025-20702 确认——当 --race 发现 RACE 服务存在但对其良性查询无响应时很有用。这是可选的,并且特意与 --race 分开:成功时会检索真实的设备固件内容,而不仅仅是关于通道是否可达的是/否信号。它从不写入、擦除、提取链路密钥或读取 RAM/寄存器(仅读取无副作用的闪存)——有关完整推理,请参见 ROADMAP.md 的第 8 阶段和范围外部分。除了标准的设备所有权提示之外,它还需要自己的单独确认,详细描述其具体功能。
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(GATT 表漂移)、FIRMWARE_DOWNGRADE(固件降级)或 BOND_STATE_DRIFT(绑定状态漂移)。这回答的是“自上次信任该设备以来是否有变化”,而不是“此设备是否易受攻击”——这是一个启发式的入侵信号,不是法证证据。任何带有漂移标志的设备都会得到 SUSPECTED_COMPROMISE(疑似遭入侵)判定,该判定优先于所有其他判定。
buds_audit.py --watch
在固定长度的窗口中连续扫描(Ctrl+C 停止),并根据名称和制造商数据关联看到的每个广告。如果两个不同的地址在重叠的观察窗口中广播相同的身份——意味着两者同时在空中具有该身份——则标记为 POSSIBLE_IMPERSONATION(可能冒充)。单个物理设备随时间旋转其 BLE 地址(顺序看到,非并发)不会被标记;只有真正第二个发射器才会被标记。对应威胁模型的最后一步:冒充耳机与受害者的手机进行通信。
data/affected_devices.json 是精心策划的目录,而非详尽列表。目前已确认:
| 品牌 | 型号 | Airoha SoC | CVE | 已修补固件 |
|---|---|---|---|---|
| 索尼 | 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(缺失蓝牙经典配对强制),通过 RACE 固件构建版本检查。主动测试无声配对握手是否可以完成需要通过 Bumble 以及专用的 Bumble 兼容 USB 蓝牙加密狗进行原始 HCI 访问——无法通过 BlueZ/bleak 实现,这也是本工具未尝试的原因。请参阅 ERNW 的 race-toolkit 获取交互式、基于加密狗的参考实现,涵盖所有三个 CVE。
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/