Ben bought an e-scooter, found a button combination that skipped the PIN entry, and spent the next six months reverse engineering it down to the firmware — then replaced the display unit’s firmware with his own in Rust. Getting in was easier than it should have been:
- The phone app’s Bluetooth handlers exposed over-the-air update capability, plus telemetry the app never shows (motor current, battery charge history) that is also reported to the manufacturer
- The display’s “charge only” USB-C port turned out to be carrying a CAN bus on two of its pins
- The controller MCU (an APM32E103, an STM32F103 clone) still had its markings and an SWD header: OpenOCD dumped flash and RAM, and Ghidra found three images — bootloader, updater and application
- The firmware used the chip vendor’s peripheral library with no LTO, so decompiled HAL functions matched the published source closely enough to recover pin assignments and peripheral configuration
The update path is the part that should worry anyone buying one of these:
- Updates arrive over CAN with no cryptography: 64-byte chunks across nine frames with a CRC-16-CCITT checksum, and a first “chunk” that is just the filename and length as ASCII
- The new image is written over the running application in place, so a failed update bricks the display — but the bootloader always listens for update packets at power-up, which also means any scooter’s firmware can be rewritten without authentication
- He stopped at the display unit deliberately. The controller is safety-critical field-oriented-control motor code, and its own safeguard is that it shuts the motor down if the display stops sending valid throttle positions
The Rust rewrite is where the engineering content is:
- The AT32F415 is not a clone of any single ST part (STM32F1 peripherals, an STM32F3-style RTC), so Embassy could not be used as-is — he forked the STM32 support crates and built his own HAL, bringing up clocks and timers first, then ADC, UART, CAN and RTC
- With
mipidsidriving the ST7796 panel anddekudescribing bit-level CAN structs, the same protocol definitions could be reused in a desktop tool that decodes raw CAN logs into named, readable records - Firmware is a set of Embassy actors communicating over pub/sub and watch channels, which he credits with making the system easy to reason about — and which let him write an emulator to test the GUI off-device
- Redraw was the hard problem: 32 KB of RAM means no framebuffer, and redrawing every component produced unusable flicker. Tracking dirty regions in two quadtrees — one dirty, one overdrawn — narrowed updates to what actually changed
- The cost: Buoyant’s generic type expansion ate roughly 80% of the 190 KB binary
- The last bug was classic initialization order — behind the real bootloader, CAN threw framing errors because the bootloader leaves clocks and peripherals enabled, and a running clock cannot have its divisor changed. The fix is an explicit teardown of the clock registers before reconfiguring them
The value here is the method, not the novelty: un-obfuscated chips, a standard vendor HAL, no link-time optimization, a leftover debug menu still compiled in. Difficulty in reverse engineering is usually a byproduct of ordinary build choices rather than deliberate defence.
And the device’s protection is weaker than it looks. The bootloader’s recovery path and its unauthenticated write path are the same code, and what keeps a scooter from being bricked is not a check on the application image but the fact that nobody has tried.