iOS won't tell me a BLE device's MAC, so I started reading it out of the vendors' own payloads. What else leaks like this?
CoreBluetooth will not give you a peripheral's Bluetooth address. You get a UUID that the system assigns the first time it encounters the peer, and Apple documents it exactly that way, so on iOS every address-level trick is off the table before you start.
Then I noticed that a lot of devices just hand it over themselves.
Xiaomi MiBeacon, service data UUID 0xFE95, carries a frame control word where bit 4 means "MAC included". When that bit is set, the six bytes at offset 5 are the address, stored reversed. It is right there in the xiaomi-ble parser that Home Assistant depends on, so this is not me guessing at a hex dump. SwitchBot and Govee do their own version of the same thing in their own service data.
So the reliable route to a hardware address, on a platform that deliberately hides it, runs straight through the payload the device is already shouting at everyone in range.
The part I cannot make sense of is that this forecloses address rotation completely. Whatever the link layer does with private addresses, a fixed six bytes sitting in the advertisement hands any passive scanner a permanent handle on the device. I can construct one excuse, that the vendor's app needs something stable to bind against while the address moves underneath it, but that argues for a derived per-vendor id rather than the real MAC.
I am now checking both ends of every service data blob I decode for six plausible bytes, and I am hitting more than I expected to.
Which vendors am I still missing, and has anyone found an actual protocol constraint that justifies putting it there? I would rather be corrected than write it off as sloppiness.
Disclosure so nobody has to check: the scanner is my own iOS app, BLE Scanner. The payload is the interesting part here, not the app.