Megafeis-palm: Exploiting Vulnerabilities to Open Bluetooth SmartLocks
By Abdullah Ansari on 3 March 2023
Introduction
I discovered multiple issues with how a brand named Megafeis handles account and smart padlock management. By combining these issues, it is possible for an attacker to perform the following:
- While within Bluetooth broadcasting range of a target lock, enumerate account information about the lock's owner
- Obtain control of the target lock via one of the following:
- Performing a brute force attack to obtain access to the owner's account
- Send a modified web request which forces the API server to switch control of the lock to the attacker's account
This means that an attacker within bluetooth range of one of the affected models of Megafeis smartlocks could take over and open a targeted lock armed only with its MAC address.
As of the publishing of this post, Megafeis has not responded to WithSecure's attempts to reach them, nor is WithSecure aware of any steps taken to remediate the issue.
This post details how I came to discover the authorization flaw and how I developed some proof-of-concept scripts to exploit it. The scripts can be found in the megafeis-palm GitHub repository.
Background
When I began my internship at WithSecure, I was tasked with conducting research into an area of information security that interested me. At the time, I was hearing of some fascinating attacks being performed against IoT devices and came across an article that really piqued my interest in IoT security. It described an insecure smart fish tank thermometer in a casino which gave network access to hackers, resulting in the exfiltration of the high-roller database! After some deliberation, I eventually selected smart locks as my area of research. The goal of my research: to remotely open any smart lock without the owner's permission.
To select my target, I went onto Amazon and searched for smart locks. I found several models that were designed to protect home doors and while breaking into one of those would be far more impactful, they were quite expensive and would have depleted my research budget in one fell swoop. I narrowed my search to smart pad locks and picked the most popular one. At the time, that lock happened to be the FB50S smart pad lock from a brand called Megafeis (https://www.megafeis.com/padlocks ), which came with a mobile companion app called DBD+. Further research revealed that the company was a subsidiary of a larger Chinese entity and sold a couple of smart lock models in addition to the FB50S including the GS60FB, GS40S, and the GQ10FB, all of which I ended up purchasing for testing reasons.
Equipment & Tools
- Kali Linux Virtual Machine (VM)
- Megafeis FB50S, GS60FB, GS40S, and GQ10FB Smart Locks
- Rooted LG Nexus 5
- Wireshark
- JADX
- Burp Suite
- Android SDK Platform Tools
Bluetooth Low Energy
When I set out to research IoT locks, the main attack surface I aimed to explore was the smart lock's implementation of Bluetooth. I spent a couple of days reading everything I could get my hands on about Bluetooth security and the advent of smart Bluetooth (aka Bluetooth low energy). I decided that the first analysis I would conduct would be of the Bluetooth traffic between the smart lock and my rooted Android phone when I unlocked the smart lock.
I went ahead and enabled the "HCI Snoop Logging" option in the developer settings, which would log all the Bluetooth traffic sent to and from my device. I then launched the DBD+ mobile companion application, created an account, added the lock to my account, and unlocked it. After moving the Bluetooth log to my testing VM and loading it into Wireshark, I attempted to decipher the communications that took place to unlock the smart lock.
A sample 49-packet communication filtered for exchanges between the Android phone (LGElectr_e5:cd:ed / 64:BC:0C:E5:CD:ED) and the smart lock (7F:06:02:05:0C:BF) is shown below:
The logs seemed to indicate that once the lock and the phone established what GATT services, characteristics, and descriptors were available, the actual unlocking sequence began with a write request and a write response as shown in packets #234 and #236.
Once the green light was given by the smart lock, the phone sent a random 32-character alphanumeric string to the lock (Packet #237, #240, #243, #246, #249). The lock then performed an operation on the string and returned another 32-character alphanumeric string to the phone (Packet #239, #242, #245, #248). This exchange was repeated a total of 4 times, with the 5th value sent by the phone unlocking the smart lock.
A single occurrence of the above Bluetooth communication is shown below:
Phone to Smart Lock
Bluetooth Attribute Protocol
Opcode: Write Command (0x52)
Authentication Signature: False
Command: True
Method: Write Request (0x12)
Handle: 0x0013 (Unknown: Unknown)
[Service UUID: 0000800000001000800057616c6b697a]
[UUID: 0000800100001000800057616c6b697a]
Value: 72f917381e8287483ca5cbbac917363c
Smart Lock to Phone
Bluetooth Attribute Protocol
Opcode: Handle Value Notification (0x1b)
Authentication Signature: False
Command: False
Method: Handle Value Notification (0x1b)
Handle: 0x0015 (Unknown: Unknown)
[Service UUID: 0000800000001000800057616c6b697a]
[UUID: 0000800200001000800057616c6b697a]
Value: 3a774d4865f8544cf4318a43f01c5bc5
Uncovering the communications that took place between the phone and the lock led me to believe that some type of encryption was being used to generate and modify these 32-character alphanumeric strings to unlock the lock. Logically, whatever secret operation was being performed by the phone on the encryption string must have been written in the source code for the DBD+ application. Thus, the next step was to reverse engineer the application in order to uncover how the 32-character strings were being generated.
The Android App
Android applications are truly a monster subject to dive into, but I was determined to get this lock open, so I proceeded to refresh my intermediate knowledge of the Java programming language and learn everything I could about reverse engineering Android applications. This included topics such as how applications are decompiled, deobfuscated, and how actual logic is extracted from gibberish functions and classes by refactoring the code for clarity.
Once I had a decent handle on how the tools and methodology worked, I pulled the DBD+ APK file from the Google Play store and loaded it into a popular APK decompiler called JADX. I began to peruse the application's source code files searching for the Java class which managed Bluetooth communications, however, a couple of factors unique to this application made reading its source code and attempting to decipher its logic absolutely mind-numbing.
It appeared that the DBD+ application was importing classes from the dcloud.io package, indicating that it was built using the Uni-App framework for application development ( https://en.uniapp.dcloud.io/ ). Uni-App allows developers to write applications for multiple platforms using a JavaScript framework known as Vue.js. After many hours staring into the void trying to make sense of confusing source code, I searched the entire APK file for the string: "value:" since it was the field in which the encryption keys were passed during Bluetooth communications.
Scrolling through the thousand or so results, I eventually landed upon a file in the application's Assets folder which contained minified and uglified JavaScript. This file was called "app-service.js" and was what I had been looking for all along. After passing its contents through a JavaScript beautifier, I discovered that all of the app's core functionality was written in this file.
A preview of the code used to generate the Bluetooth unlocking packets is shown below:
Reading through the packet generation's logic, I came across some code that looked quite suspicious. I have included a snippet below:
Though I didn't completely understand what the code was doing, the variable "secretKey" and the function call "this._toHttpEncrypt" were enough to arouse my suspicion that perhaps I should temporarily divert my searching efforts to the app's HTTP traffic.
The API
I began by launching Burp Suite, opening a listener on all interfaces, installing the Burp certificate on my Android phone, and setting up the proxy in the Wi-Fi settings. Once everything was ready, I launched the DBD+ application and started analyzing the HTTP traffic. To my surprise, the data being passed to and from the API was in plain-text HTTP requests; no TLS/SSL at all. One of the requests in particular happened to contain the "secretKey" value which I came across earlier in the "app-service.js" file.
The request and response are shown below:
Although, I couldn't exactly figure out how the "secretKey" was being used in the encryption process, I did move the request to Repeater and resent it to the API; it worked! I thought if I could just make arbitrary requests to the API that is managing these locks, I wouldn't even need to break the Bluetooth encryption. Instead, I could just abuse the API to bind and unbind smart locks from target accounts as I wished. I began to enumerate the API by performing all the actions allowed by the app including:
- Signing in and out
- Binding and unbinding locks
- Giving temporary unlock permissions to other accounts
- Adding a lock by QR code
- Adding a lock by Bluetooth discovery
- And several more...
There was one problem though, when I intercepted an API request, modified it, and released it to the API, I received a stark warning from the server:
To better understand why modified requests were being rejected (and logged) by the API, I sent two identical requests to Comparer. What I found was that a single HTTP header value (other than the timestamp) was being changed throughout the requests. It was a MD5 hash in a custom HTTP header called "SecSignDest". The difference is shown below:
I theorized that the API may only be using this MD5 hash to verify request integrity and decided to focus my attention and effort into reverse engineering how this hash value was being generated by the application. I suspected that if I could generate this hash value myself, I would be able to perform arbitrary requests to this API, thereby giving me the power to enumerate and tamper with any locks belonging to any account on the DBD+ application.
Reversing SecSignDest
Searching for the header's name in the APK files led me to the same "app-service.js" file I was reading earlier. The function responsible for generating signed HTTP API requests is shown below:
We can see in the code above that the "SecSignDest" header is the value of the "g" variable. The "g" variable happened to call the "createSignKey" function on the "m" variable which consisted of:
- The "p" variable (also used in the "Content-Date" header value)
- The "v" variable
- The "/app/" string
- The "e.name" variable
The components above were concatenated together after various operations were performed on each individual variable. This "m" key, when passed to the "createSignKey" function, was slightly modified and returned as shown below:
The "createSignKey" function appeared to take the "m" variable, prefix it with the "OKLOK" string, hash the entire string with MD5, and return the upper case version of the final hash value. I have created a logic flow chart to help understand how the final signing key hash was being generated:
Attempting to manually recreate this formula to generate the signing key for a sample API request proved to be quite troublesome, so I decided to modify the app itself to show me the value of the "m" variable before sending it to the "createSignKey" function.
Decompiling, recompiling, and reinstalling the application on my Android phone yielded numerous errors resulting in repeated crashes. However, the method I had in mind to exfiltrate the "m" variable's value only required the modification of the "app-service.js" file which was located in the Assets folder, and did not contain compiled source code.
After some hunting, I came across a blog post ( https://blogs.sap.com/2014/05/21/how-to-modify-an-apk-file/ ) which described a way to modify APK resource files without any decompilation tools using WinRAR. I proceeded to follow the steps outlined in the post:
- Open the APK file in WinRAR
- Delete the developer's signature files
- Copy over the "app-service.js" file to the desktop
- Make the appropriate modifications
- Drag the modified file back into the APK archive
- Resign the APK file
- Reinstall the application
My chosen modifications included adding two extra headers to the web request generation code, one with the "m" variable's value, and the other with the final hash as shown below:
Once my changes were made, I dragged the "app-service.js" file back to its original location in WinRAR, resigned the APK, reinstalled it on my Android phone, and launched it. The first request to the API was captured and is shown below:
Once I was able to see the raw value of the "m" variable, I prefixed it with the fixed "OKLOK" string (as was performed in the createSignKey function) and threw it into an MD5 hashing tool. To my utter excitement, it matched the value included in the request earlier!
In my elated state, it dawned on me that if the API was checking the cookie or the token headers for authorization, I would only be able to attack my own account which wouldn't cause any impactful damage.
To test whether the API was actually using any other values for authorization, I created a new account and performed the following:
- Added a smart lock to the new account
- Picked out an old request for unbinding a lock from an account
- Modified the request with the new account's information
- Manually regenerated the signing key
- Using the old account's cookies and tokens, resent the unbinding request to the API
My suspicions were correct! The lock disappeared from the new account! I shouted with victory and my brain raced with the excitement of finding my first major vulnerability!
I did some further testing to ensure that request tampering worked with all the API endpoints and began to script an attack which would take the lock's MAC address, retrieve its owner, unbind the lock from their account, and bind it to an attacker account. The script would also offer the option for an attacker to return the lock to the victim's account after it was unlocked thereby avoiding suspicion.
Proof of Concept
I began the process of automating this attack by collecting all the API URLs which were vulnerable as well as which endpoints I would need to send data to in order to perform the steps outlined previously. I found that the application used 4 different URLs based on availability and while there were several API endpoints, 5 in particular were needed to conduct smart lock theft. They are as follows:
- /app/login/pword - Login and retrieve the necessary authentication token
- /app/device/detail2 - Convert the lock's MAC address to the lock's serial number
- /app/device/detailAll - Discover the user code of the lock's current administrator account
- /app/device/unbind - Disconnect the lock from the victim's account
- /app/device/bind - Connect the lock to the attacker's account
The following URLs were found to be using the above API endpoints:
- http://service.oklok.com.cn
- https://service.oklok.com.cn
- http://service.oklok.net
- https://service.oklok.net
The API endpoints listed above along with the following Python code were used to automate the generation of API request signing keys and perform lock reassignment on a target smart lock. In order to closely recreate the signing key generation process, I opted to use the py2js library to execute JavaScript code since the original hashing logic was written in JavaScript (and also because I didn't want to write it again in Python). The hashlib library was used to create the MD5 digest.
The full exploit code has been omitted due to its size, however, it can be found in the megafeis-palm GitHub repository along with the PoC code for other flaws found in the DBD+ application.
Demonstration
Screenshots
The screenshots below show execution of a lock-theft attack on a test user account and a sample lock. In this scenario, the "dayihed329@chimpad.com" account will simulate a victim, the "diyoveh601@5k2u.com" account will simulate an attacker, and the target lock will be the FB50S model device with the MAC address of 7F:06:02:05:0C:BF.
Step 1: MEGAFEIS Lock MAC Address Discovery
To find a target lock, we open the DBD+ app and tap the "Add" button. Once a lock advertising its model number is found, we record its Bluetooth MAC address as it will be needed later on. The lock we want to take over has the MAC address 7F:06:02:05:0C:BF.
Step 2: PoC Execution
As mentioned above, this attack scenario shows a Megafeis lock bound to a legitimate user's account (dayihed329@chimpad.com):
Also shown is an attacker account (diyoveh601@5k2u.com) ready to steal it.
Once the PoC script executes, note that the ownership of the lock is transferred from the victim's account to the attacker's account.
After the attacker executes the attack, they will have added the lock to their account and removed it from the victim user's account.
Video
I have also recorded a brief video demonstrating this attack in action on all 4 of the MEGAFEIS smart lock models including the FB50S, GS60FB, GS40S, and GQ10FB. The video is available for viewing at the megafeis-palm GitHub repository.
Conclusion and Further Reading
Overall, this research was incredibly rewarding and led to my first actual findings with real impact on production devices. In addition to API exploitation, I also discovered 3 separate issues including the lack of rate limiting, insecure password reset code expiry, and information disclosure. The advisories I have published describing their impact can be found below:
- CVE-2022-45636: Insecure Authorization Scheme for API Requests in DBD+ Mobile Companion Application for Megafeis Smart Locks
- CVE-2022-45637: Insecure Password Reset Code Expiry Mechanism for Megafeis Smart Locks
- CVE-2022-45634: Username Disclosure Vulnerability in DBD+ Application Used by Megafeis Smart Locks
- CVE-2022-45635: Insecure Password Policy & Lack of Rate Limiting on Megafeis Smart Lock API Server
Proof of concept scripts and additional information about these issues can be found in the GitHub repository: https://github.com/WithSecureLabs/megafeis-palm