逆向工程我的电动滑板车并用Rust语言重写固件
摘要
Ben逆向工程了Egret GT电动滑板车,通过蓝牙和CAN总线发现了隐藏功能,并用Rust语言为显示屏单元编写了自定义固件。
<p><a href="https://lobste.rs/s/pdr2bo/reverse_engineering_my_e_scooter">评论</a></p>
查看缓存全文
缓存时间: 2026/09/08 23:09
rust
::ptr()).odt().modify(|r, w| {
// 仅当掩码未覆盖所有位时,我们才需要读取之前的状态。
let prev = if const { MASK & 0xFFFF != 0xFFFF } {
r.bits() & !(MASK as u32)
} else {
0
};
let new = ((state << SHIFT) & MASK) as u32;
w.bits(prev | new)
});
}
}
fn _get_state(&self) -> u16 {
unsafe {
let unshifted = (*Gpio::ptr()).odt().read().bits() & !(MASK as u32);
(unshifted >> SHIFT) as u16
}
}
}
impl mipidsi::interface::OutputBus for Bus {
type Word = u16;
const KIND: mipidsi::interface::InterfaceKind = InterfaceKind::Parallel16Bit;
type Error = Infallible;
#[inline(always)]
fn set_value(&mut self, value: Self::Word) -> Result<(), Self::Error> {
self.set_state(value);
Ok(())
}
}
```
```rust
/// 一个 GPIO 引脚的总线
///
/// SHIFT: 我们操作的引脚范围:0 => 0..16, 8 => 8..16
/// MASK: 用于选择哪些引脚属于此总线的位掩码。该掩码未进行移位。
pub struct Bus {
_mode: PhantomData,
}
impl Bus {
fn _set_state(&mut self, state: u16) {
unsafe {
(*Gpio::ptr()).odt().modify(|r, w| {
// 仅当掩码未覆盖所有位时,我们才需要读取之前的状态。
let prev = if const { MASK & 0xFFFF != 0xFFFF } {
r.bits() & !(MASK as u32)
} else {
0
};
let new = ((state << SHIFT) & MASK) as u32;
w.bits(prev | new)
});
}
}
fn _get_state(&self) -> u16 {
unsafe {
let unshifted = (*Gpio::ptr()).odt().read().bits() & !(MASK as u32);
(unshifted >> SHIFT) as u16
}
}
}
impl mipidsi::interface::OutputBus for Bus {
type Word = u16;
const KIND: mipidsi::interface::InterfaceKind = InterfaceKind::Parallel16Bit;
type Error = Infallible;
#[inline(always)]
fn set_value(&mut self, value: Self::Word) -> Result<(), Self::Error> {
self.set_state(value);
Ok(())
}
}
```
然后,我们可以将显示屏使用的引脚声明为 Rust 类型:
```rust
pub type Bus = at32f4xx_hal::gpio::Bus<'B', 0, 0xFFFF, Output>;
pub type CsPin = Pin<'C', 13, Output>;
pub type DcPin = Pin<'C', 14, Output>;
pub type RdPin = Pin<'C', 0, Output>;
pub type WrPin = Pin<'C', 15, Output>;
pub type RstPin = Pin<'C', 1, Output>;
pub type Backlight = PwmChannel;
pub type InnerDisplay = mipidsi::Display<
mipidsi::interface::ParallelInterface,
mipidsi::models::ST7796,
RstPin,
>;
pub fn init(
mut rd: RdPin,
mut cs: CsPin,
dc: DcPin,
wr: WrPin,
rst: RstPin,
bus: Bus,
delay: &mut SysDelay,
backlight: Backlight,
) -> Display {
cs.set_low();
rd.set_high();
let interface = mipidsi::interface::ParallelInterface::new(bus, dc, wr);
let mut display = mipidsi::Builder::new(mipidsi::models::ST7796, interface)
.reset_pin(rst)
.invert_colors(mipidsi::options::ColorInversion::Inverted)
.orientation(mipidsi::options::Orientation {
rotation: mipidsi::options::Rotation::Deg0,
mirrored: true,
})
.color_order(mipidsi::options::ColorOrder::Bgr)
.init(delay)
.unwrap();
Display {
_cs_pin: cs,
_rd_pin: rd,
inner: display,
backlight,
}
}
```
现在,我们有了一个可以绘制的 [Display](https://docs.rs/mipidsi/latest/mipidsi/struct.Display.html)。通过在 Ghidra 中打开编译后的固件,我们也可以确认数据传输循环变成了一个简单的循环,将一系列字节写入单个 MMIO 寄存器:
```c
void __rustcall mipidsi::interface::parallel::send_command<>(ParallelInterface<> *self,
u8 command,
&[u8] args)
{
byte *pbVar1;
u8 *puVar2;
_DAT_40010c0c = command & 0xff;
_DAT_422202b8 = 1;
_DAT_42220238 = 1;
pbVar1 = args.data_ptr;
for (puVar2 = args.len; puVar2 != 0x0; puVar2 = puVar2 + -1) {
_DAT_40010c0c = *pbVar1;
pbVar1 = pbVar1 + 1;
_DAT_422202bc = 1;
_DAT_4222023c = 1;
}
return;
}
```
显示屏工作后,我接着实现了 CAN 和蓝牙协议的编码和解码。为此,我使用了 [deku](https://github.com/sharksforarms/deku),因为它允许你使用相当简洁的宏来声明结构体的字节和位级解析器^6 ([https://bensimms.moe/reverse-engineering-scooter/#can-proto-impls](https://bensimms.moe/reverse-engineering-scooter/#can-proto-impls)):
```rust
/// 5132
#[derive(deku::DekuRead, deku::DekuSize, defmt::Format, Clone, PartialEq, Eq)]
#[cfg_attr(test, derive(deku::DekuWrite, Debug))]
#[deku(bit_order = "lsb", endian = "little")]
pub struct ControllerSpeed {
/// 单位为 km/h * 100
#[deku(pad_bytes_after = "2")]
pub motor_speed: u16,
#[deku(bits = 1)]
pub walk_mode: bool,
#[deku(bits = 1)]
pub headlight_on: bool,
#[deku(bits = 1, pad_bits_after = "5")]
pub brake_light_on: bool,
}
#[test]
fn test_display_throttle() {
let mut buf = [0u8; 8];
deser_roundtrip(&mut buf, &DisplayThrottle::new(511, false, false, 0));
assert_eq!(buf, [0xff, 0b1, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
deser_roundtrip(&mut buf, &DisplayThrottle::new(511, true, false, 0));
assert_eq!(buf, [0xff, 0b011, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00]);
deser_roundtrip(&mut buf, &DisplayThrottle::new(511, true, true, 2));
assert_eq!(buf, [0xff, 0b111, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00]);
deser_roundtrip(&mut buf, &DisplayThrottle::new(1, false, true, 2));
assert_eq!(buf, [0x01, 0b100, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00]);
deser_roundtrip(&mut buf, &DisplayThrottle::new(256, false, true, 2));
assert_eq!(buf, [0x00, 0b101, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00]);
}
```
用 Rust 做这件事的一个妙处在于,我可以将这些定义用在一个完全不同的程序中,将 CAN 日志解码为人类可读的形式:
```
L1 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L2 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48500, current_ma: 3094 })
L3 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L4 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L5 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48500, current_ma: 3094 })
L6 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L7 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L8 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L9 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L10 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L11 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48499, current_ma: 3075 })
L12 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L13 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L14 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L15 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L16 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L17 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48499, current_ma: 3075 })
L18 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L19 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L20 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L21 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L22 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L23 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48498, current_ma: 3055 })
L24 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L25 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L26 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L27 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L28 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L29 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48498, current_ma: 3055 })
L30 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L31 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L32 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L33 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L34 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L35 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48500, current_ma: 3049 })
L36 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L37 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L38 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L39 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L40 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L41 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48500, current_ma: 3049 })
L42 id=1028 ext=false from=battery BatteryCapacityTemp(BatteryCapacityTemp { capacity_mah: 20000, battery_charged: true, battery_charging: false, battery_temp: 270 })
L43 id=774 ext=false from=display DisplayThrottle(DisplayThrottle { throttle: 85, left_blinker: false, right_blinker: false, speed_limit: 0, magic: DekuConst })
L44 id=513 ext=false from=controller ControllerSpeed(ControllerSpeed { motor_speed: 0, walk_mode: false, headlight_on: false, brake_light_on: false })
L45 id=515 ext=false from=controller ControllerSpeedMode(ControllerSpeedMode { unknown: 33 })
L46 id=528 ext=false from=controller ControllerSpeedLimit(ControllerSpeedLimit { speed_limit: false })
L47 id=768 ext=false from=display DisplaySpeedMode(DisplaySpeedMode { mode: 0, mode_high: 90, headlight: 100, magic: Normal, speed_mode_byte: 0, walk_counter: 0 })
L48 id=494 ext=false from=display unknown [60, 00, 00, 00, 00, 00, 00, 00]
L49 id=495 ext=false from=unknown unknown [4c, 44, 2e, 43, 52, 2e, 53, 38]
L50 id=495 ext=false from=unknown unknown [30, 37, 2e, 43, 2e, 32, 2e, 31]
L51 id=495 ext=false from=unknown unknown [45, 47, 2e, 32, 2e, 32, 2e, 31]
L52 id=495 ext=false from=unknown unknown [31, 00, 00, 00, 00, 00, 00, 00]
L53 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L54 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L55 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L56 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L57 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L58 id=768 ext=false from=display DisplaySpeedMode(DisplaySpeedMode { mode: 0, mode_high: 90, headlight: 100, magic: Normal, speed_mode_byte: 0, walk_counter: 0 })
L59 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L60 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L61 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L62 id=495 ext=false from=unknown unknown [00, 00, 00, 00, 00, 00, 00, 00]
L63 id=1856 ext=true from=display unknown [4b, 00]
L64 id=1024 ext=false from=battery BatteryCommandState(BatteryCommandState { command: 16384, state: 1159, estimated_range: 0 })
L65 id=1025 ext=false from=battery BatteryVoltageCurrent(BatteryVoltageCurrent { voltage_mv: 48500, current_ma: 3044 })
L66 id=1857 ext=true
相似文章
I hacked into the worst e-bike and fixed it [video]
作者通过逆向工程、破解蓝牙密码并自制定制屏幕,成功修复了被称为“世界上最差电动自行车”的Revo无轮毂自行车,使其所有功能恢复正常并增加了新功能。
远程解锁电动滑板车
一位安全研究人员详细描述了通过对公司网络基础设施进行侦察,并利用WordPress和操作面板中的漏洞,远程解锁电动滑板车的过程。
@blackanger: 从 Epic Lora 里蒸馏了不少好的实践
An experiment on using Rust's type system as an AI coding specification, distilling practices from Epic Lora.
修复我的吉他音箱固件
作者通过UART和JTAG逆向工程了Yamaha THR10c吉他音箱的固件,转储并修补了它,以修复旁路增益问题,并构建了一个API。
用 Rust 重写
本文评估了2026年的‘Rewrite It In Rust’运动,讨论了现实世界中的性能提升、诸如新错误和平台支持等挑战,并提倡增量重写而非完全重写。