This project is used as a tool to simulate fault attacks to ARM-M processors (Thumb mode).
The simulator supports two modes of operation:
- C Project Mode: Compile and test the included C project from the "content" folder
- Firmware Mode: Load and instrument an independent ELF file (e.g., real firmware binaries) with custom memory regions, code patches, and register initialization
Faults are introduces depending the predefined ranges or manualy. For the simulated attacks "all", "single" and "double", all implemented faults are executed till one leads to an successful attack. (e.g. "--class double"). For specific cases the check of the C code operation can be disabled with the "--no-check" option. This will allow to remove for e.g. the SUCCESS_DATA from the file under attack.
Once a vulnerability is found, the attack command sequence can be further analyzed using the '--analysis' command line parameter.
Screenshot of the attack visualization with highlighted instructions.
For fast reproduction of a successful attack, the faults can be setup with the --faults feature manualy. (E.g. "--faults glitch_1 glitch_10" -a double attack with 1 and 10 instruction glitches) Code examples for main.c are located at: "content\src\examples"
Inject a program counter (PC) glitch (skips 1–10 assembly instructions).
Syntax:
- Attack class:
glitch - Specific attacks:
glitch_1,glitch_2, ...,glitch_10
Example:
glitch_3 # Skips 3 instructionsFlip bits in registers R0–R12 using XOR with a hex mask (single-bit only).
Syntax:
- Attack class:
regbf - Specific attacks:
regbf_rX_YYYYYYYY(X=0–12, Y=hex mask)
Examples:
regbf_r0_00000001 # Flip bit 0 of R0
regbf_r12_80000000 # Flip bit 31 of R12Flood a register with 0x00000000 or 0xFFFFFFFF.
Syntax:
- Attack class:
regfld - Specific attacks:
regfld_rX_00000000,regfld_rX_FFFFFFFF
Example:
regfld_r5_FFFFFFFF # Set R5 to 0xFFFFFFFFFlip bits in instructions during fetch (single-bit only).
Syntax:
- Attack class:
cmdbf - Specific attacks:
cmdbf_YYYYYYYY(Y=hex mask)
Example:
cmdbf_00000001 # Flip bit 0 of the fetched instructionThe included C project (/content) is compiled with these flags:
TARGET = armv8-m.main
CFLAGS = -c -O3 -Iinclude \
-g -gdwarf -Wno-unused-but-set-variable -fno-inline -fno-omit-frame-pointer \
-fno-ipa-cp-clone -fno-ipa-cp -fno-common -fno-builtin -ffreestanding -fno-stack-protector \
-Wall -Wno-format-security -Wno-format-nonliteral -Wno-return-local-addr -Wno-int-to-pointer-cast \
-march=$(TARGET) -DMCUBOOT_FIH_PROFILE_ON -DMCUBOOT_FIH_PROFILE_HIGH -DFAULT_INJECTION_TEST
CFLAGS_LD = -N -Wl,--build-id=none -g -gdwarf -Os -Wno-unused-but-set-variable \
-Wno-return-local-addr -fno-inline -fno-ipa-cp-clone \
-fno-ipa-cp -nostartfiles -nodefaultlibsRust Toolchain
- Included crates:
unicorn-engineelflogenv_loggercapstoneindicatifgit-versionrayonitertoolsclapserdeserder_json
Compiler Toolchain
gcc-arm-none-eabicompiler toolchain
Build Tools
maketoolchain
Ghidra Trace Visualization
- Ghidra 11.3 or newer with PyGhidra mode.
You can configure the simulator using either command-line arguments or a JSON5 config file.
CLI arguments always override values from the config file.
| Flag/Option | Description |
|---|---|
-c, --config <CONFIG> |
Load configuration from JSON file |
-t, --threads <THREADS> |
Number of threads started in parallel [default: 1]. "-t 0" activate full thread usage |
-n, --no-compilation |
Suppress re-compilation of target program |
--class <ATTACK>,<GROUPS> |
Attack class to be executed. Possible values are: all, single, double [default: all]. GROUPS can be the names of the implemented attacks. E.g. --class single regbf separated by ' ' |
--faults <FAULTS> |
Run a command line defined sequence of faults. Alternative to --attack. (E.g. --faults glitch_1 glitch_10). Current implemented fault attacks: - glitch_1 .. glitch_10 - regbf_r0_00000001 .. regbf_r12_80000000 - regfld_r0_00000000 or regfld_r0_FFFFFFFF - cmdbf_00000000 .. cmdbf_80000000 |
-a, --analysis |
Activate trace analysis of picked fault |
-d, --deep-analysis |
Check with deep analysis scan. Repeated code (e.g. loops) are fully analysed |
-m, --max_instructions |
Maximum number of instructions to be executed. Required for longer code under investigation (Default value: 2000) |
--no_check |
Disable program flow check |
-e, --elf <FILE> |
Use external elf file w/o compilation step |
--trace |
Trace and analyse program w/o fault injection |
-r, --run-through |
Don't stop on first successful fault injection |
--log-level <LEVEL> |
Set logging verbosity: off, error, warn, info, debug, trace [default: off] |
--success-addresses [<SUCCESS_ADDRESSES>...] |
List of memory addresses that indicate success when accessed Format: --success-addresses 0x8000123 0x8000456 |
--failure-addresses [<FAILURE_ADDRESSES>...] |
List of memory addresses that indicate failure when accessed Format: --failure-addresses 0x8000789 0x8000abc |
-h, --help |
Print help |
-V, --version |
Print version |
-
Single glitch attack with trace analysis (CLI):
cargo run -- --class single glitch --analysis
-
Single glitch attack with trace analysis (JSON5 config): Create a file (e.g.,
example.json5):{ class: ["single", "glitch"], analysis: true, }
Run with:
cargo run -- --config example.json5
-
Mixing CLI and JSON5:
cargo run -- --config example.json5 --analysis false -
Double attack (glitch + register flood) on custom ELF:
cargo run -- --class double glitch regfld --elf tests/bin/victim.elf -t 4
-
Running a fault sequence with register bit-flip and glitch:
cargo run -- --faults regbf_r1_0100 glitch_1
-
Running with custom initial register context: Create a config file (
custom_context.json5):{ elf: "tests/bin/victim_3.elf", class: ["single", "glitch"], analysis: true, initial_registers: { R0: "0x12345678", // Custom value R7: "0x2000FFF8", // Frame pointer SP: "0x2000FFF8", // Stack pointer LR: "0x08000005", // Link register PC: "0x08000620", // Program counter - entry point }, }
Run with:
cargo run -- --config custom_context.json5
Supported registers: R0-R12, SP, LR, PC, CPSR
Value formats: Hex strings ("0x12345678") or decimal numbers (42)
Case insensitive: "r0", "R0", "sp", "SP" all work
The following features are only available using the JSON5 configuration file.
Control logging verbosity in the configuration file. Can be overridden by the RUST_LOG environment variable. This can be helpful when debugging a config file.
{
log_level: "error", // Options: "off", "error", "warn", "info", "debug", "trace"
}Log Levels:
"off"- No logging output (default)"error"- Only critical errors (memory access violations, etc.)"warn"- Warnings and errors"info"- Informational messages, warnings, and errors"debug"- Detailed diagnostic information including memory mapping"trace"- Maximum verbosity with all internal operations
Note: The RUST_LOG environment variable takes precedence if set:
RUST_LOG=debug cargo run -- --config myconfig.json5Apply binary patches to modify firmware behavior at specific addresses or symbols. Useful for bypassing security functions or modifying control flow.
{
code_patches: [
// Patch function to return immediately (bx lr)
{
symbol: "decision_activation",
data: "0x4770",
},
// Patch at offset from symbol (symbol address + offset)
{
symbol: "check_secret",
offset: "0x10",
data: "0x2001", // movs r0, #1
},
// Replace function with bx lr (immediate return)
{
address: "0x08000100",
data: "0x4770",
},
// Replace instruction with nop; nop
{
address: "0x08000200",
data: "0xbf00bf00",
},
],
}Fields:
address(string, optional): Memory address to patch (hex format) - use eitheraddressorsymbolsymbol(string, optional): Symbol name to patch (resolved from ELF symbol table) - use eitheraddressorsymboloffset(string, optional): Hex offset to add to the symbol address (only used withsymbol, defaults to 0)data(string): Hex data to write at the address
Note: Each patch must specify either address OR symbol, but not both. When using symbol, you can optionally specify an offset to patch at a location relative to the symbol address. Using symbols makes configurations more portable across firmware versions.
Define custom memory regions with optional data loading from files. Essential for firmware that expects specific memory layouts (SRAM, peripherals, flash).
{
memory_regions: [
// SRAM region
{
address: "0x20000000", // SRAM base
size: "0x20000", // 128 KB
},
// Peripheral registers initialized from file
{
address: "0x40000000", // Peripheral base
size: "0x10000", // 64 KB
file: "peripheral_data.bin",
},
// Memory region with inline hex data
{
address: "0x30000000",
size: "0x1000",
data: "0xDEADBEEF", // Inline hex value to initialize region
},
// SRAM with memory dump - force merge fragmented ELF segments
{
address: "0x34000000",
size: "0x10000",
file: "sram_dump.bin",
force_overwrite: true, // Merge ELF segments to load full dump
},
],
}Fields:
address(string): Starting address of the memory region (hex format)size(string): Size of the region in bytes (hex format)file(string, optional): Binary file to load into this regiondata(string, optional): Hex value to initialize the region with (e.g., "0xDEADBEEF")force_overwrite(boolean, optional, default: false): If true, merges fragmented ELF segments within this region into one contiguous block to ensure the entire region can be mapped and overwritten with custom data
Check register values at specific addresses to determine success or failure.
{
result_checks: {
success_checks: [
{
address: "0x08000490",
expected_registers: {
R0: "0x00000000",
}
}
],
failure_checks: [
{
address: "0x08000490",
expected_registers: {
R0: "0xFFFFFFFF",
}
}
]
}
}success_checks: Conditions that indicate a successful attackfailure_checks: Conditions that indicate expected secure behavioraddress: Where to check (hex format)expected_registers: Register values that must match (supports R0-R12, SP, LR, PC, CPSR)
All specified registers must match for the check to trigger. If result_checks is set, it takes precedence over success_addresses and failure_addresses.
The Ghidra script you created enhances the visualization of the trace output generated by the simulator with the -a, --analysis option.
Usage:
- Ensure Ghidra 11.3 or newer is installed and running in PyGhidra mode as described in the Ghidra Installation Guide.
- Start the script in Ghidra.
- Paste the trace output from the simulation.
- Observe the executed instructions highlighted in green and the faulted instruction in red.
- Use the table window to step through the instruction trace.
Visualization Example:
Screenshot of the Ghidra visualization with highlighted instructions.