diff --git a/plugins/angrimportfinder/README.md b/plugins/angrimportfinder/README.md index 07885393..fe19794a 100644 --- a/plugins/angrimportfinder/README.md +++ b/plugins/angrimportfinder/README.md @@ -1,7 +1,7 @@ # Imported and Exported function name extractor Plugin for SBOM Surfactant A plugin for Surfactant that uses the [angr](https://github.com/angr/angr) -Python library to extract the imported function names from ELF and PE files. +Python library to extract the imported and exported function names from ELF and PE files. ## Quickstart diff --git a/plugins/reachability/README.md b/plugins/reachability/README.md new file mode 100644 index 00000000..30be63c3 --- /dev/null +++ b/plugins/reachability/README.md @@ -0,0 +1,42 @@ +# Import Reachability Plugin for SBOM Surfactant + +A plugin for Surfactant that checks the reachability of imported functions within exported functions to narrow down reachable code and reduse the amount of deadcode analysts are having to view. + +## Quickstart + +To install this plugin within the same virtual environment as Surfactant, use the command `pip install .`. + +For developers modifying the plugin, the editable installation can be achieved with `pip install -e .`. + +After installing the plugin, run Surfactant to generate an SBOM as usual and entries for ELF +and PE files will contain a metadata object with the information that checksec.py was able +to get about security related features. + +After the plugin installation, run Surfactant as you normally would to create an SBOM. For binary files analyzed by this plugin, additional JSON files will be generated containing vulnerability data extracted from the binaries. If there are duplicate hashed files, the extractor will check if they have the exported functions entries and skip remaking the output file if so. + +Example: +Output Filename: `reachability.json` + +```json +{ + "filename": { + "exp_func": { + "library": [ + "imp_func1", + "imp_func2" + ] + } + } +} +``` + +The plugin's functionality can be toggled via Surfactant's plugin management features, using the plugin name `surfactantplugin_reachability.py` as defined in the `pyproject.toml` under the `project.entry-points."surfactant"` section. + +## Uninstalling + +Remove the plugin from your environment with `pip uninstall surfactantplugin_reachability`. + +## Important Licensing Information +Main Project License (Surfactant): MIT License. + +Plugin License: MIT License, but it includes and uses cve-bin-tool, which is GPL-3.0 licensed. diff --git a/plugins/reachability/pyproject.toml b/plugins/reachability/pyproject.toml new file mode 100644 index 00000000..9561380a --- /dev/null +++ b/plugins/reachability/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "surfactantplugin_reachability" +authors = [ + {name = "Seth Bredbenner", email = "bredbenner1@llnl.gov"}, +] +description = "Surfactant plugin for running grype on files" +readme = "README.md" +requires-python = ">=3.8" +keywords = ["surfactant"] +license = {text = "MIT License"} +classifiers = [ + "Programming Language :: Python :: 3", + "Environment :: Console", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "License :: OSI Approved :: MIT License", +] +dependencies = [ + "angr", + "surfactant", +] +dynamic = ["version"] + +[project.entry-points."surfactant"] +"surfactantplugin_reachability" = "surfactantplugin_reachability" + +[tool.setuptools] +py-modules=["surfactantplugin_reachability"] diff --git a/plugins/reachability/surfactantplugin_reachability.py b/plugins/reachability/surfactantplugin_reachability.py new file mode 100644 index 00000000..03f37a0f --- /dev/null +++ b/plugins/reachability/surfactantplugin_reachability.py @@ -0,0 +1,87 @@ +# Copyright 2023 Lawrence Livermore National Security, LLC +# See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: MIT +from pathlib import Path + +import angr +from cle import CLECompatibilityError +from loguru import logger + +import surfactant.plugin +from surfactant.sbomtypes import SBOM, Software + + +@surfactant.plugin.hookimpl(specname="extract_file_info") +# extract_strings(sbom: SBOM, software: Software, filename: str, filetype: str): +# def angrimport_finder(filename: str, filetype: str, filehash: str): +def reachability(sbom: SBOM, software: Software, filename: str, filetype: str): + """ + :param sbom(SBOM): The SBOM that the software entry/file is being added to. Can be used to add observations or analysis data. + :param software(Software): The software entry associated with the file to extract information from. + :param filename (str): The full path to the file to extract information from. + :param filetype (str): File type information based on magic bytes. + """ + + # Only parsing executable files + if filetype not in ["ELF", "PE"]: + pass + filename = Path(filename) + + database = {} + + try: + if not filename.exists(): + raise FileNotFoundError(f"No such file: '{filename}'") + # Add your extraction code here. + # Create an angr project + project = angr.Project(filename, load_options={"auto_load_libs": True}) + + # library dependencies {import: library} + lookup = {} + for obj in project.loader.main_object.imports.keys(): + library = project.loader.find_symbol(obj) + if library is None: + continue + lookup[obj] = library.owner.provides + + # recreates our angr project without the libraries loaded to save on time + project = angr.Project(filename, load_options={"auto_load_libs": False}) + + # holds every export address error is here + exports = [ + func.rebased_addr for func in project.loader.main_object.symbols if func.is_export + ] # _exports is only available for PE files + + cfg = project.analyses.CFGFast( + start_at_entry=False, force_smart_scan=False, function_starts=exports + ) + + # go through every exported function + for exp_addr in exports: + exp_name = cfg.functions.get(exp_addr) + if exp_name is None: + continue + database[exp_name.name] = {} + exp_name = exp_name.name + + # goes through every function that is reachable from exported function + for imported_address in cfg.functions.callgraph.successors(exp_addr): + imported_function = cfg.functions.get(imported_address) + + # checks if the function is imported + if imported_function.name in project.loader.main_object.imports.keys(): + library = lookup[imported_function.name] + + if library not in database[exp_name].keys(): + database[exp_name][library] = [] + + # adds our reachable imported function as a dependency + if imported_function.name not in database[exp_name][library]: + database[exp_name][library].append(imported_function.name) + + return {"export_fn_reachability": database} + + except CLECompatibilityError as e: + logger.info(f"Angr Error {filename} {e}") + return None