CoderFrank

Reverse engineering a Blood Pressure Monitor’s software

In this blog post, I wanted to write about my adventure of reverse engineering software that accompanied my mother’s blood pressure monitor.

Background

My grandfather passed away this year (April 2017) and mom (technically grandmother — she raised me though) started checking her blood pressure more frequently as of late. During thanksgiving 2017, I had a scare with her blood pressure readings at 196 systolic and I wasn’t sure why it was so high. I called a nurse hotline and didn’t hear back until 2 hours later. It’s 3am as he commanded me to get up and check it again. Well long story short I ended up calling 911. Paramedics arrived and they verified it wasn’t as high. Her blood pressure was at 174 systolic. Her blood pressure was high but her medication had been changed earlier in the week by her doctor. The doctor mentioned it may impact her vitals until her body grew accustomed to the new medication.

We figured it was time to purchase a new blood pressure monitor after the imprecise readings we observed. The new device was accompanied with software to download readings from the device and visualize the data over time.

After realizing software was available an idea was born. The software did as it was designed to download and visualize data. I thought I could ask my father who is currently staying with my mom to export the data and upload to a cloud object storage of some kind. Well, I then thought that wasn’t going to be possible without additional intervention and after a Google search, I discovered the WebUSB API. This web API would allow me to create a website that could communicate with the blood pressure monitor without installing additional software.

You can read about the WebUSB API at developers.google.com.

Next, I needed to know the communication protocol for the device so I could use the WebUSB API to communicate with the device. I first decided to decompile the accompanying software and understand the communication.

Decompilation

I decided to use the Hopper Disassembler to disassemble the accompanying software. Great, I have the at&t assembly and psuedo-code that is provided by Hopper. I have no idea how to continue now. I spent some time trying to read through the result, but it wasn’t very clear. I asked “why not check logs?”, but where? What about a promiscuous mode for USB devices? I Googled, and learned Wireshark does this, woah, but it only worked on Linux and Windows. So not doing that on my Macbook.

After some more time, I thought to try starting the software from my terminal. Huzzah, the software author decided to output detailed information to stdout.

2017-11-29 22:35:13.241 XXX[28423:378061] reading itemNodes count = 40
2017-11-29 22:35:13.241 XXX[28423:378061] new Mutable Array reading for ID 1001
2017-11-29 22:35:13.254 XXX[28423:378061] new Mutable Array reading for ID 1002

I truncated the information and redacted device revealing information.

Next, I searched for output on the information download step and found:

2017-11-29 22:35:42.010 XXX[28423:378061] ok: message panel
getXXXData
[data begin]
2017-11-29 22:35:44.353 XXX[28423:378082] get Data –>  no data

Great! I found getXXXData which is sounded promising. I searched the assembly from the disassembler and found the function definition.

_getBPMCycleData:
00044bd0 push ebp ; CODE XREF=+[XXXModel getXXXData:]+321
00044bd1 mov ebp, esp
00044bd3 push ebx
00044bd4 push edi
00044bd5 push esi
00044bd6 sub esp, 0x35c
00044bdc call _getXXXData+17
00044be1 pop eax ; CODE XREF=_getXXXData+12
00044be2 mov ecx, dword [ebp+0x18]
00044be5 mov edx, dword [ebp+0x14]
00044be8 mov esi, dword [ebp+0x10]
00044beb mov edi, dword [ebp+0xc]
00044bee mov ebx, dword [ebp+8]
00044bf1 mov dword [ebp-0x28c], eax

Searching, I found a _hid_write function call. After a Google search, I found the HID API a C library for communicating with USB devices. hid_write expects a few parameters, the device, a buffer with data, and the length of the data being transmitted.

