修补脉搏血氧仪固件
Patching Pulse Oximeter Firmware

原始链接: https://stefan-gloor.ch/pulseoximeter-hack

## Beurer PO 80 脉搏血氧仪:深度分析 该项目涉及对一款经济型Beurer PO 80脉搏血氧仪的详细分析,旨在了解其功能和安全性。初步调查配套的“SpO2 Assistant”软件引发了对“德国工程”声称的质疑,揭示了可能存在重复使用的代码。通过协议嗅探器反向工程了与设备的通信,从而创建了一个Python工具来读取脉搏和SpO2数据。 拆解显示该设备使用GigaDevice GD32F350RBT6微控制器和一个调试连接器。尽管启用了闪存读取保护,但利用已知的硬件漏洞(感谢a1exdandy的研究)成功转储了固件——导致芯片变砖,但可以通过更换芯片恢复。 固件补丁禁用了运行时安全措施和睡眠模式,从而实现了完全的调试访问。然后分析了显示功能,允许动态补丁缓冲区地址以显示自定义图像,甚至包括Doom启动画面。虽然运行Doom是可行的,但作者计划专注于安全研究,特别是探索USB协议中潜在的远程代码执行漏洞。该项目强调了即使在廉价医疗设备上进行逆向工程的可及性。

一个黑客新闻的讨论围绕着一个补丁脉搏血氧仪固件的项目。最初的帖子详细介绍了过程中遇到的挑战,主要是令人沮丧的硬件错误:黑客由于零件编号非常相似,意外订购了一个闪存容量为原始型号(GD32F350RBT6)一半的微控制器(GD32F350R8T6)。 评论者指出,这种命名方式在STM32克隆产品中很常见,有时即使超过报告的大小,也可以成功刷新更大的镜像。讨论还涉及了设备的自我修复安全措施以及使用一次性可编程熔丝的潜在固件保护方法。最后,用户指出在速卖通上可以买到非常便宜的类似设备,这使它们成为有吸引力的黑客目标,但有人开玩笑地抱怨缺乏《毁灭战士》的兼容性。
相关文章

原文

Recently, I came across relatively cheap medical devices: consumer-grade pulse oximeters. These devices clip onto your finger and shine a light through it. By analyzing the light transmitted through your finger, the device can infer your pulse and blood oxygen saturation.

In this project, I specifically looked at the Beurer PO 80. This is a German-engineered medical device (according to the box) for less than $100 at reputable resellers. It has a USB port for connecting to a PC to view pulse and SpO2 in real-time and to download previously recorded data.

PC Software

These pulse oximeters are compatible with the free “SpO2 Assistant” software. This software seems to support a variety of different pulse oximeter models. It plots pulse and SpO2 data in real time, allows for exporting recorded data, and lets you configure some basic settings of the pulse oximeter like patient name (no idea why this would be necessary) or the current date and time.

First, I unpacked the SpO2 Assistant software and loaded it into Ghidra. My initial plan was to reverse-engineer the custom USB HID protocol that the Beurer PO 80 seems to use. Quickly, I stumbled upon embedded strings and a logo that makes me question the “German engineering” claim. But to be fair, the software was technically not part of the pulse oximeter itself.

I soon realized that static decompilation is probably not the most effective way to understand the USB HID protocol. Instead, I connected the pulse oximeter and used a protocol sniffer to eavesdrop on the communication between device and PC software.

With this dynamic analysis method and some trial-and-error, I was able to partly reverse-engineer the protocol. I wrote a Python tool that can initialize and fetch pulse and SpO2 data from the Beurer PO 80.

Device Teardown

As a next step, I took the pulse oximeter apart. It disassembles nicely without any screws or glue. The build quality is definitely not outstanding, but not surprising at this price. You can find suspiciously similar devices for less than $10 on Aliexpress.

The device has a 240 x 240 color display and a user button on the front side. The main microcontroller is a GigaDevice GD32F350RBT6, a 108 MHz, Arm Cortex-M4 core with 128 kB flash and 16 kB SRAM. Additionally, there is a serial flash memory chip for recording data. An accelerometer detects the current orientation and rotates the display accordingly. There is also room for a Bluetooth module that is not populated on the USB-only PO 80.

