This article is part of a series where I attempt to reverse engineer the protocol used by PayRange BluKey laundry devices. In this first part, I perform some preliminary investigations using Bluetooth LE advertisements and characteristics in order to remotely read machine identity and state.
For Educational And Informational Purposes Only. All information presented in this article, including linked data, source code, and materials, is provided for educational and informational purposes only. The information provided should not be used for any unlawful purpose or employed in order to circumvent payment systems. Use at your own risk: the author assumes no liability for any claim or damages arising from or in connection with the provided information. All analysis performed in the course of this article involves publicly-accessible information and the interpretation of unencrypted data sent through public radio channels. No security systems or techniques were circumvented.
Introduction
I recently moved into an apartment building that uses a mobile app rather than coins to operate the laundry machines. I was curious about how the system works, so I decided to attempt to reverse engineer the protocol and build my own laundry monitoring / control system.
Posted signage indicated that I should install an app called PayRange. After doing so, I was instructed to create an account. Interestingly, I was not asked to specify a property name or street address, nor provide any kind of sign-up code. This implied that the app locates and controls laundry machines using a local wireless connection rather than solely via the Internet.
After a brief scanning period, the app displayed six numbered items corresponding to the six laundry machines. Each item displayed the machine’s state as well, indicating whether it was idle or in use. On selecting an item, an interface appeared with several payment amounts; confirming payment caused the corresponding machine to increase its payment counter in 25¢ increments until the final value was reached and the cleaning cycle could be started.
Given that the app can read the status of several machines at once and does not require any sort of pairing procedure, I strongly suspected it communicates via Bluetooth LE. I theorized it would accomplish this using publicly-advertised addresses and over an unencrypted channel.
Peripheral Scanning
Performing a quick Bluetooth scan immediately revealed the presence of several nearby BLE peripherals with the “PayRange” name, confirming the first part of my theory.
I wrote a simple Python script (available in Appendix A) to scan for BLE peripherals using the bluepy library, and captured the results of running the following command:
payrange scan -p PayRange > machines.json
(Note that the complete MAC addresses have been redacted for privacy. WW was skipped to avoid conflation with a washing machine.)
Address
Name
Type
7C:79:E8:TT:TT:TT
PayRange
LE Only, Legacy Advertising
7C:79:E8:UU:UU:UU
PayRange
LE Only, Legacy Advertising
7C:79:E8:VV:VV:VV
PayRange
LE Only, Legacy Advertising
7C:79:E8:XX:XX:XX
PayRange
LE Only, Legacy Advertising
7C:79:E8:YY:YY:YY
PayRange
LE Only, Legacy Advertising
7C:79:E8:ZZ:ZZ:ZZ
PayRange
LE Only, Legacy Advertising
I located six peripherals in total, corresponding to the three washing machines and three dryers. Unfortunately, all six BLE peripherals share the same name. This meant that I was unable to associate an address with a specific machine. Furthermore, I was unable to discern a pattern in the addresses that might correspond to each machine type.
Bluetooth LE Characteristics
I then expanded my Python script to enumerate the Bluetooth LE characteristics available on each peripheral, and captured the results of running the following command for each address:
payrange findchars [ADDR] > characteristics.json
By comparing the results for each address, I determined that all peripherals exposed the same set of BLE characteristics.
I hoped that the characteristics would encode the machine’s identity or state (e.g. idle, in use, complete), providing a way to associate the MAC addresses to individual machines. With this in mind, I added a command to capture the values of all readable characteristics from all peripherals:
payrange readchars machines.json characteristics.json > snapshot.json
Perplexingly, all readable characteristic values were the same across all six peripherals, except the “System ID” (which encodes the MAC address in a legacy format described by the BLE GATT specification).
Characteristic
Properties
Read Value
Appearance
Read
Generic/Unknown
Device Name
Read
“BluKey”
Firmware Revision String
Read
“5”
Manufacturer Name String
Read
“PayRange”
Peripheral Preferred
Connection ParametersRead
Interval: 10ms-20ms, Latency: 0,
Timeout: 4000ms
Service Changed
Read, Indicate
0x0500FFFF
Software Revision String
Read
“Jul 26 2017”
System ID
Read
0xXXXXXXFEFFE8797C
00001011-d102-11e1-9b23-00025b00a5a5
Read
0x07
00001013-d102-11e1-9b23-00025b00a5a5
Read, Write
0x01
00001014-d102-11e1-9b23-00025b00a5a5
Read, Notify
“”
00001018-d102-11e1-9b23-00025b00a5a5
Write
n/a
0a1934f5-24b8-4f13-9842-37bb167c6aff
Read, Write, Notify,
Write-No-Response0x00
18cda784-4bd3-4370-85bb-bfed91ec86af
Read, Notify
0x0000000000000000000000000000000000000000
99564a02-dc01-4d3c-b04e-3bb1ef0571b2
Read
0x01000218001A001C001D001F0021002200
a87988b9-694c-479c-900e-95dfa6c00a24
Read, Write, Notify,
Write-No-Response0x01
bf03260c-7205-4c25-af43-93b1c299d159
Write, Notify,
Write-No-Responsen/a
fdd6b4d3-046d-4330-bdec-1fd0c90cb43b
Read, Notify
0x01
I took multiple snapshots for each characteristic on all peripherals across various states:
- All machines idle
- Washer paid for but not started
- Washer started
- Washer completed
- Dryer paid for but not started
- Dryer started
- Dryer completed
Unfortunately, all readable characteristics reported the same values across each state. This implies that the machine state is not reported by any of these characteristics.
It is possible that machine identifiers / states are only reported after a write
operation; notably, characteristics 00001013-d102-11e1-9b23-00025b00a5a5
,
a1934f5-24b8-4f13-9842-37bb167c6aff
, a87988b9-694c-479c-900e-95dfa6c00a24
,
and bf03260c-7205-4c25-af43-93b1c299d159
all expose both read and write properties.
Unfortunately, without a way to sniff BLE packets sent by the PayRange app, I had
no way to test this hypothesis.
PayRange App
I decided to take a break and check the PayRange website. I located some operator manuals, one of which revealed that the app has a hidden function: after double-tapping the “Special Offers” header with two fingers, a “Tech Info” box appeared, containing a Device ID and firmware version:
(As before, the complete IDs have been redacted for privacy.)
Machine
Device ID
Firmware Version
W1
10GGGGGG
32
W2
10HHHHHH
32
W3
10IIIIII
32
D4
10JJJJJJ
32
D5
10KKKKKK
32
D6
10LLLLLL
32
I hoped these IDs would correspond in some way to the MAC addresses, perhaps matching directly to the three differing bytes; unfortunately, I was unable to find any pattern connecting the two values.
RSSI Distance Estimation
At this point, I realized I was taking too complex of an approach while trying to associate each machine with a MAC address. I added a command to record advertisement RSSI for each address, placed my computer on top of each machine, and placed the average RSSI into a matrix:
payrange rssi machines.json > rssi.json
RSSI at Position
W1
W2
W3
D4
D5
D6
7C:79:E8:TT:TT:TT
-58.7
-69.2
-68.6
-75.0
-75.0
-74.0
7C:79:E8:UU:UU:UU
-64.2
-61.4
-57.3
-78.5
-80.0
-73.7
7C:79:E8:VV:VV:VV
-68.3
-65.2
-67.4
-62.0
-68.7
-65.0
7C:79:E8:XX:XX:XX
-65.0
-57.9
-69.2
-75.5
-68.0
-78.2
7C:79:E8:YY:YY:YY
-66.7
-69.6
-69.2
-58.0
-57.0
-67.4
7C:79:E8:ZZ:ZZ:ZZ
-72.3
-74.0
-69.9
-67.0
-62.2
-60.8
For each machine location, the table entry corresponding to the MAC address with the highest RSSI is bolded. This provided a “best guess” association. However, the small sample size and imprecision of RSSIs prevent me from drawing any definite conclusions.
A more robust version of this test could involve calculation of confidence intervals around each possible permutation, determined using propagation of uncertainty. I may revisit this method in a future article.
Advertisements Revisited
I later realized that the BLE advertisement packets sent by each peripheral contain more information than my script was capturing: some packets (but not all) include manufacturer specific data. I added a new command to the Python script and captured some values:
payrange mfgdata -p Payrange > mfg.json
7C:79:E8:TT:TT:TT
7C:79:E8:ZZ:ZZ:ZZ
0x8500FFGGGG9G00010800197A1F1801
0x8500FFLLLL9L00010800664451F601
0x8500FFGGGG9G00010800CFE9582801
0x8500FFLLLL9L00010800AEA5927501
0x8500FFGGGG9G000108007BCDFA9D01
0x8500FFLLLL9L000108004D3E416801
0x8500FFGGGG9G00010800ECCB8E1A01
0x8500FFLLLL9L000108003E0131AA01
0x8500FFGGGG9G00010800D860F7D501
0x8500FFLLLL9L00010800F18C694D01
7C:79:E8:UU:UU:UU
7C:79:E8:YY:YY:YY
0x8500FFIIII9I00010800BEC2D59001
0x8500FFKKKK9K00010800A116D21F01
0x8500FFIIII9I00010800DD613D8201
0x8500FFKKKK9K00010800A29C0C3301
7C:79:E8:VV:VV:VV
7C:79:E8:XX:XX:XX
0x8500FFJJJJ9J00010800D31968C901
0x8500FFHHHH9H00010800B24DDF4C01
0x8500FFJJJJ9J0001080020C991E401
0x8500FFHHHH9H0001080091D3A60C01
0x8500FFJJJJ9J0001080080CD7A8401
0x8500FFHHHH9H000108002451808701
0x8500FFHHHH9H000108007ABB9CBD01
Some patterns are immediately visible:
- Bytes 0–1 specify the Bluetooth radio manufacturer, BlueRadios, Inc.
- Byte 2 is
0xFF
in all recorded packets. - Bytes 3–5 correspond to the Device ID displayed in the hidden “Tech Info” box in the PayRange app.
- Bytes 6–9 are
0x00010800
in all recorded packets. - Bytes 10–13 differ across machines and over time without any immediately obvious pattern.
- Byte 14 is
0x01
in all recorded packets.
In a future part, I will investigate the effect of changing machine states on manufacturer specific data and attempt to decode state information.
Device Identification
Combining the results in Tables 3 and 5 allowed me to create a map between each machine, Device ID, and MAC address. Furthermore, it confirms that the methodology employed in Table 4 produced correct results.
It remains unclear how the PayRange app associates each Device ID with the displayed machine numbers. I suspect the association is made via an API call to an external server containing a database of Device IDs, machine numbers, and payment recipients; identifying this proposed API is outside the scope of this article.
Machine
Device ID
MAC Address
W1
10GGGGGG
7C:79:E8:TT:TT:TT
W2
10HHHHHH
7C:79:E8:XX:XX:XX
W3
10IIIIII
7C:79:E8:UU:UU:UU
D4
10JJJJJJ
7C:79:E8:VV:VV:VV
D5
10KKKKKK
7C:79:E8:YY:YY:YY
D6
10LLLLLL
7C:79:E8:ZZ:ZZ:ZZ
Summary
In this first part, I determined that PayRange BluKey devices broadcast Bluetooth LE advertising packets that contain the Device ID (in addition to other undecoded data). I used this information to associate each machine with a Device ID and MAC address.
I also identified the set of BLE characteristics exposed by each peripheral, which can be used to determine the software and firmware revisions (in addition to other undecoded data).
In the next part, I will use differential techniques to further study the advertising packet’s manufacturer specific data. I will also use a dedicated Bluetooth LE sniffer device to capture communications between the PayRange app and connected machines.
Appendix A: Source Code
- PayRange Tool v0.0.1 : Python script that logs Bluetooth LE data for analysis.