00044ceb lea edx, dword [ebp-0x110]
00044cf1 xor esi, esi
00044cf3 mov edi, 0x100
00044cf8 mov ebx, edx
00044cfa mov dword [esp], ebx ; argument “b” for method imp___symbol_stub__memset
00044cfd mov dword [esp+4], 0x0 ; argument “c” for method imp___symbol_stub__memset
00044d05 mov dword [esp+8], 0x100 ; argument “len” for method imp___symbol_stub__memset
00044d0d mov dword [ebp-0x2a4], eax
00044d13 mov dword [ebp-0x2a8], ecx
00044d19 mov dword [ebp-0x2ac], edx
00044d1f mov dword [ebp-0x2b0], esi
00044d25 mov dword [ebp-0x2b4], edi
00044d2b call imp___symbol_stub__memset ; memset
00044d30 mov byte [ebp-0x110], 0x14
00044d37 mov byte [ebp-0x10f], 0x12
00044d3e mov byte [ebp-0x10e], 0x16
00044d45 mov byte [ebp-0x10d], 0x18
00044d4c mov byte [ebp-0x10c], 0x22
00044d53 mov eax, dword [ebp-0x230]
00044d59 mov dword [esp], eax ; argument #1 for method _hid_write
00044d5c mov eax, dword [ebp-0x2ac]
00044d62 mov dword [esp+4], eax ; argument #2 for method _hid_write
00044d66 mov dword [esp+8], 0x5 ; argument #3 for method _hid_write
00044d6e call _hid_write ; _hid_write

A look over at the pseudo-code gave me more insight on what is “possibly” going on, but it wasn’t able to produce pseudo-code so I found a similar function name nearby and tried looking at that. I found that before the _hid_write there exists a buffer defined with 5 bytes of data. Obviously, the pseudo-code doesn’t look like an array but I took a guess that given what the function expects as parameters it probably is an array. The values assigned to the array are probably a start sequence.

var_110 = 0x14;
var_10F = 0x12;
var_10E = 0x16;
var_10D = 0x18;
var_10C = 0x22;
eax = _hid_write(var_220, &var_110, 0x5);

This can we be found in the other function definition as well but in assembly.

00044d30 mov byte [ebp-0x110], 0x14
00044d37 mov byte [ebp-0x10f], 0x12
00044d3e mov byte [ebp-0x10e], 0x16
00044d45 mov byte [ebp-0x10d], 0x18
00044d4c mov byte [ebp-0x10c], 0x22

Looks like I’m set, great! So I move on and write some WebUSB API javascript to send the request to the device.

I GOT DATA, but not sure what it means. I split the data into three cases to attempt decoding without reading too much assembly.

Case 0: No measurement exists on the device.
Result: No data is returned.

Case 1: 1 measurement exists on the device.
Measurement 1: 164 SYS. | 106 DIA. | 69 PUL.  | 11-30-2017 | 9:33 PM
Result:
Sample 1:

0xf3,0x6,0x30,0x30,0x34,0x39,0x39,0x31,0xf7,0x30,
0x31,0x30,0x30,0x30,0x31,0x33,0xf7,0x31,0x33,0x30,
0x33,0x30,0x33,0x32,0xf7,0x30,0x30,0x30,0x30,0x30,
0x30,0x30,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf2,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf3,0x31,
0x37,0x31,0x30,0x30,0x30,0x30,0xf7,0x31,0x33,0x30,
0x32,0x31,0x32,0x36

Sample 2:

0xf3,0x6,0x30,0x30,0x31,0x41,0x38,0x41,0xf7,0x30,0x31,
0x30,0x30,0x30,0x31,0x33,0xf7,0x31,0x33,0x30,0x33,0x30,
0x33,0x32,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf7,
0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf2,0x30,0x30,0x30,
0x30,0x30,0x30,0x30,0xf7,0x31,0x37,0x31,0x31,0x33,0x30,
0x32,0xf7,0x31,0x32,0x36,0x30,0x30,0x30,0x30

Sample 3:

0xf3,0x6,0x30,0x30,0x34,0x39,0x39,0x31,
0xf7,0x30,0x31,0x30,0x30,0x30,0x31,0x33,
0xf7,0x31,0x33,0x30,0x33,0x30,0x33,0x32,
0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf4,0x30,0x30,0x31,0x37,0x30,0x30,0x30,
0xf7,0x31,0x31,0x33,0x30,0x32,0x31,0x32,
0xf7,0x36,0x30,0x30,0x30,0x30,0x30,0x30

0x30,0x30,0x30,0x31,0x30,0x30,0x30,0x31,
0x33,0x31,0x33,0x30,0x33,0x30,0x33,0x32,
0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0x31,0x37,0x31,0x31,0x33,0x30,0x32,0x31,0x32,
0x36,
0x30,0x30,0x30,0x30,0x30,0x30

Case 2: 2 measurements exist on the device.
Measurement 1: 164 SYS. | 106 DIA. | 69 PUL.  | 11-30-2017 | 9:33 PM
Measurement 2: 153 SYS. | 98 DIA. | 74 PUL. | 11-30-2017 | 9:35 PM
Result:
Sample 1:

