├── .gitignore ├── Criteria.md ├── LICENSE ├── README.md ├── samples ├── CARVE │ ├── bftpd │ │ ├── Aggressive │ │ │ └── bftpd │ │ ├── Conservative │ │ │ └── bftpd │ │ ├── Moderate │ │ │ └── bftpd │ │ └── Original │ │ │ └── bftpd │ ├── libcurl │ │ ├── Aggressive │ │ │ └── libcurl.so.4.5.0 │ │ ├── Case Study │ │ │ └── libcurl.so.4.5.0 │ │ ├── Conservative │ │ │ └── libcurl.so.4.5.0 │ │ ├── Moderate │ │ │ └── libcurl.so.4.5.0 │ │ └── Original │ │ │ └── libcurl.so.4.5.0 │ └── libmodbus │ │ ├── Aggressive │ │ └── libmodbus.so.5.1.0 │ │ ├── Conservative │ │ └── libmodbus.so.5.1.0 │ │ ├── Moderate │ │ └── libmodbus.so.5.1.0 │ │ └── Original │ │ └── libmodbus.so.5.1.0 └── CHISEL │ ├── bzip │ ├── bzip2-1.0.5.origin │ └── bzip2-1.0.5.reduced │ ├── chown │ ├── chown-8.2.origin │ └── chown-8.2.reduced │ ├── date │ ├── date-8.21.origin │ └── date-8.21.reduced │ ├── grep │ ├── grep-2.19.origin │ └── grep-2.19.reduced │ ├── gzip │ ├── gzip-1.2.4.origin │ └── gzip-1.2.4.reduced │ ├── mkdir │ ├── mkdir-5.2.1.origin │ └── mkdir-5.2.1.reduced │ ├── rm │ ├── rm-8.4.origin │ └── rm-8.4.reduced │ ├── tar │ ├── tar-1.14.origin │ └── tar-1.14.reduced │ └── uniq │ ├── uniq-8.16.origin │ └── uniq-8.16.reduced └── src ├── GSA.py ├── __init__.py ├── static_analyzer ├── Gadget.py ├── GadgetSet.py ├── GadgetStats.py ├── Instruction.py └── __init__.py └── utility.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /Criteria.md: -------------------------------------------------------------------------------- 1 | # GSA Gadget Criteria Reference 2 | This file contains reference information on the criteria GSA uses to (1) eliminate gadgets from consideration in the set and (2) score the remaining gadgets in theset for quality. 3 | 4 | 5 | ## Elimination Criteria 6 | 7 | 1. Gadgets that consist only of the GPI (SYSCALL gadgets excluded) 8 | 2. Gadgets that have a first opcode that is not useful - we assume that the first instruction is part of the 9 | desired operation to be performed (otherwise attacker would just use the shorter version) 10 | 3. Gadgets that end in a call/jmp (ROPgadget should not include these in the first place) 11 | 4. Gadgets that create values in segment or extension registers, or are RIP-relative 12 | 5. Gadgets ending in returns with offsets that are not byte aligned or greater than 32 bytes 13 | 6. Gadgets containing ring-0 instructions / operands 14 | 7. Gadgets that contain an intermediate GPI/interrupt (ROPgadget should not include these in the first place) 15 | 8. ROP Gadgets that perform non-static assignments to the stack pointer register 16 | 9. JOP/COP Gadgets that overwrite the target of and indirect branch GPI 17 | 10. JOP/COP gadgets that are RIP-relative 18 | 11. Syscall gadgets that end in an interrupt handler that is not 0x80 (ROPgadget should not include these) 19 | 12. Gadgets that create value in the first instruction only to overwrite that value before the GPI 20 | 13. Gadgets that contain intermediate static calls 21 | 22 | 23 | ## Scoring Criteria 24 | 25 | ### General 26 | 1. (+3.0) Gadget has intermediate conditional jump 27 | 2. (+2.0) Gadget has intermediate conditional move or exchange 28 | 3. (+1.0) Gadget has intermediate set instruction 29 | 4. (+1.5) Gadget has intermediate static shift/rotate operation on value-carrying register 30 | 5. (+1.0) Gadget has intermediate static non-shift/rotate operation on value-carrying register 31 | 6. (+1.0) Gadget has intermediate run-time modification to a bystander register 32 | 7. (+0.5) Gadget has intermediate static modification to a bystander register 33 | 8. (+1.0) Gadget has intermediate instruction that stores value in memory location 34 | 35 | ### ROP only 36 | 1. (+2.0) Gadget contains intermediate leave instruction 37 | 2. (+2.0) Gadget's cumulative stack pointer offsets are negative 38 | 3. (+4.0) Gadget has intermediate instruction that performs move, exhange, or load address operation on RSP/ESP 39 | 4. (+3.0) Gadget has intermediate instruction that performs shift/rotate operation on RSP/ESP 40 | 5. (+1.0) Gadget has intermediate instruction that pops stack value into RSP/ESP 41 | 6. (+2.0) Gadget has intermediate instruction that performs any other static operation on RSP/ESP 42 | 43 | ### JOP/COP only 44 | 1. (+3.0) Gadget has intermediate instruction that performs shift/rotate operation on the register targeted by the GPI 45 | 2. (+2.0) Gadget has intermediate instruction that performs any other static operation on the register targeted by the GPI 46 | 47 | 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael D Brown 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GadgetSetAnalyzer 2 | A security-oriented static binary analysis tool for comparing the quantity, quality, and locality of code reuse gadget sets in program variants. 3 | 4 | If you use this tool in your research, please cite the following paper: 5 | 6 | **Brown, Michael D., and Santosh Pande. "Is less really more? towards better metrics for measuring security improvements realized through software debloating." In 12th {USENIX} Workshop on Cyber Security Experimentation and Test ({CSET} 19). 2019.**[\[pdf\]](https://www.usenix.org/system/files/cset19-paper_brown.pdf) 7 | 8 | GSA has been updated to include new metrics since the publication of this paper. The expanded version of the paper that includes expanded metrics is available here: 9 | 10 | **Brown, Michael D., and Santosh Pande. "Is Less Really More? Why Reducing Code Reuse Gadget Counts via 11 | Software Debloating Doesn’t Necessarily Indicate Improved Security" arXiv:1902.10880v3 [cs.CR]. 2019.**[\[pdf\]](https://arxiv.org/pdf/1902.10880.pdf) 12 | 13 | ## Description 14 | GSA is an automated tool for gathering security-oriented data on the effects of software transformation. It takes as input an original software package binary that has not been transformed, and at least one transformed variant of that package. It produces as output the following data files: 15 | 16 | 1. Functional Gadget Set Expressivity Change: The change in gadget set expressivity (ROP) between the original package and each variant. 17 | 2. Gadget Count Reduction: The change in overall gadget count between the original package and each variant. 18 | 3. Gadget Introduction: The rate at which new gadgets are introduced by software transformation. 19 | 4. Special Purpose Gadget Count Reduction: Same as 2, but for special purpose gadgets. 20 | 5. Special Purpose Gadget Introduction: Same as 3, but for special purpose gadgets. 21 | 6. Gadget Locality: The percentage of gadgets in a variant set that are also in present in the original set and also at the same offset. 22 | 7. Functional Gadget Set Quality Change: The change in quality (as measured by ease of use and the absence of side constraints) of the gadget set between the original package and each variant. 23 | 8. Likely Gadget Locations: For each special purpose gadget in each variant binary, the most likely function name in source where the gadget was introduced. 24 | 9. Gadget Comparison data in LaTeX table format. 25 | 26 | ## Dependencies 27 | The static analyzer is dependent upon the following third party packages: 28 | 29 | 1. ROPgadget - for collecting gadget based information from binaries. 30 | 2. angr - for finding source code functions associated with introduced gadgets. 31 | 3. numpy - for calculating some final statistics 32 | 33 | ## Installing 34 | To install GSA: 35 | 36 | 1. Install ROPgadget (https://github.com/JonathanSalwan/ROPgadget) 37 | 2. Install angr (https://docs.angr.io/introductory-errata/install) 38 | 3. Install numpy: `pip install numpy` 39 | 4. Clone this repo 40 | 41 | ## Running 42 | GSA has the following optional inputs: 43 | 44 | 1. Output Metrics (--output_metrics): Indicates that GSA should produce output files 1-5 and 7. 45 | 2. Output Addresses (--output_addresses): Indicates that GSA should produce output file 8. Ignored if --output_metrics is not set or if a directory is specified. Takes extra time to use angr. 46 | 3. Result Folder Name (--result_folder_name ): Indicates that GSA should place results for the run in results/ 47 | 4. Original Name (--original_name ): Indicates that GSA should use a specific in the output for the original binary. 48 | 5. Output Console (--output_console): Indicates that GSA should output gadget set and comparison data to the console. 49 | 6. Output Tables (--output_tables): Indicates that GSA should produce output file 9. Ignored if --output_metrics is not set. 50 | 7. Output Gadget Locality (--output_locality): Indicates that GSA should produce output file 6. Ignored if --output_metrics is not set. Takes extra time. 51 | 8. Output Simplified Metrics (--output_simple): Indicates that GSA should produce a simplified version of outputs useful for determining security improvements. Ignored if --output_metrics is not set. 52 | 53 | The analyzer has 2 required inputs: 54 | 55 | 1. Original Binary: Filepath to the original binary or directory of one or more binaries. 56 | 2. Variant Labels / Binaries: A sequence of variant labels and paths in the format `--variants label1=path1 label2=path2` 57 | 58 | Note that if a directory is specified, GSA will attempt to create a gadget set for analysis from all binaries in the folder, allowing for gadget set analysis that includes libraries. 59 | 60 | Example invocation: 61 | ``` 62 | python3 GSA.py --output_metrics --output_addresses ../samples/CHISEL/date/date-8.21.origin --variants Aggressive="../samples/CHISEL/date/date-8.21.reduced" 63 | ``` 64 | -------------------------------------------------------------------------------- /samples/CARVE/bftpd/Aggressive/bftpd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/bftpd/Aggressive/bftpd -------------------------------------------------------------------------------- /samples/CARVE/bftpd/Conservative/bftpd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/bftpd/Conservative/bftpd -------------------------------------------------------------------------------- /samples/CARVE/bftpd/Moderate/bftpd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/bftpd/Moderate/bftpd -------------------------------------------------------------------------------- /samples/CARVE/bftpd/Original/bftpd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/bftpd/Original/bftpd -------------------------------------------------------------------------------- /samples/CARVE/libcurl/Aggressive/libcurl.so.4.5.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libcurl/Aggressive/libcurl.so.4.5.0 -------------------------------------------------------------------------------- /samples/CARVE/libcurl/Case Study/libcurl.so.4.5.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libcurl/Case Study/libcurl.so.4.5.0 -------------------------------------------------------------------------------- /samples/CARVE/libcurl/Conservative/libcurl.so.4.5.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libcurl/Conservative/libcurl.so.4.5.0 -------------------------------------------------------------------------------- /samples/CARVE/libcurl/Moderate/libcurl.so.4.5.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libcurl/Moderate/libcurl.so.4.5.0 -------------------------------------------------------------------------------- /samples/CARVE/libcurl/Original/libcurl.so.4.5.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libcurl/Original/libcurl.so.4.5.0 -------------------------------------------------------------------------------- /samples/CARVE/libmodbus/Aggressive/libmodbus.so.5.1.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libmodbus/Aggressive/libmodbus.so.5.1.0 -------------------------------------------------------------------------------- /samples/CARVE/libmodbus/Conservative/libmodbus.so.5.1.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libmodbus/Conservative/libmodbus.so.5.1.0 -------------------------------------------------------------------------------- /samples/CARVE/libmodbus/Moderate/libmodbus.so.5.1.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libmodbus/Moderate/libmodbus.so.5.1.0 -------------------------------------------------------------------------------- /samples/CARVE/libmodbus/Original/libmodbus.so.5.1.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CARVE/libmodbus/Original/libmodbus.so.5.1.0 -------------------------------------------------------------------------------- /samples/CHISEL/bzip/bzip2-1.0.5.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/bzip/bzip2-1.0.5.origin -------------------------------------------------------------------------------- /samples/CHISEL/bzip/bzip2-1.0.5.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/bzip/bzip2-1.0.5.reduced -------------------------------------------------------------------------------- /samples/CHISEL/chown/chown-8.2.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/chown/chown-8.2.origin -------------------------------------------------------------------------------- /samples/CHISEL/chown/chown-8.2.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/chown/chown-8.2.reduced -------------------------------------------------------------------------------- /samples/CHISEL/date/date-8.21.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/date/date-8.21.origin -------------------------------------------------------------------------------- /samples/CHISEL/date/date-8.21.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/date/date-8.21.reduced -------------------------------------------------------------------------------- /samples/CHISEL/grep/grep-2.19.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/grep/grep-2.19.origin -------------------------------------------------------------------------------- /samples/CHISEL/grep/grep-2.19.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/grep/grep-2.19.reduced -------------------------------------------------------------------------------- /samples/CHISEL/gzip/gzip-1.2.4.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/gzip/gzip-1.2.4.origin -------------------------------------------------------------------------------- /samples/CHISEL/gzip/gzip-1.2.4.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/gzip/gzip-1.2.4.reduced -------------------------------------------------------------------------------- /samples/CHISEL/mkdir/mkdir-5.2.1.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/mkdir/mkdir-5.2.1.origin -------------------------------------------------------------------------------- /samples/CHISEL/mkdir/mkdir-5.2.1.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/mkdir/mkdir-5.2.1.reduced -------------------------------------------------------------------------------- /samples/CHISEL/rm/rm-8.4.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/rm/rm-8.4.origin -------------------------------------------------------------------------------- /samples/CHISEL/rm/rm-8.4.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/rm/rm-8.4.reduced -------------------------------------------------------------------------------- /samples/CHISEL/tar/tar-1.14.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/tar/tar-1.14.origin -------------------------------------------------------------------------------- /samples/CHISEL/tar/tar-1.14.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/tar/tar-1.14.reduced -------------------------------------------------------------------------------- /samples/CHISEL/uniq/uniq-8.16.origin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/uniq/uniq-8.16.origin -------------------------------------------------------------------------------- /samples/CHISEL/uniq/uniq-8.16.reduced: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelbrownuc/GadgetSetAnalyzer/54d43fb698a45b732f5894f0a0d69675eb1c2749/samples/CHISEL/uniq/uniq-8.16.reduced -------------------------------------------------------------------------------- /src/GSA.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | """ 3 | GadgetSetAnalyzer (GSA) 4 | This static analysis tool compares an original binary with one or more variants derived from it to determine how a 5 | software transformation or difference in binary production (e.g., compiler used, optimizations selected) impacts the 6 | set of code-reuse gadgets available. Several metrics are generated by GSA, and are described in the README. 7 | 8 | Dependencies: 9 | GSA uses the common tool under the hood to obtain a gadget catalog. Output of sensitive addresses uses the 10 | library. 11 | 12 | """ 13 | 14 | # Standard Library Imports 15 | import argparse 16 | import sys 17 | 18 | # Third Party Imports 19 | 20 | 21 | # Local Imports 22 | from utility import * 23 | from static_analyzer.GadgetSet import GadgetSet 24 | from static_analyzer.GadgetStats import GadgetStats 25 | 26 | LINE_SEP= "\n" # line separator 27 | 28 | # Parse Arguments 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument("original", help="Original program binary or directory of binaries.", type=str) 31 | parser.add_argument("--variants", 32 | metavar="VARIANT=PATH", 33 | nargs='+', 34 | help="Sequence of variant names and variant paths. Example: = =' ", 35 | type=str) 36 | parser.add_argument("--output_metrics", help="Output metric data as a CSV file.", action='store_true') 37 | parser.add_argument("--output_addresses", help="Output addresses of sensitive gadgets as a CSV file. Ignored if --output_metrics is not specified.", action='store_true') 38 | parser.add_argument("--output_tables", help="Output metric data as tables in LaTeX format. Ignored if --output_metrics is not specified. If specified, provide a row label, such as the program name.", action='store', type=str, default='') 39 | parser.add_argument("--result_folder_name", help="Optionally specifies a specific output file name for the results folder.", action="store", type=str) 40 | parser.add_argument("--original_name", help="Optionally specifies a specific name for the 'original' binary.", action="store", type=str, default="Original") 41 | parser.add_argument("--output_console", help="Output gadget set and comparison data to console.", action="store_true") 42 | parser.add_argument("--output_locality", help="Output gadget locality metric as a CSV file. Ignored if --output_metrics is not specified.", action='store_true') 43 | parser.add_argument("--output_simple", help="Output simplified version of results in single file. Ignored if --output_metrics is not specified.", action='store_true') 44 | args = parser.parse_args() 45 | args.output_locality = args.output_locality or args.output_simple # enable locality if output_simple because output_simple uses locality 46 | 47 | variants_dict = {} 48 | for variant in args.variants: 49 | parts = variant.split("=") 50 | if len(parts) != 2 or not parts[0] or not parts[1]: 51 | print("Error: variants are not in proper format") 52 | exit(1) 53 | variants_dict[parts[0]] = parts[1] 54 | 55 | print("Starting Gadget Set Analyzer") 56 | 57 | # Create Gadget sets for original 58 | print("Analyzing original package [" + args.original_name + "] located at: " + args.original) 59 | original = GadgetSet(args.original_name, args.original, False, args.output_console) 60 | 61 | if not args.output_metrics: 62 | # Iterate through variants and compare, output to console. 63 | for key in variants_dict.keys(): 64 | filepath = variants_dict.get(key) 65 | print("Analyzing variant package [" + key + "] located at: " + filepath) 66 | 67 | variant = GadgetSet(key, filepath, args.output_addresses, args.output_console) 68 | stat = GadgetStats(original, variant, args.output_console, args.output_locality) 69 | 70 | # Prepare output lines for files 71 | else: 72 | # Create a timestamped results folder 73 | try: 74 | if args.result_folder_name is None: 75 | directory_name = create_output_directory("results/analyzer_results_") 76 | else: 77 | directory_name = create_output_directory("results/" + args.result_folder_name, False) 78 | except OSError as osErr: 79 | print("An OS Error occurred during creation of results directory: " + osErr.strerror) 80 | sys.exit("Results cannot be logged, aborting operation...") 81 | print("Writing metrics files to " + directory_name) 82 | 83 | rate_format = "{:.1%}" 84 | float_format = "{:.2f}" 85 | 86 | # Prepare simplified file output for original if indicated 87 | if args.output_simple: 88 | simple_lines = ["Variant Name,Expressivity,Quality,Locality,S.P. Types,Syscall Available" + LINE_SEP] 89 | orig_metrics = original.name + "," + str(original.practical_ROP_expressivity) + "," 90 | orig_metrics = orig_metrics + float_format.format(original.average_functional_quality) + "," 91 | orig_metrics = orig_metrics + "NA," 92 | orig_metrics = orig_metrics + str(original.total_sp_types) + "," 93 | orig_metrics = orig_metrics + str(len(original.SyscallGadgets)) + LINE_SEP 94 | simple_lines.append(orig_metrics) 95 | 96 | else: 97 | # Prepare file line arrays 98 | # Output file 1: Gadget Counts/Reduction, Total and by Category 99 | file_1_lines = ["Package Variant,Total Gadgets,ROP Gadgets,JOP Gadgets,COP Gadgets,Special Purpose Gadgets" + LINE_SEP] 100 | orig_counts = original.name + "," + str(original.total_unique_gadgets) 101 | orig_counts = orig_counts + "," + str(len(original.ROPGadgets)) 102 | orig_counts = orig_counts + "," + str(len(original.JOPGadgets)) 103 | orig_counts = orig_counts + "," + str(len(original.COPGadgets)) 104 | orig_counts = orig_counts + "," + str(original.total_sp_gadgets) + LINE_SEP 105 | file_1_lines.append(orig_counts) 106 | 107 | # Output file 2: Gadget Introduction Rates 108 | file_2_lines = ["Package Variant,Total Gadgets,Total Introduction Rate,ROP Gadgets,ROP Introduction Rate,JOP Gadgets,JOP Introduction Rate,COP Gadgets,COP Introduction Rate" + LINE_SEP] 109 | orig_counts = original.name + "," + str(original.total_unique_gadgets) + ", ," 110 | orig_counts = orig_counts + str(len(original.ROPGadgets)) + ", ," 111 | orig_counts = orig_counts + str(len(original.JOPGadgets)) + ", ," 112 | orig_counts = orig_counts + str(len(original.COPGadgets)) + LINE_SEP 113 | file_2_lines.append(orig_counts) 114 | 115 | # Output file #3: SP Gadget Counts + Introduction 116 | file_3_lines = ["Special Purpose Gadget Counts + Introduction" + LINE_SEP, 117 | "Package Variant,Syscall Gadgets,JOP Dispatcher Gadgets,JOP Dataloader Gadgets,JOP Initializers,JOP Trampolines,COP Dispatcher Gadgets,COP Dataloader Gadgets,COP Initializers,COP Strong Trampoline Gadgets,COP Intra-stack Pivot Gadgets" + LINE_SEP] 118 | orig_counts = original.name + "," + str(len(original.SyscallGadgets)) 119 | orig_counts = orig_counts + "," + str(len(original.JOPDispatchers)) 120 | orig_counts = orig_counts + "," + str(len(original.JOPDataLoaders)) 121 | orig_counts = orig_counts + "," + str(len(original.JOPInitializers)) 122 | orig_counts = orig_counts + "," + str(len(original.JOPTrampolines)) 123 | orig_counts = orig_counts + "," + str(len(original.COPDispatchers)) 124 | orig_counts = orig_counts + "," + str(len(original.COPDataLoaders)) 125 | orig_counts = orig_counts + "," + str(len(original.COPInitializers)) 126 | orig_counts = orig_counts + "," + str(len(original.COPStrongTrampolines)) 127 | orig_counts = orig_counts + "," + str(len(original.COPIntrastackPivots)) + LINE_SEP 128 | file_3_lines.append(orig_counts) 129 | 130 | # Output File 4: Special Purpose Gadget Introduction Counts/Rates 131 | file_4_lines = ["Special Purpose Gadget Introduction Data" + LINE_SEP, 132 | "Package Variant,Syscall Gadgets,Syscall Gadget Introduction Rate," + 133 | "JOP Dispatcher Gadgets,JOP Dispatcher Gadget Introduction Rate," + 134 | "JOP Dataloader Gadgets,JOP Dataloader Gadget Introduction Rate," + 135 | "JOP Initializer Gadgets,JOP Initializer Gadget Introduction Rate," + 136 | "JOP Trampoline Gadgets,JOP Trampoline Gadget Introduction Rate," + 137 | "COP Dispatcher Gadgets,COP Dispatcher Gadget Introduction Rate," + 138 | "COP Dataloader Gadgets,COP Dataloader Gadget Introduction Rate," + 139 | "COP Initializer Gadgets,COP Initializer Gadget Introduction Rate," + 140 | "COP Strong Trampoline Gadgets,COP Strong Trampoline Gadget Introduction Rate," + 141 | "COP Intra-stack Pivot Gadgets,COP Intra-stack Pivot Gadget Introduction Rate" + LINE_SEP] 142 | orig_counts = original.name + "," + str(len(original.SyscallGadgets)) + ", ," 143 | orig_counts = orig_counts + str(len(original.JOPDispatchers)) + ", ," 144 | orig_counts = orig_counts + str(len(original.JOPDataLoaders)) + ", ," 145 | orig_counts = orig_counts + str(len(original.JOPInitializers)) + ", ," 146 | orig_counts = orig_counts + str(len(original.JOPTrampolines)) + ", ," 147 | orig_counts = orig_counts + str(len(original.COPDispatchers)) + ", ," 148 | orig_counts = orig_counts + str(len(original.COPDataLoaders)) + ", ," 149 | orig_counts = orig_counts + str(len(original.COPInitializers)) + ", ," 150 | orig_counts = orig_counts + str(len(original.COPStrongTrampolines)) + ", ," 151 | orig_counts = orig_counts + str(len(original.COPIntrastackPivots)) + LINE_SEP 152 | file_4_lines.append(orig_counts) 153 | 154 | # Output File 5: Gadget Expressivity Classes Fulfilled By Variant 155 | orig_prac_rop = str(original.practical_ROP_expressivity) + " of 11" 156 | orig_ASLR_prac_rop = str(original.practical_ASLR_ROP_expressivity) + " of 35" 157 | orig_simple_tc = str(original.turing_complete_ROP_expressivity) + " of 17" 158 | file_5_lines = ["Package Variant,Practical ROP Exploit,ASLR-Proof Practical ROP Exploit,Simple Turing Completeness" + LINE_SEP] 159 | orig_counts = original.name + "," 160 | orig_counts = orig_counts + orig_prac_rop + "," 161 | orig_counts = orig_counts + orig_ASLR_prac_rop + "," 162 | orig_counts = orig_counts + orig_simple_tc + LINE_SEP 163 | file_5_lines.append(orig_counts) 164 | 165 | # Output File 6: Overall Gadget Locality 166 | file_6_lines = ["Package Variant,Gadget Locality" + LINE_SEP] 167 | 168 | # Output File 7: Average Gadget Quality (and count of quality functional gadgets) 169 | file_7_lines = ["Package Variant,Quality ROP Gadgets,Average ROP Gadget Quality,Quality JOP Gadgets,Average JOP Gadget Quality,Quality COP Gadgets,Average COP Gadget Quality" + LINE_SEP] 170 | orig_quality = original.name + "," + str(len(original.ROPGadgets)) + "," + str(original.averageROPQuality) 171 | orig_quality += "," + str(len(original.JOPGadgets)) + "," + str(original.averageJOPQuality) 172 | orig_quality += "," + str(len(original.COPGadgets)) + "," + str(original.averageCOPQuality) + LINE_SEP 173 | file_7_lines.append(orig_quality) 174 | 175 | # Output File 8: Suspected function names containing introduced special purpose gadgets. 176 | file_8_lines = [] 177 | if args.output_addresses: 178 | print("Writing function names associated with special purpose gadgets to disk.") 179 | 180 | # Output File 9: LaTeX formatted table data 181 | table_lines = ["" for i in range(4)] 182 | if args.output_tables != '': 183 | print("Writing LaTeX formatted table data to disk.") 184 | table_lines[0] = args.output_tables + " & " + str(original.total_unique_gadgets) 185 | table_lines[1] = args.output_tables + " & " + str(original.practical_ROP_expressivity) + "/" + str(original.practical_ASLR_ROP_expressivity) + "/" + str(original.turing_complete_ROP_expressivity) 186 | table_lines[2] = args.output_tables + " & " + str(original.total_functional_gadgets) + " / " + float_format.format(original.average_functional_quality) 187 | table_lines[3] = args.output_tables + " & " + str(original.total_sp_types) 188 | 189 | # Iterate through the variants. Scan them to get a gadget set, compare it to the original, add data to output files 190 | for key in variants_dict.keys(): 191 | filepath = variants_dict.get(key) 192 | print("Analyzing variant package [" + key + "] located at: " + filepath) 193 | 194 | variant = GadgetSet(key, filepath, args.output_addresses, args.output_console) 195 | stat = GadgetStats(original, variant, args.output_console, args.output_locality) 196 | 197 | # Prepare simplified file output for original if indicated 198 | if args.output_simple: 199 | stat_metrics = variant.name + "," + str(stat.practical_ROP_exp_diff) + "," 200 | stat_metrics = stat_metrics + float_format.format(stat.total_average_quality_diff) + "," 201 | stat_metrics = stat_metrics + fmt_percent_keep_precision(stat.gadgetLocality) + "," # do not round locality 202 | stat_metrics = stat_metrics + str(stat.total_sp_type_reduction) + "," 203 | stat_metrics = stat_metrics + str(stat.SysCountDiff) + LINE_SEP 204 | simple_lines.append(stat_metrics) 205 | 206 | else: 207 | # Output file 1 variant lines 208 | stat_counts = variant.name + "," + str(variant.total_unique_gadgets) + " (" + str(stat.totalUniqueCountDiff) + "; " + rate_format.format(stat.totalUniqueCountReduction) + ")," 209 | stat_counts = stat_counts + str(len(variant.ROPGadgets)) + " (" + str(stat.ROPCountDiff) + "; " + rate_format.format(stat.ROPCountReduction) + ")," 210 | stat_counts = stat_counts + str(len(variant.JOPGadgets)) + " (" + str(stat.JOPCountDiff) + "; " + rate_format.format(stat.JOPCountReduction) + ")," 211 | stat_counts = stat_counts + str(len(variant.COPGadgets)) + " (" + str(stat.COPCountDiff) + "; " + rate_format.format(stat.COPCountReduction) + ")," 212 | stat_counts = stat_counts + str(variant.total_sp_gadgets) + " (" + str(stat.total_sp_count_diff) + "; " + rate_format.format(stat.total_sp_reduction) + ")" + LINE_SEP 213 | file_1_lines.append(stat_counts) 214 | 215 | # Output file 2 variant lines 216 | stat_counts = variant.name + "," + str(variant.total_unique_gadgets) + "," 217 | stat_counts = stat_counts + rate_format.format(stat.totalUniqueIntroductionRate) + "," 218 | stat_counts = stat_counts + str(len(variant.ROPGadgets)) + "," 219 | stat_counts = stat_counts + rate_format.format(stat.ROPIntroductionRate) + "," 220 | stat_counts = stat_counts + str(len(variant.JOPGadgets)) + "," 221 | stat_counts = stat_counts + rate_format.format(stat.JOPIntroductionRate) + "," 222 | stat_counts = stat_counts + str(len(variant.COPGadgets)) + "," 223 | stat_counts = stat_counts + rate_format.format(stat.COPIntroductionRate) + LINE_SEP 224 | file_2_lines.append(stat_counts) 225 | 226 | # Output file 3 variant lines 227 | stat_counts = variant.name + "," + str(len(variant.SyscallGadgets)) + " (" + str( 228 | stat.SysCountDiff) + "; " + rate_format.format(stat.SysCountReduction) + ")," 229 | stat_counts = stat_counts + str(len(variant.JOPDispatchers)) + " (" + str( 230 | stat.JOPDispatchersCountDiff) + "; " + rate_format.format(stat.JOPDispatchersCountReduction) + ")," 231 | stat_counts = stat_counts + str(len(variant.JOPDataLoaders)) + " (" + str( 232 | stat.JOPDataLoadersCountDiff) + "; " + rate_format.format(stat.JOPDataLoadersCountReduction) + ")," 233 | stat_counts = stat_counts + str(len(variant.JOPInitializers)) + " (" + str( 234 | stat.JOPInitializersCountDiff) + "; " + rate_format.format(stat.JOPInitializersCountReduction) + ")," 235 | stat_counts = stat_counts + str(len(variant.JOPTrampolines)) + " (" + str( 236 | stat.JOPTrampolinesCountDiff) + "; " + rate_format.format(stat.JOPTrampolinesCountReduction) + ")," 237 | stat_counts = stat_counts + str(len(variant.COPDispatchers)) + " (" + str( 238 | stat.COPDispatchersCountDiff) + "; " + rate_format.format(stat.COPDispatchersCountReduction) + ")," 239 | stat_counts = stat_counts + str(len(variant.COPDataLoaders)) + " (" + str( 240 | stat.COPDataLoadersCountDiff) + "; " + rate_format.format(stat.COPDataLoadersCountReduction) + ")," 241 | stat_counts = stat_counts + str(len(variant.COPInitializers)) + " (" + str( 242 | stat.COPInitializersCountDiff) + "; " + rate_format.format(stat.COPInitializersCountReduction) + ")," 243 | stat_counts = stat_counts + str(len(variant.COPStrongTrampolines)) + " (" + str( 244 | stat.COPStrongTrampolinesCountDiff) + "; " + rate_format.format( 245 | stat.COPStrongTrampolinesCountReduction) + ")," 246 | stat_counts = stat_counts + str(len(variant.COPIntrastackPivots)) + " (" + str( 247 | stat.COPIntrastackPivotsCountDiff) + "; " + rate_format.format( 248 | stat.COPIntrastackPivotsCountReduction) + ")" + LINE_SEP 249 | file_3_lines.append(stat_counts) 250 | 251 | # Output file 4 variant lines 252 | stat_counts = variant.name + "," + str(len(variant.SyscallGadgets)) + "," 253 | stat_counts = stat_counts + rate_format.format(stat.SysIntroductionRate) + "," 254 | stat_counts = stat_counts + str(len(variant.JOPDispatchers)) + "," 255 | stat_counts = stat_counts + rate_format.format(stat.JOPDispatchersIntroductionRate) + "," 256 | stat_counts = stat_counts + str(len(variant.JOPDataLoaders)) + "," 257 | stat_counts = stat_counts + rate_format.format(stat.JOPDataLoadersIntroductionRate) + "," 258 | stat_counts = stat_counts + str(len(variant.JOPInitializers)) + "," 259 | stat_counts = stat_counts + rate_format.format(stat.JOPInitializersIntroductionRate) + "," 260 | stat_counts = stat_counts + str(len(variant.JOPTrampolines)) + "," 261 | stat_counts = stat_counts + rate_format.format(stat.JOPTrampolinesIntroductionRate) + "," 262 | stat_counts = stat_counts + str(len(variant.COPDispatchers)) + "," 263 | stat_counts = stat_counts + rate_format.format(stat.COPDispatchersIntroductionRate) + "," 264 | stat_counts = stat_counts + str(len(variant.COPDataLoaders)) + "," 265 | stat_counts = stat_counts + rate_format.format(stat.COPDataLoadersIntroductionRate) + "," 266 | stat_counts = stat_counts + str(len(variant.COPInitializers)) + "," 267 | stat_counts = stat_counts + rate_format.format(stat.COPInitializersIntroductionRate) + "," 268 | stat_counts = stat_counts + str(len(variant.COPStrongTrampolines)) + "," 269 | stat_counts = stat_counts + rate_format.format(stat.COPStrongTrampolinesIntroductionRate) + "," 270 | stat_counts = stat_counts + str(len(variant.COPIntrastackPivots)) + "," 271 | stat_counts = stat_counts + rate_format.format(stat.COPIntrastackPivotsIntroductionRate) + LINE_SEP 272 | file_4_lines.append(stat_counts) 273 | 274 | # Output file 5 variant lines 275 | stat_counts = variant.name + "," + str(variant.practical_ROP_expressivity) + " (" + str(stat.practical_ROP_exp_diff) + ")," 276 | stat_counts += str(variant.practical_ASLR_ROP_expressivity) + " (" + str(stat.practical_ASLR_ROP_exp_diff) + ")," 277 | stat_counts += str(variant.turing_complete_ROP_expressivity) + " (" + str(stat.turing_complete_ROP_exp_diff) + ")" + LINE_SEP 278 | file_5_lines.append(stat_counts) 279 | 280 | # Output file 6 variant lines 281 | if args.output_locality: 282 | stat_locality = variant.name + "," + fmt_percent_keep_precision(stat.gadgetLocality) + LINE_SEP 283 | file_6_lines.append(stat_locality) 284 | 285 | # Output file 7 variant lines 286 | stat_quality = variant.name + "," + str(len(variant.ROPGadgets)) + " (" + str(stat.keptQualityROPCountDiff) + ")," 287 | stat_quality += str(variant.averageROPQuality) + " (" + str(stat.averageROPQualityDiff) + ")," 288 | stat_quality += str(len(variant.JOPGadgets)) + " (" + str(stat.keptQualityJOPCountDiff) + ")," 289 | stat_quality += str(variant.averageJOPQuality) + " (" + str(stat.averageJOPQualityDiff) + ")," 290 | stat_quality += str(len(variant.COPGadgets)) + " (" + str(stat.keptQualityCOPCountDiff) + ")," 291 | stat_quality += str(variant.averageCOPQuality) + " (" + str(stat.averageCOPQualityDiff) + ")" + LINE_SEP 292 | file_7_lines.append(stat_quality) 293 | 294 | # Output file 8 variant lines 295 | if args.output_addresses: 296 | file_8_lines.append("Sensitive gadgets introduced in variant: " + variant.name + LINE_SEP) 297 | specialSets = [variant.SyscallGadgets, variant.JOPDispatchers, 298 | variant.JOPDataLoaders, variant.JOPInitializers, 299 | variant.JOPTrampolines, variant.COPDispatchers, 300 | variant.COPDataLoaders, variant.COPInitializers, 301 | variant.COPStrongTrampolines, variant.COPIntrastackPivots] 302 | for specialSet in specialSets: 303 | for gadget in specialSet: 304 | file_8_lines.append("Gadget: " + str(gadget.instructions) + LINE_SEP) 305 | file_8_lines.append("Found at offset: " + gadget.offset + LINE_SEP) 306 | function = variant.getFunction(gadget.offset) 307 | if function is None: 308 | file_8_lines.append("No associated function found." + LINE_SEP) 309 | else: 310 | file_8_lines.append("Most likely location in source code: " + function + LINE_SEP) 311 | file_8_lines.append("----------------------------------------------------------" + LINE_SEP) 312 | 313 | # Output File 9 variant lines 314 | if args.output_tables != '': 315 | table_lines[0] = table_lines[0] + " & " + str(variant.total_unique_gadgets) + " (" + rate_format.format(stat.totalUniqueIntroductionRate) + ")" 316 | table_lines[1] = table_lines[1] + " & " + str(variant.practical_ROP_expressivity) + "/" + str(variant.practical_ASLR_ROP_expressivity) + "/" + str(variant.turing_complete_ROP_expressivity) + " & (" + \ 317 | str(stat.practical_ROP_exp_diff) + "/" + str(stat.practical_ASLR_ROP_exp_diff) + "/" + str(stat.turing_complete_ROP_exp_diff) + ")" 318 | table_lines[2] = table_lines[2] + " & " + str(variant.total_functional_gadgets) + " / " + float_format.format(variant.average_functional_quality) + " & (" + \ 319 | str(stat.total_functional_count_diff) + " / " + float_format.format(stat.total_average_quality_diff) + ")" 320 | table_lines[3] = table_lines[3] + " & " + str(variant.total_sp_types) + " & (" + str(stat.total_sp_type_reduction) + ")" 321 | 322 | # Write file lines to disk. 323 | try: 324 | if args.output_simple: 325 | # Output Simplified Results 326 | file = open(directory_name + "/GadgetSetAnalysis.csv", "w") 327 | file.writelines(simple_lines) 328 | file.close() 329 | 330 | else: 331 | # Output file 1 332 | file = open(directory_name + "/GadgetCounts_Reduction.csv", "w") 333 | file.writelines(file_1_lines) 334 | file.close() 335 | 336 | # Output file 2 337 | file = open(directory_name + "/Gadget_Introduction_Counts_Rate.csv", "w") 338 | file.writelines(file_2_lines) 339 | file.close() 340 | 341 | # Output file 3 342 | file = open(directory_name + "/SpecialPurpose_GadgetCounts_Reduction.csv", "w") 343 | file.writelines(file_3_lines) 344 | file.close() 345 | 346 | # Output file 4 347 | file = open(directory_name + "/SpecialPurpose_Gadget_Introduction_Counts_Rate.csv", "w") 348 | file.writelines(file_4_lines) 349 | file.close() 350 | 351 | # Output file 5 352 | file = open(directory_name + "/Expressivity_Counts.csv", "w") 353 | file.writelines(file_5_lines) 354 | file.close() 355 | 356 | # Output file 6 357 | if args.output_locality: 358 | file = open(directory_name + "/Gadget Locality.csv", "w") 359 | file.writelines(file_6_lines) 360 | file.close() 361 | 362 | # Output file 7 363 | file = open(directory_name + "/Gadget Quality.csv", "w") 364 | file.writelines(file_7_lines) 365 | file.close() 366 | 367 | # Output file 8 368 | if args.output_addresses: 369 | file = open(directory_name + "/Likely_Gadget_Locations.txt", "w") 370 | file.writelines(file_8_lines) 371 | file.close() 372 | 373 | if args.output_tables != '': 374 | table_lines[0] = table_lines[0] + " \\\\ " + LINE_SEP 375 | table_lines[1] = table_lines[1] + " \\\\ " + LINE_SEP 376 | table_lines[2] = table_lines[2] + " \\\\ " + LINE_SEP 377 | table_lines[3] = table_lines[3] + " \\\\ " + LINE_SEP 378 | file = open(directory_name + "/Table_Formatted.txt", "w") 379 | file.writelines(table_lines) 380 | file.close() 381 | except OSError as osErr: 382 | print(osErr) 383 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | # Source directory for the gadget analyzer. 2 | -------------------------------------------------------------------------------- /src/static_analyzer/Gadget.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gadget class 3 | """ 4 | 5 | # Standard Library Imports 6 | 7 | # Third Party Imports 8 | 9 | # Local Imports 10 | from static_analyzer.Instruction import Instruction 11 | 12 | 13 | class Gadget(object): 14 | """ 15 | The Gadget class represents a single gadget. 16 | """ 17 | 18 | def __init__(self, binary_name, raw_gadget): 19 | """ 20 | Gadget constructor 21 | :param str binary_name: name of the binary the gadget is sourced from. 22 | :param str raw_gadget: raw line output from ROPgadget 23 | """ 24 | 25 | self.binary_name = binary_name 26 | 27 | # Parse the raw line 28 | self.offset = raw_gadget[:raw_gadget.find(":")] 29 | self.instruction_string = raw_gadget[raw_gadget.find(":") + 2:] 30 | 31 | # Parse instruction objects 32 | self.instructions = [] 33 | for instr in self.instruction_string.split(" ; "): 34 | self.instructions.append(Instruction(instr)) 35 | 36 | # Initialize score 37 | self.score = 0.0 38 | 39 | def is_useless_op(self): 40 | """ 41 | :return boolean: Returns True if the first instruction opcode is in the "useless" list, False otherwise 42 | Default behavior is to consider opcodes useful unless otherwise observed. 43 | """ 44 | first_opcode = self.instructions[0].opcode 45 | 46 | # Bulk catch for all "jump" opcodes: No reason to include the instruction, just use the suffix directly 47 | if first_opcode.startswith("j"): 48 | return True 49 | # Bulk catch for bounds checked jumps, same reason as above 50 | if first_opcode.startswith("bnd"): 51 | return True 52 | # Bulk catch for all "ret" opcodes: Bug in ROP gadget finds some gadgets that start with this GPI 53 | if first_opcode.startswith("ret"): 54 | return True 55 | # Bulk catch for all "iret" opcodes: Bug in ROP gadget finds some gadgets that start with this GPI 56 | if first_opcode.startswith("iret"): 57 | return True 58 | # Bulk catch for all "call" opcodes: Bug in ROP gadget finds some gadgets that start with this GPI 59 | if first_opcode.startswith("call"): 60 | return True 61 | 62 | # Useless opcodes: 63 | # NOP - No reason to include the instruction, just use the suffix directly 64 | # LJMP - Same reason as "jump" opcodes above 65 | useless = ["nop", "fnop", "ljmp"] 66 | return first_opcode in useless 67 | 68 | def contains_unusable_op(self): 69 | """ 70 | :return boolean: Returns True if any instruction opcode is unusable. False otherwise 71 | unusable instructions are Ring-0 opcodes that trap in user mode and some other exceptional ops. 72 | """ 73 | for instr in self.instructions: 74 | # Bulk catch for all "invalidate" opcodes: Ring-0 instructions 75 | if instr.opcode.startswith("inv"): 76 | return True 77 | # Bulk catch for all "Virtual-Machine" opcodes: Ring-0 instructions 78 | if instr.opcode.startswith("vm") and instr.opcode != "vminsd" and instr.opcode != "vminpd": 79 | return True 80 | # Bulk catch for all "undefined" opcodes 81 | if instr.opcode.startswith("ud"): 82 | return True 83 | 84 | # Other Ring-0 opcodes and RSM, LOCK prefix 85 | unusable = ["clts", "hlt", "lgdt", "lidt", "lldt", "lmsw", "ltr", "monitor", "mwait", 86 | "swapgs", "sysexit", "sysreturn", "wbinvd", "wrmsr", "xsetbv", "rsm", "lock"] 87 | if instr.opcode in unusable: 88 | return True 89 | 90 | # Check for ring-0 operands (control, debug, and test registers) 91 | if instr.op1 is not None: 92 | if instr.op1.startswith("cr") or instr.op1.startswith("tr") or instr.op1.startswith("db"): 93 | return True 94 | if instr.op2 is not None: 95 | if instr.op2.startswith("cr") or instr.op2.startswith("tr") or instr.op2.startswith("db"): 96 | return True 97 | 98 | return False 99 | 100 | def is_gpi_only(self): 101 | """ 102 | :return boolean: Returns True if the gadget is a single instruction and starts with 'ret', 'jmp', or 'call', 103 | False otherwise 104 | """ 105 | if len(self.instructions) == 1: 106 | opcode = self.instructions[0].opcode 107 | if opcode.startswith("ret") or opcode.startswith("jmp") or opcode.startswith("call"): 108 | return True 109 | return False 110 | 111 | def is_invalid_branch(self): 112 | """ 113 | :return boolean: Returns True if the gadget is 'jmp' or 'call' ending and the call target is a constant offset 114 | or does not target a recognized register family. False otherwise 115 | """ 116 | last_instr = self.instructions[len(self.instructions)-1] 117 | if last_instr.opcode.startswith("call") or last_instr.opcode.startswith("jmp"): 118 | if Instruction.get_operand_register_family(last_instr.op1) is None: 119 | return True 120 | return False 121 | 122 | def has_invalid_ret_offset(self): 123 | """ 124 | :return boolean: Returns True if the gadget is 'ret' ending and contains a constant offset that is not byte 125 | aligned or is greater than 32 bytes, False otherwise 126 | """ 127 | last_instr = self.instructions[len(self.instructions)-1] 128 | if last_instr.opcode.startswith("ret") and last_instr.op1 is not None: 129 | offset = Instruction.get_operand_as_constant(last_instr.op1) 130 | if (offset % 2 != 0) or (offset > 32): 131 | return True 132 | 133 | return False 134 | 135 | def clobbers_created_value(self): 136 | """ 137 | :return boolean: Returns True if the gadget completely overwrites the value created in the first instruction, 138 | False otherwise. 139 | """ 140 | 141 | first_instr = self.instructions[0] 142 | 143 | # Check if the first instruction creates a value or is an xchg operand (excluded as an edge case) 144 | if not first_instr.creates_value() or "xchg" in first_instr.opcode: 145 | return False 146 | 147 | # Check op1 to find the register family to protect 148 | first_family = Instruction.get_operand_register_family(first_instr.op1) 149 | 150 | # Most likely means first operand is a constant, exclude from analysis 151 | if first_family is None: 152 | return False 153 | 154 | # Iterate through intermediate instructions, determine if it overwrites protected value (or part of it) 155 | for i in range(1, len(self.instructions)-1): 156 | cur_instr = self.instructions[i] 157 | 158 | # Ignore instructions that do not create values 159 | if not cur_instr.creates_value() or "xchg" in cur_instr.opcode: 160 | continue 161 | 162 | # Check for non-static modification of the register family 163 | if first_family == Instruction.get_operand_register_family(cur_instr.op1): 164 | if (cur_instr.op2 is None and cur_instr.opcode not in ["inc", "dec", "neg", "not"]) or \ 165 | (cur_instr.op2 is not None and not Instruction.is_constant(cur_instr.op2)): 166 | return True 167 | 168 | return False 169 | 170 | def creates_unusable_value(self): 171 | """ 172 | :return boolean: Returns True if the gadget creates a value in segment or extension registers, or are 173 | RIP-relative, or are constant memory locations; False otherwise. 174 | """ 175 | # Check if the first instruction creates a value (or may potentially set a flag 176 | first_instr = self.instructions[0] 177 | if first_instr.opcode in ["cmp", "test", "push"] or first_instr.op1 is None: 178 | return False 179 | 180 | # Check if first operand is not a constant and it does not belong to a recognized register family 181 | if not Instruction.is_constant(first_instr.op1) and \ 182 | Instruction.get_operand_register_family(first_instr.op1) is None: 183 | return True 184 | 185 | return False 186 | 187 | 188 | def contains_intermediate_GPI(self): 189 | """ 190 | :return boolean: Returns True if the gadget's intermediate instructions contain a GPI (or a generic interrupt), 191 | False otherwise. 192 | """ 193 | for i in range(len(self.instructions)-1): 194 | cur_opcode = self.instructions[i].opcode 195 | cur_target = self.instructions[i].op1 196 | if cur_opcode.startswith("ret") or \ 197 | cur_opcode == "syscall" or cur_opcode == "sysenter" or cur_opcode.startswith("int") or \ 198 | ("jmp" in cur_opcode and not Instruction.is_constant(cur_target)) or \ 199 | ("call" in cur_opcode and not Instruction.is_constant(cur_target)): 200 | return True 201 | 202 | return False 203 | 204 | def clobbers_stack_pointer(self): 205 | """ 206 | :return boolean: Returns True if the ROP gadget's instructions assign a non-static value to the stack pointer 207 | register, False otherwise. 208 | """ 209 | # Only check ROP gadgets 210 | last_instr = self.instructions[len(self.instructions) - 1] 211 | if last_instr.opcode.startswith("ret"): 212 | for i in range(len(self.instructions) - 1): 213 | cur_instr = self.instructions[i] 214 | 215 | # Ignore instructions that do not create values 216 | if not cur_instr.creates_value(): 217 | continue 218 | 219 | # Check for non-static modification of the stack pointer register family 220 | if Instruction.get_operand_register_family(cur_instr.op1) == 7: # RSP, ESP family number 221 | if (cur_instr.op2 is None and cur_instr.opcode not in ["inc", "dec", "pop"]) or \ 222 | (cur_instr.op2 is not None and not Instruction.is_constant(cur_instr.op2)): 223 | return True 224 | return False 225 | 226 | def clobbers_indirect_target(self): 227 | """ 228 | :return boolean: Returns True if the JOP/COP gadget's instructions modify the indirect branch register in 229 | certain ways, False otherwise. 230 | """ 231 | # Get the register family of the indirect jump / call 232 | last_instr = self.instructions[len(self.instructions)-1] 233 | if last_instr.opcode.startswith("jmp") or last_instr.opcode.startswith("call"): 234 | family = Instruction.get_operand_register_family(last_instr.op1) 235 | 236 | # Check each instruction to see if it clobbers the value 237 | for i in range(len(self.instructions)-1): 238 | cur_instr = self.instructions[i] 239 | 240 | # First check if the instruction modifies the target 241 | if cur_instr.op1 in Instruction.register_families[family]: 242 | # Does the instruction zeroize out the target? 243 | if cur_instr.opcode == "xor" and cur_instr.op1 == cur_instr.op2: 244 | return True 245 | # Does the instruction perform a RIP-relative LEA into the target? 246 | if cur_instr.opcode == "lea" and ("rip" in cur_instr.op2 or "eip" in cur_instr.op2): 247 | return True 248 | # Does the instruction load a string or a value of an input port into the target? 249 | if cur_instr.opcode.startswith("lods") or cur_instr.opcode == "in": 250 | return True 251 | # Does the instruction overwrite the target with a static value or segment register value? 252 | if "mov" in cur_instr.opcode and (Instruction.is_constant(cur_instr.op2) or 253 | Instruction.get_operand_register_family(cur_instr.op2) is None): 254 | return True 255 | return False 256 | 257 | def has_invalid_int_handler(self): 258 | """ 259 | :return boolean: Returns True if the gadget's instructions assign a non-static value to the stack pointer 260 | register, False otherwise. 261 | """ 262 | last_instr = self.instructions[len(self.instructions) - 1] 263 | if last_instr.opcode.startswith("int") and last_instr.op1 != "0x80": 264 | return True 265 | 266 | return False 267 | 268 | def is_rip_relative_indirect_branch(self): 269 | """ 270 | :return boolean: Returns True if the gadget is a JOP/COP gadget relying on a RIP relative indirect branch, 271 | False otherwise. 272 | """ 273 | last_instr = self.instructions[len(self.instructions) - 1] 274 | if last_instr.opcode.startswith("jmp") or last_instr.opcode.startswith("call"): 275 | if "rip" in last_instr.op1 or "eip" in last_instr.op1: 276 | return True 277 | 278 | return False 279 | 280 | def contains_static_call(self): 281 | for i in range(1, len(self.instructions)-1): 282 | cur_instr = self.instructions[i] 283 | if cur_instr.opcode.startswith("call") and Instruction.is_constant(cur_instr.op1): 284 | return True 285 | 286 | return False 287 | 288 | def __repr__(self): 289 | # Two gadgets are the same if they have the same offset and instruction string 290 | return f"Gadget(binary: {self.binary_name}, offset: {self.offset}, instructions: {self.instruction_string})" 291 | 292 | def is_equal(self, rhs): 293 | """ 294 | :return boolean: Returns True if the gadgets are an exact match, including offset. 295 | """ 296 | return repr(self) == repr(rhs) 297 | 298 | def is_duplicate(self, rhs): 299 | """ 300 | :return boolean: Returns True if the gadgets are a semantic match. Used for non-locality gadget metrics. 301 | Semantic match is defined as the exact same sequence of equivalent instructions. 302 | """ 303 | if len(self.instructions) != len(rhs.instructions): 304 | return False 305 | 306 | for i in range(len(self.instructions)): 307 | if not self.instructions[i].is_equivalent(rhs.instructions[i]): 308 | return False 309 | 310 | return True 311 | 312 | def is_JOP_COP_dispatcher(self): 313 | """ 314 | :return boolean: Returns True if the gadget is a JOP or COP dispatcher. Defined as a gadget that begins with a 315 | arithmetic operation on a register and ends with a branch to a deference of that register. Used 316 | to iterate through instructions in payload. Only restrictions on the arithmetic operation is 317 | that it doesn't use the same register as both operands. 318 | """ 319 | first_instr = self.instructions[0] 320 | last_instr = self.instructions[len(self.instructions) - 1] 321 | 322 | # Only consider gadgets that end in dereference of a register and start with opcodes of interest 323 | if "[" in last_instr.op1 and \ 324 | first_instr.opcode in ["inc", "dec", "add", "adc", "sub", "sbb"] and "[" not in first_instr.op1: 325 | gpi_target = Instruction.get_operand_register_family(last_instr.op1) 326 | arith_target_1 = Instruction.get_operand_register_family(first_instr.op1) 327 | 328 | # Secondary check: if the second op is a constant ensure it is in range [1, 32] 329 | if Instruction.is_constant(first_instr.op2): 330 | additive_value = Instruction.get_operand_as_constant(first_instr.op2) 331 | if additive_value < 1 or additive_value > 32: 332 | return False 333 | 334 | arith_target_2 = Instruction.get_operand_register_family(first_instr.op2) 335 | return gpi_target == arith_target_1 and arith_target_1 != arith_target_2 336 | 337 | return False 338 | 339 | def is_JOP_COP_dataloader(self): 340 | """ 341 | :return boolean: Returns True if the gadget is a JOP or COP data loader. Defined as a gadget that begins with a 342 | pop opcode to a non-memory location, that is also not the target of the GPI. Used to pop a 343 | necessary value off stack en masse before redirecting to the dispatcher. 344 | """ 345 | first_instr = self.instructions[0] 346 | 347 | if first_instr.opcode == "pop" and "[" not in first_instr.op1: 348 | gpi_target = Instruction.get_operand_register_family(self.instructions[len(self.instructions) - 1].op1) 349 | pop_target = Instruction.get_operand_register_family(first_instr.op1) 350 | return gpi_target != pop_target 351 | 352 | return False 353 | 354 | 355 | def is_JOP_initializer(self): 356 | """ 357 | :return boolean: Returns True if the gadget is a JOP Initializer. Defined as a gadget that begins with a 358 | "pop all" opcode, used to pop necessary values off stack en masse before redirecting to the 359 | dispatcher. 360 | """ 361 | return self.instructions[0].opcode.startswith("popa") 362 | 363 | def is_JOP_trampoline(self): 364 | """ 365 | :return boolean: Returns True if the gadget is a JOP trampoline. Defined as a gadget that begins with a 366 | pop opcode to a non-memory location, and that ends in a dereference of that value. Used to 367 | redirect execution to value stored in memory. 368 | """ 369 | first_instr = self.instructions[0] 370 | gpi_target_op = self.instructions[len(self.instructions) - 1].op1 371 | 372 | if first_instr.opcode == "pop" and "[" not in first_instr.op1: 373 | gpi_target = Instruction.get_operand_register_family(gpi_target_op) 374 | pop_target = Instruction.get_operand_register_family(first_instr.op1) 375 | return gpi_target == pop_target and "[" in gpi_target_op 376 | 377 | return False 378 | 379 | def is_COP_initializer(self): 380 | """ 381 | :return boolean: Returns True if the gadget is a COP initializer. Defined as a gadget that begins with a 382 | "pop all" opcode, does not use register bx/cx/dx/di as the call target, and does not clobber 383 | bx/cx/dx or the call target in an intermediate instruction 384 | """ 385 | first_instr = self.instructions[0] 386 | last_instr = self.instructions[len(self.instructions)-1] 387 | call_target = Instruction.get_operand_register_family(last_instr.op1) 388 | 389 | if first_instr.opcode.startswith("popa") and call_target not in [1, 2, 3, 5]: # BX, CX, DX, DI families 390 | # Build collective list of register families to protect from being clobbered 391 | protected_families = [1, 2, 3, call_target] 392 | protected_registers = [] 393 | for family in protected_families: 394 | for register in Instruction.register_families[family]: 395 | protected_registers.append(register) 396 | 397 | # Scan intermediate instructions to ensure they do not clobber a protected register 398 | for i in range(1, len(self.instructions)-1): 399 | cur_instr = self.instructions[i] 400 | 401 | # Ignore instructions that do not create values 402 | if not cur_instr.creates_value(): 403 | continue 404 | 405 | # Check for non-static modification of the register family 406 | if cur_instr.op1 in protected_registers: 407 | if (cur_instr.op2 is None and cur_instr.opcode not in ["inc", "dec", "neg", "not"]) or \ 408 | (cur_instr.op2 is not None and not Instruction.is_constant(cur_instr.op2)): 409 | return False 410 | return True 411 | 412 | return False 413 | 414 | def is_COP_strong_trampoline(self): 415 | """ 416 | :return boolean: Returns True if the gadget is a COP strong trampoline. Defined as a gadget that begins with a 417 | pop opcode, and contains at least one other pop operation. The last non-pop all operation must 418 | target the call target. 419 | """ 420 | first_instr = self.instructions[0] 421 | last_instr = self.instructions[len(self.instructions) - 1] 422 | call_target = Instruction.get_operand_register_family(last_instr.op1) 423 | 424 | # Only consider instructions that start with a pop 425 | if first_instr.opcode == "pop" and "[" not in first_instr.op1: 426 | cnt_pops = 1 427 | last_pop_target = first_instr.op1 428 | 429 | # Scan intermediate instructions for pops 430 | for i in range(1, len(self.instructions)-1): 431 | cur_instr = self.instructions[i] 432 | 433 | if cur_instr.opcode.startswith("popa"): 434 | cnt_pops += 1 435 | 436 | if cur_instr.opcode == "pop" and "[" not in cur_instr.op1: 437 | cnt_pops += 1 438 | last_pop_target = cur_instr.op1 439 | 440 | # Check that at least two pops occurred and the last pop target is the call target 441 | if cnt_pops > 1 and last_pop_target in Instruction.register_families[call_target]: 442 | return True 443 | 444 | return False 445 | 446 | def is_COP_intrastack_pivot(self): 447 | """ 448 | :return boolean: Returns True if the gadget is a COP Intra-stack pivot gadget. Defined as a gadget that begins 449 | with an additive operation on the stack pointer register. Used to move around in shellcode 450 | during COP exploits. Only restriction on the arithmetic operation is that the second operand 451 | is not a pointer. 452 | """ 453 | first_instr = self.instructions[0] 454 | 455 | if first_instr.opcode in ["inc", "add", "adc", "sub", "sbb"] and "[" not in first_instr.op1: 456 | arith_target = Instruction.get_operand_register_family(first_instr.op1) 457 | if arith_target == 7: # RSP, ESP family number 458 | if first_instr.op2 is None or "[" not in first_instr.op2: 459 | return True 460 | 461 | return False 462 | 463 | def check_contains_leave(self): 464 | """ 465 | :return void: Increases gadget's score if the gadget has an intermediate "leave" instruction. 466 | """ 467 | for i in range(1, len(self.instructions)-1): 468 | if self.instructions[i].opcode == "leave": 469 | self.score += 2.0 470 | return # Only penalize gadget once 471 | 472 | def check_sp_target_of_operation(self): 473 | """ 474 | :return void: Increases gadget's score if the gadget has an intermediate instruction that performs certain 475 | operations on the stack pointer register family. 476 | """ 477 | # Scan instructions to determine if they modify the stack pointer register family 478 | for i in range(len(self.instructions)-1): 479 | cur_instr = self.instructions[i] 480 | 481 | # Ignore instructions that do not create values 482 | if not cur_instr.creates_value(): 483 | continue 484 | 485 | # Increase score by 4 for move, load address, and exchange ops, 3 for shift/rotate ops, and 2 for others 486 | if Instruction.get_operand_register_family(cur_instr.op1) == 7: # RSP, ESP family number 487 | if "xchg" in cur_instr.opcode or "mov" in cur_instr.opcode or cur_instr.opcode in ["lea"]: 488 | self.score += 4.0 489 | elif cur_instr.opcode in ["shl", "shr", "sar", "sal", "ror", "rol", "rcr", "rcl"]: 490 | self.score += 3.0 491 | elif cur_instr.opcode == "pop": 492 | self.score += 1.0 493 | else: 494 | self.score += 2.0 # Will be a static modification, otherwise it would have been rejected earlier 495 | 496 | def check_negative_sp_offsets(self): 497 | """ 498 | :return void: Increases gadget's score if its cumulative register offsets are negative. 499 | """ 500 | sp_offset = 0 501 | 502 | # Scan instructions to determine if they modify the stack pointer 503 | for i in range(len(self.instructions)): 504 | cur_instr = self.instructions[i] 505 | 506 | if cur_instr.opcode == "push": 507 | sp_offset -= 8 508 | 509 | elif cur_instr.opcode == "pop" and cur_instr.op1 not in Instruction.register_families[7]: 510 | sp_offset += 8 511 | 512 | elif cur_instr.opcode in ["add", "adc"] and cur_instr.op1 in Instruction.register_families[7] and \ 513 | Instruction.is_constant(cur_instr.op2): 514 | sp_offset += Instruction.get_operand_as_constant(cur_instr.op2) 515 | 516 | elif cur_instr.opcode in ["sub", "sbb"] and cur_instr.op1 in Instruction.register_families[7] and \ 517 | Instruction.is_constant(cur_instr.op2): 518 | sp_offset -= Instruction.get_operand_as_constant(cur_instr.op2) 519 | 520 | elif cur_instr.opcode == "inc" and cur_instr.op1 in Instruction.register_families[7]: 521 | sp_offset += 1 522 | 523 | elif cur_instr.opcode == "dec" and cur_instr.op1 in Instruction.register_families[7]: 524 | sp_offset -= 1 525 | 526 | elif cur_instr.opcode.startswith("ret") and cur_instr.op1 is not None: 527 | sp_offset += Instruction.get_operand_as_constant(cur_instr.op1) 528 | 529 | if sp_offset < 0: 530 | self.score += 2.0 531 | 532 | def check_contains_conditional_op(self): 533 | """ 534 | :return void: Increases gadget's score if it contains conditional instructions like jumps, sets, and moves. 535 | """ 536 | # Scan instructions to determine if they modify the stack pointer 537 | for i in range(len(self.instructions)-1): 538 | cur_instr = self.instructions[i] 539 | 540 | if cur_instr.opcode.startswith("j") and cur_instr.opcode != "jmp": 541 | self.score += 3.0 542 | elif "cmov" in cur_instr.opcode or "cmpxchg" in cur_instr.opcode: 543 | self.score += 2.0 544 | elif "set" in cur_instr.opcode: 545 | self.score += 1.0 546 | 547 | def check_register_ops(self): 548 | """ 549 | :return void: Increases gadget's score if it contains operations on a value carrying or a bystander register 550 | """ 551 | first_instr = self.instructions[0] 552 | 553 | # Check if the first instruction creates a value or is an xchg operand (excluded as an edge case) 554 | if not first_instr.creates_value() or "xchg" in first_instr.opcode: 555 | first_family = None 556 | else: 557 | # Check op1 to find the register family to protect 558 | first_family = Instruction.get_operand_register_family(first_instr.op1) 559 | 560 | for i in range(1, len(self.instructions)-1): 561 | cur_instr = self.instructions[i] 562 | 563 | # Ignore instructions that do not create values 564 | if not cur_instr.creates_value(): 565 | continue 566 | 567 | # If the new value is a modification of the value-carrying register 568 | if first_family is not None and first_family == Instruction.get_operand_register_family(cur_instr.op1): 569 | if cur_instr.opcode in ["shl", "shr", "sar", "sal", "ror", "rol", "rcr", "rcl"]: 570 | self.score += 1.5 571 | else: 572 | self.score += 1.0 # Will be a static modification, otherwise it would have been rejected earlier 573 | elif "xchg" not in cur_instr.opcode and cur_instr.opcode != "pop": 574 | # The modification is to a "bystander register". static mods +0.5, non-static +1.0 575 | if cur_instr.op2 is not None and Instruction.get_operand_register_family(cur_instr.op2) is not None: 576 | self.score += 1.0 577 | else: 578 | self.score += 0.5 579 | 580 | def check_branch_target_of_operation(self): 581 | """ 582 | :return void: Increases gadget's score if the gadget has an intermediate instruction that performs certain 583 | operations on the indirect branch target register family. 584 | """ 585 | last_instr = self.instructions[len(self.instructions)-1] 586 | target_family = Instruction.get_operand_register_family(last_instr.op1) 587 | 588 | # Scan instructions to determine if they modify the target register family 589 | for i in range(len(self.instructions) - 1): 590 | cur_instr = self.instructions[i] 591 | 592 | # Ignore instructions that do not create values 593 | if not cur_instr.creates_value(): 594 | continue 595 | 596 | # Increase score by 3 for shift/rotate ops, and 2 for others 597 | if Instruction.get_operand_register_family(cur_instr.op1) == target_family: 598 | if cur_instr.opcode in ["shl", "shr", "sar", "sal", "ror", "rol", "rcr", "rcl"]: 599 | self.score += 3.0 600 | else: # All other modifications to target register 601 | self.score += 2.0 602 | 603 | def check_memory_writes(self): 604 | """ 605 | :return void: Increases gadget's score if the gadget has an instruction that writes to memory. 606 | """ 607 | # Iterate through instructions except GPI 608 | for i in range(len(self.instructions)-1): 609 | cur_instr = self.instructions[i] 610 | 611 | # Ignore instructions that do not create values 612 | if not cur_instr.creates_value(): 613 | continue 614 | 615 | # Have to check both operands for xchg instrucitons 616 | if "xchg" in cur_instr.opcode and ("[" in cur_instr.op1 or "[" in cur_instr.op2): 617 | self.score += 1.0 618 | elif cur_instr.op1 is not None and "[" in cur_instr.op1: 619 | self.score += 1.0 620 | -------------------------------------------------------------------------------- /src/static_analyzer/GadgetSet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gadget Set Class 3 | """ 4 | 5 | # Standard Library Imports 6 | import subprocess 7 | from pathlib import Path 8 | import os 9 | 10 | # Third Party Imports 11 | import angr 12 | 13 | # Local Imports 14 | from static_analyzer.Gadget import Gadget 15 | from static_analyzer.Instruction import Instruction 16 | 17 | 18 | class GadgetSet(object): 19 | """ 20 | The GadgetSet class is initialized from a binary file and records information about the utility and availability 21 | of gadgets present in the binary's encoding. 22 | """ 23 | 24 | def __init__(self, name, filepath, createCFG, output_console): 25 | """ 26 | GadgetSet constructor 27 | :param str name: Name for the gadget set 28 | :param str filepath: Path to the file on disk. 29 | :param boolean createCFG: whether or not to use angr to create a CFG. 30 | :param boolean output_console: Indicates whether or not to print info when computed 31 | """ 32 | 33 | # Determine how many binaries will make up the gadget set 34 | self.binaries = [] 35 | self.binary_path = Path(os.path.expanduser(filepath)) 36 | if not self.binary_path.exists(): 37 | raise FileNotFoundError(filepath) 38 | elif os.path.isfile(self.binary_path): 39 | self.binaries.append(filepath) 40 | elif os.path.isdir(self.binary_path): 41 | # Walk the directory and add all files to list of binaries. 42 | for fp in os.listdir(self.binary_path): 43 | full_fp = os.path.join(self.binary_path, fp) 44 | if os.path.isfile(full_fp): 45 | self.binaries.append(full_fp) 46 | 47 | self.name = name 48 | self.cnt_rejected = 0 49 | self.cnt_duplicate = 0 50 | 51 | # Init the CFG with angr for finding functions 52 | if createCFG and len(self.binaries) == 1: 53 | try: 54 | proj = angr.Project(filepath, main_opts={'base_addr': 0}, load_options={'auto_load_libs': False}) 55 | self.cfg = proj.analyses.CFG() 56 | self.cfg.normalize() 57 | except Exception as e: 58 | print(str(e)) 59 | else: 60 | self.cfg = None 61 | 62 | # Initialize functional gadget type lists 63 | self.allGadgets = [] 64 | self.ROPGadgets = [] 65 | self.JOPGadgets = [] 66 | self.COPGadgets = [] 67 | 68 | # Initialize special purpose gadget type lists 69 | self.SyscallGadgets = [] 70 | self.JOPDispatchers = [] 71 | self.JOPDataLoaders = [] 72 | self.JOPInitializers = [] 73 | self.JOPTrampolines = [] 74 | self.COPDispatchers = [] 75 | self.COPStrongTrampolines = [] 76 | self.COPIntrastackPivots = [] 77 | self.COPDataLoaders = [] 78 | self.COPInitializers = [] 79 | 80 | # Initialize total and average quality scores 81 | self.total_ROP_score = 0.0 82 | self.total_JOP_score = 0.0 83 | self.total_COP_score = 0.0 84 | self.averageROPQuality = 0.0 85 | self.averageJOPQuality = 0.0 86 | self.averageCOPQuality = 0.0 87 | self.average_functional_quality = 0.0 88 | 89 | # Run ROPgadget to populate total gadget set (includes duplicates and multi-branch gadgets) 90 | for fp in self.binaries: 91 | self.parse_gadgets(fp, GadgetSet.runROPgadget(fp, "--all --multibr")) 92 | 93 | # Reject unusable gadgets, sort gadgets into their appropriate category sets, score gadgets 94 | for gadget in self.allGadgets: 95 | self.analyze_gadget(gadget) 96 | 97 | # Calculate gadget set counts / quality metrics 98 | self.total_sp_gadgets = 0 99 | self.total_sp_types = 0 100 | if len(self.SyscallGadgets) > 0: 101 | self.total_sp_types += 1 102 | self.total_sp_gadgets += len(self.SyscallGadgets) 103 | if len(self.JOPInitializers) > 0: 104 | self.total_sp_types += 1 105 | self.total_sp_gadgets += len(self.JOPInitializers) 106 | if len(self.JOPTrampolines) > 0: 107 | self.total_sp_types += 1 108 | self.total_sp_gadgets += len(self.JOPTrampolines) 109 | if len(self.JOPDispatchers) > 0: 110 | self.total_sp_types += 1 111 | self.total_sp_gadgets += len(self.JOPDispatchers) 112 | if len(self.JOPDataLoaders) > 0: 113 | self.total_sp_types += 1 114 | self.total_sp_gadgets += len(self.JOPDataLoaders) 115 | if len(self.COPDataLoaders) > 0: 116 | self.total_sp_types += 1 117 | self.total_sp_gadgets += len(self.COPDataLoaders) 118 | if len(self.COPDispatchers) > 0: 119 | self.total_sp_types += 1 120 | self.total_sp_gadgets += len(self.COPDispatchers) 121 | if len(self.COPInitializers) > 0: 122 | self.total_sp_types += 1 123 | self.total_sp_gadgets += len(self.COPInitializers) 124 | if len(self.COPStrongTrampolines) > 0: 125 | self.total_sp_types += 1 126 | self.total_sp_gadgets += len(self.COPStrongTrampolines) 127 | if len(self.COPIntrastackPivots) > 0: 128 | self.total_sp_types += 1 129 | self.total_sp_gadgets += len(self.COPIntrastackPivots) 130 | 131 | self.total_functional_gadgets = len(self.ROPGadgets) + len(self.JOPGadgets) + len(self.COPGadgets) 132 | self.total_unique_gadgets = self.total_sp_gadgets + self.total_functional_gadgets 133 | 134 | self.total_score = self.total_ROP_score + self.total_JOP_score + self.total_COP_score 135 | 136 | if self.total_ROP_score != 0.0: 137 | self.averageROPQuality = self.total_ROP_score / len(self.ROPGadgets) 138 | if self.total_JOP_score != 0.0: 139 | self.averageJOPQuality = self.total_JOP_score / len(self.JOPGadgets) 140 | if self.total_COP_score != 0.0: 141 | self.averageCOPQuality = self.total_COP_score / len(self.COPGadgets) 142 | if self.total_functional_gadgets != 0: 143 | self.average_functional_quality = self.total_score / self.total_functional_gadgets 144 | 145 | # Scan ROP gadgets to determine set expressivity 146 | self.practical_ROP = [False for i in range(11)] 147 | self.practical_ASLR_ROP = [False for i in range(35)] 148 | self.turing_complete_ROP = [False for i in range(17)] 149 | quality_threshold = 4.0 150 | 151 | for gadget in self.ROPGadgets: 152 | if gadget.score <= quality_threshold: 153 | self.classify_gadget(gadget) 154 | 155 | # Perform a secondary scan of JOP gadgets that can be used instead of some ROP gadgets 156 | for gadget in self.JOPGadgets: 157 | self.classify_JOP_gadget(gadget) 158 | 159 | # Calculate satisfaction scores 160 | self.practical_ROP_expressivity = sum(self.practical_ROP) 161 | self.practical_ASLR_ROP_expressivity = sum(self.practical_ASLR_ROP) 162 | self.turing_complete_ROP_expressivity = sum(self.turing_complete_ROP) 163 | 164 | if output_console: 165 | self.print_stats() 166 | 167 | def print_stats(self): 168 | print(" Gadget Set Stats for " + self.name) 169 | print(" Total number of all gadgets: " + str(len(self.allGadgets))) 170 | print(" Number of rejected gadgets: " + str(self.cnt_rejected)) 171 | print(" Number of duplicate gadgets: " + str(self.cnt_duplicate)) 172 | print(" Unique ROP gadgets: " + str(len(self.ROPGadgets))) 173 | print(" Unique JOP gadgets: " + str(len(self.JOPGadgets))) 174 | print(" Unique COP gadgets: " + str(len(self.COPGadgets))) 175 | print(" Unique SYS gadgets: " + str(len(self.SyscallGadgets))) 176 | print(" Unique JOP dispatcher gadgets: " + str(len(self.JOPDispatchers))) 177 | print(" Unique JOP initializer gadgets: " + str(len(self.JOPInitializers))) 178 | print(" Unique JOP dataloader gadgets: " + str(len(self.JOPDataLoaders))) 179 | print(" Unique JOP trampoline gadgets: " + str(len(self.JOPTrampolines))) 180 | print(" Unique COP dispatcher gadgets: " + str(len(self.COPDispatchers))) 181 | print(" Unique COP initializer gadgets: " + str(len(self.COPInitializers))) 182 | print(" Unique COP dataloader gadgets: " + str(len(self.COPDataLoaders))) 183 | print(" Unique COP strong trampoline gadgets: " + str(len(self.COPStrongTrampolines))) 184 | print(" Unique COP intrastack pivot gadgets: " + str(len(self.COPIntrastackPivots))) 185 | print(" -----") 186 | print(" ROP - Total Score: " + str(self.total_ROP_score) + " Average Score: " + str(self.averageROPQuality)) 187 | print(" JOP - Total Score: " + str(self.total_JOP_score) + " Average Score: " + str(self.averageJOPQuality)) 188 | print(" COP - Total Score: " + str(self.total_COP_score) + " Average Score: " + str(self.averageCOPQuality)) 189 | print(" -----") 190 | print(" Prac ROP - Expressivity: " + str(self.practical_ROP_expressivity)) 191 | print(" ASLR-proof Prac ROP - Expressivity: " + str(self.practical_ASLR_ROP_expressivity)) 192 | print(" Simple Turing Complete - Expressivity: " + str(self.turing_complete_ROP_expressivity)) 193 | 194 | def parse_gadgets(self, filepath, output): 195 | """ 196 | Converts raw ROPgadget output into a list of Gadget objects. 197 | :param str output: Plain text output from run of ROPgadget 198 | :return: List of Gadget objects 199 | """ 200 | # Iterate through each line and generate a gadget object 201 | lines = output.split("\n") 202 | for line in lines: 203 | # Exclude header/footer information 204 | if line == "Gadgets information" or \ 205 | line == "============================================================" or \ 206 | line == "" or \ 207 | line.startswith("Unique gadgets found"): 208 | continue 209 | else: 210 | self.allGadgets.append(Gadget(filepath, line)) 211 | 212 | @staticmethod 213 | def runROPgadget(filepath, flags): 214 | """ 215 | Runs ROPgadget on the binary at filepath with flags passed. 216 | :param str filepath: path to binary to analyze 217 | :param str flags: string containing the flags for execution 218 | :return: Output from the ROPgadget command as a standard string, None if the data was not collected as expected. 219 | """ 220 | 221 | sub = subprocess.Popen("ROPgadget --binary " + filepath + " " + flags, shell=True, stdout=subprocess.PIPE) 222 | subprocess_return = sub.stdout.read() 223 | return subprocess_return.decode("utf-8") 224 | 225 | def analyze_gadget(self, gadget): 226 | """ 227 | Analyzes a gadget to determine its properties 228 | :param Gadget gadget: gadget to analyze 229 | :return: None, but modifies GadgetSet collections and Gadget object members 230 | """ 231 | 232 | # Step 1: Eliminate useless gadgets, defined as: 233 | # 1) Gadgets that consist only of the GPI (SYSCALL gadgets excluded) 234 | # 2) Gadgets that have a first opcode that is not useful - we assume that the first instruction is part of the 235 | # desired operation to be performed (otherwise attacker would just use the shorter version) 236 | # 3) Gadgets that end in a call/jmp (ROPgadget should not include these in the first place) 237 | # 4) Gadgets that create values in segment or extension registers, or are RIP-relative 238 | # 5) Gadgets ending in returns with offsets that are not byte aligned or greater than 32 bytes 239 | # 6) Gadgets containing ring-0 instructions / operands 240 | # 7) Gadgets that contain an intermediate GPI/interrupt (ROPgadget should not include these in the first place) 241 | # 8) ROP Gadgets that perform non-static assignments to the stack pointer register 242 | # 9) JOP/COP Gadgets that overwrite the target of and indirect branch GPI 243 | # 10) JOP/COP gadgets that are RIP-relative 244 | # 11) Syscall gadgets that end in an interrupt handler that is not 0x80 (ROPgadget should not include these) 245 | # 12) Gadgets that create value in the first instruction only to overwrite that value before the GPI 246 | # 13) Gadgets that contain intermediate static calls 247 | if gadget.is_gpi_only() or gadget.is_useless_op() or gadget.is_invalid_branch() or \ 248 | gadget.creates_unusable_value() or gadget.has_invalid_ret_offset() or gadget.contains_unusable_op() or \ 249 | gadget.contains_intermediate_GPI() or gadget.clobbers_stack_pointer() or \ 250 | gadget.is_rip_relative_indirect_branch() or gadget.clobbers_indirect_target() or \ 251 | gadget.has_invalid_int_handler() or gadget.clobbers_created_value() or gadget.contains_static_call(): 252 | self.cnt_rejected += 1 253 | return 254 | 255 | # Step 2: Sort the gadget by type. Gadget type determined by GPI and secondary check for S.P. gadgets. Scoring 256 | # is only performed for unique functional gadgets. 257 | gpi = gadget.instructions[len(gadget.instructions)-1].opcode 258 | 259 | if gpi.startswith("ret"): 260 | if self.add_if_unique(gadget, self.ROPGadgets): 261 | # Determine score, first checking ROP-specific side constraints 262 | gadget.check_sp_target_of_operation() # increase score if stack pointer family is target of certain ops 263 | gadget.check_contains_leave() # +2 if gadget contains an intermediate "leave" instruction 264 | gadget.check_negative_sp_offsets() # +2 if gadget's cumulative stack pointer offsets are negative 265 | 266 | # Next check general side-constraints 267 | gadget.check_contains_conditional_op() # increase score if gadget contains conditional operations 268 | gadget.check_register_ops() # increases score for ops on value and bystander register 269 | gadget.check_memory_writes() # increases score for each memory write in the gadget 270 | 271 | self.total_ROP_score += gadget.score 272 | 273 | elif gpi.startswith("jmp"): 274 | if gadget.is_JOP_COP_dispatcher(): 275 | self.add_if_unique(gadget, self.JOPDispatchers) 276 | elif gadget.is_JOP_COP_dataloader(): 277 | self.add_if_unique(gadget, self.JOPDataLoaders) 278 | elif gadget.is_JOP_initializer(): 279 | self.add_if_unique(gadget, self.JOPInitializers) 280 | elif gadget.is_JOP_trampoline(): 281 | self.add_if_unique(gadget, self.JOPTrampolines) 282 | else: 283 | if self.add_if_unique(gadget, self.JOPGadgets): 284 | # Determine score, first checking JOP-specific side constraints 285 | gadget.check_branch_target_of_operation() # increase score if branch register is target of ops 286 | 287 | # Next check general side-constraints 288 | gadget.check_contains_conditional_op() # increase score if gadget contains conditional operations 289 | gadget.check_register_ops() # increases score for ops on value and bystander register 290 | gadget.check_memory_writes() # increases score for each memory write in the gadget 291 | 292 | self.total_JOP_score += gadget.score 293 | 294 | elif gpi.startswith("call"): 295 | if gadget.is_JOP_COP_dispatcher(): 296 | self.add_if_unique(gadget, self.COPDispatchers) 297 | elif gadget.is_JOP_COP_dataloader(): 298 | self.add_if_unique(gadget, self.COPDataLoaders) 299 | elif gadget.is_COP_initializer(): 300 | self.add_if_unique(gadget, self.COPInitializers) 301 | elif gadget.is_COP_strong_trampoline(): 302 | self.add_if_unique(gadget, self.COPStrongTrampolines) 303 | elif gadget.is_COP_intrastack_pivot(): 304 | self.add_if_unique(gadget, self.COPIntrastackPivots) 305 | else: 306 | if self.add_if_unique(gadget, self.COPGadgets): 307 | # Determine score, first checking COP-specific side constraints 308 | gadget.check_branch_target_of_operation() # increase score if branch register is target of ops 309 | 310 | # Next check general side-constraints 311 | gadget.check_contains_conditional_op() # increase score if gadget contains conditional operations 312 | gadget.check_register_ops() # increases score for ops on value and bystander register 313 | gadget.check_memory_writes() # increases score for each memory write in the gadget 314 | 315 | self.total_COP_score += gadget.score 316 | else: 317 | self.add_if_unique(gadget, self.SyscallGadgets) 318 | 319 | def add_if_unique(self, gadget, collection): 320 | for rhs in collection: 321 | if gadget.is_duplicate(rhs): 322 | self.cnt_duplicate += 1 323 | return False 324 | collection.append(gadget) 325 | return True 326 | 327 | def getFunction(self, rop_addr): 328 | rop_addr = int(rop_addr, 16) 329 | try: 330 | rop_function = self.cfg.functions.floor_func(rop_addr).name 331 | except Exception as e: 332 | print(str(e)) 333 | return 334 | if rop_function: 335 | return rop_function 336 | else: 337 | return None 338 | 339 | def classify_gadget(self, gadget): 340 | """ 341 | Analyzes a gadget to determine which expressivity classes it satisfies 342 | :param Gadget gadget: gadget to analyze 343 | :return: None, but modifies Gadget expressivity collections 344 | """ 345 | first_instr = gadget.instructions[0] 346 | opcode = first_instr.opcode 347 | op1 = first_instr.op1 348 | op2 = first_instr.op2 349 | op1_family = Instruction.get_word_operand_register_family(op1) 350 | op2_family = Instruction.get_word_operand_register_family(op2) 351 | 352 | # For performance, iterate through the expressivity classes and perform analysis. Analysis rules should 353 | # set as many classes as possible. 354 | if self.practical_ROP[0] is False: 355 | if opcode == "dec" and op1_family in [0, 5] and "[" not in op1: 356 | self.practical_ROP[0] = True 357 | 358 | # Also satisfies: 359 | self.turing_complete_ROP[0] = True 360 | self.practical_ASLR_ROP[9] = True 361 | 362 | if self.practical_ROP[1] is False: 363 | if opcode == "inc" and op1_family in [0, 5] and "[" not in op1: 364 | self.practical_ROP[1] = True 365 | 366 | # Also satisfies: 367 | self.turing_complete_ROP[0] = True 368 | self.practical_ASLR_ROP[8] = True 369 | 370 | if self.practical_ROP[2] is False: 371 | if opcode == "pop" and op1_family in [0, 5] and "[" not in op1: 372 | self.practical_ROP[2] = True 373 | 374 | # Also satisfies: 375 | self.turing_complete_ROP[1] = True 376 | self.practical_ASLR_ROP[5] = True 377 | 378 | if self.practical_ROP[3] is False: 379 | if (opcode == "pop" and op1_family == 4 and "[" not in op1) or \ 380 | (opcode in ["xchg", "move"] and op1_family == 4 and op2_family in [0, 5] 381 | and "[" not in op1 and "[" not in op2) or \ 382 | (opcode == "lea" and op1_family == 4 and op2_family in [0, 5] 383 | and "+" not in op2 and "-" not in op2 and "*" not in op2) or \ 384 | (opcode == "xchg" and op1_family in [0, 5] and op2_family == 4 and "[" not in op1 and "[" not in op2): 385 | self.practical_ROP[3] = True 386 | 387 | if self.practical_ROP[4] is False: 388 | if opcode == "xchg" and ((op1_family == 0 and op2_family == 5) or (op2_family == 0 and op1_family == 5)) \ 389 | and "[" not in op1 and "[" not in op2: 390 | self.practical_ROP[4] = True 391 | 392 | if self.practical_ROP[5] is False: 393 | if opcode == "push" and op1_family in [0, 4, 5] and "[" not in op1: 394 | self.practical_ROP[5] = True 395 | 396 | if self.practical_ROP[6] is False: 397 | if opcode in ["clc", "sahf"] or \ 398 | (opcode in ["test", "add", "adc", "sub", "sbb", "and", "or", "xor", "cmp"] and 399 | op1_family in [0, 4, 5] and op2_family in [0, 4, 5] and "[" not in op1 and "[" not in op2): 400 | self.practical_ROP[6] = True 401 | 402 | # Also satisfies: 403 | self.turing_complete_ROP[7] = True 404 | self.practical_ASLR_ROP[4] = True 405 | 406 | if self.practical_ROP[7] is False: 407 | if (opcode.startswith("stos") or opcode in ["mov", "add", "or"]) and "[" in op1 and "+" not in op1 and \ 408 | "-" not in op1 and "*" not in op1 and op1_family in [0, 4, 5] and op2_family in [0, 4, 5] and \ 409 | op1_family != op2_family: 410 | self.practical_ROP[7] = True 411 | 412 | # Also satisfies: 413 | self.turing_complete_ROP[6] = True 414 | self.practical_ASLR_ROP[2] = True 415 | 416 | if self.practical_ROP[8] is False: 417 | if (opcode.startswith("lods") or opcode in ["mov", "add", "adc", "sub", "sbb", "and", "or", "xor"]) and \ 418 | "[" in op2 and "+" not in op2 and "-" not in op2 and "*" not in op2 and op1_family in [0, 4, 5] and \ 419 | op2_family in [0, 4, 5] and op1_family != op2_family: 420 | self.practical_ROP[8] = True 421 | 422 | # Also satisfies: 423 | self.turing_complete_ROP[5] = True 424 | self.practical_ASLR_ROP[1] = True 425 | 426 | # NOTE: Single rule for two classes 427 | if self.practical_ROP[9] is False or self.practical_ASLR_ROP[7] is False: 428 | if opcode == "leave": 429 | self.practical_ROP[9] = True 430 | self.practical_ASLR_ROP[7] = True 431 | 432 | if self.practical_ROP[10] is False: 433 | if (opcode == "pop" and op1_family == 6 and "[" not in op1) or \ 434 | (opcode == "xchg" and op1_family is not None and op2_family is not None and op1_family != op2_family 435 | and (op1_family == 6 or op2_family == 6) and "[" not in op1 and "[" not in op2) or \ 436 | (opcode in ["add", "adc", "sub", "sbb"] and "[" not in op1 and op1_family == 6 and 437 | op2_family not in [None, 6] and "+" not in op2 and "-" not in op2 and "*" not in op2): 438 | self.practical_ROP[10] = True 439 | 440 | if self.turing_complete_ROP[0] is False: 441 | if opcode in ["inc", "dec"] and op1_family not in [None, 7] and "+" not in op1 and "-" not in op1 and \ 442 | "*" not in op1: 443 | self.turing_complete_ROP[0] = True 444 | 445 | if self.turing_complete_ROP[1] is False: 446 | if opcode == "pop" and op1_family not in [None, 7] and "[" not in op1: 447 | self.turing_complete_ROP[1] = True 448 | 449 | if self.turing_complete_ROP[2] is False: 450 | if opcode in ["add", "adc", "sub", "sbb"] and op1_family not in [None, 7] and "+" not in op1 and \ 451 | "-" not in op1 and "*" not in op1 and op2_family not in [None, 7] and "+" not in op2 and \ 452 | "-" not in op2 and "*" not in op2 and op1_family != op2_family: 453 | self.turing_complete_ROP[2] = True 454 | 455 | if self.turing_complete_ROP[3] is False: 456 | if (opcode == "xor" and op1_family not in [None, 7] and "+" not in op1 and "-" not in op1 and "*" not in op1 457 | and op2_family not in [None, 7] and "+" not in op2 and "-" not in op2 and "*" not in op2 458 | and op1_family != op2_family) or \ 459 | (opcode in ["neg", "not"] and op1_family not in [None, 7] and "+" not in op1 and "-" not in op1 460 | and "*" not in op1): 461 | self.turing_complete_ROP[3] = True 462 | 463 | if self.turing_complete_ROP[4] is False: 464 | if opcode in ["and", "or"] and op1_family not in [None, 7] and "+" not in op1 and \ 465 | "-" not in op1 and "*" not in op1 and op2_family not in [None, 7] and "+" not in op2 and \ 466 | "-" not in op2 and "*" not in op2 and op1_family != op2_family: 467 | self.turing_complete_ROP[4] = True 468 | 469 | if self.turing_complete_ROP[5] is False: 470 | if (opcode.startswith("lods") or opcode in ["mov", "add", "adc", "sub", "sbb", "and", "or", "xor"]) and \ 471 | "[" in op2 and "+" not in op2 and "-" not in op2 and "*" not in op2 and \ 472 | op1_family not in [None, 7] and op2_family not in [None, 7] and op1_family != op2_family: 473 | self.turing_complete_ROP[5] = True 474 | 475 | if self.turing_complete_ROP[6] is False: 476 | if (opcode.startswith("stos") or opcode in ["mov", "add", "or"]) and "[" in op1 and "+" not in op1 and \ 477 | "-" not in op1 and "*" not in op1 and op1_family not in [None, 7] and op2_family not in [None, 7] and \ 478 | op1_family != op2_family: 479 | self.turing_complete_ROP[6] = True 480 | 481 | if self.turing_complete_ROP[7] is False: 482 | if opcode in ["clc", "sahf"] or \ 483 | (opcode in ["test", "add", "adc", "sub", "sbb", "and", "or", "xor", "cmp"] and 484 | op1_family not in [None, 7] and op2_family not in [None, 7] and "+" not in op1 and "-" not in op1 and 485 | "*" not in op1 and "+" not in op2 and "-" not in op2 and "*" not in op2 and op1_family != op2_family): 486 | self.turing_complete_ROP[7] = True 487 | 488 | if self.turing_complete_ROP[8] is False: 489 | if opcode in ["add", "adc", "sub", "sbb"] and "[" not in op2 and op2_family == 7 and \ 490 | op1_family not in [None, 7] and "+" not in op1 and "-" not in op1 and "*" not in op1: 491 | self.turing_complete_ROP[8] = True 492 | 493 | if self.turing_complete_ROP[9] is False: 494 | if (opcode == "pop" and op1_family == 7 and "[" not in op1) or \ 495 | (opcode == "xchg" and op1_family is not None and op2_family is not None and op1_family != op2_family 496 | and (op1_family == 7 or op2_family == 7) and "[" not in op1 and "[" not in op2) or \ 497 | (opcode in ["add", "adc", "sub", "sbb"] and "[" not in op1 and op1_family == 7 and 498 | op2_family not in [None, 7] and "+" not in op2 and "-" not in op2 and "*" not in op2): 499 | self.turing_complete_ROP[9] = True 500 | 501 | if self.turing_complete_ROP[10] is False: 502 | if opcode in ["lahf", "pushf"] or \ 503 | (opcode in ["adc", "sbb"] and op1_family not in [None, 7] and op2_family not in [None, 7] and 504 | "+" not in op1 and "-" not in op1 and "*" not in op1 and 505 | "+" not in op2 and "-" not in op2 and "*" not in op2 and op1_family != op2_family): 506 | self.turing_complete_ROP[10] = True 507 | 508 | # Next 6 classes have common and very specific requirements, check once 509 | if opcode == "xchg" and "[" not in op1 and "[" not in op2 and op1_family != op2_family: 510 | if self.turing_complete_ROP[11] is False: 511 | if op1_family in [0, 1] and op2_family in [0, 1]: 512 | self.turing_complete_ROP[11] = True 513 | 514 | if self.turing_complete_ROP[12] is False: 515 | if op1_family in [0, 2] and op2_family in [0, 2]: 516 | self.turing_complete_ROP[12] = True 517 | 518 | if self.turing_complete_ROP[13] is False: 519 | if op1_family in [0, 3] and op2_family in [0, 3]: 520 | self.turing_complete_ROP[13] = True 521 | 522 | if self.turing_complete_ROP[14] is False: 523 | if op1_family in [0, 6] and op2_family in [0, 6]: 524 | self.turing_complete_ROP[14] = True 525 | 526 | if self.turing_complete_ROP[15] is False: 527 | if op1_family in [0, 4] and op2_family in [0, 4]: 528 | self.turing_complete_ROP[15] = True 529 | 530 | if self.turing_complete_ROP[16] is False: 531 | if op1_family in [0, 5] and op2_family in [0, 5]: 532 | self.turing_complete_ROP[16] = True 533 | 534 | if self.practical_ASLR_ROP[0] is False: 535 | if opcode == "push" and op1_family not in [None, 6, 7] and "[" not in op1: 536 | self.practical_ASLR_ROP[0] = True 537 | 538 | if self.practical_ASLR_ROP[1] is False: 539 | if (opcode.startswith("lods") or opcode in ["mov", "add", "adc", "sub", "sbb", "and", "or", "xor"]) and \ 540 | "[" in op2 and "+" not in op2 and "-" not in op2 and "*" not in op2 and \ 541 | op1_family not in [None, 7] and op2_family not in [None, 6, 7] and op1_family != op2_family: 542 | self.practical_ASLR_ROP[1] = True 543 | 544 | if self.practical_ASLR_ROP[2] is False: 545 | if (opcode.startswith("stos") or opcode == "mov") and "[" in op1 and "+" not in op1 and \ 546 | "-" not in op1 and "*" not in op1 and op1_family not in [None, 7] and op2_family not in [None, 7] and \ 547 | op1_family != op2_family: 548 | self.practical_ASLR_ROP[2] = True 549 | 550 | if self.practical_ASLR_ROP[3] is False: 551 | if opcode in ["mov", "add", "adc", "and", "or", "xor"] and "[" not in op1 and "[" not in op2 and \ 552 | op1_family not in [None, 7] and op2_family == 7: 553 | self.practical_ASLR_ROP[3] = True 554 | 555 | if self.practical_ASLR_ROP[4] is False: 556 | if opcode in ["clc", "sahf"] or \ 557 | (opcode in ["test", "add", "adc", "sub", "sbb", "and", "or", "xor", "cmp"] and 558 | op1_family not in [None, 7] and op2_family not in [None, 7] and "[" not in op1 and "[" not in op2): 559 | self.practical_ASLR_ROP[4] = True 560 | 561 | if self.practical_ASLR_ROP[5] is False: 562 | if opcode == "pop" and op1_family in [0, 4, 5] and "[" not in op1: 563 | self.practical_ASLR_ROP[5] = True 564 | 565 | if self.practical_ASLR_ROP[6] is False: 566 | if opcode == "pop" and op1_family in [1, 2, 3, 6] and "[" not in op1: 567 | self.practical_ASLR_ROP[6] = True 568 | 569 | # NOTE class 8 (index 7) is combined above 570 | 571 | if self.practical_ASLR_ROP[8] is False: 572 | if opcode == "inc" and op1_family not in [None, 7] and "[" not in op1: 573 | self.practical_ASLR_ROP[8] = True 574 | 575 | if self.practical_ASLR_ROP[9] is False: 576 | if opcode == "dec" and op1_family not in [None, 7] and "[" not in op1: 577 | self.practical_ASLR_ROP[9] = True 578 | 579 | if self.practical_ASLR_ROP[10] is False: 580 | if opcode in ["add", "adc", "sub", "sbb"] and op1_family not in [None, 7] and "[" not in op1 and \ 581 | op2_family not in [None, 7] and "[" not in op2 and op1_family != op2_family: 582 | self.practical_ASLR_ROP[10] = True 583 | 584 | # For the next 24 classes, some fairly common gadgets will satisfy many classes and significant 585 | # overlap in definitions exists. Check these without first seeing if the class is satisfied 586 | # POP AX 587 | if opcode == "pop" and "[" not in op1 and op1_family == 0: 588 | self.practical_ASLR_ROP[13] = True 589 | self.practical_ASLR_ROP[17] = True 590 | self.practical_ASLR_ROP[21] = True 591 | self.practical_ASLR_ROP[25] = True 592 | self.practical_ASLR_ROP[29] = True 593 | self.practical_ASLR_ROP[33] = True 594 | 595 | # XCHG AX with another GPR 596 | if opcode == "xchg" and "[" not in op1 and "[" not in op2: 597 | if op1_family == 0: 598 | if op2_family == 1: 599 | self.practical_ASLR_ROP[11] = True 600 | self.practical_ASLR_ROP[12] = True 601 | self.practical_ASLR_ROP[13] = True 602 | self.practical_ASLR_ROP[14] = True 603 | elif op2_family == 2: 604 | self.practical_ASLR_ROP[15] = True 605 | self.practical_ASLR_ROP[16] = True 606 | self.practical_ASLR_ROP[17] = True 607 | self.practical_ASLR_ROP[18] = True 608 | elif op2_family == 3: 609 | self.practical_ASLR_ROP[19] = True 610 | self.practical_ASLR_ROP[20] = True 611 | self.practical_ASLR_ROP[21] = True 612 | self.practical_ASLR_ROP[22] = True 613 | elif op2_family == 6: 614 | self.practical_ASLR_ROP[23] = True 615 | self.practical_ASLR_ROP[24] = True 616 | self.practical_ASLR_ROP[25] = True 617 | self.practical_ASLR_ROP[26] = True 618 | elif op2_family == 4: 619 | self.practical_ASLR_ROP[27] = True 620 | self.practical_ASLR_ROP[28] = True 621 | self.practical_ASLR_ROP[29] = True 622 | self.practical_ASLR_ROP[30] = True 623 | elif op2_family == 5: 624 | self.practical_ASLR_ROP[31] = True 625 | self.practical_ASLR_ROP[32] = True 626 | self.practical_ASLR_ROP[33] = True 627 | self.practical_ASLR_ROP[34] = True 628 | 629 | elif op2_family == 0: 630 | if op1_family == 1: 631 | self.practical_ASLR_ROP[11] = True 632 | self.practical_ASLR_ROP[12] = True 633 | self.practical_ASLR_ROP[13] = True 634 | self.practical_ASLR_ROP[14] = True 635 | elif op1_family == 2: 636 | self.practical_ASLR_ROP[15] = True 637 | self.practical_ASLR_ROP[16] = True 638 | self.practical_ASLR_ROP[17] = True 639 | self.practical_ASLR_ROP[18] = True 640 | elif op1_family == 3: 641 | self.practical_ASLR_ROP[19] = True 642 | self.practical_ASLR_ROP[20] = True 643 | self.practical_ASLR_ROP[21] = True 644 | self.practical_ASLR_ROP[22] = True 645 | elif op1_family == 6: 646 | self.practical_ASLR_ROP[23] = True 647 | self.practical_ASLR_ROP[24] = True 648 | self.practical_ASLR_ROP[25] = True 649 | self.practical_ASLR_ROP[26] = True 650 | elif op1_family == 4: 651 | self.practical_ASLR_ROP[27] = True 652 | self.practical_ASLR_ROP[28] = True 653 | self.practical_ASLR_ROP[29] = True 654 | self.practical_ASLR_ROP[30] = True 655 | elif op1_family == 5: 656 | self.practical_ASLR_ROP[31] = True 657 | self.practical_ASLR_ROP[32] = True 658 | self.practical_ASLR_ROP[33] = True 659 | self.practical_ASLR_ROP[34] = True 660 | 661 | # MOV between AX and another GPR 662 | if opcode == "mov" and "[" not in op1 and "[" not in op2: 663 | if op1_family == 0: 664 | if op2_family == 1: 665 | self.practical_ASLR_ROP[13] = True 666 | self.practical_ASLR_ROP[14] = True 667 | elif op2_family == 2: 668 | self.practical_ASLR_ROP[17] = True 669 | self.practical_ASLR_ROP[18] = True 670 | elif op2_family == 3: 671 | self.practical_ASLR_ROP[21] = True 672 | self.practical_ASLR_ROP[22] = True 673 | elif op2_family == 6: 674 | self.practical_ASLR_ROP[25] = True 675 | self.practical_ASLR_ROP[26] = True 676 | elif op2_family == 4: 677 | self.practical_ASLR_ROP[29] = True 678 | self.practical_ASLR_ROP[30] = True 679 | elif op2_family == 5: 680 | self.practical_ASLR_ROP[33] = True 681 | self.practical_ASLR_ROP[34] = True 682 | 683 | elif op2_family == 0: 684 | if op1_family == 1: 685 | self.practical_ASLR_ROP[11] = True 686 | self.practical_ASLR_ROP[12] = True 687 | elif op1_family == 2: 688 | self.practical_ASLR_ROP[15] = True 689 | self.practical_ASLR_ROP[16] = True 690 | elif op1_family == 3: 691 | self.practical_ASLR_ROP[19] = True 692 | self.practical_ASLR_ROP[20] = True 693 | elif op1_family == 6: 694 | self.practical_ASLR_ROP[23] = True 695 | self.practical_ASLR_ROP[24] = True 696 | elif op1_family == 4: 697 | self.practical_ASLR_ROP[27] = True 698 | self.practical_ASLR_ROP[28] = True 699 | elif op1_family == 5: 700 | self.practical_ASLR_ROP[31] = True 701 | self.practical_ASLR_ROP[32] = True 702 | 703 | # ["add", "adc", "sub", "sbb", "and", "or", "xor"] between AX and another GPR 704 | if opcode in ["add", "adc", "sub", "sbb", "and", "or", "xor"] and "[" not in op1 and "[" not in op2: 705 | if op1_family == 0: 706 | if op2_family == 1: 707 | self.practical_ASLR_ROP[14] = True 708 | elif op2_family == 2: 709 | self.practical_ASLR_ROP[18] = True 710 | elif op2_family == 3: 711 | self.practical_ASLR_ROP[22] = True 712 | elif op2_family == 6: 713 | self.practical_ASLR_ROP[26] = True 714 | elif op2_family == 4: 715 | self.practical_ASLR_ROP[30] = True 716 | elif op2_family == 5: 717 | self.practical_ASLR_ROP[34] = True 718 | 719 | elif op2_family == 0: 720 | if op1_family == 1: 721 | self.practical_ASLR_ROP[12] = True 722 | elif op1_family == 2: 723 | self.practical_ASLR_ROP[16] = True 724 | elif op1_family == 3: 725 | self.practical_ASLR_ROP[20] = True 726 | elif op1_family == 6: 727 | self.practical_ASLR_ROP[24] = True 728 | elif op1_family == 4: 729 | self.practical_ASLR_ROP[28] = True 730 | elif op1_family == 5: 731 | self.practical_ASLR_ROP[32] = True 732 | 733 | # Resume checks for individual classes 11, 15, 19, 23, 27, and 31. Others entirely checked entirely above 734 | if self.practical_ASLR_ROP[11] is False: 735 | if opcode == "pop" and "[" not in op1 and op1_family == 1: 736 | self.practical_ASLR_ROP[11] = True 737 | 738 | if self.practical_ASLR_ROP[15] is False: 739 | if opcode == "pop" and "[" not in op1 and op1_family == 2: 740 | self.practical_ASLR_ROP[15] = True 741 | 742 | if self.practical_ASLR_ROP[19] is False: 743 | if opcode == "pop" and "[" not in op1 and op1_family == 3: 744 | self.practical_ASLR_ROP[19] = True 745 | 746 | if self.practical_ASLR_ROP[23] is False: 747 | if opcode == "pop" and "[" not in op1 and op1_family == 6: 748 | self.practical_ASLR_ROP[23] = True 749 | 750 | if self.practical_ASLR_ROP[27] is False: 751 | if opcode == "pop" and "[" not in op1 and op1_family == 4: 752 | self.practical_ASLR_ROP[27] = True 753 | 754 | if self.practical_ASLR_ROP[31] is False: 755 | if opcode == "pop" and "[" not in op1 and op1_family == 5: 756 | self.practical_ASLR_ROP[31] = True 757 | 758 | def classify_JOP_gadget(self, gadget): 759 | """ 760 | Analyzes a gadget to determine which expressivity classes it satisfies 761 | :param Gadget gadget: gadget to analyze 762 | :return: None, but modifies Gadget expressivity collections 763 | """ 764 | last_instr = gadget.instructions[len(gadget.instructions)-1] 765 | op1 = last_instr.op1 766 | op1_family = Instruction.get_word_operand_register_family(op1) 767 | 768 | if self.practical_ROP[3] is False: 769 | if "[" in op1 and op1_family in [0, 5] and "+" not in op1 and "-" not in op1 and "*" not in op1: 770 | self.practical_ROP[3] = True 771 | 772 | if self.practical_ROP[5] is False: 773 | if op1_family in [0, 5] and "+" not in op1 and "-" not in op1 and "*" not in op1: 774 | self.practical_ROP[5] = True 775 | 776 | if self.practical_ROP[7] is False: 777 | if "[" in op1 and op1_family in [0, 4, 5] and "+" not in op1 and "-" not in op1 and "*" not in op1: 778 | self.practical_ROP[7] = True 779 | 780 | if self.practical_ASLR_ROP[0] is False: 781 | if "[" not in op1 and op1_family not in [None, 6, 7]: 782 | self.practical_ASLR_ROP[0] = True 783 | 784 | if self.practical_ASLR_ROP[1] is False: 785 | if "[" in op1 and op1_family not in [None, 6, 7] and "+" not in op1 and "-" not in op1 and "*" not in op1: 786 | self.practical_ASLR_ROP[1] = True 787 | -------------------------------------------------------------------------------- /src/static_analyzer/GadgetStats.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gadget Stats class 3 | """ 4 | 5 | # Standard Library Imports 6 | 7 | # Third Party Imports 8 | 9 | # Local Imports 10 | from static_analyzer.GadgetSet import GadgetSet 11 | from static_analyzer.Gadget import Gadget 12 | 13 | 14 | class GadgetStats(object): 15 | """ 16 | The Gadget Stats class represents data resulting from the comparison of an original package's gadget set to the 17 | gadget set of its transformed variant. 18 | """ 19 | 20 | def __init__(self, original, variant, output_console, output_locality): 21 | """ 22 | GadgetStats constructor 23 | :param GadgetSet original: Gadget Set from the original package 24 | :param GadgetSet variant: Gadget Set from the variant package 25 | :param boolean output_console: Indicates whether or not to print info when computed 26 | :param boolean output_locality: Indicates whether or not to calculate gadget locality, which is CPU intensive 27 | """ 28 | self.original = original 29 | self.variant = variant 30 | self.name = original.name + " <-> " + variant.name 31 | 32 | # Gadget Count Differences and Reduction Percentages 33 | self.ROPCountDiff = len(original.ROPGadgets) - len(variant.ROPGadgets) 34 | if len(original.ROPGadgets) > 0: 35 | self.ROPCountReduction = self.ROPCountDiff / len(original.ROPGadgets) 36 | else: 37 | self.ROPCountReduction = 0 38 | 39 | self.JOPCountDiff = len(original.JOPGadgets) - len(variant.JOPGadgets) 40 | if len(original.JOPGadgets) > 0: 41 | self.JOPCountReduction = self.JOPCountDiff / len(original.JOPGadgets) 42 | else: 43 | self.JOPCountReduction = 0 44 | 45 | self.COPCountDiff = len(original.COPGadgets) - len(variant.COPGadgets) 46 | if len(original.COPGadgets) > 0: 47 | self.COPCountReduction = self.COPCountDiff / len(original.COPGadgets) 48 | else: 49 | self.COPCountReduction = 0 50 | 51 | self.SysCountDiff = len(original.SyscallGadgets) - len(variant.SyscallGadgets) 52 | if len(original.SyscallGadgets) > 0: 53 | self.SysCountReduction = self.SysCountDiff / len(original.SyscallGadgets) 54 | else: 55 | self.SysCountReduction = 0 56 | 57 | self.JOPDispatchersCountDiff = len(original.JOPDispatchers) - len(variant.JOPDispatchers) 58 | if len(original.JOPDispatchers) > 0: 59 | self.JOPDispatchersCountReduction = self.JOPDispatchersCountDiff / len(original.JOPDispatchers) 60 | else: 61 | self.JOPDispatchersCountReduction = 0 62 | 63 | self.JOPDataLoadersCountDiff = len(original.JOPDataLoaders) - len(variant.JOPDataLoaders) 64 | if len(original.JOPDataLoaders) > 0: 65 | self.JOPDataLoadersCountReduction = self.JOPDataLoadersCountDiff / len(original.JOPDataLoaders) 66 | else: 67 | self.JOPDataLoadersCountReduction = 0 68 | 69 | self.JOPInitializersCountDiff = len(original.JOPInitializers) - len(variant.JOPInitializers) 70 | if len(original.JOPInitializers) > 0: 71 | self.JOPInitializersCountReduction = self.JOPInitializersCountDiff / len(original.JOPInitializers) 72 | else: 73 | self.JOPInitializersCountReduction = 0 74 | 75 | self.JOPTrampolinesCountDiff = len(original.JOPTrampolines) - len(variant.JOPTrampolines) 76 | if len(original.JOPTrampolines) > 0: 77 | self.JOPTrampolinesCountReduction = self.JOPTrampolinesCountDiff / len(original.JOPTrampolines) 78 | else: 79 | self.JOPTrampolinesCountReduction = 0 80 | 81 | self.COPDispatchersCountDiff = len(original.COPDispatchers) - len(variant.COPDispatchers) 82 | if len(original.COPDispatchers) > 0: 83 | self.COPDispatchersCountReduction = self.COPDispatchersCountDiff / len(original.COPDispatchers) 84 | else: 85 | self.COPDispatchersCountReduction = 0 86 | 87 | self.COPDataLoadersCountDiff = len(original.COPDataLoaders) - len(variant.COPDataLoaders) 88 | if len(original.COPDataLoaders) > 0: 89 | self.COPDataLoadersCountReduction = self.COPDataLoadersCountDiff / len(original.COPDataLoaders) 90 | else: 91 | self.COPDataLoadersCountReduction = 0 92 | 93 | self.COPInitializersCountDiff = len(original.COPInitializers) - len(variant.COPInitializers) 94 | if len(original.COPInitializers) > 0: 95 | self.COPInitializersCountReduction = self.COPInitializersCountDiff / len(original.COPInitializers) 96 | else: 97 | self.COPInitializersCountReduction = 0 98 | 99 | self.COPStrongTrampolinesCountDiff = len(original.COPStrongTrampolines) - len(variant.COPStrongTrampolines) 100 | if len(original.COPStrongTrampolines) > 0: 101 | self.COPStrongTrampolinesCountReduction = self.COPStrongTrampolinesCountDiff / len(original.COPStrongTrampolines) 102 | else: 103 | self.COPStrongTrampolinesCountReduction = 0 104 | 105 | self.COPIntrastackPivotsCountDiff = len(original.COPIntrastackPivots) - len(variant.COPIntrastackPivots) 106 | if len(original.COPIntrastackPivots) > 0: 107 | self.COPIntrastackPivotsCountReduction = self.COPIntrastackPivotsCountDiff / len(original.COPIntrastackPivots) 108 | else: 109 | self.COPIntrastackPivotsCountReduction = 0 110 | 111 | # Gadget Introduction Counts and Percentages by type 112 | originalROPSet = GadgetStats.get_gadget_set(original.ROPGadgets) 113 | variantROPSet = GadgetStats.get_gadget_set(variant.ROPGadgets) 114 | commonROPSet = originalROPSet & variantROPSet 115 | ROPIntroducedSet = variantROPSet - commonROPSet 116 | if len(variantROPSet) > 0: 117 | self.ROPIntroductionRate = len(ROPIntroducedSet) / len(variantROPSet) 118 | else: 119 | self.ROPIntroductionRate = 0 120 | 121 | originalJOPSet = GadgetStats.get_gadget_set(original.JOPGadgets) 122 | variantJOPSet = GadgetStats.get_gadget_set(variant.JOPGadgets) 123 | commonJOPSet = originalJOPSet & variantJOPSet 124 | JOPIntroducedSet = variantJOPSet - commonJOPSet 125 | if len(variantJOPSet) > 0: 126 | self.JOPIntroductionRate = len(JOPIntroducedSet) / len(variantJOPSet) 127 | else: 128 | self.JOPIntroductionRate = 0 129 | 130 | originalCOPSet = GadgetStats.get_gadget_set(original.COPGadgets) 131 | variantCOPSet = GadgetStats.get_gadget_set(variant.COPGadgets) 132 | commonCOPSet = originalCOPSet & variantCOPSet 133 | COPIntroducedSet = variantCOPSet - commonCOPSet 134 | if len(variantCOPSet) > 0: 135 | self.COPIntroductionRate = len(COPIntroducedSet) / len(variantCOPSet) 136 | else: 137 | self.COPIntroductionRate = 0 138 | 139 | originalSysSet = GadgetStats.get_gadget_set(original.SyscallGadgets) 140 | variantSysSet = GadgetStats.get_gadget_set(variant.SyscallGadgets) 141 | commonSysSet = originalSysSet & variantSysSet 142 | sysIntroducedSet = variantSysSet - commonSysSet 143 | if len(variantSysSet) > 0: 144 | self.SysIntroductionRate = len(sysIntroducedSet) / len(variantSysSet) 145 | else: 146 | self.SysIntroductionRate = 0 147 | 148 | originalJOPDispatchersSet = GadgetStats.get_gadget_set(original.JOPDispatchers) 149 | variantJOPDispatchersSet = GadgetStats.get_gadget_set(variant.JOPDispatchers) 150 | commonJOPDispatchersSet = originalJOPDispatchersSet & variantJOPDispatchersSet 151 | JOPDispatchersIntroducedSet = variantJOPDispatchersSet - commonJOPDispatchersSet 152 | if len(variantJOPDispatchersSet) > 0: 153 | self.JOPDispatchersIntroductionRate = len(JOPDispatchersIntroducedSet) / len(variantJOPDispatchersSet) 154 | else: 155 | self.JOPDispatchersIntroductionRate = 0 156 | 157 | originalJOPDataLoadersSet = GadgetStats.get_gadget_set(original.JOPDataLoaders) 158 | variantJOPDataLoadersSet = GadgetStats.get_gadget_set(variant.JOPDataLoaders) 159 | commonJOPDataLoadersSet = originalJOPDataLoadersSet & variantJOPDataLoadersSet 160 | JOPDataLoadersIntroducedSet = variantJOPDataLoadersSet - commonJOPDataLoadersSet 161 | if len(variantJOPDataLoadersSet) > 0: 162 | self.JOPDataLoadersIntroductionRate = len(JOPDataLoadersIntroducedSet) / len(variantJOPDataLoadersSet) 163 | else: 164 | self.JOPDataLoadersIntroductionRate = 0 165 | 166 | originalJOPInitializersSet = GadgetStats.get_gadget_set(original.JOPInitializers) 167 | variantJOPInitializersSet = GadgetStats.get_gadget_set(variant.JOPInitializers) 168 | commonJOPInitializersSet = originalJOPInitializersSet & variantJOPInitializersSet 169 | JOPInitializersIntroducedSet = variantJOPInitializersSet - commonJOPInitializersSet 170 | if len(variantJOPInitializersSet) > 0: 171 | self.JOPInitializersIntroductionRate = len(JOPInitializersIntroducedSet) / len(variantJOPInitializersSet) 172 | else: 173 | self.JOPInitializersIntroductionRate = 0 174 | 175 | originalJOPTrampolinesSet = GadgetStats.get_gadget_set(original.JOPTrampolines) 176 | variantJOPTrampolinesSet = GadgetStats.get_gadget_set(variant.JOPTrampolines) 177 | commonJOPTrampolinesSet = originalJOPTrampolinesSet & variantJOPTrampolinesSet 178 | JOPTrampolinesIntroducedSet = variantJOPTrampolinesSet - commonJOPTrampolinesSet 179 | if len(variantJOPTrampolinesSet) > 0: 180 | self.JOPTrampolinesIntroductionRate = len(JOPTrampolinesIntroducedSet) / len(variantJOPTrampolinesSet) 181 | else: 182 | self.JOPTrampolinesIntroductionRate = 0 183 | 184 | originalCOPDispatchersSet = GadgetStats.get_gadget_set(original.COPDispatchers) 185 | variantCOPDispatchersSet = GadgetStats.get_gadget_set(variant.COPDispatchers) 186 | commonCOPDispatchersSet = originalCOPDispatchersSet & variantCOPDispatchersSet 187 | COPDispatchersIntroducedSet = variantCOPDispatchersSet - commonCOPDispatchersSet 188 | if len(variantCOPDispatchersSet) > 0: 189 | self.COPDispatchersIntroductionRate = len(COPDispatchersIntroducedSet) / len(variantCOPDispatchersSet) 190 | else: 191 | self.COPDispatchersIntroductionRate = 0 192 | 193 | originalCOPDataLoadersSet = GadgetStats.get_gadget_set(original.COPDataLoaders) 194 | variantCOPDataLoadersSet = GadgetStats.get_gadget_set(variant.COPDataLoaders) 195 | commonCOPDataLoadersSet = originalCOPDataLoadersSet & variantCOPDataLoadersSet 196 | COPDataLoadersIntroducedSet = variantCOPDataLoadersSet - commonCOPDataLoadersSet 197 | if len(variantCOPDataLoadersSet) > 0: 198 | self.COPDataLoadersIntroductionRate = len(COPDataLoadersIntroducedSet) / len(variantCOPDataLoadersSet) 199 | else: 200 | self.COPDataLoadersIntroductionRate = 0 201 | 202 | originalCOPInitializersSet = GadgetStats.get_gadget_set(original.COPInitializers) 203 | variantCOPInitializersSet = GadgetStats.get_gadget_set(variant.COPInitializers) 204 | commonCOPInitializersSet = originalCOPInitializersSet & variantCOPInitializersSet 205 | COPInitializersIntroducedSet = variantCOPInitializersSet - commonCOPInitializersSet 206 | if len(variantCOPInitializersSet) > 0: 207 | self.COPInitializersIntroductionRate = len(COPInitializersIntroducedSet) / len(variantCOPInitializersSet) 208 | else: 209 | self.COPInitializersIntroductionRate = 0 210 | 211 | originalCOPStrongTrampolinesSet = GadgetStats.get_gadget_set(original.COPStrongTrampolines) 212 | variantCOPStrongTrampolinesSet = GadgetStats.get_gadget_set(variant.COPStrongTrampolines) 213 | commonCOPStrongTrampolinesSet = originalCOPStrongTrampolinesSet & variantCOPStrongTrampolinesSet 214 | COPStrongTrampolinesIntroducedSet = variantCOPStrongTrampolinesSet - commonCOPStrongTrampolinesSet 215 | if len(variantCOPStrongTrampolinesSet) > 0: 216 | self.COPStrongTrampolinesIntroductionRate = len(COPStrongTrampolinesIntroducedSet) / len(variantCOPStrongTrampolinesSet) 217 | else: 218 | self.COPStrongTrampolinesIntroductionRate = 0 219 | 220 | originalCOPIntrastackPivotsSet = GadgetStats.get_gadget_set(original.COPIntrastackPivots) 221 | variantCOPIntrastackPivotsSet = GadgetStats.get_gadget_set(variant.COPIntrastackPivots) 222 | commonCOPIntrastackPivotsSet = originalCOPIntrastackPivotsSet & variantCOPIntrastackPivotsSet 223 | COPIntrastackPivotsIntroducedSet = variantCOPIntrastackPivotsSet - commonCOPIntrastackPivotsSet 224 | if len(variantCOPIntrastackPivotsSet) > 0: 225 | self.COPIntrastackPivotsIntroductionRate = len(COPIntrastackPivotsIntroducedSet) / len(variantCOPIntrastackPivotsSet) 226 | else: 227 | self.COPIntrastackPivotsIntroductionRate = 0 228 | 229 | # Total Set 230 | orig_total_set = originalROPSet | originalJOPSet | originalCOPSet | originalSysSet | \ 231 | originalJOPInitializersSet | originalJOPDispatchersSet | originalJOPDataLoadersSet | \ 232 | originalJOPTrampolinesSet | originalCOPStrongTrampolinesSet | originalCOPDataLoadersSet | \ 233 | originalCOPInitializersSet | originalCOPDispatchersSet | originalCOPIntrastackPivotsSet 234 | variant_total_set = variantROPSet | variantJOPSet | variantCOPSet | variantSysSet | \ 235 | variantJOPInitializersSet | variantJOPDispatchersSet | variantJOPDataLoadersSet | \ 236 | variantJOPTrampolinesSet | variantCOPStrongTrampolinesSet | variantCOPDataLoadersSet | \ 237 | variantCOPInitializersSet | variantCOPDispatchersSet | variantCOPIntrastackPivotsSet 238 | 239 | self.totalUniqueCountDiff = len(orig_total_set) - len(variant_total_set) 240 | if len(orig_total_set) > 0: 241 | self.totalUniqueCountReduction = self.totalUniqueCountDiff / len(orig_total_set) 242 | else: 243 | self.totalUniqueCountReduction = 0 244 | 245 | self.total_sp_count_diff = original.total_sp_gadgets - variant.total_sp_gadgets 246 | if original.total_sp_gadgets > 0: 247 | self.total_sp_reduction = self.total_sp_count_diff / original.total_sp_gadgets 248 | else: 249 | self.total_sp_reduction = 0 250 | 251 | total_common_set = orig_total_set & variant_total_set 252 | total_introduced_set = variant_total_set - total_common_set 253 | if len(variant_total_set) > 0: 254 | self.totalUniqueIntroductionRate = len(total_introduced_set) / len(variant_total_set) 255 | else: 256 | self.totalUniqueIntroductionRate = 0 257 | 258 | self.total_sp_type_reduction = original.total_sp_types - variant.total_sp_types 259 | 260 | # Satisfied classes count differences 261 | self.practical_ROP_exp_diff = original.practical_ROP_expressivity - variant.practical_ROP_expressivity 262 | self.practical_ASLR_ROP_exp_diff = original.practical_ASLR_ROP_expressivity - variant.practical_ASLR_ROP_expressivity 263 | self.turing_complete_ROP_exp_diff = original.turing_complete_ROP_expressivity - variant.turing_complete_ROP_expressivity 264 | 265 | # Calculate gadget locality 266 | if output_locality: 267 | local_gadgets = GadgetStats.findEqualGadgets(original.allGadgets, variant.allGadgets) 268 | unique_all_gadgets = len(set(repr(gadget) for gadget in variant.allGadgets)) 269 | self.gadgetLocality = local_gadgets / unique_all_gadgets 270 | else: 271 | self.gadgetLocality = None 272 | 273 | # Calculate gadget quality 274 | self.keptQualityROPCountDiff = len(original.ROPGadgets) - len(variant.ROPGadgets) 275 | self.keptQualityJOPCountDiff = len(original.JOPGadgets) - len(variant.JOPGadgets) 276 | self.keptQualityCOPCountDiff = len(original.COPGadgets) - len(variant.COPGadgets) 277 | self.total_functional_count_diff = original.total_functional_gadgets - variant.total_functional_gadgets 278 | 279 | self.averageROPQualityDiff = original.averageROPQuality - variant.averageROPQuality 280 | self.averageJOPQualityDiff = original.averageJOPQuality - variant.averageJOPQuality 281 | self.averageCOPQualityDiff = original.averageCOPQuality - variant.averageCOPQuality 282 | self.total_average_quality_diff = original.average_functional_quality - variant.average_functional_quality 283 | 284 | if output_console: 285 | self.printStats(output_locality) 286 | 287 | def printStats(self, output_locality): 288 | rate_format = "{:.1%}" 289 | print("======================================================================") 290 | print("Gadget Stats for " + self.name) 291 | print("======================================================================") 292 | print("Total Unique Gadgets:") 293 | print("Count Difference: " + str(self.totalUniqueCountDiff)) 294 | print("Reduction Rate: " + rate_format.format(self.totalUniqueCountReduction)) 295 | print("Introduction Rate: " + rate_format.format(self.totalUniqueIntroductionRate)) 296 | print("======================================================================") 297 | print("ROP Gadgets:") 298 | print("Count Difference: " + str(self.ROPCountDiff)) 299 | print("Reduction Rate: " + rate_format.format(self.ROPCountReduction)) 300 | print("Introduction Rate: " + rate_format.format(self.ROPIntroductionRate)) 301 | print("======================================================================") 302 | print("JOP Gadgets:") 303 | print("Count Difference: " + str(self.JOPCountDiff)) 304 | print("Reduction Rate: " + rate_format.format(self.JOPCountReduction)) 305 | print("Introduction Rate: " + rate_format.format(self.JOPIntroductionRate)) 306 | print("======================================================================") 307 | print("COP Gadgets:") 308 | print("Count Difference: " + str(self.COPCountDiff)) 309 | print("Reduction Rate: " + rate_format.format(self.COPCountReduction)) 310 | print("Introduction Rate: " + rate_format.format(self.COPIntroductionRate)) 311 | print("======================================================================") 312 | print("Syscall Gadgets:") 313 | print("Count Difference: " + str(self.SysCountDiff)) 314 | print("Reduction Rate: " + rate_format.format(self.SysCountReduction)) 315 | print("Introduction Rate: " + rate_format.format(self.SysIntroductionRate)) 316 | print("======================================================================") 317 | print("JOP Dispatcher Gadgets:") 318 | print("Count Difference: " + str(self.JOPDispatchersCountDiff)) 319 | print("Reduction Rate: " + rate_format.format(self.JOPDispatchersCountReduction)) 320 | print("Introduction Rate: " + rate_format.format(self.JOPDispatchersIntroductionRate)) 321 | print("======================================================================") 322 | print("JOP Data Loader Gadgets:") 323 | print("Count Difference: " + str(self.JOPDataLoadersCountDiff)) 324 | print("Reduction Rate: " + rate_format.format(self.JOPDataLoadersCountReduction)) 325 | print("Introduction Rate: " + rate_format.format(self.JOPDataLoadersIntroductionRate)) 326 | print("======================================================================") 327 | print("JOP Initializer Gadgets:") 328 | print("Count Difference: " + str(self.JOPInitializersCountDiff)) 329 | print("Reduction Rate: " + rate_format.format(self.JOPInitializersCountReduction)) 330 | print("Introduction Rate: " + rate_format.format(self.JOPInitializersIntroductionRate)) 331 | print("======================================================================") 332 | print("JOP Trampoline Gadgets:") 333 | print("Count Difference: " + str(self.JOPTrampolinesCountDiff)) 334 | print("Reduction Rate: " + rate_format.format(self.JOPTrampolinesCountReduction)) 335 | print("Introduction Rate: " + rate_format.format(self.JOPTrampolinesIntroductionRate)) 336 | print("======================================================================") 337 | print("COP Dispatcher Gadgets:") 338 | print("Count Difference: " + str(self.COPDispatchersCountDiff)) 339 | print("Reduction Rate: " + rate_format.format(self.COPDispatchersCountReduction)) 340 | print("Introduction Rate: " + rate_format.format(self.COPDispatchersIntroductionRate)) 341 | print("======================================================================") 342 | print("COP Data Loader Gadgets:") 343 | print("Count Difference: " + str(self.COPDataLoadersCountDiff)) 344 | print("Reduction Rate: " + rate_format.format(self.COPDataLoadersCountReduction)) 345 | print("Introduction Rate: " + rate_format.format(self.COPDataLoadersIntroductionRate)) 346 | print("======================================================================") 347 | print("COP Initializer Gadgets:") 348 | print("Count Difference: " + str(self.COPInitializersCountDiff)) 349 | print("Reduction Rate: " + rate_format.format(self.COPInitializersCountReduction)) 350 | print("Introduction Rate: " + rate_format.format(self.COPInitializersIntroductionRate)) 351 | print("======================================================================") 352 | print("COP Strong Trampoline Gadgets:") 353 | print("Count Difference: " + str(self.COPStrongTrampolinesCountDiff)) 354 | print("Reduction Rate: " + rate_format.format(self.COPStrongTrampolinesCountReduction)) 355 | print("Introduction Rate: " + rate_format.format(self.COPStrongTrampolinesIntroductionRate)) 356 | print("======================================================================") 357 | print("COP Intrastack Pivot Gadgets:") 358 | print("Count Difference: " + str(self.COPIntrastackPivotsCountDiff)) 359 | print("Reduction Rate: " + rate_format.format(self.COPIntrastackPivotsCountReduction)) 360 | print("Introduction Rate: " + rate_format.format(self.COPIntrastackPivotsIntroductionRate)) 361 | print("======================================================================") 362 | print("ROP Gadget Quality:") 363 | print("ROP Count Difference: " + str(self.keptQualityROPCountDiff)) 364 | print("ROP Average Quality Difference: " + str(self.averageROPQualityDiff)) 365 | print("JOP Gadget Quality:") 366 | print("JOP Count Difference: " + str(self.keptQualityJOPCountDiff)) 367 | print("JOP Average Quality Difference: " + str(self.averageJOPQualityDiff)) 368 | print("COP Gadget Quality:") 369 | print("COP Count Difference: " + str(self.keptQualityCOPCountDiff)) 370 | print("COP Average Quality Difference: " + str(self.averageCOPQualityDiff)) 371 | print("======================================================================") 372 | print("ROP Expressivity:") 373 | print("Practical ROP Exploit Difference: " + str(self.practical_ROP_exp_diff)) 374 | print("Practical ASLR-Proof ROP Exploit Difference: " + str(self.practical_ASLR_ROP_exp_diff)) 375 | print("Simple Turing Complete ROP Exploit Difference: " + str(self.turing_complete_ROP_exp_diff)) 376 | print("======================================================================") 377 | if output_locality: 378 | print("Gadget Locality for all gadgets: " + rate_format.format(self.gadgetLocality)) 379 | 380 | 381 | @staticmethod 382 | def findEqualGadgets(original_set, variant_set): 383 | original_set_set = set(repr(x) for x in original_set) 384 | variant_set_set = set(repr(x) for x in variant_set) 385 | equal = original_set_set.intersection(variant_set_set) 386 | return len(equal) 387 | 388 | @staticmethod 389 | def get_gadget_set(gadget_list): 390 | """ 391 | Static method for generating a set of gadget strings suitable for performing set operations. 392 | :param Gadget[] gadget_list: A list of Gadget objects 393 | :return: A set of gadget strings suitable for performing set arithmetic 394 | """ 395 | gadget_set = set() 396 | 397 | for gadget in gadget_list: 398 | gadget_set.add(gadget.instruction_string) 399 | 400 | return gadget_set 401 | 402 | -------------------------------------------------------------------------------- /src/static_analyzer/Instruction.py: -------------------------------------------------------------------------------- 1 | """ 2 | Instruction class 3 | """ 4 | 5 | # Standard Library Imports 6 | 7 | # Third Party Imports 8 | 9 | # Local Imports 10 | 11 | 12 | class Instruction(object): 13 | """ 14 | The Instruction class represents a single instruction within a Gadget. 15 | """ 16 | 17 | register_families = [["rax", "eax", "ax", "al", "ah"], # 0 18 | ["rbx", "ebx", "bx", "bl", "bh"], # 1 19 | ["rcx", "ecx", "cx", "cl", "ch"], # 2 20 | ["rdx", "edx", "dx", "dl", "dh"], # 3 21 | ["rsi", "esi", "si", "sil"], # 4 22 | ["rdi", "edi", "di", "dil"], # 5 23 | ["rbp", "ebp", "bp", "bpl"], # 6 24 | ["rsp", "esp", "sp", "spl"], # 7 25 | ["r8", "r8d", "r8w", "r8b"], # 8 26 | ["r9", "r9d", "r9w", "r9b"], # 9 27 | ["r10", "r10d", "r10w", "r10b"], # 10 28 | ["r11", "r11d", "r11w", "r11b"], # 11 29 | ["r12", "r12d", "r12w", "r12b"], # 12 30 | ["r13", "r13d", "r13w", "r13b"], # 13 31 | ["r14", "r14d", "r14w", "r14b"], # 14 32 | ["r15", "r15d", "r15w", "r15b"]] # 15 33 | 34 | word_register_families = [["rax", "eax", "ax"], # 0 35 | ["rbx", "ebx", "bx"], # 1 36 | ["rcx", "ecx", "cx"], # 2 37 | ["rdx", "edx", "dx"], # 3 38 | ["rsi", "esi", "si"], # 4 39 | ["rdi", "edi", "di"], # 5 40 | ["rbp", "ebp", "bp"], # 6 41 | ["rsp", "esp", "sp"], # 7 42 | ["r8", "r8d", "r8w"], # 8 43 | ["r9", "r9d", "r9w"], # 9 44 | ["r10", "r10d", "r10w"], # 10 45 | ["r11", "r11d", "r11w"], # 11 46 | ["r12", "r12d", "r12w"], # 12 47 | ["r13", "r13d", "r13w"], # 13 48 | ["r14", "r14d", "r14w"], # 14 49 | ["r15", "r15d", "r15w"]] # 15 50 | 51 | def __init__(self, raw_instr): 52 | """ 53 | Gadget constructor 54 | :param str raw_instr: the raw instruction 55 | """ 56 | 57 | self.raw = raw_instr 58 | 59 | self.opcode = None 60 | self.op1 = None 61 | self.op2 = None 62 | 63 | # Look for a space, if not found this gadget takes no operands 64 | spc_idx = raw_instr.find(" ") 65 | if spc_idx == -1: 66 | self.opcode = raw_instr 67 | # otherwise, opcode ends at space and need to parse operand(s) 68 | else: 69 | self.opcode = raw_instr[:spc_idx] 70 | comma_idx = raw_instr.find(", ") 71 | 72 | # If no space found, then there is only one operand and it is what is left 73 | if comma_idx == -1: 74 | self.op1 = raw_instr[spc_idx+1:] 75 | # Otherwise, op1 ends at comma and rest is op2 76 | else: 77 | self.op1 = raw_instr[spc_idx+1:comma_idx] 78 | self.op2 = raw_instr[comma_idx+2:] 79 | 80 | # Error checks on parsing 81 | if self.opcode is None: 82 | print(" ERROR parsing gadget, no opcode found.") 83 | 84 | test_str = self.opcode 85 | 86 | if self.op1 is not None: 87 | test_str = test_str + " " + self.op1 88 | 89 | if self.op2 is not None: 90 | test_str = test_str + ", " + self.op2 91 | 92 | if raw_instr != test_str: 93 | print(" ERROR parsing gadget, parsed gadget doesn't match raw input.") 94 | 95 | def creates_value(self): 96 | """ 97 | :return boolean: Returns True if the gadget creates a value. 98 | """ 99 | if self.opcode.startswith("j"): 100 | return False 101 | 102 | if self.opcode in ["cmp", "test", "push", "ljump", "out"] or self.op1 is None: 103 | return False 104 | 105 | return True 106 | 107 | @staticmethod 108 | def is_hex_constant(operand): 109 | if operand is None: 110 | return False 111 | 112 | try: 113 | int(operand, 16) 114 | return True 115 | except ValueError: 116 | return False 117 | 118 | 119 | @staticmethod 120 | def is_dec_constant(operand): 121 | if operand is None: 122 | return False 123 | 124 | try: 125 | int(operand) 126 | return True 127 | except ValueError: 128 | return False 129 | 130 | @staticmethod 131 | def is_constant(operand): 132 | if operand is None: 133 | return False 134 | 135 | return Instruction.is_hex_constant(operand) or Instruction.is_dec_constant(operand) 136 | 137 | @staticmethod 138 | def get_operand_as_constant(operand): 139 | if Instruction.is_hex_constant(operand): 140 | return int(operand, 16) 141 | elif Instruction.is_dec_constant(operand): 142 | return int(operand) 143 | else: 144 | print(" ERROR: Operand is not a hex or decimal constant. " + operand) 145 | 146 | @staticmethod 147 | def get_operand_register_family(operand): 148 | # Dummy check for None or constant operands 149 | if operand is None or Instruction.is_constant(operand): 150 | return None 151 | 152 | register = operand 153 | 154 | # Check if operand is a pointer, if so pull register from brackets 155 | pointer_loc = operand.find('[') 156 | if pointer_loc != -1: 157 | next_space_loc = operand.find(' ', pointer_loc) 158 | end_bracket_loc = operand.find(']') 159 | mult_loc = operand.find('*', pointer_loc) 160 | if next_space_loc == -1 and mult_loc == -1: 161 | register = operand[pointer_loc+1 : end_bracket_loc] 162 | elif mult_loc == -1: 163 | register = operand[pointer_loc + 1: next_space_loc] 164 | elif next_space_loc == -1: 165 | register = operand[pointer_loc + 1: mult_loc] 166 | else: 167 | end = min(next_space_loc, mult_loc) 168 | register = operand[pointer_loc+1 : end] 169 | 170 | # Iterate through families, find and return the index 171 | for i in range(len(Instruction.register_families)): 172 | if register in Instruction.register_families[i]: 173 | return i 174 | 175 | # Default return for non-integer registers, instruction pointer register, etc. 176 | return None 177 | 178 | @staticmethod 179 | def get_word_operand_register_family(operand): 180 | # Dummy check for None or constant operands 181 | if operand is None or Instruction.is_constant(operand): 182 | return None 183 | 184 | register = operand 185 | 186 | # Check if operand is a pointer, if so pull register from brackets 187 | pointer_loc = operand.find('[') 188 | if pointer_loc != -1: 189 | next_space_loc = operand.find(' ', pointer_loc) 190 | end_bracket_loc = operand.find(']') 191 | mult_loc = operand.find('*', pointer_loc) 192 | if next_space_loc == -1 and mult_loc == -1: 193 | register = operand[pointer_loc + 1: end_bracket_loc] 194 | elif mult_loc == -1: 195 | register = operand[pointer_loc + 1: next_space_loc] 196 | elif next_space_loc == -1: 197 | register = operand[pointer_loc + 1: mult_loc] 198 | else: 199 | end = min(next_space_loc, mult_loc) 200 | register = operand[pointer_loc + 1: end] 201 | 202 | # Iterate through families, find and return the index 203 | for i in range(len(Instruction.word_register_families)): 204 | if register in Instruction.word_register_families[i]: 205 | return i 206 | 207 | # Default return for non-integer registers, instruction pointer register, byte registers etc. 208 | return None 209 | 210 | def is_equivalent(self, rhs): 211 | """ 212 | :return boolean: Returns True if the instructions are equivalent. Used for non-locality gadget metrics. 213 | equivalence is defined as the exact same instruction. The only exception is if the 214 | instructions are intermediate branches for multi-branch gadgets. If so, then the gadgets are 215 | considered equivalent if they have the same opcode, op1 is a constant, and op2 is None. 216 | """ 217 | if self.raw == rhs.raw: 218 | return True 219 | 220 | if self.opcode.startswith("j") and self.opcode == rhs.opcode and \ 221 | Instruction.is_constant(self.op1) and Instruction.is_constant(rhs.op1) and \ 222 | self.op2 is None and rhs.op2 is None: 223 | return True 224 | 225 | return False 226 | -------------------------------------------------------------------------------- /src/static_analyzer/__init__.py: -------------------------------------------------------------------------------- 1 | # static analyzer classes 2 | -------------------------------------------------------------------------------- /src/utility.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gadget Set Analyzer Utility Library 3 | This common utility library contains generally useful system functions that are frequently used by GSA. 4 | """ 5 | 6 | # Standard Library Imports 7 | from datetime import datetime 8 | import os 9 | 10 | # Third Party Imports 11 | from numpy import format_float_positional 12 | 13 | # Local Imports 14 | 15 | 16 | def create_output_directory(prefix, timestamp=True): 17 | """ 18 | Create a subdirectory in the current directory for output like logs, fuzzing results, etc. 19 | 20 | :param str prefix: String to prefix to the timestamp on the directory label 21 | :param bool timestamp: Whether or not to timstamp the directory. 22 | :return: Name of the directory created by the system 23 | :rtype: str 24 | :raises: OSError if an error occurs during directory creation. 25 | """ 26 | if timestamp: 27 | directory_name = prefix + str(datetime.now()) 28 | else: 29 | directory_name = prefix 30 | os.makedirs(directory_name) 31 | return directory_name 32 | 33 | def fmt_percent_keep_precision(num): 34 | """ 35 | Format a number to be a percent, without rounding 36 | :param float num: Number to format 37 | :rtype str 38 | """ 39 | num *= 100 40 | formatted = format_float_positional(num, precision=None, unique=True) 41 | return f"{formatted}%" 42 | --------------------------------------------------------------------------------