BT785: Difference between revisions

From RevSpace
Jump to navigation Jump to search
(6 intermediate revisions by the same user not shown)
Line 68: Line 68:
The very first step is probably upping the MTU, since by default GATT only allows very small blocks of data to be exchanged.
The very first step is probably upping the MTU, since by default GATT only allows very small blocks of data to be exchanged.


==== Request ====
The phone sends a device info request:
The phone sends a device info request:
Encrypted, this looks like
Encrypted, this looks like
Line 75: Line 76:
8b77f23fb840607c58d0beb91c463a98
8b77f23fb840607c58d0beb91c463a98
</pre>
</pre>
Fields:
* header: 0x21 data bytes, sequence 0x20?, security flag 0x04
* iv: 55304552fd7810cdb72cced8831dc514
* encrypted: 8b77f23fb840607c58d0beb91c463a98
This decrypts to:
<pre>
00000001 00000000 0000 0002 00f3 784e
</pre>
Which decodes to:
* [0-3]: cmd sn = 1
* [4-7]: ack sn = 0
* [8-9]: code = 0 = device info request?
* [A-B]: length = 2
* [C-D]: data = 0x00f3 = ???
* [E-F]: crc = 0x784e
==== Response ====


The device responds with:
The device responds with:
Line 97: Line 117:
The decoded response is:
The decoded response is:
<pre>
<pre>
00000001000000010000006001020402
000: 00000001000000010000006001020402
1001c4984a2a590f0100c14161b10880
010: 1001c4984a2a590f0100c14161b10880
5adba92a221eb39d6e2f000000000000
020: 5adba92a221eb39d6e2f000000000000
00000000000000000000000102000100
030: 00000000000000000000000102000100
0001006266366138346d66796b756262
040: 0001006266366138346d66796b756262
696a3300000000000000000000000000
050: 696a3300000000000000000000000000
0000000000e76d694f23dc00755d0202
060: 0000000000e76d694f23dc00755d0202
</pre>
</pre>


What we get from this:
What we get from this:
* offset 0x00: 00000001 = sn
* offset 0x04: 00000001 = sn_ack
* offset 0x08: 0000 = code (device_info)
* offset 0x0A: 0006 = length
* offset 0x0C, 0102 means: device version 1.2
* offset 0x0C, 0102 means: device version 1.2
* offset 0x0E, 0402 means: protocol version 4.2
* offset 0x0E, 0402 means: protocol version 4.2
Line 112: Line 136:
* offset 0x11, 01: is_bind  
* offset 0x11, 01: is_bind  
* offset 0x12, we find the srand key = 0xc4984a2a590f
* offset 0x12, we find the srand key = 0xc4984a2a590f
{| class="wikitable"
! Offset (hex) !! Size (bytes) !! Hex (example) !! Field name !! Meaning / comment
|-
| 0x00 || 4 || 00 00 00 01 || sn || Sequence number = 1
|-
| 0x04 || 4 || 00 00 00 01 || sn_ack || Acknowledged SN = 1
|-
| 0x08 || 2 || 00 00 || code || 0x0000 = device_info response
|-
| 0x0A || 2 || 00 60 || length || 0x0060 = 96 bytes payload
|-
| 0x0C || 1 || 01 || protocol_major || Protocol major = 1
|-
| 0x0D || 1 || 02 || protocol_minor || Protocol minor = 2
|-
| 0x0E || 1 || 04 || security_flag || 4 = AES using login-key MD5
|-
| 0x0F || 1 || 02 || capability_flags || Device capability / flags (vendor)
|-
| 0x12 || 6 || c4 98 4a 2a 59 0f || srand || **6-byte session random** (used to derive session key)
|-
| 0x18 || 32 || 01 00 c1 41 61 b1 08 80 5a db a9 2a 22 1e b3 9d 6e 2f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 || auth/device key material || 32 bytes — ???
|-
| 0x43 || 16 || 62 66 36 61 38 34 6d 66 79 6b 75 62 62 69 6a 33 || device_id (ASCII) || "bf6a84mfykubbij3" followed by padding
|-
| 0x75 || 6 || e7 6d 69 4f 23 dc || reversed_mac || Reversed BLE MAC bytes
|}