0xf3,0x6,0x30,0x30,0x0,0x0,0xff,0x0,
0xf7,0x30,0x32,0x30,0x30,0x30,0x31,0x33,
0xf7,0x31,0x33,0x30,0x33,0x30,0x33,0x32,
0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf6,0x30,0x30,0x31,0x37,0x31,0x31,0x30,
0xf7,0x33,0x30,0x32,0x31,0x32,0x36,0x30,
0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x34,
0xf7,0x35,0x31,0x41,0x38,0x41,0x34,0x30,
0xf7,0x34,0x35,0x31,0x41,0x38,0x41,0x34,
0xf5,0x31,0x37,0x31,0x31,0x33,0x41,0x34,
0xf7,0x30,0x32,0x31,0x33,0x35,0x30,0x30,
0xf7,0x30,0x30,0x30,0x30,0x30,0x34,0x41,
0xf7,0x31,0x38,0x38,0x39,0x39,0x30,0x34,
0xf7,0x41,0x31,0x38,0x38,0x39,0x39,0x32,
0xf1,0x32,0x31,0x38,0x38,0x39,0x39,0x32

Extracted Data:
0x30,0x30,0x30,0x30,0x32,0x30,0x30,0x30,0x31,0x33,
0x31,0x33,0x30,0x33,0x30,0x33,0x32,
0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0x30,0x30,0x31,0x37,0x31,0x31
0x33,0x30,0x32,0x31,0x32,0x36,0x30,
0x30,0x30,0x30,0x30,0x30,0x30,0x34,
0x35,0x31,0x41,0x38,0x41,0x34,0x30,
0x34,0x35,0x31,0x41,0x38,0x41,0x34,
0x31,0x37,0x31,0x31,0x33
0x30,0x32,0x31,0x33,0x35,0x30,0x30,
0x30,0x30,0x30,0x30,0x30,0x34,0x41,
0x31,0x38,0x38,0x39,0x39,0x30,0x34,
0x41,0x31,0x38,0x38,0x39,0x39,0x32,
0x32

Sample 2:

0xf3,0x6,0x30,0x30,0x38,0x39,0x39,0x32,0xf7,0x30,0x32,
0x30,0x30,0x30,0x31,0x33,0xf7,0x31,0x33,0x30,0x33,0x30,
0x33,0x32,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf7,
0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf2,0x30,0x30,0x30,
0x30,0x30,0x30,0x30,0xf1,0x31,0x30,0x30,0x30,0x30,0x30,
0x30,0xf7,0x37,0x31,0x31,0x33,0x30,0x32,0x31,0xf7,0x32,0x36,
0x30,0x30,0x30,0x30,0x30,0xf7,0x30,0x30,0x34,0x35,0x31,
0x41,0x38,0xf7,0x41,0x34,0x30,0x34,0x35,0x31,0x41,0xf3,0x38,
0x41,0x34,0x34,0x35,0x31,0x41,0xf2,0x31,0x37,0x34,0x34,0x35,
0x31,0x41,0xf7,0x31,0x31,0x33,0x30,0x32,0x31,0x33,0xf7,0x35,
0x30,0x30,0x30,0x30,0x30,0x30,0xf7,0x30,0x34,0x41,0x31,0x38,
0x38,0x39

Sample 3:

0xf3,0x6,0x30,0x30,0x32,0x31,0x38,0x38,0xf7,0x30,0x32,
0x30,0x30,0x30,0x31,0x33,0xf7,0x31,0x33,0x30,0x33,0x30,
0x33,0x32,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf7,
0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xf2,0x30,0x30,0x30,
0x30,0x30,0x30,0x30,0xf5,0x31,0x37,0x31,0x31,0x33,0x30,
0x30,0xf7,0x30,0x32,0x31,0x32,0x36,0x30,0x30,0xf7,0x30,
0x30,0x30,0x30,0x30,0x34,0x35,0xf7,0x31,0x41,0x38,0x41,
0x34,0x30,0x34,0xf6,0x35,0x31,0x41,0x38,0x41,0x34,0x34,
0xf7,0x31,0x37,0x31,0x31,0x33,0x30,0x32,0xf7,0x31,0x33,
0x35,0x30,0x30,0x30,0x30,0xf7,0x30,0x30,0x30,0x34,0x41,
0x31,0x38,0xf7,0x38,0x39,0x39,0x30,0x34,0x41,0x31,0xf6,
0x38,0x38,0x39,0x39,0x32,0x32,0x31

 

