Run an injection campaign
In the BIOS password tutorial, one instruction is replaced by a chosen opcode. A physical attacker rarely controls the faulted value that precisely: a more realistic model is a single bit flip. Which bits of the instruction, once flipped, bypass the password check?
Answering this by hand means 32 attacks. A campaign runs them all, observes the BIOS answer after each fault, and writes a report.
You need: the SoC of the BIOS password tutorial, built and loaded, and two serial ports on the host: the UART bridge used by litex_server, and the BIOS console.
The campaign file
Section titled “The campaign file”The example is in examples/poc_basys3_vexriscv/campaign.toml:
[campaign]name = "bios-unlock-bitflip"repeat = 1output = "bios_unlock_results.csv"
[sweep]bit = "0..31"
[[step]]when = { ibus_adr = "0x0000037A" }offset = 1fault = { model = "bit-flip", signal = "ibus_dat_r", bits = "{bit}" }
[target]uart = "/dev/ttyUSB0" # BIOS console of the SoC (not the UART bridge), change to your port.send = "unlock aaaa\n"success = "Access granted"failure = "Access refused"timeout = 2.0[sweep]defines the parameters of the campaign.bittakes the values 0 to 31, and{bit}is replaced in the steps for each run.[[step]]is a trigger, with the same fields as the command line. Several steps make a multi-fault attack.[target]tells how to make the target run and how to read the result: for each run, LiteInjector is armed,sendis written on the BIOS console, and the answer is matched against thesuccessandfailurepatterns.
Run it
Section titled “Run it”-
Start
litex_serveron the UART bridge, in its own terminal.Terminal window $ litex_server --uart --uart-port /dev/ttyUSB1 -
Set the BIOS console port in the
[target]section, and closelitex_termif it uses this port. -
Check the campaign without hardware. Every run is encoded and printed.
Terminal window $ cd liteinjector/examples/poc_basys3_vexriscv$ liteinjector_cli campaign campaign.toml --dry-run -
Run it.
Terminal window $ liteinjector_cli campaign campaign.toml
Read the results
Section titled “Read the results”Every run gets an outcome:
| Outcome | Meaning |
|---|---|
success |
The success pattern was seen: the check was bypassed. |
no-effect |
The failure pattern was seen: the software behaved normally. |
crash |
A crash pattern was seen (optional patterns, e.g. a trap message). |
timeout |
Nothing recognized before the timeout: the software hung or crashed silently. |
not-injected |
The emulator did not inject any fault: check the trigger. |
The CSV file has one line per run with the sweep parameters, the outcome, the hit and fault counters, the cycle of the fault, the instruction before and after the fault (capture_before, capture_after) and the end of the console output. It can be loaded in any spreadsheet, or with pandas:
import pandas as pd
df = pd.read_csv("bios_unlock_results.csv")print(df["outcome"].value_counts())print(df[df.outcome == "success"][["params", "capture_before", "capture_after"]])Variations
Section titled “Variations”- Random faults: use
model = "bit-flip-random"withbits = "0-31"andrepeat = 100. Every run flips a new random set of bits, setseedin[campaign]to make the campaign reproducible. - Timing: sweep the offset instead of the bit, e.g.
offset = "0..4"in[sweep]andoffset = "{offset}"in the step. - Linux targets: replace
uart/sendby acommand, for example a program run overssh.
All the fields are described in the campaign file reference.