Side Channel and Fault Injection Attacks with the ChipWhisperer

Aswin

2022-01-12

Couple months ago, I had participated in the CSAW Embedded Security Challenges (ESC)- a perennial competition about security of embedded systems and devices. Each year, the competition comes with a different theme and the time around, it was about Side Channel and Fault Injection attacks with the ChipWhisperer Nano. The team and I had a fun time learning and getting used to them.

This blog is aimed at giving a short glimpse on what SCAs and other things in its realm are in a nutshell. In between, I’ll be explaining how we approached a few of the challenges, as well.

Introduction

Side channel attacks (SCAs) and Fault Injection attacks (FIAs) fall under the attack vectors which target the external particulars of the computer system instead of trying to exploit the actual software or cryptographic protocol. Figuring out what a computer is doing by analysing the power usage statistics, i.e., understanding something using a “side channel”, is a fine example of what an SCA is. Historically, these vectors are often detected and utilized through the holes left open by the chip manufacturers or the system vendors. For example, trying to sneak in through the parts of the debug mode of a chip or through some other information left by the software’s operation. Spectre is probably the most famous and easily relatable example of a vulnerability that is exploited through a Side Channel Attack.

Before jumping right into SCAs, let me elaborate a bit on the scheme of things so that you’ll feel more used to the workflow of CSAW ESC, ChipWhisperer and the things that I’ll be talking about.

A little on the ChipWhisperer

We’ll be talking about doing Side Channel Attacks with a ChipWhisperer.

The ChipWhisperer Nano is a device which provides a fast and easy way to learn and get used to Side Channel and Fault Injection attacks. It’s a small device and it’s regularly used for training and research purposes. The device has two sections: one that has the target and the other that is used for measurement purposes. The target is a STM32F030F4P6 which has 16KB of FLASH and 4KB of SRAM. The target is where we’ll be flashing (loading) our files into.

The ChipWhisperer has a great, free and open source software toolchain and it’s available as a Python package. It’s built on top of PyUSB and allows seamless interaction with all parts of the device in Python and offers amazing APIs.

The workflow of CSAW ESC 2021

For each challenge (puzzle or the CTF problem), the precompiled (.hex) file and the pseudo- code (.c) files are given.

The .hex file is what you flash onto the target of the ChipWhisperer and the pseudo-code file is to understand what’s been implemented in that challenge. The .c file is more of like the actual source code of the challenge, but it’ll only have enough information to let you know about what it does and what you need to do.

Therefore, the workflow for each challenge is:

Side Channel Attacks

We defined what SCAs are a while ago: they target the implementation of the system.

SCAs are broadly subdivided on the basis of the source of the data. Generally, you’ll hear about Timing Attacks and Power Analysis attacks, and that means that the time and the power taken are taken into consideration and analyzed, respectively.

Power Analysis Attacks

Simple power analysis involves making deductions from the trends occurring in power over a period of time. This is possible as the power consumption varies while the device performs different operations when measured with a device such as a standard digital oscilloscope. Obviously, this can be measured only on small embedded devices.

But the ChipWhisperer makes things a lot easier. You can just call a particular API and it’ll return the power traces as a Python List. We’ll get to all of that shortly.

We’ll begin by taking a look at this code, which is the pseudo code of the challenge recall from the first set of challenges of the CSAW 2021 Finals. You can grab the full source here.

Just take a look at the loop and how the elements of the data (our input) are compared and handled.