=== Measurement reports ===
=== Measurement reports ===

Revision as of 17:19, 12 October 2025

Project BT785
Connecting a BT785 EC meter to raspberry pi
Status In progress
Contact bertrik
Last Update 2025-10-12

Introduction

This page is about reverse engineering the BT-785, an inexpensive Tuya-compatible water electrical conductivity (EC) meter. It measures EC and temperature, and has a display and communicates its reading over Bluetooth Low-Energy (BLE) to the Tuya app on a phone. The goal is to understand the communication protocol so we can communicate with it from the raspberry pi in the Karaburan project.

Analysis

BLE characteristics in NRF-connect

On the lowest level, it uses bluetooth low-level with the GATT profile on the right:

Most important seem two characteristics:

  • 00000002-0000-1001-8001-00805f9b07d0: module to phone, notifies of data from the EC meter
  • 00000001-0000-1001-8001-00805f9b07d0: phone to module, we write data to the EC meter

The device connects to the Tuya app on a phone. Data transfer is encrypted.

While the tuya app is running, we can see notifications coming in, for example:

00 31 46 05 36 46 B1 F5 36 55 B2 56 34 E1 B3 C9
13 33 0A D9 17 E8 FE 4F 5C 9A 70 CE DB 26 A6 17
22 CC DF 65 7D F7 0D 01 02 0A 33 45 B0 C1 A5 A8
42 F5 C1 B2

Composition of the data:

  • 0x31 is the length of the data
  • 0x46 probably some sequence number
  • 0x05 the "security flag" indicating the type of encryption
  • 36 46 B1 F5 36 55 B2 56 34 E1 B3 C9 13 33 0A D9: 16 bytes of initial vector
  • rest: encrypted data, padded to 16 bytes unit

Another example:

00-31-40-05-8A-8B-20-8F-95-46-06-C0-CF-B2-9C-98
45-F8-E5-0F-8F-43-86-79-CA-1A-11-4B-30-F5-CB-1C
3F-93-3C-41-CE-27-DD-23-82-8A-2A-95-AE-71-4E-BF
DC-50-F2-32

HCI snoop log

...

Protocol

The protocol has the following steps:

  • Pairing: Initially, when the EC meter is paired with the Tuya app, a key is generated. This key is stored inside the meter and stored in the app. You can register with the Tuya developer console, link your device to the console, and fetch this key. Example key: '<z)o}Ezmuw01.TxQ'. Let's call this the 'local key'.
  • Session start When the EC meter is powered on and communication starts, the session is started by the host sending a 'device info request'. The response contains a session key. This exchange is secured using part of the 'local key' (only 6 bytes!?).
  • Measurement, the device sends measurements as encrypted notifications.

The encrypted frames typically do no fit in basic GATT frames. A GATT notification can typically hold only about 23 bytes of data.

An encrypted frame contains of a 5-byte header, a 16-byte IV, so basically no room left for the actual data.

I see in the app-device communication that the MTU is increased before session start:

BLE MTU negotiation

Pairing

I plan to not investigate this, just let the device and the app figure this out, then fetch the local key from the API

Session start

The very first step is probably upping the MTU, since by default GATT only allows very small blocks of data to be exchanged.

Request

The phone sends a device info request: Encrypted, this looks like

00212004
55304552fd7810cdb72cced8831dc514
8b77f23fb840607c58d0beb91c463a98

Fields:

  • header: 0x21 data bytes, sequence 0x20?, security flag 0x04
  • iv: 55304552fd7810cdb72cced8831dc514
  • encrypted: 8b77f23fb840607c58d0beb91c463a98

This decrypts to:

00000001 00000000 0000 0002 00f3 784e

