├── .github ├── CODE OF CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── BUG REPORT.md │ └── FEATURE REQUEST.md └── PYTHON STYLE.md ├── .gitignore ├── .lldbinit ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── arch ├── __init__.py ├── aarch64.py ├── arm.py ├── base_arch.py ├── i386.py ├── ppc.py └── x86_64.py ├── assets ├── llef-dragon-small.png └── rebase-feature.png ├── commands ├── __init__.py ├── base_command.py ├── base_container.py ├── base_settings.py ├── color_settings.py ├── context.py ├── hexdump.py ├── pattern.py └── settings.py ├── common ├── __init__.py ├── base_settings.py ├── color_settings.py ├── constants.py ├── context_handler.py ├── de_bruijn.py ├── settings.py ├── singleton.py ├── state.py └── util.py ├── handlers ├── __init__.py └── stop_hook.py ├── install.sh └── llef.py /.github/CODE OF CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to make participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, sex characteristics, gender identity and expression, 10 | level of experience, education, socio-economic status, nationality, personal 11 | appearance, race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or 42 | reject comments, commits, code, wiki edits, issues, and other contributions 43 | that are not aligned to this Code of Conduct, or to ban temporarily or 44 | permanently any contributor for other behaviors that they deem inappropriate, 45 | threatening, offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies within all project spaces, and it also applies when 50 | an individual is representing the project or its community in public spaces. 51 | Examples of representing a project or community include using an official 52 | project e-mail address, posting via an official social media account, or acting 53 | as an appointed representative at an online or offline event. Representation of 54 | a project may be further defined and clarified by project maintainers. 55 | 56 | ## Enforcement 57 | 58 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 59 | reported by contacting the project team at info@foundryzero.co.uk . All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 73 | 74 | [homepage]: https://www.contributor-covenant.org 75 | 76 | For answers to common questions about this code of conduct, see 77 | https://www.contributor-covenant.org/faq 78 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Foundry Zero Open Source Project Contributing Guide 2 | 3 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 4 | 5 | 6 | The following is a set of guidelines for contributing to LLEF which is hosted in the Foundry Zero organisation on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 7 | 8 | ## Code of Conduct 9 | This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to info@foundryzero.co.uk. 10 | 11 | ## What should I know before I get started? 12 | 13 | Take a look at the [README.md](https://github.com/foundryzero/llef/blob/main/README.md) for more information about LLEF. 14 | 15 | ## How Can I Contribute? 16 | 17 | ### Your First Code Contribution 18 | 19 | Unsure where to begin contributing to LLEF? You can start by looking through these `beginner` and `help-wanted` issues: 20 | 21 | * [Beginner issues](https://github.com/foundryzero/llef/labels/good%20first%20issue) - issues which should only require a few lines of code, and a test or two. 22 | * [Help wanted issues](https://github.com/foundryzero/llef/labels/help-wanted) - issues which should be a bit more involved than `beginner` issues. 23 | 24 | #### Pull Requests 25 | 26 | The process described here has several goals: 27 | 28 | - Maintain LLEF's quality 29 | - Fix problems that are important to users 30 | - Engage the community in working toward the best possible LLEF 31 | 32 | Please follow the [styleguides](https://github.com/foundryzero/llef/blob/main/.CONTRIBUTING/PYTHON%20STYLE.md) 33 | 34 | While the prerequisites above must be satisfied prior to having your pull request reviewed, the reviewer(s) may ask you to complete additional design work, tests, or other changes before your pull request can be ultimately accepted. 35 | 36 | 37 | ### Reporting Bugs 38 | 39 | #### Before Submitting A Bug Report 40 | 41 | Before submitting a bug report, please check to see if the bug has already been raised as an issue by searching [our github issues](https://github.com/foundryzero/llef/labels/bug) 42 | 43 | #### How Do I Submit A (Good) Bug Report? 44 | 45 | Bugs are tracked as GitHub issues. Please raise your bug as a GitHub issue using our [enhancement template](https://github.com/foundryzero/llef/blob/main/.github/ISSUE_TEMPLATE/BUG%20REPORT.md). 46 | 47 | Explain the problem and include additional details to help maintainers reproduce the problem: 48 | 49 | * Use a clear and descriptive title for the issue to identify the problem. 50 | * Describe the exact steps which reproduce the problem in as many details as possible. 51 | * Provide specific examples to demonstrate the steps. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use Markdown code blocks. 52 | * Describe the behavior you observed after following the steps and point out what exactly is the problem with that behavior. 53 | * Explain which behavior you expected to see instead and why. 54 | 55 | ### Suggesting Enhancements 56 | 57 | This section guides you through submitting an enhancement suggestion for LLEF, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion 📝 and find related suggestions 🔎. 58 | 59 | #### Before Submitting A Feature Request 60 | 61 | Before submitting a feature request, please check to see if the bug has already been raised as an issue by searching [our github issues](https://github.com/foundryzero/llef/labels/enhancement) 62 | 63 | #### How Do I Submit A (Good) Enhancement Suggestion? 64 | 65 | Features are tracked as GitHub issues. Please raise your bug as a GitHub issue using our [bug issue template](https://github.com/foundryzero/llef/blob/main/.github/ISSUE_TEMPLATE/FEATURE%20REQUEST.md). 66 | 67 | * **Use a clear and descriptive title** for the issue to identify the suggestion. 68 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 69 | * **Provide specific examples to demonstrate the steps**. Include copy/pasteable snippets which you use in those examples, as [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). 70 | * **Describe the current behavior** and **explain which behavior you would like to see instead** and why. 71 | * **Explain why this enhancement would be useful** 72 | * **Specify the name and version of the OS you're using.** 73 | 74 | 75 | ## Attribution 76 | 77 | This contributor guide is based on the [guide](https://github.com/atom/atom/blob/master/.CONTRIBUTING/CONTRIBUTING.md) developed by the Atom project -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG REPORT.md: -------------------------------------------------------------------------------- 1 | ### Environment 2 | 3 | Please list the OS, version of LLDB and version of LLEF under which this bug was experienced. 4 | 5 | ### Description 6 | 7 | 8 | 9 | ### Steps to Reproduce 10 | 11 | 1. 12 | 2. 13 | 3. 14 | 15 | **Expected behavior:** 16 | 17 | What you expect to happen 18 | 19 | **Actual behavior:** 20 | 21 | What actually happens 22 | 23 | ### Additional Information 24 | 25 | Any additional information, configuration or data that might be necessary to reproduce the issue. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE REQUEST.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | 5 | ## Motivation 6 | 7 | 8 | 9 | ## Describe alternatives you've considered 10 | 11 | 12 | 13 | ## Additional context 14 | 15 | -------------------------------------------------------------------------------- /.github/PYTHON STYLE.md: -------------------------------------------------------------------------------- 1 | # Foundry Zero Open Source Python Style Guide 2 | 3 | The LLEF project comes with a .vscode workspace settings file which should enforce some of these style guidelines for you. However for completeness, the guidelines against which pull requests will be reviewed are included below. 4 | 5 | ## Code formatting 6 | 7 | `black` should be used for code formatting. `black` is best described by the project themselves: 8 | 9 | > Black is the uncompromising Python code formatter. By using it, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from `pycodestyle` nagging about formatting. You will save time and mental energy for more important matters. 10 | 11 | Python repositories should specify a `requirements.txt` file in the root of the project directory containing all the external pip dependies. 12 | 13 | ## Documentation 14 | All public functions and classes should be documented in the standard Python docstring style, detailing the intention of the function, the arguments, any exceptions it may raise, and the return value. 15 | 16 | Private functions should ideally be documented too, for ease of maintainability. 17 | 18 | When using type hints, it is not necessary to include the argument types in the documentation. 19 | 20 | The `sphinx-notypes` style is recommended. 21 | 22 | ``` 23 | def function(arg1: int, arg2: str) -> str: 24 | """ 25 | This is a function 26 | :param arg1: An argument 27 | :param arg2: Another argument 28 | :raises KeyError: description of error condition 29 | :return: The return string 30 | """ 31 | ``` 32 | 33 | ## Linting 34 | Set up VS Code (or your IDE of choice) to make use of pylint to check your project for easily catchable issues. 35 | 36 | ## Type hints 37 | When writing complex Python code, consider using type hints and mypy to statically check them. 38 | 39 | Remember: Type hints are not enforced by the Python interpreter, so only static analysis tools like mypy will inform you of errors in your code caught by type hinting. 40 | 41 | # Import ordering 42 | Imports should be ordered in alphabetical order, grouped from most widely applicable (e.g. language built ins) to most specific (e.g. project specified) 43 | 44 | ``` 45 | import json 46 | import time 47 | 48 | from django.contrib import messages 49 | from django.contrib.auth.decorators import login_required 50 | from django.forms.formsets import formset_factory 51 | from django.forms.models import inlineformset_factory 52 | from django.views.decorators.http import require_POST 53 | 54 | from bibtexparser.bparser import BibTexParser 55 | from bibtexparser.customization import convert_to_unicode 56 | 57 | from .forms import KeywordForm, SynonymForm 58 | ``` 59 | 60 | A tool such as `isort` should be used to do this automatically for you and to ensure consistency. 61 | 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /.lldbinit: -------------------------------------------------------------------------------- 1 | command script import llef.py 2 | settings set stop-disassembly-display never -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "aaaabaaaca", 4 | "aabacadaea", 5 | "aarch", 6 | "Atomatically", 7 | "bitmask", 8 | "Bitmasks", 9 | "cmdtemplate", 10 | "colour", 11 | "colourify", 12 | "COLOURS", 13 | "cpsr", 14 | "ENDC", 15 | "hlyeff", 16 | "inscope", 17 | "libc", 18 | "lldb", 19 | "lldbinit", 20 | "llef", 21 | "pwnlib", 22 | "refd", 23 | "regs", 24 | "rflags", 25 | "ssbs", 26 | "virtualx" 27 | ], 28 | "python.linting.flake8Enabled": true, 29 | "python.linting.prospectorEnabled": true, 30 | "python.linting.pycodestyleEnabled": true, 31 | "python.linting.pylamaEnabled": true, 32 | "python.linting.pylintEnabled": true, 33 | "python.linting.mypyEnabled": true, 34 | "python.linting.mypyArgs": [ 35 | "--follow-imports=silent", 36 | "--ignore-missing-imports", 37 | "--show-column-numbers", 38 | "--no-pretty", 39 | "--strict" 40 | ], 41 | "python.linting.pycodestyleArgs": [ 42 | "--max-line-length=120" 43 | ], 44 | "python.linting.pylamaArgs": [ 45 | "--max-line-length=120" 46 | ], 47 | "python.linting.pylintArgs": [ 48 | "--max-line-length=120" 49 | ], 50 | "python.linting.ignorePatterns": [ 51 | "**/de_bruijn.py", 52 | "**/site-packages/**/*.py" 53 | ] 54 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Foundry Zero 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | llef logo 3 |

