├── .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 | [![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](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 | ![Screenshot](screenshot.png "ARMORY Example Output") 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 10 | #include 11 | #include 12 | #include 13 | 14 | namespace armory 15 | { 16 | using namespace mulator; 17 | 18 | class FaultTracer 19 | { 20 | public: 21 | 22 | /* 23 | * Creates a new fault tracer. 24 | * The context should be the same as was used in the fault simulator. 25 | */ 26 | FaultTracer(const Context& ctx); 27 | ~FaultTracer() = default; 28 | 29 | 30 | /* 31 | * Start the fault tracing and print the trace to stdout. 32 | * 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. 33 | * 34 | * @param[in] emulator - the base emulator 35 | * @param[in] faults - the modelific fault combination to inject 36 | * @param[in] start_log_after_first_fault - if true, output is only printed after the first fault was injected 37 | * @param[in] log_cpu_state - if true, all current register values are printed after each executed instruction 38 | * 39 | * @returns true if the faults were exploitable. 40 | */ 41 | bool trace(const Emulator& emulator, FaultCombination& faults, bool start_log_after_first_fault = true, bool log_cpu_state = false); 42 | 43 | /* 44 | * Verify a modelific fault combination. 45 | * Nothing is printed to stdout. 46 | * 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. 47 | * 48 | * @param[in] emulator - the base emulator 49 | * @param[in] faults - the modelific fault combination to inject 50 | * 51 | * @returns true if the faults were exploitable. 52 | */ 53 | bool verify(const Emulator& emulator, FaultCombination& faults); 54 | 55 | private: 56 | static void log_instructions(Emulator& emu, const Instruction& instruction, void* hook_context); 57 | static void log_cpu_state(Emulator& emu, const Instruction& instruction, void* hook_context); 58 | static void detect_end_of_execution(Emulator& emu, u32 address, u32 instr_size, void* hook_context); 59 | static void handle_instruction_faults(Emulator& emu, u32 address, u32 instr_size, void* hook_context); 60 | static void handle_register_faults(Emulator& emu, u32 address, u32 instr_size, void* hook_context); 61 | static void handle_permanent_register_fault_overwrite(Emulator& emu, Register reg, u32 value, void* hook_context); 62 | void fault_injected(); 63 | 64 | Context m_ctx; 65 | std::unique_ptr m_disassembler; 66 | bool m_end_reached; 67 | ExploitabilityModel::Decision m_decision; 68 | std::unique_ptr m_exploitability_model; 69 | bool m_first_fault_reached; 70 | bool m_start_after_first_fault; 71 | bool m_log_cpu_state; 72 | bool m_faulted_this_instruction; 73 | bool m_verification_mode; 74 | }; 75 | } // namespace armory 76 | -------------------------------------------------------------------------------- /subprojects/armory/include/armory/instruction_fault_model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "armory/fault_model.h" 4 | 5 | #include 6 | 7 | namespace armory 8 | { 9 | struct InstructionFault; 10 | 11 | class InstructionFaultModel : public FaultModel 12 | { 13 | public: 14 | using IterationCounter = std::function; 15 | using Tester = std::function; 16 | using Injector = std::function; 17 | 18 | enum class FaultType 19 | { 20 | PERMANENT, // the fault is injected at the beginning of emulation and never removed 21 | TRANSIENT, // the fault is injected for a single instruction execution only 22 | }; 23 | 24 | /* 25 | * Constructs a new instruction fault model. 26 | * 27 | * @param[in] name - the name of the model 28 | * @param[in] type - the type of the faults to be injected 29 | * @param[in] counter - a function that returns, based on a concrete fault, how many iterations of the injector are performed 30 | * @param[in] tester - a function that returns true, if a concrete fault shall be executed 31 | * @param[in] injector - a function that sets the "manipulated_instruction" value in the fault, based on the "original_instruction" value 32 | */ 33 | InstructionFaultModel(const std::string& name, const FaultType& type, const IterationCounter& counter, const Tester& tester, const Injector& injector); 34 | 35 | bool is_permanent() const override; 36 | 37 | FaultType get_type() const; 38 | 39 | u32 get_number_of_iterations(const InstructionFault& fault) const; 40 | 41 | bool is_applicable(const InstructionFault& fault) const; 42 | 43 | void apply(InstructionFault& fault) const; 44 | 45 | private: 46 | FaultType m_type; 47 | IterationCounter m_counter; 48 | Tester m_tester; 49 | Injector m_injector; 50 | }; 51 | 52 | struct InstructionFault : public Fault 53 | { 54 | InstructionFault(const InstructionFaultModel* model, u32 time, u32 fault_model_iteration); 55 | ~InstructionFault() = default; 56 | 57 | const InstructionFaultModel* model; 58 | u32 address; 59 | u32 instr_size; 60 | u8 original_instruction[4]; 61 | u8 manipulated_instruction[4]; 62 | 63 | bool operator==(const InstructionFault& other) const; 64 | bool operator!=(const InstructionFault& other) const; 65 | bool operator<(const InstructionFault& other) const; 66 | bool operator>(const InstructionFault& other) const; 67 | }; 68 | 69 | } // namespace armory 70 | 71 | template<> 72 | struct std::hash 73 | { 74 | std::size_t operator()(const armory::InstructionFault& x) const noexcept 75 | { 76 | size_t seed = 0; 77 | armory::hash_combine(seed, x.address); 78 | armory::hash_combine(seed, x.model); 79 | armory::hash_combine(seed, x.time); 80 | armory::hash_combine(seed, x.fault_model_iteration); 81 | return seed; 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /subprojects/armory/include/armory/register_fault_model.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "armory/fault_model.h" 4 | 5 | namespace armory 6 | { 7 | struct RegisterFault; 8 | 9 | class RegisterFaultModel : public FaultModel 10 | { 11 | public: 12 | using IterationCounter = std::function; 13 | using Tester = std::function; 14 | using Injector = std::function; 15 | 16 | enum class FaultType 17 | { 18 | PERMANENT, // the register is faulted at the program beginning and whenever it changes 19 | UNTIL_OVERWRITE, // the register is faulted once 20 | TRANSIENT // the register is faulted once and the original value is restored after instruction execution 21 | }; 22 | 23 | /* 24 | * Constructs a new register fault model. 25 | * 26 | * @param[in] name - the name of the model 27 | * @param[in] type - the type of the faults to be injected 28 | * @param[in] counter - a function that returns, based on a concrete fault, how many iterations of the injector are performed 29 | * @param[in] tester - a function that returns true, if a concrete fault shall be executed 30 | * @param[in] injector - a function that sets the "manipulated_value" in the fault, based on the "original_value" 31 | */ 32 | RegisterFaultModel(const std::string& name, const FaultType& type, const IterationCounter& counter, const Tester& tester, const Injector& injector); 33 | 34 | bool is_permanent() const override; 35 | 36 | FaultType get_type() const; 37 | 38 | u32 get_number_of_iterations(const RegisterFault& fault) const; 39 | 40 | bool is_applicable(const RegisterFault& fault) const; 41 | 42 | void apply(RegisterFault& fault) const; 43 | 44 | private: 45 | FaultType m_type; 46 | IterationCounter m_counter; 47 | Tester m_tester; 48 | Injector m_injector; 49 | }; 50 | 51 | struct RegisterFault : public Fault 52 | { 53 | RegisterFault(const RegisterFaultModel* model, u32 time, u32 fault_model_iteration); 54 | ~RegisterFault() = default; 55 | 56 | const RegisterFaultModel* model; 57 | Register reg; 58 | u32 original_value; 59 | u32 manipulated_value; 60 | 61 | bool operator==(const RegisterFault& other) const; 62 | bool operator!=(const RegisterFault& other) const; 63 | bool operator<(const RegisterFault& other) const; 64 | bool operator>(const RegisterFault& other) const; 65 | }; 66 | 67 | } // namespace armory 68 | 69 | template<> 70 | struct std::hash 71 | { 72 | std::size_t operator()(const armory::RegisterFault& x) const noexcept 73 | { 74 | size_t seed = 0; 75 | armory::hash_combine(seed, x.reg); 76 | armory::hash_combine(seed, x.model); 77 | armory::hash_combine(seed, x.time); 78 | armory::hash_combine(seed, x.fault_model_iteration); 79 | return seed; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /subprojects/armory/include/armory/snapshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/emulator.h" 4 | 5 | namespace armory 6 | { 7 | using namespace mulator; 8 | 9 | class Snapshot 10 | { 11 | public: 12 | /* 13 | * Instantiates a new snapshot for the given emulator. 14 | * Snapshots can be used to reset an emulator to a previous state. 15 | * However, resetting invalidates all OTHER snapshots for the same emulator which where backed-up later. 16 | * Earlier backups work fine. 17 | * 18 | * This also registers a before-memory-read-hook in the emulator. 19 | * Clearing all hooks of the emulator will render the snapshot incorrect. 20 | */ 21 | Snapshot(Emulator& emu); 22 | ~Snapshot(); 23 | 24 | /* 25 | * Resets the snapshot. 26 | * This will cause the next call to backup() to be complete and not incremental based on monitored memory accesses. 27 | * Necessary when the emulator was restored with an older backup than saved in this snapshot. 28 | */ 29 | void reset(); 30 | 31 | /* 32 | * Save a backup of the current state including registers and RAM. 33 | */ 34 | void backup(); 35 | 36 | /* 37 | * Restores the emulator to the backed-up state in the snapshot. 38 | */ 39 | void restore(); 40 | 41 | private: 42 | Snapshot(const Snapshot&) = delete; 43 | static void on_memory_write(Emulator& emu, u32 address, u32 size, u32, void* user_data); 44 | 45 | Emulator& m_emulator; 46 | CPU_State m_state; 47 | u32 m_hook; 48 | u32 m_ram_offset; 49 | u32 m_ram_size; 50 | u8* m_ram_data; 51 | bool m_initialized; 52 | u32 m_low_change_start; 53 | u32 m_low_change_end; 54 | u32 m_high_change_start; 55 | u32 m_high_change_end; 56 | }; 57 | } // namespace armory 58 | -------------------------------------------------------------------------------- /subprojects/armory/include/armory/subset_chooser.h: -------------------------------------------------------------------------------- 1 | // Subset Chooser by Toby Speith 2 | // https://codereview.stackexchange.com/a/164766 3 | 4 | #include 5 | #include 6 | // C++ Concepts: template 7 | 8 | template 9 | class SubsetChooser 10 | { 11 | using Subset = std::vector; 12 | using Predicate = std::function; 13 | 14 | const It m_first; 15 | const It m_last; 16 | const size_t m_subset_size; 17 | const Predicate m_is_valid; 18 | 19 | Subset m_state; 20 | 21 | public: 22 | SubsetChooser(It first, It last, size_t subset_size, SubsetChooser::Predicate is_valid); 23 | const Subset& subset() const; 24 | bool advance(); 25 | }; 26 | 27 | //--- 28 | // factory methods 29 | 30 | template 31 | auto make_chooser(It first, It last, size_t subset_size, Predicate is_valid) 32 | { 33 | return SubsetChooser{first, last, subset_size, is_valid}; 34 | } 35 | template 36 | auto make_chooser(const Container& c, size_t subset_size, Predicate is_valid) 37 | { 38 | using std::begin; 39 | using std::end; 40 | return make_chooser(begin(c), end(c), subset_size, is_valid); 41 | } 42 | 43 | //--- 44 | // private helpers 45 | 46 | // Calculate it+n==end, without requiring a BidirectionalIterator 47 | template 48 | bool is_n_from(Iter it, Distance n, const Iter& end) 49 | { 50 | std::advance(it, n); 51 | return it == end; 52 | } 53 | 54 | //--- 55 | // implementation 56 | 57 | template 58 | SubsetChooser::SubsetChooser(It first, It last, size_t subset_size, SubsetChooser::Predicate is_valid) 59 | : m_first{first}, m_last{last}, 60 | m_subset_size{subset_size}, 61 | m_is_valid{is_valid}, 62 | m_state{} 63 | { 64 | m_state.reserve(m_subset_size); 65 | } 66 | 67 | template 68 | const typename SubsetChooser::Subset& SubsetChooser::subset() const 69 | { 70 | return m_state; 71 | } 72 | 73 | template 74 | bool SubsetChooser::advance() 75 | { 76 | do { 77 | if (m_state.empty()) { 78 | m_state.push_back(m_first); 79 | } else { 80 | if (m_state.size() < m_subset_size && m_is_valid(m_state)) { 81 | m_state.push_back(m_state.back()); 82 | } 83 | 84 | // Roll over when the remaining elements wouldn't fill the subset. 85 | while (is_n_from(++m_state.back(), m_subset_size - m_state.size(), m_last)) { 86 | m_state.pop_back(); 87 | if (m_state.empty()) 88 | // we have run out of possibilities 89 | return false; 90 | } 91 | } 92 | } while (m_state.size() < m_subset_size || !m_is_valid(m_state)); 93 | return true; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /subprojects/armory/include/armory/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define UNUSED(x) (void)(x); 6 | 7 | using u8 = uint8_t; 8 | using u16 = uint16_t; 9 | using u32 = uint32_t; 10 | using u64 = uint64_t; 11 | 12 | using i8 = int8_t; 13 | using i16 = int16_t; 14 | using i32 = int32_t; 15 | using i64 = int64_t; 16 | -------------------------------------------------------------------------------- /subprojects/armory/meson.build: -------------------------------------------------------------------------------- 1 | project('armory', 'cpp', 2 | version : '0.1', 3 | default_options : ['warning_level=3', 'cpp_std=c++17']) 4 | 5 | libmulator_dep = subproject('m-ulator').get_variable('libmulator_dep') 6 | 7 | incdir = include_directories('include') 8 | 9 | src = [] 10 | subdir('src') 11 | 12 | thread_dep = dependency('threads') 13 | 14 | libarmory = static_library('armory', sources: src, include_directories : incdir, dependencies: libmulator_dep) 15 | libarmory_dep = declare_dependency(include_directories : incdir, link_with: libarmory, dependencies: [libmulator_dep,thread_dep]) 16 | -------------------------------------------------------------------------------- /subprojects/armory/src/fault_combination.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/fault_combination.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace armory 8 | { 9 | FaultCombination::FaultCombination() 10 | { 11 | m_sorted_size = 0; 12 | } 13 | 14 | FaultCombination::FaultCombination(const FaultCombination& other) : instruction_faults(other.instruction_faults), register_faults(other.register_faults) 15 | { 16 | m_sorted_size = 0; 17 | } 18 | FaultCombination& FaultCombination::operator=(const FaultCombination& other) 19 | { 20 | instruction_faults = other.instruction_faults; 21 | register_faults = other.register_faults; 22 | m_sorted_size = 0; 23 | return *this; 24 | } 25 | 26 | void FaultCombination::add(const InstructionFault& fault) 27 | { 28 | instruction_faults.push_back(fault); 29 | } 30 | 31 | void FaultCombination::add(const RegisterFault& fault) 32 | { 33 | register_faults.push_back(fault); 34 | } 35 | 36 | static bool is_less_than(const Fault* a, const Fault* b) 37 | { 38 | if (auto p_i_a = dynamic_cast(a)) 39 | { 40 | if (auto p_i_b = dynamic_cast(b)) 41 | { 42 | if ((*p_i_a) < (*p_i_b)) 43 | { 44 | return true; 45 | } 46 | if ((*p_i_b) < (*p_i_a)) 47 | { 48 | return false; 49 | } 50 | } 51 | } 52 | else if (auto p_r_a = dynamic_cast(a)) 53 | { 54 | if (auto p_r_b = dynamic_cast(b)) 55 | { 56 | if ((*p_r_a) < (*p_r_b)) 57 | { 58 | return true; 59 | } 60 | if ((*p_r_b) < (*p_r_a)) 61 | { 62 | return false; 63 | } 64 | } 65 | } 66 | else 67 | { 68 | if (a->time != b->time) 69 | return a->time < b->time; 70 | if (a->fault_model_iteration != b->fault_model_iteration) 71 | return a->fault_model_iteration < b->fault_model_iteration; 72 | } 73 | return false; 74 | } 75 | 76 | const std::vector& FaultCombination::get_sorted_faults() const 77 | { 78 | if (m_sorted_size != size()) 79 | { 80 | m_sorted_size = size(); 81 | 82 | m_sorted.clear(); 83 | for (auto& x : instruction_faults) 84 | { 85 | m_sorted.push_back(&x); 86 | } 87 | for (auto& x : register_faults) 88 | { 89 | m_sorted.push_back(&x); 90 | } 91 | 92 | std::sort(m_sorted.begin(), m_sorted.end(), is_less_than); 93 | } 94 | return m_sorted; 95 | } 96 | 97 | template 98 | static bool custom_includes(const std::vector& a, const std::vector& b) 99 | { 100 | size_t i = 0; 101 | size_t end = b.size(); 102 | 103 | if (i == end) 104 | { 105 | return true; 106 | } 107 | 108 | for (auto& x : a) 109 | { 110 | if (x == b[i]) 111 | { 112 | ++i; 113 | if (i == end) 114 | { 115 | return true; 116 | } 117 | } 118 | } 119 | return false; 120 | } 121 | 122 | bool FaultCombination::includes(const FaultCombination& other) const 123 | { 124 | if (instruction_faults.size() < other.instruction_faults.size() || register_faults.size() < other.register_faults.size()) 125 | { 126 | return false; 127 | } 128 | return custom_includes(instruction_faults, other.instruction_faults) && custom_includes(register_faults, other.register_faults); 129 | // return std::includes(instruction_faults.begin(), instruction_faults.end(), other.instruction_faults.begin(), other.instruction_faults.end()) && std::includes(register_faults.begin(), register_faults.end(), other.register_faults.begin(), other.register_faults.end()); 130 | } 131 | 132 | bool FaultCombination::operator==(const FaultCombination& other) const 133 | { 134 | if (instruction_faults.size() != other.instruction_faults.size() || register_faults.size() != other.register_faults.size()) 135 | { 136 | return false; 137 | } 138 | 139 | return instruction_faults == other.instruction_faults && register_faults == other.register_faults; 140 | } 141 | 142 | bool FaultCombination::operator!=(const FaultCombination& other) const 143 | { 144 | return !(*this == other); 145 | } 146 | 147 | bool FaultCombination::operator<(const FaultCombination& other) const 148 | { 149 | u32 this_size = size(); 150 | u32 other_size = other.size(); 151 | if (this_size < other_size) 152 | { 153 | return true; 154 | } 155 | if (this_size > other_size) 156 | { 157 | return false; 158 | } 159 | auto& this_sorted = get_sorted_faults(); 160 | auto& other_sorted = other.get_sorted_faults(); 161 | 162 | for (u32 i = 0; i < this_size; ++i) 163 | { 164 | if (is_less_than(this_sorted[i], other_sorted[i])) 165 | { 166 | return true; 167 | } 168 | if (is_less_than(other_sorted[i], this_sorted[i])) 169 | { 170 | return false; 171 | } 172 | } 173 | return false; 174 | } 175 | 176 | bool FaultCombination::operator>(const FaultCombination& other) const 177 | { 178 | u32 this_size = size(); 179 | u32 other_size = other.size(); 180 | if (this_size > other_size) 181 | { 182 | return true; 183 | } 184 | if (this_size < other_size) 185 | { 186 | return false; 187 | } 188 | auto& this_sorted = get_sorted_faults(); 189 | auto& other_sorted = other.get_sorted_faults(); 190 | 191 | for (u32 i = 0; i < this_size; ++i) 192 | { 193 | if (is_less_than(this_sorted[i], other_sorted[i])) 194 | { 195 | return false; 196 | } 197 | if (is_less_than(other_sorted[i], this_sorted[i])) 198 | { 199 | return true; 200 | } 201 | } 202 | return false; 203 | } 204 | 205 | u32 FaultCombination::size() const 206 | { 207 | return instruction_faults.size() + register_faults.size(); 208 | } 209 | 210 | } // namespace armory 211 | -------------------------------------------------------------------------------- /subprojects/armory/src/fault_model.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/fault_model.h" 2 | 3 | namespace armory 4 | { 5 | 6 | FaultModel::FaultModel(const std::string& name) 7 | { 8 | m_name = name; 9 | } 10 | 11 | std::string FaultModel::get_name() const 12 | { 13 | return m_name; 14 | } 15 | 16 | Fault::Fault(u32 _time, u32 _fault_model_iteration) 17 | { 18 | this->time = _time; 19 | this->fault_model_iteration = _fault_model_iteration; 20 | } 21 | 22 | Fault::~Fault() {} 23 | 24 | } // namespace armory 25 | -------------------------------------------------------------------------------- /subprojects/armory/src/fault_tracer.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/fault_tracer.h" 2 | 3 | #include "armory/termcolor.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace armory; 10 | 11 | FaultTracer::FaultTracer(const Context& ctx) 12 | { 13 | m_ctx = ctx; 14 | m_verification_mode = true; 15 | } 16 | 17 | bool FaultTracer::trace(const Emulator& emulator, FaultCombination& faults, bool start_after_first_fault, bool log_cpu_state) 18 | { 19 | m_verification_mode = false; 20 | m_start_after_first_fault = start_after_first_fault; 21 | m_log_cpu_state = log_cpu_state; 22 | 23 | auto exploitable = verify(emulator, faults); 24 | 25 | std::cout << "--------------------------------" << std::endl; 26 | if (!m_first_fault_reached) 27 | { 28 | std::cout << "WARNING: no fault was injected" << std::endl; 29 | } 30 | if (exploitable) 31 | { 32 | std::cout << "Result: fault is exploitable!" << std::endl; 33 | } 34 | else 35 | { 36 | std::cout << "Result: fault is " << termcolor::red << "not" << termcolor::reset << " exploitable!" << std::endl; 37 | } 38 | 39 | m_verification_mode = true; 40 | 41 | return exploitable; 42 | } 43 | 44 | bool FaultTracer::verify(const Emulator& emulator, FaultCombination& faults) 45 | { 46 | using namespace std::placeholders; 47 | 48 | m_disassembler = std::make_unique(emulator.get_architecture()); 49 | 50 | m_first_fault_reached = false; 51 | m_faulted_this_instruction = false; 52 | 53 | Emulator emu(emulator); 54 | 55 | std::tuple hook_user_data(this, &faults); 56 | 57 | emu.before_fetch_hook.add(&FaultTracer::detect_end_of_execution, this); 58 | 59 | if (!faults.instruction_faults.empty()) 60 | { 61 | emu.before_fetch_hook.add(&FaultTracer::handle_instruction_faults, (void*)&hook_user_data); 62 | } 63 | 64 | if (!faults.register_faults.empty()) 65 | { 66 | emu.before_fetch_hook.add(&FaultTracer::handle_register_faults, (void*)&hook_user_data); 67 | } 68 | 69 | for (const auto& f : faults.register_faults) 70 | { 71 | if (f.model->get_type() == RegisterFaultModel::FaultType::PERMANENT) 72 | { 73 | emu.after_register_write_hook.add(&FaultTracer::handle_permanent_register_fault_overwrite, (void*)&hook_user_data); 74 | break; 75 | } 76 | } 77 | 78 | emu.instruction_decoded_hook.add(&FaultTracer::log_instructions, this); 79 | if (m_log_cpu_state) 80 | { 81 | emu.instruction_executed_hook.add(&FaultTracer::log_cpu_state, this); 82 | } 83 | 84 | m_end_reached = false; 85 | 86 | if (m_ctx.exploitability_model != nullptr) 87 | { 88 | m_exploitability_model = m_ctx.exploitability_model->clone(); 89 | } 90 | 91 | auto ret = emu.emulate(m_ctx.emulation_timeout); 92 | 93 | if (!m_verification_mode) 94 | { 95 | std::cout << "end of emulation: "; 96 | switch (ret) 97 | { 98 | case ReturnCode::MAX_INSTRUCTIONS_REACHED: 99 | std::cout << "timeout"; 100 | break; 101 | case ReturnCode::END_ADDRESS_REACHED: 102 | std::cout << "end address reached"; 103 | break; 104 | case ReturnCode::STOP_EMULATION_CALLED: 105 | std::cout << "stopped by user"; 106 | break; 107 | default: 108 | std::cout << ret; 109 | } 110 | std::cout << std::endl; 111 | } 112 | 113 | if (m_end_reached && m_decision != ExploitabilityModel::Decision::EXPLOITABLE) 114 | { 115 | m_end_reached = false; 116 | } 117 | 118 | return m_end_reached; 119 | } 120 | 121 | /////////////////////////////////////////// 122 | ///////////// PRIVATE /////////////////// 123 | /////////////////////////////////////////// 124 | 125 | void FaultTracer::fault_injected() 126 | { 127 | if (!m_first_fault_reached) 128 | { 129 | if (!m_verification_mode && m_start_after_first_fault) 130 | { 131 | std::cout << "..." << std::endl; 132 | } 133 | m_first_fault_reached = true; 134 | } 135 | } 136 | 137 | void FaultTracer::log_instructions(Emulator& emu, const Instruction& instruction, void* hook_context) 138 | { 139 | auto inst_ptr = (FaultTracer*)(hook_context); 140 | 141 | if (!inst_ptr->m_verification_mode && (inst_ptr->m_first_fault_reached || !inst_ptr->m_start_after_first_fault)) 142 | { 143 | inst_ptr->m_disassembler->disassemble(instruction); 144 | 145 | std::cout << std::dec << std::setw(5) << std::setfill(' ') << emu.get_time() << " | "; 146 | std::cout << std::hex << instruction.address << ": "; 147 | if (inst_ptr->m_faulted_this_instruction) 148 | std::cout << termcolor::bright_red; 149 | std::cout << std::setw(2 * instruction.size) << std::setfill('0') << instruction.encoding; 150 | std::cout << " " << inst_ptr->m_disassembler->get_string(); 151 | if (inst_ptr->m_faulted_this_instruction) 152 | std::cout << termcolor::reset; 153 | std::cout << std::endl; 154 | } 155 | } 156 | 157 | void FaultTracer::log_cpu_state(Emulator& emu, const Instruction& instruction, void* hook_context) 158 | { 159 | UNUSED(emu); 160 | UNUSED(instruction); 161 | auto inst_ptr = (FaultTracer*)(hook_context); 162 | if (!inst_ptr->m_verification_mode && (inst_ptr->m_first_fault_reached || !inst_ptr->m_start_after_first_fault)) 163 | { 164 | for (u32 i = 0; i <= 15; ++i) 165 | { 166 | std::cout << " " << (Register)i << " = " << std::hex << std::setw(8) << std::setfill('0') << emu.read_register((Register)i) << std::endl; 167 | } 168 | std::cout << std::endl; 169 | } 170 | } 171 | 172 | void FaultTracer::detect_end_of_execution(Emulator& emu, u32 address, u32 instr_size, void* hook_context) 173 | { 174 | UNUSED(instr_size); 175 | auto inst_ptr = (FaultTracer*)(hook_context); 176 | for (auto end_address : inst_ptr->m_ctx.halting_points) 177 | { 178 | if (end_address == address) 179 | { 180 | if (inst_ptr->m_exploitability_model == nullptr) 181 | { 182 | inst_ptr->m_decision = ExploitabilityModel::Decision::EXPLOITABLE; 183 | } 184 | else 185 | { 186 | inst_ptr->m_decision = inst_ptr->m_exploitability_model->evaluate(emu, inst_ptr->m_ctx, address); 187 | } 188 | if (inst_ptr->m_decision != ExploitabilityModel::Decision::CONTINUE_SIMULATION) 189 | { 190 | inst_ptr->m_end_reached = true; 191 | emu.stop_emulation(); 192 | } 193 | return; 194 | } 195 | } 196 | } 197 | 198 | void FaultTracer::handle_instruction_faults(Emulator& emu, u32 address, u32 instr_size, void* hook_context) 199 | { 200 | UNUSED(instr_size); 201 | auto [inst_ptr, faults] = *((std::tuple*)hook_context); 202 | 203 | inst_ptr->m_faulted_this_instruction = false; 204 | 205 | for (auto& fault : faults->instruction_faults) 206 | { 207 | if (fault.model->get_type() == InstructionFaultModel::FaultType::TRANSIENT) 208 | { 209 | if (fault.time == emu.get_time() - 1) 210 | { 211 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 212 | } 213 | } 214 | } 215 | 216 | for (auto& fault : faults->instruction_faults) 217 | { 218 | if (fault.time == emu.get_time()) 219 | { 220 | emu.read_memory(fault.address, fault.original_instruction, fault.instr_size); 221 | fault.model->apply(fault); 222 | emu.write_memory(fault.address, fault.manipulated_instruction, fault.instr_size); 223 | if (!fault.model->is_permanent()) 224 | { 225 | inst_ptr->fault_injected(); 226 | inst_ptr->m_faulted_this_instruction = true; 227 | } 228 | } 229 | 230 | if (fault.model->is_permanent() && fault.address == address) 231 | { 232 | inst_ptr->fault_injected(); 233 | inst_ptr->m_faulted_this_instruction = true; 234 | } 235 | } 236 | } 237 | 238 | void FaultTracer::handle_register_faults(Emulator& emu, u32 address, u32 instr_size, void* hook_context) 239 | { 240 | UNUSED(address); 241 | UNUSED(instr_size); 242 | auto [inst_ptr, faults] = *((std::tuple*)hook_context); 243 | 244 | for (const auto& fault : faults->register_faults) 245 | { 246 | if (fault.model->get_type() == RegisterFaultModel::FaultType::TRANSIENT) 247 | { 248 | if (emu.get_time() == fault.time + 1) 249 | { 250 | emu.write_register(fault.reg, fault.original_value); 251 | if (!inst_ptr->m_verification_mode) 252 | { 253 | std::cout << " revert " << fault.reg << " back to " << std::hex << std::setw(8) << std::setfill('0') << fault.original_value << std::endl; 254 | } 255 | } 256 | } 257 | } 258 | 259 | for (auto& fault : faults->register_faults) 260 | { 261 | if (fault.time == emu.get_time()) 262 | { 263 | fault.original_value = emu.read_register(fault.reg); 264 | fault.model->apply(fault); 265 | emu.write_register(fault.reg, fault.manipulated_value); 266 | inst_ptr->fault_injected(); 267 | if (!inst_ptr->m_verification_mode) 268 | { 269 | std::cout << " " << fault.reg << " : " << std::hex << std::setw(8) << std::setfill('0') << fault.original_value; 270 | std::cout << " -> " << termcolor::bright_red << std::setw(8) << std::setfill('0') << fault.manipulated_value << termcolor::reset << std::endl; 271 | } 272 | } 273 | } 274 | } 275 | 276 | void FaultTracer::handle_permanent_register_fault_overwrite(Emulator& emu, Register reg, u32 value, void* hook_context) 277 | { 278 | auto [inst_ptr, faults] = *((std::tuple*)hook_context); 279 | for (auto& fault : faults->register_faults) 280 | { 281 | if (fault.model->is_permanent() && fault.reg == reg) 282 | { 283 | fault.original_value = value; 284 | fault.model->apply(fault); 285 | emu.write_register(fault.reg, fault.manipulated_value); 286 | inst_ptr->fault_injected(); 287 | if (!inst_ptr->m_verification_mode) 288 | { 289 | std::cout << " " << fault.reg << " : " << std::hex << std::setw(8) << std::setfill('0') << fault.original_value; 290 | std::cout << " -> " << termcolor::bright_red << std::setw(8) << std::setfill('0') << fault.manipulated_value << termcolor::reset << std::endl; 291 | } 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /subprojects/armory/src/instruction_fault_model.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/instruction_fault_model.h" 2 | 3 | namespace armory 4 | { 5 | InstructionFaultModel::InstructionFaultModel(const std::string& name, const FaultType& type, const IterationCounter& counter, const Tester& tester, const Injector& injector) : FaultModel(name) 6 | { 7 | m_type = type; 8 | m_counter = counter; 9 | m_tester = tester; 10 | m_injector = injector; 11 | } 12 | 13 | bool InstructionFaultModel::is_permanent() const 14 | { 15 | return m_type == FaultType::PERMANENT; 16 | } 17 | 18 | InstructionFaultModel::FaultType InstructionFaultModel::get_type() const 19 | { 20 | return m_type; 21 | } 22 | 23 | u32 InstructionFaultModel::get_number_of_iterations(const InstructionFault& fault) const 24 | { 25 | return m_counter(fault); 26 | } 27 | 28 | bool InstructionFaultModel::is_applicable(const InstructionFault& fault) const 29 | { 30 | return m_tester(fault); 31 | } 32 | 33 | void InstructionFaultModel::apply(InstructionFault& fault) const 34 | { 35 | m_injector(fault); 36 | } 37 | 38 | // ######################################################## 39 | // ######################################################## 40 | // ######################################################## 41 | 42 | InstructionFault::InstructionFault(const InstructionFaultModel* _model, u32 _time, u32 _fault_model_iteration) : Fault(_time, _fault_model_iteration) 43 | { 44 | this->model = _model; 45 | } 46 | 47 | bool InstructionFault::operator<(const InstructionFault& other) const 48 | { 49 | if (time != other.time) 50 | return time < other.time; 51 | if (address != other.address) 52 | return address < other.address; 53 | if (fault_model_iteration != other.fault_model_iteration) 54 | return fault_model_iteration < other.fault_model_iteration; 55 | return false; 56 | } 57 | 58 | bool InstructionFault::operator>(const InstructionFault& other) const 59 | { 60 | return other < (*this); 61 | } 62 | 63 | bool InstructionFault::operator==(const InstructionFault& other) const 64 | { 65 | return model == other.model && address == other.address && time == other.time && fault_model_iteration == other.fault_model_iteration; 66 | } 67 | 68 | bool InstructionFault::operator!=(const InstructionFault& other) const 69 | { 70 | return !(*this == other); 71 | } 72 | 73 | } // namespace armory 74 | -------------------------------------------------------------------------------- /subprojects/armory/src/meson.build: -------------------------------------------------------------------------------- 1 | src += files( 2 | 'fault_combination.cpp', 3 | 'fault_model.cpp', 4 | 'fault_simulator.cpp', 5 | 'fault_tracer.cpp', 6 | 'instruction_fault_model.cpp', 7 | 'register_fault_model.cpp', 8 | 'simulate_instruction_fault.cpp', 9 | 'simulate_register_fault.cpp', 10 | 'snapshot.cpp', 11 | ) 12 | -------------------------------------------------------------------------------- /subprojects/armory/src/register_fault_model.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/register_fault_model.h" 2 | 3 | namespace armory 4 | { 5 | RegisterFaultModel::RegisterFaultModel(const std::string& name, const FaultType& type, const IterationCounter& counter, const Tester& tester, const Injector& injector) : FaultModel(name) 6 | { 7 | m_type = type; 8 | m_counter = counter; 9 | m_tester = tester; 10 | m_injector = injector; 11 | } 12 | 13 | bool RegisterFaultModel::is_permanent() const 14 | { 15 | return m_type == FaultType::PERMANENT; 16 | } 17 | 18 | RegisterFaultModel::FaultType RegisterFaultModel::get_type() const 19 | { 20 | return m_type; 21 | } 22 | 23 | u32 RegisterFaultModel::get_number_of_iterations(const RegisterFault& fault) const 24 | { 25 | return m_counter(fault); 26 | } 27 | 28 | bool RegisterFaultModel::is_applicable(const RegisterFault& fault) const 29 | { 30 | return m_tester(fault); 31 | } 32 | 33 | void RegisterFaultModel::apply(RegisterFault& fault) const 34 | { 35 | m_injector(fault); 36 | } 37 | 38 | // ######################################################## 39 | // ######################################################## 40 | // ######################################################## 41 | 42 | RegisterFault::RegisterFault(const RegisterFaultModel* _model, u32 _time, u32 _fault_model_iteration) : Fault(_time, _fault_model_iteration) 43 | { 44 | this->model = _model; 45 | } 46 | 47 | bool RegisterFault::operator<(const RegisterFault& other) const 48 | { 49 | if (time != other.time) 50 | return time < other.time; 51 | if (reg != other.reg) 52 | return reg < other.reg; 53 | if (fault_model_iteration != other.fault_model_iteration) 54 | return fault_model_iteration < other.fault_model_iteration; 55 | return false; 56 | } 57 | 58 | bool RegisterFault::operator>(const RegisterFault& other) const 59 | { 60 | return other < (*this); 61 | } 62 | 63 | bool RegisterFault::operator==(const RegisterFault& other) const 64 | { 65 | return model == other.model && time == other.time && reg == other.reg && fault_model_iteration == other.fault_model_iteration; 66 | } 67 | 68 | bool RegisterFault::operator!=(const RegisterFault& other) const 69 | { 70 | return !(*this == other); 71 | } 72 | 73 | } // namespace armory 74 | -------------------------------------------------------------------------------- /subprojects/armory/src/simulate_instruction_fault.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/fault_simulator.h" 2 | #include "m-ulator/emulator.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // ######################################### 12 | 13 | using namespace armory; 14 | 15 | // used as a hook in get_instruction_order 16 | void FaultSimulator::instruction_collector(Emulator& emu, const Instruction& instruction, void* user_data) 17 | { 18 | if (!emu.is_running()) 19 | { 20 | return; 21 | } 22 | 23 | UNUSED(emu); 24 | auto [inst_ptr, addresses_to_fault] = *((std::tuple>*>*)user_data); 25 | 26 | // check if the current address is in an ignore range 27 | for (const auto& range : inst_ptr->m_ctx.ignore_memory_ranges) 28 | { 29 | if (instruction.address >= range.offset && instruction.address < range.offset + range.size) 30 | { 31 | return; 32 | } 33 | } 34 | for (const auto& range : inst_ptr->m_ctx.ignore_time_ranges) 35 | { 36 | if (emu.get_time() >= range.start && emu.get_time() < range.end) 37 | { 38 | return; 39 | } 40 | } 41 | 42 | addresses_to_fault->emplace_back(instruction.address, instruction.size); 43 | } 44 | 45 | std::vector> FaultSimulator::get_instruction_order(ThreadContext& thread_ctx, u32 remaining_cycles) 46 | { 47 | using namespace std::placeholders; 48 | 49 | std::vector> addresses_to_fault; 50 | 51 | // strategy: just run the emulator from the current address and collect all executed instructions 52 | std::tuple>*> hook_user_data(this, &addresses_to_fault); 53 | u32 hook = thread_ctx.emu.instruction_decoded_hook.add(&FaultSimulator::instruction_collector, &hook_user_data); 54 | 55 | if (m_ctx.exploitability_model != nullptr) 56 | { 57 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 58 | } 59 | 60 | thread_ctx.emu.emulate(remaining_cycles); 61 | 62 | thread_ctx.emu.instruction_decoded_hook.remove(hook); 63 | 64 | return addresses_to_fault; 65 | } 66 | 67 | void FaultSimulator::simulate_instruction_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 fault_model_index, u32 remaining_cycles, const FaultCombination& current_chain) 68 | { 69 | auto& emu = thread_ctx.emu; 70 | auto active_model = dynamic_cast(m_fault_models[fault_model_index]); 71 | 72 | thread_ctx.snapshots[fault_model_index]->reset(); 73 | thread_ctx.snapshots[fault_model_index]->backup(); 74 | 75 | auto addresses_to_fault = get_instruction_order(thread_ctx, remaining_cycles); 76 | u32 num_fault_addresses = addresses_to_fault.size(); 77 | 78 | u32 last_index = 0; 79 | u32 current_index = -1; 80 | 81 | u8 original_instruction[4]; 82 | 83 | while (true) 84 | { 85 | if (fault_model_index == 0) 86 | { 87 | current_index = m_thread_progress++; 88 | } 89 | else 90 | { 91 | current_index++; 92 | } 93 | 94 | if (current_index >= num_fault_addresses) 95 | { 96 | break; 97 | } 98 | 99 | if (m_print_progress && fault_model_index == 0) 100 | { 101 | update_progress(std::round(100.0 * (current_index + 1.0) / num_fault_addresses)); 102 | } 103 | 104 | thread_ctx.snapshots[fault_model_index]->restore(); 105 | 106 | if (m_ctx.exploitability_model != nullptr) 107 | { 108 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 109 | } 110 | 111 | while (last_index <= current_index) 112 | { 113 | auto [next_address, unused] = addresses_to_fault.at(last_index); 114 | UNUSED(unused); 115 | 116 | auto ret = emu.emulate(next_address, remaining_cycles); 117 | if (ret == ReturnCode::MAX_INSTRUCTIONS_REACHED) 118 | { 119 | return; 120 | } 121 | if (ret != ReturnCode::END_ADDRESS_REACHED) 122 | { 123 | throw std::runtime_error("error which should never occur (" + to_string(ret) + ")"); 124 | return; 125 | } 126 | remaining_cycles -= emu.get_emulated_time(); 127 | last_index++; 128 | } 129 | 130 | if (remaining_cycles == 0) 131 | { 132 | return; 133 | } 134 | thread_ctx.snapshots[fault_model_index]->backup(); 135 | 136 | auto [fault_address, fault_instr_size] = addresses_to_fault.at(current_index); 137 | 138 | bool already_processed = false; 139 | 140 | for (const auto& other : current_chain.instruction_faults) 141 | { 142 | if (other.model->is_permanent() && other.address == fault_address) 143 | { 144 | already_processed = true; 145 | break; 146 | } 147 | } 148 | if (already_processed) 149 | continue; 150 | 151 | emu.read_memory(fault_address, original_instruction, fault_instr_size); 152 | 153 | auto now = emu.get_time(); 154 | 155 | u32 iteration = -1; 156 | while (true) 157 | { 158 | iteration++; 159 | 160 | InstructionFault fault(active_model, now, iteration); 161 | fault.address = fault_address; 162 | fault.instr_size = fault_instr_size; 163 | std::memcpy(fault.original_instruction, original_instruction, fault.instr_size); 164 | 165 | if (iteration >= active_model->get_number_of_iterations(fault)) 166 | { 167 | break; 168 | } 169 | 170 | if (!active_model->is_applicable(fault)) 171 | { 172 | continue; 173 | } 174 | 175 | active_model->apply(fault); 176 | 177 | thread_ctx.num_fault_injections++; 178 | 179 | if (std::memcmp(fault.original_instruction, fault.manipulated_instruction, fault.instr_size) == 0) 180 | { 181 | continue; 182 | } 183 | 184 | FaultCombination new_chain(current_chain); 185 | new_chain.instruction_faults.push_back(fault); 186 | 187 | if (is_fault_redundant(new_chain)) 188 | { 189 | continue; 190 | } 191 | 192 | thread_ctx.snapshots[fault_model_index]->restore(); 193 | 194 | emu.write_memory(fault.address, fault.manipulated_instruction, fault.instr_size); 195 | 196 | thread_ctx.end_reached = false; 197 | if (m_ctx.exploitability_model != nullptr) 198 | { 199 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 200 | } 201 | 202 | auto ret = emu.emulate(1); 203 | if (ret != ReturnCode::STOP_EMULATION_CALLED && ret != ReturnCode::MAX_INSTRUCTIONS_REACHED) 204 | { 205 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 206 | continue; 207 | } 208 | 209 | if (active_model->get_type() == InstructionFaultModel::FaultType::TRANSIENT) 210 | { 211 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 212 | } 213 | 214 | if (!thread_ctx.end_reached && remaining_cycles > 1) 215 | { 216 | emu.emulate(remaining_cycles - 1); 217 | } 218 | 219 | if (thread_ctx.end_reached && thread_ctx.decision == ExploitabilityModel::Decision::EXPLOITABLE) 220 | { 221 | thread_ctx.new_faults.push_back(new_chain); 222 | } 223 | else if (fault_model_index < m_fault_models.size() - 1 && remaining_cycles > 2) 224 | { 225 | thread_ctx.snapshots[fault_model_index]->restore(); 226 | 227 | emu.write_memory(fault.address, fault.manipulated_instruction, fault.instr_size); 228 | 229 | if (emu.emulate(1) != ReturnCode::MAX_INSTRUCTIONS_REACHED) 230 | { 231 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 232 | continue; 233 | } 234 | 235 | if (active_model->get_type() == InstructionFaultModel::FaultType::TRANSIENT) 236 | { 237 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 238 | } 239 | 240 | simulate_fault(thread_ctx, recursion_data, fault_model_index + 1, remaining_cycles - 1, new_chain); 241 | } 242 | 243 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 244 | } 245 | } 246 | } 247 | 248 | void FaultSimulator::simulate_permanent_instruction_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 fault_model_index, u32 remaining_cycles, const FaultCombination& current_chain) 249 | { 250 | auto& emu = thread_ctx.emu; 251 | auto active_model = dynamic_cast(m_fault_models[fault_model_index]); 252 | 253 | thread_ctx.snapshots[fault_model_index]->reset(); 254 | thread_ctx.snapshots[fault_model_index]->backup(); 255 | 256 | u32 num_fault_addresses = m_all_instructions.size(); 257 | 258 | u32 current_index = recursion_data - 1; 259 | 260 | u8 original_instruction[4]; 261 | 262 | auto time = emu.get_time(); 263 | 264 | while (true) 265 | { 266 | if (fault_model_index == 0) 267 | { 268 | current_index = m_thread_progress++; 269 | } 270 | else 271 | { 272 | current_index++; 273 | } 274 | 275 | if (current_index >= num_fault_addresses) 276 | { 277 | break; 278 | } 279 | 280 | if (m_print_progress && fault_model_index == 0) 281 | { 282 | update_progress(std::round(100.0 * (current_index + 1.0) / num_fault_addresses)); 283 | } 284 | 285 | auto [fault_address, fault_instr_size] = m_all_instructions.at(current_index); 286 | 287 | bool already_processed = false; 288 | for (const auto& other : current_chain.instruction_faults) 289 | { 290 | if (other.address == fault_address) 291 | { 292 | already_processed = true; 293 | break; 294 | } 295 | } 296 | if (already_processed) 297 | continue; 298 | 299 | emu.read_memory(fault_address, original_instruction, fault_instr_size); 300 | 301 | u32 iteration = -1; 302 | while (true) 303 | { 304 | iteration++; 305 | 306 | InstructionFault fault(active_model, time, iteration); 307 | fault.address = fault_address; 308 | fault.instr_size = fault_instr_size; 309 | 310 | std::memcpy(fault.original_instruction, original_instruction, fault.instr_size); 311 | 312 | if (iteration >= active_model->get_number_of_iterations(fault)) 313 | { 314 | break; 315 | } 316 | 317 | if (!active_model->is_applicable(fault)) 318 | { 319 | continue; 320 | } 321 | 322 | active_model->apply(fault); 323 | 324 | thread_ctx.num_fault_injections++; 325 | 326 | if (std::memcmp(fault.original_instruction, fault.manipulated_instruction, fault.instr_size) == 0) 327 | { 328 | continue; 329 | } 330 | 331 | { 332 | auto [success, instr] = thread_ctx.decoder.decode_instruction(fault.address, fault.manipulated_instruction, fault.instr_size, emu.in_IT_block(), emu.last_in_IT_block()); 333 | UNUSED(instr); 334 | if (success != ReturnCode::OK) 335 | { 336 | continue; 337 | } 338 | } 339 | 340 | FaultCombination new_chain(current_chain); 341 | new_chain.instruction_faults.push_back(fault); 342 | 343 | if (is_fault_redundant(new_chain)) 344 | { 345 | continue; 346 | } 347 | 348 | thread_ctx.snapshots[fault_model_index]->restore(); 349 | 350 | emu.write_memory(fault.address, fault.manipulated_instruction, fault.instr_size); 351 | 352 | if (fault_model_index == m_fault_models.size() - 1) 353 | { 354 | thread_ctx.end_reached = false; 355 | if (m_ctx.exploitability_model != nullptr) 356 | { 357 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 358 | } 359 | 360 | emu.emulate(remaining_cycles); 361 | 362 | if (thread_ctx.end_reached && thread_ctx.decision == ExploitabilityModel::Decision::EXPLOITABLE) 363 | { 364 | thread_ctx.new_faults.push_back(new_chain); 365 | } 366 | } 367 | else 368 | { 369 | u32 next_recursion_data = 0; 370 | if (m_fault_models[fault_model_index + 1] == active_model) 371 | { 372 | next_recursion_data = current_index + 1; 373 | } 374 | simulate_fault(thread_ctx, next_recursion_data, fault_model_index + 1, remaining_cycles, new_chain); 375 | } 376 | 377 | emu.write_memory(fault.address, fault.original_instruction, fault.instr_size); 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /subprojects/armory/src/simulate_register_fault.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/fault_simulator.h" 2 | #include "m-ulator/emulator.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // ######################################### 12 | 13 | using namespace armory; 14 | 15 | void FaultSimulator::add_new_registers_vector(Emulator& emu, const Instruction& instruction, void* user_data) 16 | { 17 | UNUSED(emu); 18 | UNUSED(instruction); 19 | 20 | auto [inst_ptr, collection] = *((std::tuple>*>*)user_data); 21 | 22 | auto& c = collection->back(); 23 | 24 | for (const auto& range : inst_ptr->m_ctx.ignore_memory_ranges) 25 | { 26 | if (instruction.address >= range.offset && instruction.address < range.offset + range.size) 27 | { 28 | c.clear(); 29 | break; 30 | } 31 | } 32 | for (const auto& range : inst_ptr->m_ctx.ignore_time_ranges) 33 | { 34 | if (emu.get_time() >= range.start && emu.get_time() < range.end) 35 | { 36 | c.clear(); 37 | break; 38 | } 39 | } 40 | 41 | if (!c.empty()) 42 | { 43 | std::sort(c.begin(), c.end()); 44 | c.erase(std::unique(c.begin(), c.end()), c.end()); 45 | } 46 | 47 | collection->push_back({}); 48 | } 49 | 50 | static void collect_read_registers(Emulator& emu, Register reg, u32 value, void* user_data) 51 | { 52 | UNUSED(emu); 53 | UNUSED(value); 54 | auto collection = (std::vector>*)user_data; 55 | collection->back().push_back(reg); 56 | } 57 | 58 | static void revert_transient_fault(Emulator& emu, const Instruction& instruction, void* user_data) 59 | { 60 | UNUSED(instruction); 61 | auto fault = (RegisterFault*)user_data; 62 | if (emu.get_time() == fault->time) 63 | { 64 | emu.write_register(fault->reg, fault->original_value); 65 | } 66 | } 67 | 68 | void FaultSimulator::simulate_register_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 fault_model_index, u32 remaining_cycles, const FaultCombination& current_chain) 69 | { 70 | auto& emu = thread_ctx.emu; 71 | auto active_model = dynamic_cast(m_fault_models[fault_model_index]); 72 | 73 | thread_ctx.snapshots[fault_model_index]->reset(); 74 | thread_ctx.snapshots[fault_model_index]->backup(); 75 | 76 | std::vector> read_registers; 77 | read_registers.push_back({}); 78 | { 79 | if (m_ctx.exploitability_model != nullptr) 80 | { 81 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 82 | } 83 | std::tuple>*> hook_user_data(this, &read_registers); 84 | auto hook1 = emu.instruction_executed_hook.add(&FaultSimulator::add_new_registers_vector, &hook_user_data); 85 | auto hook2 = emu.before_register_read_hook.add(&collect_read_registers, &read_registers); 86 | emu.emulate(remaining_cycles); 87 | emu.instruction_executed_hook.remove(hook1); 88 | emu.before_register_read_hook.remove(hook2); 89 | } 90 | read_registers.pop_back(); 91 | 92 | u32 instruction_count = read_registers.size(); 93 | u32 last_index = 0; 94 | u32 current_index = -1; 95 | 96 | while (true) 97 | { 98 | if (fault_model_index == 0) 99 | { 100 | current_index = m_thread_progress++; 101 | } 102 | else 103 | { 104 | current_index++; 105 | } 106 | 107 | if (current_index >= instruction_count) 108 | { 109 | break; 110 | } 111 | 112 | if (read_registers[current_index].empty()) 113 | { 114 | continue; 115 | } 116 | 117 | if (m_print_progress && fault_model_index == 0) 118 | { 119 | update_progress(std::round(100.0 * (current_index + 1.0) / instruction_count)); 120 | } 121 | 122 | thread_ctx.snapshots[fault_model_index]->restore(); 123 | if (m_ctx.exploitability_model != nullptr) 124 | { 125 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 126 | } 127 | 128 | if (last_index < current_index) 129 | { 130 | auto ret = emu.emulate(current_index - last_index); 131 | remaining_cycles -= emu.get_emulated_time(); 132 | last_index = current_index; 133 | if (ret != ReturnCode::MAX_INSTRUCTIONS_REACHED) 134 | { 135 | throw std::runtime_error("error which should never occur (" + to_string(ret) + ") "); 136 | return; 137 | } 138 | } 139 | 140 | if (remaining_cycles == 0) 141 | { 142 | return; 143 | } 144 | 145 | thread_ctx.snapshots[fault_model_index]->backup(); 146 | 147 | auto now = emu.get_time(); 148 | 149 | for (auto reg : read_registers[current_index]) 150 | { 151 | bool already_processed = false; 152 | for (const auto& other : current_chain.register_faults) 153 | { 154 | if (other.reg == reg) 155 | { 156 | if (other.model->is_permanent() || other.time == now) 157 | { 158 | already_processed = true; 159 | break; 160 | } 161 | } 162 | } 163 | if (already_processed) 164 | { 165 | continue; 166 | } 167 | 168 | thread_ctx.snapshots[fault_model_index]->restore(); 169 | auto original_value = emu.read_register(reg); 170 | 171 | for (const auto& other : current_chain.register_faults) 172 | { 173 | if (other.model->get_type() == RegisterFaultModel::FaultType::UNTIL_OVERWRITE && other.reg == reg && other.manipulated_value == original_value) 174 | { 175 | already_processed = true; 176 | break; 177 | } 178 | } 179 | if (already_processed) 180 | { 181 | continue; 182 | } 183 | 184 | u32 iteration = -1; 185 | while (true) 186 | { 187 | iteration++; 188 | 189 | RegisterFault fault(active_model, now, iteration); 190 | fault.reg = reg; 191 | fault.original_value = original_value; 192 | 193 | if (iteration >= active_model->get_number_of_iterations(fault)) 194 | { 195 | break; 196 | } 197 | 198 | if (!active_model->is_applicable(fault)) 199 | { 200 | continue; 201 | } 202 | 203 | active_model->apply(fault); 204 | 205 | thread_ctx.num_fault_injections++; 206 | 207 | if (fault.original_value == fault.manipulated_value) 208 | { 209 | continue; 210 | } 211 | 212 | FaultCombination new_chain(current_chain); 213 | new_chain.register_faults.push_back(fault); 214 | 215 | if (is_fault_redundant(new_chain)) 216 | { 217 | continue; 218 | } 219 | 220 | thread_ctx.snapshots[fault_model_index]->restore(); 221 | 222 | emu.write_register(fault.reg, fault.manipulated_value); 223 | 224 | if (emu.read_register(fault.reg) == fault.original_value) 225 | { 226 | continue; 227 | } 228 | 229 | u32 recursive_hook = 0; 230 | if (active_model->get_type() == RegisterFaultModel::FaultType::TRANSIENT) 231 | { 232 | recursive_hook = emu.instruction_executed_hook.add(&revert_transient_fault, &fault); 233 | } 234 | 235 | thread_ctx.end_reached = false; 236 | if (m_ctx.exploitability_model != nullptr) 237 | { 238 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 239 | } 240 | 241 | emu.emulate(remaining_cycles); 242 | 243 | if (thread_ctx.end_reached && thread_ctx.decision == ExploitabilityModel::Decision::EXPLOITABLE) 244 | { 245 | thread_ctx.new_faults.push_back(new_chain); 246 | } 247 | else if (fault_model_index < m_fault_models.size() - 1 && remaining_cycles > 0) 248 | { 249 | thread_ctx.snapshots[fault_model_index]->restore(); 250 | 251 | emu.write_register(fault.reg, fault.manipulated_value); 252 | 253 | simulate_fault(thread_ctx, recursion_data, fault_model_index + 1, remaining_cycles, new_chain); 254 | } 255 | 256 | if (recursive_hook != 0) 257 | { 258 | emu.instruction_executed_hook.remove(recursive_hook); 259 | recursive_hook = 0; 260 | } 261 | } 262 | } 263 | } 264 | } 265 | 266 | void FaultSimulator::handle_permanent_register_fault_overwrite(Emulator& emu, Register reg, u32 value, void* hook_context) 267 | { 268 | auto [inst_ptr, reg_fault] = *((std::tuple*)hook_context); 269 | 270 | if (reg_fault->reg == reg) 271 | { 272 | auto addr = emu.read_register(Register::PC) - 4; 273 | 274 | for (const auto& range : inst_ptr->m_ctx.ignore_memory_ranges) 275 | { 276 | if (addr >= range.offset && addr < range.offset + range.size) 277 | { 278 | return; 279 | } 280 | } 281 | 282 | reg_fault->original_value = value; 283 | reg_fault->model->apply(*reg_fault); 284 | emu.write_register(reg_fault->reg, reg_fault->manipulated_value); 285 | } 286 | } 287 | 288 | void FaultSimulator::simulate_permanent_register_fault(ThreadContext& thread_ctx, u32 recursion_data, u32 fault_model_index, u32 remaining_cycles, const FaultCombination& current_chain) 289 | { 290 | auto& emu = thread_ctx.emu; 291 | auto active_model = dynamic_cast(m_fault_models[fault_model_index]); 292 | 293 | thread_ctx.snapshots[fault_model_index]->reset(); 294 | thread_ctx.snapshots[fault_model_index]->backup(); 295 | 296 | std::vector read_registers = { 297 | Register::R0, 298 | Register::R1, 299 | Register::R2, 300 | Register::R3, 301 | Register::R4, 302 | Register::R5, 303 | Register::R6, 304 | Register::R7, 305 | Register::R8, 306 | Register::R9, 307 | Register::R10, 308 | Register::R11, 309 | Register::R12, 310 | Register::SP, 311 | Register::LR, 312 | Register::PC, 313 | Register::PSR, 314 | }; 315 | 316 | u32 register_count = read_registers.size(); 317 | u32 current_index = recursion_data - 1; 318 | 319 | auto time = emu.get_time(); 320 | 321 | while (true) 322 | { 323 | if (fault_model_index == 0) 324 | { 325 | current_index = m_thread_progress++; 326 | } 327 | else 328 | { 329 | current_index++; 330 | } 331 | 332 | if (current_index >= register_count) 333 | { 334 | break; 335 | } 336 | 337 | if (m_print_progress && fault_model_index == 0) 338 | { 339 | update_progress(std::round(100.0 * (current_index + 1.0) / register_count)); 340 | } 341 | 342 | auto current_reg = read_registers[current_index]; 343 | 344 | bool already_processed = false; 345 | for (const auto& other : current_chain.register_faults) 346 | { 347 | if (other.reg == current_reg) 348 | { 349 | already_processed = true; 350 | break; 351 | } 352 | } 353 | if (already_processed) 354 | continue; 355 | 356 | u32 iteration = -1; 357 | 358 | thread_ctx.snapshots[fault_model_index]->restore(); 359 | 360 | auto original_value = emu.read_register(current_reg); 361 | 362 | while (true) 363 | { 364 | iteration++; 365 | 366 | RegisterFault fault(active_model, time, iteration); 367 | fault.reg = current_reg; 368 | fault.original_value = original_value; 369 | 370 | if (iteration >= active_model->get_number_of_iterations(fault)) 371 | { 372 | break; 373 | } 374 | 375 | if (!active_model->is_applicable(fault)) 376 | { 377 | continue; 378 | } 379 | 380 | active_model->apply(fault); 381 | 382 | thread_ctx.num_fault_injections++; 383 | 384 | FaultCombination new_chain(current_chain); 385 | new_chain.register_faults.push_back(fault); 386 | 387 | if (is_fault_redundant(new_chain)) 388 | { 389 | continue; 390 | } 391 | 392 | thread_ctx.snapshots[fault_model_index]->restore(); 393 | 394 | emu.write_register(fault.reg, fault.manipulated_value); 395 | 396 | std::tuple hook_user_data(this, &fault); 397 | auto recursive_hook = emu.after_register_write_hook.add(&FaultSimulator::handle_permanent_register_fault_overwrite, &hook_user_data); 398 | 399 | if (fault_model_index == m_fault_models.size() - 1) 400 | { 401 | thread_ctx.end_reached = false; 402 | if (m_ctx.exploitability_model != nullptr) 403 | { 404 | thread_ctx.exploitability_model = m_ctx.exploitability_model->clone(); 405 | } 406 | 407 | emu.emulate(remaining_cycles); 408 | 409 | if (thread_ctx.end_reached && thread_ctx.decision == ExploitabilityModel::Decision::EXPLOITABLE) 410 | { 411 | thread_ctx.new_faults.push_back(new_chain); 412 | } 413 | } 414 | else 415 | { 416 | u32 next_recursion_data = 0; 417 | if (m_fault_models[fault_model_index + 1] == active_model) 418 | { 419 | next_recursion_data = current_index + 1; 420 | } 421 | simulate_fault(thread_ctx, next_recursion_data, fault_model_index + 1, remaining_cycles, new_chain); 422 | } 423 | 424 | emu.after_register_write_hook.remove(recursive_hook); 425 | } 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /subprojects/armory/src/snapshot.cpp: -------------------------------------------------------------------------------- 1 | #include "armory/snapshot.h" 2 | 3 | namespace armory 4 | { 5 | 6 | Snapshot::Snapshot(Emulator& emu) : m_emulator(emu) 7 | { 8 | m_ram_offset = emu.get_ram_offset(); 9 | m_ram_size = emu.get_ram_size(); 10 | m_ram_data = new u8[m_ram_size]; 11 | m_initialized = false; 12 | m_hook = m_emulator.after_memory_write_hook.add(Snapshot::on_memory_write, this); 13 | reset(); 14 | } 15 | 16 | Snapshot::~Snapshot() 17 | { 18 | m_emulator.after_memory_write_hook.remove(m_hook); 19 | delete[] m_ram_data; 20 | } 21 | 22 | void Snapshot::reset() 23 | { 24 | m_low_change_start = 0; 25 | m_low_change_end = 0; 26 | 27 | m_high_change_start = 0; 28 | m_high_change_end = 0; 29 | 30 | m_initialized = false; 31 | } 32 | 33 | void Snapshot::backup() 34 | { 35 | m_state = m_emulator.get_cpu_state(); 36 | if (!m_initialized) 37 | { 38 | m_emulator.read_memory(m_ram_offset, m_ram_data, m_ram_size); 39 | m_initialized = true; 40 | } 41 | else 42 | { 43 | u32 size = m_low_change_end - m_low_change_start; 44 | if (size != 0) 45 | { 46 | u32 offset = m_low_change_start - m_ram_offset; 47 | m_emulator.read_memory(m_low_change_start, m_ram_data + offset, size); 48 | } 49 | size = m_high_change_end - m_high_change_start; 50 | if (size != 0) 51 | { 52 | u32 offset = m_high_change_start - m_ram_offset; 53 | m_emulator.read_memory(m_high_change_start, m_ram_data + offset, size); 54 | } 55 | } 56 | m_low_change_start = 0; 57 | m_low_change_end = 0; 58 | m_high_change_start = 0; 59 | m_high_change_end = 0; 60 | } 61 | 62 | void Snapshot::restore() 63 | { 64 | if (!m_initialized) 65 | return; 66 | 67 | m_emulator.set_cpu_state(m_state); 68 | 69 | u32 size = m_low_change_end - m_low_change_start; 70 | if (size != 0) 71 | { 72 | u32 offset = m_low_change_start - m_ram_offset; 73 | m_emulator.write_memory(m_low_change_start, m_ram_data + offset, size); 74 | } 75 | size = m_high_change_end - m_high_change_start; 76 | if (size != 0) 77 | { 78 | u32 offset = m_high_change_start - m_ram_offset; 79 | m_emulator.write_memory(m_high_change_start, m_ram_data + offset, size); 80 | } 81 | m_low_change_start = 0; 82 | m_low_change_end = 0; 83 | m_high_change_start = 0; 84 | m_high_change_end = 0; 85 | } 86 | 87 | void Snapshot::on_memory_write(Emulator& emu, u32 address, u32 size, u32, void* user_data) 88 | { 89 | auto snapshot = (Snapshot*)user_data; 90 | auto sp_boundary = emu.read_register(Register::SP) - 20 * 4; 91 | 92 | if (address < sp_boundary) 93 | { 94 | if (snapshot->m_low_change_start == snapshot->m_low_change_end) 95 | { 96 | snapshot->m_low_change_start = address; 97 | snapshot->m_low_change_end = address + size; 98 | } 99 | else 100 | { 101 | if (address < snapshot->m_low_change_start) 102 | { 103 | snapshot->m_low_change_start = address; 104 | } 105 | if (address + size > snapshot->m_low_change_end) 106 | { 107 | snapshot->m_low_change_end = address + size; 108 | } 109 | } 110 | } 111 | else 112 | { 113 | if (snapshot->m_high_change_start == snapshot->m_high_change_end) 114 | { 115 | snapshot->m_high_change_start = address; 116 | snapshot->m_high_change_end = address + size; 117 | } 118 | else 119 | { 120 | if (address < snapshot->m_high_change_start) 121 | { 122 | snapshot->m_high_change_start = address; 123 | } 124 | if (address + size > snapshot->m_high_change_end) 125 | { 126 | snapshot->m_high_change_end = address + size; 127 | } 128 | } 129 | } 130 | } 131 | } // namespace armory 132 | -------------------------------------------------------------------------------- /subprojects/armory_cli/include/armory_cli/armory_cli.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "armory_cli/configuration.h" 4 | 5 | #include "armory/instruction_fault_model.h" 6 | #include "armory/register_fault_model.h" 7 | #include "armory/fault_combination.h" 8 | 9 | namespace armory_cli 10 | { 11 | armory::InstructionFault build_fault(const armory::InstructionFaultModel* model, u32 fault_model_iteration, u32 time, u32 address, u32 instr_size); 12 | armory::RegisterFault build_fault(const armory::RegisterFaultModel* model, u32 fault_model_iteration, u32 time, armory::Register reg); 13 | 14 | std::vector find_exploitable_faults(const mulator::Emulator& emu, const Configuration& config, const std::vector>& fault_injectors, u32 max_simulatenous_faults = 0, bool verify_faults = false); 15 | 16 | Configuration parse_arguments(int argc, char** argv); 17 | } // namespace armory_cli 18 | -------------------------------------------------------------------------------- /subprojects/armory_cli/include/armory_cli/configuration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "armory/context.h" 4 | 5 | namespace armory_cli 6 | { 7 | struct CodeSection 8 | { 9 | std::string name; 10 | std::vector bytes; 11 | u32 offset; 12 | }; 13 | 14 | struct Configuration 15 | { 16 | mulator::Architecture arch; 17 | armory::MemoryRange flash; 18 | armory::MemoryRange ram; 19 | u32 start_address; 20 | std::vector binary; 21 | std::vector halt_addresses; 22 | std::vector symbol_addresses; 23 | std::unordered_map symbol_names; 24 | 25 | armory::Context faulting_context; 26 | u32 num_threads; 27 | }; 28 | } // namespace armory_cli 29 | -------------------------------------------------------------------------------- /subprojects/armory_cli/include/armory_cli/fault_models.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "armory/instruction_fault_model.h" 4 | #include "armory/register_fault_model.h" 5 | 6 | namespace fault_models 7 | { 8 | using namespace armory; 9 | 10 | extern const InstructionFaultModel* instr_p_skip; 11 | extern const InstructionFaultModel* instr_p_setbyte; 12 | extern const InstructionFaultModel* instr_p_clearbyte; 13 | extern const InstructionFaultModel* instr_p_bitflip; 14 | 15 | extern const InstructionFaultModel* instr_t_skip; 16 | extern const InstructionFaultModel* instr_t_setbyte; 17 | extern const InstructionFaultModel* instr_t_clearbyte; 18 | extern const InstructionFaultModel* instr_t_bitflip; 19 | 20 | extern const RegisterFaultModel* reg_p_clear; 21 | extern const RegisterFaultModel* reg_p_fill; 22 | extern const RegisterFaultModel* reg_p_setbyte; 23 | extern const RegisterFaultModel* reg_p_clearbyte; 24 | extern const RegisterFaultModel* reg_p_bitset; 25 | extern const RegisterFaultModel* reg_p_bitclear; 26 | 27 | extern const RegisterFaultModel* reg_o_clear; 28 | extern const RegisterFaultModel* reg_o_fill; 29 | extern const RegisterFaultModel* reg_o_setbyte; 30 | extern const RegisterFaultModel* reg_o_clearbyte; 31 | extern const RegisterFaultModel* reg_o_bitflip; 32 | 33 | extern const RegisterFaultModel* reg_t_clear; 34 | extern const RegisterFaultModel* reg_t_fill; 35 | extern const RegisterFaultModel* reg_t_setbyte; 36 | extern const RegisterFaultModel* reg_t_clearbyte; 37 | extern const RegisterFaultModel* reg_t_bitflip; 38 | 39 | extern const std::vector all_fault_models; 40 | extern const std::vector non_permanent_fault_models; 41 | extern const std::vector instruction_fault_models; 42 | extern const std::vector register_fault_models; 43 | extern const std::vector non_single_bit_fault_models; 44 | } // namespace fault_models 45 | -------------------------------------------------------------------------------- /subprojects/armory_cli/include/armory_cli/fault_printing.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "armory/instruction_fault_model.h" 4 | #include "armory/register_fault_model.h" 5 | #include "armory_cli/configuration.h" 6 | #include "m-ulator/disassembler.h" 7 | 8 | namespace armory_cli 9 | { 10 | void print_instruction_fault(const armory::InstructionFault& f, const Configuration& ctx, mulator::Disassembler& disasm); 11 | 12 | void print_register_fault(const armory::RegisterFault& f); 13 | } // namespace armory_cli 14 | -------------------------------------------------------------------------------- /subprojects/armory_cli/meson.build: -------------------------------------------------------------------------------- 1 | project('ARMORY command line interface', 'cpp', 2 | version : '0.1', 3 | default_options : ['warning_level=3', 'cpp_std=c++17']) 4 | 5 | libarmory_dep = subproject('armory').get_variable('libarmory_dep') 6 | 7 | incdir = include_directories('include') 8 | 9 | src = [] 10 | subdir('src') 11 | 12 | libarmory_cli = static_library('armory_cli', sources: src, include_directories : incdir, dependencies: libarmory_dep) 13 | libarmory_cli_dep = declare_dependency(include_directories : incdir, link_with: libarmory_cli, dependencies: libarmory_dep) 14 | -------------------------------------------------------------------------------- /subprojects/armory_cli/src/armory_cli.cpp: -------------------------------------------------------------------------------- 1 | #include "armory_cli/armory_cli.h" 2 | 3 | #include "armory/fault_simulator.h" 4 | #include "armory/fault_tracer.h" 5 | #include "armory_cli/configuration.h" 6 | #include "armory_cli/fault_models.h" 7 | #include "armory_cli/fault_printing.h" 8 | #include "armory_cli/termcolor.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace armory_cli 18 | { 19 | using namespace armory; 20 | 21 | InstructionFault build_fault(const InstructionFaultModel* model, u32 fault_model_iteration, u32 time, u32 address, u32 instr_size) 22 | { 23 | InstructionFault f(model, time, fault_model_iteration); 24 | f.address = address; 25 | f.instr_size = instr_size; 26 | return f; 27 | } 28 | 29 | RegisterFault build_fault(const RegisterFaultModel* model, u32 fault_model_iteration, u32 time, Register reg) 30 | { 31 | RegisterFault f(model, time, fault_model_iteration); 32 | f.reg = reg; 33 | return f; 34 | } 35 | 36 | std::vector find_exploitable_faults(const Emulator& emu, 37 | const Configuration& config, 38 | const std::vector>& fault_injectors, 39 | u32 max_simulatenous_faults, 40 | bool verify_faults) 41 | { 42 | Disassembler disasm(config.arch); 43 | FaultSimulator simulator(config.faulting_context); 44 | FaultTracer tracer(config.faulting_context); 45 | 46 | simulator.enable_progress_printing(true); 47 | simulator.set_number_of_threads(config.num_threads); 48 | 49 | std::cout << "=====================================" << std::endl; 50 | std::cout << "Testing any combination"; 51 | if (max_simulatenous_faults != 0) 52 | { 53 | std::cout << " (max " << std::dec << max_simulatenous_faults << ")"; 54 | } 55 | std::cout << " of:" << std::endl; 56 | 57 | for (const auto& [model, amount] : fault_injectors) 58 | { 59 | std::cout << std::dec << " " << amount << "x '" << model->get_name() << "'" << std::endl; 60 | } 61 | 62 | auto start = std::chrono::steady_clock::now(); 63 | auto exploitable_faults = simulator.simulate_faults(emu, fault_injectors, max_simulatenous_faults); 64 | auto end = std::chrono::steady_clock::now(); 65 | 66 | std::vector failed_faults; 67 | 68 | if (verify_faults) 69 | { 70 | std::cerr << "Verifying results..." << std::flush; 71 | } 72 | 73 | for (auto& faults : exploitable_faults) 74 | { 75 | u32 total_size = faults.instruction_faults.size() + faults.register_faults.size(); 76 | std::cout << "Exploitable with " << total_size << " fault" << (total_size > 1 ? "s" : "") << std::endl; 77 | for (auto f : faults.get_sorted_faults()) 78 | { 79 | auto i_f = dynamic_cast(f); 80 | auto r_f = dynamic_cast(f); 81 | std::cout << " "; 82 | if (i_f) 83 | { 84 | print_instruction_fault(*i_f, config, disasm); 85 | } 86 | else 87 | { 88 | print_register_fault(*r_f); 89 | } 90 | } 91 | 92 | if (verify_faults && !tracer.verify(emu, faults)) 93 | { 94 | failed_faults.push_back(faults); 95 | } 96 | } 97 | 98 | if (verify_faults) 99 | { 100 | std::cerr << "\r \r" << std::flush; 101 | } 102 | 103 | if (!failed_faults.empty()) 104 | { 105 | std::cout << "=====================================" << std::endl; 106 | for (const auto& faults : failed_faults) 107 | { 108 | std::cout << termcolor::bright_red << "ERROR: not verifiable" << termcolor::reset << std::endl; 109 | for (const auto& f : faults.instruction_faults) 110 | { 111 | std::cout << " "; 112 | print_instruction_fault(f, config, disasm); 113 | } 114 | for (const auto& f : faults.register_faults) 115 | { 116 | std::cout << " "; 117 | print_register_fault(f); 118 | } 119 | } 120 | } 121 | 122 | std::cout << "-------------------------------------" << std::endl; 123 | std::cout << "Total: " << std::dec << exploitable_faults.size() << " exploitable faults"; 124 | if (!failed_faults.empty()) 125 | { 126 | std::cout << " (" << failed_faults.size() << " not verifiable)"; 127 | } 128 | std::cout << std::endl; 129 | std::cout << "Injected " << std::dec << simulator.get_number_of_injected_faults() << " faults" << std::endl; 130 | 131 | double seconds = std::chrono::duration_cast(end - start).count() / 1000.0; 132 | std::cout << "Elapsed time: " << seconds << " seconds" << std::endl << std::endl; 133 | 134 | return exploitable_faults; 135 | } 136 | 137 | static std::vector read_file(const std::string& path) 138 | { 139 | std::ifstream ifs(path, std::ios::binary | std::ios::ate); 140 | std::ifstream::pos_type pos = ifs.tellg(); 141 | std::vector result(pos); 142 | ifs.seekg(0, std::ios::beg); 143 | ifs.read((char*)(&result[0]), pos); 144 | return result; 145 | } 146 | 147 | static u32 to_int(const std::string& s) 148 | { 149 | if (s.size() > 2 && s[0] == '0' && s[1] == 'x') 150 | { 151 | std::istringstream converter(s); 152 | u32 value; 153 | converter >> std::hex >> value; 154 | return value; 155 | } 156 | return std::stoi(s); 157 | } 158 | 159 | Configuration parse_arguments(int argc, char** argv) 160 | { 161 | if (argc < 2) 162 | { 163 | std::cout << "emulator [options]" << std::endl; 164 | std::cout << " --start [required]" << std::endl; 165 | std::cout << " --halt [required]" << std::endl; 166 | std::cout << " --flash [required]" << std::endl; 167 | std::cout << " --ram [required]" << std::endl; 168 | std::cout << " --section
[required]" << std::endl; 169 | std::cout << " --timeout [optional]" << std::endl; 170 | std::cout << " --ignore [optional]" << std::endl; 171 | std::cout << " --armv{6,7}m [optional]" << std::endl; 172 | throw std::runtime_error("no options given"); 173 | } 174 | 175 | std::vector args; 176 | for (int i = 1; i < argc; i++) 177 | { 178 | args.emplace_back(argv[i]); 179 | } 180 | 181 | Configuration config; 182 | config.start_address = -1; 183 | config.flash = {0, 0}; 184 | config.ram = {0, 0}; 185 | config.arch = Architecture::ARMv7M; 186 | config.faulting_context.emulation_timeout = 0; 187 | config.num_threads = 0; 188 | 189 | for (u32 i = 0; i < args.size(); ++i) 190 | { 191 | if (args[i] == "--start") 192 | { 193 | config.start_address = to_int(args[++i]); 194 | } 195 | else if (args[i] == "--halt") 196 | { 197 | auto symbol = args[++i]; 198 | auto addr = to_int(args[++i]); 199 | config.symbol_names[addr] = symbol; 200 | config.halt_addresses.push_back(addr); 201 | config.faulting_context.halting_points.push_back(addr); 202 | } 203 | else if (args[i] == "--symbol") 204 | { 205 | auto symbol = args[++i]; 206 | auto addr = to_int(args[++i]); 207 | config.symbol_names[addr] = symbol; 208 | config.symbol_addresses.push_back(addr); 209 | } 210 | else if (args[i] == "--section") 211 | { 212 | config.binary.push_back({args[++i], read_file(args[i]), (u32)to_int(args[++i])}); 213 | } 214 | else if (args[i] == "--flash") 215 | { 216 | config.flash.offset = to_int(args[++i]); 217 | config.flash.size = to_int(args[++i]); 218 | } 219 | else if (args[i] == "--ram") 220 | { 221 | config.ram.offset = to_int(args[++i]); 222 | config.ram.size = to_int(args[++i]); 223 | } 224 | else if (args[i] == "--ignore") 225 | { 226 | auto begin = to_int(args[++i]); 227 | auto end = to_int(args[++i]); 228 | config.faulting_context.ignore_memory_ranges.emplace_back(MemoryRange({begin, end - begin})); 229 | } 230 | else if (args[i] == "--timeout") 231 | { 232 | config.faulting_context.emulation_timeout = to_int(args[++i]); 233 | } 234 | else if (args[i] == "--armv7m") 235 | { 236 | config.arch = Architecture::ARMv7M; 237 | } 238 | else if (args[i] == "--armv6m") 239 | { 240 | config.arch = Architecture::ARMv6M; 241 | } 242 | else 243 | { 244 | throw std::runtime_error("unkown option '" + args[i] + "'"); 245 | } 246 | } 247 | 248 | if (config.start_address == (u32)-1) 249 | { 250 | throw std::runtime_error("no entry point given"); 251 | } 252 | if (config.faulting_context.halting_points.size() == 0) 253 | { 254 | throw std::runtime_error("no end address given"); 255 | } 256 | if (config.ram.size == 0) 257 | { 258 | throw std::runtime_error("no RAM given"); 259 | } 260 | if (config.flash.size == 0) 261 | { 262 | throw std::runtime_error("no FLASH given"); 263 | } 264 | 265 | return config; 266 | } 267 | } // namespace armory_cli 268 | -------------------------------------------------------------------------------- /subprojects/armory_cli/src/fault_models.cpp: -------------------------------------------------------------------------------- 1 | #include "armory_cli/fault_models.h" 2 | 3 | #include 4 | 5 | namespace fault_models 6 | { 7 | using namespace armory; 8 | namespace instruction_faults 9 | { 10 | 11 | u32 return_1(const armory::InstructionFault&) 12 | { 13 | return 1; 14 | } 15 | 16 | u32 count_bytes(const armory::InstructionFault& f) 17 | { 18 | return f.instr_size; 19 | } 20 | 21 | u32 count_bits(const armory::InstructionFault& f) 22 | { 23 | return f.instr_size * 8; 24 | } 25 | 26 | bool return_true(const armory::InstructionFault&) 27 | { 28 | return true; 29 | } 30 | 31 | /////////////////////////////////////////// 32 | 33 | void instruction_skip(armory::InstructionFault& f) 34 | { 35 | if (f.instr_size == 4) 36 | { 37 | // 0xF3AF8000 is a 32-bit thumb nop 38 | f.manipulated_instruction[0] = 0xaf; 39 | f.manipulated_instruction[1] = 0xf3; 40 | f.manipulated_instruction[2] = 0x00; 41 | f.manipulated_instruction[3] = 0x80; 42 | } 43 | else if (f.instr_size == 2) 44 | { 45 | // 0xbf00 is a thumb nop 46 | f.manipulated_instruction[0] = 0x00; 47 | f.manipulated_instruction[1] = 0xbf; 48 | } 49 | } 50 | 51 | void byte_set(armory::InstructionFault& f) 52 | { 53 | std::memcpy(f.manipulated_instruction, f.original_instruction, f.instr_size); 54 | f.manipulated_instruction[f.fault_model_iteration] = 0xFF; 55 | } 56 | 57 | void byte_clear(armory::InstructionFault& f) 58 | { 59 | std::memcpy(f.manipulated_instruction, f.original_instruction, f.instr_size); 60 | f.manipulated_instruction[f.fault_model_iteration] = 0x00; 61 | } 62 | 63 | void bit_flip(armory::InstructionFault& f) 64 | { 65 | std::memcpy(f.manipulated_instruction, f.original_instruction, f.instr_size); 66 | f.manipulated_instruction[f.fault_model_iteration / 8] ^= 1 << (f.fault_model_iteration % 8); 67 | } 68 | } // namespace instruction_faults 69 | 70 | namespace register_faults 71 | { 72 | 73 | u32 return_1(const armory::RegisterFault&) 74 | { 75 | return 1; 76 | } 77 | 78 | u32 return_4(const armory::RegisterFault&) 79 | { 80 | return 4; 81 | } 82 | 83 | u32 return_32(const armory::RegisterFault&) 84 | { 85 | return 32; 86 | } 87 | 88 | bool register_tester(const armory::RegisterFault& f) 89 | { 90 | return f.reg != mulator::Register::SP && f.reg != mulator::Register::PC; 91 | } 92 | 93 | /////////////////////////////////////////// 94 | 95 | void clear_register(armory::RegisterFault& f) 96 | { 97 | f.manipulated_value = 0; 98 | } 99 | 100 | void fill_register(armory::RegisterFault& f) 101 | { 102 | f.manipulated_value = 0xFFFFFFFF; 103 | } 104 | 105 | void byte_set(armory::RegisterFault& f) 106 | { 107 | f.manipulated_value = f.original_value | ((u32)0xFF << (8 * f.fault_model_iteration)); 108 | } 109 | 110 | void byte_clear(armory::RegisterFault& f) 111 | { 112 | f.manipulated_value = f.original_value & ~((u32)0xFF << (8 * f.fault_model_iteration)); 113 | } 114 | 115 | void bit_set(armory::RegisterFault& f) 116 | { 117 | f.manipulated_value = f.original_value | ((u32)1 << f.fault_model_iteration); 118 | } 119 | void bit_clear(armory::RegisterFault& f) 120 | { 121 | f.manipulated_value = f.original_value & ~((u32)1 << f.fault_model_iteration); 122 | } 123 | void bit_flip(armory::RegisterFault& f) 124 | { 125 | f.manipulated_value = f.original_value ^ ((u32)1 << f.fault_model_iteration); 126 | } 127 | } // namespace register_faults 128 | 129 | const std::unique_ptr instr_p_skip_owner = std::make_unique("Permanent Instruction Skip", InstructionFaultModel::FaultType::PERMANENT, instruction_faults::return_1, instruction_faults::return_true, instruction_faults::instruction_skip); 130 | const InstructionFaultModel* instr_p_skip = instr_p_skip_owner.get(); 131 | const std::unique_ptr instr_p_setbyte_owner = std::make_unique("Permanent Instruction Byte-Set", InstructionFaultModel::FaultType::PERMANENT, instruction_faults::count_bytes, instruction_faults::return_true, instruction_faults::byte_set); 132 | const InstructionFaultModel* instr_p_setbyte = instr_p_setbyte_owner.get(); 133 | const std::unique_ptr instr_p_clearbyte_owner = std::make_unique("Permanent Instruction Byte-Clear", InstructionFaultModel::FaultType::PERMANENT, instruction_faults::count_bytes, instruction_faults::return_true, instruction_faults::byte_clear); 134 | const InstructionFaultModel* instr_p_clearbyte = instr_p_clearbyte_owner.get(); 135 | const std::unique_ptr instr_p_bitflip_owner = std::make_unique("Permanent Instruction Bit-Flip", InstructionFaultModel::FaultType::PERMANENT, instruction_faults::count_bits, instruction_faults::return_true, instruction_faults::bit_flip); 136 | const InstructionFaultModel* instr_p_bitflip = instr_p_bitflip_owner.get(); 137 | 138 | const std::unique_ptr instr_t_skip_owner = std::make_unique("Transient Instruction Skip", InstructionFaultModel::FaultType::TRANSIENT, instruction_faults::return_1, instruction_faults::return_true, instruction_faults::instruction_skip); 139 | const InstructionFaultModel* instr_t_skip = instr_t_skip_owner.get(); 140 | const std::unique_ptr instr_t_setbyte_owner = std::make_unique("Transient Instruction Byte-Set", InstructionFaultModel::FaultType::TRANSIENT, instruction_faults::count_bytes, instruction_faults::return_true, instruction_faults::byte_set); 141 | const InstructionFaultModel* instr_t_setbyte = instr_t_setbyte_owner.get(); 142 | const std::unique_ptr instr_t_clearbyte_owner = std::make_unique("Transient Instruction Byte-Clear", InstructionFaultModel::FaultType::TRANSIENT, instruction_faults::count_bytes, instruction_faults::return_true, instruction_faults::byte_clear); 143 | const InstructionFaultModel* instr_t_clearbyte = instr_t_clearbyte_owner.get(); 144 | const std::unique_ptr instr_t_bitflip_owner = std::make_unique("Transient Instruction Bit-Flip", InstructionFaultModel::FaultType::TRANSIENT, instruction_faults::count_bits, instruction_faults::return_true, instruction_faults::bit_flip); 145 | const InstructionFaultModel* instr_t_bitflip = instr_t_bitflip_owner.get(); 146 | 147 | const std::unique_ptr reg_p_clear_owner = std::make_unique("Permanent Register Clear", RegisterFaultModel::FaultType::PERMANENT, register_faults::return_1, register_faults::register_tester, register_faults::clear_register); 148 | const RegisterFaultModel* reg_p_clear = reg_p_clear_owner.get(); 149 | const std::unique_ptr reg_p_fill_owner = std::make_unique("Permanent Register Fill", RegisterFaultModel::FaultType::PERMANENT, register_faults::return_1, register_faults::register_tester, register_faults::fill_register); 150 | const RegisterFaultModel* reg_p_fill = reg_p_fill_owner.get(); 151 | const std::unique_ptr reg_p_setbyte_owner = std::make_unique("Permanent Register Byte-Set", RegisterFaultModel::FaultType::PERMANENT, register_faults::return_4, register_faults::register_tester, register_faults::byte_set); 152 | const RegisterFaultModel* reg_p_setbyte = reg_p_setbyte_owner.get(); 153 | const std::unique_ptr reg_p_clearbyte_owner = std::make_unique("Permanent Register Byte-Clear", RegisterFaultModel::FaultType::PERMANENT, register_faults::return_4, register_faults::register_tester, register_faults::byte_clear); 154 | const RegisterFaultModel* reg_p_clearbyte = reg_p_clearbyte_owner.get(); 155 | const std::unique_ptr reg_p_bitset_owner = std::make_unique("Permanent Register Bit-Set", RegisterFaultModel::FaultType::PERMANENT, register_faults::return_32, register_faults::register_tester, register_faults::bit_set); 156 | const RegisterFaultModel* reg_p_bitset = reg_p_bitset_owner.get(); 157 | const std::unique_ptr reg_p_bitclear_owner = std::make_unique("Permanent Register Bit-Clear", RegisterFaultModel::FaultType::PERMANENT, register_faults::return_32, register_faults::register_tester, register_faults::bit_clear); 158 | const RegisterFaultModel* reg_p_bitclear = reg_p_bitclear_owner.get(); 159 | 160 | const std::unique_ptr reg_o_clear_owner = std::make_unique("Until-Overwrite Register Clear", RegisterFaultModel::FaultType::UNTIL_OVERWRITE, register_faults::return_1, register_faults::register_tester, register_faults::clear_register); 161 | const RegisterFaultModel* reg_o_clear = reg_o_clear_owner.get(); 162 | const std::unique_ptr reg_o_fill_owner = std::make_unique("Until-Overwrite Register Fill", RegisterFaultModel::FaultType::UNTIL_OVERWRITE, register_faults::return_1, register_faults::register_tester, register_faults::fill_register); 163 | const RegisterFaultModel* reg_o_fill = reg_o_fill_owner.get(); 164 | const std::unique_ptr reg_o_setbyte_owner = std::make_unique("Until-Overwrite Register Byte-Set", RegisterFaultModel::FaultType::UNTIL_OVERWRITE, register_faults::return_4, register_faults::register_tester, register_faults::byte_set); 165 | const RegisterFaultModel* reg_o_setbyte = reg_o_setbyte_owner.get(); 166 | const std::unique_ptr reg_o_clearbyte_owner = std::make_unique("Until-Overwrite Register Byte-Clear", RegisterFaultModel::FaultType::UNTIL_OVERWRITE, register_faults::return_4, register_faults::register_tester, register_faults::byte_clear); 167 | const RegisterFaultModel* reg_o_clearbyte = reg_o_clearbyte_owner.get(); 168 | const std::unique_ptr reg_o_bitflip_owner = std::make_unique("Until-Overwrite Register Bit-Flip", RegisterFaultModel::FaultType::UNTIL_OVERWRITE, register_faults::return_32, register_faults::register_tester, register_faults::bit_flip); 169 | const RegisterFaultModel* reg_o_bitflip = reg_o_bitflip_owner.get(); 170 | 171 | const std::unique_ptr reg_t_clear_owner = std::make_unique("Transient Register Clear", RegisterFaultModel::FaultType::TRANSIENT, register_faults::return_1, register_faults::register_tester, register_faults::clear_register); 172 | const RegisterFaultModel* reg_t_clear = reg_t_clear_owner.get(); 173 | const std::unique_ptr reg_t_fill_owner = std::make_unique("Transient Register Fill", RegisterFaultModel::FaultType::TRANSIENT, register_faults::return_1, register_faults::register_tester, register_faults::fill_register); 174 | const RegisterFaultModel* reg_t_fill = reg_t_fill_owner.get(); 175 | const std::unique_ptr reg_t_setbyte_owner = std::make_unique("Transient Register Byte-Set", RegisterFaultModel::FaultType::TRANSIENT, register_faults::return_4, register_faults::register_tester, register_faults::byte_set); 176 | const RegisterFaultModel* reg_t_setbyte = reg_t_setbyte_owner.get(); 177 | const std::unique_ptr reg_t_clearbyte_owner = std::make_unique("Transient Register Byte-Clear", RegisterFaultModel::FaultType::TRANSIENT, register_faults::return_4, register_faults::register_tester, register_faults::byte_clear); 178 | const RegisterFaultModel* reg_t_clearbyte = reg_t_clearbyte_owner.get(); 179 | const std::unique_ptr reg_t_bitflip_owner = std::make_unique("Transient Register Bit-Flip", RegisterFaultModel::FaultType::TRANSIENT, register_faults::return_32, register_faults::register_tester, register_faults::bit_flip); 180 | const RegisterFaultModel* reg_t_bitflip = reg_t_bitflip_owner.get(); 181 | 182 | const std::vector all_fault_models = {instr_p_skip, instr_p_setbyte, instr_p_clearbyte, instr_p_bitflip, instr_t_skip, instr_t_setbyte, instr_t_clearbyte, instr_t_bitflip, reg_p_clear, reg_p_fill, reg_p_setbyte, reg_p_clearbyte, reg_p_bitset, reg_p_bitclear, reg_o_clear, reg_o_fill, reg_o_setbyte, reg_o_clearbyte, reg_o_bitflip, reg_t_clear, reg_t_fill, reg_t_setbyte, reg_t_clearbyte, reg_t_bitflip}; 183 | const std::vector non_permanent_fault_models = {instr_t_skip, instr_t_setbyte, instr_t_clearbyte, instr_t_bitflip, reg_o_clear, reg_o_fill, reg_o_setbyte, reg_o_clearbyte, reg_o_bitflip, reg_t_clear, reg_t_fill, reg_t_setbyte, reg_t_clearbyte, reg_t_bitflip}; 184 | const std::vector instruction_fault_models = {instr_p_skip, instr_p_setbyte, instr_p_clearbyte, instr_p_bitflip, instr_t_skip, instr_t_setbyte, instr_t_clearbyte, instr_t_bitflip}; 185 | const std::vector register_fault_models = {reg_p_clear, reg_p_fill, reg_p_setbyte, reg_p_clearbyte, reg_p_bitset, reg_p_bitclear, reg_o_clear, reg_o_fill, reg_o_setbyte, reg_o_clearbyte, reg_o_bitflip, reg_t_clear, reg_t_fill, reg_t_setbyte, reg_t_clearbyte, reg_t_bitflip}; 186 | const std::vector non_single_bit_fault_models = {instr_p_skip, instr_p_setbyte, instr_p_clearbyte, instr_t_skip, instr_t_setbyte, instr_t_clearbyte, reg_p_clear, reg_p_fill, reg_p_setbyte, reg_p_clearbyte, reg_o_clear, reg_o_fill, reg_o_setbyte, reg_o_clearbyte, reg_t_clear, reg_t_fill, reg_t_setbyte, reg_t_clearbyte}; 187 | 188 | } // namespace fault_models 189 | -------------------------------------------------------------------------------- /subprojects/armory_cli/src/fault_printing.cpp: -------------------------------------------------------------------------------- 1 | #include "armory_cli/fault_printing.h" 2 | 3 | #include "armory_cli/termcolor.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace armory_cli 11 | { 12 | static std::string to_hex(u32 x, u32 padding) 13 | { 14 | std::stringstream stream; 15 | stream << std::hex << std::setw(padding) << std::setfill('0') << x; 16 | return stream.str(); 17 | } 18 | 19 | static void print_differences(const std::string& a, const std::string& b) 20 | { 21 | for (u32 i = 0; i < b.size(); ++i) 22 | { 23 | if (i < a.size() && a[i] != b[i]) 24 | { 25 | std::cout << termcolor::bright_red << b[i] << termcolor::reset; 26 | } 27 | else 28 | { 29 | std::cout << b[i]; 30 | } 31 | } 32 | } 33 | 34 | void print_instruction_fault(const armory::InstructionFault& f, const Configuration& ctx, mulator::Disassembler& disasm) 35 | { 36 | const CodeSection* text_segment = nullptr; 37 | for (const auto& segment : ctx.binary) 38 | { 39 | if (segment.offset <= f.address && segment.offset + segment.bytes.size() >= f.address) 40 | { 41 | text_segment = &segment; 42 | } 43 | } 44 | 45 | if (text_segment == nullptr) 46 | { 47 | std::cout << "ERROR: " << std::hex << f.address << " is not within a defined segment of the binary" << std::endl; 48 | return; 49 | } 50 | 51 | u8 bytes[4] = {0xFF, 0xFF, 0xFF, 0xFF}; 52 | std::memcpy(bytes, text_segment->bytes.data() + f.address - text_segment->offset, std::min((u32)4, (u32)text_segment->bytes.size() - (f.address - text_segment->offset))); 53 | std::string original_code; 54 | std::string original_string; 55 | 56 | { 57 | disasm.reset(); 58 | auto success = disasm.disassemble(f.address, bytes, f.instr_size); 59 | auto instr = disasm.get_instruction(); 60 | if (success == armory::ReturnCode::OK) 61 | { 62 | original_code = disasm.get_string(); 63 | original_string = to_hex(instr.encoding, instr.size * 2); 64 | } 65 | else 66 | { 67 | original_code = ""; 68 | original_string = to_hex(instr.encoding >> 16, 4); 69 | } 70 | } 71 | 72 | std::memcpy(bytes, f.manipulated_instruction, f.instr_size); 73 | disasm.reset(); 74 | disasm.disassemble(f.address, bytes, 4); 75 | auto instr = disasm.get_instruction(); 76 | auto manipulated_code = disasm.get_string(); 77 | 78 | std::string manip_str = to_hex(instr.encoding, instr.size * 2); 79 | 80 | std::cout << std::hex << f.address << ": "; 81 | std::cout << original_string; 82 | std::cout << " --> "; 83 | for (u32 i = 0; i < manip_str.size(); ++i) 84 | { 85 | if (i < original_string.size() && original_string[i] != manip_str[i]) 86 | { 87 | std::cout << termcolor::bright_red << manip_str[i] << termcolor::reset; 88 | } 89 | else 90 | { 91 | std::cout << manip_str[i]; 92 | } 93 | } 94 | 95 | std::istringstream stream_a(original_code); 96 | std::vector parts_a(std::istream_iterator{stream_a}, std::istream_iterator()); 97 | std::istringstream stream_b(manipulated_code); 98 | std::vector parts_b(std::istream_iterator{stream_b}, std::istream_iterator()); 99 | 100 | std::cout << " [" << original_code << " -->"; 101 | for (u32 i = 0; i < parts_a.size() && i < parts_b.size(); ++i) 102 | { 103 | if (parts_a.at(i) != parts_b.at(i)) 104 | { 105 | std::cout << " " << termcolor::bright_red << parts_b.at(i) << termcolor::reset; 106 | } 107 | else 108 | { 109 | std::cout << " " << parts_b.at(i); 110 | } 111 | } 112 | for (u32 i = parts_a.size(); i < parts_b.size(); ++i) 113 | { 114 | std::cout << " " << termcolor::bright_red << parts_b.at(i) << termcolor::reset; 115 | } 116 | 117 | std::cout << "]"; 118 | 119 | std::cout << " | " << f.model->get_name() << std::dec; 120 | 121 | if (f.model->get_number_of_iterations(f) > 1) 122 | { 123 | std::cout << " | position=" << f.fault_model_iteration; 124 | } 125 | 126 | if (!f.model->is_permanent()) 127 | { 128 | std::cout << " | time=" << std::dec << f.time; 129 | } 130 | 131 | std::cout << std::endl; 132 | } 133 | 134 | void print_register_fault(const armory::RegisterFault& f) 135 | { 136 | auto orig_str = to_hex(f.original_value, 8); 137 | auto manip_str = to_hex(f.manipulated_value, 8); 138 | std::cout << f.reg; 139 | if (!f.model->is_permanent()) 140 | { 141 | std::cout << ": " << orig_str << " --> "; 142 | print_differences(orig_str, manip_str); 143 | } 144 | 145 | std::cout << " | " << f.model->get_name() << std::dec; 146 | 147 | if (f.model->get_number_of_iterations(f) > 1) 148 | { 149 | std::cout << " | position=" << f.fault_model_iteration; 150 | } 151 | 152 | if (!f.model->is_permanent()) 153 | { 154 | std::cout << " | time=" << std::dec << f.time; 155 | } 156 | 157 | std::cout << std::endl; 158 | } 159 | } // namespace armory_cli 160 | -------------------------------------------------------------------------------- /subprojects/armory_cli/src/meson.build: -------------------------------------------------------------------------------- 1 | src += files( 2 | 'fault_models.cpp', 3 | 'fault_printing.cpp', 4 | 'armory_cli.cpp', 5 | ) 6 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/architectures.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mulator 7 | { 8 | 9 | enum Architecture 10 | { 11 | ARMv6M, 12 | ARMv7M, 13 | ARMv7EM, 14 | }; 15 | 16 | std::string to_string(const Architecture& x); 17 | std::ostream& operator<<(std::ostream& os, const Architecture& x); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/arm_functions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/types.h" 4 | #include "m-ulator/shift_types.h" 5 | 6 | #include 7 | 8 | namespace mulator::arm_functions 9 | { 10 | // armv6m 11 | std::tuple shift_c(u32 value, ShiftType type, u8 amount, u8 carry_in); 12 | std::tuple shift(u32 value, ShiftType type, u8 amount, u8 carry_in); 13 | 14 | std::tuple LSL_C(u32 x, u32 shift); 15 | std::tuple LSL(u32 x, u32 shift); 16 | 17 | std::tuple LSR_C(u32 x, u32 shift); 18 | std::tuple LSR(u32 x, u32 shift); 19 | 20 | std::tuple ASR_C(u32 x, u32 shift); 21 | std::tuple ASR(u32 x, u32 shift); 22 | 23 | std::tuple ROR_C(u32 x, u32 shift); 24 | std::tuple ROR(u32 x, u32 shift); 25 | 26 | std::tuple RRX_C(u32 x, u8 carry_in); 27 | std::tuple RRX(u32 x, u8 carry_in); 28 | 29 | std::tuple add_with_carry(u32 x, u32 y, u8 carry_in); 30 | 31 | u32 align(u32 address, u32 alignment); 32 | 33 | std::tuple decode_imm_shift(u8 type, u32 imm5); 34 | ShiftType decode_reg_shift(u8 type); 35 | 36 | u32 sign_extend(u32 value, u32 num_bits); 37 | u32 bit_count(u32 value); 38 | u32 lowest_set_bit(u32 value); 39 | 40 | // armv7m 41 | std::tuple signed_sat_Q(i32 i, u32 n); 42 | 43 | std::tuple unsigned_sat_Q(i32 i, u32 n); 44 | 45 | u32 signed_sat(i32 i, u32 n); 46 | 47 | u32 unsigned_sat(i32 i, u32 n); 48 | 49 | std::tuple sat_Q(i32 i, u32 n, bool unsigned_sat); 50 | 51 | u32 sat(i32 i, u32 n, bool unsigned_sat); 52 | 53 | std::tuple thumb_expand_imm_C(u32 imm, u8 carry_in); 54 | std::tuple thumb_expand_imm(u32 imm); 55 | } 56 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/callback_hook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/types.h" 4 | 5 | #include 6 | 7 | namespace mulator 8 | { 9 | template 10 | class CallbackHook 11 | { 12 | public: 13 | using FuncPtr = void (*)(Args..., void*); 14 | using FuncStd = std::function; 15 | 16 | template 17 | u32 add(const Func& func, void* user_data = nullptr) 18 | { 19 | auto id = m_next_id++; 20 | m_callbacks.emplace_back(id, func, user_data); 21 | return id; 22 | } 23 | 24 | void execute(Args... args) const 25 | { 26 | if (!empty()) 27 | { 28 | for (const auto& entry : m_callbacks) 29 | { 30 | if (entry.to_remove) 31 | { 32 | continue; 33 | } 34 | 35 | if (entry.f_ptr != nullptr) 36 | { 37 | entry.f_ptr(std::forward(args)..., entry.user_data); 38 | } 39 | else 40 | { 41 | entry.f_std(std::forward(args)..., entry.user_data); 42 | } 43 | } 44 | cleanup_removed(); 45 | } 46 | } 47 | 48 | void remove(u32 id) 49 | { 50 | for (auto& entry : m_callbacks) 51 | { 52 | if (entry.id == id) 53 | { 54 | m_remove_requested = true; 55 | entry.to_remove = true; 56 | return; 57 | } 58 | } 59 | } 60 | 61 | void clear() 62 | { 63 | m_remove_requested = true; 64 | for (auto& entry : m_callbacks) 65 | { 66 | entry.to_remove = true; 67 | } 68 | } 69 | 70 | bool empty() const 71 | { 72 | return m_callbacks.empty(); 73 | } 74 | 75 | private: 76 | struct Entry 77 | { 78 | u32 id; 79 | FuncPtr f_ptr = nullptr; 80 | FuncStd f_std = {}; 81 | void* user_data; 82 | bool to_remove = false; 83 | 84 | Entry(u32 id, FuncPtr f_ptr, void* user_data) : id(id), f_ptr(f_ptr), user_data(user_data){}; 85 | Entry(u32 id, FuncStd f_std, void* user_data) : id(id), f_std(f_std), user_data(user_data){}; 86 | }; 87 | 88 | void cleanup_removed() const 89 | { 90 | if (m_remove_requested) 91 | { 92 | m_callbacks.erase(std::remove_if(m_callbacks.begin(), m_callbacks.end(), [](auto& e) { return e.to_remove; }), m_callbacks.end()); 93 | m_remove_requested = false; 94 | } 95 | } 96 | 97 | mutable std::vector m_callbacks; 98 | mutable bool m_remove_requested = false; 99 | u32 m_next_id = 0; 100 | }; 101 | 102 | } // namespace mulator 103 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/conditions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mulator 7 | { 8 | 9 | enum Condition 10 | { 11 | EQ = 0, // Equal Z == 1 12 | NE, // Not equal Z == 0 13 | CS, // HS (unsigned higher or same) is a synonym for CS. Carry set C == 1 14 | CC, // LO (unsigned lower) is a synonym for CC. Carry clear C == 0 15 | MI, // Minus, negative N == 1 16 | PL, // Plus, positive or zero N == 0 17 | VS, // Overflow V == 1 18 | VC, // No overflow V == 0 19 | HI, // Unsigned higher C == 1 and Z == 0 20 | LS, // Unsigned lower or same C == 0 or Z == 1 21 | GE, // Signed greater than or equal N == V 22 | LT, // Signed less than N != V 23 | GT, // Signed greater than Z == 0 and N == V 24 | LE, // Signed less than or equal Z == 1 or N != V 25 | AL, // Always (never encoded) 26 | }; 27 | 28 | std::string to_string(const Condition& x); 29 | std::ostream& operator<<(std::ostream& os, const Condition& x); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/disassembler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/instruction_decoder.h" 4 | 5 | #include 6 | 7 | namespace mulator 8 | { 9 | class Disassembler 10 | { 11 | public: 12 | Disassembler(Architecture arch); 13 | ~Disassembler(); 14 | 15 | /* 16 | * Getters for internals. 17 | */ 18 | InstructionDecoder get_decoder() const; 19 | Architecture get_architecture() const; 20 | 21 | /* 22 | * Resets the disassembler by clearing the IT state. 23 | */ 24 | void reset(); 25 | 26 | /* 27 | * Decode and disassemble a single instruction from a byte array. 28 | * 29 | * @param[in] address - current address 30 | * @param[in] code - pointer to the first byte of the instruction 31 | * @param[in] code_size - length of the remaining bytes in byte array 32 | */ 33 | ReturnCode disassemble(u32 address, const u8* code, u32 code_size); 34 | 35 | /* 36 | * Disassemble a single already decoded instruction. 37 | * 38 | * @param[in] instr - the instruction to disassemble 39 | */ 40 | ReturnCode disassemble(Instruction instr); 41 | 42 | /* 43 | * Getters for disassembled data. 44 | */ 45 | Instruction get_instruction() const; 46 | std::string get_mnemonic() const; 47 | std::string get_operands() const; 48 | std::string get_string() const; 49 | 50 | private: 51 | InstructionDecoder m_decoder; 52 | u32 m_it_state; 53 | 54 | std::string format_name(const Instruction& instr) const; 55 | std::string format_shift(const Instruction& instr) const; 56 | std::string format_immediate(u32 value, bool add_flag = true, bool decimal = false, bool print_signed = false) const; 57 | 58 | bool is_load_operation(const Mnemonic& mnemonic) const; 59 | bool is_store_operation(const Mnemonic& mnemonic) const; 60 | bool is_shift_operation(const Mnemonic& mnemonic) const; 61 | 62 | Instruction m_instruction; 63 | std::string m_mnemonic; 64 | std::string m_operands; 65 | 66 | bool in_IT_block() const; 67 | bool last_in_IT_block() const; 68 | Condition pop_IT_condition(); 69 | }; 70 | 71 | } // namespace mulator 72 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/emulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/architectures.h" 4 | #include "m-ulator/callback_hook.h" 5 | #include "m-ulator/instruction_decoder.h" 6 | #include "m-ulator/memory_region.h" 7 | #include "m-ulator/registers.h" 8 | #include "m-ulator/types.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace mulator 16 | { 17 | struct CPU_State 18 | { 19 | u32 registers[REGISTER_COUNT]; 20 | 21 | struct 22 | { 23 | bool N; 24 | bool Z; 25 | bool C; 26 | bool V; 27 | bool Q; 28 | u8 it_state; 29 | } psr; 30 | 31 | u32 exclusive_address; 32 | 33 | u32 CCR; 34 | u8 PRIMASK; 35 | u8 FAULTMASK; 36 | u8 BASEPRI; 37 | u8 CONTROL; 38 | 39 | u32 time; 40 | }; 41 | 42 | class Emulator 43 | { 44 | public: 45 | Emulator(Architecture arch); 46 | Emulator(const Emulator& other); 47 | ~Emulator(); 48 | 49 | /* 50 | * Getters for internals. 51 | */ 52 | InstructionDecoder get_decoder() const; 53 | Architecture get_architecture() const; 54 | 55 | /* 56 | * Sets the flash region. 57 | * 58 | * @param[in] offset - the starting address 59 | * @param[in] size - the memory region size 60 | */ 61 | void set_flash_region(u32 offset, u32 size); 62 | 63 | /* 64 | * Getters for the flash region. 65 | */ 66 | u32 get_flash_offset() const; 67 | u32 get_flash_size() const; 68 | u8* get_flash() const; 69 | 70 | /* 71 | * Sets the ram region. 72 | * 73 | * @param[in] offset - the starting address 74 | * @param[in] size - the memory region size 75 | */ 76 | void set_ram_region(u32 offset, u32 size); 77 | 78 | /* 79 | * Getters for the ram region. 80 | */ 81 | u32 get_ram_offset() const; 82 | u32 get_ram_size() const; 83 | u8* get_ram() const; 84 | 85 | /* 86 | * Read and write access to registers. 87 | * Does not fire any hooks! 88 | */ 89 | u32 read_register(Register reg) const; 90 | void write_register(Register reg, u32 value); 91 | 92 | /* 93 | * Read and write access to memory. 94 | * Does not fire any hooks! 95 | */ 96 | void read_memory(u32 address, u8* buffer, u32 len) const; 97 | void write_memory(u32 dst_address, const u8* buffer, u32 len); 98 | 99 | /* 100 | * Starts/continues emulation of the code from the current state. 101 | * Stops automatically after a maximum number of instructions or when the end address is hit. 102 | */ 103 | ReturnCode emulate(u64 max_instructions); 104 | ReturnCode emulate(u32 end_address, u64 max_instructions); 105 | 106 | /* 107 | * Stop the ongoing emulation. Useful in hooks. 108 | */ 109 | void stop_emulation(); 110 | 111 | /* 112 | * Check whether the emulator is still emulating. Useful in hooks. 113 | */ 114 | bool is_running() const; 115 | 116 | /* 117 | * Gets the total number of executed instructions in this emulator. 118 | */ 119 | u32 get_time() const; 120 | 121 | /* 122 | * Gets the number of executed instructions in the last 'emulate' call. 123 | */ 124 | u32 get_emulated_time() const; 125 | 126 | /* 127 | * Returns true if emulation is currently in an IT block. 128 | */ 129 | bool in_IT_block() const; 130 | 131 | /* 132 | * Returns true if emulation is currently at the last instruction in an IT block. 133 | */ 134 | bool last_in_IT_block() const; 135 | 136 | /* 137 | * Returns the current CPU state. 138 | * By backing up the CPU state and RAM contents the state of the emulator can be backed up completely. 139 | */ 140 | CPU_State get_cpu_state() const; 141 | 142 | /* 143 | * Set the emulator to a specific CPU state. 144 | */ 145 | void set_cpu_state(const CPU_State& state); 146 | 147 | /* 148 | * ##################################################################################### 149 | * Hooks 150 | * ##################################################################################### 151 | * Every "XY_hook.add" function returns a unique identifier that can be used to remove a hook. 152 | * This unique identifier is never 0. 153 | */ 154 | 155 | /* 156 | * Hooking before instruction fetch, i.e., before memory is read and passed to the decoder. 157 | * 158 | * The hook callback receives: 159 | * emu - the emulator that executed the hook 160 | * address - the address of the instruction which will be fetched 161 | * instr_size - the size of the instruction which will be fetched 162 | * user_data - a pointer supplied while registering the hook 163 | */ 164 | CallbackHook before_fetch_hook; 165 | 166 | /* 167 | * Hooking either after an instruction has been decoded or after it has been executed. 168 | * 169 | * The hook callback receives: 170 | * emu - the emulator that executed the hook 171 | * instruction - the decoded/executed instruction 172 | * user_data - a pointer supplied while registering the hook 173 | */ 174 | CallbackHook instruction_decoded_hook; 175 | CallbackHook instruction_executed_hook; 176 | 177 | /* 178 | * Hooking memory access. 179 | * 180 | * The hook callback receives: 181 | * emu - the emulator that executed the hook 182 | * address - the address which is accessed 183 | * size - the number of bytes written, maximum 4 184 | * value - for the read hooks value holds the current content of the memory, for write hooks value holds the content to be written 185 | * user_data - a pointer supplied while registering the hook 186 | */ 187 | CallbackHook before_memory_read_hook; 188 | CallbackHook after_memory_read_hook; 189 | CallbackHook before_memory_write_hook; 190 | CallbackHook after_memory_write_hook; 191 | 192 | /* 193 | * Hooking register access. 194 | * 195 | * The hook callback receives: 196 | * emu - the emulator that executed the hook 197 | * reg - the regester which is accessed 198 | * value - for the read hooks value holds the current content of the register, for write hooks value holds the content to be written 199 | * user_data - a pointer supplied while registering the hook 200 | */ 201 | CallbackHook before_register_read_hook; 202 | CallbackHook after_register_read_hook; 203 | CallbackHook before_register_write_hook; 204 | CallbackHook after_register_write_hook; 205 | 206 | /* 207 | * Remove all registered hooks. 208 | */ 209 | void clear_hooks(); 210 | 211 | private: 212 | InstructionDecoder m_decoder; 213 | 214 | ReturnCode m_return_code; 215 | 216 | MemoryRegion m_flash; 217 | MemoryRegion m_ram; 218 | 219 | CPU_State m_cpu_state; 220 | u32 m_emulated_time; 221 | bool m_psr_updated; 222 | 223 | u8* validate_address(u32 address); 224 | void clock_cpu(); 225 | 226 | u32 read_memory_internal(u32 address, u8 bytes); 227 | void write_memory_internal(u32 address, u32 value, u8 bytes); 228 | 229 | u32 read_register_internal(Register reg); 230 | void write_register_internal(Register reg, u32 value); 231 | 232 | Condition pop_IT_condition(); 233 | 234 | bool execute(const Instruction& instr); 235 | 236 | bool evaluate_condition(const Instruction& instr); 237 | 238 | void branch_write_PC(u32 address); 239 | void bx_write_PC(u32 address); 240 | void blx_write_PC(u32 address); 241 | 242 | void set_exclusive_monitors(u32 address, u32 align); 243 | bool exclusive_monitors_pass(u32 address, u32 align) const; 244 | 245 | bool in_priviledged_mode() const; 246 | i32 get_execution_priority() const; 247 | }; 248 | 249 | } // namespace mulator 250 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/instruction.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/types.h" 4 | #include "m-ulator/mnemonics.h" 5 | #include "m-ulator/conditions.h" 6 | #include "m-ulator/registers.h" 7 | #include "m-ulator/architectures.h" 8 | #include "m-ulator/shift_types.h" 9 | 10 | namespace mulator 11 | { 12 | 13 | struct Instruction 14 | { 15 | Instruction(); 16 | ~Instruction() = default; 17 | 18 | u32 address; 19 | u8 size; 20 | u32 encoding; 21 | 22 | Mnemonic name; 23 | 24 | Condition condition; 25 | 26 | ShiftType shift_type; 27 | u32 shift_amount; 28 | 29 | struct 30 | { 31 | bool S; 32 | bool wback; 33 | bool index; 34 | bool add; 35 | bool unaligned_allowed; 36 | } flags; 37 | 38 | enum class OperandType 39 | { 40 | NONE, 41 | I, // immediate 42 | R, // register 43 | RI, // register, immediate 44 | RR, // register, register 45 | RRI, // register, register, immediate 46 | RRR, // register, register, register 47 | RRII, // register, register, immediate, immediate 48 | RRRI, // register, register, register, immediate 49 | RRRR // register, register, register, register 50 | } operand_type; 51 | 52 | Register Rd; 53 | Register Rn; 54 | Register Rm; 55 | Register Ra; 56 | u32 imm; 57 | u32 imm2; 58 | 59 | bool uses_immediate() const; 60 | bool uses_only_registers() const; 61 | u32 get_register_count() const; 62 | u32 get_immediate_count() const; 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/instruction_decoder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/instruction.h" 4 | #include "m-ulator/return_codes.h" 5 | #include 6 | 7 | namespace mulator 8 | { 9 | 10 | class InstructionDecoder 11 | { 12 | public: 13 | /* 14 | * Returns the size (2 or 4 bytes) of the instruction starting with the given bytes. 15 | * The supplied array has to be at least 2 bytes long. 16 | */ 17 | static u8 get_instruction_size(const u8* bytes); 18 | 19 | /* 20 | * Constructs a new decoder for the given architecture. 21 | */ 22 | InstructionDecoder(Architecture arch); 23 | 24 | /* 25 | * Get the architecture this decoder was instantiated for. 26 | */ 27 | Architecture get_architecture() const; 28 | 29 | /* 30 | * Decodes an instruction from the byte stream. 31 | * If decoding was not successful, the returned instruction will still be filled with as much correct information as possible. 32 | * 33 | * @param[in] address - the logical address of the current instruction 34 | * @param[in] bytes - a pointer to the first byte of the instruction 35 | * @param[in] code_size - the number of remaining bytes in the byte stream 36 | * @param[in] in_IT_block - indicates whether the instruction is to be decoded as if execution is currently in an If-Then (IT) block 37 | * @param[in] last_in_IT_block - indicates whether the instruction is the last one in an If-Then (IT) block 38 | * 39 | * @returns A pair of return code and decoded instruction. Only for return code "OK" the instruction was decoded from valid Assembly. 40 | */ 41 | std::pair decode_instruction(u32 address, const u8* bytes, u32 code_size, bool in_IT_block, bool last_in_IT_block); 42 | 43 | private: 44 | Architecture m_arch; 45 | std::string m_error_message; 46 | }; 47 | 48 | } // namespace mulator 49 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/memory_region.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "m-ulator/types.h" 4 | 5 | namespace mulator 6 | { 7 | 8 | struct MemoryRegion 9 | { 10 | u32 offset; 11 | u32 size; 12 | u8* bytes; 13 | struct 14 | { 15 | bool read; 16 | bool write; 17 | bool execute; 18 | } access; 19 | 20 | bool contains(u32 address, u32 len) const 21 | { 22 | return (address >= offset) && ((u64)(address - offset) + len <= (u64)size); 23 | } 24 | 25 | u8* get(u32 address) const 26 | { 27 | return bytes + (address - offset); 28 | } 29 | }; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/mnemonics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mulator 7 | { 8 | 9 | enum class Mnemonic 10 | { 11 | ADC, 12 | ADD, 13 | ADR, 14 | AND, 15 | ASR, 16 | B, 17 | BIC, 18 | BKPT, 19 | BL, 20 | BLX, 21 | BX, 22 | CMN, 23 | CMP, 24 | CPS, 25 | DMB, 26 | DSB, 27 | EOR, 28 | ISB, 29 | LDM, 30 | LDR, 31 | LDRB, 32 | LDRH, 33 | LDRSB, 34 | LDRSH, 35 | LSL, 36 | LSR, 37 | MOV, 38 | MRS, 39 | MSR, 40 | MUL, 41 | MVN, 42 | NOP, 43 | ORR, 44 | POP, 45 | PUSH, 46 | REV, 47 | REV16, 48 | REVSH, 49 | ROR, 50 | RSB, 51 | SBC, 52 | SEV, 53 | STM, 54 | STR, 55 | STRB, 56 | STRH, 57 | SUB, 58 | SVC, 59 | SXTB, 60 | SXTH, 61 | TST, 62 | UDF, 63 | UXTB, 64 | UXTH, 65 | WFE, 66 | WFI, 67 | YIELD, 68 | 69 | // armv7m only 70 | ADDW, 71 | BFC, 72 | BFI, 73 | CBZ, 74 | CBNZ, 75 | CLREX, 76 | CLZ, 77 | CSDB, 78 | DBG, 79 | IT, 80 | LDMDB, 81 | LDRBT, 82 | LDRD, 83 | LDREX, 84 | LDREXB, 85 | LDREXH, 86 | LDRHT, 87 | LDRSBT, 88 | LDRSHT, 89 | LDRT, 90 | MLA, 91 | MLS, 92 | MOVT, 93 | MOVW, 94 | ORN, 95 | PLD, 96 | PLI, 97 | PSSBB, 98 | RBIT, 99 | RRX, 100 | SBFX, 101 | SDIV, 102 | SMLAL, 103 | SMULL, 104 | SSAT, 105 | SSBB, 106 | STMDB, 107 | STRBT, 108 | STRD, 109 | STREX, 110 | STREXB, 111 | STREXH, 112 | STRHT, 113 | STRT, 114 | SUBW, 115 | TBB, 116 | TBH, 117 | TEQ, 118 | UBFX, 119 | UDIV, 120 | UMLAL, 121 | UMULL, 122 | USAT, 123 | }; 124 | 125 | std::string to_string(const Mnemonic& x); 126 | std::ostream& operator<<(std::ostream& os, const Mnemonic& x); 127 | 128 | bool has_narrow_encoding(const Mnemonic& x); 129 | bool has_wide_encoding(const Mnemonic& x); 130 | 131 | } 132 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/registers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mulator 6 | { 7 | 8 | const int REGISTER_COUNT = 17; 9 | 10 | enum Register 11 | { 12 | R0 = 0, 13 | R1, 14 | R2, 15 | R3, 16 | R4, 17 | R5, 18 | R6, 19 | R7, 20 | R8, 21 | R9, 22 | R10, 23 | R11, 24 | R12, 25 | R13, 26 | R14, 27 | R15, 28 | 29 | SB = R9, 30 | SL = R10, 31 | FP = R11, 32 | IP = R12, 33 | SP = R13, 34 | LR = R14, 35 | PC = R15, 36 | 37 | PSR, 38 | }; 39 | 40 | std::string to_string(const Register& x); 41 | std::ostream& operator<<(std::ostream& os, const Register& x); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/return_codes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace mulator 7 | { 8 | 9 | enum class ReturnCode 10 | { 11 | OK, 12 | 13 | INCOMPLETE_DATA, 14 | UNDEFINED, 15 | UNPREDICTABLE, 16 | UNSUPPORTED, 17 | UNEXPECTED_ENCODING, 18 | NOT_IMPLEMENTED, 19 | UNKNOWN_INSTRUCTION, 20 | INVALID_IMMEDIATE, 21 | 22 | INVALID_REGISTER, 23 | INVALID_ALIGNMENT, 24 | INVALID_MEMORY_ACCESS, 25 | INVALID_SHIFT_ARGUMENT, 26 | HARD_FAULT, 27 | 28 | UNINITIALIZED, 29 | MAX_INSTRUCTIONS_REACHED, 30 | END_ADDRESS_REACHED, 31 | STOP_EMULATION_CALLED, 32 | }; 33 | 34 | std::string to_string(const ReturnCode& x); 35 | std::ostream& operator<<(std::ostream& os, const ReturnCode& x); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/shift_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mulator 6 | { 7 | 8 | enum class ShiftType 9 | { 10 | LSL, 11 | LSR, 12 | ASR, 13 | ROR, 14 | RRX 15 | }; 16 | 17 | std::string to_string(const ShiftType& x); 18 | std::ostream& operator<<(std::ostream& os, const ShiftType& x); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /subprojects/m-ulator/include/m-ulator/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mulator 6 | { 7 | 8 | #define UNUSED(x) (void)(x); 9 | 10 | using u8 = uint8_t; 11 | using u16 = uint16_t; 12 | using u32 = uint32_t; 13 | using u64 = uint64_t; 14 | 15 | using i8 = int8_t; 16 | using i16 = int16_t; 17 | using i32 = int32_t; 18 | using i64 = int64_t; 19 | 20 | #define _1BIT(x) ((x) & 0x1) 21 | #define _2BIT(x) ((x) & 0x3) 22 | #define _3BIT(x) ((x) & 0x7) 23 | #define _4BIT(x) ((x) & 0xF) 24 | #define _5BIT(x) ((x) & 0x1F) 25 | #define _6BIT(x) ((x) & 0x3F) 26 | #define _7BIT(x) ((x) & 0x7F) 27 | #define _8BIT(x) ((x) & 0xFF) 28 | #define _9BIT(x) ((x) & 0x1FF) 29 | #define _10BIT(x) ((x) & 0x3FF) 30 | #define _11BIT(x) ((x) & 0x7FF) 31 | #define _12BIT(x) ((x) & 0xFFF) 32 | #define _13BIT(x) ((x) & 0x1FFF) 33 | #define _14BIT(x) ((x) & 0x3FFF) 34 | #define _15BIT(x) ((x) & 0x7FFF) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /subprojects/m-ulator/meson.build: -------------------------------------------------------------------------------- 1 | project('m-ulator', 'cpp', 2 | version : '0.1', 3 | default_options : ['warning_level=3', 'cpp_std=c++17']) 4 | 5 | incdir = include_directories('include') 6 | 7 | src = [] 8 | subdir('src') 9 | 10 | libmulator = static_library('mulator', sources: src, include_directories : incdir) 11 | libmulator_dep = declare_dependency(include_directories : incdir, link_with : libmulator) 12 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/architectures.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/architectures.h" 2 | 3 | namespace mulator 4 | { 5 | 6 | std::string to_string(const Architecture& x) 7 | { 8 | switch (x) 9 | { 10 | case Architecture::ARMv6M: return "ARMv6-M"; 11 | case Architecture::ARMv7M: return "ARMv7-M"; 12 | case Architecture::ARMv7EM: return "ARMv7E-M"; 13 | } 14 | 15 | return "(x)) + ">"; 16 | } 17 | 18 | std::ostream& operator<<(std::ostream& os, const Architecture& x) 19 | { 20 | return os << to_string(x); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/arm_functions.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/arm_functions.h" 2 | 3 | namespace mulator::arm_functions 4 | { 5 | std::tuple shift(u32 value, ShiftType type, u8 amount, u8 carry_in) 6 | { 7 | auto [a, b, c] = shift_c(value, type, amount, carry_in); 8 | UNUSED(c); 9 | return {a, b}; 10 | } 11 | 12 | std::tuple shift_c(u32 value, ShiftType type, u8 amount, u8 carry_in) 13 | { 14 | if (type == ShiftType::RRX && amount != 1) 15 | { 16 | return {false, 0, 0}; 17 | } 18 | 19 | if (amount == 0) 20 | { 21 | return {true, value, carry_in}; 22 | } 23 | 24 | switch (type) 25 | { 26 | case ShiftType::LSL: return LSL_C(value, amount); 27 | case ShiftType::LSR: return LSR_C(value, amount); 28 | case ShiftType::ASR: return ASR_C(value, amount); 29 | case ShiftType::ROR: return ROR_C(value, amount); 30 | case ShiftType::RRX: return RRX_C(value, carry_in); 31 | } 32 | 33 | return {false, 0, 0}; 34 | } 35 | 36 | std::tuple LSL_C(u32 x, u32 shift) 37 | { 38 | if (shift == 0) 39 | { 40 | return {false, 0, 0}; 41 | } 42 | u32 result = 0; 43 | if (shift <= 32) result = x << (shift - 1); 44 | u8 carry = (result >> 31) & 1; 45 | if (shift <= 32) result <<= 1; 46 | return {true, result, carry}; 47 | } 48 | std::tuple LSL(u32 x, u32 shift) 49 | { 50 | if (shift == 0) 51 | { 52 | return {true, x}; 53 | } 54 | auto [a, b, c] = LSL_C(x, shift); 55 | UNUSED(c); 56 | return {a, b}; 57 | } 58 | 59 | std::tuple LSR_C(u32 x, u32 shift) 60 | { 61 | if (shift == 0) 62 | { 63 | return {false, 0, 0}; 64 | } 65 | u32 result = 0; 66 | if (shift <= 32) result = x >> (shift - 1); 67 | u8 carry = result & 1; 68 | if (shift <= 32) result >>= 1; 69 | return {true, result, carry}; 70 | } 71 | std::tuple LSR(u32 x, u32 shift) 72 | { 73 | if (shift == 0) 74 | { 75 | return {true, x}; 76 | } 77 | auto [a, b, c] = LSR_C(x, shift); 78 | UNUSED(c); 79 | return {a, b}; 80 | } 81 | 82 | std::tuple ASR_C(u32 x, u32 shift) 83 | { 84 | if (shift == 0) 85 | { 86 | return {false, 0, 0}; 87 | } 88 | u64 sign_extended = (u64)((i64)((i32)x)); 89 | u64 result = 0xFFFFFFFF * (sign_extended >> 31) & 1; 90 | if (shift <= 32) result = sign_extended >> (shift - 1); 91 | u8 carry = result & 1; 92 | if (shift <= 32) result >>= 1; 93 | return {true, (u32)result, carry}; 94 | } 95 | std::tuple ASR(u32 x, u32 shift) 96 | { 97 | if (shift == 0) 98 | { 99 | return {true, x}; 100 | } 101 | auto [a, b, c] = ASR_C(x, shift); 102 | UNUSED(c); 103 | return {a, b}; 104 | } 105 | 106 | std::tuple ROR_C(u32 x, u32 shift) 107 | { 108 | if (shift == 0) 109 | { 110 | return {false, 0, 0}; 111 | } 112 | u32 m = shift % 32; 113 | auto [b1, lsr] = LSR(x, m); 114 | auto [b2, lsl] = LSL(x, 32 - m); 115 | u32 result = lsr | lsl; 116 | return {b1 & b2, result, result >> 31}; 117 | } 118 | std::tuple ROR(u32 x, u32 shift) 119 | { 120 | if (shift == 0) 121 | { 122 | return {true, x}; 123 | } 124 | auto [a, b, c] = ROR_C(x, shift); 125 | UNUSED(c); 126 | return {a, b}; 127 | } 128 | 129 | std::tuple RRX_C(u32 x, u8 carry_in) 130 | { 131 | return {true, ((u32)carry_in << 31) | (x >> 1), x & 1}; 132 | } 133 | std::tuple RRX(u32 x, u8 carry_in) 134 | { 135 | return {true, ((u32)carry_in << 31) | (x >> 1)}; 136 | } 137 | 138 | 139 | std::tuple add_with_carry(u32 x, u32 y, u8 carry_in) 140 | { 141 | u64 unsigned_sum = (u64)carry_in + (u64)x + (u64)y; 142 | i64 signed_sum = (i64)carry_in + (i32)x + (i32)y; 143 | u32 result = (u32)unsigned_sum; 144 | u8 carry_out = (result == unsigned_sum) ? 0 : 1; 145 | u8 overflow = ((i64)((i32)result) == signed_sum) ? 0 : 1; 146 | return {result, carry_out, overflow}; 147 | } 148 | 149 | 150 | u32 align(u32 address, u32 alignment) 151 | { 152 | if (alignment == 1) 153 | { 154 | return address; 155 | } 156 | else if (alignment == 2) 157 | { 158 | return address & 0xFFFFFFFE; 159 | } 160 | else if (alignment == 4) 161 | { 162 | return address & 0xFFFFFFFC; 163 | } 164 | return 0; 165 | } 166 | 167 | 168 | std::tuple decode_imm_shift(u8 type, u32 imm5) 169 | { 170 | switch (type) 171 | { 172 | case 0b00: 173 | return {ShiftType::LSL, imm5}; 174 | case 0b01: 175 | return {ShiftType::LSR, (imm5 == 0) ? 32 : imm5}; 176 | case 0b10: 177 | return {ShiftType::ASR, (imm5 == 0) ? 32 : imm5}; 178 | case 0b11: 179 | { 180 | if (imm5 == 0) 181 | { 182 | return {ShiftType::RRX, 1}; 183 | } 184 | else 185 | { 186 | return {ShiftType::ROR, imm5}; 187 | } 188 | } 189 | } 190 | return {ShiftType::LSL, 0}; // never reached 191 | } 192 | 193 | ShiftType decode_reg_shift(u8 type) 194 | { 195 | switch (type) 196 | { 197 | case 0b00: return ShiftType::LSL; 198 | case 0b01: return ShiftType::LSR; 199 | case 0b10: return ShiftType::ASR; 200 | case 0b11: return ShiftType::ROR; 201 | } 202 | return ShiftType::LSL; // never reached 203 | } 204 | 205 | u32 sign_extend(u32 value, u32 num_bits) 206 | { 207 | u8 sign = (value >> (num_bits - 1)) & 1; 208 | u32 mask = sign * (0xFFFFFFFF << num_bits); 209 | return mask | value; 210 | } 211 | 212 | u32 bit_count(u32 value) 213 | { 214 | u32 cnt = 0; 215 | while (value != 0) 216 | { 217 | cnt += (value & 1); 218 | value >>= 1; 219 | } 220 | return cnt; 221 | } 222 | 223 | u32 lowest_set_bit(u32 value) 224 | { 225 | for (u32 i = 0; i < 32; ++i) 226 | { 227 | if ((value >> i) & 1) 228 | { 229 | return i; 230 | } 231 | } 232 | return 32; 233 | } 234 | 235 | 236 | std::tuple signed_sat_Q(i32 i, u32 n) 237 | { 238 | i32 sat = 1 << (n - 1); 239 | if (i > sat - 1) 240 | { 241 | return {(u32)(sat - 1), true}; 242 | } 243 | else if (i < -sat) 244 | { 245 | return {(u32)(-sat), true}; 246 | } 247 | return {(u32)i, false}; 248 | } 249 | 250 | std::tuple unsigned_sat_Q(i32 i, u32 n) 251 | { 252 | i32 sat = 1 << n; 253 | if (i > sat - 1) 254 | { 255 | return {(u32)(sat - 1), true}; 256 | } 257 | else if (i < 0) 258 | { 259 | return {0, true}; 260 | } 261 | return {(u32)i, false}; 262 | } 263 | 264 | u32 signed_sat(i32 i, u32 n) 265 | { 266 | return std::get<0>(signed_sat_Q(i, n)); 267 | } 268 | 269 | u32 unsigned_sat(i32 i, u32 n) 270 | { 271 | return std::get<0>(unsigned_sat_Q(i, n)); 272 | } 273 | 274 | std::tuple sat_Q(i32 i, u32 n, bool unsigned_sat) 275 | { 276 | if (unsigned_sat) 277 | { 278 | return unsigned_sat_Q(i, n); 279 | } 280 | return signed_sat_Q(i, n); 281 | } 282 | 283 | u32 sat(i32 i, u32 n, bool unsigned_sat) 284 | { 285 | return std::get<0>(sat_Q(i, n, unsigned_sat)); 286 | } 287 | 288 | std::tuple thumb_expand_imm_C(u32 imm, u8 carry_in) 289 | { 290 | u32 res; 291 | u8 carry_out; 292 | if (_2BIT(imm >> 10) == 0b00) 293 | { 294 | u8 tmp = _2BIT(imm >> 8); 295 | u32 byte = _8BIT(imm); 296 | if (tmp == 0b00) 297 | { 298 | res = byte; 299 | } 300 | else if (tmp == 0b01) 301 | { 302 | if (byte == 0) 303 | { 304 | return {false, 0, 0}; 305 | } 306 | res = (byte << 16) | byte; 307 | } 308 | else if (tmp == 0b10) 309 | { 310 | if (byte == 0) 311 | { 312 | return {false, 0, 0}; 313 | } 314 | res = (byte << 24) | (byte << 8); 315 | } 316 | else // if (tmp == 0b11) 317 | { 318 | if (byte == 0) 319 | { 320 | return {false, 0, 0}; 321 | } 322 | res = (byte << 24) | (byte << 16) | (byte << 8) | byte; 323 | } 324 | carry_out = carry_in; 325 | } 326 | else 327 | { 328 | u32 tmp = (1 << 7) | _7BIT(imm); 329 | auto [b, rot, c] = ROR_C(tmp, _5BIT(imm >> 7)); 330 | if (!b) return {false, 0, 0}; 331 | res = rot; 332 | carry_out = c; 333 | } 334 | return {true, res, carry_out}; 335 | } 336 | 337 | std::tuple thumb_expand_imm(u32 imm) 338 | { 339 | auto [a, b, c] = thumb_expand_imm_C(imm, 0); 340 | UNUSED(c); 341 | return {a, b}; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/conditions.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/conditions.h" 2 | 3 | namespace mulator 4 | { 5 | 6 | std::string to_string(const Condition& x) 7 | { 8 | switch (x) 9 | { 10 | case EQ: return "eq"; 11 | case NE: return "ne"; 12 | case CS: return "cs"; 13 | case CC: return "cc"; 14 | case MI: return "mi"; 15 | case PL: return "pl"; 16 | case VS: return "vs"; 17 | case VC: return "vc"; 18 | case HI: return "hi"; 19 | case LS: return "ls"; 20 | case GE: return "ge"; 21 | case LT: return "lt"; 22 | case GT: return "gt"; 23 | case LE: return "le"; 24 | case AL: return "al"; 25 | } 26 | 27 | return "(x)) + ">"; 28 | } 29 | 30 | std::ostream& operator<<(std::ostream& os, const Condition& x) 31 | { 32 | return os << to_string(x); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/disassembler.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/disassembler.h" 2 | #include "m-ulator/arm_functions.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace mulator; 9 | 10 | using OperandType = Instruction::OperandType; 11 | 12 | Disassembler::Disassembler(Architecture arch) : m_decoder(arch) 13 | { 14 | reset(); 15 | } 16 | 17 | Disassembler::~Disassembler() 18 | { 19 | } 20 | 21 | InstructionDecoder Disassembler::get_decoder() const 22 | { 23 | return m_decoder; 24 | } 25 | 26 | Architecture Disassembler::get_architecture() const 27 | { 28 | return m_decoder.get_architecture(); 29 | } 30 | 31 | void Disassembler::reset() 32 | { 33 | m_it_state = 0; 34 | } 35 | 36 | ReturnCode Disassembler::disassemble(u32 address, const u8* code, u32 code_size) 37 | { 38 | m_mnemonic = ""; 39 | m_operands = ""; 40 | 41 | auto [status, instr] = m_decoder.decode_instruction(address, code, code_size, in_IT_block(), last_in_IT_block()); 42 | m_instruction = instr; 43 | 44 | if (status == ReturnCode::OK) 45 | { 46 | status = disassemble(instr); 47 | 48 | if (status == ReturnCode::OK) 49 | { 50 | pop_IT_condition(); 51 | } 52 | } 53 | 54 | return status; 55 | } 56 | 57 | Instruction Disassembler::get_instruction() const 58 | { 59 | return m_instruction; 60 | } 61 | 62 | std::string Disassembler::get_mnemonic() const 63 | { 64 | return m_mnemonic; 65 | } 66 | 67 | std::string Disassembler::get_operands() const 68 | { 69 | return m_operands; 70 | } 71 | 72 | std::string Disassembler::get_string() const 73 | { 74 | if (m_operands.empty()) 75 | { 76 | return m_mnemonic; 77 | } 78 | return m_mnemonic + " " + m_operands; 79 | } 80 | 81 | /////////////////////////////////////////// 82 | ///////////// PRIVATE /////////////////// 83 | /////////////////////////////////////////// 84 | 85 | std::string Disassembler::format_name(const Instruction& instr) const 86 | { 87 | std::stringstream stream; 88 | stream << instr.name; 89 | if (instr.flags.S) 90 | { 91 | stream << "s"; 92 | } 93 | if (instr.condition != AL) 94 | { 95 | stream << instr.condition; 96 | } 97 | // if (instr.size == 4 && has_narrow_encoding(instr.name)) 98 | // { 99 | // stream << ".w"; 100 | // } 101 | return stream.str(); 102 | } 103 | 104 | std::string Disassembler::format_shift(const Instruction& instr) const 105 | { 106 | std::stringstream stream; 107 | if (instr.shift_amount != 0) 108 | { 109 | stream << ", "; 110 | if (!is_shift_operation(instr.name)) 111 | { 112 | stream << instr.shift_type << " "; 113 | } 114 | stream << "#" << instr.shift_amount; 115 | } 116 | return stream.str(); 117 | } 118 | 119 | std::string Disassembler::format_immediate(u32 value, bool add_flag, bool decimal, bool print_signed) const 120 | { 121 | std::stringstream stream; 122 | stream << "#"; 123 | if (!add_flag) 124 | { 125 | stream << "-"; 126 | } 127 | if (print_signed && value == 0xFFFFFFFF) 128 | { 129 | stream << "-1"; 130 | } 131 | else 132 | { 133 | if (decimal || value < 10) 134 | { 135 | stream << std::dec; 136 | } 137 | else 138 | { 139 | stream << std::hex << "0x"; 140 | } 141 | stream << (print_signed ? (i32)value : value); 142 | } 143 | return stream.str(); 144 | } 145 | 146 | /////////////////////////////////////////// 147 | ///////////// INHERITED ///////////////// 148 | /////////////////////////////////////////// 149 | 150 | bool Disassembler::in_IT_block() const 151 | { 152 | if (m_decoder.get_architecture() == Architecture::ARMv6M) 153 | { 154 | return false; 155 | } 156 | 157 | return _4BIT(m_it_state) != 0; 158 | } 159 | 160 | bool Disassembler::last_in_IT_block() const 161 | { 162 | if (m_decoder.get_architecture() == Architecture::ARMv6M) 163 | { 164 | return false; 165 | } 166 | return _4BIT(m_it_state) == 0b1000; 167 | } 168 | 169 | Condition Disassembler::pop_IT_condition() 170 | { 171 | Condition c = (Condition)(m_it_state >> 4); 172 | if (_3BIT(m_it_state) == 0) 173 | { 174 | m_it_state = 0; 175 | } 176 | else 177 | { 178 | m_it_state = (m_it_state & 0xE0) | ((m_it_state << 1) & 0x1F); 179 | } 180 | return c; 181 | } 182 | 183 | bool Disassembler::is_load_operation(const Mnemonic& mnemonic) const 184 | { 185 | return (mnemonic == Mnemonic::LDR || mnemonic == Mnemonic::LDRT || mnemonic == Mnemonic::LDRH || mnemonic == Mnemonic::LDRHT || mnemonic == Mnemonic::LDRB || mnemonic == Mnemonic::LDRBT || mnemonic == Mnemonic::LDRSH || mnemonic == Mnemonic::LDRSHT || mnemonic == Mnemonic::LDRSB || mnemonic == Mnemonic::LDRSBT || mnemonic == Mnemonic::LDRD || mnemonic == Mnemonic::LDREX || mnemonic == Mnemonic::LDREXB || mnemonic == Mnemonic::LDREXH || mnemonic == Mnemonic::PLD || mnemonic == Mnemonic::PLI); 186 | } 187 | 188 | bool Disassembler::is_store_operation(const Mnemonic& mnemonic) const 189 | { 190 | return (mnemonic == Mnemonic::STR || mnemonic == Mnemonic::STRT || mnemonic == Mnemonic::STRH || mnemonic == Mnemonic::STRHT || mnemonic == Mnemonic::STRB || mnemonic == Mnemonic::STRBT || mnemonic == Mnemonic::STRD || mnemonic == Mnemonic::STREX || mnemonic == Mnemonic::STREXB || mnemonic == Mnemonic::STREXH); 191 | } 192 | 193 | bool Disassembler::is_shift_operation(const Mnemonic& mnemonic) const 194 | { 195 | return (mnemonic == Mnemonic::ASR || mnemonic == Mnemonic::LSL || mnemonic == Mnemonic::LSR || mnemonic == Mnemonic::ROR); 196 | } 197 | 198 | ReturnCode Disassembler::disassemble(Instruction instr) 199 | { 200 | u32 pc_value = instr.address + 4; 201 | m_instruction = instr; 202 | m_mnemonic = format_name(instr); 203 | m_operands = ""; 204 | 205 | auto shift_extension = format_shift(instr); 206 | 207 | u32 max_regs = instr.get_register_count(); 208 | 209 | u32 regs = 0; 210 | if ((u32)instr.Rn != 0xFFFFFFFF && (u32)instr.Rd != 0xFFFFFFFF && instr.Rn != instr.Rd && regs < max_regs) 211 | { 212 | m_operands = to_string(instr.Rd); 213 | regs++; 214 | } 215 | if ((u32)instr.Rn != 0xFFFFFFFF && regs < max_regs) 216 | { 217 | if (!m_operands.empty()) 218 | m_operands += ", "; 219 | m_operands += to_string(instr.Rn); 220 | regs++; 221 | } 222 | if ((u32)instr.Rm != 0xFFFFFFFF && regs < max_regs) 223 | { 224 | if (!m_operands.empty()) 225 | m_operands += ", "; 226 | m_operands += to_string(instr.Rm); 227 | regs++; 228 | } 229 | if ((u32)instr.Ra != 0xFFFFFFFF && regs < max_regs) 230 | { 231 | if (!m_operands.empty()) 232 | m_operands += ", "; 233 | m_operands += to_string(instr.Ra); 234 | regs++; 235 | } 236 | 237 | if (regs < max_regs) 238 | { 239 | if (m_operands.empty()) 240 | { 241 | m_operands = to_string(instr.Rd); 242 | } 243 | else 244 | { 245 | m_operands = to_string(instr.Rd) + ", " + m_operands; 246 | } 247 | } 248 | 249 | if (instr.uses_immediate() && (instr.name == Mnemonic::MOV || instr.name == Mnemonic::MVN || instr.name == Mnemonic::TST || instr.name == Mnemonic::TEQ || instr.name == Mnemonic::AND || instr.name == Mnemonic::BIC || instr.name == Mnemonic::EOR || instr.name == Mnemonic::ORR || instr.name == Mnemonic::ORN)) 250 | { 251 | auto [b, v] = arm_functions::thumb_expand_imm(instr.imm); 252 | instr.imm = v; 253 | 254 | if (!b) 255 | { 256 | return ReturnCode::UNEXPECTED_ENCODING; 257 | } 258 | } 259 | 260 | if (instr.uses_immediate() && !is_shift_operation(instr.name)) 261 | { 262 | m_operands += ", " + format_immediate(instr.imm); 263 | if (instr.get_immediate_count() > 1) 264 | { 265 | m_operands += ", " + format_immediate(instr.imm2); 266 | } 267 | } 268 | m_operands += shift_extension; 269 | 270 | if (instr.operand_type == Instruction::OperandType::NONE) 271 | { 272 | if (instr.name == Mnemonic::DMB || instr.name == Mnemonic::DSB || instr.name == Mnemonic::ISB) 273 | { 274 | m_operands = "sy"; 275 | } 276 | } 277 | else if (instr.operand_type == Instruction::OperandType::I) 278 | { 279 | if (instr.name == Mnemonic::B || instr.name == Mnemonic::BL) 280 | { 281 | m_operands = format_immediate(instr.imm + pc_value, true, false, true); 282 | } 283 | else if (instr.name == Mnemonic::IT && m_decoder.get_architecture() >= Architecture::ARMv7M) 284 | { 285 | m_it_state = instr.imm; 286 | u8 mask = m_it_state & 0xF; 287 | u8 conditions = 3; 288 | while (_1BIT(mask) != 1) 289 | { 290 | mask >>= 1; 291 | conditions--; 292 | } 293 | mask >>= 1; 294 | 295 | u8 lsb = _1BIT(m_it_state >> 4); 296 | 297 | for (i32 i = conditions - 1; i >= 0; --i) 298 | { 299 | m_mnemonic += (_1BIT(mask >> i) == lsb) ? "t" : "e"; 300 | } 301 | 302 | m_operands = to_string((Condition)(m_it_state >> 4)); 303 | } 304 | } // end Instruction::OperandType::I 305 | else if (instr.operand_type == Instruction::OperandType::RRRR) 306 | { 307 | if (instr.name == Mnemonic::UMLAL || instr.name == Mnemonic::SMLAL || instr.name == Mnemonic::SMULL || instr.name == Mnemonic::UMULL) 308 | { 309 | m_operands = to_string(instr.Ra) + ", " + to_string(instr.Rd) + ", " + to_string(instr.Rn) + ", " + to_string(instr.Rm); 310 | } 311 | } 312 | 313 | if (is_load_operation(instr.name) || is_store_operation(instr.name)) 314 | { 315 | bool add_flag = instr.flags.add; 316 | 317 | std::string base = ""; 318 | std::string addr = ""; 319 | u32 base_regs = 1; 320 | 321 | if (instr.name == Mnemonic::LDRD || instr.name == Mnemonic::STRD) 322 | { 323 | base_regs = 2; 324 | } 325 | 326 | if (instr.name == Mnemonic::PLD || instr.name == Mnemonic::PLI) 327 | { 328 | base_regs = 0; 329 | } 330 | 331 | if (instr.name == Mnemonic::LDREX || instr.name == Mnemonic::LDREXB || instr.name == Mnemonic::LDREXH) 332 | { 333 | add_flag = true; 334 | } 335 | 336 | if (instr.name == Mnemonic::STREX || instr.name == Mnemonic::STREXB || instr.name == Mnemonic::STREXH) 337 | { 338 | base_regs = 2; 339 | add_flag = true; 340 | } 341 | 342 | if (base_regs >= 1) 343 | { 344 | base = to_string(instr.Rd); 345 | } 346 | if (base_regs >= 2) 347 | { 348 | base += ", " + to_string(instr.Rm); 349 | } 350 | 351 | u32 addr_regs = instr.get_register_count() - base_regs; 352 | 353 | if (addr_regs == 0) 354 | { 355 | addr = "[" + to_string(Register::PC); 356 | } 357 | if (addr_regs >= 1) 358 | { 359 | addr = "[" + to_string(instr.Rn); 360 | } 361 | if (addr_regs >= 2) 362 | { 363 | addr += ", " + to_string(instr.Rm) + shift_extension; 364 | } 365 | 366 | std::string imm_str = ""; 367 | if (instr.uses_immediate()) 368 | { 369 | imm_str = ", " + format_immediate(instr.imm, add_flag); 370 | } 371 | 372 | if (instr.flags.index && instr.flags.wback) 373 | { 374 | addr += imm_str + "]!"; 375 | } 376 | else if (!instr.flags.index && instr.flags.wback) 377 | { 378 | addr += "]" + imm_str; 379 | } 380 | else 381 | { 382 | if (imm_str != ", #0") 383 | { 384 | addr += imm_str; 385 | } 386 | addr += "]"; 387 | } 388 | 389 | m_operands = base.empty() ? addr : base + ", " + addr; 390 | } 391 | 392 | if (instr.name == Mnemonic::PUSH || instr.name == Mnemonic::POP || instr.name == Mnemonic::LDM || instr.name == Mnemonic::LDMDB || instr.name == Mnemonic::STM || instr.name == Mnemonic::STMDB) 393 | { 394 | m_operands = ""; 395 | if (instr.name == Mnemonic::LDM || instr.name == Mnemonic::LDMDB || instr.name == Mnemonic::STM || instr.name == Mnemonic::STMDB) 396 | { 397 | m_operands = to_string(instr.Rn) + (instr.flags.wback ? "!, " : ", "); 398 | } 399 | 400 | std::stringstream stream; 401 | stream << "{"; 402 | u32 cnt = arm_functions::bit_count(instr.imm); 403 | for (u32 i = 0; i < 16; ++i) 404 | { 405 | if ((instr.imm >> i) & 1) 406 | { 407 | stream << to_string((Register)i); 408 | cnt--; 409 | if (cnt != 0) 410 | { 411 | stream << ", "; 412 | } 413 | } 414 | } 415 | stream << "}"; 416 | m_operands += stream.str(); 417 | } 418 | 419 | if (instr.name == Mnemonic::SSAT || instr.name == Mnemonic::USAT) 420 | { 421 | m_operands = to_string(instr.Rd) + ", " + format_immediate(instr.imm) + ", " + to_string(instr.Rn) + shift_extension; 422 | } 423 | 424 | if (instr.name == Mnemonic::CPS) 425 | { 426 | bool enable = _1BIT(instr.imm >> 4) == 0; 427 | bool affectPRI = _1BIT(instr.imm >> 1) == 1; 428 | bool affectFAULT = _1BIT(instr.imm) == 1; 429 | m_mnemonic += enable ? "ie" : "id"; 430 | m_operands = ""; 431 | m_operands += affectPRI ? "i" : ""; 432 | m_operands += affectFAULT ? "f" : ""; 433 | } 434 | 435 | return ReturnCode::OK; 436 | } 437 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/instruction.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/instruction.h" 2 | 3 | using namespace mulator; 4 | 5 | Instruction::Instruction() 6 | { 7 | name = Mnemonic::UDF; 8 | address = 0xFFFFFFFF; 9 | condition = Condition::AL; 10 | shift_type = ShiftType::LSL; 11 | shift_amount = 0; 12 | 13 | flags.S = false; 14 | flags.wback = false; 15 | flags.index = false; 16 | flags.add = false; 17 | flags.unaligned_allowed = false; 18 | 19 | operand_type = OperandType::NONE; 20 | Rd = Rn = Rm = Ra = (Register)0xFFFFFFFF; 21 | imm = imm2 = 0xFFFFFFFF; 22 | } 23 | 24 | bool Instruction::uses_immediate() const 25 | { 26 | return operand_type == OperandType::I || operand_type == OperandType::RI || operand_type == OperandType::RRI || operand_type == OperandType::RRII || operand_type == OperandType::RRRI; 27 | } 28 | 29 | bool Instruction::uses_only_registers() const 30 | { 31 | return operand_type == OperandType::R || operand_type == OperandType::RR || operand_type == OperandType::RRR || operand_type == OperandType::RRRR; 32 | } 33 | 34 | 35 | u32 Instruction::get_register_count() const 36 | { 37 | switch (operand_type) 38 | { 39 | case OperandType::R: 40 | case OperandType::RI: return 1; 41 | 42 | case OperandType::RR: 43 | case OperandType::RRI: 44 | case OperandType::RRII: return 2; 45 | 46 | case OperandType::RRR: 47 | case OperandType::RRRI: return 3; 48 | 49 | case OperandType::RRRR: return 4; 50 | 51 | default: return 0; 52 | } 53 | } 54 | 55 | u32 Instruction::get_immediate_count() const 56 | { 57 | switch (operand_type) 58 | { 59 | case OperandType::I: 60 | case OperandType::RI: 61 | case OperandType::RRI: 62 | case OperandType::RRRI: return 1; 63 | 64 | case OperandType::RRII: return 2; 65 | 66 | default: return 0; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/meson.build: -------------------------------------------------------------------------------- 1 | src += files( 2 | 'architectures.cpp', 3 | 'arm_functions.cpp', 4 | 'conditions.cpp', 5 | 'disassembler.cpp', 6 | 'emulator_base.cpp', 7 | 'emulator_execution.cpp', 8 | 'instruction.cpp', 9 | 'instruction_decoder.cpp', 10 | 'mnemonics.cpp', 11 | 'registers.cpp', 12 | 'return_codes.cpp', 13 | 'shift_types.cpp', 14 | ) 15 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/mnemonics.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/mnemonics.h" 2 | 3 | namespace mulator 4 | { 5 | 6 | std::string to_string(const Mnemonic& x) 7 | { 8 | switch (x) 9 | { 10 | case Mnemonic::ADC : return "adc"; 11 | case Mnemonic::ADD : return "add"; 12 | case Mnemonic::ADR : return "adr"; 13 | case Mnemonic::AND : return "and"; 14 | case Mnemonic::ASR : return "asr"; 15 | case Mnemonic::B : return "b"; 16 | case Mnemonic::BIC : return "bic"; 17 | case Mnemonic::BKPT : return "bkpt"; 18 | case Mnemonic::BL : return "bl"; 19 | case Mnemonic::BLX : return "blx"; 20 | case Mnemonic::BX : return "bx"; 21 | case Mnemonic::CMN : return "cmn"; 22 | case Mnemonic::CMP : return "cmp"; 23 | case Mnemonic::CPS : return "cps"; 24 | case Mnemonic::DMB : return "dmb"; 25 | case Mnemonic::DSB : return "dsb"; 26 | case Mnemonic::EOR : return "eor"; 27 | case Mnemonic::ISB : return "isb"; 28 | case Mnemonic::LDM : return "ldm"; 29 | case Mnemonic::LDR : return "ldr"; 30 | case Mnemonic::LDRB : return "ldrb"; 31 | case Mnemonic::LDRH : return "ldrh"; 32 | case Mnemonic::LDRSB : return "ldrsb"; 33 | case Mnemonic::LDRSH : return "ldrsh"; 34 | case Mnemonic::LSL : return "lsl"; 35 | case Mnemonic::LSR : return "lsr"; 36 | case Mnemonic::MOV : return "mov"; 37 | case Mnemonic::MRS : return "mrs"; 38 | case Mnemonic::MSR : return "msr"; 39 | case Mnemonic::MUL : return "mul"; 40 | case Mnemonic::MVN : return "mvn"; 41 | case Mnemonic::NOP : return "nop"; 42 | case Mnemonic::ORR : return "orr"; 43 | case Mnemonic::POP : return "pop"; 44 | case Mnemonic::PUSH : return "push"; 45 | case Mnemonic::REV : return "rev"; 46 | case Mnemonic::REV16 : return "rev16"; 47 | case Mnemonic::REVSH : return "revsh"; 48 | case Mnemonic::ROR : return "ror"; 49 | case Mnemonic::RSB : return "rsb"; 50 | case Mnemonic::SBC : return "sbc"; 51 | case Mnemonic::SEV : return "sev"; 52 | case Mnemonic::STM : return "stm"; 53 | case Mnemonic::STR : return "str"; 54 | case Mnemonic::STRB : return "strb"; 55 | case Mnemonic::STRH : return "strh"; 56 | case Mnemonic::SUB : return "sub"; 57 | case Mnemonic::SVC : return "svc"; 58 | case Mnemonic::SXTB : return "sxtb"; 59 | case Mnemonic::SXTH : return "sxth"; 60 | case Mnemonic::TST : return "tst"; 61 | case Mnemonic::UDF : return "udf"; 62 | case Mnemonic::UXTB : return "uxtb"; 63 | case Mnemonic::UXTH : return "uxth"; 64 | case Mnemonic::WFE : return "wfe"; 65 | case Mnemonic::WFI : return "wfi"; 66 | case Mnemonic::YIELD : return "yield"; 67 | 68 | // armv7m only 69 | case Mnemonic::ADDW : return "addw"; 70 | case Mnemonic::BFC : return "bfc"; 71 | case Mnemonic::BFI : return "bfi"; 72 | case Mnemonic::CBZ : return "cbz"; 73 | case Mnemonic::CBNZ : return "cbnz"; 74 | case Mnemonic::CLREX : return "clrex"; 75 | case Mnemonic::CLZ : return "clz"; 76 | case Mnemonic::CSDB : return "csdb"; 77 | case Mnemonic::DBG : return "dbg"; 78 | case Mnemonic::IT : return "it"; 79 | case Mnemonic::LDMDB : return "ldmdb"; 80 | case Mnemonic::LDRBT : return "ldrbt"; 81 | case Mnemonic::LDRD : return "ldrd"; 82 | case Mnemonic::LDREX : return "ldrex"; 83 | case Mnemonic::LDREXB : return "ldrexb"; 84 | case Mnemonic::LDREXH : return "ldrexh"; 85 | case Mnemonic::LDRHT : return "ldrht"; 86 | case Mnemonic::LDRSBT : return "ldrsbt"; 87 | case Mnemonic::LDRSHT : return "ldrsht"; 88 | case Mnemonic::LDRT : return "ldrt"; 89 | case Mnemonic::MLA : return "mla"; 90 | case Mnemonic::MLS : return "mls"; 91 | case Mnemonic::MOVT : return "movt"; 92 | case Mnemonic::MOVW : return "movw"; 93 | case Mnemonic::ORN : return "orn"; 94 | case Mnemonic::PLD : return "pld"; 95 | case Mnemonic::PLI : return "pli"; 96 | case Mnemonic::PSSBB : return "pssbb"; 97 | case Mnemonic::RBIT : return "rbit"; 98 | case Mnemonic::RRX : return "rrx"; 99 | case Mnemonic::SBFX : return "sbfx"; 100 | case Mnemonic::SDIV : return "sdiv"; 101 | case Mnemonic::SMLAL : return "smlal"; 102 | case Mnemonic::SMULL : return "smull"; 103 | case Mnemonic::SSAT : return "ssat"; 104 | case Mnemonic::SSBB : return "ssbb"; 105 | case Mnemonic::STMDB : return "stmdb"; 106 | case Mnemonic::STRBT : return "strbt"; 107 | case Mnemonic::STRD : return "strd"; 108 | case Mnemonic::STREX : return "strex"; 109 | case Mnemonic::STREXB : return "strexb"; 110 | case Mnemonic::STREXH : return "strexh"; 111 | case Mnemonic::STRHT : return "strht"; 112 | case Mnemonic::STRT : return "strt"; 113 | case Mnemonic::SUBW : return "subw"; 114 | case Mnemonic::TBB : return "tbb"; 115 | case Mnemonic::TBH : return "tbh"; 116 | case Mnemonic::TEQ : return "teq"; 117 | case Mnemonic::UBFX : return "ubfx"; 118 | case Mnemonic::UDIV : return "udiv"; 119 | case Mnemonic::UMLAL : return "umlal"; 120 | case Mnemonic::UMULL : return "umull"; 121 | case Mnemonic::USAT : return "usat"; 122 | } 123 | 124 | return "(x)) + ">"; 125 | } 126 | 127 | std::ostream& operator<<(std::ostream& os, const Mnemonic& x) 128 | { 129 | return os << to_string(x); 130 | } 131 | 132 | bool has_narrow_encoding(const Mnemonic& x) 133 | { 134 | switch (x) 135 | { 136 | case Mnemonic::ADC: 137 | case Mnemonic::ADD: 138 | case Mnemonic::ADR: 139 | case Mnemonic::AND: 140 | case Mnemonic::ASR: 141 | case Mnemonic::B: 142 | case Mnemonic::BIC: 143 | case Mnemonic::BKPT: 144 | case Mnemonic::BLX: 145 | case Mnemonic::BX: 146 | case Mnemonic::CMN: 147 | case Mnemonic::CMP: 148 | case Mnemonic::CPS: 149 | case Mnemonic::EOR: 150 | case Mnemonic::LDM: 151 | case Mnemonic::LDR: 152 | case Mnemonic::LDRB: 153 | case Mnemonic::LDRH: 154 | case Mnemonic::LDRSB: 155 | case Mnemonic::LDRSH: 156 | case Mnemonic::LSL: 157 | case Mnemonic::LSR: 158 | case Mnemonic::MOV: 159 | case Mnemonic::MUL: 160 | case Mnemonic::MVN: 161 | case Mnemonic::NOP: 162 | case Mnemonic::ORR: 163 | case Mnemonic::POP: 164 | case Mnemonic::PUSH: 165 | case Mnemonic::REV16: 166 | case Mnemonic::REV: 167 | case Mnemonic::REVSH: 168 | case Mnemonic::ROR: 169 | case Mnemonic::RSB: 170 | case Mnemonic::SBC: 171 | case Mnemonic::SEV: 172 | case Mnemonic::STM: 173 | case Mnemonic::STR: 174 | case Mnemonic::STRB: 175 | case Mnemonic::STRH: 176 | case Mnemonic::SUB: 177 | case Mnemonic::SVC: 178 | case Mnemonic::SXTB: 179 | case Mnemonic::TST: 180 | case Mnemonic::UDF: 181 | case Mnemonic::UXTB: 182 | case Mnemonic::UXTH: 183 | case Mnemonic::WFE: 184 | case Mnemonic::WFI: 185 | case Mnemonic::YIELD: return true; 186 | default: return false; 187 | } 188 | } 189 | 190 | 191 | 192 | bool has_wide_encoding(const Mnemonic& x) 193 | { 194 | switch (x) 195 | { 196 | case Mnemonic::ADC: 197 | case Mnemonic::ADD: 198 | case Mnemonic::ADDW: 199 | case Mnemonic::ADR: 200 | case Mnemonic::AND: 201 | case Mnemonic::ASR: 202 | case Mnemonic::B: 203 | case Mnemonic::BFC: 204 | case Mnemonic::BFI: 205 | case Mnemonic::BIC: 206 | case Mnemonic::BL: 207 | case Mnemonic::CLREX: 208 | case Mnemonic::CLZ: 209 | case Mnemonic::CMN: 210 | case Mnemonic::CMP: 211 | case Mnemonic::CSDB: 212 | case Mnemonic::DBG: 213 | case Mnemonic::DMB: 214 | case Mnemonic::DSB: 215 | case Mnemonic::EOR: 216 | case Mnemonic::ISB: 217 | case Mnemonic::LDM: 218 | case Mnemonic::LDMDB: 219 | case Mnemonic::LDR: 220 | case Mnemonic::LDRB: 221 | case Mnemonic::LDRBT: 222 | case Mnemonic::LDRD: 223 | case Mnemonic::LDREX: 224 | case Mnemonic::LDREXB: 225 | case Mnemonic::LDREXH: 226 | case Mnemonic::LDRH: 227 | case Mnemonic::LDRHT: 228 | case Mnemonic::LDRSB: 229 | case Mnemonic::LDRSBT: 230 | case Mnemonic::LDRSH: 231 | case Mnemonic::LDRSHT: 232 | case Mnemonic::LDRT: 233 | case Mnemonic::LSL: 234 | case Mnemonic::LSR: 235 | case Mnemonic::MLA: 236 | case Mnemonic::MLS: 237 | case Mnemonic::MOV: 238 | case Mnemonic::MOVT: 239 | case Mnemonic::MOVW: 240 | case Mnemonic::MRS: 241 | case Mnemonic::MSR: 242 | case Mnemonic::MUL: 243 | case Mnemonic::MVN: 244 | case Mnemonic::NOP: 245 | case Mnemonic::ORN: 246 | case Mnemonic::ORR: 247 | case Mnemonic::PLD: 248 | case Mnemonic::PLI: 249 | case Mnemonic::POP: 250 | case Mnemonic::PSSBB: 251 | case Mnemonic::PUSH: 252 | case Mnemonic::RBIT: 253 | case Mnemonic::REV16: 254 | case Mnemonic::REV: 255 | case Mnemonic::REVSH: 256 | case Mnemonic::ROR: 257 | case Mnemonic::RRX: 258 | case Mnemonic::RSB: 259 | case Mnemonic::SBC: 260 | case Mnemonic::SBFX: 261 | case Mnemonic::SDIV: 262 | case Mnemonic::SEV: 263 | case Mnemonic::SMLAL: 264 | case Mnemonic::SMULL: 265 | case Mnemonic::SSAT: 266 | case Mnemonic::SSBB: 267 | case Mnemonic::STM: 268 | case Mnemonic::STMDB: 269 | case Mnemonic::STR: 270 | case Mnemonic::STRB: 271 | case Mnemonic::STRBT: 272 | case Mnemonic::STRD: 273 | case Mnemonic::STREX: 274 | case Mnemonic::STREXB: 275 | case Mnemonic::STREXH: 276 | case Mnemonic::STRH: 277 | case Mnemonic::STRHT: 278 | case Mnemonic::STRT: 279 | case Mnemonic::SUB: 280 | case Mnemonic::SUBW: 281 | case Mnemonic::SXTB: 282 | case Mnemonic::SXTH: 283 | case Mnemonic::TEQ: 284 | case Mnemonic::TST: 285 | case Mnemonic::UBFX: 286 | case Mnemonic::UDF: 287 | case Mnemonic::UDIV: 288 | case Mnemonic::UMLAL: 289 | case Mnemonic::UMULL: 290 | case Mnemonic::USAT: 291 | case Mnemonic::UXTB: 292 | case Mnemonic::UXTH: 293 | case Mnemonic::WFE: 294 | case Mnemonic::WFI: 295 | case Mnemonic::YIELD: return true; 296 | default: return false; 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/registers.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/registers.h" 2 | 3 | namespace mulator 4 | { 5 | 6 | std::string to_string(const Register& x) 7 | { 8 | if (x == SP) 9 | { 10 | return "sp"; 11 | } 12 | else if (x == LR) 13 | { 14 | return "lr"; 15 | } 16 | else if (x == PC) 17 | { 18 | return "pc"; 19 | } 20 | else if (x == PSR) 21 | { 22 | return "xpsr"; 23 | } 24 | 25 | else if (x == 9) // optional 26 | { 27 | return "sb"; 28 | } 29 | else if (x == 10) // optional 30 | { 31 | return "sl"; 32 | } 33 | else if (x == 11) // optional 34 | { 35 | return "fp"; 36 | } 37 | else if (x == 12) // optional 38 | { 39 | return "ip"; 40 | } 41 | 42 | else if (x >= 0 && x <= 12) 43 | { 44 | return "r" + std::to_string(x); 45 | } 46 | 47 | return "(x)) + ">"; 48 | } 49 | 50 | 51 | std::ostream& operator<<(std::ostream& os, const Register& x) 52 | { 53 | return os << to_string(x); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/return_codes.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/return_codes.h" 2 | 3 | namespace mulator 4 | { 5 | 6 | std::string to_string(const ReturnCode& x) 7 | { 8 | switch (x) 9 | { 10 | case ReturnCode::OK: return "OK"; 11 | case ReturnCode::INCOMPLETE_DATA: return "INCOMPLETE_DATA"; 12 | case ReturnCode::UNDEFINED: return "UNDEFINED"; 13 | case ReturnCode::UNPREDICTABLE: return "UNPREDICTABLE"; 14 | case ReturnCode::UNSUPPORTED: return "UNSUPPORTED"; 15 | case ReturnCode::UNEXPECTED_ENCODING: return "UNEXPECTED_ENCODING"; 16 | case ReturnCode::NOT_IMPLEMENTED: return "NOT_IMPLEMENTED"; 17 | case ReturnCode::UNKNOWN_INSTRUCTION: return "UNKNOWN_INSTRUCTION"; 18 | case ReturnCode::INVALID_IMMEDIATE: return "INVALID_IMMEDIATE"; 19 | 20 | case ReturnCode::INVALID_REGISTER: return "INVALID_REGISTER"; 21 | case ReturnCode::INVALID_ALIGNMENT: return "INVALID_ALIGNMENT"; 22 | case ReturnCode::INVALID_MEMORY_ACCESS: return "INVALID_MEMORY_ACCESS"; 23 | case ReturnCode::INVALID_SHIFT_ARGUMENT: return "INVALID_SHIFT_ARGUMENT"; 24 | case ReturnCode::HARD_FAULT: return "HARD_FAULT"; 25 | 26 | case ReturnCode::UNINITIALIZED: return "UNINITIALIZED"; 27 | case ReturnCode::MAX_INSTRUCTIONS_REACHED: return "MAX_INSTRUCTIONS_REACHED"; 28 | case ReturnCode::END_ADDRESS_REACHED: return "END_ADDRESS_REACHED"; 29 | case ReturnCode::STOP_EMULATION_CALLED: return "STOP_EMULATION_CALLED"; 30 | } 31 | 32 | return "(x)) + ">"; 33 | } 34 | 35 | std::ostream& operator<<(std::ostream& os, const ReturnCode& x) 36 | { 37 | return os << to_string(x); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /subprojects/m-ulator/src/shift_types.cpp: -------------------------------------------------------------------------------- 1 | #include "m-ulator/shift_types.h" 2 | 3 | namespace mulator 4 | { 5 | 6 | std::string to_string(const ShiftType& x) 7 | { 8 | switch (x) 9 | { 10 | case ShiftType::LSL : return "lsl"; 11 | case ShiftType::LSR : return "lsr"; 12 | case ShiftType::ASR : return "asr"; 13 | case ShiftType::ROR : return "ror"; 14 | case ShiftType::RRX : return "rrx"; 15 | } 16 | 17 | return "(x)) + ">"; 18 | } 19 | 20 | 21 | std::ostream& operator<<(std::ostream& os, const ShiftType& x) 22 | { 23 | return os << to_string(x); 24 | } 25 | } 26 | --------------------------------------------------------------------------------