Analyzing Bluetooth Smart Gadget Communications

Bluetooth Smart (Bluetooth Low Energy; BLE) is everywhere!

Ge0rG (@ge0rg@chaos.social)

Easter-H-Egg 23 (4. April 2026, 12:45, Cheshire Den)

Agenda

  1. Bluetooth Smart Background

  2. Arming the Androids

  3. Gadget Reconnaissance

  4. Snooping the Log

  5. Analyzing the App

Bluetooth Smart Background

Le Smart Classic Bluetooth? 🤔

🪦 “Bluetooth Smart” / “Smart Ready” (2009-2017)


  • Bluetooth Low Energy ⇒ Bluetooth LE ⇒ BLE

  • The “S” in Bluetooth LE stands for “Security”

  • Everything is just “Bluetooth” as of 5.0!

Bluetooth LE Profiles

Beacons / Advertising
Generic Access Profile GAP
Generic ATTribute Profile GATT

Bluetooth LE Service Structure

Every item has a UUID

  • “Short” UUID (0x1800) - approved by Bluetooth Special Interest Group (SIG)
    • Short-form based on Bluetooth SIG Base UUID:
      0x1800 = 00001800-0000-1000-8000-00805F9B34FB
  • “Long” (random!) UUID - vendor-specific, collision-resistant


Reading and Writing

  • “Service”: functionality discoverable by UUID
  • “Characteristic”: individual data element (e.g. “Device name”)
    • WRITE ➤ send data
    • READ ➤ request data immediately
    • NOTIFY ➤ request data updates
  • “Handles” used on the wire for services and characteristics
    • data fragmentation based on MTU: 🎲 23 / 158 / 185 / 517
  • Common combo: 1x WRITE + 1x NOTIFY as “serial interface”

Challenge Levels

Gadget Name Properties Level
Fnirsi IR40 Laser Distance Meter Simple, unauthenticated, NOTIFY with distance value in mm, WRITE to start continuous measurement (webtool) ★☆☆☆☆
Colmi P80 Simple WRITE and NOTIFY, >60 commands, sub- and sub-sub-commands, opaque payloads (Moyoung) ★★☆☆☆
SurpLife SMART LED Curtain Simple WRITE commands for single-color fill, complex image format for bitmaps, Flutter binary app 🤐 (code) ★★★☆☆
Xiaomi S400 Body Scale Encrypted, authenticated, requires online login and cloud token extraction ★★★★☆
SpportsTech ES600 Simple protocol, BUT: Requires pedaling the bike to connect 🥵🥵🥵🥵

Preparation

Arming the Androids

On the Phone

  1. Developer Mode: “Settings” ➤ “Software” ➤ “Build Number” ➤ 7x 🖕

  2. Bluetooth Logging: “Developer Settings” ➤ “Bluetooth HCI Snoop”

  3. nRF Connect for Mobile (Google Play)

  4. Install your gadget’s app

On the PC

  1. Install adb and Android USB drivers ➤ obtain HCI log files

  2. Install Wireshark ➤ read HCI log files

  3. Install jadx-gui ➤ decompile Android APKs

  4. Bonus level: install apk-mitm + Zed Attack Proxy ➤ 🐒 Monkey-in-the-Middle

Gadget Reconnaissance

Am I the first?

Use nRF Connect

  • Get “Device Name”
  • Get service and characteristic UUIDs
  • Device Name sometimes contains parts of MAC address

Use Google your favorite search engine!

  • Search for BLE name, UUIDs, brand name, model, …
  • Look up MAC address manufacturer…
  • Check GitHub, GitLab, Codeberg, …
  • Fitness: check Gadgetbridge issues, ask the devs

Xavax 7W E27 RGB Light

  • No clear model name, opaque PN (“001119730001”)
  • Googled “Xavax RGB LED Bluetooth”
  • Found User manual with hint:
    • App is called “XAVAX II” (~2018) - deleted from Play Store eons ago
    • APKPure has mirror of APK
    • Package name com.mobifyi.xavax_app

Downloading HCI Logs

At first ensure that you are recording logs, and can fetch them!

Rooted Android

$ adb root
$ adb pull /data/misc/bluetooth/logs/btsnoop_hci.log

Kinder-Android

$ 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)

Recording Useful HCI Logs

🤔 What exactly did I do, in which order? 🤔

  1. Take the time (Seconds precision! On your Android!)

  2. Start the “original” app

  3. Make notes or screenshots

    • Timestamp (“17:23:42” - it’s in the screenshot filename!)
    • Exact action (“set wakeup time”)
    • Exact parameters (“09:30 AM”)
  4. Remember if you screenshotted before or after the action

  5. Download HCI log and start digging

Wiresharking the log

Wireshark: Filters

  • bluetooth.addr==DE:AD:CA:FE:BA:BE (from/to gadget)
  • btatt (Bluetooth ATT payloads)
    • btatt.opcode values: 0x0b=READ ··· 0x1b=NOTIFY ··· 0x52=WRITE

Analyzing the App

Getting the APK

Obtaining the Package Name

  • “Share” from the Play Store listing ➤ URL query parameter
  • Needle in the haystack: adb shell cmd package list packages | grep title
  • From the APK: SDK/build-tools/36.1.0/aapt dump badging shitty.apk | head -1

Potential download sources

  1. Download from phone (adb)
$ 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
  1. Download from phone (APK Extractor)

  2. Google them / Aurora Store / APKPure / QR code (Chinese apps)

Analyzing the App

Decompile with jadx

  • jadx-gui base.apk ➤ open in interactive viewer
  • jadx base.apk ➤ will extract into package_name/

Exploration and digging

  1. Find the UUIDs / names / other things
  • “Navigation” ➤ “Text Search” ➤ fff1 (UUID for Sportstech ES600)
    • BluetoothAdapter.LeScanCallback
    • BluetoothGattCallback
    • BluetoothGattCharacteristic

Example: SportsTech ES600

Device Under Test

“eHealth” app (2018)

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

Python Reimplementation with Blake

(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
  • I crashed it (the controller reboots)!

🎉 🤦

Further Examples

BLE Printers

Other gadgets

Bonus Level

Zed Attack Proxy

Perform a MitM attack on app’s cloud connection to get a full picture

  1. Install and launch ZAP on your PC, under Tools ➤ Options ➤
    • Local Servers/Proxies ➤ Main Proxy: check IP and port!
    • Server Certificates: Generate, Save zap_root_ca.cer
  2. Use apk-mitm on the target APK
  3. Fully uninstall the originally signed app, install the MitM-patched APK (if you get a certificate mismatch error, also uninstall from work profile 🤦)
  4. adb push zap_root_ca.cer /sdcard/
  5. Android Settings ➤
    • Connections ➤ WiFi ➤ “My Network” ➤ More ➤ Proxy ➤ Manual ➤ Your laptop IP
    • Security ➤ Encryption & Credentials ➤ Install a certificate ➤ CA certificate
  6. PROFIT!