Written payload — 0x26 (getDateTime)

Sample 1:

0xf3,0x6,0x31,0x31,0x30,0x30,0xd,0xa,0xf7,0x33,0x30,0x31,
0x37,0x32,0x30,0x32,0xf7,0x31,0x35,0x35,0x35,0x34,0x30,
0x30,0xf7,0x30,0x20,0x30,0x31,0x31,0x38,0x30,0xf7,0x33,
0x30,0x31,0x30,0x30,0x30,0x37,0xf7,0x30,0x30,0x30,0x30,
0x30,0x30,0x30,0xf7,0x30,0x30,0x30,0x30,0x30,0x46,0x46,
0xf7,0x33,0x32,0x39,0x36,0x46,0x30,0x31

Sample 2:

0xf3,0x6,0x31,0x31,0x30,0x30,0x39,0x35,0xf7,0x33,0x30,0x31,
0x37,0x32,0x30,0x32,0xf7,0x31,0x35,0x37,0x30,0x32,0x30,0x30,
0xf7,0x30,0x20,0x30,0x31,0x31,0x38,0x30,0xf7,0x33,0x30,0x31,
0x30,0x30,0x30,0x37,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf7,0x30,0x30,0x30,0x30,0x30,0x46,0x46,0xf7,0x33,0x32,0x39,
0x36,0x46,0x30,0x31

Sample 3:

0xf3,0x6,0x31,0x31,0x30,0x30,0x39,0x30,0xf7,0x33,0x30,0x31,
0x37,0x32,0x30,0x32,0xf7,0x31,0x35,0x37,0x32,0x39,0x30,0x30,
0xf7,0x30,0x20,0x30,0x31,0x31,0x38,0x30,0xf7,0x33,0x30,0x31,
0x30,0x30,0x30,0x37,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x30,
0xf7,0x30,0x30,0x30,0x30,0x30,0x46,0x46,0xf7,0x33,0x32,0x39,
0x36,0x46,0x30,0x31

Data retrieval requests function call:
_getBPMCycleData: – 0x14, 0x12, 0x16, 0x18, 0x22
_getBPMCycleDataCount: – 0x14, 0x12, 0x16, 0x18, 0x22
_getBPMID: – 0x14, 0x12, 0x16, 0x18, 0x24
_getBPMDate: – 0x14, 0x12, 0x16, 0x18, 0x26
_getBPMVersion: – 0x14, 0x12, 0x16, 0x18, 0x3e

Data transmission function call:
_sendID: – 0x14, 0x12, 0x16, 0x18, 0x23
_setBPMDate: – 0x14, 0x12, 0x16, 0x18, 0x27

How do I know that the data is accurate? The disassembled code has a checksum and I need to port over to Javascript below is the function:

// stack[2049] = arg1;
// stack[2048] = arg0;
// var_14 = arg0;
// var_18 = arg1;
// var_1C = 0x0;
// var_20 = 0x0;
// var_2C = eax;
// if (var_18 < 0x2) {
// eax = var_2C;
// eax = printf(“checkSum error : data length error\n”);
// var_D = 0x0;
// var_30 = eax;
// }
// else {
// for (var_20 = 0x0; var_20 < var_18 – 0x2; var_20 = var_20 + 0x1) {
// var_1C = (*(int8_t *)(var_14 + var_20) & 0xff) + var_1C;
// }
// var_1C = var_1C & 0xffff;
// if (var_1C <= 0xffff) {
// edx = var_2C;
// var_34 = 0x0;
// var_38 = 0x5;
// var_3C = __sprintf_chk(&var_25, 0x0, 0x5, “%04X”, var_1C);
// var_41 = LOBYTE(0x0);
// if (sign_extend_32(var_23) == (*(int8_t *)(var_14 + (var_18 – 0x2)) & 0xff)) {
// var_41 = LOBYTE(sign_extend_32(var_22) == (*(int8_t *)(var_14 + (var_18 – 0x1)) & 0xff) ? 0x1 : 0x0);
// }
// var_26 = LOBYTE(LOBYTE(LOBYTE(var_41) & 0x1) & 0xff);
// if (var_26 == 0x0) {
// eax = var_2C;
// var_48 = printf(“checkSum error res2:%c res1:%c daat2:%c data1:%c\n”, sign_extend_32(var_23), *(int8_t *)(var_14 + (var_18 – 0x2)) & 0xff, sign_extend_32(var_22), *(int8_t *)(var_14 + (var_18 – 0x1)) & 0xff);
// }
// var_D = LOBYTE(var_26);
// }
// else {
// eax = var_2C;
// eax = printf(“checkSum error exceed range\n”);
// var_D = 0x0;
// var_40 = eax;
// }
// }
// eax = var_D & 0xff;
// esi = stack[2044];
// edi = stack[2045];
// ebx = stack[2046];
// esp = esp + 0x6c;
// ebp = stack[2047];
// return eax;

