Feature Deep-Dive: Voltage Multiplexing and Parameter Optimization
Attacking the ESP32v1.3 with the Pico Glitcher v2
I am now selling the PicoGlitcher on tindie.com.
More links:
- The Pico Glitcher and findus (the software to control the Pico Glitcher) is open source: fault-injection-library
- Documentation of the Pico Glitcher and findus: fault-injection-library.readthedocs.io
- hackaday.io project page: hackaday.io/project/196357-picoglitcher
- The Pico Glitcher was featured on Hackaday: hackaday.com/2024/10/30/use-picoglitcher-for-voltage-glitching-attacks/
As previously covered here, voltage multiplexing is a technique to quickly switch between different supply voltage levels of the target microcontroller. Similar to glitching with the crowbar technique, this can cause a fault in the target microcontroller. A fundamental difference is that with the voltage multiplexing method, the supply voltage of the microcontroller is not pulled to GND, but intermediate voltages can also be used, for example 1.8V or user-defined voltages. It is also possible to go through a sequence of different supply voltages, so called voltage profiles.
By using the multiplexing method, the possible parameter combinations can increase exponentially, which makes the search for suitable parameters more difficult. Of course, one can also keep multiple parameters constant and only vary one specific parameter. Another option is to vary all parameters and apply a genetic algorithm to the search space to find suitable parameters.
In the following, we will investigate how the multiplexing method can be optimized for an attack against the ESP32v1.3 processor using a genetic algorithm. But of course the multiplexing method can also be used without the genetic algorithm.
Recap: Voltage Multiplexing of the Pico Glitcher v2
The Pico Glitcher v2 is capable of switching between up to four different voltages quickly via the multiplexing stage. The multiplexing output can also be used to supply the target board with power. The following figure shows an example voltage trace that was generated by the Pico Glitcher v2.
Initializing the multiplexing stage is as easy as setting up the crowbar stage. First the Glitcher is initialized. With the optional step 'glitcher.change_config_and_reset("mux_vinit", "1.8")' a diffferent initial voltage can be set. Then the Glitcher is instructed to use a rising-edge trigger. The last step enables the multiplexing stage:
from findus import PicoGlitcher
glitcher = PicoGlitcher()
glitcher.init(port='/dev/ttyACM0')
# optional steps: set the initial voltage value
# the initial voltage for multiplexing must be hard-coded and can only be applied if the raspberry pi pico is reset and re-initialized.
glitcher.change_config_and_reset("mux_vinit", "1.8")
glitcher.init(port='/dev/ttyACM0')
glitcher.rising_edge_trigger()
glitcher.set_multiplexing()
During glitching, the parameters of the voltage trace can be adapted via the following command:
mul_config = {"t1": 100, "v1": "1.8", "t2": length, "v2": "VCC", "t3": 50, "v3": "GND"}
glitcher.arm_multiplexing(delay, mul_config)
Genetic Algorithm
A genetic algorithm is an optimization and search technique inspired by the principles of natural selection and evolution. It is commonly used to solve complex problems where traditional optimization methods are ineffective.
The advantages are that genetic algorithms can handle complex, nonlinear, and multidimensional problems and do not require gradient information. A genetic algorithm explores a wide parameter space, reducing the risk of getting stuck in local optima.
A genetic algorithm was implemented in the fault-injection-library to search for an optimal parameter set. The algorithm is easy to use and can be easily integrated into an already developed glitching script.
To integrate the genetic algorithm, the 'OptimizationController' class must be imported:
from findus import OptimizationController
After initializing the glitcher, the parameter space must be defined. This is done by defining the parameter space boundaries, and the corresponding divisions into bins. If for example, the parameter space is three-dimensional (glitch 'delay' and 'length' parameter and an additional time parameter 't1' for an intermediate voltage step), the parameter space would look like this:
# Genetic Algorithm to search for the best performing bin
boundaries = [(s_delay, e_delay), (s_t1, e_t1), (s_length, e_length)]
divisions = [10, 10, 5]
opt = OptimizationController(parameter_boundaries=boundaries, parameter_divisions=divisions, number_of_individuals=10, length_of_genom=20, malus_factor_for_equal_bins=1)
The 'delay' and 't1' intervals are divided into 10 bins, whereas the 'length' interval is divided into 5 bins, which sums up to a total of 500 bins. These 500 bins are used for the parameter search and the optimum parameters are determined on the basis of this binning. The number of bins can be increased, which increases the search time but improves accuracy.
The 'OptimizationController' object is also constructed with 10 individuals and a genom length of 20, meaning that 10 individuals compete for survival, with each individual observing 20 parameter space bins simultaneously. The malus factor 'malus_factor_for_equal_bins = 1' is used to penalize individuals that look into same bins only.
In the while-loop before the experiment is performed, the best performing bins can be monitored from time to time. The next parameter set is pulled with the command 'opt.step()'. Also the glitch outcome must be classified and fed into the genetic algorithm again.
# get the next parameter set
delay, t1, length = opt.step()
if experiment_id % 100 == 0:
opt.print_best_performing_bins()
# perform arming, triggering, read response
[...]
# classify response
color, weight = glitcher.classify(response)
# add experiment to parameterspace of genetic algorithm
opt.add_experiment(weight, delay, t1, length)
Since the genetic algorithm needs to classify the outcome of each bin with a numerical value (the so-called weight), a classify-method with additional weight factors must be implemented. These weights are fed into the genetic algorithm.
def classify(self, response):
if b'read-out protection enabled\r\n' in response:
color, weight = 'G', 0
elif b'' == response:
color, weight = 'M', 0
elif b'Error' in response:
color, weight = 'M', 0
elif b'Fatal exception' in response:
color, weight = 'M', 0
elif b'Timeout' in response:
color, weight = 'Y', -1
else:
color, weight = 'R', 2
return color, weight
Results
To test the genetic algorithm and the multiplexing stage, the ESP32v1.3 was attacked. The complete script to attack this processor can be found here.
In this case, the ESP32 was flashed with a simple program that increments a control variable in a loop (similar to the program used for the glitching attack on the rp2040). While this control variable is incremented, a GPIO pin is enabled and the glitch is set while this pin is high.
The result is that the control variable is incorrectly incremented and subsequently the variable does not contain the expected result. The correctness of the variable can be checked by sending the control variable to the attacker computer via UART.
As can be seen in the previous figure, the control variable is incremented incorrectly every 5000 experiments. What can also be seen is that some areas of the parameter space contain "gaps". The genetic algorithm obviously observed fewer successes here, which is why fewer points were thrown into these areas.
The multiplexing method is another option for carrying out voltage glitching attacks, whereby a large number of additional parameters are added. Overall, the use of the genetic algorithm can lead to a higher success rate without making the glitching attack more complicated.