I recently let the magic smoke from my personal ChipWhisperer and was in dire need of a fault injection hardware to do some experiments. Like any other hacker, instead of ordering and waiting I decided to make one myself using some of the other hardware I already have. Since hardware obviously needs software to run the fault injecting setup, why not use ChatGPT to help me make one quickly and analyze how it works rather than writing all the code myself ?  

Before we head to the main topic, let’s have some basic introduction on fault injection.

Fault Injection

Hardware fault injection is a technique used to deliberately induce faults or errors into hardware components, such as microprocessors, memory chips, or communication interfaces. This results in bypassing critical security implementations such as code read protection, debug protection, password authentication or corrupting cryptographic operations (DFA) to gain control over a system. 

Hardware fault injection tools work by injecting targeted errors – aka faults – into the hardware at specific points in time or under specific conditions. The tool can inject different types of faults, such as single-bit errors, stuck-at faults, or transient faults, and can control the timing and duration of the faults. 

One common hardware fault injection technique is called “clock glitching”, which involves manipulating the clock signals of a processor or other hardware component to introduce timing errors. Another technique is “voltage glitching”, which involves briefly reducing the voltage supplied to a component to induce errors.

The hardware module typically includes circuitry that can generate the required faults, such as digital glitches or voltage perturbations, and can monitor the system’s response to the injected faults. The software running on the host computer provides a user interface to configure the fault injection parameters, such as the type and duration of the fault, and to control the timing of the fault injection. 

The tool may also include mechanisms to automate the fault injection process, such as scripting or scheduling tools that allow users to create and run automated fault injection test cases. 

During the fault injection process, the tool injects the targeted faults into the hardware system while monitoring its behavior and recording any errors or anomalies that occur. The results of the fault injection test can then be analyzed to identify potential weaknesses or vulnerabilities in the system and to evaluate the resistance to faults in the system.

Here is an example of a vanilla target application that increments the value of “cnt” using two nested loops. The loops execute when a serial message is received, and GPIO 2 goes HIGH at the beginning of the loop and LOW once it’s done. This gives us a window of opportunity to inject a fault. 

Graphical user interface, text, application, email Description automatically generated
A sample application incrementing a “cnt” value using two nested loops.

Think of this tool as Ash controlling Pikachu – once the fault is injected, the loop can be disrupted in various ways, such as skipping or corrupting instructions, breaking out of the loop, or jumping to a different code location. This results in the “cnt” value being altered from its original value. Identifying the type of fault is known as fault modelling. 

If you want to learn more about fault injection and do some serious stuff, you check on Colin’s amazing work and check out this book on Hardware Hacking 

ChatGPT to the rescue

The objective of this project was to create a simple and easy-to-use fault injection tool using the Teensy 4.0 microcontroller and the Arduino platform with the help of AI assistance. The side-quest is also to make the project as cross-compatible as possible with other Arduino supported board which means we avoid playing with Teensy hardware specific implementation.

We can visit https://chat.openai.com to inquire about building a fault injection tool and explore the AI’s response.

Text Description automatically generated

I was surprised to learn that it might be considered illegal. Let’s provide more specific details to the AI and see what it suggests.

Graphical user interface, text, application Description automatically generated

I am familiar with this code, but I won’t run it because the jitter in the loop digitalRead() is too high. Instead, we can explore using hardware interrupts, which are superior to pooling loops. 

Text Description automatically generated

The interrupt implementation was successful in setting the “triggered” flag as intended, but it does not solve the main issue as the flag is still being checked within the loop as before. To address this, let’s further investigate and make some modifications.

Text Description automatically generated

This is a promising development. While the use of micros and time for creating delay is a good idea, it’s important to ensure that the main logic is not happening within the loop() function. At this point, human intervention is necessary to collaborate with the AI and achieve optimal results. Let’s continue to seek further improvements. 

Text Description automatically generated

