Ray Felch //
Recently I was afforded the opportunity to research the findings of a well-known security firm (F-Secure), who had discovered a vulnerability in the Guardtec KeyWe Smart Lock. The F-Secure people found that due to a design flaw, an attacker could intercept and decrypt traffic coming from a legitimate owner of the lock. I found their blog (posted in December of 2019) to be extremely fascinating and very informative. I soon became motivated to see if I could duplicate their efforts, realizing that F-Secure had issued an advisory and that the vendor had been given an opportunity to mitigate their exposure. Unfortunately, their mitigation options were extremely limited due to the fact that they had no firmware update functionality. Instead, they chose to use obfuscation in their android app in an attempt to hide the more relevant sections of code (which they did quite well I might add).
Although F-Secure had laid the groundwork, they were careful not to reveal too much information and even REDACTED some of their own tools, thereby retaining the ‘keys to the kingdom’ as they put it. During my journey, I found myself constantly going back to their blogs, especially as I discovered new and relevant information of my own. This blog is intended to, not only consolidate my notes and document my research but to maybe inform others of some pretty cool tools and methods for reverse engineering Android/iOS applications.
After receiving the shipment of my KeyWe Smart Lock and creating a test fixture to mount it, I downloaded the android app to my mobile phone and created my account. I got familiar with the functionality of the lock and the look and feel of the mobile app. I also ran my Nordic nRF Connect mobile app (available for free on Google Play store) to gain useful information about my lock, such as the Bluetooth address, primary service UUIDs, characteristics, etc. Note: Understanding Bluetooth Low Energy GATT and GAP is beyond the scope of this write-up, however, the BLE specifications are easily accessible here https://www.bluetooth.com/specifications/, should you want to read up.
Communications between the KeyWe (lock) and the (mobile) App are transported via Bluetooth Low Energy (BLE) packets, which are encrypted using the standard ECB AES-128 cipher in order to prevent third-party eavesdropping. The security of the message channel is based solely upon 3 secret keys used for the encrypting and decrypting of the OTA (over-the-air) AES-128 packets.
- Common Key (CommonKey) used for the initial key exchange
- App Key (AppKey) used to encrypt packets sent by the App to the Door
- Door Key (DoorKey) used to encrypt packets sent by the Door to the App
The CommonKey is based entirely on a static 16-byte value which is simply enumerated with the last 5 bytes of the device’s Bluetooth address as follows:
Upon further examination of the CommonKey on multiple KeyWe devices, it appears the only difference between all devices examined were the last two bytes of the device Bluetooth address! In my case, the values 4C and 93 were unique to my device. This suggests that the CommonKey is highly predictable and based solely on two bytes within a 16-byte static value per device!
The AppKey and DoorKey are created from two algorithms (and heavily obfuscated) methods makeAppKey and makeDoorKey. The F-Secure people created a nice tool that generates the three secret keys by simulating the action of these two methods (although they REDACTED and obfuscated their work, rendering it generally harmless). After some considerable time, however, I was able to reverse engineer the functionality of these two methods myself working with a handy open-source tool called Frida (more on this later).
AppKey and DoorKey creation consists of passing two arguments to the makeAppKey and makeDoorKey functions. These two arguments are AppNumber and DoorNumber respectively.
AppNumber is a static (hard-coded) 12-byte value (padded with four zero bytes): 92 4b 03 5f bd a5 6a e5 ef 0e d0 5a 00 00 00 00 Note: AppNumber is encrypted with the CommonKey and sent to the Door (lock) as the very first packet transmission of the user session, thereby initiating commencement of the session.
DoorNumber is a dynamic (changes every new session) 12-byte value (padded with four zero bytes) generated by the Door (lock). This value (also encrypted with the CommonKey) is sent to the App, in response to receiving the AppNumber. (see diagram below)
Note: These two ‘opening’ transmissions (AppNumber and DoorNumber) complete the initial key exchange process and allow for the creation of the AppKey and DoorKey which will afford secure OTA communications between the App and Door going forward.
Now that the AppNumber and DoorNumber have been created and exchanged, we have the two components required for generating the remaining two secret keys (AppKey and DoorKey). This is accomplished by calling the makeAppKey and makeDoorKey functions with AppNumber and DoorNumber as arguments. This is done internally within the firmware and not sent OTA.
APPLICATION FLOW DIAGRAM
Now that we have generated and exchanged AppKey and DoorKey, each side can now encrypt/decrypt packets sent or received. All packets transmitted by the App will be encrypted using the AppKey and all packets transmitted by the Door will be encrypted using the DoorKey. Both sides know each other’s encryption scheme and can, therefore, decrypt the packet.
The following diagram demonstrates the order of events of a typical user session:
It should be noted that all of these packets are transmitted OTA at the beginning of every user session, and all 13 of these packets are encrypted with either the AppKey or the DoorKey depending upon the transport direction.
REVERSING THE ANDROID APK
All mobile applications are downloaded as APK (Android Application Package) files. APK files are saved in ZIP format and are typically downloaded directly to Android devices, usually by way of the Google Play store, but can also be found on other websites. When reverse engineering android APKs, I find that it is often helpful to use third party sites to search for older versions of the APK in question. A favorite of mine is https://apkpure.com/.
Java version 1.8.0_251
ADB (android debug bridge) version 1.0.41
APKStudio (wrapper for Apktool) version 2.4.1
Jadx version 1.1.0
Frida version 12.8.9
Rooted android phone
A typical APK contains some very useful content, such as an AndroidManifest.xml, classes.dex, and resource.arsc file; as well as a Meta-INF and res folder. There are a few different ways to open an APK residing on your PC. Obviously, because it is a ZIP file, any of the various UNZIP extractors will work just fine, however using tools like Dex2Jar, Apktool, and Jadx (to name a few) offer additional advantages such as converting .dex files to java code for better readability and GUI support for ease of navigating the code.
DEX files (Dalvik executable files) are developer files used to initialize and execute applications for the Android mobile platform. Tools like Apktool can decompile the DEX (machine language) files into Smali (assembly language source) files. We can also use tools like dex2jar to convert DEX files to JAR (java) files and use jadx GUI to open the JAR file as java source code. Java source code can be a lot easier to read than Smali source. There are many options available to navigate the Android APK, including a favorite of mine, APKStudio.
With the many options available, it would be beyond the scope of this write-up to describe the various steps involved with implementing any one of these tools. I would suggest downloading them and experimenting with several techniques to find your best fit. There are plenty of helpful tutorials out there.
The F-Secure researchers stated in their blog that they were able to intercept function calls in the android app using a tool called Frida. I was not aware of this tool, so I decided to check it out. This tool is amazing! Understanding how to implement Frida is beyond the scope of this write-up. However, suffice to say, Frida allows a researcher the ability to attach to existing functions within an application and dynamically dump the arguments and return values. This is most definitely worth checking out! https://frida.re/docs/home/
Frida w/toolkit installation
- pip install frida
- pip install frida-tools
- frida-server and adb (android debug bridge)
- rooted android phone for debugging apk (I used an old Samsung GS5)
Install frida-server on rooted phone
To install the server, navigate to https://github.com/frida/frida/releases and download the appropriate file for the specific phone platform being used.
(If you are not sure of the phone’s architecture, download and run Droid Hardware Info (from Google Play store)
- Copy the downloaded file to your project directory
- Navigate to your project directory
- Unzip the .xz file with : xz -d -k frida-server-12.9.8-android-arm.xz
- Using adb (android debug bridge) tool push the extracted file to the rooted phone:
INITIAL SETUP: (installs frida-server on rooted phone)
- $ adb push frida-server-12.9.4-android-arm /data/local/tmp
- $ adb shell ### shell into phone
- $ su ### root level user
- # cd /data/local/tmp
- # chmod 777 frida-server
- # ./frida-server & ### start frida-server daemon
Once the frida-server is installed on the rooted phone, begin a new session as follows:
- $ adb shell
- $ su
- # cd /data/local/tmp
- # ./frida-server &
Unfortunately, due to the heavy obfuscation of my newer version of the Android APK, the ‘RootTool’ class did not exist and I was forced to search my APK code in an attempt to find it’s equivalent class. After a considerable amount of time searching the code, I eventually located the root check methods. It turned out the ‘RootTool’ class was now referenced as ‘n’, and the ‘iSRooted’ function is now referenced as function ‘b’.
Modified F-Secure’s KeyWe-Tooling script according to my search findings
Resulted in successfully evading the root detection!
Moving along, I determined that working with the latest release of the android application was more trouble than it was worth. The obfuscation was immense and becoming extremely tedious and frustrating trying to find functions whose names had been changed to a single letter. Fortunately, about this time, a colleague provided me with a link to some older KeyWe android APK’s https://apkpure.com/keywe-for-a-smarter-life/com.guardtec.keywe/versions. This turned out to be a great find, as now I could snag a version just prior to the advisory, with all referenced functions still intact. I proved this by returning to the original (unmodified) version of the F-Secure root-evasion script and it worked! Also, I learned that their ‘trace_java_functions’ tool now worked as well, providing me with an enormous amount of definitive data to work with.
In the following Frida hexdump example, we can see the call being made to the AES-128 cipher function (generated by the App) passing two byte_array arguments (AppNumber, CommonKey). This is clearly encrypting the AppNumber with the CommonKey for the OTA packet transmission to the Door, starting the session sequence of events.
Likewise, this example also shows another call being made to the AES-128 cipher function, passing the arguments (DoorNumber, CommonKey), to encrypt the DoorNumber for OTA transmission to the App.
Lastly, from this example, Frida allows us to see the arguments passed and the return values of the two internal function calls that generate the AppKey and DoorKey.
Based upon many Frida sessions captured and the deciphering of the data, I soon became very familiar with the KeyWe application and had a good understanding of how and where the important keys were generated, as well as the sequence of events on start-up. In addition, I learned that during an active session, the status of the door is constantly monitored and updated by information exchanged between the App and the Door. My sessions included signing in, unlocking, and locking the Door using the App on my phone.
F-Secure Frida java scripts:
Evade root-detection: disable_root_detection.js
Trace injected java functions: trace_java_functions.js (original)
- Sign-in, wait for connection and for LOCKED (red) status
- Click to UNLOCK, wait a few seconds, click to LOCK, wait a few seconds
- Click to UNLOCK, wait for auto-LOCK, disconnect
BTSNOOP (Android Bluetooth HCI logger)
Ultimately, BTSNOOP has to be one of my greatest finds when wanting to capture a complete Bluetooth session between central (phone) and peripheral (lock), I attempted various methods to capture my OTA Bluetooth sessions, including Nordic’s nRF Sniffer development board nRF52840-DK, Sena’s UD100 dongle, the Ubertooth-One and Texas Instruments CC2540 dongle. The problem with all of these approaches is they couldn’t follow the connection due to Bluetooth Low Energy (BLE) channel hopping. The Nordic nRF52840-DK came close when using it together with Wireshark and Nordic’s BLE sniffer plugin, but unfortunately, the packet captures were at the Link Layer (rather than the host controller interface layer) resulting in encrypted data that was unable to be parsed.
Being that it was not captured at the HCI layer, meant that it was susceptible to built in CCM AES-128 BLE security key exchange protocol handshakes. From what I could determine, this meant if the nRF52840-DK was not sniffing at the time of the pairing , it would miss the security handshake entirely, resulting in no decryption of the packets.
IMPORTANT: nRF Sniffer shortcoming!
It appears that if the KeyWe lock executes a channel hop after pairing, but before the App transmits the 1st packet, the nRF Sniffer will miss the initial CCM AES-128 BLE security key exchange. This would result in encrypted ‘useless’ packets. This is a shortcoming of the nRF Sniffer’s inability to follow the channel map. Note: the CCM AES-128 BLE security key exchange is a security protocol found in all Bluetooth Low Energy OTA wireless connections to prevent MiM eavesdropping.
CCM AES-128 BLE security key exchange: (this is general information unrelated to the KeyWe project)
The temporary key is used during the Bluetooth pairing process. The short term key is used as the key for encrypting a connection the very first time devices pair. The short term key is generated by using three pieces of information: the Temporary Key, and two random numbers, one generated by the slave and one generated by the master
Once the connection is encrypted with the short term key, the other keys are distributed. The Long Term Key replaces the short term key to encrypt the connection. The Identity Resolving Key is used for privacy. The Connection Signature Key is used for authentication.
Fortunately, there is an excellent way to capture Bluetooth traffic using your android device!
On your Android phone
- Go to settings
- If developer options is not enabled, enable it now
- Go to developer options
- Enable the option Enable Bluetooth HCI snoop log
- Perform the actions which need to be captured (session)
- Disable the option Enable Bluetooth HCI snoop log
- Copy the file to PC using ADB (Android Debug Bridge)
- The file of interest is btsnoop_hci.log
Note: Typically, I’ll leave the option Enable Bluetooth HCI snoop log enabled, as it’s on my rooted test phone
Obtain btsnoop_hci.log of complete bluetooth session
Listed android files
Pulled btsnoop_hci log files
Renamed btsnoop_hci.log to btsnoop_hci-07-31-20.log (appended with my session date)
Importing the btsnoop_hci.log into Wireshark, we can see the OTA encrypted packet exchanges. This, in conjunction with the Frida function hexdumps provides a valuable way to cross-reference the activity of the user session. From the massive number of sessions generated during my research of the KeyWe lock, I can confirm these packet exchanges follow the same sequence every session and never vary in the least. In the following example, we can see the opening key exchange, initiated by the App and followed by the Door.
EXAMPLE 1: APP sends AppNumber — DOOR returns DoorNumber — DOOR sends Hello
Interesting note: (in the screenshot above): fb2b28c68b3f99c514b98fada4bf0b89 (transmission #2) can be decrypted with the CommonKey to reproduce the DoorNumber!
This can be verified by using the free online AES-128 Cipher tool here: http://aes.online-domain-tools.com/
Enter the encrypted packet fb2b28c68b3f99c514b98fada4bf0b89 and enter the secret key (CommonKey) c88ff4150f4a4c27934a6c5e6741efac followed by clicking Decrypt
Using the online AES-128 Cipher tool is a handy way to correlate the Wireshark session data with the Frida hexdump data. The next few screenshots show examples of how the various Bluetooth OTA traffic coincides with the known function calls of the App. This provides us with an enormous amount of information regarding program flow and execution.
Example 2: APP sends Welcome — DOOR sends START — APP and DOOR both exchange doorMode
Example 3: APP sends eKey — DOOR sends eKey (authentication and authorization)
Example 4: DOOR STATUS — doorTimeSet exchanges
Example 5: DOOR STATUS exchanges
As can be seen by the screenshots (above), the entire Bluetooth session can be analyzed in Wireshark using the btsnoop_hci.log (capture) and compared side by side against the data found using the Frida tools. Also notice that every one of the encrypted packets being sent over the air (OTA) can be decrypted by simply determining the transport direction (App to Door or Door to App) and using the appropriate key (AppKey or DoorKey) to decrypt.
Now that we have the keys and know how to interpret all of the data, we can attempt to operate the lock with a replay attack. Again, F-Secure provided a nice tool in their Github that they called ‘open_from_pcap’. Based upon information in their pre-recorded pcap session, this tool allowed them to replay the session and operate the lock. Of course, this tool was rendered harmless when they REDACTED their keys.py script. However, as I stated earlier, I was eventually able to reverse engineer the functionality. So, by swapping F-Secure’s REDACTED keys.py with my own version, it allowed me to implement the ‘open_from_pcap’ tool on my btsnoop_hci.log capture.
Using my Sena UD100 Bluetooth USB adapter, the first result of running the ‘open_from_pcap’ script appears below:
Apparently, my modified keys.py correctly determined the CommonKey, AppKey and DoorKey, but failed at the eKeyVerify stage.
Without getting too deep into F-Secure’s coding, the open_from_pcap python script calls a function in another script (decode_from_pcap) which supposedly retrieves the eKey from the session pcap. Unfortunately, that did not work properly for me. Maybe it was related to a difference in format structure of their pcap file versus my btsnoop_hci.log file (saved as pcap from within a Wireshark session), Regardless, I decided to forego the deciphering of their code and instead modified the ‘open_from_pcap’ file to use my hardcoded eKey rather than trying to retrieve it.
As you can see from the screenshot above, as part of my testing, I decided to delete my (OLD) KeyWe account and create a (NEW) account. By doing so, I might be able to determine what changes occur from one user account to another, and if the eKey might be predictable. From what I can see, there are no obvious detectable patterns. The eKey (also known as the User password) is generated when the owner creates their account, so it makes sense that the key is totally randomized and potentially derived from the User Password during account setup. Also, when I created the new account, I intentionally changed only one character in the original password. My intention here was that this might help me determine if the eKey was derived solely from the User Password. In my opinion, it appears it was not.
From the screenshot above, it seems pretty clear that the eKeyVerify function was called by the App and the argument passed was a 6-byte value (eKey). The returned value can be assumed to be a modified resultant eKey to be sent by the App (encrypted with the AppKey) to the Door. Without digging deeper into the code, I can only assume that the 6-byte value (eKey) provides a link to the actual (modified) eKey stored away. Regardless, this 6-byte eKey is all that’s required to complete the replay session.
Hard-coding the eKey into the replay.py script resulted in the following replay session:
This replay was successful because I was able to obtain the eKey (used for authentication and authorization) by extracting it from my rooted phone using Frida. As it stands right now, this replay attack would not work in the wild due to the fact that the eKey of a legitimate owner is not accessible in this manner. Likewise, as I stated earlier, the eKey is not transmitted OTA.
Well, that statement is not entirely true!
Consider the following snippet captured by my phone’s btsboop_hci.log and the corresponding Frida hexdump of the eKeyVerify function:
The Wireshark capture shows the opening packet transfers (key exchanges, handshakes, etc). Pay close attention to the 8th packet in this session. It shows the encrypted packet sent by the App containing the modified eKey value. From the Frida hexdump, notice also that the 6-byte eKey value is enumerated into bytes (5:11) of the modified eKey value.
Based upon our learned knowledge of how this packet is encrypted before transmission, we know that it will be an AES-128 cipher using the AppKey as the secret key.
Using the online AES-128 Cipher tool to decrypt the 8th OTA encrypted packet: 46402315a85a72e66e9671d044b513af using the AppKey: e022c1193ebb3882efc9cf79b6e557d1 as the secret key, we would get the decrypted modified eKey. And as we know, the 6 byte eKey value is enumerated into bytes (5:11) of the decrypted modified eKey value. (See below)
This little exercise clearly shows that if we can do an OTA capture of the opening packet exchange of a legitimate owner in the wild, we would have everything we need (including their User Password – eKey) to compromise their home security and unlock their door!
I have to admit that I spent an enormous amount of time with my research (in the order of a few months) on this project, mainly because of dealing with the heavy obfuscation of the code, but also due to the learning curve involved with learning any new tool. Getting familiar with the Frida tool and a few of its many features and implementations was one of the high points of this project for me. Furthermore, it was equally rewarding to be able to reverse engineer an Android application, while realizing it has been over a year since the F-Secure advisory was issued and the vendor having had sufficient opportunity to mitigate what they could. Inasmuch, I strongly agree with the findings of the F-Secure researchers, in that firmware update capability would certainly have mitigated a great deal of their exposure and that using custom (In-house) crypto algorithms is never a good idea.
I’ll close out this write-up by saying that I still have some unfinished research to deal with, that being an OTA capture of a session between my non-rooted personal phone and the KeyWe lock, in order to create a replay attack that WILL work in the wild. Regardless, to mitigate their exposure, I strongly recommend that current owners of the Guardtec KeyWe Smart Lock upgrade their Mobile Application to the latest release as soon as possible (version 2.1.0 at the time of this writing).
Check out our Cyber Range, not just a place to work through challenges and play, but also an open direct/hands-on training environment.