#!/usr/bin/env python3 """Scan decompiled Android sources for BLE UUIDs and GATT-related calls.""" from __future__ import annotations import argparse import re from pathlib import Path UUID_RE = re.compile( r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" ) GATT_TERMS = [ "BluetoothGatt", "BluetoothGattCallback", "BluetoothGattCharacteristic", "BluetoothGattDescriptor", "BluetoothLeScanner", "startScan", "startLeScan", "connectGatt", "createBond", "discoverServices", "getService", "getCharacteristic", "setCharacteristicNotification", "writeCharacteristic", "readCharacteristic", "writeDescriptor", "onCharacteristicChanged", "onCharacteristicRead", "onCharacteristicWrite", ] def interesting(path: Path) -> bool: return path.suffix.lower() in {".java", ".kt", ".xml", ".smali"} def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("source", type=Path, help="JADX/apktool output directory") args = parser.parse_args() for path in sorted(p for p in args.source.rglob("*") if p.is_file() and interesting(p)): try: text = path.read_text(errors="ignore") except OSError: continue matches = sorted(set(UUID_RE.findall(text))) terms = [term for term in GATT_TERMS if term in text] if not matches and not terms: continue print(f"## {path}") if matches: print("UUIDs:") for value in matches: print(f"- {value.lower()}") if terms: print("GATT terms:") for value in terms: print(f"- {value}") print() return 0 if __name__ == "__main__": raise SystemExit(main())