Bluetooth Smart (Bluetooth Low Energy; BLE) is everywhere!
Ge0rG (@ge0rg@chaos.social)
GPN24 (6. Juni 2026, 22:30, ZKM Kubus)
Bluetooth Smart Background
Arming the Androids
Gadget Reconnaissance
Snooping the Log
Analyzing the App

Bluetooth Low Energy ⇒ Bluetooth LE ⇒ BLE
“Bluetooth 4.2” / “Bluetooh 5.0” sometimes
The “S” in Bluetooth LE stands for “Security”
0x1800) - approved by Bluetooth Special
Interest Group (SIG)
0x1800 = 00001800-0000-1000-8000-00805F9B34FBWRITE ➤ send dataREAD ➤ request data immediatelyNOTIFY ➤ request data updatesWRITE + 1x NOTIFY as
“serial interface”
| Gadget | Name | Properties | Level |
|---|---|---|---|
![]() |
Fnirsi IR40 Laser Distance Meter | React Native. Unauthenticated NOTIFY with distance in
mm, WRITE to start continuous measurement (webtool) |
★☆☆☆☆ |
![]() |
Colmi P80 | Obfuscated Java. Simple WRITE and NOTIFY,
>60 commands, sub-, sub-sub-commands, opaque (Moyoung) |
★★☆☆☆ |
![]() |
SurpLife SMART LED Curtain | Flutter binary app 🤐. Simple WRITE commands for
single-color fill, complex image format for bitmaps (code) |
★★★☆☆ |
![]() |
Xiaomi S400 Body Scale | Obfuscated Java. Encrypted, authenticated, requires online login and cloud token extraction | ★★★★☆ |
![]() |
SpportsTech ES600 | Unobfuscated Java. Simple protocol, BUT: Requires pedaling the bike to connect | 🥵🥵🥵🥵 |
Developer Mode: “Settings” ➤ “Software” ➤ “Build Number” ➤ 7x 🖕
Bluetooth Logging: “Developer Settings” ➤ “Bluetooth HCI Snoop”
nRF Connect for Mobile (Google Play)
Install your gadget’s app
Install adb and Android USB drivers ➤ obtain HCI log files
Install Wireshark ➤ read HCI log files
Install jadx-gui ➤ decompile Android APKs
Bonus level: install apk-mitm + Zed Attack Proxy ➤ 🐒 Monkey-in-the-Middle



com.mobifyi.xavax_app
At first ensure that you are recording logs, and can fetch them!
$ adb root
$ adb pull /data/misc/bluetooth/logs/btsnoop_hci.log
$ adb shell su -c "tar c -C/data/log/bt/ btsnoop_hci.log" |tar xvv
$ adb bugreport bugreport-2026-03-15-mygadget.zip
... ⌛ ... 🥱 ... ☕ ... (2 minutes later)
$ unzip bugreport-2026-03-15-mygadget.zip FS/data/log/bt/btsnoop_hci.log
$ file FS/data/log/bt/btsnoop_hci.log
FS/data/log/bt/btsnoop_hci.log: BTSnoop version 1, HCI UART (H4)
🤔 What exactly did I do, in which order? 🤔
Take the time (Seconds precision! On your Android!)
Start the “original” app
Make notes or screenshots (or a screen recording)
Remember if you screenshotted before or after the action
Download HCI log and start digging


bluetooth.addr==DE:AD:CA:FE:BA:BE (from/to gadget)btatt (Bluetooth ATT payloads)
btatt.opcode values:
0x0b=READ ···
0x1b=NOTIFY ···
0x52=WRITEadb shell cmd package list packages | grep titleSDK/build-tools/36.1.0/aapt dump badging shitty.apk | head -1adb)$ adb shell pm path com.crrepa.band.colmi_fit
package:/data/app/~~9L5M7kslKekKaroB33ApYQ==/com.crrepa.band.colmi_fit-wx5dVrs6O5sbSG-SoFXlsA==/base.apk
package:/data/app/...../split_config.arm64_v8a.apk
package:/data/app/...../split_config.de.apk
package:/data/app/...../split_config.en.apk
package:/data/app/...../split_config.xxhdpi.apk
$ adb pull /data/app/~~9L5M7kslKekKaroB33ApYQ==/com.crrepa.band.colmi_fit-wx5dVrs6O5sbSG-SoFXlsA==/base.apk
Download from phone (APK Extractor)
Google them / Aurora Store / APKPure / QR code (Chinese apps)
jadx-gui base.apk ➤ open in interactive viewerjadx base.apk ➤ will extract into
package_name/fff1 (UUID for
SportsTech ES600)
BluetoothAdapter.LeScanCallbackBluetoothGattCallbackBluetoothGattCharacteristic

Package name: com.yuedongsports.e_health - UUIDs
fff1 and fff2
/* com.yuedongsports.e_health.service.UartService */ RX_CHAR_UUID = UUID.fromString("0000fff2-0000-1000-8000-00805f9b34fb"); TX_CHAR_UUID = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb"); /* stripped irrelevant code, error handling, debug output */ public boolean writeRXCharacteristic(byte[] payload) { service = mBluetoothGatt.getService(RX_SERVICE_UUID); characteristic = service.getCharacteristic(RX_CHAR_UUID); characteristic.setValue(payload); mBluetoothGatt.writeCharacteristic(characteristic); }
/* com.yuedongsports.e_health.util.BluetoothCommand */ public static boolean readUserInfo(UartService uartService) { L.e("readUserInfo..."); byte[] payload = { 242, 194, 1, 0, 0 }; payload[4] = BluetoothCalculatorUtils.checkOutSum(payload); return uartService.writeRXCharacteristic(payload); }

242 = header, 194 = cmd “read user info”,
1 = length, 0 = payload,
checkOutSum() - all bytes’ sum
(venv) $ python3 cli.py 68:9E:19:xx:xx:xx
Connecting...
Connected!
>> f2c000b2
>> f2c1050100000000b9
<< R_LINK_COMMAND len: 0 b'\xc2' 194 b''
>> f2c20100b5
<< R_MEMORY_COMMAND len: 12 b'\t' 9 b'010118000020000000000000'
<< UNK_210 len: 14 b'\xbe' 190 b'00001901003200a0000000000000'
...
EOFError
🎉 🤦

Perform a MitM attack on app’s cloud connection to get a full picture
zap_root_ca.ceradb push zap_root_ca.cer /sdcard/Alternative: mitmproxy (web UI, wireguard “service”)


Lua runtime that will process a packet, converting it into a protocol tree
Example: MOYOUNG / COLMI (high-level description, Lua dissector)
moyoung = Proto("MOYOUNG", "MOYOUNG BLE Protocol")
moyoung.fields.uuid = ProtoField.uint16("moyoung.uuid", "DaFit UUID", base.HEX)
moyoung.fields.size = ProtoField.uint16("moyoung.size", "Size", base.DEC, nil, 0x1fff)
function moyoung.dissector(buffer, pinfo, tree)
if buffer:len() > 4 and buffer(0, 2):uint() == 0xfeea then
pinfo.cols.protocol = moyoung.name
local subtree = tree:add(moyoung, buffer(), "MOYOUNG protocol message")
subtree:add(moyoung.fields.uuid, buffer(0, 2))
subtree:add(moyoung.fields.size, buffer(2, 2))
local cmd = buffer(4, 1):uint()
subtree:add(moyoung.fields.cmd, buffer(4, 1))
end
end