This implementation is preferable, and although it uses digitalWrite(), Teensy also provides digitalFastWrite() which is significantly faster, as well as delayNanoseconds(). This delayNanoseconds function also works with other generic arduino but it’s pulse width depends on the speed of the MCU. So benchmark your minimum pulse width and pulse delay from interrupt. Let’s instruct the AI to utilize these functions.

Text Description automatically generated

Having obtained enough code and knowledge, I can now modify the code to create a basic Fault Injection logic. After completing the modifications, I tested the code with pulseWidth and pulseDelay set to 0. I observed a delay of approximately 120 nanoseconds in pulseDelay and 40 nanoseconds in pulseWidth, as shown below. Although this timing is not optimal for precise measurements, it is acceptable for a hobby-level device. 

Improvements in timing can be made later through compiler adjustments, overclocking, or direct register access, but those are not the focus for today.

A screenshot of a computer Description automatically generated with medium confidence
Our observation of 120 nanoseconds in pulse delay and 40 nanoseconds of pulse width.

I have now increased the pulseWidth and pulseDelay to 50ns, resulting in approximately 170ns and 90ns, respectively. Although this can be compensated for in the code, it is acceptable for my purposes. 

Next, it’s time to focus on the computer side of things. I decided to ask the AI to create a Python library for easier importing. The initial result was promising, but some adjustments were necessary. Nonetheless, I am satisfied with the outcome.

A screenshot of a computer Description automatically generated
Graphical user interface, application Description automatically generated

I now possess sufficient code to construct a Teensy-based fault injection framework with the aid of AI. Although the working codebase is not entirely generated by AI, it is AI-assisted, and a human has refined it to remove bugs and make it functional.  

The Final Code: Explanation

This code sets up a Teensy microcontroller to generate a pulse on an output pin triggered by an input signal on a specified GPIO pin. The pulse width and delay can be adjusted via serial input commands. Here’s a breakdown of the code: 

volatile  int gpioPin = 1; 
volatile  int outputPin = 3; 
volatile  int triggerCondition = RISING;
volatile  int resetPin = 9;
volatile unsigned long pulseDelay = 50; 
volatile unsigned long pulseWidth = 50; 
volatile unsigned long lastTriggerTime = 0; 
volatile unsigned long pulseTimeout = 1000; 
volatile unsigned long resetWidth = 1000; 
volatile bool interruptEnabled = false;

This code defines several global variables used for controlling the behavior of a microcontroller.

All of these variables are marked as volatile, which means that the compiler will not optimize them out of the code. This is important because these variables are modified by interrupt service routines, and if they were optimized out, the code would not behave correctly.

  Serial.begin(115200);
  pinMode(outputPin, OUTPUT);
  pinMode(gpioPin, INPUT);
  pinMode(resetPin, OUTPUT);
  digitalWriteFast(resetPin, HIGH);	

This is the setup function that runs once when the microcontroller is first powered on. It initializes the serial communication and sets the mode of the input, output, and reset pins. 

void loop() {
  if (Serial.available()) {
    String input = Serial.readStringUntil('\n');
    input.trim();
    handleSerialInput(input); 
  }
  if (interruptEnabled && millis() - lastTriggerTime > pulseTimeout) { 
    detachInterrupt(digitalPinToInterrupt(gpioPin));
    interruptEnabled = false;
    Serial.println("NOK");
  }
}				
				

This is the main loop function that runs continuously. It checks for serial input and calls a function to handle it. It also checks if the interrupt is enabled and if it has not been triggered for a certain amount of time, it disables the interrupt. 

void triggerInterrupt() {
  delayNanoseconds(pulseDelay);
  digitalWriteFast(outputPin, HIGH);
  delayNanoseconds(pulseWidth);
  digitalWriteFast(outputPin, LOW);
  detachInterrupt(digitalPinToInterrupt(gpioPin));
  interruptEnabled = false;
  Serial.println("AOK");
}
					
				

