├── .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 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------