逆向工程我的电动滑板车并用Rust语言重写固件

Lobsters Hottest 新闻

摘要

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]

Hacker News Top

作者通过逆向工程、破解蓝牙密码并自制定制屏幕,成功修复了被称为“世界上最差电动自行车”的Revo无轮毂自行车,使其所有功能恢复正常并增加了新功能。

远程解锁电动滑板车

Hacker News Top

一位安全研究人员详细描述了通过对公司网络基础设施进行侦察,并利用WordPress和操作面板中的漏洞,远程解锁电动滑板车的过程。

修复我的吉他音箱固件

Lobsters Hottest

作者通过UART和JTAG逆向工程了Yamaha THR10c吉他音箱的固件,转储并修补了它,以修复旁路增益问题,并构建了一个API。

用 Rust 重写

Hacker News Top

本文评估了2026年的‘Rewrite It In Rust’运动,讨论了现实世界中的性能提升、诸如新错误和平台支持等挑战,并提倡增量重写而非完全重写。