README.md
Rendering markdown...
"""
CVE-2025-36911 check
DESCRIPTION:
This tool tests Bluetooth audio devices for CVE-2025-36911, which allows
unauthenticated L2CAP connections to audio profiles (A2DP/AVRCP) without
prior pairing. Vulnerable devices accept connections on restricted PSMs,
potentially allowing unauthorized audio control or stream access.
AFFECTED PROFILES:
- AVDTP (PSM 0x0019): Advanced Audio Distribution Profile
- AVCTP (PSM 0x0017): Audio/Video Remote Control Profile
- Fast Pair (BLE UUID 0xFE2C): Google Fast Pair protocol
DISCLAIMER:
Use only on your own devices or with explicit authorization.
Do not deny pairing requests during testing to avoid implicit bonding.
"""
import asyncio
import socket
import sys
import subprocess
from typing import Dict, List, Optional
from bleak import BleakScanner, BleakClient
from bleak.exc import BleakError
try:
from bleak.exc import BleakDBusError
except Exception:
BleakDBusError = BleakError
PSM_AVCTP = 0x0017
PSM_AVDTP = 0x0019
PSM_AVCTP_BROWSING = 0x001B
async def scan_ble_devices() -> List[Dict[str, str]]:
try:
devices = await BleakScanner.discover()
return [{"address": d.address, "name": d.name or ""} for d in devices]
except BleakDBusError as e:
print("BLE unavailable (BlueZ/DBus):", e)
return []
except BleakError as e:
print("BLE error:", e)
return []
async def has_fast_pair_service(addr: str) -> bool:
if not addr or addr.lower() == "00:00:00:00:00:00":
return False
try:
client = BleakClient(addr)
await asyncio.wait_for(client.connect(), timeout=8.0)
try:
services = client.services
for s in services:
if s.uuid.lower() == "0000fe2c-0000-1000-8000-00805f9b34fb":
return True
finally:
try:
await client.disconnect()
except Exception:
pass
except (BleakError, asyncio.TimeoutError, asyncio.CancelledError):
return False
return False
def l2cap_connect(addr: str, psm: int, timeout: float = 5.0) -> bool:
sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP)
sock.settimeout(timeout)
try:
sock.connect((addr, psm))
return True
except Exception:
return False
finally:
try:
sock.close()
except Exception:
pass
def test_unpaired_br_edr(addr: str) -> Dict[str, bool]:
results = {
"avdtp": l2cap_connect(addr, PSM_AVDTP),
"avctp": l2cap_connect(addr, PSM_AVCTP),
"avctp_browsing": l2cap_connect(addr, PSM_AVCTP_BROWSING),
}
return results
async def run_tests(target_addr: str) -> None:
print(f"Target: {target_addr}")
br_results = test_unpaired_br_edr(target_addr)
print("\nBR/EDR Audio PSM Results (unauthenticated):")
for k, v in br_results.items():
status = "OPEN" if v else "BLOCKED"
print(f" {k:20} : {status}")
fp = await has_fast_pair_service(target_addr)
print(f"\nFast Pair Service (BLE): {'DETECTED' if fp else 'Not found'}")
if br_results.get("avdtp") or br_results.get("avctp") or br_results.get("avctp_browsing"):
print("\n> CVE-2025-36911 VULNERABILITY DETECTED")
print("> Device accepts unauthenticated L2CAP connections to audio profiles")
else:
print("\n> Device not vulnerable to CVE-2025-36911 - pairing required for audio access")
def bluetoothctl_info(addr: str) -> Dict[str, str]:
info: Dict[str, str] = {}
try:
out = subprocess.check_output(["bluetoothctl", "info", addr], text=True)
for line in out.splitlines():
line = line.strip()
if line.startswith("Paired:"):
info["paired"] = line.split(":", 1)[1].strip()
elif line.startswith("Connected:"):
info["connected"] = line.split(":", 1)[1].strip()
elif line.startswith("Trusted:"):
info["trusted"] = line.split(":", 1)[1].strip()
elif line.startswith("Blocked:"):
info["blocked"] = line.split(":", 1)[1].strip()
elif line.startswith("Bonded:"):
info["bonded"] = line.split(":", 1)[1].strip()
except Exception:
pass
return info
def bluetoothctl_cmd(*args: str) -> bool:
try:
subprocess.check_call(["bluetoothctl", *args], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except Exception:
return False
def validate_mac_address(addr: str) -> bool:
if not addr:
return False
parts = addr.split(":")
if len(parts) != 6:
return False
for part in parts:
if len(part) != 2:
return False
try:
int(part, 16)
except ValueError:
return False
return True
def device_exists(addr: str) -> bool:
try:
out = subprocess.check_output(["bluetoothctl", "devices"], text=True)
for line in out.splitlines():
if addr.lower() in line.lower():
return True
return False
except Exception:
return False
def print_help() -> None:
print("""
CVE-2025-36911 scanner
USAGE:
python CVE-2025-36911.py [OPTIONS] [BLUETOOTH_ADDRESS]
OPTIONS:
--help Display this help message
--skip-ble Skip BLE device scanning (BR/EDR only mode, you must provide address)
--require-unpaired Refuse to test already paired devices (usefull to avoid false positives)
--lock-pairing Disable pairing during test (pairable off, usefull to avoid implicit bonding)
ARGUMENTS:
BLUETOOTH_ADDRESS Target device address (XX:XX:XX:XX:XX:XX)
If not provided, BLE scan will discover devices (if --skip-ble not used)
EXAMPLES:
python CVE-2025-36911.py
Interactive mode: scan BLE devices and select target
python CVE-2025-36911.py AA:BB:CC:DD:EE:FF
Test specific device by address
python CVE-2025-36911.py --require-unpaired --lock-pairing AA:BB:CC:DD:EE:FF
Test unpaired device with pairing locked during test
DESCRIPTION:
Tests Bluetooth audio devices for CVE-2025-36911 vulnerability by attempting
unauthenticated L2CAP connections to audio profiles (AVDTP/AVCTP). Vulnerable
devices accept connections without prior pairing, allowing unauthorized audio
control or stream access.
DISCLAIMER:
Use only on your own devices or with explicit authorization.
""")
async def main(argv: List[str]) -> None:
"""Main entry point with argument parsing."""
if "--help" in argv or "-h" in argv:
print_help()
return
skip_ble = False
require_unpaired = False
lock_pairing = False
target: Optional[str] = None
for a in argv[1:]:
if a == "--skip-ble":
skip_ble = True
elif a == "--require-unpaired":
require_unpaired = True
elif a == "--lock-pairing":
lock_pairing = True
elif ":" in a:
if validate_mac_address(a):
target = a
else:
print(f"Error: Invalid MAC address format: {a}")
print("MAC address must be in format XX:XX:XX:XX:XX:XX")
return
if not target:
if not skip_ble:
print(" Scanning BLE devices...")
devices = await scan_ble_devices()
for i, d in enumerate(devices):
print(f"[{i}] {d['address']} - {d['name']}")
if devices:
sel = input("\nSelect target index or enter BR/EDR address: ").strip()
if ":" in sel:
if validate_mac_address(sel):
target = sel
else:
print(f"Error: Invalid MAC address format: {sel}")
print("MAC address must be in format XX:XX:XX:XX:XX:XX")
return
else:
try:
idx = int(sel)
target = devices[idx]["address"]
except Exception:
print("Invalid selection.")
return
else:
print("No BLE devices found. Have you turn on Bluetooth?")
return
else:
print("Provide BR/EDR address as argument.")
return
if target and require_unpaired:
info = bluetoothctl_info(target)
paired = info.get("paired")
if paired is None:
print(f"Warning: Cannot determine pairing state for {target}")
print("The device may not be currently discoverable.")
print("\nOptions:")
print(" 1. Remove --skip-ble to scan and discover the device first")
print(" 2. Remove --require-unpaired to test regardless of pairing state")
print(" 3. Ensure the device is powered on and in pairing mode")
return
if paired.lower() == "yes":
print("Error: Device already paired. Unpair before testing.")
return
if target:
if not device_exists(target):
print(f"Error: Device {target} not found in Bluetooth cache.")
print("Please ensure the device is discoverable and has been scanned.")
print("Run without address to scan for available devices first.")
return
if lock_pairing:
bluetoothctl_cmd("pairable", "off")
await run_tests(target)
if target:
info_after = bluetoothctl_info(target)
if info_after.get("paired", "no").lower() == "yes":
print("\n> WARNING: Device is now paired.")
print("> L2CAP connections may have triggered implicit bonding.")
if lock_pairing:
bluetoothctl_cmd("pairable", "on")
if __name__ == "__main__":
try:
asyncio.run(main(sys.argv))
except KeyboardInterrupt:
print("\n Interrupted.")