Conveniently, there is a debug connector that exposes the SWD debug interface of the microcontroller. With this, I was hoping to dump the firmware for further analysis.

Bypassing Flash Readout Protection

After connecting a debug probe, the device was successfully detected, but I could not dump the firmware. The device had “low-level protection” mode enabled. While in this mode, SRAM and memory-mapped peripherals can be read through the debugger, but read-out of flash is prohibited; only code can access flash. Also, boot from SRAM is disabled in this mode, to prevent loading a flash dumper directly into SRAM.

I used a known hardware vulnerability of these microcontrollers to bypass the read-out protection. The die revisions used in my device were still vulnerable. Huge thanks to a1exdandy, the author of the original research, for publishing the blog post and helping me get his exploit to work.

With this exploit, I could successfully dump the firmware of the device. Due to the nature of the exploit, the chip had to be completely bricked during this process, i.e., the SWD debug port had to be completely disabled. This, however, is not a problem. I can just buy a new microcontroller, flash the original firmware that I just dumped, and now I have a fully unlocked development device.

As a side note: replacing the chip took longer than expected. I accidentally ordered a GD32F350R8T6, instead of the GD32F350RBT6 that was in the device originally. These two types differ in their flash sizes: 64 kB vs 128 kB. Don’t ask me why GigaDevice thought this naming scheme and this font was a good idea. I only realized my mistake after a few hours of debugging, where I noticed that only half of the firmware would be flashed correctly. After reordering the correct type, my pulse oximeter was working again.

Customizing Firmware

After resurrecting the device, connecting GDB and stepping through the code still did not work as smoothly as expected. This was mainly due to two things: First, the firmware would automatically enable low level protection again at run time. Hence, the microcontroller can only be debugged once. Second, the device would enter a sleep mode after a few seconds of inactivity, even if a debugger was connected. By searching for references to the option byte control register, I quickly identified the following snippet in the firmware:

By cross-referencing with the datasheet, you can see that this indeed enables low-level protection.

By changing 0xBB to 0xA5 in a hex editor, I was able to successfully patch out this protection mechanism; the microcontroller would now stay unlocked indefinitely.

Similarly, I was able to resolve the second problem by patching out the watchdog configuration and the deep-sleep enter (wfi instruction).

Using the Display

Next, I wanted to access the display. While I could reverse-engineer the PCB layout and rewrite the firmware from scratch, I wanted to reuse as much of the original firmware as possible.

To identify code that is responsible for accessing the display (there are no symbols or debug messages in the binary), I started the pulse oximeter and interrupted it with a debugger while it was displaying a splash screen. From there, I could backtrack and identify the relevant function.

This function essentially looks like this:

void display_draw(char* buf, int x, int y, int width, int height)
                

i.e., it can display an arbitrary-sized buffer at an arbitrary location on the display. To display the Doom splash screen, I patched the image data into an unused section of the flash memory. Instead of manipulating the function call in the binary (which can be a bit tricky), I used the following GDB script to dynamically patch the buffer address on the stack at run time, right before the display_draw function call.

target extended-remote :4242      # Connect to the target
b *0x08007f06                     # Set break before display_draw
define hook-stop                  # Define a hook, run at break
x/10x $sp                         # Print the current stack
set {int}0x20001698 = 0x08002000  # Replace the buffer on the stack
continue                          # Resume execution
end                               # End of hook definition
monitor reset                     # Reset the target
continue                          # Continue until break

While this allows me to draw anything I want on the display, it is not actually running Doom yet. For this, I would probably have to start using a compiler instead of only a hex editor.

Since I can freely program, patch, and debug the microcontroller firmware, this is definitely doable. However, I think it would be more interesting to leverage this level of access for further investigation of the existing firmware, e.g., to look for exploitable memory corruption vulnerabilities. It would be really cool if, e.g., a buffer overflow in the custom USB HID protocol could be used to gain code execution without physical access to the device.

Please reach out if you would like to contribute to this challenge. I may follow-up with this project in the future, but for now, I will focus on other things. Thanks again a1exdandy for the help with bypassing the GD32 readout protection and others for their help and advice.

联系我们 contact @ memedata.com