This Interrupt handler function waits for the specified delay before setting the output pin to HIGH using digitalWriteFast(outputPin, HIGH). The delay is specified in pulseDelay, which is in nanoseconds. 

Next, the function waits for the specified pulse width before setting the output pin back to LOW using digitalWriteFast(outputPin, LOW). The pulse width is specified in pulseWidth, also in nanoseconds. 

After the pulse has been generated, the function detaches the interrupt using detachInterrupt(digitalPinToInterrupt(gpioPin)) to prevent it from triggering again while we’re still handling this one. 

The interruptEnabled flag is then updated to indicate that the interrupt is no longer enabled. 

Finally, a message is sent over serial to indicate that the trigger was successful using Serial.println(“AOK”). 

void handleSerialInput(String input) {
    // Parse and handle serial input
    if (input.startsWith("gpioPin")) {
    gpioPin = input.substring(7).toInt();
    pinMode(gpioPin, INPUT_PULLUP);
    Serial.println("OK");
  } else if (input.startsWith("outputPin")) {
    outputPin = input.substring(9).toInt();
    pinMode(outputPin, OUTPUT);
    Serial.println("OK");
  } else if (input.startsWith("trigger")) {
    triggerCondition = input.substring(7).toInt();
    Serial.println("OK");
  } else if (input.startsWith("pulseDelay")) {
    pulseDelay = input.substring(10).toInt();
    Serial.println("OK");
  } else if (input.startsWith("pulseWidth")) {
    pulseWidth = input.substring(10).toInt();
    Serial.println("OK");
  } else if (input.startsWith("pulseTimeout")) {
    pulseTimeout = input.substring(12).toInt();
    Serial.println("OK");
  } else if (input.startsWith("tReset")) {
    resetWidth = input.substring(6).toInt();
    digitalWrite(resetPin, LOW);
    delay(resetWidth);
    digitalWrite(resetPin, HIGH);
    Serial.println("OK");
  } else if (input.startsWith("enable")) {
    interruptEnabled = true;
    Serial.println("OK");
    attachInterrupt(digitalPinToInterrupt(gpioPin), triggerInterrupt, triggerCondition);
    lastTriggerTime = millis();
  } else if (input == "print") {
    printVariables();
  } else {
    Serial.println("NAK");
  }
}

void printVariables() {
  Serial.println("gpioPin: " + String(gpioPin));
  Serial.println("outputPin: " + String(outputPin));
  Serial.println("trigger:" + String(triggerCondition));
  Serial.println("pulseDelay: " + String(pulseDelay) + " ns");
  Serial.println("pulseWidth: " + String(pulseWidth) + " ns");
  Serial.println("pulseTimeout: " + String(pulseTimeout) + " ms");
  Serial.println();
}				
				

This is the function that handles the serial input received from the computer via the serial port. The function takes a string as input and then checks what the input is using a series of if-else statements. The different input types that the function can handle are: 

“gpioPin”: This input sets the GPIO pin that the input signal will be read from. The function uses the substring function to extract the pin number from the input string and then converts it to an integer using the toInt function. The function then sets the pinMode of the specified pin to INPUT_PULLUP and sends a response of “OK” back to the computer via the serial port. 

“outputPin”: This input sets the GPIO pin that the output signal will be sent to. The function uses the substring function to extract the pin number from the input string and then converts it to an integer using the toInt function. The function then sets the pinMode of the specified pin to OUTPUT and sends a response of “OK” back to the computer via the serial port. 

“trigger”: This input sets the trigger condition for the pulse. The function uses the substring function to extract the trigger condition from the input string and then converts it to an integer using the toInt function. The function then sends a response of “OK” back to the computer via the serial port. 

“pulseDelay”: This input sets the delay between the trigger and the start of the pulse. The function uses the substring function to extract the delay time from the input string and then converts it to an integer using the toInt function. The function then sends a response of “OK” back to the computer via the serial port. 

