4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2025-36911.py PY
"""
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.")