// DUMMY VAL
uint8_t correct_mem[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

uint8_t verify(uint8_t* data, uint8_t dlen) {
  uint8_t mem_different = 0;
  uint8_t zero = 0, one = 1;

  trigger_high();
  for (uint8_t i = 0; i < sizeof(correct_mem); i++) {
    if (correct_mem[i] != data[i]) {
      mem_different = 1;
      break;
    }
  }
  trigger_low();

  if (mem_different) {
    simpleserial_put('r', 1, &zero); // Wrong password
  } else {
    simpleserial_put('r', 1, &one); // GOAL
  }
}

The function stops comparing the moment it finds an incorrect character, breaks and writes a zero through the SimpleSerial protocol. This makes the code take different amount of time and power to complete its execution depending on the input. In each iteration of i, an incorrect character will take a particular amount of time and the correct input will take more time, since it’ll start comparing the next letter. This makes this program vulnerable to a power analysis attack.

Now, let’s see what sort of APIs the ChipWhisperer has in its sleeves to aid us in power analysis.

The toolchain offers scope.capture() and scope.get_last_trace() which can help us with this. scope.capture() does the trace and get_last_trace() returns the traces, which are basically an array of values.

Let’s get a function that returns the power traces when a particular input is sent.

def get_trace(password_guess):
    
    # do some init

    target.write(password_guess)
    ret = scope.capture()

    # check ret for NULL, etc

    return scope.get_last_trace()

Alright, our plan here is to try out all possible characters (uint8_t, so very bruteforce-able) for each of the characters, pass the string to get_trace(), compute absolute differences of the traces and see which character takes the most time, since that will be the correct one.

To compute the difference, we need the trace of a completely incorrect input which will act as a reference trace. Then, for each character of the password, we’ll try out every possible alphanumerals and capture the traces.

known_password = '' 
for i in range(16): 					# 16 is the length of the password, it's known
	ref_trace = cap_pass_trace(known + "#"*(16-len(known)))
	p = {}
	for character in tqdm('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):	
		trace = cap_pass_trace(known + character + "#"*(15-len(known)))
		diff = np.sum(np.abs(trace - ref_trace))
		p.update({c:diff})
		if diff > 200: 				# rule of thumb
			known_password += character

By doing something like this, we’ll be able to crack the correct input (flag) in a short while:

The key takeaway from this part of the blog is that SCAs eventually comes down to data handling and about inferring from numerical observations. Almost like big data, if you will. Also understand that, something like this is only possible on small embedded systems where there’s less abstractions and simple things going around.

Fault Injection Attacks

Fault Injections are about injecting a small quirk into the execution of the processor so that a particular computation is skipped or faulted. In layman’s terms, it’s like giving the processor a boop. The ChipWhisperer supports Clock and Voltage glitching.

Below is the psuedo-code of the challenge err0r. Here, the pointer txt will point to the array which contains our input.

uint8_t hash_loop(uint8_t* txt, uint8_t len) {
    uint32_t crc = 0;
    uint32_t crc_2 = 0;

    char buf[8]="";
    for(int i=0;i<8;i++){
      buf[i]=txt[i];
    }
    trigger_high();

    crc32(buf, sizeof(buf), &crc);
    crc32(buf, sizeof(buf), &crc_2);
    trigger_low();

    if (crc != crc_2) {
      simpleserial_put('r',4, win_code); // GOAL
    } else {
      simpleserial_put('r',4 , (uint8_t*)&crc);
    }
    return 0x00;
}

This one’s fairly interesting as we have to somehow make the CRC hash return different results for the same input, which is not how hash functions or redundancy checks work. So, we’ll try to glitch the execution while the CRC is being calculated so that the function would return different things.

ChipWhisperer Nano supports Voltage Glitching, i.e, supports shorting the voltage pins for a very short amount of time and that’s what we’ll do now. Initially, we need to set a couple of parameters which will decide how frequently the glitch would be inserted. This is a big deal and we’ll have to tweak it if we’re not getting good results. To solve err0r, to inject a fault, we used the simpleserial_read_witherrors API from the SimpleSerial’s standard library with glitch_timeout set as 10 samples.

Since, our program returns the CRC hash of the input if both of the hashes (crc and crc_2) are the same, we can just keep on glitching until it returns something different.

while True:
  ret = scope.capture()
  val = target.simpleserial_read_witherrors('r', 4, glitch_timeout = 10)

  ...

  hash = struct.unpack("<I", val['payload'])[0] # the string which was returned
  if hash != 3221785859: 			# CRC32 hash of the input we were giving
  	print("Glitched!")
    break

I still remember the awe I was in when I glitched something for the first time. Imagine that there’s a simple strcmp password check somewhere. Now, imagine if you can just skip the check and try to run the code that comes below it.

We must realize that glitching is not entirely deterministic. There are a lot of variables around and a lot of things fighting against us and the glitches. Therefore, we can insert glitches and only hope for the best. At least, that’s what I understood.

Conclusion

That’s it! I hope I was able to give you a really short glimpse of what’s SCA and what’s possible with the ChipWhisperer. I aimed at keeping this blog short, and of course, there are a lot of other amazing things you can do here. For all of that, see the ChipWhisperer Wiki and tutorials. The tutorials are an amazing resource. Most of the code on this blog and its pattern was heavily inspired from the sample code from the tutorials.

Thanks to everyone who worked on the ChipWhisperer and to all who helped answer our questions on Discord. The hardware and the software is inspiring and is an amazing example of quality engineering.