FTLab Wireless Smart Geiger — Part 2
Previous post:
With no app to talk to the FTLab Wireless Smart Geiger BSG-001, let’s see if we can manually talk to it from Linux.
Bluetooth
The device is a BLE (Bluetooth Low Energy) connected radiation detector.
So let’s start by installing BlueZ and starting it:
$ sudo apt install bluez
$ sudo systemctl enable --now bluetooth
Check if Bluetooth is turned on:
$ bluetoothctl show | grep -E 'Powered|Name'
Name: xxx
Powered: yes
$ rfkill list bluetooth
2: hci0: Bluetooth
Soft blocked: no
Hard blocked: no
Find the device
$ bluetoothctl --timeout 10 scan on
Discovery started
[CHG] Controller AC:45:EF:XX:XX:XX Discovering: yes
[NEW] Device 61:F9:F2:XX:XX:XX 61-F9-F2-XX-XX-XX
[NEW] Device B8:2D:28:XX:XX:XX Bouygtel4K
[NEW] Device 53:E2:64:XX:XX:XX 53-E2-64-XX-XX-XX
[NEW] Device DC:97:58:XX:XX:XX MiTV-XXXXX
[NEW] Device 3E:32:E2:XX:XX:XX 3E-32-E2-XX-XX-XX
[NEW] Device F4:C2:33:XX:XX:XX XXXXXXXXXXXXXX
[NEW] Device 50:32:37:XX:XX:XX 50-32-37-XX-XX-XX
[NEW] Device D4:EA:38:XX:XX:XX BLE_Gamma:XXXX
[NEW] Device 47:8F:E5:XX:XX:XX 47-8F-E5-XX-XX-XX
[NEW] Device 90:DD:5D:XX:XX:XX 90-DD-5D-XX-XX-XX
That’s the one named “BLE_Gamma:XXXX”. Quickly after the previous command, run the following one, while the information is still in the cache:
$ bluetoothctl info D4:EA:38:XX:XX:XX
Device D4:EA:38:XX:XX:XX (random)
Name: BLE_Gamma:XXXX
Alias: BLE_Gamma:XXXX
Paired: no
Bonded: no
Trusted: no
Blocked: no
Connected: no
LegacyPairing: no
UUID: Unknown (00001523-0000-1000-8000-00805f9b34fb)
UUID: Vendor specific (00001523-1212-EFDE-1523-785FEABCD123)
Here, what we learn is that:
- the Bluetooth address is a static random address and seems persistent over time (I have never seen it change), but doesn’t tell us anything about the vendor;
- the name is a
BLE_Gamma:prefix, followed by a string configured in the app; - looking up the vendor-specific UUID lands us on Nordic’s nRF Connect SDK LED Button Service;
- the unknown UUID is a 16-bit UUID in the reserved range but is not an assigned one; given it’s the same short number as the vendor-specific one, I guess it’s just the same but advertised with the wrong type.
That “LED button service” makes me think that FTLab reused an SDK example to develop the BSG-001 firmware, keeping the same UUID base but doing their own implementation. Which in turn makes me think that the firmware will probably not follow standards and will probably contain bugs. Just a regular ship PoC to prod antipattern.
This device can’t be paired: trying to do so on Linux does nothing.
List the services
Note that in this post, I use gatttool which is now deprecated in BlueZ.
bluetoothctl replaces it: it’s an interactive CLI where you connect to the device first, then send the commands (menu gatt, select-attribute, read, write, notify on).
$ gatttool -t random -b D4:EA:38:XX:XX:XX --primary
attr handle = 0x0001, end grp handle = 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle = 0x0008, end grp handle = 0x0008 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle = 0x0009, end grp handle = 0x0011 uuid: 00001523-1212-efde-1523-785feabcd123
attr handle = 0x0012, end grp handle = 0xffff uuid: 0000180a-0000-1000-8000-00805f9b34fb
So according to the official list of services provided by the Bluetooth SIG:
- 1800: Generic Access Profile (GAP);
- 1801: Generic Attribute Profile (GATT), its start and end handles are the same (
0x0008), so it has no characteristics; - 00001523-1212-efde-1523-785feabcd123: vendor-specific, matching Nordic’s LED button service, repurposed by FTLab;
- 180a: Device Information Service (DIS).
Let’s dig a bit more:
$ gatttool -t random -b D4:EA:38:XX:XX:XX --characteristics
handle = 0x0002, char properties = 0x0a, char value handle = 0x0003, uuid = 00002a00-0000-1000-8000-00805f9b34fb
handle = 0x0004, char properties = 0x02, char value handle = 0x0005, uuid = 00002a01-0000-1000-8000-00805f9b34fb
handle = 0x0006, char properties = 0x02, char value handle = 0x0007, uuid = 00002a04-0000-1000-8000-00805f9b34fb
handle = 0x000a, char properties = 0x0a, char value handle = 0x000b, uuid = 00001524-1212-efde-1523-785feabcd123
handle = 0x000c, char properties = 0x12, char value handle = 0x000d, uuid = 00001525-1212-efde-1523-785feabcd123
handle = 0x000f, char properties = 0x12, char value handle = 0x0010, uuid = 00001526-1212-efde-1523-785feabcd123
handle = 0x0013, char properties = 0x02, char value handle = 0x0014, uuid = 00002a29-0000-1000-8000-00805f9b34fb
| bit | meaning |
|---|---|
| 0x02 | read |
| 0x04 | write without response |
| 0x08 | write |
| 0x10 | notify |
| 0x20 | indicate |
So:
0x0a=0x02+0x08= read + write;0x12=0x02+0x10= read + notify.
And with the official list of characteristic UUIDs:
| Value handle | UUID | Properties | Description | Value (if readable) |
|---|---|---|---|---|
| 0x0003 | 2a00 | read, write | Device Name | BLE_Gamma:XXXX |
| 0x0005 | 2a01 | read | Appearance | 0x0000 = “unknown” |
| 0x0007 | 2a04 | read | Peripheral Preferred Connection Parameters | 06 00 90 01 00 00 90 01 (see below) |
| 0x000b | 1524 | read, write | Vendor specific | |
| 0x000d | 1525 | read, notify | Vendor specific | |
| 0x0010 | 1526 | read, notify | Vendor specific | |
| 0x0014 | 2a29 | read | Manufacturer Name String | FTLAB |
To read the value of a characteristic, use the gatttool command with the --char-read option and xxd if the raw value needs to be decoded to ASCII.
For example:
$ gatttool -t random -b D4:EA:38:XX:XX:XX --char-read -a 0x0014
Characteristic value/descriptor: 46 54 4c 41 42
$ echo "46 54 4c 41 42" | xxd -r -p; echo
FTLAB
For “Peripheral Preferred Connection Parameters”, according to LL_CONNECTION_PARAM_REQ:
- Minimum connection interval:
06 00=0x0006= 6 × 1.25 ms = 7.5 ms; - Maximum connection interval:
90 01=0x0190= 400 × 1.25 ms = 500 ms; - Peripheral latency:
00 00=0x0000= 0 intervals; - Supervision timeout:
90 01=0x0190= 400 × 10 ms = 4 s.
The minimum interval is the lowest this legacy format allows (7.5 ms) and the maximum is 500 ms. It’s a wide range, so the device basically lets the phone decide.
The phone app reports the battery level, so why isn’t it exposed using the standard battery level characteristic (2a19)?
More and more things hinting that the firmware was a PoC.
Try the device name characteristic
$ A=D4:EA:38:XX:XX:XX
# Write a new name (ASCII "BLE_Gamma:TEST" in hex):
$ gatttool -t random -b $A --char-write-req -a 0x0003 -n $(printf 'BLE_Gamma:TEST' | xxd -p)
Characteristic value was written successfully
# Read it back:
$ gatttool -t random -b $A --char-read -a 0x0003 | sed 's/.*: //' | xxd -r -p; echo
BLE_Gamma:TEST
# So that was written properly.
# Let’s see if the advertised name has been updated too (remove it first to be sure to not get a cached version):
$ bluetoothctl remove $A
$ bluetoothctl --timeout 10 scan on
Discovery started
[CHG] Controller AC:45:EF:XX:XX:XX Discovering: yes
[NEW] Device D4:EA:38:XX:XX:XX BLE_Gamma:XXXX
# It hasn’t 🫤.
# Reboot the device, then re-read the value:
$ gatttool -t random -b $A --char-read -a 0x0003 | sed 's/.*: //' | xxd -r -p; echo
BLE_Gamma:XXXX
# So the write was not persisted.
# Re-check advertised name:
$ bluetoothctl --timeout 10 scan on
Discovery started
[CHG] Controller AC:45:EF:XX:XX:XX Discovering: yes
[NEW] Device D4:EA:38:XX:XX:XX BLE_Gamma:XXXX
# Same
The device name is writable but mostly ignored… a bit pointless.
It should have been marked as read-only then, but I guess that was a copy/paste from Nordic’s example.
So the iOS/Android apps probably use a vendor characteristic (that 1524 writable one).
Should we try to poke around 1524 next?