Design Solutions Research & Design Hub

Building Against Fault Injection Attacks

Written by Colin O'Flynn

Cautious Coding

Fault injection are powerful attacks for bypassing security mechanisms. Rather than work on just showing the attacks, in this article Colin demonstrates how you can start to protect against them with some modest changes to your code flow.

In several articles now, I’ve brought up the idea of fault injection (FI) attacks, and how they could be used to bypass security. I previously demonstrated this as a method of dumping a private key from a USB key (Circuit Cellar 346, May 2019), as well as demonstrating how you could bypass fuse bytes (Circuit Cellar 338, September 2018), and how electromagnetic fault injection works (Circuit Cellar 350, September 2019).

I also gave an overview of several fault injection attacks in my January 2018 article (Circuit Cellar 330). All of those have been about the offense. So, in this article, I’m going to discuss the defense—how you can help improve your code against such attacks. Check out some of those old articles for more details on how we perform FI attacks as well. With all that in mind, let’s dig in!

WHAT ARE FAULT ATTACKS?
While I’ve covered fault attacks previously in more detail, it’s worth recapping what exactly these attacks can accomplish. A fault attack is one where an attacker modifies the flow of the program, normally in order to bypass security mechanisms. These bypasses can have devastating effects. We often rely on things like fuse bytes to protect our IP programmed into a microcontroller (MCU) for example, or we rely on a signature operation to ensure that only valid code is loaded onto the MCU.

Unfortunately, there isn’t much you can do when the MCU features themselves are vulnerable to a FI attack. If using the LPC1114 from NXP Semiconductors that I demonstrated attacking in my January 2018 (Circuit Cellar 330) article (based on work by Chris Gerlinsky), you must try to rearchitect your system to work within the new security bounds. This would mean not storing any critical secrets with the flash, because you know it can be easily read by an attacker.

Luckily, not all devices have easily exploitable implementations. This means you are given a useful starting point, but it’s easy to quickly shoot yourself in the foot. As two examples, let’s first look at a simple output routine in Listing 1. This C-level code might not have an obvious exploitable defect, being just a simple loop, right? First let’s take a look at Listing 2, which is the assembly code generated by a recent Arm GCC compiler. If you want to explore the connection between C and ASM, be sure to check out
godbolt.org which is an online compiler explorer as shown in Figure 1.

void write_bytes(char * data[], unsigned int datalen) {
for(int i = 0; i < datalen; i++){
uart_write(data[i]);
}
}

— ADVERTISMENT—

Advertise Here

LISTING 1 – A simple function for sending a buffer over a serial port