Which decodes to:

  • [0-3]: cmd sn = 1
  • [4-7]: ack sn = 0
  • [8-9]: code = 0 = device info request?
  • [A-B]: length = 2
  • [C-D]: data = 0x00f3 = ???
  • [E-F]: crc = 0x784e

Response

The device responds with:

0081014004
65db80c3df1cde3aebe8bf8ceee18b39
f2be5ee93a59f09e0c65af2963440497
394317d56482a56e2ed32d05c727585b
980b73b3266e782acb9a7705233c5807
444cacccef3daa3e1eec12c271069a42
9ed448d8312d33212f0a886abaa5cecb
ed8ca761eea1daaef97cd5fc9033fb09
5f08353bf0ae6a7f231ee94df6100ce8

This can be decoded as follows:

  • header says this is a block of 0x81 bytes, sequency number 0x40, with security flag 0x04
  • determine iv = first 16 bytes after the header = 65db80c3df1cde3aebe8bf8ceee18b39
  • calculate key = md5(first 6 bytes of local key) = md5("<z)o}E") = 64ba62ad750c312d08e10aa343769fbf
  • decode remaining bytes using AES in CBC mode with the iv and key just determined

The decoded response is:

000: 00000001000000010000006001020402
010: 1001c4984a2a590f0100c14161b10880
020: 5adba92a221eb39d6e2f000000000000
030: 00000000000000000000000102000100
040: 0001006266366138346d66796b756262
050: 696a3300000000000000000000000000
060: 0000000000e76d694f23dc00755d0202

What we get from this:

  • offset 0x00: 00000001 = sn
  • offset 0x04: 00000001 = sn_ack
  • offset 0x08: 0000 = code (device_info)
  • offset 0x0A: 0006 = length
  • offset 0x0C, 0102 means: device version 1.2
  • offset 0x0E, 0402 means: protocol version 4.2
  • offset 0x10, 10: flag
  • offset 0x11, 01: is_bind
  • offset 0x12, we find the srand key = 0xc4984a2a590f
Offset (hex) Size (bytes) Hex (example) Field name Meaning / comment
0x00 4 00 00 00 01 sn Sequence number = 1
0x04 4 00 00 00 01 sn_ack Acknowledged SN = 1
0x08 2 00 00 code 0x0000 = device_info response
0x0A 2 00 60 length 0x0060 = 96 bytes payload
0x0C 1 01 protocol_major Protocol major = 1
0x0D 1 02 protocol_minor Protocol minor = 2
0x0E 1 04 security_flag 4 = AES using login-key MD5
0x0F 1 02 capability_flags Device capability / flags (vendor)
0x12 6 c4 98 4a 2a 59 0f srand **6-byte session random** (used to derive session key)
0x18 32 01 00 c1 41 61 b1 08 80 5a db a9 2a 22 1e b3 9d 6e 2f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 auth/device key material 32 bytes — ???
0x43 16 62 66 36 61 38 34 6d 66 79 6b 75 62 62 69 6a 33 device_id (ASCII) "bf6a84mfykubbij3" followed by padding
0x75 6 e7 6d 69 4f 23 dc reversed_mac Reversed BLE MAC bytes

Measurement reports

Measurements are sent on the notification characteristic.

example 1

Example data:

00314705
09a25c5146e55def691d26bbf80cddc6
7375b68f9c835408df05a2c2301f5a7f
671f46c3be8bb16e3500cda30b1571b7

This can be decoded as follows:

  • header: 0x31 bytes, sequence 0x47, decryption type 0x05
  • iv = first 16 bytes after header
  • key = md5(first 6 bytes(local key)) + srand)
  • decode using AES mode CBC,

Decrypted data:

0000000800000000000a00030000008e
a10f0f0f0f0f0f0f0f0f0f0f0f0f0f0f

Looks like this is padded with 0x0f somehow?

example 2

Data:

00314805
1d390b3225723a56c67c88ec8b69e788
b34aa66917000fe4cc5d92f01d308800
d160ae0e59175d83f88cfcad83deb8c4

decrypts to:

00000009000000008006000f00f00000
0080006f02000400000147e773030303