“pulseWidth”: This input sets the width of the pulse. The function uses the substring function to extract the pulse width from the input string and then converts it to an integer using the toInt function. The function then sends a response of “OK” back to the computer via the serial port. 

“pulseTimeout”: This input sets the maximum duration of the pulse. The function uses the substring function to extract the timeout from the input string and then converts it to an integer using the toInt function. The function then sends a response of “OK” back to the computer via the serial port. 

“tReset”: This input sets the duration of the reset pulse. The function uses the substring function to extract the reset width from the input string and then converts it to an integer using the toInt function. The function then toggles the reset pin LOW and then HIGH for the specified duration using the digitalWrite and delay functions. Finally, the function sends a response of “OK” back to the computer via the serial port. 

“enable”: This input enables the interrupt that will trigger the pulse. The function sets the interruptEnabled variable to true and sends a response of “OK” back to the computer via the serial port. The function then uses the attachInterrupt function to attach the triggerInterrupt function to the specified GPIO pin with the specified trigger condition. Finally, the function sets the lastTriggerTime variable to the current time in milliseconds using the millis function. 

“print”: This input prints the current values of all the variables that can be set using the serial port. The function calls the printVariables function to do this. 

Any other input: If the input does not match any of the above cases, the function sends a response of “NAK” (Negative Acknowledgment) back to the computer via the serial port. 

The printVariables function simply prints the current values of all the variables that can be set using the serial port. The function uses the Serial.println function to send each variable value to the computer via the serial port. 

This is a Python class called FIfromAI which provides a high-level interface for communicating with the Arduino board over a serial connection. The class is designed to control the Arduino board which is running the firmware that we have previously discussed. 