Payload decoding

Well, long story short I wasn’t successful at reversing the checksum. So I tried a different strategy by using a debugger to attach the process and view breakpoints before and after a call. I focused on the smallest piece, a patient ID.

Decoding the Patient ID.

Request: 0x14, 0x12, 0x16, 0x18, 0x24
Debugger Response: 33 31 33 30 33 30 33 32 30 30 30 30 30 30 30 30 30 30 30 30 30 30 00 30
Debugger Output: 31 30 30 32 00 00 00 00 (1002 in ascii) expected output after stepping through with multiple breakpoints.

Expected length matters for Patient ID: Used 24 bytes and received two chunks. Can get away with 48 bytes but didn’t want to spend too much time trying to figure that. Trying to figure out what section actually matters in the response for the Patient ID.

Raw-response: 

,0xf3,0x6,0x33,0x31,0x30,0x30,0xd,0xa,0xf7,0x33,0x30,0x33,
0x30,0x33,0x32,0x30,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,
0x30,0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x2c,0xf7,0x30,
0x35,0x2e,0x30,0x30,0xd,0xa,0xf2,0x36,0x35,0x2e,0x30,0x30,
0xd,0xa

Split to align with blood pressure monitor software data found in the debugger:

??>>,0xf3,0x6,
IN software data>>0x33,0x31.
??>>,0x30,0x30,0xd,0xa,0xf7,
IN software data>>0x33,0x30,0x33,0x30,0x33,0x32,0x30,
??>>0xf7,
IN software data>>0x30,0x30,0x30,0x30,0x30,0x30,0x30, << I’m unsure if this belongs in the buffer or a following section?
??>>0xf7,0x30,0x30,0x30,0x30,0x30,0x30,0x2c,0xf7,0x30,0x35,0x2e,
??>>0x30,0x30,0xd,0xa,0xf2,0x36,0x35,0x2e,0x30,0x30,0xd,0xa

I noticed after writing the parsed section above using the raw response that the ID is determined by the following character after a 0x30. For example:

33 31 33 30 33 30 33 32 30 30 30 30 30 30 30 30 30 30 30 30 30 30 00 30

Turns into:

31 30 30 32 == 1002 (ignoring all the 30’s)

Trying a different ID 100002

Sample 1:

0xf3 0x6 0x33 0x31 0x30 0x30 0x39 0x46
0xf7 0x33 0x30 0x33 0x30 0x33 0x30 0x33
0xf7 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0xf7 0x30 0x30 0x30 0x30 0x30 0x30 0x2c
0xf7 0x30 0x35 0x2e 0x30 0x30 0xd 0xa
0xf2 0x36 0x42 0x2e 0x30 0x30 0xd 0xa

Sample 2:

0xf3 0x6 0x33 0x31 0x30 0x30 0x39 0x39
0xf7 0x33 0x30 0x33 0x30 0x33 0x30 0x33
0xf7 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0xf7 0x30 0x30 0x30 0x30 0x30 0x30 0x2c
0xf7 0x30 0x35 0x2e 0x30 0x30 0xd 0xa
0xf2 0x36 0x42 0x2e 0x30 0x30 0xd 0xa

Trying to figure out the message decoding procedure for Patient ID:

0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x2c 0x30
0x35 0x2e 0x30 0x30 0xd 0x0a 0x36 0x42

My Version based on the assumption that 0xf7 determines the length of real bytes: 0xf7 -> 0x7 bytes

0x6  <- pesky byte not sure what this is actually for? maybe states the beginning of a new message? It does.
0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x2c  0x30
0x35 0x2e 0x30 0x30 0xd 0xa 0x36 0x42

0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x2c 0x30
0x35 0x2e 0x30 0x30 0xd 0xa 0x36 0x42