write_bytes:
push {r4, r5, r6, lr}
subs r5, r1, #0
beq .L1
sub r4, r0, #4
add r5, r4, r5, lsl #2
.L3:
ldr r0, [r4, #4]!
bl uart_write
cmp r5, r4
bne .L3
.L1:
pop {r4, r5, r6, lr}
bx lr

LISTING 2 – The resulting arm assembly code from Listing 1

FIGURE 1 – This is an example of comparing C to ASM using Godbolt.org, which will be a useful resource as you explore examples in this article.

Now, the loop ending in Listing 2 has been converted to a “branch if not equal” or bne instruction. This minor fact has a very significant implication for our fault injection attack: Should an attacker “skip” a single comparison during the end value of the loop, the loop will now continue to iterate until the integer value wraps around! And this type of effect is exactly what an attacker can do in practice, meaning they can suddenly dump huge sections of code. If the loop was done with a “branch if less than” instruction, the attacker would need to skip that instruction on every iteration through the loop.

This type of attack was demonstrated by Micah Scott for dumping an entire MCU firmware over USB. A detailed video on this is provided here:

Other people have used the attack in a similar fashion, causing a target device to simply “read out” memory. You can see how minor changes in program flow (in this case the compiler adding a “branch if not equal“) have big effects, so I wanted to take you through a few more obvious “poor design choices” that make you especially vulnerable to fault injection attacks.

FAULTY TOWERS
Now the previous example might be interesting, but it’s not the most common target. In fact, the most common target is typically a signature or password check function. If you take a look at most bootloaders on the market nowadays, what you’ll find is a piece of code that looks something like Listing 3. This type of logic is found in almost every embedded bootloader I’ve recently examined, so I won’t single any particular vendors out (but they know who they are!).

void * boot_image;

load_image(boot_image);

if (verify_image(boot_image)) {
jump_to_image(boot_image);
}

boot_backup_image();

LISTING 3 – A simple bootloader that performs image verification on a received image

— ADVERTISMENT—

Advertise Here

What is the problem with this? If you skip the signature check, suddenly you are booting the unvalidated image. While the attack does require some level of physical access to perform, the typical attack vector has someone performing this attack once to dump code memory. Once the attacker has the code memory, they may be able to find other vulnerabilities or even read out sensitive (secret) keys they can then use to perform a more advanced attack.

Rather than skipping the check, another pattern is some sort of “jump to infinite loop,” as shown in Listing 4. Again, it’s not always the case that the designer intended to generate this program flow, but that the compiler may have inserted it. This infinite loop is a poor practice, since an attacker doesn’t need to be particularly clever with their timing to jump out of the loop. In the example in Listing 4, we perform the same type of image validation as Listing 3. But in the program flow from Listing 4, a failed image means we go into an infinite loop that requires a system reset. But an attacker can instead send an incorrect image, and then perform a fault injection attack to skip one of the branch instructions making up the infinite loop.

void * boot_image;

load_image(boot_image);

// verify_image() Returns -1 if
// verification fails
if (verify_image(boot_image) < 0) {
//User must reset device to retry
while(1);
}

jump_to_image(boot_image);

LISTING 4 – Program flow using an infinite loop after a failed comparison

Once the attacker breaks out of the loop, the code continues to execute as if a valid image was loaded. This type of code flow goes back to satellite TV smart-card days. (some of you may remember the idea of “unloopers.”) It’s particularly vulnerable because it doesn’t require the careful timing that the code flow from Listing 3 required.

LEANING TOWERS
While the previous fault vulnerabilities might seem obvious (at least in retrospect), it’s not always easy to prevent them from being attacked. Consider an attempt someone has made in Listing 5 to protect their comparison function by adding some time jitter. The idea here being that an attacker breaking Listing 3 would be sweeping though time to find the right location where the comparison happens. By adding significant time jitter in Listing 5, it means an attacker no longer has perfect timing. This doesn’t necessarily make the attack impossible, but it should make the attack much harder to replicate.

void * boot_image;
load_image(boot_image);
delay(random());

if (verify_image(boot_image)) {
jump_to_image(boot_image);
}
while(1);

LISTING 5 – Time jitter used to attempt and complicate FI attacks

The problem is that if we again look at the assembly code in Listing 6, you can see the jump to the delay function could be skipped! This might require two faults in a row, which may be reasonably practical as just requires skipping multiple instructions compared to one.

Since you likely came to this article for guidance and not more examples of incorrect code, let’s move on to how we can do this correctly.

main:
push {r4, lr}
mov r4, #0
mov r0, r4
bl load_image
bl random
bl delay
mov r0, r4
bl verify_image
cmp r0, r4
bne .L8
.L5:
b .L5
.L8:
mov r0, r4
bl jump_to_image
b .L5

LISTING 6 – Assembly code from Listing 5 shows the delay itself can be skipped.

POWER TOWERS
First, we need to understand that adding fault tolerance is likely to add overhead in both code size and speed. But we can get away with some pretty minor adjustments. One example of more difficult-to-fault code is given in Listing 7. The major change is I’ve now introduced two variables. I first load the untrusted image into test_image, rather than directly into the final image that we’ll boot. The other thing I do is push the actual assignment of boot_image into the comparison function itself, where we can do more complex operations.

void * test_image;
void * boot_image = ERROR_HANDLER_ADDRESS;
unsigned int status = 0;

load_image(test_image);

delay(random());
status = verify_image(test_image, &boot_image)
//verify_image copies test_image to boot_image
if (status == 0xDEADF00D) {
//Looks OK...
delay(random());
jump_to_image(boot_image);
} else if (status == 0xF4110911) {
//Signature failed
test_image = NULL;
boot_image = NULL;
while(1);
} else {
//Unexpected result - fault attack??
erase_sensitive_data();
while(1);
}
boot_backup_image();

LISTING 7 – Simple fault injection armored code from Listing 3

— ADVERTISMENT—

Advertise Here

Now the value of boot_image will only be set to the trusted value somewhere inside the verification function. In addition, I’ve made more complex return values that are less likely to be faulted. The function comparison is checked against a specific value, rather than just checked against being non-zero. Should an attacker be corrupting memory instead of skipping instructions, they will find it more difficult to corrupt memory into the specific value I’m checking. With this, I can also detect unexpected operating conditions that could be an ongoing attack. Our ability to respond will depend on the device. Such failures could in fact be innocent mistakes—such as ESD discharge or corrupted memory—but we are now making conscious decisions to deal with the detection flag.

Now I’ve hidden the special verify_image() function away from you, so we also need to explore that a little before I can claim I’ve given you a complete look. This is shown in Listing 8. What makes this function more difficult to glitch? First off, you’ll notice comparisons are done multiple times. In this case there are several comparisons that check if the expected hash matches the calculated, and if that fails it will return a failure flag.

unsigned int verify_image(void * image, void ** boot_ptr)
{
//We’ll compare expected_hash to hash
unsigned int expected_hash = get_known_hash();
unsigned int hash = calculate_hash(image);

//We also mask the value of the pointer we will jump to
//Correctly executing code will remove these effects to
//leave the original image pointer.
void * possible_ptr = (void *)get_known_hash() ^ image;
possible_ptr ^= (void *)(1 << 14);
possible_ptr ^= (void *)(1<<15);

//Perform multiple tests
if (expected_hash != hash) return 0xF4110911;
if (expected_hash == hash) possible_ptr ^= (void *)(1 << 14);
delay(random());
if (expected_hash == hash) possible_ptr ^= (void *)(1 << 15);
if (expected_hash != hash) return 0xF4110911;
delay(random());
if (expected_hash == hash) possible_ptr ^= (void *)expected_hash;
if (expected_hash != hash) return 0xF4110911;
if (expected_hash == hash) *boot_ptr = possible_ptr
if (expected_hash == hash) return 0xDEADF00D;
return -1;
}

LISTING 8 – Details of the verification function, which requires correct execution to result in a useful result

The other major change, is that there is no single comparison that carries the sensitive operation. You’ll notice that the critical variable in this case is the possible_ptr variable. If the hash comparison is successful, this variable will get copied to the boot_image variable. But several “unmasking” steps are needed for the valid value to get loaded, including toggling several bits that will otherwise cause this to point to some invalid memory area. In theory, the call in Listing 7 to jump_to_image() with the expected image point will only occur if every comparison passes successfully in Listing 8.

Of course, this is all done from the C code level! Looking at assembly you can still identify some potential risky points. For example, what if the calls that were supposed to initialize expected_hash and hash never happen? Well, suddenly this means the entire comparison would pass! So additional guarding of the variables is needed to ensure you cannot simply skip that initial setup. But keeping fault attacks in mind is the most critical first step in designing truly secure embedded systems.

TOWER GUARD
How can you use this in practice then? I’ve already shown you that fault injection attacks are a serious threat to any embedded system. A careful review of your code should show you were attackers might find the most valuable targets, and you can concentrate on building fault injection resistant code around those points.

To help you out here, I’ve released an open-source library called ChipArmour (being Canadian I keep the “u” in Armour) that uses some of these best practices. You can either use the library as a reference for building fault-injection resistant code, or directly integrate it into your firmware project. This library is released under a permissive Apache license, so you can use this in both your own open-source and commercial projects.

My future columns will explore ChipArmour in more detail. This is still in an early beta, so you may not find a complete build available when you are reading this column. But I wanted to first bring you through the specifics of how fault injection attacks can be applied to a simple codebase, and how you can reduce the vulnerability of your existing code to fault injection attacks with some small modifications. 

Additional materials from the author are available at:
www.circuitcellar.com/article-materials

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JANUARY 2020 #354 – Get a PDF of the issue


Don't miss out on upcoming issues of Circuit Cellar. Subscribe today!

 
 
Note: We’ve made the October 2017 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.


Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Become a Sponsor
Website | + posts

Colin O’Flynn, writes the column Embedded System Essentials for Circuit Cellar. Colin has been building and breaking electronic devices for many years, and is currently completing a PhD at Dalhousie University in Halifax, NS, Canada. His most recent work focuses on embedded security, but he still enjoys everything from FPGA development to hand-soldering his prototype circuits. Some of his work is posted on his website.