class FIfromAI:
    def __init__(self, port, baudrate=115200, timeout=1):
        self.ser = serial.Serial(port, baudrate=baudrate, timeout=timeout)

    def __del__(self):
        if self.ser.isOpen():
            self.ser.close()

    def set_gpio_pin(self, pin):
        self.ser.write(('gpioPin' + str(pin) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'

    def set_output_pin(self, pin):
        self.ser.write(('outputPin' + str(pin) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'

    def set_pulse_delay(self, delay):
        self.ser.write(('pulseDelay' + str(delay) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'

    def set_pulse_width(self, width):
        self.ser.write(('pulseWidth' + str(width) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'

    def set_pulse_timeout(self, timeout):
        self.ser.write(('pulseTimeout' + str(timeout) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'

    def t_reset(self, width):
        self.ser.write(('tReset' + str(width) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'

    def set_trigger_condition(self, condition):
        self.ser.write(('trigger' + str(condition) + '\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'
    def enable(self):
        self.ser.write(('enable\n').encode())
        response = self.ser.readline().decode().rstrip()
        return response == 'OK'
    def check(self):
        response = self.ser.readline().decode().rstrip()
        ##print(response)
        return response == 'AOK'
    def print_variables(self):
        self.ser.write(('print\n').encode())
        response = ''
        while not response.endswith('\r\n\r\n'):
            response += self.ser.readline().decode()
        return response.rstrip()					
				

The methods are: 

To use this class, you would first create an instance of it by passing the serial port that the Arduino board is connected to as an argument to the constructor. You could then call the different methods on the instance to configure and control the pulse generator. 

import serial
import FIfromAI
import time
import random

## edge types
RISING = 3 
FALLING = 2
CHANGE = 4

# create FIfromAI instance
tt = FIfromAI.FIfromAI('COM14', 115200, timeout=1)

# serial target
ser = serial.Serial('COM11', 115200,timeout=0.5)

# set GPIO and output pins
tt.set_gpio_pin(1)
tt.set_output_pin(11)

# set trigger condition to rising edge
tt.set_trigger_condition(RISING)

# set pulse delay and width
tt.set_pulse_delay(random.randint(100,500))
tt.set_pulse_width(random.randint(100,5000))

# set pulse timeout
tt.set_pulse_timeout(1000)
print(tt.print_variables())
rcount = 0
while(1):

    #send random values to delay and width
    tt.set_pulse_delay(50)
    tt.set_pulse_width(50)
    # enable interrupt
    tt.enable()
    # send A to serial to start operation
    ser.write(b'A')
    # check if glitched or timedout
    timeout = tt.check()

		

This code generates a glitch by sending a pulse of random width and delay to a target over a serial interface, while monitoring the output for changes. If a glitch occurs, the target will respond with a message over the serial interface. 

The code imports the serial module to communicate with a target device, the time module to pause execution for a given number of seconds, and the FIfromAI module which is a custom class that contains several methods for controlling the device. 

The RISING, FALLING, and CHANGE variables are defined to indicate different types of edges that can trigger an interrupt. 

The code initializes an instance of the FIfromAI class with a given port and baud rate, sets the GPIO and output pins, and configures the trigger condition to detect a rising edge. It also sets a random pulse delay, pulse width, and pulse timeout. 

A while loop is then executed which repeatedly sends random values to the delay and width parameters, enables an interrupt, sends an A character over the serial interface to start operation, and checks if a glitch has occurred. If a glitch has occurred, it prints the output from the serial interface. 

There are also commented-out sections of code that can be used to reset the target if a timeout occurs or to print debugging output. 

The code is made with Teensy 4.0 in mind but also in a such a way it is compatible with other Arduino boards but the pulse parameters will be different. 

You can find all the codebase in https://github.com/onekey-sec//fi-from-ai/

Disclaimer: This tool is not intended to replace other professional fault injection tools. It should be noted that the tool relies on interrupts, which can introduce some degree of jitter. A more precise implementation can be achieved by using an FPGA with more precise timing capabilities.  Maybe ChatGPT can help me someday with it too. Hack responsibly. 

Voltage FI: as an example

Now to the non-ChatGPT part of the project. If you want to experiment with voltage glitching, you cannot use the GPIO output as these are just a control signal for different glitching medium like voltage or EM or laser. 

For voltage-based FI, you need a way to drop the power that goes to the target device and GPIO is too weak, so MOSFETs are used to control the high voltage/current like a switch. This technique is called the crowbar method.  

Diagram Description automatically generated
A crowbar technique setup.

I am using a MOSFET(E42AB) board which comes with a driver. Ideally you can use any MOSFET, which has a very quick switching characteristic. This also adds more propagation delay and keep a note of it with the scope. For low speed targets, the required glitch width are usually in microseconds, so it is acceptable and also note that the common dual-MOSFET module cannot switch effectively from Teensy GPIO for shorter pulse because of larger gate capacitance.  

Two MOSFET boards (E42AB with driver on the left, dual-MOSFET module on the right).

Watch out for the next blog series which goes into using this open source tool to characterize and model the faults which is usefull before mounting a real attack.

Conclusion:

Building an AI-assisted Teensy-based fault injection framework is a challenging but rewarding process. With the help of AI and a human’s touch, we were able to create a functional tool for testing system security. This tool may not be a professional-grade fault-injection tool, but it provides an excellent starting point for hobbyists interested in exploring the field.

In conclusion, fault injection tools are powerful and necessary for any organization that takes the security of its hardware seriously. With the ability to simulate real-world attacks, these tools help identify vulnerabilities before they can be exploited by attackers. However, it’s important to note that while fault injection tools can be incredibly helpful, they should not be the only tool in your arsenal. 

At ONEKEY, we offer comprehensive hardware penetration testing services that go beyond just fault injection testing. Our team of experts is dedicated to uncovering vulnerabilities in your hardware systems and providing you with actionable recommendations to improve your security posture. We utilize the latest tools and techniques to identify weaknesses in your hardware, and our team has years of experience in the field. 

Don’t wait until it’s too late. Contact ONEKEY today to schedule a hardware penetration test and ensure the security of your organization’s hardware systems.