The device transmits data in 8-byte chunks and I can decode the data in the 8-byte chunks by using the first byte. The first byte determines the length of actual payload data in the next 7 bytes.

Next device data:

115 Sys, 73 DIA, 125 PUL | 12-03 7:39PM
ByteLength: 66

0x30 0x30 0x30 0x31 0x30 0x30 0x30 0x31
0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30
0x31 0x37 0x31 0x32 0x30 0x33 0x31 0x39
0x33 0x39 0x0 0x30 0x30 0x30 0x30 0x30
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x36 0x33

0x37 0x44 0x31 0x32 0x34
0x37 0x33 0x30
0x37 0x44 0x31 0x32 0x34
0x37 0x33 0x36 0x33

2 Measurements:

Byte Length: 98

0x30 0x30 0x30 0x32 0x30 0x30 0x30 0x31
0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30
0x31 0x37 0x31 0x32 0x30 0x33 0x31 0x39
0x33 0x39 0x0 0x30 0x30 0x30 0x30 0x30
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x31 0x37 0x31 0x32 0x30 0x35 0x30 0x38
0x31 0x31 0x30 0x30 0x30 0x30 0x30 0x30
0x30 0x34 0x45 0x31 0x41 0x30 0x38 0x46
0x30 0x34 0x45 0x31 0x41 0x30 0x38 0x46
0x31 0x30

3 Measurements:

Byte Length: 130

0x30 0x30 0x30 0x33 0x30 0x30 0x30 0x31
0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30
0x31 0x37 0x31 0x32 0x30 0x33 0x31 0x39
0x33 0x39 0x0 0x30 0x30 0x30 0x30 0x30
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x31 0x37 0x31 0x32 0x30 0x35 0x30 0x38
0x31 0x31 0x30 0x30 0x30 0x30 0x30 0x30
0x30 0x34 0x45 0x31 0x41 0x30 0x38 0x46
0x30 0x34 0x45 0x31 0x41 0x30 0x38 0x46
0x31 0x37 0x31 0x32 0x30 0x35 0x30 0x38
0x31 0x38 0x30 0x30 0x30 0x30 0x30 0x30
0x30 0x34 0x34 0x31 0x37 0x43 0x39 0x41
0x30 0x34 0x34 0x31 0x37 0x43 0x39 0x41
0x41 0x43

Byte Length: 162

0x30 0x30 0x30 0x34 0x30 0x30 0x30 0x31
0x33 0x31 0x33 0x30 0x33 0x30 0x33 0x30
0x33 0x30 0x33 0x32 0x30 0x30 0x30 0x30
0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30
0x31 0x37 0x31 0x32 0x30 0x33 0x31 0x39
0x33 0x39 0x0 0x30 0x30 0x30 0x30 0x30
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x30 0x37 0x44 0x31 0x32 0x34 0x37 0x33
0x31 0x37 0x31 0x32 0x30 0x35 0x30 0x38
0x31 0x31 0x30 0x30 0x30 0x30 0x30 0x30
0x30 0x34 0x45 0x31 0x41 0x30 0x38 0x46
0x30 0x34 0x45 0x31 0x41 0x30 0x38 0x46
0x31 0x37 0x31 0x32 0x30 0x35 0x30 0x38
0x31 0x38 0x30 0x30 0x30 0x30 0x30 0x30
0x30 0x34 0x34 0x31 0x37 0x43 0x39 0x41
0x30 0x34 0x34 0x31 0x37 0x43 0x39 0x41
0x31 0x37 0x31 0x32 0x30 0x35 0x30 0x38
0x32 0x30 0x30 0x30 0x30 0x30 0x30 0x30
0x30 0x34 0x36 0x31 0x37 0x34 0x39 0x42
0x30 0x34 0x36 0x31 0x37 0x34 0x39 0x42
0x32 0x39

 

After trying to decode data from the CycleData payload, I accidentally found the expected information. Here’s an example:

7D 1247307D12473
125 Pulse |73 Diastolic |115 Systolic|
0
4E 1A0 8F 04E1A08F
78 Pulse | 104 Diastolic | 143 Systolic |
0
44 17C 9A 0 4417C9A
68 Pulse |95 Diastolic |160 Systolic
0
46 174 9B 0461749B29
70 Pulse | 93 Diastolic | 155 Systolic |

Success

Success reverse engineering the bpm.

 

I was successful in retrieving data from the Blood Pressure Monitor using the Web USB API and a lot of time with trial and error. The project code can be found on my Github.