├── .clang-format
├── .gitignore
├── LICENSE
├── README.md
├── example
├── example.c
├── linker.ld
└── start_emulation.py
├── include
└── termcolor.h
├── meson.build
├── run_example.py
├── screenshot.png
├── src
└── main.cpp
└── subprojects
├── armory
├── include
│ └── armory
│ │ ├── context.h
│ │ ├── fault_combination.h
│ │ ├── fault_model.h
│ │ ├── fault_simulator.h
│ │ ├── fault_tracer.h
│ │ ├── instruction_fault_model.h
│ │ ├── register_fault_model.h
│ │ ├── snapshot.h
│ │ ├── subset_chooser.h
│ │ ├── termcolor.h
│ │ └── types.h
├── meson.build
└── src
│ ├── fault_combination.cpp
│ ├── fault_model.cpp
│ ├── fault_simulator.cpp
│ ├── fault_tracer.cpp
│ ├── instruction_fault_model.cpp
│ ├── meson.build
│ ├── register_fault_model.cpp
│ ├── simulate_instruction_fault.cpp
│ ├── simulate_register_fault.cpp
│ └── snapshot.cpp
├── armory_cli
├── include
│ └── armory_cli
│ │ ├── armory_cli.h
│ │ ├── configuration.h
│ │ ├── fault_models.h
│ │ ├── fault_printing.h
│ │ └── termcolor.h
├── meson.build
└── src
│ ├── armory_cli.cpp
│ ├── fault_models.cpp
│ ├── fault_printing.cpp
│ └── meson.build
└── m-ulator
├── include
└── m-ulator
│ ├── architectures.h
│ ├── arm_functions.h
│ ├── callback_hook.h
│ ├── conditions.h
│ ├── disassembler.h
│ ├── emulator.h
│ ├── instruction.h
│ ├── instruction_decoder.h
│ ├── memory_region.h
│ ├── mnemonics.h
│ ├── registers.h
│ ├── return_codes.h
│ ├── shift_types.h
│ └── types.h
├── meson.build
└── src
├── architectures.cpp
├── arm_functions.cpp
├── conditions.cpp
├── disassembler.cpp
├── emulator_base.cpp
├── emulator_execution.cpp
├── instruction.cpp
├── instruction_decoder.cpp
├── meson.build
├── mnemonics.cpp
├── registers.cpp
├── return_codes.cpp
└── shift_types.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | BasedOnStyle: Google
4 | AccessModifierOffset: -4
5 | AlignAfterOpenBracket: Align
6 | AlignConsecutiveAssignments: true
7 | AlignConsecutiveDeclarations: false
8 | AlignEscapedNewlinesLeft: true
9 | AlignOperands: true
10 | AlignTrailingComments: true
11 | AllowAllParametersOfDeclarationOnNextLine: false
12 | AllowShortBlocksOnASingleLine: false
13 | AllowShortCaseLabelsOnASingleLine: false
14 | AllowShortFunctionsOnASingleLine: None
15 | AllowShortIfStatementsOnASingleLine: false
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterDefinitionReturnType: None
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: true
21 | BinPackArguments: false
22 | BinPackParameters: false
23 | BraceWrapping:
24 | AfterClass: true
25 | AfterControlStatement: true
26 | AfterEnum: true
27 | AfterFunction: true
28 | AfterNamespace: true
29 | AfterObjCDeclaration: true
30 | AfterStruct: true
31 | AfterUnion: true
32 | BeforeCatch: true
33 | BeforeElse: true
34 | IndentBraces: false
35 | BreakBeforeBinaryOperators: NonAssignment
36 | BreakBeforeBraces: Custom
37 | # BreakBeforeInheritanceComma: false
38 | BreakBeforeTernaryOperators: true
39 | BreakConstructorInitializersBeforeComma: false
40 | BreakAfterJavaFieldAnnotations: false
41 | BreakStringLiterals: true
42 | ColumnLimit: 200
43 | CommentPragmas: '^ IWYU pragma:'
44 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
45 | ConstructorInitializerIndentWidth: 4
46 | ContinuationIndentWidth: 4
47 | Cpp11BracedListStyle: true
48 | DerivePointerAlignment: false
49 | DisableFormat: false
50 | ExperimentalAutoDetectBinPacking: false
51 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
52 | IncludeCategories:
53 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
54 | Priority: 2
55 | - Regex: '^(<|"(gtest|isl|json)/)'
56 | Priority: 3
57 | - Regex: '.*'
58 | Priority: 1
59 | IncludeIsMainRegex: '$'
60 | IndentCaseLabels: true
61 | IndentWidth: 4
62 | IndentWrappedFunctionNames: true
63 | JavaScriptQuotes: Leave
64 | JavaScriptWrapImports: true
65 | KeepEmptyLinesAtTheStartOfBlocks: false
66 | MacroBlockBegin: ''
67 | MacroBlockEnd: ''
68 | MaxEmptyLinesToKeep: 1
69 | NamespaceIndentation: All
70 | ObjCBlockIndentWidth: 2
71 | ObjCSpaceAfterProperty: false
72 | ObjCSpaceBeforeProtocolList: true
73 | PenaltyBreakBeforeFirstCallParameter: 19
74 | PenaltyBreakComment: 300
75 | PenaltyBreakFirstLessLess: 120
76 | PenaltyBreakString: 1000
77 | PenaltyExcessCharacter: 1000000
78 | PenaltyReturnTypeOnItsOwnLine: 60
79 | PointerAlignment: Left
80 | ReflowComments: false
81 | SortIncludes: true
82 | SpaceAfterCStyleCast: false
83 | SpaceAfterTemplateKeyword: false
84 | SpaceBeforeAssignmentOperators: true
85 | SpaceBeforeParens: ControlStatements
86 | SpaceInEmptyParentheses: false
87 | SpacesBeforeTrailingComments: 4
88 | SpacesInAngles: false
89 | SpacesInContainerLiterals: false
90 | SpacesInCStyleCastParentheses: false
91 | SpacesInParentheses: false
92 | SpacesInSquareBrackets: false
93 | Standard: Cpp11
94 | TabWidth: 4
95 | UseTab: Never
96 | ...
97 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .vscode
3 | example/tmp_data/
4 | disassembled.txt
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Max Planck Institute for Security and Privacy, Embedded Security Group. All rights reserved.
4 | Copyright (c) 2020 Max Hoffmann ("ORIGINAL AUTHORS"). All rights reserved.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://lbesson.mit-license.org/)
2 |
3 |
4 | # Navigation
5 | 1. [Introduction](#introduction)
6 | 2. [Running The Example](#build-instructions)
7 | 3. [Using ARMORY in Your Own Projects](#quickstart)
8 | 4. [Academic Context](#academic-context)
9 |
10 | # Welcome to ARMORY!
11 |
12 | ARMORY is our fully automated exhaustive fault simulator for ARM-M binaries, developed for our paper **"ARMORY: Fully Automated and Exhaustive Fault Simulation on ARM-M Binaries"**.
13 |
14 | Given a binary, a set of fault models to inject, and an exploitability model, ARMORY exhaustively finds all exploitable faults via simulation, automatically utilizing all available CPU cores.
15 | ARMORY is based on M-Ulator, our own emulator for the ARMv6-M and ARMv7-M families, beating state-of-the-art emulator Unicorn in performance and, when it comes to invalid Assembly, also in correctness.
16 |
17 | Here is an example of the output of ARMORY:
18 |
19 | 
20 |
21 | # Running The Example
22 |
23 | ARMORY is build using the meson build system (install via `pip3 install meson`).
24 | You also need the ARM gcc toolchain to build and read ARM binaries (install via `sudo apt install gcc-arm-none-eabi`).
25 | To build ARMORY and run an example, simply
26 | 1. clone this git
27 | 2. `python3 run_example.py`
28 |
29 | You can also easily play around with the example by modifying `src/main.cpp`.
30 | Try out multivariate fault injection by injecting each model twice ;)
31 | Note that this will produce quite some output, so you may want to pipe the output to a file.
32 | By default, progress information is printed to stderr and results are printed to stdout.
33 |
34 | ## Using ARMORY in Your Own Projects
35 | 1. clone this git
36 | 2. copy the modules you would like to use from the `subprojects` directory into your own meson `subprojects` directory
37 | 3. in your `meson.build` file, pull in the dependencies you need via
38 | * `libmulator_dep = subproject('m-ulator').get_variable('libmulator_dep')`
39 | * `libarmory_dep = subproject('armory').get_variable('libarmory_dep')` (includes M-ulator)
40 | * `libfault_simulator_dep = subproject('fault_simulator').get_variable('libfault_simulator_dep')` (includes ARMORY and M-ulator)
41 | 4. add the dependency to your projects' `dependencies` variables
42 |
43 | It's best to take a look at the example in `src/main.cpp` to get a quick overview on how to use ARMORY.
44 | Our `fault_simulator` wrapper around ARMORY makes it easy to prepare everything you need in just a few steps.
45 |
46 | For a more complex application of ARMORY, take a look at the code we used for the experiments in our publication.
47 | This code can be found in a different [repository](https://github.com/emsec/arm-fault-simulator-paper-results).
48 |
49 |
50 | # Academic Context
51 |
52 | If you use ARMORY or M-ulator in an academic context, please cite our paper using the reference below:
53 | ```latex
54 | @article{9206547,
55 | author={M. {Hoffmann} and F. {Schellenberg} and C. {Paar}},
56 | journal={IEEE Transactions on Information Forensics and Security},
57 | title={ARMORY: Fully Automated and Exhaustive Fault Simulation on ARM-M Binaries},
58 | year={2021},
59 | volume={16},
60 | number={},
61 | pages={1058-1073},
62 | doi={10.1109/TIFS.2020.3027143}}
63 | }
64 | ```
65 |
66 | ## Reproducing the experiments from our paper
67 | The code that was used for the experiments in our paper and all the report files and visualizations are available in a separate [repository](https://github.com/emsec/arm-fault-simulator-paper-results).
68 | The code there will not be updated to retain reproducibility.
69 |
70 |
71 | # Known Issues
72 | - M-Ulator:
73 | - ARMv6-M unsupported instructions:
74 | - MSR/MRS
75 | - CPS
76 | - SVC
77 | - ARMv7E-M (DSP extension) not implemented
78 | - floating point extension not implemented
79 | - privilege modes not implemented (i.e., always unprotected access)
80 | - exceptions/interrupts not implemented
81 |
82 | - ARMORY:
83 | - None
84 |
--------------------------------------------------------------------------------
/example/example.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | typedef uint8_t u8;
5 | typedef uint32_t u32;
6 |
7 | void __attribute__((noinline)) super_secret_function()
8 | {
9 | while (true)
10 | {
11 | }
12 | }
13 |
14 | void interesting_to_fault(u8 array[32])
15 | {
16 | u8 comparison_data[32] = {0x17, 0xad, 0xa6, 0xf7, 0x91, 0xad, 0xab, 0x07, 0xa6, 0x03, 0x45, 0x84, 0x6a, 0x27, 0x5b, 0xf3,
17 | 0x09, 0xdd, 0xde, 0x18, 0x9f, 0xa9, 0xd4, 0x23, 0x7b, 0x7b, 0x92, 0x2a, 0xe1, 0x4a, 0xef, 0x27};
18 |
19 | bool equal = true;
20 | for (u32 i = 0; i < 32; ++i)
21 | {
22 | if (array[i] != comparison_data[i])
23 | {
24 | equal = false;
25 | break;
26 | }
27 | }
28 |
29 | if (equal)
30 | {
31 | super_secret_function();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/linker.ld:
--------------------------------------------------------------------------------
1 | MEMORY
2 | {
3 | flash (rx) : ORIGIN = 0x00008000, LENGTH = 32K /* FLASH area */
4 | ram (xrw): ORIGIN = 0x00020000, LENGTH = 192K /* RAM area */
5 | }
6 |
7 |
8 | SECTIONS
9 | {
10 | .text : /* collect all code related sections */
11 | {
12 | . = ALIGN(4);
13 | *(.text) /* all .text sections (code) */
14 | *(.rodata) /* all .rodata sections (constants,…) */
15 | _etext = .; /* define a global symbol _etext */
16 | _exit = .;
17 | } >flash /* put all the above into flash */
18 |
19 | .data : /* all .data sections to RAM */
20 | {
21 | . = ALIGN(4);
22 | _data = .; /* symbol marking the .data start */
23 | *(.data) /* all .data sections */
24 | _edata = .; /* symbol marking the .data end */
25 | } >ram AT >flash /* .data will reside in RAM, but it is
26 | stored in the flash */
27 |
28 | .bss : /* .bss sections to RAM */
29 | {
30 | . = ALIGN(4);
31 | _bss_start = .; /*.bss section start*/
32 | *(.bss) /* all .bss sections */
33 | _bss_end = . ; /* .bss end symbol */
34 | } >ram /* put all the above in RAM */
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/example/start_emulation.py:
--------------------------------------------------------------------------------
1 | START_SYMBOL = "interesting_to_fault"
2 | HALT_SYMBOLS = ["super_secret_function"]
3 | IGNORE_SYMBOLS = []
4 |
5 |
6 | ################################################################
7 | ############## DO NOT CHANGE ANYTHING BELOW ####################
8 | ################################################################
9 |
10 | from subprocess import Popen, PIPE
11 | import os
12 | import sys
13 |
14 | def is_int(s):
15 | try:
16 | int(s, 16)
17 | except:
18 | return False
19 | return True
20 |
21 | def run(cmd):
22 | p = Popen(cmd.split(), stdin=PIPE, stdout=PIPE, stderr=PIPE)
23 | output, err = p.communicate()
24 | output = output.decode('UTF-8')
25 | err = err.decode('UTF-8')
26 | if p.returncode != 0:
27 | print(output)
28 | print(err)
29 | sys.exit(1)
30 | return output, err
31 |
32 | def num(s):
33 | return hex(int(s, 16))
34 |
35 | # extract/verify supported architecture
36 |
37 | armv6 = None
38 | out,err = run("arm-none-eabi-readelf -A tmp_data/binary.elf")
39 |
40 | if "Tag_CPU_name:" in out:
41 | out = out[out.find("Tag_CPU_name:"):]
42 | out = out[out.find(":")+1 : out.find("\n")].strip()
43 | if out == '"6-M"':
44 | armv6 = True
45 | elif out == '"7-M"':
46 | armv6 = False
47 | else:
48 | print("ERROR: {} is not an ARMv6-M or ARMv7-M binary.".format(out))
49 | sys.exit(1)
50 | else:
51 | print("ERROR: 'Tag_CPU_name' is missing in the output of readelf.")
52 | sys.exit(1)
53 |
54 |
55 |
56 | # extract addresses of symbols
57 |
58 | with open("tmp_data/binary.map", "rt") as file:
59 | lines = file.readlines()
60 |
61 | flash = (0,0)
62 | end_of_flash = None
63 | ram = (0,0)
64 | start_address = None
65 | halt_addresses = list()
66 | ignore_ranges = list()
67 |
68 | for line in lines:
69 | parts = line.split()
70 | if len(parts) < 2: continue
71 | if parts[0] == "flash":
72 | flash = (num(parts[1]), num(parts[2])) # origin, length
73 | end_of_flash = int(parts[1], 16) + int(parts[2],16)
74 | elif parts[0] == "ram":
75 | ram = (num(parts[1]), num(parts[2])) # origin, length
76 |
77 |
78 | # extract all symbols
79 |
80 | begin_ignore = None
81 | sections = list()
82 | out,err = run("arm-none-eabi-objdump tmp_data/binary.elf -t")
83 | lines = sorted([l for l in out.split("\n")])
84 | for l in lines:
85 | parts = l.split()
86 |
87 | if len(parts) < 3: continue
88 |
89 | if begin_ignore != None and parts[2] == "F" and not (parts[-1] in IGNORE_SYMBOLS):
90 | if is_int(parts[0]):
91 | ignore_ranges.append((begin_ignore, hex(min(int(parts[0],16), end_of_flash))))
92 | begin_ignore = None
93 |
94 | if parts[-1] == START_SYMBOL:
95 | start_address = num(parts[0])
96 | elif parts[-1] in HALT_SYMBOLS:
97 | halt_addresses.append((parts[-1], num(parts[0])))
98 | elif (parts[-1] in IGNORE_SYMBOLS or parts[2] == "O") and begin_ignore == None:
99 | begin_ignore = num(parts[0])
100 |
101 | if begin_ignore != None:
102 | ignore_ranges.append((begin_ignore, hex(end_of_flash)))
103 |
104 | if flash == (0,0) or ram == (0,0):
105 | print("flash or ram section could not be found in map file")
106 | sys.exit(1)
107 |
108 | if start_address == None:
109 | print("{} symbol could not be found in map file".format(START_SYMBOL))
110 | sys.exit(1)
111 |
112 | if not halt_addresses:
113 | print("no end symbol could not be found in map file")
114 | sys.exit(1)
115 |
116 | # extract all sections to binary files
117 |
118 | sections = list()
119 | out,err = run("arm-none-eabi-objdump tmp_data/binary.elf -h")
120 | lines = [l for l in out.split("\n")]
121 | while len(lines) > 0:
122 | l = lines.pop(0)
123 | parts = l.split()
124 | if len(parts) == 7 and parts[1].startswith("."):
125 | attr = lines.pop(0)
126 | if "ALLOC" not in attr: continue
127 | if int(parts[2], 16) != 0: # size != 0
128 | sections.append((parts[1], num(parts[3]))) # name, offset
129 |
130 | for name, _ in sections:
131 | run("arm-none-eabi-objcopy -O binary --only-section="+name+" tmp_data/binary.elf tmp_data/code_section"+name)
132 |
133 | # generate command line args
134 |
135 | args = "--start {}".format(start_address)
136 | for x in halt_addresses:
137 | args += " --halt {} {}".format(x[0], x[1])
138 | for start, end in ignore_ranges:
139 | args += " --ignore {} {}".format(start, end)
140 |
141 | args += " --flash {} {} --ram {} {}".format(flash[0], flash[1], ram[0], ram[1])
142 |
143 | if armv6: args += " --armv6m"
144 | else: args += " --armv7m"
145 |
146 | for name, offset in sections:
147 | args += " --section tmp_data/code_section{} {}".format(name, offset)
148 |
149 | sys.stderr.write(args+"\n")
150 |
151 | os.system("../build/armory_example " + args)
152 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('ARMORY_EXAMPLE', 'cpp',
2 | version : '0.1',
3 | default_options : ['warning_level=3', 'cpp_std=c++17', 'b_lto=true'])
4 |
5 | message(get_option('buildtype'), 'build')
6 |
7 | libarmory_cli_dep = subproject('armory_cli').get_variable('libarmory_cli_dep')
8 |
9 | executable('armory_example',
10 | sources: files('src/main.cpp'),
11 | include_directories : include_directories('include'),
12 | dependencies: libarmory_cli_dep)
13 |
14 |
--------------------------------------------------------------------------------
/run_example.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import os
4 | import sys
5 | import shutil
6 |
7 | # build ARMORY
8 | if not os.path.exists("build/"):
9 | if os.system("meson -Db_lto=true --buildtype=release build") != 0: sys.exit(1)
10 | if os.system("meson compile -C build/") != 0: sys.exit(1)
11 |
12 | # dir for tmp data
13 | os.makedirs("example/tmp_data/", exist_ok=True)
14 |
15 | # build the ARM binary
16 | if os.system("arm-none-eabi-gcc -Wall -Wpedantic -std=c11 -ffreestanding -nostdlib -mthumb -march=armv7-m -O3 -Wl,-Texample/linker.ld -Wl,-Map,example/tmp_data/binary.map example/example.c -o example/tmp_data/binary.elf") != 0: sys.exit(1)
17 |
18 | # create disassembly for looking up armory results later
19 | if os.system("arm-none-eabi-objdump example/tmp_data/binary.elf -d > disassembled.txt") != 0: sys.exit(1)
20 |
21 | # start fault simulation
22 | if os.system("cd example && python3 start_emulation.py") != 0: sys.exit(1)
23 |
24 | # cleanup tmp data
25 | shutil.rmtree("example/tmp_data/")
26 |
27 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emsec/arm-fault-simulator/e292e2bd244dd708cec632052d60637037faec99/screenshot.png
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include "armory/fault_tracer.h"
2 | #include "armory_cli/fault_models.h"
3 | #include "armory_cli/armory_cli.h"
4 | #include "termcolor.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | using namespace armory;
11 |
12 | int main(int argc, char** argv)
13 | {
14 | // parse command line arguments, generated by start_emulation.py
15 | armory_cli::Configuration config;
16 | try
17 | {
18 | config = armory_cli::parse_arguments(argc, argv);
19 | }
20 | catch (std::exception& ex)
21 | {
22 | std::cout << ex.what() << std::endl;
23 | return 1;
24 | }
25 |
26 | // setup the emulator instance
27 | Emulator main_emulator(config.arch);
28 | main_emulator.set_flash_region(config.flash.offset, config.flash.size);
29 | main_emulator.set_ram_region(config.ram.offset, config.ram.size);
30 | for (const auto& s : config.binary)
31 | {
32 | main_emulator.write_memory(s.offset, s.bytes.data(), s.bytes.size());
33 | }
34 | main_emulator.write_register(Register::SP, config.ram.offset + config.ram.size);
35 | main_emulator.write_register(Register::PC, config.start_address);
36 | main_emulator.write_register(Register::LR, 0xFFFFFFFF);
37 |
38 | // example function `interesting_to_fault` gets a 32-byte array as first parameter, so we fill it with random data
39 | for (u32 i = 0; i < 8; ++i)
40 | {
41 | u32 random = rand();
42 | main_emulator.write_memory(config.ram.offset + 4 * i, (u8*)&random, 4);
43 | }
44 | main_emulator.write_register(Register::R0, config.ram.offset);
45 |
46 | // set a maximum number of instructions to execute before a fault is automatically regarded as not exploitable
47 | config.faulting_context.emulation_timeout = 500;
48 |
49 | // we could select a different exploitability model...
50 | // setting this to nullptr is equivalent to regarding every fault that makes execution reach a halting point as exploitable
51 | config.faulting_context.exploitability_model = nullptr;
52 |
53 |
54 | // now let's inject some faults
55 |
56 | u32 total_faults = 0;
57 | auto start = std::chrono::steady_clock::now();
58 |
59 | for (const auto& spec : fault_models::all_fault_models)
60 | {
61 | // inject the model 1 time
62 | auto model_injection = std::make_pair(spec, 1);
63 |
64 | // inject the model 2 times
65 | // auto model_injection = std::make_pair(spec, 2);
66 |
67 | auto exploitable_faults = armory_cli::find_exploitable_faults(main_emulator, config, {model_injection});
68 | total_faults += exploitable_faults.size();
69 | }
70 |
71 | auto end = std::chrono::steady_clock::now();
72 | double seconds = std::chrono::duration_cast(end - start).count() / 1000.0;
73 |
74 | std::cout << "_______________________________________" << std::endl;
75 | std::cout << "All tested models combined: " << std::dec << total_faults << " exploitable faults" << std::endl;
76 | std::cout << "Total time: " << seconds << " seconds" << std::endl << std::endl;
77 |
78 | return 0;
79 | }
80 |
--------------------------------------------------------------------------------
/subprojects/armory/include/armory/context.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "m-ulator/emulator.h"
4 | #include "armory/types.h"
5 |
6 | #include
7 | #include
8 | #include
9 |
10 | namespace armory
11 | {
12 | struct Context;
13 |
14 | using namespace mulator;
15 |
16 | struct ExploitabilityModel
17 | {
18 | ExploitabilityModel() = default;
19 | virtual ~ExploitabilityModel() = default;
20 |
21 | enum class Decision
22 | {
23 | EXPLOITABLE,
24 | NOT_EXPLOITABLE,
25 | CONTINUE_SIMULATION,
26 | };
27 |
28 | virtual std::unique_ptr clone() = 0;
29 |
30 | virtual Decision evaluate(const Emulator& emu, const Context& ctx, u32 reached_end_address) = 0;
31 | };
32 |
33 | struct TimeRange
34 | {
35 | u32 start;
36 | u32 end;
37 | };
38 |
39 | struct MemoryRange
40 | {
41 | u32 offset;
42 | u32 size;
43 | };
44 |
45 | struct Context
46 | {
47 | /*
48 | * Defines addresses on which fault simulation is stopped.
49 | * Use addresses where you want to check/know whether a fault was exploitable.
50 | */
51 | std::vector halting_points;
52 |
53 | /*
54 | * The model is checked whenever an "end address" is hit.
55 | * If it returns FAULT_EXPLOITABLE, the fault is regarded as exploitable and the next iteration is tested.
56 | * If it returns NOT_EXPLOITABLE, the fault is regarded as not exploitable and the next iteration is tested.
57 | * If it returns CONTINUE_SIMULATION, the fault is regarded as not exploitable and the ongoing simulation is continued for the same fault.
58 | * If the function object is left empty, every fault for which an end address is reached is regarded as FAULT_EXPLOITABLE.
59 | */
60 | ExploitabilityModel* exploitability_model = nullptr;
61 |
62 | /*
63 | * Give a timeout in number of executed instructions for binary emulation.
64 | * The timeout is applied for every combination of faults individually.
65 | */
66 | u32 emulation_timeout = 0;
67 |
68 | /*
69 | * If you want to not inject faults in a time tange, define it here.
70 | * This does not affect permanent faults!
71 | */
72 | std::vector ignore_time_ranges;
73 |
74 | /*
75 | * If you want to not inject faults in an address range, define it here
76 | */
77 | std::vector ignore_memory_ranges;
78 | };
79 | } // namespace armory
80 |
--------------------------------------------------------------------------------
/subprojects/armory/include/armory/fault_combination.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "armory/instruction_fault_model.h"
4 | #include "armory/register_fault_model.h"
5 |
6 | namespace armory
7 | {
8 | class FaultCombination
9 | {
10 | public:
11 | FaultCombination();
12 | FaultCombination(const FaultCombination& other);
13 | FaultCombination& operator=(const FaultCombination& other);
14 |
15 | /*
16 | * Adds a fault to this combination.
17 | */
18 | void add(const InstructionFault& fault);
19 | void add(const RegisterFault& fault);
20 |
21 | /*
22 | * Checks whether this FaultCombination includes another one.
23 | * If both combinations lead to exploitable faults and one includes the other, only the smaller one needs to be kept.
24 | */
25 | bool includes(const FaultCombination& other) const;
26 |
27 | /*
28 | * Returns a sorted vector of all faults in the combination.
29 | * The sorted vector is also cached, i.e., first call after adding faults is slower than subsequent calls
30 | */
31 | const std::vector& get_sorted_faults() const;
32 |
33 | bool operator==(const FaultCombination& other) const;
34 | bool operator!=(const FaultCombination& other) const;
35 | bool operator<(const FaultCombination& other) const;
36 | bool operator>(const FaultCombination& other) const;
37 |
38 | u32 size() const;
39 |
40 | /*
41 | * Direct member access to the vectors of specific faults.
42 | */
43 | std::vector instruction_faults;
44 | std::vector register_faults;
45 |
46 | private:
47 | mutable std::vector m_sorted;
48 | mutable u32 m_sorted_size;
49 | };
50 |
51 | } // namespace armory
52 |
53 | template<>
54 | struct std::hash
55 | {
56 | std::size_t operator()(const armory::FaultCombination& x) const noexcept
57 | {
58 | size_t seed = 0;
59 | for (auto fault : x.get_sorted_faults())
60 | {
61 | if (auto ptr = dynamic_cast(fault); ptr != nullptr)
62 | {
63 | armory::hash_combine(seed, *ptr);
64 | }
65 | else if (auto ptr = dynamic_cast(fault); ptr != nullptr)
66 | {
67 | armory::hash_combine(seed, *ptr);
68 | }
69 | }
70 | return seed;
71 | }
72 | };
73 |
--------------------------------------------------------------------------------
/subprojects/armory/include/armory/fault_model.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "armory/context.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | namespace armory
11 | {
12 | /*
13 | * Base class.
14 | * See InstructionFaultModel or RegisterFaultModel for implementations.
15 | *
16 | * In general a FaultModel specifies the effect and duration of a fault.
17 | */
18 | class FaultModel
19 | {
20 | public:
21 | FaultModel(const std::string& name);
22 | virtual ~FaultModel() = default;
23 |
24 | std::string get_name() const;
25 |
26 | virtual bool is_permanent() const = 0;
27 |
28 | private:
29 | std::string m_name;
30 | };
31 |
32 | /*
33 | * Base class.
34 | * See InstructionFault or RegisterFault for implementations.
35 | *
36 | * In general a Fault holds the effect of the application of a specific iteration of a FaultModel.
37 | */
38 | struct Fault
39 | {
40 | Fault(u32 time, u32 fault_model_iteration);
41 | virtual ~Fault() = 0;
42 | u32 time;
43 | u32 fault_model_iteration;
44 | };
45 |
46 | template
47 | inline void hash_combine(std::size_t& seed, const T& v)
48 | {
49 | std::hash hasher;
50 | seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
51 | }
52 |
53 | } // namespace armory
54 |
--------------------------------------------------------------------------------
/subprojects/armory/include/armory/fault_simulator.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "armory/context.h"
4 | #include "armory/fault_combination.h"
5 | #include "armory/snapshot.h"
6 | #include "m-ulator/emulator.h"
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | namespace armory
20 | {
21 | using namespace mulator;
22 |
23 | class FaultSimulator
24 | {
25 | public:
26 | /*
27 | * Creates a new fault simulator with the given context.
28 | * The context defines when a fault is exploitable
29 | */
30 | FaultSimulator(const Context& ctx);
31 | ~FaultSimulator() = default;
32 |
33 | /*
34 | * Enables printing of progress information.
35 | * Disabled by default.
36 | * Information is printed to stderr in order to be separable from other output.
37 | */
38 | void enable_progress_printing(bool enable);
39 |
40 | /*
41 | * Sets the number of threads used for fault simulation.
42 | * A value of 0 (default) uses the same number of threads as CPU cores are available.
43 | */
44 | void set_number_of_threads(u32 threads);
45 |
46 | /*
47 | * Start the fault simulation.
48 | * The given emulator is taken as the base state, i.e., you can initialize an emulator, add your own hooks, and emulate arbitrary instructions before starting fault injection.
49 | * 'fault_models' contains all FaultModels to be tested, together with their amount.
50 | * Every FaultModel must appear only once in the vector, use the amount to test multiple instances of the same model.
51 | *
52 | * The fault simulator will automatically test all permutations and combinations of the models.
53 | * A maximum of 'max_simulatenous_faults' faults is injected during a single test. A value of 0 indicates no upper limit.
54 | * This function automatically utilizes all available CPU cores.
55 | */
56 | std::vector simulate_faults(const Emulator& emulator, std::vector> fault_models, u32 max_simulatenous_faults);
57 |
58 | /*
59 | * Gets the number of faults that were injected during the last call to 'simulate_faults'.
60 | */
61 | u64 get_number_of_injected_faults();
62 |
63 | private:
64 | struct ThreadContext
65 | {
66 | ThreadContext(const Emulator& main_emulator) : emu(main_emulator), decoder(emu.get_decoder())
67 | {
68 | }
69 |
70 | Emulator emu;
71 | InstructionDecoder decoder;
72 | bool end_reached;
73 | ExploitabilityModel::Decision decision;
74 | std::unique_ptr exploitability_model;
75 | std::vector> snapshots;
76 | std::vector new_faults;
77 | u64 num_fault_injections;
78 | };
79 |
80 | void gather_faultable_instructions(const Emulator& main_emulator);
81 |
82 | std::vector> compute_model_combinations(const std::vector>& fault_models, u32 max_simulatenous_faults);
83 |
84 | std::vector prepare_known_exploitable_faults(const std::vector& current_models, const std::map, std::vector>& memorized_faults);
85 |
86 | static void detect_end_of_execution(Emulator& emu, u32 address, u32 instr_size, void* hook_context);
87 |
88 | void simulate(const Emulator& main_emulator);
89 |
90 | static void instruction_collector(Emulator& emu, const Instruction& instruction, void* user_data);
91 | std::vector> get_instruction_order(ThreadContext& thread_ctx, u32 remaining_cycles);
92 |
93 | static void add_new_registers_vector(Emulator& emu, const Instruction& instruction, void* user_data);
94 |
95 | void simulate_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 order, u32 remaining_cycles, const FaultCombination& current_chain);
96 |
97 | void simulate_permanent_instruction_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 order, u32 remaining_cycles, const FaultCombination& current_chain);
98 | void simulate_instruction_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 order, u32 remaining_cycles, const FaultCombination& current_chain);
99 |
100 | static void handle_permanent_register_fault_overwrite(Emulator& emu, Register reg, u32 value, void* hook_context);
101 |
102 | void simulate_permanent_register_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 order, u32 remaining_cycles, const FaultCombination& current_chain);
103 | void simulate_register_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 order, u32 remaining_cycles, const FaultCombination& current_chain);
104 |
105 | void update_progress(u32 new_progress);
106 | void print_progress();
107 |
108 | bool is_fault_redundant(const FaultCombination& c);
109 |
110 | Context m_ctx;
111 | std::vector m_fault_models;
112 | std::vector> m_all_instructions;
113 |
114 | bool m_print_progress;
115 | std::atomic m_progress;
116 | std::mutex m_print_mutex;
117 |
118 | u32 m_num_threads;
119 | std::atomic m_active_thread_count;
120 |
121 | std::atomic m_thread_progress;
122 | std::mutex m_synch_mutex;
123 |
124 | std::unordered_map> m_known_exploitable_faults;
125 | std::vector m_known_exploitable_fault_hashes;
126 | std::vector m_new_exploitable_faults;
127 |
128 | u64 m_num_fault_injections;
129 | };
130 | } // namespace armory
131 |
--------------------------------------------------------------------------------
/subprojects/armory/include/armory/fault_tracer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "armory/fault_combination.h"
4 | #include "armory/context.h"
5 | #include "m-ulator/disassembler.h"
6 | #include "m-ulator/emulator.h"
7 |
8 | #include
9 | #include