4 | 5 | # LLEF 6 | 7 | LLEF (pronounced ɬɛf - "hlyeff") is an LLDB plugin to make it more usable for low-level RE and VR. Similar to [GEF](https://github.com/hugsy/gef), but for LLDB. 8 | 9 | It uses LLDB's Python API to add extra status output and a few new commands, so that security researchers can more easily use LLDB to analyse software as it's running. 10 | 11 | ![llef demo](https://foundryzero.co.uk/assets/img/llef-small.gif) 12 | 13 | ## 💻 Supported Architectures 14 | * x86_64 15 | * arm 16 | * aarch64 / arm64 17 | * i386 18 | * PowerPC 19 | 20 | ## 📓 Requirements 21 | * LLDB 15+ (https://apt.llvm.org/) _On macOS this is bundled with Xcode 14.3+_ 22 | 23 | ## ⚙ Installation 24 | The instructions below will install LLEF so that it is used by LLDB by default. 25 | 26 | 1. Clone the repository. 27 | 2. `cd ` 28 | 3. Run `./install.sh` 29 | 4. Select automatic (overwrites `~/.lldbinit`) or manual installation. 30 | 31 | _LLDB uses AT&T disassembly syntax for x86 binaries by default. The installer provides an option to override this._ 32 | 33 | ## ▶ Usage 34 | 35 | ### Launch LLDB 36 | 37 | ```bash 38 | lldb-15 39 | ``` 40 | 41 | ### Use commands: 42 | 43 | #### llefsettings 44 | Various commands for setting, saving, loading and listing LLEF specific commands: 45 | ``` 46 | (lldb) llefsettings --help 47 | list list all settings 48 | save Save settings to config file 49 | reload Reload settings from config file (retain session values) 50 | reset Reload settings from config file (purge session values) 51 | set Set LLEF settings 52 | ``` 53 | 54 | Settings are stored in a file `.llef` located in your home directory formatted as following: 55 | ``` 56 | [LLEF] 57 | = 58 | ``` 59 | 60 | ##### Available Settings 61 | 62 | | Setting | Type | Description | 63 | | ------------------ | ------- | -------------------------------------------------- | 64 | | color_output | Boolean | Enable/disable color terminal output | 65 | | register_coloring | Boolean | Enable/disable register coloring | 66 | | show_legend | Boolean | Enable/disable legend output | 67 | | show_registers | Boolean | Enable/disable registers output | 68 | | show_stack | Boolean | Enable/disable stack output | 69 | | show_code | Boolean | Enable/disable code output | 70 | | show_threads | Boolean | Enable/disable threads output | 71 | | show_trace | Boolean | Enable/disable trace output | 72 | | force_arch | String | Force register display architecture (experimental) | 73 | | rebase_addresses | Boolean | Enable/disable address rebase output | 74 | | rebase_offset | Int | Set the rebase offset (default 0x100000) | 75 | | show_all_registers | Boolean | Enable/disable extended register output | 76 | 77 | #### llefcolorsettings 78 | Allows setting LLEF GUI colors: 79 | ``` 80 | (lldb) llefcolorsettings --help 81 | list list all color settings 82 | save Save settings to config file 83 | reload Reload settings from config file (retain session values) 84 | reset Reload settings from config file (purge session values) 85 | set Set LLEF color settings 86 | ``` 87 | 88 | ##### Available Color Settings 89 | 90 | Supported colors: BLUE, GREEN, YELLOW, RED, PINK, CYAN, GREY 91 | 92 | | Color | 93 | | ----------------------------- | 94 | | register_color | 95 | | modified_register_color | 96 | | code_color | 97 | | heap_color | 98 | | stack_color | 99 | | string_color | 100 | | stack_address_color | 101 | | function_name_color | 102 | | instruction_color | 103 | | highlighted_instruction_color | 104 | | line_color | 105 | | rebased_address_color | 106 | | section_header_color | 107 | | highlighted_index_color | 108 | | index_color | 109 | | dereferenced_value_color | 110 | | dereferenced_register_color | 111 | | frame_argument_name_color | 112 | | read_memory_address_color | 113 | 114 | #### Hexdump 115 | View memory contents with: 116 | ``` 117 | (lldb) hexdump type address [--size SIZE] [--reverse] 118 | ``` 119 | e.g. 120 | ``` 121 | (lldb) hexdump byte 0x7fffffffecc8 --size 0x38 122 | 0x7fffffffecc8 3d 2f 75 73 72 2f 6c 6f 63 61 6c 2f 73 62 69 6e =/usr/local/sbin 123 | 0x7fffffffecd8 3a 2f 75 73 72 2f 6c 6f 63 61 6c 2f 62 69 6e 3a :/usr/local/bin: 124 | 0x7fffffffece8 2f 75 73 72 2f 73 62 69 6e 3a 2f 75 73 72 2f 62 /usr/sbin:/usr/b 125 | 0x7fffffffecf8 69 6e 3a 2f 73 62 69 6e in:/sbin 126 | (lldb) hexdump word 0x7fffffffecc8 --reverse 127 | 0x7fffffffece6│+001e: 0x4654 128 | 0x7fffffffece4│+001c: 0x4361 129 | 0x7fffffffece2│+001a: 0x746f 130 | 0x7fffffffece0│+0018: 0x4e23 131 | 0x7fffffffecde│+0016: 0x3f73 132 | 0x7fffffffecdc│+0014: 0x6968 133 | 0x7fffffffecda│+0012: 0x742d 134 | 0x7fffffffecd8│+0010: 0x6564 135 | 0x7fffffffecd6│+000e: 0x6f63 136 | 0x7fffffffecd4│+000c: 0x6564 137 | 0x7fffffffecd2│+000a: 0x2d75 138 | 0x7fffffffecd0│+0008: 0x6f79 139 | 0x7fffffffecce│+0006: 0x2d64 140 | 0x7fffffffeccc│+0004: 0x6964 141 | 0x7fffffffecca│+0002: 0x2d79 142 | 0x7fffffffecc8│+0000: 0x6857 143 | ``` 144 | 145 | #### Context 146 | 147 | Refresh the LLEF GUI with: 148 | ``` 149 | (lldb) context 150 | ``` 151 | Refresh components of the LLEF GUI with: 152 | ``` 153 | (lldb) context [{registers,stack,code,threads,trace,all} ...] 154 | ``` 155 | 156 | #### Pattern Create 157 | ``` 158 | (lldb) pattern create 10 159 | [+] Generating a pattern of 10 bytes (n=4) 160 | aaaabaaaca 161 | [+] Pattern saved in variable: $8 162 | (lldb) pattern create 100 -n 2 163 | [+] Generating a pattern of 100 bytes (n=2) 164 | aabacadaea 165 | [+] Pattern saved in variable: $9 166 | ``` 167 | 168 | #### Pattern Search 169 | 170 | ``` 171 | (lldb) pattern search $rdx 172 | [+] Found in $10 at index 45 (big endian) 173 | (lldb) pattern search $8 174 | [+] Found in $10 at index 0 (little endian) 175 | (lldb) pattern search aaaabaaac 176 | [+] Found in $8 at index 0 (little endian) 177 | (lldb) pattern search 0x61616161626161616361 178 | [+] Found in $8 at index 0 (little endian) 179 | ``` 180 | 181 | 182 | ### Breakpoint hook 183 | This is automatic and prints all the currently implemented information at a break point. 184 | 185 | #### Address Rebasing 186 | Configurable with the `rebase_addresses` setting the address rebasing feature performs a lookup for each code address presented in the output to display the associated binary and relative address. This relative address is offset by the value defined in setting `rebase_offset` which defaults to the Ghidra base address of `0x100000`. The result is an address output that can be easily copied and pasted into an IDE "Go To Address" feature without having to do the maths to convert from the runtime address. 187 | 188 | Rebased addresses are shown in brackets after the runtime address: 189 | ![rebase address feature](assets/rebase-feature.png) 190 | 191 | ## 👷‍♂️ Troubleshooting LLDB Python support 192 | LLDB comes bundled with python modules that are required for LLEF to run. If on launching LLDB with LLEF you encounter `ModuleNotFoundError` messages it is likely you will need to manually add the LLDB python modules on your python path. 193 | 194 | To do this run the following to establish your site-packages location: 195 | 196 | ```bash 197 | python3 -m site --user-site 198 | ``` 199 | 200 | Then locate the LLDB python modules location. This is typically at a location such as `/usr/lib/llvm-15/lib/python3.10/dist-packages` but depends on your python version. 201 | 202 | Finally, modify and execute the following to add the above LLDB module path into a new file `lldb.pth` in the site-packages location discovered above. 203 | 204 | ```bash 205 | echo "/usr/lib/llvm-15/lib/python3.10/dist-packages" > ~/.local/lib/python3.10/site-packages/lldb.pth 206 | ``` 207 | 208 | ## Performance Optimisations 209 | 210 | Rendering LLEF output at each breakpoint has been observed to be slow on some platforms. The root cause of this has been traced to the underlying `GetMemoryRegions` LLDB API call. Fortunately, this is only used to identify to whether register values point to code, stack or heap addresses. 211 | 212 | To disable register coloring, and potentially significantly improve LLEF performance, disable the `register_coloring` feature using the following `llefsettings` command. 213 | 214 | ``` 215 | llefsettings set register_coloring False 216 | ``` 217 | 218 | 219 | ## 👏 Thanks 220 | We’re obviously standing on the shoulders of giants here - we’d like to credit [hugsy](https://twitter.com/_hugsy_) for [GEF](https://github.com/hugsy/gef) in particular, from which this tool draws *heavy* inspiration! Please consider this imitation as flattery 🙂 221 | 222 | If you'd like to read a bit more about LLEF you could visit our [launch blog post](https://foundryzero.co.uk/2023/07/13/llef.html). 223 | -------------------------------------------------------------------------------- /arch/__init__.py: -------------------------------------------------------------------------------- 1 | """Arch module __init__.py""" 2 | from typing import Type 3 | 4 | from lldb import SBTarget 5 | 6 | from arch.aarch64 import Aarch64 7 | from arch.arm import Arm 8 | from arch.base_arch import BaseArch 9 | from arch.i386 import I386 10 | from arch.x86_64 import X86_64 11 | from arch.ppc import PPC 12 | from common.constants import MSG_TYPE 13 | from common.util import extract_arch_from_triple, print_message 14 | 15 | # macOS devices running arm chips identify as arm64. 16 | # aarch64 and arm64 backends have been merged, so alias arm64 to aarch64. 17 | # There's also arm64e architecture, which is basically ARMv8.3 18 | # but includes pointer authentication and for now is Apple-specific. 19 | supported_arch = { 20 | "arm": Arm, 21 | "i386": I386, 22 | "x86_64": X86_64, 23 | "aarch64": Aarch64, 24 | "arm64": Aarch64, 25 | "arm64e": Aarch64, 26 | "powerpc": PPC 27 | } 28 | 29 | 30 | def get_arch(target: SBTarget) -> Type[BaseArch]: 31 | """Get the architecture of a given target""" 32 | arch = extract_arch_from_triple(target.triple) 33 | return get_arch_from_str(arch) 34 | 35 | 36 | def get_arch_from_str(arch: str) -> Type[BaseArch]: 37 | """Get the architecture class from string""" 38 | if arch in supported_arch: 39 | return supported_arch[arch] 40 | 41 | print_message(MSG_TYPE.ERROR, f"Unknown Architecture: {arch}") 42 | raise TypeError(f"Unknown target architecture: {arch}") 43 | -------------------------------------------------------------------------------- /arch/aarch64.py: -------------------------------------------------------------------------------- 1 | """aarch64 architecture definition.""" 2 | 3 | from arch.base_arch import BaseArch, FlagRegister 4 | 5 | 6 | class Aarch64(BaseArch): 7 | """ 8 | aarch64 support file 9 | """ 10 | 11 | bits = 64 12 | 13 | gpr_registers = [ 14 | "x0", 15 | "x1", 16 | "x2", 17 | "x3", 18 | "x4", 19 | "x5", 20 | "x6", 21 | "x7", 22 | "x8", 23 | "x9", 24 | "x10", 25 | "x11", 26 | "x12", 27 | "x13", 28 | "x14", 29 | "x15", 30 | "x16", 31 | "x17", 32 | "x18", 33 | "x19", 34 | "x20", 35 | "x21", 36 | "x22", 37 | "x23", 38 | "x24", 39 | "x25", 40 | "x26", 41 | "x27", 42 | "x28", 43 | "x29", 44 | "x30", 45 | "fp", 46 | "lr", 47 | "sp", 48 | "pc", 49 | ] 50 | 51 | gpr_key = "general" 52 | 53 | # Bitmasks used to extract flag bits from cpsr register value 54 | _cpsr_register_bit_masks = { 55 | "n": 0x80000000, 56 | "z": 0x40000000, 57 | "c": 0x20000000, 58 | "v": 0x10000000, 59 | "q": 0x8000000, 60 | "ssbs": 0x800000, 61 | "pan": 0x400000, 62 | "dit": 0x200000, 63 | "ge": 0xF0000, 64 | "e": 0x200, 65 | "a": 0x100, 66 | "i": 0x80, 67 | "f": 0x40, 68 | "m": 0xF, 69 | } 70 | 71 | flag_registers = [ 72 | FlagRegister("cpsr", _cpsr_register_bit_masks) 73 | ] 74 | -------------------------------------------------------------------------------- /arch/arm.py: -------------------------------------------------------------------------------- 1 | """arm architecture definition.""" 2 | 3 | from arch.base_arch import BaseArch, FlagRegister 4 | 5 | 6 | class Arm(BaseArch): 7 | """ 8 | arm support file 9 | """ 10 | 11 | bits = 32 12 | 13 | gpr_registers = [ 14 | "r0", 15 | "r1", 16 | "r2", 17 | "r3", 18 | "r4", 19 | "r5", 20 | "r6", 21 | "r7", 22 | "r8", 23 | "r9", 24 | "r10", 25 | "r11", 26 | "r12", 27 | "sp", 28 | "lr", 29 | "pc", 30 | ] 31 | 32 | gpr_key = "general" 33 | 34 | # Bitmasks used to extract flag bits from cpsr register value 35 | _cpsr_register_bit_masks = { 36 | "n": 0x80000000, 37 | "z": 0x40000000, 38 | "c": 0x20000000, 39 | "v": 0x10000000, 40 | "q": 0x8000000, 41 | "j": 0x1000000, 42 | "ge": 0xF0000, 43 | "e": 0x200, 44 | "a": 0x100, 45 | "i": 0x80, 46 | "f": 0x40, 47 | "t": 0x20, 48 | } 49 | 50 | flag_registers = [ 51 | FlagRegister("cpsr", _cpsr_register_bit_masks) 52 | ] 53 | -------------------------------------------------------------------------------- /arch/base_arch.py: -------------------------------------------------------------------------------- 1 | """Base arch abstract class definition.""" 2 | 3 | from abc import ABC, abstractmethod 4 | from dataclasses import dataclass 5 | from typing import Dict, List 6 | 7 | 8 | @dataclass 9 | class FlagRegister: 10 | """FlagRegister dataclass to store register name / bitmask associations""" 11 | name: str 12 | bit_masks: Dict[str, int] 13 | 14 | 15 | class BaseArch(ABC): 16 | """BaseArch abstract class definition.""" 17 | 18 | @property 19 | @abstractmethod 20 | def bits(self) -> int: 21 | """Bit count property""" 22 | 23 | @property 24 | @abstractmethod 25 | def gpr_registers(self) -> List[str]: 26 | """GPR register property""" 27 | 28 | @property 29 | @abstractmethod 30 | def gpr_key(self) -> str: 31 | """GPR key property""" 32 | 33 | @property 34 | @abstractmethod 35 | def flag_registers(self) -> List[FlagRegister]: 36 | """List of flag registers with associated bit masks""" 37 | -------------------------------------------------------------------------------- /arch/i386.py: -------------------------------------------------------------------------------- 1 | """i386 architecture definition.""" 2 | from arch.base_arch import BaseArch, FlagRegister 3 | 4 | 5 | class I386(BaseArch): 6 | """ 7 | These are currently hardcoded for i386. 8 | """ 9 | 10 | bits = 32 11 | 12 | gpr_registers = [ 13 | "eax", 14 | "ebx", 15 | "ecx", 16 | "edx", 17 | "edi", 18 | "esi", 19 | "ebp", 20 | "esp", 21 | "eip", 22 | "cs", 23 | "fs", 24 | "gs", 25 | "ss", 26 | "ds", 27 | "es", 28 | ] 29 | 30 | gpr_key = "general purpose" 31 | 32 | # Bitmasks used to extract flag bits from eflags register value 33 | _eflags_register_bit_masks = { 34 | "zero": 0x40, 35 | "carry": 0x1, 36 | "parity": 0x4, 37 | "adjust": 0x10, 38 | "sign": 0x80, 39 | "trap": 0x100, 40 | "interrupt": 0x200, 41 | "direction": 0x400, 42 | "overflow": 0x800, 43 | "resume": 0x10000, 44 | "virtual8086": 0x20000, 45 | "identification": 0x200000, 46 | } 47 | 48 | flag_registers = [ 49 | FlagRegister("eflags", _eflags_register_bit_masks), 50 | FlagRegister("rflags", _eflags_register_bit_masks) 51 | ] 52 | -------------------------------------------------------------------------------- /arch/ppc.py: -------------------------------------------------------------------------------- 1 | """PowerPC architecture definition.""" 2 | from arch.base_arch import BaseArch, FlagRegister 3 | 4 | 5 | class PPC(BaseArch): 6 | """ 7 | These are currently hardcoded for PowerPC. 8 | """ 9 | 10 | bits = 32 11 | 12 | gpr_registers = [ 13 | "r0", 14 | "r1", 15 | "r2", 16 | "r3", 17 | "r4", 18 | "r5", 19 | "r6", 20 | "r7", 21 | "r8", 22 | "r9", 23 | "r10", 24 | "r11", 25 | "r12", 26 | "r13", 27 | "pc", # program counter 28 | "msr", # machine state register 29 | "lr", # link register 30 | "ctr", # counter 31 | ] 32 | 33 | gpr_key = "general purpose" 34 | 35 | _xer_register_bit_masks = { 36 | "summary_overflow": 0x80000000, 37 | "overflow": 0x40000000, 38 | "carry": 0x20000000, 39 | } 40 | 41 | _cr_register_bit_masks = { 42 | "cr0_lt": 0x80000000, 43 | "cr0_gt": 0x40000000, 44 | "cr0_eq": 0x20000000, 45 | "cr0_so": 0x10000000 46 | } 47 | 48 | flag_registers = [ 49 | FlagRegister("cr", _cr_register_bit_masks), 50 | FlagRegister("xer", _xer_register_bit_masks) 51 | ] 52 | -------------------------------------------------------------------------------- /arch/x86_64.py: -------------------------------------------------------------------------------- 1 | """x86_64 architecture definition.""" 2 | from arch.base_arch import BaseArch, FlagRegister 3 | 4 | 5 | class X86_64(BaseArch): 6 | """ 7 | These are currently hardcoded for X86_64. 8 | """ 9 | 10 | bits = 64 11 | 12 | gpr_registers = [ 13 | "rax", 14 | "rbx", 15 | "rcx", 16 | "rdx", 17 | "rsp", 18 | "rbp", 19 | "rsi", 20 | "rdi", 21 | "rip", 22 | "r8", 23 | "r9", 24 | "r10", 25 | "r11", 26 | "r12", 27 | "r13", 28 | "r14", 29 | "r15", 30 | ] 31 | 32 | gpr_key = "general purpose" 33 | 34 | # Bitmasks used to extract flag bits from eflags register value 35 | _eflag_register_bit_masks = { 36 | "zero": 0x40, 37 | "carry": 0x1, 38 | "parity": 0x4, 39 | "adjust": 0x10, 40 | "sign": 0x80, 41 | "trap": 0x100, 42 | "interrupt": 0x200, 43 | "direction": 0x400, 44 | "overflow": 0x800, 45 | "resume": 0x10000, 46 | "virtualx86": 0x20000, 47 | "identification": 0x200000, 48 | } 49 | 50 | # Whether LLDB exposes eflags or rflags varies depending on the platform 51 | # rflags and eflags bit masks are identical for the lower 32-bits 52 | flag_registers = [ 53 | FlagRegister("rflags", _eflag_register_bit_masks), 54 | FlagRegister("eflags", _eflag_register_bit_masks) 55 | ] 56 | -------------------------------------------------------------------------------- /assets/llef-dragon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundryzero/llef/d860cedadbb37498b9789531b849c7e2ec6b6103/assets/llef-dragon-small.png -------------------------------------------------------------------------------- /assets/rebase-feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundryzero/llef/d860cedadbb37498b9789531b849c7e2ec6b6103/assets/rebase-feature.png -------------------------------------------------------------------------------- /commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundryzero/llef/d860cedadbb37498b9789531b849c7e2ec6b6103/commands/__init__.py -------------------------------------------------------------------------------- /commands/base_command.py: -------------------------------------------------------------------------------- 1 | """Base command definition.""" 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import Type 5 | 6 | from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext 7 | 8 | from commands.base_container import BaseContainer 9 | 10 | 11 | class BaseCommand(ABC): 12 | """An abstract base class for all commands.""" 13 | 14 | @abstractmethod 15 | def __init__(self) -> None: 16 | pass 17 | 18 | @property 19 | @abstractmethod 20 | def container(self) -> Type[BaseContainer]: 21 | """Container property.""" 22 | 23 | @property 24 | @abstractmethod 25 | def program(self) -> str: 26 | """Program property.""" 27 | 28 | @abstractmethod 29 | def __call__( 30 | self, 31 | debugger: SBDebugger, 32 | command: str, 33 | exe_ctx: SBExecutionContext, 34 | result: SBCommandReturnObject, 35 | ) -> None: 36 | pass 37 | 38 | @staticmethod 39 | @abstractmethod 40 | def get_short_help() -> str: 41 | """Get short help string.""" 42 | 43 | @staticmethod 44 | @abstractmethod 45 | def get_long_help() -> str: 46 | """Get long help string.""" 47 | 48 | @classmethod 49 | def lldb_self_register(cls, debugger: SBDebugger, module_name: str) -> None: 50 | """Automatically register a subcommand.""" 51 | 52 | if cls.container is not None: 53 | command = f"command script add -c {module_name}.{cls.__name__} {cls.container.container_verb} {cls.program}" 54 | else: 55 | command = ( 56 | f"command script add -c {module_name}.{cls.__name__} {cls.program}" 57 | ) 58 | 59 | debugger.HandleCommand(command) 60 | -------------------------------------------------------------------------------- /commands/base_container.py: -------------------------------------------------------------------------------- 1 | """Base container definition.""" 2 | from abc import ABC, abstractmethod 3 | 4 | from lldb import SBDebugger 5 | 6 | 7 | class BaseContainer(ABC): 8 | """Base container class.""" 9 | 10 | @property 11 | @abstractmethod 12 | def container_verb(self) -> str: 13 | """Container verb property.""" 14 | 15 | @staticmethod 16 | @abstractmethod 17 | def get_short_help() -> str: 18 | """Get short help message.""" 19 | 20 | @staticmethod 21 | @abstractmethod 22 | def get_long_help() -> str: 23 | """Get long help message.""" 24 | 25 | @classmethod 26 | def lldb_self_register(cls, debugger: SBDebugger, _: str) -> None: 27 | """Automatically register a container.""" 28 | container_command = f'command container add -h "{cls.get_long_help()}" -H "{cls.get_short_help()}" {cls.container_verb}' 29 | debugger.HandleCommand(container_command) 30 | -------------------------------------------------------------------------------- /commands/base_settings.py: -------------------------------------------------------------------------------- 1 | """Base settings command class.""" 2 | import argparse 3 | import shlex 4 | from typing import Any, Dict 5 | from abc import ABC, abstractmethod 6 | 7 | from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext 8 | 9 | from commands.base_command import BaseCommand 10 | from common.util import output_line 11 | 12 | 13 | class BaseSettingsCommand(BaseCommand, ABC): 14 | """Base class for generic settings command""" 15 | 16 | program: str = "" 17 | container = None 18 | settings = None 19 | 20 | def __init__(self, debugger: SBDebugger, __: Dict[Any, Any]) -> None: 21 | super().__init__() 22 | self.parser = self.get_command_parser() 23 | 24 | @classmethod 25 | @abstractmethod 26 | def get_command_parser(cls) -> argparse.ArgumentParser: 27 | """Get the command parser.""" 28 | 29 | @staticmethod 30 | @abstractmethod 31 | def get_short_help() -> str: 32 | """Return a short help message""" 33 | 34 | @classmethod 35 | def get_long_help(cls) -> str: 36 | """Return a longer help message""" 37 | return cls.get_command_parser().format_help() 38 | 39 | def __call__( 40 | self, 41 | debugger: SBDebugger, 42 | command: str, 43 | exe_ctx: SBExecutionContext, 44 | result: SBCommandReturnObject, 45 | ) -> None: 46 | """Handles the invocation of the command""" 47 | args = self.parser.parse_args(shlex.split(command)) 48 | 49 | if not hasattr(args, "action"): 50 | output_line(self.__class__.get_long_help()) 51 | return 52 | 53 | if args.action == "list": 54 | self.settings.list() 55 | elif args.action == "save": 56 | self.settings.save() 57 | elif args.action == "reload": 58 | self.settings.load() 59 | elif args.action == "reset": 60 | self.settings.load(reset=True) 61 | elif args.action == "set": 62 | self.settings.set(args.setting, args.value) 63 | -------------------------------------------------------------------------------- /commands/color_settings.py: -------------------------------------------------------------------------------- 1 | """llefcolorsettings command class.""" 2 | import argparse 3 | from typing import Any, Dict 4 | 5 | from lldb import SBDebugger 6 | 7 | from common.color_settings import LLEFColorSettings 8 | from commands.base_settings import BaseSettingsCommand 9 | 10 | 11 | class ColorSettingsCommand(BaseSettingsCommand): 12 | """Implements the llefcolorsettings commands""" 13 | 14 | program: str = "llefcolorsettings" 15 | container = None 16 | 17 | def __init__(self, debugger: SBDebugger, dictionary: Dict[Any, Any]) -> None: 18 | super().__init__(debugger, dictionary) 19 | self.settings = LLEFColorSettings() 20 | 21 | @classmethod 22 | def get_command_parser(cls) -> argparse.ArgumentParser: 23 | """Get the command parser.""" 24 | parser = argparse.ArgumentParser(description="LLEF settings command for colors") 25 | subparsers = parser.add_subparsers() 26 | 27 | list_parser = subparsers.add_parser("list", help="list all color settings") 28 | list_parser.set_defaults(action="list") 29 | 30 | save_parser = subparsers.add_parser("save", help="Save settings to config file") 31 | save_parser.set_defaults(action="save") 32 | 33 | reload_parser = subparsers.add_parser("reload", help="Reload settings from config file (retain session values)") 34 | reload_parser.set_defaults(action="reload") 35 | 36 | reset_parser = subparsers.add_parser("reset", help="Reload settings from config file (purge session values)") 37 | reset_parser.set_defaults(action="reset") 38 | 39 | set_parser = subparsers.add_parser("set", help="Set LLEF color settings") 40 | set_parser.add_argument("setting", type=str, help="LLEF color setting name") 41 | set_parser.add_argument("value", type=str, help="New color") 42 | set_parser.set_defaults(action="set") 43 | 44 | return parser 45 | 46 | @staticmethod 47 | def get_short_help() -> str: 48 | return "Usage: llefcolorsettings \n" 49 | -------------------------------------------------------------------------------- /commands/context.py: -------------------------------------------------------------------------------- 1 | """Context command class.""" 2 | import argparse 3 | import shlex 4 | from typing import Any, Dict 5 | 6 | from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext 7 | from lldb import ( 8 | SBDebugger, 9 | SBExecutionContext, 10 | ) 11 | 12 | from commands.base_command import BaseCommand 13 | from common.context_handler import ContextHandler 14 | from common.util import output_line 15 | 16 | 17 | class ContextCommand(BaseCommand): 18 | """Implements the context""" 19 | 20 | program: str = "context" 21 | container = None 22 | 23 | def __init__(self, debugger: SBDebugger, __: Dict[Any, Any]) -> None: 24 | super().__init__() 25 | self.parser = self.get_command_parser() 26 | self.context_handler = ContextHandler(debugger) 27 | 28 | @classmethod 29 | def get_command_parser(cls) -> argparse.ArgumentParser: 30 | """Get the command parser.""" 31 | parser = argparse.ArgumentParser(description="context command") 32 | parser.add_argument( 33 | "sections", 34 | nargs="*", 35 | choices=["registers", "stack", "code", "threads", "trace", "all"], 36 | default="all" 37 | ) 38 | 39 | return parser 40 | 41 | @staticmethod 42 | def get_short_help() -> str: 43 | return "Usage: context [section (optional)]\n" 44 | 45 | @staticmethod 46 | def get_long_help() -> str: 47 | return "Refresh and print the context\n" 48 | 49 | def __call__( 50 | self, 51 | debugger: SBDebugger, 52 | command: str, 53 | exe_ctx: SBExecutionContext, 54 | result: SBCommandReturnObject, 55 | ) -> None: 56 | """Handles the invocation of 'context' command""" 57 | 58 | if not exe_ctx.frame: 59 | output_line("Program not running") 60 | return 61 | 62 | args = self.parser.parse_args(shlex.split(command)) 63 | 64 | if not hasattr(args, "sections"): 65 | output_line(self.__class__.get_long_help()) 66 | return 67 | 68 | self.context_handler.refresh(exe_ctx) 69 | 70 | if "all" in args.sections: 71 | self.context_handler.display_context(exe_ctx, False) 72 | else: 73 | if "registers" in args.sections: 74 | self.context_handler.display_registers() 75 | if "stack" in args.sections: 76 | self.context_handler.display_stack() 77 | if "code" in args.sections: 78 | self.context_handler.display_code() 79 | if "threads" in args.sections: 80 | self.context_handler.display_threads() 81 | if "trace" in args.sections: 82 | self.context_handler.display_trace() 83 | -------------------------------------------------------------------------------- /commands/hexdump.py: -------------------------------------------------------------------------------- 1 | """Hexdump command class.""" 2 | import argparse 3 | import shlex 4 | from typing import Any, Dict 5 | 6 | from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext 7 | 8 | from commands.base_command import BaseCommand 9 | from common.context_handler import ContextHandler 10 | from common.constants import SIZES 11 | 12 | 13 | class HexdumpCommand(BaseCommand): 14 | """Implements the hexdump command""" 15 | 16 | program: str = "hexdump" 17 | container = None 18 | context_handler = None 19 | 20 | def __init__(self, debugger: SBDebugger, __: Dict[Any, Any]) -> None: 21 | super().__init__() 22 | self.parser = self.get_command_parser() 23 | self.context_handler = ContextHandler(debugger) 24 | 25 | @classmethod 26 | def get_command_parser(cls) -> argparse.ArgumentParser: 27 | """Get the command parser.""" 28 | parser = argparse.ArgumentParser() 29 | parser.add_argument( 30 | "type", 31 | choices=["qword", "dword", "word", "byte"], 32 | default="byte", 33 | help="The format for presenting data" 34 | ) 35 | parser.add_argument( 36 | "--reverse", 37 | action="store_true", 38 | help="The direction of output lines. Low to high by default" 39 | ) 40 | parser.add_argument("--size", type=positive_int, default=16, help="The number of qword/dword/word/bytes to display") 41 | parser.add_argument( 42 | "address", 43 | type=hex_int, 44 | help="A value/address/symbol used as the location to print the hexdump from" 45 | ) 46 | return parser 47 | 48 | @staticmethod 49 | def get_short_help() -> str: 50 | """Return a short help message""" 51 | return "Usage: hexdump (qword|dword|word|byte) [-h] [--reverse] [--size SIZE] [address]" 52 | 53 | @staticmethod 54 | def get_long_help() -> str: 55 | """Return a longer help message""" 56 | return HexdumpCommand.get_command_parser().format_help() 57 | 58 | def __call__( 59 | self, 60 | debugger: SBDebugger, 61 | command: str, 62 | exe_ctx: SBExecutionContext, 63 | result: SBCommandReturnObject, 64 | ) -> None: 65 | """Handles the invocation of the hexdump command""" 66 | args = self.parser.parse_args(shlex.split(command)) 67 | 68 | divisions = SIZES[args.type.upper()].value 69 | address = args.address 70 | size = args.size 71 | 72 | self.context_handler.refresh(exe_ctx) 73 | 74 | start = (size-1) * divisions if args.reverse else 0 75 | end = -divisions if args.reverse else size * divisions 76 | step = -divisions if args.reverse else divisions 77 | 78 | if divisions == SIZES.BYTE.value: 79 | if args.reverse: 80 | self.context_handler.print_bytes(address + size - (size % 16), size % 16) 81 | start = size - (size % 16) - 16 82 | end = -1 83 | step = -16 84 | 85 | for i in range(start, end, -16 if args.reverse else 16): 86 | self.context_handler.print_bytes(address + i, min(16, size - abs(start - i))) 87 | else: 88 | for i in range(start, end, step): 89 | self.context_handler.print_memory_address(address + i, i, divisions) 90 | 91 | 92 | def hex_int(x): 93 | """A converter for input arguments in different bases to ints""" 94 | return int(x, 0) 95 | 96 | 97 | def positive_int(x): 98 | """A converter for input arguments in different bases to positive ints""" 99 | x = int(x, 0) 100 | if x <= 0: 101 | raise argparse.ArgumentTypeError("Must be positive") 102 | return x 103 | -------------------------------------------------------------------------------- /commands/pattern.py: -------------------------------------------------------------------------------- 1 | """Pattern command class.""" 2 | 3 | import argparse 4 | import binascii 5 | import os 6 | import shlex 7 | from typing import Any, Dict, Type 8 | 9 | from lldb import SBCommandReturnObject, SBDebugger, SBExecutionContext 10 | 11 | from commands.base_command import BaseCommand 12 | from commands.base_container import BaseContainer 13 | from common.constants import MSG_TYPE, TERM_COLORS 14 | from common.de_bruijn import generate_cyclic_pattern 15 | from common.state import LLEFState 16 | from common.util import print_message, output_line 17 | 18 | 19 | class PatternContainer(BaseContainer): 20 | """Creates a container for the Pattern command. Sub commands are implemented in inner classes""" 21 | 22 | container_verb: str = "pattern" 23 | 24 | @staticmethod 25 | def get_short_help() -> str: 26 | return "pattern (create|search)" 27 | 28 | @staticmethod 29 | def get_long_help() -> str: 30 | return """ 31 | Generate or Search a De Bruijn Sequence of unique substrings of length N 32 | and a total length of LENGTH. The default value of N is set to match the 33 | currently loaded architecture. 34 | """ 35 | 36 | 37 | class PatternCreateCommand(BaseCommand): 38 | """Implements the 'create' subcommand""" 39 | 40 | program: str = "create" 41 | container: Type[BaseContainer] = PatternContainer 42 | state: LLEFState 43 | 44 | @classmethod 45 | def get_command_parser(cls) -> argparse.ArgumentParser: 46 | """Get the command parser.""" 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument("length", type=int, help="Length of desired output") 49 | parser.add_argument( 50 | "-n", 51 | "--cycle-length", 52 | type=int, 53 | help="The length of the De Bruijn Cycle", 54 | ) 55 | return parser 56 | 57 | @staticmethod 58 | def get_short_help() -> str: 59 | return "Usage: pattern create L [-n]" 60 | 61 | @staticmethod 62 | def get_long_help() -> str: 63 | return ( 64 | "Generate a De Bruijn Sequence of unique substrings of length N and a total length of LENGTH." 65 | + os.linesep 66 | + PatternCreateCommand.get_command_parser().format_help() 67 | ) 68 | 69 | def __init__(self, _: SBDebugger, __: Dict[Any, Any]) -> None: 70 | """Class initializer.""" 71 | self.parser = self.get_command_parser() 72 | self.state = LLEFState() 73 | 74 | def __call__( 75 | self, 76 | debugger: SBDebugger, 77 | command: str, 78 | exe_ctx: SBExecutionContext, 79 | result: SBCommandReturnObject, 80 | ) -> None: 81 | """Handles the invocation of 'pattern create' command""" 82 | args = self.parser.parse_args(shlex.split(command)) 83 | length = args.length 84 | num_chars = args.cycle_length or 4 # Hardcoded default value. 85 | print_message( 86 | MSG_TYPE.INFO, f"Generating a pattern of {length} bytes (n={num_chars})" 87 | ) 88 | pattern = generate_cyclic_pattern(length, num_chars) 89 | output_line(pattern.decode("utf-8")) 90 | 91 | if exe_ctx.GetProcess().GetState() == 0: 92 | print_message( 93 | MSG_TYPE.ERROR, 94 | "Created pattern cannot be stored in a convenience variable as there is no running process", 95 | ) 96 | else: 97 | value = exe_ctx.GetTarget().EvaluateExpression( 98 | f'"{pattern.decode("utf-8")}"' 99 | ) 100 | print_message( 101 | MSG_TYPE.INFO, 102 | f"Pattern saved in variable: {TERM_COLORS.RED.value}{value.GetName()}{TERM_COLORS.ENDC.value}", 103 | ) 104 | self.state.created_patterns.append( 105 | { 106 | "name": value.GetName(), 107 | "pattern_bytes": pattern, 108 | "pattern_string": pattern.decode("utf-8"), 109 | "length": length, 110 | "num_chars": num_chars, 111 | } 112 | ) 113 | 114 | 115 | class PatternSearchCommand(BaseCommand): 116 | """Implements the 'search' subcommand.""" 117 | 118 | program = "search" 119 | container: Type[BaseContainer] = PatternContainer 120 | state: LLEFState 121 | 122 | @classmethod 123 | def get_command_parser(cls) -> argparse.ArgumentParser: 124 | """Get the command parser.""" 125 | parser = argparse.ArgumentParser() 126 | parser.add_argument("pattern", help="The pattern of bytes to search for") 127 | return parser 128 | 129 | @staticmethod 130 | def get_short_help() -> str: 131 | return "Usage: pattern search " 132 | 133 | @staticmethod 134 | def get_long_help() -> str: 135 | return ( 136 | "Search a pattern (e.g. a De Bruijn Sequence) of unique substring." 137 | + os.linesep 138 | + PatternCreateCommand.get_command_parser().format_help() 139 | ) 140 | 141 | def __init__(self, _: SBDebugger, __: Dict[Any, Any]) -> None: 142 | """Class initializer.""" 143 | self.parser = self.get_command_parser() 144 | self.state = LLEFState() 145 | 146 | def __call__( 147 | self, 148 | debugger: SBDebugger, 149 | command: str, 150 | exe_ctx: SBExecutionContext, 151 | result: SBCommandReturnObject, 152 | ) -> None: 153 | """Handles the invocation of 'pattern create' command.""" 154 | args = self.parser.parse_args(shlex.split(command)) 155 | 156 | pattern = args.pattern 157 | if pattern.startswith("$"): 158 | pattern_value = exe_ctx.GetTarget().EvaluateExpression(pattern) 159 | pattern_array = pattern_value.GetData().uint8 160 | pattern = "".join([chr(x) for x in pattern_array]) 161 | pattern = pattern.rstrip("\x00") 162 | elif pattern.startswith("0x"): 163 | pattern = binascii.unhexlify(pattern[2:]).decode() 164 | else: 165 | pass 166 | if pattern: 167 | for created_pattern in self.state.created_patterns: 168 | pattern_string = created_pattern.get("pattern_string") 169 | if pattern_string and pattern in pattern_string: 170 | print_message( 171 | MSG_TYPE.INFO, 172 | f"Found in {created_pattern.get('name')} at index" 173 | f" {pattern_string.index(pattern)} (little endian)", 174 | ) 175 | reverse_pattern = pattern[::-1] 176 | if pattern_string and reverse_pattern in pattern_string: 177 | print_message( 178 | MSG_TYPE.INFO, 179 | f"Found in {created_pattern.get('name')} at index" 180 | f" {pattern_string.index(reverse_pattern)} (big endian)", 181 | ) 182 | -------------------------------------------------------------------------------- /commands/settings.py: -------------------------------------------------------------------------------- 1 | """llefsettings command class.""" 2 | import argparse 3 | from typing import Any, Dict 4 | 5 | from lldb import SBDebugger 6 | 7 | from common.settings import LLEFSettings 8 | from commands.base_settings import BaseSettingsCommand 9 | 10 | 11 | class SettingsCommand(BaseSettingsCommand): 12 | """Implements the llefsettings command""" 13 | 14 | program: str = "llefsettings" 15 | container = None 16 | 17 | def __init__(self, debugger: SBDebugger, dictionary: Dict[Any, Any]) -> None: 18 | super().__init__(debugger, dictionary) 19 | self.settings = LLEFSettings(debugger) 20 | 21 | @classmethod 22 | def get_command_parser(cls) -> argparse.ArgumentParser: 23 | """Get the command parser.""" 24 | parser = argparse.ArgumentParser(description="LLEF settings command") 25 | subparsers = parser.add_subparsers() 26 | 27 | list_parser = subparsers.add_parser("list", help="list all settings") 28 | list_parser.set_defaults(action="list") 29 | 30 | save_parser = subparsers.add_parser("save", help="Save settings to config file") 31 | save_parser.set_defaults(action="save") 32 | 33 | reload_parser = subparsers.add_parser("reload", help="Reload settings from config file (retain session values)") 34 | reload_parser.set_defaults(action="reload") 35 | 36 | reset_parser = subparsers.add_parser("reset", help="Reload settings from config file (purge session values)") 37 | reset_parser.set_defaults(action="reset") 38 | 39 | set_parser = subparsers.add_parser("set", help="Set LLEF settings") 40 | set_parser.add_argument("setting", type=str, help="LLEF setting name") 41 | set_parser.add_argument("value", type=str, help="New setting value") 42 | set_parser.set_defaults(action="set") 43 | 44 | return parser 45 | 46 | @staticmethod 47 | def get_short_help() -> str: 48 | return "Usage: llefsettings \n" 49 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundryzero/llef/d860cedadbb37498b9789531b849c7e2ec6b6103/common/__init__.py -------------------------------------------------------------------------------- /common/base_settings.py: -------------------------------------------------------------------------------- 1 | """A base class for global settings""" 2 | import configparser 3 | import os 4 | 5 | from abc import abstractmethod 6 | from common.singleton import Singleton 7 | from common.util import output_line 8 | 9 | 10 | class BaseLLEFSettings(metaclass=Singleton): 11 | """ 12 | Global settings class - loaded from file defined in `LLEF_CONFIG_PATH` 13 | """ 14 | LLEF_CONFIG_PATH = os.path.join(os.path.expanduser('~'), ".llef") 15 | GLOBAL_SECTION = "LLEF" 16 | 17 | _RAW_CONFIG: configparser.ConfigParser = configparser.ConfigParser() 18 | 19 | @classmethod 20 | def _get_setting_names(cls): 21 | return [name for name, value in vars(cls).items() if isinstance(value, property)] 22 | 23 | def __init__(self): 24 | self.load() 25 | 26 | @abstractmethod 27 | def validate_settings(self, setting=None) -> bool: 28 | """ 29 | Validate settings 30 | """ 31 | 32 | def load_default_settings(self): 33 | """ 34 | Reset settings and use default values 35 | """ 36 | self._RAW_CONFIG = configparser.ConfigParser() 37 | self._RAW_CONFIG.add_section(self.GLOBAL_SECTION) 38 | 39 | def load(self, reset=False): 40 | """ 41 | Load settings from file 42 | """ 43 | if reset: 44 | self._RAW_CONFIG = configparser.ConfigParser() 45 | 46 | if not os.path.isfile(self.LLEF_CONFIG_PATH): 47 | self.load_default_settings() 48 | return 49 | 50 | output_line(f"Loading LLEF settings from {self.LLEF_CONFIG_PATH}") 51 | 52 | self._RAW_CONFIG.read(self.LLEF_CONFIG_PATH) 53 | 54 | if not self._RAW_CONFIG.has_section(self.GLOBAL_SECTION): 55 | self.load_default_settings() 56 | output_line("Settings file missing 'LLEF' section. Default settings loaded.") 57 | 58 | if not self.validate_settings(): 59 | self.load_default_settings() 60 | output_line("Error parsing config. Default settings loaded.") 61 | 62 | def list(self): 63 | """ 64 | List all settings and their current values 65 | """ 66 | settings_names = self._get_setting_names() 67 | for setting_name in settings_names: 68 | output_line(f"{setting_name}={getattr(self, setting_name)}") 69 | 70 | def save(self): 71 | """ 72 | Save LLEF setting to file defined in `LLEF_CONFIG_PATH` 73 | """ 74 | with open(self.LLEF_CONFIG_PATH, "w") as configfile: 75 | self._RAW_CONFIG.write(configfile) 76 | 77 | def set(self, setting: str, value: str): 78 | """ 79 | Set a LLEF setting 80 | """ 81 | if not hasattr(self, setting): 82 | output_line(f"Invalid LLEF setting {setting}") 83 | 84 | restore_value = getattr(self, setting) 85 | self._RAW_CONFIG.set(self.GLOBAL_SECTION, setting, value) 86 | 87 | if not self.validate_settings(setting=setting): 88 | self._RAW_CONFIG.set(self.GLOBAL_SECTION, setting, str(restore_value)) 89 | else: 90 | output_line(f"Set {setting} to {getattr(self, setting)}") 91 | -------------------------------------------------------------------------------- /common/color_settings.py: -------------------------------------------------------------------------------- 1 | """Color settings module""" 2 | import configparser 3 | import os 4 | 5 | from typing import List 6 | 7 | from common.singleton import Singleton 8 | from common.constants import TERM_COLORS 9 | from common.base_settings import BaseLLEFSettings 10 | from common.util import output_line 11 | 12 | 13 | class LLEFColorSettings(BaseLLEFSettings, metaclass=Singleton): 14 | """ 15 | Color settings class - loaded from file defined in `LLEF_CONFIG_PATH` 16 | """ 17 | LLEF_CONFIG_PATH = os.path.join(os.path.expanduser('~'), ".llef_colors") 18 | GLOBAL_SECTION = "LLEF" 19 | 20 | supported_colors: List[str] = [] 21 | 22 | @property 23 | def register_color(self): 24 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "register_color", fallback="BLUE").upper() 25 | 26 | @property 27 | def modified_register_color(self): 28 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "modified_register_color", fallback="RED").upper() 29 | 30 | @property 31 | def code_color(self): 32 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "code_color", fallback="RED").upper() 33 | 34 | @property 35 | def heap_color(self): 36 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "heap_color", fallback="GREEN").upper() 37 | 38 | @property 39 | def stack_color(self): 40 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "stack_color", fallback="PINK").upper() 41 | 42 | @property 43 | def string_color(self): 44 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "string_color", fallback="YELLOW").upper() 45 | 46 | @property 47 | def stack_address_color(self): 48 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "stack_address_color", fallback="CYAN").upper() 49 | 50 | @property 51 | def function_name_color(self): 52 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "function_name_color", fallback="GREEN").upper() 53 | 54 | @property 55 | def instruction_color(self): 56 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "instruction_color", fallback="GREY").upper() 57 | 58 | @property 59 | def highlighted_instruction_color(self): 60 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "highlighted_instruction_color", fallback="GREEN").upper() 61 | 62 | @property 63 | def line_color(self): 64 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "line_color", fallback="GREY").upper() 65 | 66 | @property 67 | def rebased_address_color(self): 68 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "rebased_address_color", fallback="GREY").upper() 69 | 70 | @property 71 | def section_header_color(self): 72 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "section_header_color", fallback="BLUE").upper() 73 | 74 | @property 75 | def highlighted_index_color(self): 76 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "highlighted_index_color", fallback="GREEN").upper() 77 | 78 | @property 79 | def index_color(self): 80 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "index_color", fallback="PINK").upper() 81 | 82 | @property 83 | def dereferenced_value_color(self): 84 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "dereferenced_value_color", fallback="GREY").upper() 85 | 86 | @property 87 | def dereferenced_register_color(self): 88 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "dereferenced_register_color", fallback="BLUE").upper() 89 | 90 | @property 91 | def frame_argument_name_color(self): 92 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "frame_argument_name_color", fallback="YELLOW").upper() 93 | 94 | @property 95 | def read_memory_address_color(self): 96 | return self._RAW_CONFIG.get(self.GLOBAL_SECTION, "read_memory_address_color", fallback="CYAN").upper() 97 | 98 | def __init__(self): 99 | self.supported_colors = [color.name for color in TERM_COLORS] 100 | self.supported_colors.remove(TERM_COLORS.ENDC.name) 101 | super().__init__() 102 | 103 | def validate_settings(self, setting=None) -> bool: 104 | """ 105 | Validate settings by attempting to retrieve all properties thus executing any ConfigParser coverters 106 | Check all colors are valid options 107 | """ 108 | settings_names = LLEFColorSettings._get_setting_names() 109 | 110 | if setting: 111 | if setting not in settings_names: 112 | output_line(f"Invalid LLEF setting {setting}") 113 | return False 114 | settings_names = [setting] 115 | 116 | valid = True 117 | for setting_name in settings_names: 118 | try: 119 | value = getattr(self, setting_name) 120 | if value not in self.supported_colors: 121 | raise ValueError 122 | except ValueError: 123 | valid = False 124 | raw_value = self._RAW_CONFIG.get(self.GLOBAL_SECTION, setting_name) 125 | output_line(f"Error parsing setting {setting_name}. Invalid value '{raw_value}'") 126 | return valid 127 | 128 | def list(self): 129 | """ 130 | List all color settings and their current values, colored appropriately 131 | """ 132 | supported_colours_strings = [] 133 | for color in self.supported_colors: 134 | supported_colours_strings.append(f"{TERM_COLORS[color].value}{color}{TERM_COLORS.ENDC.value}") 135 | output_line(f"Supported Colors: {', '.join(supported_colours_strings)}\n") 136 | 137 | settings_names = self._get_setting_names() 138 | for setting_name in settings_names: 139 | color = getattr(self, setting_name) 140 | output_line(f"{setting_name}={TERM_COLORS[color].value}{color}{TERM_COLORS.ENDC.value}") 141 | -------------------------------------------------------------------------------- /common/constants.py: -------------------------------------------------------------------------------- 1 | """Constant definitions.""" 2 | 3 | from enum import Enum 4 | 5 | 6 | class TERM_COLORS(Enum): 7 | """Used to colorify terminal output.""" 8 | 9 | BLUE = "\033[34m" 10 | GREEN = "\033[32m" 11 | YELLOW = "\033[33m" 12 | RED = "\033[31m" 13 | PINK = "\033[35m" 14 | CYAN = "\033[36m" 15 | GREY = "\033[1;38;5;240m" 16 | ENDC = "\033[0m" 17 | 18 | 19 | class MSG_TYPE(Enum): 20 | """Log message types.""" 21 | 22 | INFO = 1 23 | SUCCESS = 2 24 | ERROR = 3 25 | 26 | 27 | class GLYPHS(Enum): 28 | """Various characters required to match GEF output.""" 29 | 30 | LEFT_ARROW = " ← " 31 | RIGHT_ARROW = " → " 32 | DOWN_ARROW = "↳" 33 | HORIZONTAL_LINE = "─" 34 | VERTICAL_LINE = "│" 35 | CROSS = "✘ " 36 | TICK = "✓ " 37 | BP_GLYPH = "●" 38 | 39 | 40 | class ALIGN(Enum): 41 | """Alignment values.""" 42 | 43 | LEFT = 1 44 | CENTRE = 2 45 | RIGHT = 3 46 | 47 | 48 | class SIZES(Enum): 49 | """Size of data types""" 50 | 51 | QWORD = 8 52 | DWORD = 4 53 | WORD = 2 54 | BYTE = 1 55 | -------------------------------------------------------------------------------- /common/context_handler.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from typing import Dict, Type, Optional 4 | from string import printable 5 | 6 | from lldb import ( 7 | SBAddress, 8 | SBDebugger, 9 | SBError, 10 | SBExecutionContext, 11 | SBFrame, 12 | SBProcess, 13 | SBTarget, 14 | SBThread, 15 | SBValue, 16 | ) 17 | 18 | from arch import get_arch, get_arch_from_str 19 | from arch.base_arch import BaseArch, FlagRegister 20 | from common.constants import GLYPHS, TERM_COLORS 21 | from common.settings import LLEFSettings 22 | from common.color_settings import LLEFColorSettings 23 | from common.state import LLEFState 24 | from common.util import ( 25 | attempt_to_read_string_from_memory, 26 | clear_page, 27 | get_frame_arguments, 28 | get_registers, 29 | is_code, 30 | is_heap, 31 | is_stack, 32 | print_instruction, 33 | print_line, 34 | print_line_with_string, 35 | change_use_color, 36 | output_line 37 | ) 38 | 39 | 40 | class ContextHandler: 41 | """Context handler.""" 42 | 43 | frame: SBFrame 44 | process: SBProcess 45 | target: SBTarget 46 | thread: SBThread 47 | arch: Type[BaseArch] 48 | debugger: SBDebugger 49 | exe_ctx: SBExecutionContext 50 | settings: LLEFSettings 51 | color_settings: LLEFColorSettings 52 | state: LLEFState 53 | 54 | def __init__( 55 | self, 56 | debugger: SBDebugger, 57 | ) -> None: 58 | """ 59 | For up to date documentation on args provided to this function run: `help target stop-hook add` 60 | """ 61 | self.debugger = debugger 62 | self.settings = LLEFSettings(debugger) 63 | self.color_settings = LLEFColorSettings() 64 | self.state = LLEFState() 65 | change_use_color(self.settings.color_output) 66 | 67 | def generate_rebased_address_string(self, address: SBAddress) -> str: 68 | module = address.GetModule() 69 | 70 | if module is not None and self.settings.rebase_addresses is True: 71 | file_name = os.path.basename(str(module.file)) 72 | rebased_address = address.GetFileAddress() + self.settings.rebase_offset 73 | return ( 74 | f" {TERM_COLORS[self.color_settings.rebased_address_color].value}" 75 | f"({file_name} {rebased_address:#x})" 76 | f"{TERM_COLORS.ENDC.value}" 77 | ) 78 | 79 | return "" 80 | 81 | def generate_printable_line_from_pointer( 82 | self, pointer: SBValue, address_containing_pointer: Optional[int] = None 83 | ) -> str: 84 | """ 85 | Generate a line from a memory address (@pointer) that contains relevant 86 | information about the address. 87 | This is intended to be used when printing stack and register values. 88 | """ 89 | 90 | line = "" 91 | pointer_value = SBAddress(pointer, self.target) 92 | 93 | if pointer_value.symbol.IsValid(): 94 | offset = ( 95 | pointer_value.offset - pointer_value.symbol.GetStartAddress().offset 96 | ) 97 | line += ( 98 | f"{self.generate_rebased_address_string(pointer_value)} {GLYPHS.RIGHT_ARROW.value}" 99 | f"{TERM_COLORS[self.color_settings.dereferenced_value_color].value}" 100 | f"<{pointer_value.symbol.name}+{offset}>" 101 | f"{TERM_COLORS.ENDC.value}" 102 | ) 103 | 104 | referenced_string = attempt_to_read_string_from_memory( 105 | self.process, pointer_value.GetLoadAddress(self.target) 106 | ) 107 | 108 | if len(referenced_string) > 0 and referenced_string.isprintable(): 109 | # Only add this to the line if there are any printable characters in refd_string 110 | referenced_string = referenced_string.replace("\n", " ") 111 | line += ( 112 | f' {GLYPHS.RIGHT_ARROW.value} ("' 113 | f'{TERM_COLORS[self.color_settings.string_color].value}' 114 | f'{referenced_string}' 115 | f'{TERM_COLORS.ENDC.value}"?)' 116 | ) 117 | 118 | if address_containing_pointer is not None: 119 | registers_pointing_to_address = [] 120 | for register in get_registers(self.frame, self.arch().gpr_key): 121 | if register.GetValueAsUnsigned() == address_containing_pointer: 122 | registers_pointing_to_address.append(f"${register.GetName()}") 123 | if len(registers_pointing_to_address) > 0: 124 | reg_list = ", ".join(registers_pointing_to_address) 125 | line += ( 126 | f" {TERM_COLORS[self.color_settings.dereferenced_register_color].value}" 127 | f"{GLYPHS.LEFT_ARROW.value}{reg_list}" 128 | f"{TERM_COLORS.ENDC.value}" 129 | ) 130 | 131 | return line 132 | 133 | def print_stack_addr(self, addr: SBValue, offset: int) -> None: 134 | """Produce a printable line containing information about a given stack @addr and print it""" 135 | # Add stack address to line 136 | line = ( 137 | f"{TERM_COLORS[self.color_settings.stack_address_color].value}{hex(addr.GetValueAsUnsigned())}" 138 | + f"{TERM_COLORS.ENDC.value}{GLYPHS.VERTICAL_LINE.value}" 139 | ) 140 | # Add offset to line 141 | line += f"+{offset:04x}: " 142 | 143 | # Add value to line 144 | err = SBError() 145 | stack_value = self.process.ReadPointerFromMemory(addr.GetValueAsUnsigned(), err) 146 | if err.Success(): 147 | line += f"0x{stack_value:0{self.arch().bits // 4}x}" 148 | else: 149 | # Shouldn't happen as stack should always contain something 150 | line += str(err) 151 | 152 | line += self.generate_printable_line_from_pointer( 153 | stack_value, addr.GetValueAsUnsigned() 154 | ) 155 | output_line(line) 156 | 157 | def print_memory_address(self, addr: int, offset: int, size: int) -> None: 158 | """Print a line containing information about @size bytes at @addr displaying @offset""" 159 | # Add address to line 160 | line = ( 161 | f"{TERM_COLORS[self.color_settings.read_memory_address_color].value}{hex(addr)}" 162 | + f"{TERM_COLORS.ENDC.value}{GLYPHS.VERTICAL_LINE.value}" 163 | ) 164 | # Add offset to line 165 | line += f"+{offset:04x}: " 166 | 167 | # Add value to line 168 | err = SBError() 169 | memory_value = int.from_bytes(self.process.ReadMemory(addr, size, err), 'little') 170 | if err.Success(): 171 | line += f"0x{memory_value:0{size * 2}x}" 172 | else: 173 | line += str(err) 174 | 175 | output_line(line) 176 | 177 | def print_bytes(self, addr: int, size: int) -> None: 178 | """Print a line containing information about @size individual bytes at @addr""" 179 | if size > 0: 180 | # Add address to line 181 | line = ( 182 | f"{TERM_COLORS[self.color_settings.read_memory_address_color].value}{hex(addr)}" 183 | + f"{TERM_COLORS.ENDC.value} " 184 | ) 185 | 186 | # Add value to line 187 | err = SBError() 188 | memory_value: bytes = self.process.ReadMemory(addr, size, err) 189 | if err.Success(): 190 | line += f"{memory_value.hex(' '):47} " 191 | 192 | # Add characters to line 193 | characters = "" 194 | for byte in memory_value: 195 | if chr(byte) in printable.strip(): 196 | characters += chr(byte) 197 | else: 198 | characters += "." 199 | 200 | line += characters 201 | else: 202 | line += str(err) 203 | 204 | output_line(line) 205 | 206 | def print_register(self, register: SBValue) -> None: 207 | """Print details of a @register""" 208 | reg_name = register.GetName() 209 | reg_value = register.GetValueAsUnsigned() 210 | 211 | if self.state.prev_registers.get(reg_name) == register.GetValueAsUnsigned(): 212 | # Register value as not changed 213 | highlight = TERM_COLORS[self.color_settings.register_color] 214 | else: 215 | # Register value has changed so highlight 216 | highlight = TERM_COLORS[self.color_settings.modified_register_color] 217 | 218 | if is_code(reg_value, self.process, self.regions): 219 | color = TERM_COLORS[self.color_settings.code_color] 220 | elif is_stack(reg_value, self.process, self.regions): 221 | color = TERM_COLORS[self.color_settings.stack_color] 222 | elif is_heap(reg_value, self.process, self.regions): 223 | color = TERM_COLORS[self.color_settings.heap_color] 224 | else: 225 | color = TERM_COLORS.ENDC 226 | formatted_reg_value = f"{reg_value:x}".ljust(12) 227 | line = ( 228 | f"{highlight.value}{reg_name.ljust(7)}{TERM_COLORS.ENDC.value}: " 229 | + f"{color.value}0x{formatted_reg_value}{TERM_COLORS.ENDC.value}" 230 | ) 231 | 232 | line += self.generate_printable_line_from_pointer(reg_value) 233 | 234 | output_line(line) 235 | 236 | def print_flags_register(self, flag_register: FlagRegister) -> None: 237 | """Format and print the contents of the flag register.""" 238 | flag_value = self.frame.register[flag_register.name].GetValueAsUnsigned() 239 | 240 | if self.state.prev_registers.get(flag_register.name) == flag_value: 241 | # No change 242 | highlight = TERM_COLORS[self.color_settings.register_color] 243 | else: 244 | # Change and highlight 245 | highlight = TERM_COLORS[self.color_settings.modified_register_color] 246 | 247 | line = f"{highlight.value}{flag_register.name.ljust(7)}{TERM_COLORS.ENDC.value}: [" 248 | line += " ".join( 249 | [ 250 | name.upper() if flag_value & bitmask else name 251 | for name, bitmask in flag_register.bit_masks.items() 252 | ] 253 | ) 254 | line += "]" 255 | output_line(line) 256 | 257 | def update_registers(self) -> None: 258 | """ 259 | This updates the cached registers, which are used to track which registered have changed. 260 | If there is no frame currently then the previous registers do not change 261 | """ 262 | self.state.prev_registers = self.state.current_registers.copy() 263 | if self.frame is not None: 264 | for reg_set in self.frame.registers: 265 | for reg in reg_set: 266 | self.state.current_registers[reg.GetName()] = reg.GetValueAsUnsigned() 267 | 268 | def print_legend(self) -> None: 269 | """Print a line containing the color legend""" 270 | 271 | output_line( 272 | f"[ Legend: " 273 | f"{TERM_COLORS[self.color_settings.modified_register_color].value}" 274 | f"Modified register{TERM_COLORS.ENDC.value} | " 275 | f"{TERM_COLORS[self.color_settings.code_color].value}Code{TERM_COLORS.ENDC.value} | " 276 | f"{TERM_COLORS[self.color_settings.heap_color].value}Heap{TERM_COLORS.ENDC.value} | " 277 | f"{TERM_COLORS[self.color_settings.stack_color].value}Stack{TERM_COLORS.ENDC.value} | " 278 | f"{TERM_COLORS[self.color_settings.string_color].value}String{TERM_COLORS.ENDC.value} ]" 279 | ) 280 | 281 | def display_registers(self) -> None: 282 | """Print the registers display section""" 283 | 284 | print_line_with_string( 285 | "registers", 286 | line_color=TERM_COLORS[self.color_settings.line_color], 287 | string_color=TERM_COLORS[self.color_settings.section_header_color] 288 | ) 289 | 290 | if self.settings.show_all_registers: 291 | register_list = [] 292 | for reg_set in self.frame.registers: 293 | for reg in reg_set: 294 | register_list.append(reg.name) 295 | for reg in self.arch.flag_registers: 296 | if reg.name in register_list: 297 | register_list.remove(reg.name) 298 | else: 299 | register_list = self.arch().gpr_registers 300 | 301 | for reg in register_list: 302 | if self.frame.register[reg] is not None: 303 | self.print_register(self.frame.register[reg]) 304 | for flag_register in self.arch.flag_registers: 305 | if self.frame.register[flag_register.name] is not None: 306 | self.print_flags_register(flag_register) 307 | 308 | def display_stack(self) -> None: 309 | """Print information about the contents of the top of the stack""" 310 | 311 | print_line_with_string( 312 | "stack", 313 | line_color=TERM_COLORS[self.color_settings.line_color], 314 | string_color=TERM_COLORS[self.color_settings.section_header_color] 315 | ) 316 | for inc in range(0, self.arch().bits, 8): 317 | stack_pointer = self.frame.GetSP() 318 | addr = self.target.EvaluateExpression(f"{stack_pointer} + {inc}") 319 | self.print_stack_addr(addr, inc) 320 | 321 | def display_code(self) -> None: 322 | """ 323 | Print the disassembly generated by LLDB. 324 | """ 325 | print_line_with_string( 326 | "code", 327 | line_color=TERM_COLORS[self.color_settings.line_color], 328 | string_color=TERM_COLORS[self.color_settings.section_header_color] 329 | ) 330 | 331 | if self.frame.disassembly: 332 | instructions = self.frame.disassembly.split("\n") 333 | 334 | current_pc = hex(self.frame.GetPC()) 335 | for i, item in enumerate(instructions): 336 | if current_pc in item.split(':')[0]: 337 | output_line(instructions[0]) 338 | if i > 3: 339 | print_instruction(instructions[i - 3], TERM_COLORS[self.color_settings.instruction_color]) 340 | print_instruction(instructions[i - 2], TERM_COLORS[self.color_settings.instruction_color]) 341 | print_instruction(instructions[i - 1], TERM_COLORS[self.color_settings.instruction_color]) 342 | print_instruction(item, TERM_COLORS[self.color_settings.highlighted_instruction_color]) 343 | # This slice notation (and the 4 below) are a buggy interaction of black and pycodestyle 344 | # See: https://github.com/psf/black/issues/157 345 | # fmt: off 346 | for instruction in instructions[i + 1:i + 6]: # noqa 347 | # fmt: on 348 | print_instruction(instruction) 349 | if i == 3: 350 | print_instruction(instructions[i - 2], TERM_COLORS[self.color_settings.instruction_color]) 351 | print_instruction(instructions[i - 1], TERM_COLORS[self.color_settings.instruction_color]) 352 | print_instruction(item, TERM_COLORS[self.color_settings.highlighted_instruction_color]) 353 | # fmt: off 354 | for instruction in instructions[i + 1:10]: # noqa 355 | # fmt: on 356 | print_instruction(instruction) 357 | if i == 2: 358 | print_instruction(instructions[i - 1], TERM_COLORS[self.color_settings.instruction_color]) 359 | print_instruction(item, TERM_COLORS[self.color_settings.highlighted_instruction_color]) 360 | # fmt: off 361 | for instruction in instructions[i + 1:10]: # noqa 362 | # fmt: on 363 | print_instruction(instruction) 364 | if i == 1: 365 | print_instruction(item, TERM_COLORS[self.color_settings.highlighted_instruction_color]) 366 | # fmt: off 367 | for instruction in instructions[i + 1:10]: # noqa 368 | # fmt: on 369 | print_instruction(instruction) 370 | else: 371 | output_line("No disassembly to print") 372 | 373 | def display_threads(self) -> None: 374 | """Print LLDB formatted thread information""" 375 | print_line_with_string( 376 | "threads", 377 | line_color=TERM_COLORS[self.color_settings.line_color], 378 | string_color=TERM_COLORS[self.color_settings.section_header_color] 379 | ) 380 | for thread in self.process: 381 | output_line(thread) 382 | 383 | def display_trace(self) -> None: 384 | """ 385 | Prints the call stack including arguments if LLDB knows them. 386 | """ 387 | print_line_with_string( 388 | "trace", 389 | line_color=TERM_COLORS[self.color_settings.line_color], 390 | string_color=TERM_COLORS[self.color_settings.section_header_color] 391 | ) 392 | 393 | for i in range(self.thread.GetNumFrames()): 394 | if i == 0: 395 | number_color = TERM_COLORS[self.color_settings.highlighted_index_color] 396 | else: 397 | number_color = TERM_COLORS[self.color_settings.index_color] 398 | line = f"[{number_color.value}#{i}{TERM_COLORS.ENDC.value}] " 399 | 400 | current_frame = self.thread.GetFrameAtIndex(i) 401 | pc_address = current_frame.GetPCAddress() 402 | func = current_frame.GetFunction() 403 | trace_address = pc_address.GetLoadAddress(self.target) 404 | 405 | if func: 406 | line += ( 407 | f"{trace_address:#x}{self.generate_rebased_address_string(pc_address)} {GLYPHS.RIGHT_ARROW.value} " 408 | f"{TERM_COLORS[self.color_settings.function_name_color].value}" 409 | f"{func.GetName()}{TERM_COLORS.ENDC.value}" 410 | ) 411 | else: 412 | line += ( 413 | f"{trace_address:#x}{self.generate_rebased_address_string(pc_address)} {GLYPHS.RIGHT_ARROW.value} " 414 | f"{TERM_COLORS[self.color_settings.function_name_color].value}" 415 | f"{current_frame.GetSymbol().GetName()}{TERM_COLORS.ENDC.value}" 416 | ) 417 | 418 | line += get_frame_arguments( 419 | current_frame, 420 | frame_argument_name_color=TERM_COLORS[self.color_settings.frame_argument_name_color] 421 | ) 422 | 423 | output_line(line) 424 | 425 | def refresh(self, exe_ctx: SBExecutionContext) -> None: 426 | """Refresh stored values""" 427 | self.frame = exe_ctx.GetFrame() 428 | self.process = exe_ctx.GetProcess() 429 | self.target = exe_ctx.GetTarget() 430 | self.thread = exe_ctx.GetThread() 431 | if self.settings.force_arch is not None: 432 | self.arch = get_arch_from_str(self.settings.force_arch) 433 | else: 434 | self.arch = get_arch(self.target) 435 | 436 | if self.settings.register_coloring is True: 437 | self.regions = self.process.GetMemoryRegions() 438 | else: 439 | self.regions = None 440 | 441 | def display_context( 442 | self, 443 | exe_ctx: SBExecutionContext, 444 | update_registers: bool 445 | ) -> None: 446 | """For up to date documentation on args provided to this function run: `help target stop-hook add`""" 447 | 448 | # Refresh frame, process, target, and thread objects at each stop. 449 | self.refresh(exe_ctx) 450 | 451 | # Update current and previous registers 452 | if update_registers: 453 | self.update_registers() 454 | 455 | # Hack to print cursor at the top of the screen 456 | clear_page() 457 | 458 | if self.settings.show_legend: 459 | self.print_legend() 460 | 461 | if self.settings.show_registers: 462 | self.display_registers() 463 | 464 | if self.settings.show_stack: 465 | self.display_stack() 466 | 467 | if self.settings.show_code: 468 | self.display_code() 469 | 470 | if self.settings.show_threads: 471 | self.display_threads() 472 | 473 | if self.settings.show_trace: 474 | self.display_trace() 475 | 476 | print_line(color=TERM_COLORS[self.color_settings.line_color]) 477 | -------------------------------------------------------------------------------- /common/de_bruijn.py: -------------------------------------------------------------------------------- 1 | """De Bruijn sequence utilities.""" 2 | 3 | import itertools 4 | 5 | 6 | def de_bruijn(alphabet: str, n: int) -> str: 7 | """ 8 | Generate De Bruijn sequence for alphabet and subsequences of length n (for compatibility. w/ pwnlib). 9 | Taken from GEF gef.py L3728 (2022.06). 10 | """ 11 | 12 | k = len(alphabet) 13 | a = [0] * k * n 14 | 15 | def db(t, p): 16 | if t > n: 17 | if n % p == 0: 18 | for j in range(1, p + 1): 19 | yield alphabet[a[j]] 20 | else: 21 | a[t] = a[t - p] 22 | yield from db(t + 1, p) 23 | 24 | for j in range(a[t - p] + 1, k): 25 | a[t] = j 26 | yield from db(t + 1, t) 27 | 28 | return db(1, 1) 29 | 30 | 31 | def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: 32 | """ 33 | Create a @length byte bytearray of a de Bruijn cyclic pattern. 34 | Taken from GEF gef.py L3749 (2022.06) 35 | """ 36 | charset = bytearray(b"abcdefghijklmnopqrstuvwxyz") 37 | return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) 38 | -------------------------------------------------------------------------------- /common/settings.py: -------------------------------------------------------------------------------- 1 | """Global settings module""" 2 | import os 3 | 4 | from arch import supported_arch 5 | from common.singleton import Singleton 6 | from common.base_settings import BaseLLEFSettings 7 | from common.util import change_use_color, output_line 8 | 9 | from lldb import SBDebugger 10 | 11 | 12 | class LLEFSettings(BaseLLEFSettings, metaclass=Singleton): 13 | """ 14 | Global general settings class - loaded from file defined in `LLEF_CONFIG_PATH` 15 | """ 16 | 17 | LLEF_CONFIG_PATH = os.path.join(os.path.expanduser('~'), ".llef") 18 | GLOBAL_SECTION = "LLEF" 19 | debugger: SBDebugger = None 20 | 21 | @property 22 | def color_output(self): 23 | default = False 24 | if self.debugger is not None: 25 | default = self.debugger.GetUseColor() 26 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "color_output", fallback=default) 27 | 28 | @property 29 | def register_coloring(self): 30 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "register_coloring", fallback=True) 31 | 32 | @property 33 | def show_legend(self): 34 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_legend", fallback=True) 35 | 36 | @property 37 | def show_registers(self): 38 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_registers", fallback=True) 39 | 40 | @property 41 | def show_stack(self): 42 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_stack", fallback=True) 43 | 44 | @property 45 | def show_code(self): 46 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_code", fallback=True) 47 | 48 | @property 49 | def show_threads(self): 50 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_threads", fallback=True) 51 | 52 | @property 53 | def show_trace(self): 54 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_trace", fallback=True) 55 | 56 | @property 57 | def force_arch(self): 58 | arch = self._RAW_CONFIG.get(self.GLOBAL_SECTION, "force_arch", fallback=None) 59 | return None if arch not in supported_arch else arch 60 | 61 | @property 62 | def rebase_addresses(self): 63 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "rebase_addresses", fallback=True) 64 | 65 | @property 66 | def rebase_offset(self): 67 | return self._RAW_CONFIG.getint(self.GLOBAL_SECTION, "rebase_offset", fallback=0x100000) 68 | 69 | @property 70 | def show_all_registers(self): 71 | return self._RAW_CONFIG.getboolean(self.GLOBAL_SECTION, "show_all_registers", fallback=False) 72 | 73 | def validate_settings(self, setting=None) -> bool: 74 | """ 75 | Validate settings by attempting to retrieve all properties thus executing any ConfigParser coverters 76 | """ 77 | settings_names = LLEFSettings._get_setting_names() 78 | 79 | if setting: 80 | if setting not in settings_names: 81 | output_line(f"Invalid LLEF setting {setting}") 82 | return False 83 | settings_names = [setting] 84 | 85 | valid = True 86 | for setting_name in settings_names: 87 | try: 88 | value = getattr(self, setting_name) 89 | if ( 90 | setting_name == "color_output" 91 | and value is True 92 | and self.debugger is not None 93 | and self.debugger.GetUseColor() is False 94 | ): 95 | print("Colour is not supported by your terminal") 96 | raise ValueError 97 | except ValueError: 98 | valid = False 99 | raw_value = self._RAW_CONFIG.get(self.GLOBAL_SECTION, setting_name) 100 | output_line(f"Error parsing setting {setting_name}. Invalid value '{raw_value}'") 101 | return valid 102 | 103 | def __init__(self, debugger: SBDebugger): 104 | super().__init__() 105 | self.debugger = debugger 106 | 107 | def set(self, setting: str, value: str): 108 | super().set(setting, value) 109 | 110 | if setting == "color_output": 111 | change_use_color(self.color_output) 112 | 113 | def load(self, reset=False): 114 | super().load(reset) 115 | change_use_color(self.color_output) 116 | -------------------------------------------------------------------------------- /common/singleton.py: -------------------------------------------------------------------------------- 1 | """Singleton module""" 2 | 3 | 4 | class Singleton(type): 5 | """ 6 | Singleton class implementation. Use with metaclass=Singleton. 7 | """ 8 | _instances = {} 9 | 10 | def __call__(cls, *args, **kwargs): 11 | if cls not in cls._instances: 12 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 13 | return cls._instances[cls] 14 | -------------------------------------------------------------------------------- /common/state.py: -------------------------------------------------------------------------------- 1 | """Global state module""" 2 | from typing import Dict 3 | 4 | from common.singleton import Singleton 5 | 6 | 7 | class LLEFState(metaclass=Singleton): 8 | """ 9 | Global state class - stores state accessible across any LLEF command/handler 10 | """ 11 | 12 | # Stores previous register state at the last breakpoint 13 | prev_registers: Dict[str, int] = {} 14 | 15 | # Stores register state at the current breakpoint (caches the contents of the current frame as frame is mutable) 16 | current_registers: Dict[str, int] = {} 17 | 18 | # Stores patterns created by the `pattern` command 19 | created_patterns = [] 20 | 21 | # Stores whether color should be used 22 | use_color = False 23 | -------------------------------------------------------------------------------- /common/util.py: -------------------------------------------------------------------------------- 1 | """Utility functions.""" 2 | 3 | from typing import List, Any 4 | import re 5 | import shutil 6 | 7 | from lldb import SBError, SBFrame, SBMemoryRegionInfo, SBMemoryRegionInfoList, SBProcess, SBValue 8 | 9 | from common.constants import ALIGN, GLYPHS, MSG_TYPE, TERM_COLORS 10 | from common.state import LLEFState 11 | 12 | 13 | def change_use_color(new_value: bool) -> None: 14 | """ 15 | Change the global use_color bool. use_color should not be written to directly 16 | """ 17 | LLEFState.use_color = new_value 18 | 19 | 20 | def output_line(line: Any) -> None: 21 | """ 22 | Format a line of output for printing. Print should not be used elsewhere. 23 | Exception - clear_page would not function without terminal characters 24 | """ 25 | line = str(line) 26 | ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') 27 | if LLEFState.use_color is False: 28 | line = ansi_escape.sub('', line) 29 | print(line) 30 | 31 | 32 | def clear_page() -> None: 33 | """ 34 | Used to clear the previously printed breakpoint information before 35 | printing the next information. 36 | """ 37 | num_lines = shutil.get_terminal_size().lines 38 | for _ in range(num_lines): 39 | print() 40 | print("\033[0;0H") # Ansi escape code: Set cursor to 0,0 position 41 | print("\033[J") # Ansi escape code: Clear contents from cursor to end of screen 42 | 43 | 44 | def print_line_with_string( 45 | string: str, 46 | char: GLYPHS = GLYPHS.HORIZONTAL_LINE, 47 | line_color: TERM_COLORS = TERM_COLORS.GREY, 48 | string_color: TERM_COLORS = TERM_COLORS.BLUE, 49 | align: ALIGN = ALIGN.RIGHT, 50 | ) -> None: 51 | """Print a line with the provided @string padded with @char""" 52 | width = shutil.get_terminal_size().columns 53 | if align == ALIGN.RIGHT: 54 | l_pad = (width - len(string) - 6) * char.value 55 | r_pad = 4 * char.value 56 | 57 | elif align == ALIGN.CENTRE: 58 | l_pad = (width - len(string)) * char.value 59 | r_pad = 4 * char.value 60 | 61 | elif align == ALIGN.LEFT: 62 | l_pad = 4 * char.value 63 | r_pad = (width - len(string) - 6) * char.value 64 | 65 | output_line( 66 | f"{line_color.value}{l_pad}{TERM_COLORS.ENDC.value} " 67 | + f"{string_color.value}{string}{TERM_COLORS.ENDC.value} {line_color.value}{r_pad}{TERM_COLORS.ENDC.value}" 68 | ) 69 | 70 | 71 | def print_line( 72 | char: GLYPHS = GLYPHS.HORIZONTAL_LINE, color: TERM_COLORS = TERM_COLORS.GREY 73 | ) -> None: 74 | """Print a line of @char""" 75 | output_line( 76 | f"{color.value}{shutil.get_terminal_size().columns*char.value}{TERM_COLORS.ENDC.value}" 77 | ) 78 | 79 | 80 | def print_message(msg_type: MSG_TYPE, message: str) -> None: 81 | """Format and print a @message""" 82 | info_color = TERM_COLORS.BLUE 83 | success_color = TERM_COLORS.GREEN 84 | error_color = TERM_COLORS.GREEN 85 | 86 | if msg_type == MSG_TYPE.INFO: 87 | output_line(f"{info_color.value}[+]{TERM_COLORS.ENDC.value} {message}") 88 | elif msg_type == MSG_TYPE.SUCCESS: 89 | output_line(f"{success_color.value}[+]{TERM_COLORS.ENDC.value} {message}") 90 | elif msg_type == MSG_TYPE.ERROR: 91 | output_line(f"{error_color.value}[+]{TERM_COLORS.ENDC.value} {message}") 92 | 93 | 94 | def print_instruction(line: str, color: TERM_COLORS = TERM_COLORS.ENDC) -> None: 95 | """Format and print a line of disassembly returned from LLDB (SBFrame.disassembly)""" 96 | loc_0x = line.find("0x") 97 | start_idx = loc_0x if loc_0x >= 0 else 0 98 | output_line(f"{color.value}{line[start_idx:]}{TERM_COLORS.ENDC.value}") 99 | 100 | 101 | def get_registers(frame: SBFrame, frame_type: str) -> List[SBValue]: 102 | """ 103 | Returns the registers in @frame that are of the specified @type. 104 | A @type is a string defined in LLDB, e.g. "General Purpose" 105 | """ 106 | registers = [] 107 | for regs in frame.GetRegisters(): 108 | if frame_type.lower() in regs.GetName().lower(): 109 | registers = regs 110 | return registers 111 | 112 | 113 | def get_frame_arguments(frame: SBFrame, frame_argument_name_color: TERM_COLORS) -> str: 114 | """ 115 | Returns a string containing args of the supplied frame 116 | """ 117 | # GetVariables(arguments, locals, statics, in_scope_only) 118 | variables = frame.GetVariables(True, False, False, True) 119 | args = [] 120 | for var in variables: 121 | # get and format argument value 122 | value = "???" 123 | var_value = var.GetValue() 124 | if var_value is None: 125 | value = "null" 126 | elif var_value: 127 | try: 128 | value = f"{int(var.GetValue(), 0):#x}" 129 | except ValueError: 130 | pass 131 | args.append( 132 | f"{frame_argument_name_color.value}{var.GetName()}{TERM_COLORS.ENDC.value}={value}" 133 | ) 134 | return f"({' '.join(args)})" 135 | 136 | 137 | def attempt_to_read_string_from_memory( 138 | process: SBProcess, addr: SBValue, buffer_size: int = 256 139 | ) -> str: 140 | """ 141 | Returns a string from a memory address if one can be read, else an empty string 142 | """ 143 | err = SBError() 144 | ret_string = "" 145 | try: 146 | string = process.ReadCStringFromMemory(addr, buffer_size, err) 147 | if err.Success(): 148 | ret_string = string 149 | except SystemError: 150 | # This swallows an internal error that is sometimes generated by a bug in LLDB. 151 | pass 152 | return ret_string 153 | 154 | 155 | def is_code(address: SBValue, process: SBProcess, regions: SBMemoryRegionInfoList) -> bool: 156 | """Determines whether an @address points to code""" 157 | if regions is None: 158 | return False 159 | region = SBMemoryRegionInfo() 160 | code_bool = False 161 | if regions.GetMemoryRegionContainingAddress(address, region): 162 | code_bool = region.IsExecutable() 163 | return code_bool 164 | 165 | 166 | def is_stack(address: SBValue, process: SBProcess, regions: SBMemoryRegionInfoList) -> bool: 167 | """Determines whether an @address points to the stack""" 168 | if regions is None: 169 | return False 170 | region = SBMemoryRegionInfo() 171 | stack_bool = False 172 | if regions.GetMemoryRegionContainingAddress(address, region): 173 | if region.GetName() == "[stack]": 174 | stack_bool = True 175 | return stack_bool 176 | 177 | 178 | def is_heap(address: SBValue, process: SBProcess, regions: SBMemoryRegionInfoList) -> bool: 179 | """Determines whether an @address points to the heap""" 180 | if regions is None: 181 | return False 182 | region = SBMemoryRegionInfo() 183 | heap_bool = False 184 | if regions.GetMemoryRegionContainingAddress(address, region): 185 | if region.GetName() == "[heap]": 186 | heap_bool = True 187 | return heap_bool 188 | 189 | 190 | def extract_arch_from_triple(triple: str) -> str: 191 | """Extracts the architecture from triple string.""" 192 | return triple.split("-")[0] 193 | -------------------------------------------------------------------------------- /handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/foundryzero/llef/d860cedadbb37498b9789531b849c7e2ec6b6103/handlers/__init__.py -------------------------------------------------------------------------------- /handlers/stop_hook.py: -------------------------------------------------------------------------------- 1 | """Break point handler.""" 2 | from typing import Any, Dict 3 | 4 | from lldb import ( 5 | SBDebugger, 6 | SBExecutionContext, 7 | SBStream, 8 | SBStructuredData, 9 | SBTarget, 10 | ) 11 | 12 | from common.context_handler import ContextHandler 13 | 14 | 15 | class StopHookHandler: 16 | """Stop Hook handler.""" 17 | 18 | context_handler: ContextHandler 19 | 20 | @classmethod 21 | def lldb_self_register(cls, debugger: SBDebugger, module_name: str) -> None: 22 | """Register the Stop Hook Handler""" 23 | 24 | command = f"target stop-hook add -P {module_name}.{cls.__name__}" 25 | debugger.HandleCommand(command) 26 | 27 | def __init__( 28 | self, target: SBTarget, _: SBStructuredData, __: Dict[Any, Any] 29 | ) -> None: 30 | """ 31 | For up to date documentation on args provided to this function run: `help target stop-hook add` 32 | """ 33 | self.context_handler = ContextHandler(target.debugger) 34 | 35 | def handle_stop(self, exe_ctx: SBExecutionContext, _: SBStream) -> None: 36 | """For up to date documentation on args provided to this function run: `help target stop-hook add`""" 37 | self.context_handler.display_context(exe_ctx, True) 38 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | install () { 4 | echo "command script import \"$( dirname -- "$( readlink -f -- "$0"; )"; )/llef.py\"" > $HOME/.lldbinit; 5 | echo "settings set stop-disassembly-display never" >> $HOME/.lldbinit; 6 | 7 | while true; do 8 | read -p "LLDB uses AT&T disassembly syntax for x86 binaries would you like to force intel syntax? [y/n] " yn 9 | case $yn in 10 | [Yy]* ) echo "settings set target.x86-disassembly-flavor intel" >> $HOME/.lldbinit; break;; 11 | [Nn]* ) exit ; break;; 12 | * ) echo "Please answer 'y' or 'n'.";; 13 | esac 14 | done 15 | } 16 | 17 | manualinstall () { 18 | echo "Paste these lines into ~/.lldbinit"; 19 | echo ""; 20 | echo "command script import \"$( dirname -- "$( readlink -f -- "$0"; )"; )/llef.py\""; 21 | echo "settings set stop-disassembly-display never"; 22 | echo ""; 23 | echo "Optionally include the following line to use intel disassembly syntax for x86 binaries rather than AT&T"; 24 | echo ""; 25 | echo "settings set target.x86-disassembly-flavor intel"; 26 | } 27 | 28 | while true; do 29 | read -p "Automatic install will overwrite $HOME/.lldbinit - Enter 'y' continue or 'n' to view manual instructions? [y/n] " yn 30 | case $yn in 31 | [Yy]* ) install; break;; 32 | [Nn]* ) manualinstall; break;; 33 | * ) echo "Please answer 'y' or 'n'.";; 34 | esac 35 | done 36 | 37 | -------------------------------------------------------------------------------- /llef.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """LLEF main handler.""" 3 | 4 | # --------------------------------------------------------------------- 5 | # To use this in the embedded python interpreter using "lldb" just 6 | # import it with the full path using the "command script import" 7 | # command`` 8 | # (lldb) command script import /path/to/cmdtemplate.py 9 | # 10 | # The __lldb_init_module function automatically loads the stop-hook-handler 11 | # --------------------------------------------------------------------- 12 | 13 | from typing import Any, Dict, List, Type, Union 14 | 15 | from lldb import SBDebugger 16 | 17 | from commands.base_command import BaseCommand 18 | from commands.base_container import BaseContainer 19 | from commands.pattern import ( 20 | PatternContainer, 21 | PatternCreateCommand, 22 | PatternSearchCommand, 23 | ) 24 | from commands.context import ContextCommand 25 | from commands.settings import SettingsCommand 26 | from commands.color_settings import ColorSettingsCommand 27 | from commands.hexdump import HexdumpCommand 28 | from handlers.stop_hook import StopHookHandler 29 | 30 | 31 | def __lldb_init_module(debugger: SBDebugger, _: Dict[Any, Any]) -> None: 32 | commands: List[Union[Type[BaseCommand], Type[BaseContainer]]] = [ 33 | PatternContainer, 34 | PatternCreateCommand, 35 | PatternSearchCommand, 36 | ContextCommand, 37 | SettingsCommand, 38 | ColorSettingsCommand, 39 | HexdumpCommand 40 | ] 41 | 42 | handlers = [StopHookHandler] 43 | 44 | for command in commands: 45 | command.lldb_self_register(debugger, "llef") 46 | 47 | for handler in handlers: 48 | handler.lldb_self_register(debugger, "llef") 49 | --------------------------------------------------------------------------------