Blog: Internet Of Things
Different ‘smart’ lock, similar security issues
I was looking through Amazon and found this padlock at the cheaper end of the scale. For twenty of my well-earnt English pounds I could become the owner of a new and shiny SLOK lock.
Image credit: Amazon
It can be unlocked by BLE and can be shared to others, what could I do but buy one and reverse it?
Initial probing
Whilst I was waiting for the lock to arrive, I had a quick look at the Android app (actually there’s two: com.supude.slok and com.supude.igearslok, I use the latter as the former is flaky and spent most of the time crashing).
This was interesting as I and a colleague spent the next two hours looking through the app and saying “WTF?”; this is possibly one of the worse written Android apps I’ve seen, with some very strange coding and design decisions all through the code base.
As an example, the app requests a total of 38 different permissions, including some I’d never expect in a padlock app:
- READ_CONTACTS
- WRITE_CONTACTS
- REQUEST_INSTALL_PACKAGES
- CALL_PHONE
- MOUNT_FORMAT_FILESYSTEMS
- MOUNT_UNMOUNT_FILESYSTEMS
- MODIFY_AUDIO_SETTINGS
BLE protocol
Unlike most cheap padlocks, this app didn’t log all BLE requests to the system log. Which meant that I needed to sniff them. In this case I just turned on Android’s HCI logging facility, which dumps all Bluetooth traffic from the HCI layer and above to a dump file on external storage. This can be copied off and viewed in Wireshark:
The device had two main characteristics, under one service that were used during the connection:
6e400002-b5a3-f393-e0a9-e50e24dcca9e, handle 0x15, which was used by the app to write to the lock (i.e. the txCharacteristic)
6e400003-b5a3-f393-e0a9-e50e24dcca9e, handle 0x17, which was used to receive notification from the lock (i.e. the rxCharacteristic)
The dump from bleah shows the permissions:
And here we have the first interesting thing, the permissions declared in the characteristic descriptor are not the permissions that are actually in place on the characteristic. In BLE permissions are assigned to the characteristic and then (usually) mirror in the descriptor of the characterstic, in this case, handle 0x16 contains the descriptor for rxCharacteristic:
We can break down this value by converting the octets into the data they represent:
If we go by only the properties in the descriptor then we should only expect the characteristic to support notifications (i.e. messages passed from the device to the app). Let’s just see whether the real permissions on the characteristic mirror the descriptor and try and read the characteristic:
Oh, um, we’ve just read the characteristic, which we shouldn’t have permission to. There’s a lesson here: the descriptor is just that, it may not represent the real world, if in doubt, test stuff out to see if it works.
Anyway, we were looking at the wireshark dump. Following a couple of unlock requests got the following requests:
If we look through these requests we can see some patterns immediately; octet 0 is always 0x55 and the last octet is always 0xAA suggesting that these are header and trailer values. Octet 1 appears to be five less than the length of the packet; which implies that there are five bytes of header and trailer.
At this point I cheated and went to the app, which fortunately isn’t obfuscated. There’re some functions which tell us the headers and the trailers quite nicely.
The function com.supude.igearslok.bluetooth.BleDateUtils.makePacket does most of the packet making:
As we can see, this makes the packet as follows:
Let’s have a look at the packets above and colour code them:
We have:
- Header (red): Always 0x55
- Length (blue): length of the data section of the packet
- Command (orange): one byte of command, the top two bits are used for flags, so we’re only really interested in values up to 63 (0x3f).
- Data (green): depends on the command
- Checksum (purple): this is a standard sum up the bytes in the header, length, command and data segments and take the bottom 8 bits type checksum
- Trailer (black) – always 0xaa
The actual values of the commands and data took a while hunting through the app to work out. To save you the trouble, I’m going to cut this down to just the packets need to unlock the lock and what the data in there is.
If I remove the 4 meta-octets from the packets about and break out the command and data, we end up with:
We can see the this is a GetID request with a timestamp (2019-02-08 22:14:32) and passing the lock’s unique key (I’m happy showing you this one, as this particular lock I did some “physical intrusion” on and it’s in bits). I’ll go into how it gets the key later.
The encryption used is interesting, I’ll have to go back to the app’s source to show this:
As we can see it calls a method, encrypt(), passing the lock’s key and a static encryption key. The encrypt() method is:
The cryptographers amongst you (or those that have Googled 1640531527) will notice that this is an implementation of the basic TEA (Tiny Encryption Algorithm) encryption algorithm. With one major difference; this line:
Which, according to the reference implementation, should be:
Which makes no real difference to the encryption but does meant I can’t use standard TEA libraries and will have to code it myself.
Anyway, the response to this is:
The response has bit 7 set to say that it is a response to the (0x81 AND 0x7f) = 0x01 = GetID packet. It returns the Lock’s unique ID, some information about the state of the lock and the battery’s power level (70%) and a unique token, which is used below.
Now we have the response the actual unlock command can be sent:
This packet is a lot simpler (and one octet shorter) than the others. Its main content is the encrypted unlock token. The other important section is some random data, this is most likely to vary the packet to prevent replay attacks (though they could still work just by altering the last four octets).
The encrypted token uses the same encrypt() method, but with a different, static, key:
The final part of the process is the unlock acknowledgement, which looks very much like the response to the GetID packet:
Why did I bother to show that packet? It is important and I’ll go into why later.
Enrolling a lock
So, we know the protocols and how stuff is encoded, there’s only one thing missing: the lock’s unique key.
This is got from the API. Now the API is very weirdly written, with some very strange decisions that I won’t go into just yet.
To get the key, it makes an API request:
Where:
- vkey is an authorisation token which is composed in a very strange way
- lock_mac is the MAC address of the lock
- lock_name is a name given by the user
- lock_num is a unique lock identifier
- user_id is the API identifier for the user, I’ve redacted this for now
- code is the API function – in this case 231 is Net_Add_Lock
These adds the lock the account and returns:
Now we have the key! Some of those parameters are not in English. My Google-fu tells me that they mean:
- guanlian_id – related_id
- jihuo – activated
But; that means to add the lock to an account, we need to get the lock num. How do we get this? I forgot about this initially, but then I went back and found that there’s a BLE GetId message sent:
Of course, the response to a GetID request over BLE returns the lock_num and the lock’s secret key. Is this a way in? I’m not quite certain as I haven’t been able to reproduce it yet.
Now, they did something sensible and you can only enrol a lock that hasn’t already been assigned to a user, so you can’t run the Net_Add_Lock API call again. The app gets around this by running another API call, 211 (Net_Get_Locks). This returns a bit of JSON:
As can be seen this has the lock_num and key in it.
Unfortunately, the API actually checks authorisation for requests, so there’s no Insecure Direct Object Reference (IDOR) which means I can’t look at another user’s locks!
Getting in
So, at the moment our only route is to try and replay that initial GetID request; which I’ve tried and got nothing useful out of it yet.
But, there’s a quirk in the way it implements BLE. As I stated way back at the start there’s a bug in the way the Slok implements BLE characteristic permissions: it allows reading of characteristics in it Slok service when it should. This means we can view the last request and response pair sent to the lock.
For example, I unlocked the padlock, then dumped the RX (0x17) and TX (0x15) characteristics:
Now we have a value of the unlock token (02 1a 8b 45) and the lockID (02 09 17 09 67 64 52 96).
Can we replay the unlock token?
Unfortunately not in my tests, there are some things on the API that are interesting, but that’s a different article.
But, finally…
I can’t leave without some form of exploit. In this case it’s a physical one.
The lock uses a CR2025 battery, which is accessible using a compartment held closed by a standard cross head screw. A cheap screwdriver is even included in the box for doing this.
If we take that screw out we can access the inside of the lock:
So there’s only the battery, and no lock gubbins, from here, but what are those things under the red conformance coating? They look suspiciously like screws. Some quick experimentation later with some screw bits and, yes, they’re screw, with a star head. A wee bit of force is need to break through the coating, but they can still be removed in seconds.
And, here we have the circuit board:
The board is mounted on top of a plastic case which hold the motor and lock gubbins in place. Everything is in some form of non-magnetic alloy (possible Zamak 3) so cannot be affected by magnets.
In the above phone I have highlighted:
- The motor contacts (in red) these can be spiked, with a simple spike tool (mine is a CR2025 battery with two 2.54 mm headers jammed over the contacts and held together with electrical tape) which allows the lock’s shackle to be opened.
- SWD debugging ports (in blue) these are linked to the MCU, I’m still investigating these.
- The MCU (in purple). This is a Nordic nRF N52810.
The other chip is just a motor driver and there’s nothing interesting on the other side.
So here’s a route for opening the lock:
- Remove the battery screw
- Remove the four internal screws
- Spike the motor with 3 volts or more
There is one more route, it doesn’t have exactly what you’d call the strongest shackle: