The debug interface of the STM32F1 chip cannot have the debugger attachment disabled. Instead, there is a Flash Memory Read Out Protection (RDP) instead; this will block all data access via the debug interface. This article is about bypassing RDP.
While playing around with a development board with RDP turned on, the authors ran the reset halt command. When doing this, they get the following output: xPSR: 0x01000000 pc: 0x08000268 msp: 0x20005000. Why is this interesting? Raw register values are sent back, which we shouldn't have access to.
Why does this happen? A reset is a special kind of exception. When an exception is called, the processor loads the exception entry address from the vector table to know what to do - vector fetch. Since this is stored in flash memory, how can that vector be accessed?
The reset vector is fetched via the ICode bus. So, the fetching of the reset information is done via the instruction fetch line instead of the standard data line. The bus being used in the reason why the read out protection doesn't work in this case!
In ARMv7-M there is a Vector Table Offset Register (VTOR) that determines the location of the vector table in the address space. This is normally used to relocate the VTOR when going between applications but can be abused. By changing the VTOR, we can relocate the vector table within the flash memory region!
Since the ICode bus passes back information via the PC and returns the address, with interrupts, we can abuse the trust mentioned above to slowly read out information we shouldn't have access to. Again, we can control everything besides flash via the debugging interface.
Several items in the VTOR are inaccessible for functional reasons. However, we can wrap around the values (max is 32) to still access the vectors! For instance, the normally inaccessible 1 and be accessed by using the interrupt 33 but there are some limitations to it.
Now, for the moment of truth - extracting the information. This is done by doing the following steps:
-
Perform a device reset to put the microcontroller into a well-defined state.
-
Configure the microcontroller to trigger an exception of our choosing. Only a handful can be truly triggered via this method though.
-
Single step in order to make the exception active. At this point, we can extract the data we would like.
Overall, this method, even with its shortcomings, was able to extract around 90% of the code in less than an hour on all of the chips. This is a pretty incredible feat by a small oversight on the part of the developers. Amazing blog post!