diff --git a/Analyzer.py b/Analyzer.py index 3a30a3e..d5acdfe 100644 --- a/Analyzer.py +++ b/Analyzer.py @@ -19,13 +19,17 @@ def __init__(self, filename: str, parsed_data: Parser, total_lines: int, out_dir """ self.filename = filename self.parsed_data = parsed_data - + # ! Outdated branch pattern detection # self.branchV1_detector = BranchV1(filename, total_lines, directory_name) - self.branchV2_detector = BranchV2(filename, parsed_data.arch.name, parsed_data.opt, directory_name, sensitivity=4) + self.branchV2_detector = BranchV2(filename, parsed_data.arch.name, parsed_data.opt, directory_name, directory_name, sensitivity=4) self.constant_detector = ConstantCoding(filename, parsed_data.arch.name, parsed_data.opt, total_lines, directory_name, sensitivity=4) self.loop_detector = LoopCheck(filename, parsed_data.arch.name, parsed_data.opt, total_lines, directory_name) self.bypass_detector = Bypass(filename, parsed_data.arch.name, parsed_data.opt, total_lines, directory_name) + # TODO: Instantiate a list of detectors, then iterate on detectors for other functions + # Doing the above avoids the below condition on subsequent function calls + if parsed_data.arch.name == "x86": + self.bypass_detector = None # if self.create_directory(console): self.static_analysis() @@ -54,7 +58,8 @@ def static_analysis(self) -> None: self.branchV2_detector.analysis(line) self.constant_detector.analysis(line) self.loop_detector.analysis(line) - self.bypass_detector.analysis(line) + if self.bypass_detector: + self.bypass_detector.analysis(line) elif type(line) == Location: self.constant_detector.analysis(line) self.loop_detector.analysis(line) @@ -80,8 +85,9 @@ def print_analysis_results(self, console: Console) -> None: console.print(f"[Pattern] [bright_yellow]LoopCheck[/bright_yellow]\n") self.loop_detector.print_results(console) - console.print(f"[Pattern] [bright_yellow]Bypass[/bright_yellow]\n") - self.bypass_detector.print_results(console) + if self.bypass_detector: + console.print(f"[Pattern] [bright_yellow]Bypass[/bright_yellow]\n") + self.bypass_detector.print_results(console) def save_and_print_analysis_results(self, console: Console) -> None: """ @@ -101,14 +107,18 @@ def save_and_print_analysis_results(self, console: Console) -> None: self.loop_detector.save_and_print_results(console) console.print(f"Saved") - console.print(f"Saving Bypass...") - self.bypass_detector.save_and_print_results(console) - console.print(f"Saved") + if self.bypass_detector: + console.print(f"Saving Bypass...") + self.bypass_detector.save_and_print_results(console) + console.print(f"Saved") def print_total_vulnerable_lines(self, console: Console) -> None: # total number of vulnerable lines total_vulnerable_lines = (len(self.branchV2_detector.vulnerable_instructions) + len(self.constant_detector.vulnerable_instructions) - + len(self.loop_detector.vulnerable_instructions) + len(self.bypass_detector.vulnerable_set)) + + len(self.loop_detector.vulnerable_instructions)) + + if self.bypass_detector: + total_vulnerable_lines += len(self.bypass_detector.vulnerable_set) print(f"Total number of vulnerable lines: {total_vulnerable_lines}") # total number of branch faults @@ -124,9 +134,15 @@ def print_total_vulnerable_lines(self, console: Console) -> None: console.print(f"\tTotal number of Loop Check vulnerabilities: {total_loop_faults}") # total number of bypass faults - total_bypass_faults = len(self.bypass_detector.vulnerable_set) - console.print(f"\tTotal number of Bypass vulnerabilities: {total_bypass_faults}") + if self.bypass_detector: + total_bypass_faults = len(self.bypass_detector.vulnerable_set) + console.print(f"\tTotal number of Bypass vulnerabilities: {total_bypass_faults}") def get_total_vulnerable_lines(self) -> int: - return (len(self.branchV2_detector.vulnerable_instructions) + len(self.constant_detector.vulnerable_instructions) - + len(self.loop_detector.vulnerable_instructions) + len(self.bypass_detector.vulnerable_set)) \ No newline at end of file + total_lines = (len(self.branchV2_detector.vulnerable_instructions) + len(self.constant_detector.vulnerable_instructions) + + len(self.loop_detector.vulnerable_instructions)) + + if self.bypass_detector: + total_lines += len(self.bypass_detector.vulnerable_set) + + return total_lines \ No newline at end of file diff --git a/Parser.py b/Parser.py index 1e8c45e..17dcac6 100755 --- a/Parser.py +++ b/Parser.py @@ -1,7 +1,10 @@ import re +from subprocess import check_output +from capstone import * from rich.console import Console +from elftools.elf.elffile import ELFFile -from constants import optimization_levels +from constants import Architectures, BinaryModes, optimization_levels class Register(): def __init__(self, name: str): @@ -97,7 +100,10 @@ def set_arguments(self, value: str): if indicator != -1: # The string has two args # The +2 here is because of the ', #' that will be at index "indicator" - args["offset"] = IntegerLiteral(int(value[value.index('#')+1:])) + try: + args["offset"] = IntegerLiteral(int(value[value.index('#')+1:])) + except ValueError: + args["offset"] = IntegerLiteral(int(value[value.index('#')+1:], 16)) return args @@ -157,15 +163,66 @@ def __init__(self, file: str, console: Console): self.total_lines: int = 0 self.arch = Architecture(line=None, instruction=None) - self.opt : str + self.opt : str = "O0" + self.is_binary = False self.parseFile(console) def parseFile(self, console: Console): console.log(f"Reading file: {self.filename}") - with open(self.filename) as f: - lines: list[str] = f.readlines() + lines: list[str] = [] + + # Source file parsing + if self.__is_file_source(self.filename): + with open(self.filename, mode="r") as source_file: + lines = source_file.readlines() + else: + self.is_binary = True + with open(self.filename, mode="rb") as binary_file: + elf_file = ELFFile(binary_file) + text_section = elf_file.get_section_by_name(".text") + data_section = elf_file.get_section_by_name(".data") + rodata_section = elf_file.get_section_by_name(".rodata") + # Sections print for debugging + for section in elf_file.iter_sections(): + print(hex(section["sh_addr"]), section.name) + + symtab = elf_file.get_section_by_name(".symtab") + for i in range(5): + print("symbol #{} - {}".format(i, symtab.get_symbol(i).name)) + + main_offset = symtab.get_symbol_by_name("main")[0].entry["st_value"] + main_size = symtab.get_symbol_by_name("main")[0].entry["st_value"] + + # Code + ops = text_section.data() + addr = text_section["sh_addr"] + + # Global Vars + dops = data_section.data() + daddr = data_section["sh_addr"] + + # Strings + rdops = rodata_section.data() + rdaddr = rodata_section["sh_addr"] + + # Determine architecture and mode for Capstone + file_target_system = self.__determine_binary_architecture(elf_file) + self.arch.architecture_found(file_target_system.name.lower()) + file_mode = None + if file_target_system == Architectures.ARM: + file_mode = CS_MODE_ARM + else: + file_mode = self.__determine_binary_mode(elf_file).value + + md = Cs(file_target_system.value, file_mode) + # Dissassemble and store lines + lines = [] + for i in md.disasm(code=ops, offset=addr): + # lines.append("{} {}".format(i.mnemonic, i.op_str)) + print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str)) console.log(f"[green]File read successfully![/green]\n") + console.log(f"Processing assembly data:") self.isolateSections(lines) @@ -175,7 +232,9 @@ def isolateSections(self, lines: list[str]): program = [] line_number = 1 - global attribute_1, attribute_2 # For determining optimization level + # For determining optimization level + attribute_1 = None + attribute_2 = None for line in lines: s = line.strip() # Line is a location @@ -195,10 +254,10 @@ def isolateSections(self, lines: list[str]): elif s.startswith(".ident"): break # if line starts with .eabi_attribute 30, we get 1st attribute for optimization level - elif s.startswith(".eabi_attribute 30"): + elif s.startswith(".eabi_attribute 30") and not self.is_binary: attribute_1 = self.get_eabi_attribute(s) # if line starts with .eabi_attribute 23, we get 2nd attribute for optimization level - elif s.startswith(".eabi_attribute 23"): + elif s.startswith(".eabi_attribute 23") and not self.is_binary: attribute_2 = self.get_eabi_attribute(s) # Line is an instruction else: @@ -241,7 +300,11 @@ def parseArguments(self, line: str, line_number: int): # Check if a number if self.isNumber(arg): - arguments.append(IntegerLiteral(int(arg[1:] if arg.startswith('#') or arg.startswith('$') else arg))) + try: + arguments.append(IntegerLiteral(int(arg[1:] if arg.startswith('#') or arg.startswith('$') else arg))) + except ValueError: + arguments.append(IntegerLiteral(int(arg[1:] if arg.startswith('#') or arg.startswith('$') else arg, 16))) + # ! This notation can also be used in ARM for LDR elif re.search(r"\.long|\.value", instruction) and self.isNumber(arg): # in case its a global variable @@ -282,3 +345,22 @@ def get_eabi_attribute(self, s: str): return int(tag_match.group(2)) else: return None + + def __is_file_source(self, file_path: str): + """Uses `file` command on the provided file path and determines its type from the command output + + Args: + file_path (str): Path to the file being analyzed + """ + file_output = check_output(["file", file_path]).decode() + + if re.match(r".*ASCII\stext\s.*", file_output): + # File is source + return True + return False + + def __determine_binary_mode(self, elf_file: ELFFile): + return BinaryModes.from_elf_class(elf_file.elfclass) + + def __determine_binary_architecture(self, elf_file: ELFFile): + return Architectures.from_elf_machine(elf_file.header.get("e_machine", "")) diff --git a/README.md b/README.md index 9e5010b..84205bd 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ The entrypoint to the program, `main.py`, serves as a central location to utiliz Main parsing module. Intended to parse assembly code. It combs through the source code and creates objects depending on what it encounters. Once the source code is transformed into a list of objects, it can be more easily worked with to discover patterns. It uses Python’s type hints to be more transparent. +> If passed a compiled binary, the parser uses [capstone](https://www.capstone-engine.org/) in combination with [pyefltools](https://github.com/eliben/pyelftools) to dissassemble and parse the binary. + `Locations` are spots in the code that can be referenced and jumped to. Example: .LC0 and main. `IntegerLiterals` are integers. In 32-bit syntax, these are prefaced with a “#” diff --git a/constants/Architectures.py b/constants/Architectures.py new file mode 100644 index 0000000..8c6fb7b --- /dev/null +++ b/constants/Architectures.py @@ -0,0 +1,16 @@ +from enum import Enum + + +class Architectures(Enum): + ARM = 0 + X86 = 3 + UNKNOWN = 9 + + @staticmethod + def from_elf_machine(elf_machine: str): + if "ARM" in elf_machine: + return Architectures.ARM + elif "X86" in elf_machine: + return Architectures.X86 + else: + return Architectures.UNKNOWN \ No newline at end of file diff --git a/constants/BinaryModes.py b/constants/BinaryModes.py new file mode 100644 index 0000000..04646cc --- /dev/null +++ b/constants/BinaryModes.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class BinaryModes(Enum): + # NOTE: These values match their Capstone Modes (CS_MODE) counterparts + B32 = 4 + B64 = 8 + UNKNOWN = 9 + + @staticmethod + def from_elf_class(elf_class: int): + if elf_class == 32: + return BinaryModes.B32 + elif elf_class == 64: + return BinaryModes.B64 + else: + return BinaryModes.UNKNOWN \ No newline at end of file diff --git a/constants/__init__.py b/constants/__init__.py index ff55369..a2fd2db 100644 --- a/constants/__init__.py +++ b/constants/__init__.py @@ -1,2 +1,4 @@ from .patterns import * -from .trivialValues import * \ No newline at end of file +from .trivialValues import * +from .Architectures import * +from .BinaryModes import * \ No newline at end of file diff --git a/constants/patterns.py b/constants/patterns.py index 5179a8a..3315456 100644 --- a/constants/patterns.py +++ b/constants/patterns.py @@ -1,8 +1,9 @@ pattern_list = { "x86": { - "branch": ['cmpl', ['jne', 'je', 'jnz', 'jz']], #cmp?? + "branch": [['cmpl'], ['jne', 'je', 'jnz', 'jz'], []], #cmp?? "constant_coding": ['movl', 'movq', 'movw', '.value', ".long"], "loop_check": ['cmpl', 'cmpl', 'j'], #cmpb, cmp?? + "bypass": [[], []] }, "arm": { "branch": [['cmp', 'subs', 'rsbs'], ['beq', 'bne', 'bcs', 'bhs', 'bcc', 'blo', 'bmi', 'bpl', 'bvs', 'bvc', 'bhi', 'bls', 'bge', diff --git a/docs/developer_notes/architecture_and_compilers.md b/docs/developer_notes/architecture_and_compilers.md index 3b0a98b..c6323ec 100644 --- a/docs/developer_notes/architecture_and_compilers.md +++ b/docs/developer_notes/architecture_and_compilers.md @@ -6,6 +6,7 @@ - [Architectures](#architectures) - [Cross-compiling](#cross-compiling) - [ARM](#arm) + - [RISC-V](#risc-v) - [x86](#x86) - [Tool Chain Conventions](#tool-chain-conventions) @@ -44,6 +45,10 @@ In order to compile the dataset, scripts will be provided in order to have both arm-none-eabi-gcc -S -o filename.s /path/to/filename.c ``` +```bash +arm-none-eabi-gcc --specs=nosys.specs -o filename /path/to/filename.c +``` + ### RISC-V The RISC-V GCC toolchain and its installation instructions can be found at this [link](https://github.com/riscv-collab/riscv-gnu-toolchain). Once you have installed the toolchain successfully, you can create Assembly binaries with the following command: ```bash diff --git a/requirements.txt b/requirements.txt index 5b2ac54..5bc2da2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ capstone -rich \ No newline at end of file +setuptools +rich +pyelftools \ No newline at end of file diff --git a/utils/patterns/PatternBase.py b/utils/patterns/PatternBase.py new file mode 100644 index 0000000..ffc2d67 --- /dev/null +++ b/utils/patterns/PatternBase.py @@ -0,0 +1,163 @@ +#-------------------------- +# PatternBase +#-------------------------- +# PatternBase Class acts as a base for all patterns, whicb allows for generics and virtual methods + +from os import path +from datetime import datetime +from typing import List +from rich.table import Table +from rich.console import Console + +from Parser import Instruction +from constants import pattern_list, trivial_values + +class PatternBase: + + @property + def name(self): + return self._name + + @property + def filename(self): + return self._filename + + @property + def total_lines(self): + return self._total_lines + + @property + def directory_name(self): + return self._directory_name + + @property + def architecture(self): + return self._architecture + + @property + def optimization(self): + return self._optimization + + @property + def sensitivity(self): + return self._sensitivity + + @property + def pattern(self): + return self._pattern + + @property + def trivial_values(self): + return self._trivial_values + + @property + def vulnerable_instructions(self): + return self._vulnerable_instructions + + + + def __init__(self, filename: str, pattern_name: str, architecture: str, optimization: str, total_lines: int, directory_name: str, sensitivity: int = 3) -> None: + """ + Represents a pattern object used for fault.pattern analysis/detection. + + Attributes: + - pattern (List[str | List[str]]): List of patterns to match instructions. + - vulnerable_instructions (List[List[Instruction]]): List of vulnerable instructions. + - current_vulnerable (List[Instruction]): List of instructions currently identified as vulnerable. + - is_vulnerable (bool): Flag indicating if a pattern vulnerability is detected. + """ + self._name = pattern_name + self._pattern: List[str | List[str]] = pattern_list[architecture][pattern_name] + self._trivial_values = trivial_values["integers"] + self._vulnerable_instructions: List[List[Instruction]] = [] + self.current_vulnerable: List[Instruction] = [] + self.is_vulnerable = False + + self._filename = filename + self._total_lines = total_lines + self._directory_name = directory_name + self._architecture = architecture + self._optimization = optimization + + # Hamming weight sensitivity compared to zero + self._sensitivity = sensitivity + + + def analysis(self, line: Instruction) -> None: + """ + Analyzes the given instruction line for vulnerabilities. + + Args: + - line (Instruction): The instruction line to analyze. + """ + pass + + + def contains_trivial_numeric_value(self, value: int) -> bool: + """ + Checks if the given value is a trivial numeric value. + + Args: + - value (int): The value to check. + + Returns: + - bool: True if the value is trivial, False otherwise. + """ + if value in self._trivial_values: + return True + return False + + def print_results(self, console: Console) -> None: + """ + Prints the results of the analysis. + """ + if self.is_vulnerable: + # Found Vulnerability + console.print("[bright_red]VULNERABILITY DETECTED[/bright_red]\n") + + # Build Table + table = Table(title="{} Vulnerabilities".format(self.name.capitalize())) + + table.add_column(header="Line #", justify="center") + table.add_column(header="Instructions") + + for vulns in self.vulnerable_instructions: + table.add_section() + for line in vulns: + table.add_row(f"{line.line_number}", f"{line.name} {', '.join( "\\" + str(arguments) if str(arguments)[0] == "[" else str(arguments) for arguments in line.arguments) if type(line) == Instruction else ''}") + + console.print(table) + console.print("\n") + else: + console.print("[green]No {} vulnerability detected![/green]".format(self.name.capitalize())) + + def save_and_print_results(self, console: Console) -> None: + """ + Prints the results of the analysis and stores to a file. + """ + # Call Print + self.print_results(console) + + # File Header + header = f"Analyzed file: {self.filename}\n" + header += f"{datetime.now().ctime()}\n" + header += f"Lines Analyzed: {self.total_lines}\n" + header += f"Vulnerable Lines: {sum(len(sub_arr) for sub_arr in self.vulnerable_instructions)}\n\n" + header += "{} Results:\n\n".format(self.name.capitalize()) + + with open(path.join(self.directory_name, "{}.txt".format(self.name.capitalize())), 'w') as file: + file.write(header) + + if self.is_vulnerable: + # Found Vulnerability + file.write("{} VULNERABILITY DETECTED\n\n".format(self.name.upper())) + + file.write("[Line #] [Opcode]\n") + + for vulns in self.vulnerable_instructions: + for line in vulns: + file.write(f"{line.line_number} {line.name} {', '.join(str(arguments) for arguments in line.arguments)}\n") + file.write("\n") + + else: + file.write("SECURED FILE - NO {} VULNERABILITIES".format(self.name.upper())) \ No newline at end of file diff --git a/utils/patterns/__init__.py b/utils/patterns/__init__.py index 56b0ade..f3480fb 100644 --- a/utils/patterns/__init__.py +++ b/utils/patterns/__init__.py @@ -1,4 +1,5 @@ from .branch_detection import * from .constant_detection import * from .loop_detection import * -from .bypass_detection import * \ No newline at end of file +from .bypass_detection import * +from .PatternBase import * \ No newline at end of file diff --git a/utils/patterns/branch_detection.py b/utils/patterns/branch_detection.py index a3e3dc0..a9e3fd8 100644 --- a/utils/patterns/branch_detection.py +++ b/utils/patterns/branch_detection.py @@ -1,40 +1,14 @@ from os import path from datetime import datetime -from typing import List from rich.table import Table from rich.console import Console from Parser import Instruction, Location, IntegerLiteral, Register -from constants import pattern_list, trivial_values +from utils.patterns.PatternBase import PatternBase -class BranchV2(): - # def __init__(self, filename: str, architecture: str, optimization: str, total_lines: int, directory_name: str, sensitivity: int) -> None: - def __init__(self, filename: str, architecture: str, optimization: str, directory_name: str, sensitivity: int) -> None: - """ - Represents a branch object used for fault.branch analysis/detection. - - Attributes: - - trivial_values (List[int]): List of trivial integer values. - - pattern (List[str | List[str]]): List of patterns to match instructions. - - vulnerable_instructions (List[List[Instruction]]): List of vulnerable instructions. - - current_vulnerable (List[Instruction]): List of instructions currently identified as vulnerable. - - is_vulnerable (bool): Flag indicating if a branch vulnerability is detected. - """ - self.trivial_values: List[int] = trivial_values["integers"] - self.pattern: List[str | List[str]] = pattern_list[architecture]["branch"] - self.vulnerable_instructions: List[List[Instruction]] = [] - self.current_vulnerable: List[Instruction] = [] - self.is_vulnerable = False - self.is_between_relevant_code = False - - self.filename = filename - # self.total_lines = total_lines - self.directory_name = directory_name - self.architecture = architecture - self.optimization = optimization - - # Hamming weight sensitivity compared to zero - self.sensitivity = sensitivity +class BranchV2(PatternBase): + def __init__(self, filename: str, architecture: str, optimization: str, total_lines: int, directory_name: str, sensitivity: int) -> None: + super().__init__(filename, "branch", architecture, optimization, total_lines, directory_name, sensitivity) def analysis(self, line: Instruction) -> None: """ @@ -131,7 +105,8 @@ def print_results(self, console: Console) -> None: for vulns in self.vulnerable_instructions: table.add_section() for line in vulns: - table.add_row(f"{line.line_number}", f"{line.name} {', '.join(str(arguments) for arguments in line.arguments)}") + table.add_row(f"{line.line_number}", + f"{line.name} {', '.join(str(arguments) for arguments in line.arguments)}") console.print(table) console.print("\n") diff --git a/utils/patterns/bypass_detection.py b/utils/patterns/bypass_detection.py index 24d081f..676c495 100644 --- a/utils/patterns/bypass_detection.py +++ b/utils/patterns/bypass_detection.py @@ -8,9 +8,10 @@ from Parser import Instruction from constants import pattern_list +from utils.patterns.PatternBase import PatternBase -class Bypass(): +class Bypass(PatternBase): # The following are the states that the Bypass object can be in depending on the current line of code being analyzed. class DetectionState(Enum): NO_PATTERN = 0 # No pattern has been detected yet. @@ -22,11 +23,7 @@ class DetectionState(Enum): INSECURE_COMPARE = 6 # compare statement after insecure move def __init__(self, filename: str, architecture: str, optimization: str, total_lines: int, directory_name: str): - self.filename = filename - self.total_lines = total_lines - self.directory_name = directory_name - self.architecture = architecture - self.optimization = optimization + super().__init__(filename, "bypass", architecture, optimization, total_lines, directory_name) ''' Represents the Bypass object used for fault.Bypass analysis/detection. @@ -142,7 +139,7 @@ def print_results(self, console: Console) -> None: table.add_section() for line in set: table.add_row(f"{line.line_number}", - f"{line.name} {', '.join(str(arguments) for arguments in line.arguments) if type(line) == Instruction else ''}") + f"{line.name} {', '.join( "\\" + str(arguments) if str(arguments)[0] == "[" else str(arguments) for arguments in line.arguments) if type(line) == Instruction else ''}") console.print(table) console.print("\n") diff --git a/utils/patterns/constant_detection.py b/utils/patterns/constant_detection.py index da5eeda..b2f256c 100644 --- a/utils/patterns/constant_detection.py +++ b/utils/patterns/constant_detection.py @@ -5,27 +5,15 @@ from rich.console import Console from Parser import Instruction, Location, IntegerLiteral, Register -from constants import pattern_list, trivial_values +from utils.patterns.PatternBase import PatternBase -class ConstantCoding(): +class ConstantCoding(PatternBase): def __init__(self, filename: str, architecture: str, optimization: str, total_lines: int, directory_name: str, sensitivity: int) -> None: - self.filename = filename - self.total_lines = total_lines - self.directory_name = directory_name - self.architecture = architecture - self.optimization = optimization - - # Pattern - Stack Storage - # movl #, -#(%rsp) - self.pattern: List[str] = pattern_list[architecture]["constant_coding"] - self.trivial_values: List[int] = trivial_values["integers"] + super().__init__(filename, "constant_coding", architecture, optimization, total_lines, directory_name) + self.lineStack : List[Instruction] = [] - self.vulnerable_instructions: List[List[Instruction]] = [] - self.is_vulnerable = False self.location_in_between = False # to mark if there is a location in between a pattern - # Hamming weight sensitivity compared to zero - self.sensitivity = sensitivity # TODO: Rewrite detection by phasing out x86 def analysis(self, line: Union[Instruction, Location]) -> None: @@ -125,7 +113,6 @@ def analysis(self, line: Union[Instruction, Location]) -> None: elif len(self.lineStack) >= 2: self.lineStack.clear() - def print_results(self, console: Console) -> None: """ Just prints the results of the analysis. @@ -142,7 +129,7 @@ def print_results(self, console: Console) -> None: for lines in self.vulnerable_instructions: table.add_section() for line in lines: - table.add_row(f"{line.line_number}", f"{line.name} {', '.join(str(arguments) for arguments in line.arguments) if type(line) == Instruction else ''}") + table.add_row(f"{line.line_number}", f"{line.name} {', '.join( "\\" + str(arguments) if str(arguments)[0] == "[" else str(arguments) for arguments in line.arguments) if type(line) == Instruction else ''}") console.print(table) console.print("\n") @@ -177,4 +164,4 @@ def save_and_print_results(self, console: Console) -> None: file.write("\n") else: - file.write(f"SECURED FILE - NO BRANCH VULNERABILITIES") \ No newline at end of file + file.write(f"SECURED FILE - NO BRANCH VULNERABILITIES") diff --git a/utils/patterns/loop_detection.py b/utils/patterns/loop_detection.py index 1f8b3be..6adb20d 100644 --- a/utils/patterns/loop_detection.py +++ b/utils/patterns/loop_detection.py @@ -4,11 +4,13 @@ from rich.table import Table from rich.console import Console -from Parser import Address, Instruction, Location -from constants import pattern_list, branch_opposites +from Parser import Instruction, Location +from constants import branch_opposites +from utils.patterns.PatternBase import PatternBase -class LoopCheck(): +class LoopCheck(PatternBase): def __init__(self, filename: str, architecture: str, optimization: str, total_lines: int, directory_name: str) -> None: + super().__init__(filename, "loop_check", architecture, optimization, total_lines, directory_name) """ Represents an object used for fault.LOOP analysis/detection. @@ -20,19 +22,12 @@ def __init__(self, filename: str, architecture: str, optimization: str, total_li - is_vulnerable (bool): Flag indicating if a loop check vulnerability is detected. """ self.locations: List[str] = [] - self.pattern: List[str | List[str]] = pattern_list[architecture]["loop_check"] - self.vulnerable_instructions: List[List[Instruction]] = [] self.suspected_vulnerable: List[Instruction] = [] self.expected_secured: List[Instruction] = [] self.secured_pattern: List[Instruction] = [] self.is_vulnerable = False self.is_between_relevant_code = False - self.filename = filename - self.total_lines = total_lines - self.directory_name = directory_name - self.architecture = architecture - self.optimization = optimization def analysis(self, line: Instruction | Location) -> None: """ @@ -209,15 +204,8 @@ def print_results(self, console: Console) -> None: for vulns in self.vulnerable_instructions: table.add_section() for line in vulns: - arguments: List[str] = [] - for arg in line.arguments: - # ! For address, anything surrounded with "[]", we need to add a backslash \ to escape the tag - # This is mainly to let "Rich", the library we use to print tables, - # To leave the content with brackets unprocessed, or not rendered. - if type(arg) == Address: - arguments.append(f"\{arg.value}") - else: arguments.append(str(arg)) - table.add_row(f"{line.line_number}", f"{line.name} {', '.join(str(argument) for argument in arguments)}") + table.add_row(f"{line.line_number}", + f"{line.name} {', '.join( "\\" + str(arguments) if str(arguments)[0] == "[" else str(arguments) for arguments in line.arguments) if type(line) == Instruction else ''}") console.print(table) console.print("\n") @@ -269,5 +257,3 @@ def is_branch_instruction(self, instruction_name: str) -> bool: # Check if the instruction is a branch instruction return instruction_name in branch_instructions or instruction_name.startswith("B") - - \ No newline at end of file