Introduction
In a previous article, we discussed the countermeasures implemented in MCUBOOT and the validity of the previously performed security assessment.
In this article, I’m going to present you Funicorn, our home made simulation based fault injection tool. Funicorn (Fault unicorn) will be used to demonstrate that MCUBOOT is far from being protected against fault injection attacks when fault injection countermeasures are not enabled.
Funicorn fault injection simulator
To carry out these new experiments a new fault injection simulator based on Unicorn is developed and named Funicorn. Actually it’s a heavily modified fork of Rainbow tool from Ledger Donjon to allow – more – efficient fault injection simulations.
At the beginning a tool based on Qemu and GDB was used but its flexibility was limited. Frequent crash of Qemu, weird support of Python by GDB and other difficulties forced us to switch from QEMU + GDB combo to Unicorn + Python. However, we kept the philosophy of the first tool when we developed the second. In short, the evaluator selects the functions that have to be tested, the fault models and defines the entry point and the output points (success, failure, detection) of the software under test (SUT). Three fault models are currently supported:
- Instruction skip
- Register set to 0x00000000
- Register set to 0xFFFFFFFF
Entry and output points can be defined using either label inserted into the source code thanks to __asm directives or at the entry point of a given function.
Faults are injected at a given address in the software binary, this approach differs from the one used by the Rainbow tool which injects the faults after a given number of instructions is executed. This mode of operation allows us to efficiently solve the “unrolling loop” problem. Concretely a tool that would inject a fault at the ith executed instruction (then i+1th and so one) would inject a fault at each instruction and during each iteration of a loop which is time consuming and not always relevant. This problem could be tackled by jumping at the end of the loop but doing this automatically is not easy. In contrast, using unicorn hooks and python static variables allows to inject a fault at the ith execution of an intrusion placed at a given address. Evaluators can easily bypass the remaining executions of the instruction by setting a maximum execution depth. Be careful however because this mode of operation may introduce subtle “bugs”. That is, if a function is used many times and the max. execution depth is set to 1, only the first call of this function would be faulted (however Funicorn returns to the user the total number of times an instruction is executed which allows to see the problem).
Another limitation of the tool is that you have to define the functions which have to be tested and all the instructions of this function are tested, even those which are not actually executed. Actually, using a golden run as reference would save a lot of processing time which would allow to improve the code coverage by setting a higher value for the instruction max. depth parameter.
Because Unicorn is far much slower than Qemu, multiprocessing is used to speed up the fault injection simulation. In our case, the address space is divided in twelve slices and a pool of twelve processes is launched which greatly improves the performances.
Results on MCUBOOT with disabled countermeasures
First, Funicorn was used on a vanilla implementation of MCUBOOT to demonstrate that the bootloader is strongly vulnerable to fault injections. MCUBOOT v1.9.0 was compiled through TF-M v1.6.0 using the following TF-M configuration (optimization level internally set to -03):
cmake -B ./build -DTFM_SPM_LOG_LEVEL=TFM_SPM_LOG_LEVEL_INFO \ -DCMAKE_BUILD_TYPE=Release \ -DTFM_TOOLCHAIN_FILE=toolchain_GNUARM.cmake \ -DTFM_PLATFORM=arm/mps2/an521 -DTEST_NS=ON -DTEST_S=ON \ -DTFM_PSA_API=ON -DMCUBOOT_PATH=~/mcuboot \ -DMCUBOOT_LOG_LEVEL=INFO .
When compiling the TF-M configured in this way, two binaries are generated, the first one is MCUBOOT and the second one a signed image of the TF-M. The signature of this image is then damaged and the two binaries are loaded into Qemu to check the proper behavior of the software. In this scenario, MCUBOOT and the TF-M binaries are configured in a way that MCUBOOT checks the hash of the TF-M binaries then its signature. Because the signature is damaged, the firmware image is rejected and MCUBOOT panics as illustrated in the following snippets.
$qemu-system-arm -M mps2-an521 -kernel ./build/bin/bl2.elf \ -device loader,file=./build/bin/tfm_s_ns_signed.bin.damaged,addr=0x10080000 \ -display none -serial stdio
[INF] Starting bootloader [INF] Beginning BL2 provisioning [WRN] TFM_DUMMY_PROVISIONING is not suitable for production! This device is NOT SECURE [INF] Swap type: none [INF] Swap type: none [ERR] Image in the primary slot is not valid! [ERR] Unable to find bootable image
When launching Funicorn on these binaries with an instruction max. depth of two and scanning the following functions:
- bootutil_img_validate
- boot_fih_memequal
- bootutil_cmp_rsasig
- bootutil_verify_sig
- boot_nv_security_counter_get
- boot_image_check
- boot_validate_slot
- split_image_check
- boot_load_and_validate_images
- context_boot_go
- boot_go
The following results are obtained after 90 minutes (Ubuntu WSL2 on Win 11, Ryzen 5 4680U, 16 Go):
Results | Nb of occurrences |
---|---|
No effect | 12866 |
No injection | 24840 |
Crash | 2120 |
Timeout | 150 |
Success | 116 |
Here, a “Crash” corresponds to a fault injection causing an access to an unmapped memory region. A “ Timeout” corresponds to either a panic (infinite loop) or an unexpectedly long execution time. Finally, 116 fault injections were successful meaning that there are at least 116 timings during MCUBOOT execution where a fault injection can lead to a successful execution of an image with a corrupted signature. There are actually far more vulnerabilities than 116. Indeed, coverage was not complete due to an execution max. depth set to two. In addition, all sensitive functions are not covered (here boot_fih_memequal() calls memcmp() which is not tested because Funicorn does not follow function calls).
Conclusion
These results show that MCUBOOT is clearly vulnerable to fault injection attacks when countermeasures are not enabled (which is the case by default). In the next article, I show that MCUBOOT is vulnerable whether countermeasures are enabled or not.
TrustnGo markets products and services which can help you to secure your products.