Sometime in the first half of 2018 there was an explosion of “Dockless e-scooters” appearing all over the Bay Area. These devices are electric scooters that anyone can rent for a one-way trip and find/leave then (at the time) anywhere you want. As one could guess this lead to lots of issues but they where convenient and I wanted one of my own so I did a little research onto how to acquire one for private use.
It turns out that two of the three big players at the time Bird and Spin both used off the self consumer electric scooters made by Xiaomi often sold as the M365. While these scooters were sold by Xiaomi it appears that they were actually designed and built by Segway-Ninebot which Xiaomi has purchased from them. Knowing this it was easy to find them for sale online.
We live in a world where there is “an app for that” for everything, and this scooter is no exception. While the scooter can be used without an app, it will have limited functionality and you will not be able to change any of the settings or enable any of the “security” features or “lock” the scooter.
When “locked” the scooter will use the motor to add resistance to the front wheel to make using it difficult or impossible to turn and sound its alarm’s beep. This won’t stop all theft but it is a good deterrent. Additionally, you can set a numeric pass-code on the scooter that you will need to enter in the app for the scooter to turn on.
The Xiaomi provided App was an all-in-one solution for every IOT thing they offer. I guess if you are deep into the Xiaomi ecosystem this makes sense, but for the simple scooter control I wanted this was overkill. Additionally the permissions the app requested made the privacy enthusiast in my cry out in terror. It wants access to almost everything Android offers, which is way overkill for a scooter control application, and likely still overkill for every other IOT device they produce. Not to mention, what while Xiaomi does make some nice things, they are based out of China which is where they produce their hardware and software which may be cause for concern.
I thought that if I could reverse-engineer the scooter portion of the Xiaomi App and put it into my own app it would solve these problems. Unfortunately the Xiaomi App was heavily obfuscated and the scooter code was a very small portion of the larger app which was all jumbled together. While I’m sure it would have been possible to do this, I set out to find an easier solution.
Before Xiaomi bought the scooter designs from Ninebot they had their own app to control their scooters. This is a much smaller app that was mostly just the code for the scooters. Unfortunately, Since Xiaomi took over the M365 NineBot has updated their app to no longer work with the Xiaomi scooters. (WHY???) Alas, version 4.3.0 and below still work with the Xiaomi M365 and can be found online and side-loaded manually.
However, while Ninebot’s app required many less permissions than Xiaomi’s app, it still required more permissions than I was comfortable with. So I continued my journey to reverse-engineer the simpler NineBot app to implement my own solution.
Luckily for me there was already a community built around modding the M365 scooters who had already started reverse-engineering some of the functionality. Most of the prior word was focused around modding the firmware of the M365 itself to change built-in functionality like the removing the speed governor and increasing the rate of acceleration. If you are interested in that, you can generate custom firmware images here and an app to flash them here.
If you did try to create a custom firmware image for the scooter and flash it, you will notice that the firmware flasher application send the firmware over Bluetooth Low-Energy (BLE). And it can replace the firmware on a device that is “locked”… Oh oh!
Bluetooth Low Energy
The BLE packet format has already been well-documented in the prior work and salvamr created the wonderful m365-ble-msg-builder Java API that simplifies the work required to send/receive BLE packets in a format that the Scooter wants. Additionally maisi’s M365-Power App to display log data from the Scooter made a good place to start.
All I needed was to find the BLE packets for the commands I want to implement from the NineBot application and make my own app to send them and parse any responses.
It should also be noted that the normal Bluetooth pairing process does involve a sort of mutual authentication between devices that helps add a layer of security on top of the Bluetooth channel making it harder for any Bluetooth device start talking to any other device. However, Bluetooth Low Energy lacks built-in authentication, relaying on the application layer to handle the authentication (if any).
After spending some time digging through the decompiled output of the NineBot app I obtained a fairly good understanding of how the auth works, to summarize, it goes something like this:
- App pairs with Scooter using BLE UART
- App asks Scooter for saved passcode
- Scooter provides App the saved passcode
- App asks user for passcode
- App compares the two passcodes
- If passcode match, App send “unlock” packet to scooter
- If passcode do not, app shoes error to user.
It does not take a security mastermind to figure out the obvious problem here. The authentication is performed “client-side”. Which means that the device a user controls (their phone running the app) is in charge of deciding for itself if it should be allowed in.
At this point I’ve become interested in the BLE packets for the following sensitive actions:
- Unlock the scooter
- Lock the scooter
- Read the saved password from the Scooter
- Save a new password to the Scooter
I was able to find the packets for retrieving and setting the passcode fairly easily, and with some help from my friend Brandon Weeks I found the lock and unlock passcode as well and implemented them into a simple test application.
Using the M365-Power app as a base, I created M365-ToolBox to test sending the interesting packets. Surprisingly everything just worked. When sending any of the packets, such as unlock, the scooter would happy respond with a “chirp” and unlock itself without any fuss. Every command I tested worked pre-authentication. So, not only was the authentication handled client-side. The scooter didn’t even care if you skipped the authentication step and just told it what you wanted it to do. I tested on a few other scooters and they all worked the same. I could remotely unlock/lock any scooter without knowing its password, additionally, I could read the passwords other users have set and even change them resulting in a Denial of Service.
If I wanted to use any of the more advanced features I could use the NineBot or Xiaomi apps with the scooter and provide them the password that my app could read from the scooter to perform any of the more advanced functions.
After finding these issues I disclosed them to all the affected parties I could before making this post and waiting to give them plenty of time to fix the issues.
Luckily Xiaomi runs a bug bounty and the scooter was in scope! I submitted my findings and a copy of my M365-Toolbox app and was eventually rewarded $500 for my finding.
- December 23rd 2018: Initial disclosure to
- December 24th 2018: Xiaomi acknowledges and requests POC
- December 25th 2018: I share POC code and detailed explanation
- December 29th 2018: Xiaomi confirms they are able to reproduce the vulnerability
- January 1st 2019: Xiaomi rewards me 4000 RMB (~$500) from their Bug Bounty
Spin which used the same hardware was also vulnerable to the same packets to take over their scooters. I had great difficulty finding contact information for anyone to disclose this to. I eventually found the emails for their support and a few of their engineers., and decided to email them all and hope for the best. Unfortunately they where not very responsive and ultimately I was never able to disclose to them. As far as I know they are still vulnerable.
- January 11th 2019: Initially reached out to Spin to disclose vulnerability
- January 21st 2019: After no contact, I follow up with some more details and increasing the sense of urgency
- January 21st 2019: I’m told the policy team will each out to me after reviewing my “case”
- Radio silence
Bird also uses the Xiaomi M365 hardware, but they replace the BLE controller board with what people are calling a Bird Brain. The Bird Brain has a cellular modem and relays all commands through Bird’s servers to the user’s phone. As a result of this, they are not vulnerable to the attack I describe here, so I did not find it necessary to disclose to them. However a quick glance at their app’s decompiled code shows some Bluetooth functionality, so this might be a good area to explorer further.
Other Related Security Findings
In the process of performing my background research on this I ran across this report from IOActive finding a very similar vulnerability in NineBot’s Segway product about a year earlier. Since IOActive found and disclosed this NineBot has patched it, which is great! However it is saddening to see that after the disclosure NineBot failed to learn from their mistake and included a very similar vulnerability in other products.
After I reported my findings to Xiaomi Rani Idan found the same vulnerabilities that I did and also reported it to Xiaomi. Xiaomi told them that they already knew of the issue (likely in part due to my report) so Rani decided to release their POC. Even after they released their POC I still felt it was right to wait at least 90 days from my disclosure before releasing mine.