├── tests ├── __init__.py ├── commands │ ├── __init__.py │ ├── kernel │ │ └── ksymaddr.py │ ├── got_audit.py │ ├── visualize_heap.py │ ├── ropper.py │ ├── unicorn_emulate.py │ ├── keystone_assemble.py │ ├── capstone_disassemble.py │ ├── set_permission.py │ └── syscall_args.py ├── requirements.txt ├── binaries │ ├── unicorn.c │ ├── default.c │ ├── visualize_heap.c │ ├── set-permission.c │ ├── syscall-args.c │ ├── Makefile │ └── utils.h ├── pytest.ini └── base.py ├── scripts ├── __init__.py ├── kernel │ ├── __init__.py │ └── symbols.py ├── libc_function_args │ ├── tables │ │ ├── libc.txt.gz │ │ └── generator.py │ └── __init__.py ├── TEMPLATE ├── error │ └── __init__.py ├── ropper.py ├── remote.py ├── stack.py ├── skel.py ├── xref-telescope.py ├── peekpointers.py ├── ftrace.py ├── bincompare.py ├── bytearray.py ├── syscall_args │ └── __init__.py ├── retdec.py ├── got_audit.py └── visualize_heap.py ├── docs ├── requirements.txt ├── archs │ ├── arm-openocd.md │ └── arm-blackmagicprobe.md ├── commands │ ├── ftrace.md │ ├── ropper.md │ ├── skel.md │ ├── visualize_heap.md │ ├── is-syscall.md │ ├── gef-openocd-remote.md │ ├── capstone-disassemble.md │ ├── error.md │ ├── ksymaddr.md │ ├── got-audit.md │ ├── glibc_function_args.md │ ├── peekpointers.md │ ├── unicorn-emulate.md │ ├── windbg.md │ ├── set-permission.md │ ├── gef-bmp-remote.md │ ├── ida-rpyc.md │ ├── retdec.md │ ├── assemble.md │ ├── syscall-args.md │ ├── bytearray.md │ └── bincompare.md ├── index.md ├── install.md └── .markdownlint.yaml ├── .gitattributes ├── .github ├── FUNDING.yml ├── workflows │ ├── docs-link-check.yml │ ├── validate.yml │ ├── generate-docs.yml │ ├── discord-notify.yml │ └── tests.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yml ├── os ├── README.md ├── macho.py └── pe.py ├── requirements.txt ├── .editorconfig ├── structs ├── malloc_chunk_t.py ├── tcache_entry.py ├── socketaddr_in_t.py ├── malloc_arena_t.py ├── elf32_t.py ├── README.md ├── io_file64_t.py └── elf64_t.py ├── .pre-commit-config.yaml ├── archs ├── arm-cortex-m.py ├── m68k.py ├── arm-openocd.py └── arm-blackmagicprobe.py ├── mkdocs.yml ├── LICENSE ├── .gitignore └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/kernel/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material 2 | lazydocs 3 | pre-commit 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # https://help.github.com/articles/dealing-with-line-endings/ 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [hugsy,] 4 | -------------------------------------------------------------------------------- /os/README.md: -------------------------------------------------------------------------------- 1 | ## Additional File Format Support for GEF 2 | 3 | Experiements for adding new OS support for GEF 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | capstone 2 | keystone-engine 3 | pygments 4 | retdec-python 5 | requests 6 | ropper 7 | rpyc 8 | unicorn 9 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | pylint 2 | pytest 3 | pytest-cov 4 | pytest-xdist 5 | pytest-benchmark 6 | pytest-forked 7 | coverage 8 | rpyc 9 | -------------------------------------------------------------------------------- /scripts/libc_function_args/tables/libc.txt.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugsy/gef-extras/HEAD/scripts/libc_function_args/tables/libc.txt.gz -------------------------------------------------------------------------------- /docs/archs/arm-openocd.md: -------------------------------------------------------------------------------- 1 | ## ARMOpenOCD 2 | 3 | The ARM OpenOCD architecture is a special arcthtecture used with the `gef-openocd-remote` command. 4 | Please read the [documentation](../commands/gef-openocd-remote.md) for the command. 5 | -------------------------------------------------------------------------------- /docs/archs/arm-blackmagicprobe.md: -------------------------------------------------------------------------------- 1 | ## ARMBlackMagicProbe 2 | 3 | The ARM BlackMagicProbe architecture is a special arcthtecture used with the `gef-bmp-remote` 4 | command. Please read the [documentation](../commands/gef-bmp-remote.md) for the command. 5 | -------------------------------------------------------------------------------- /tests/binaries/unicorn.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int function1() 5 | { 6 | const char* a1 = "PATH"; 7 | const char* a2 = "WHATEVER"; 8 | return strcmp( getenv(a1), a2); 9 | } 10 | 11 | int main() 12 | { 13 | return function1(); 14 | } 15 | -------------------------------------------------------------------------------- /docs/commands/ftrace.md: -------------------------------------------------------------------------------- 1 | ## Command ftrace 2 | 3 | 4 | A quick'n dirty function tracer scripts for GEF. 5 | 6 | To use: 7 | 8 | ```text 9 | gef➤ ftrace , , ... 10 | ``` 11 | 12 | Example: 13 | 14 | ```text 15 | gef➤ ftrace malloc,1 calloc,2 free,1 16 | ``` 17 | -------------------------------------------------------------------------------- /tests/binaries/default.c: -------------------------------------------------------------------------------- 1 | /** 2 | * default.c 3 | * -*- mode: c -*- 4 | * -*- coding: utf-8 -*- 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | int main(int argc, char** argv, char** envp) 13 | { 14 | printf("Hello World!\n"); 15 | return EXIT_SUCCESS; 16 | } 17 | -------------------------------------------------------------------------------- /docs/commands/ropper.md: -------------------------------------------------------------------------------- 1 | ## Command ropper 2 | 3 | `ropper` is a gadget finding tool, easily installable via `pip`. It provides a 4 | very convenient `--search` function to search gadgets from a regular 5 | expression: 6 | 7 | ![ropper](https://pbs.twimg.com/media/Cm4f4i5VIAAP-E2.jpg:large) 8 | 9 | `ropper` comes with a full set of options, all documented from the `--help` menu. 10 | -------------------------------------------------------------------------------- /docs/commands/skel.md: -------------------------------------------------------------------------------- 1 | ## Command skel 2 | 3 | `skel` prepares quickly a `pwntools` based exploit template for both local and remote targets, 4 | based on the currently debugged file. 5 | 6 | ### How-To use 7 | 8 | #### With a local target 9 | 10 | ```text 11 | gef➤ skel local 12 | ``` 13 | 14 | #### With a remote target 15 | 16 | ```text 17 | gef➤ skel remote=TARGET:PORT 18 | ``` 19 | -------------------------------------------------------------------------------- /tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_level = INFO 3 | minversion = 6.0 4 | required_plugins = 5 | pytest-benchmark 6 | pytest-cov 7 | pytest-xdist 8 | python_functions = 9 | test_* 10 | time_* 11 | python_files = *.py 12 | testpaths = 13 | . 14 | markers = 15 | slow: flag test as slow (deselect with '-m "not slow"') 16 | online: flag test as requiring internet to work (deselect with '-m "not online"') 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.py] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [Makefile] 17 | indent_style = tab 18 | 19 | [*.yml] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /structs/malloc_chunk_t.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "hugsy" 2 | __VERSION__ = 0.1 3 | __DESCRIPTION__ = """glibc malloc chunk structure from https://sourceware.org/glibc/wiki/MallocInternals""" 4 | 5 | from ctypes import * 6 | 7 | 8 | class malloc_chunk_t(Structure): 9 | _values_ = [] 10 | 11 | 12 | malloc_chunk_t._fields_ = [ 13 | ("prev_size", c_uint64), 14 | ("size", c_uint64), 15 | 16 | ("fd", POINTER(malloc_chunk_t)), 17 | ("bk", POINTER(malloc_chunk_t)), 18 | ] 19 | -------------------------------------------------------------------------------- /docs/commands/visualize_heap.md: -------------------------------------------------------------------------------- 1 | ## Command `visualize-libc-heap-chunks` 2 | 3 | _Alias_: `heap-view` 4 | 5 | 6 | This plugin aims to provide an ASCII-based simplistic representation of the heap layout. 7 | 8 | Currently only the glibc heap support is implemented. The command doesn't take argument, and 9 | display the heap layout. It also aggregates similar lines for better readability: 10 | 11 | ```text 12 | gef➤ visualize-libc-heap-chunks 13 | ``` 14 | 15 | ![img](https://i.imgur.com/jQYaiyB.png) 16 | -------------------------------------------------------------------------------- /tests/binaries/visualize_heap.c: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "utils.h" 11 | 12 | int main(int argc, char **argv) 13 | { 14 | void *p = malloc(0x64); 15 | (void)p; 16 | void *r = malloc(0x28); 17 | (void)r; 18 | void *q = malloc(0x64); 19 | (void)q; 20 | 21 | puts("Much space, so heap!"); 22 | fflush(stdout); 23 | 24 | DebugBreak(); 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /tests/commands/kernel/ksymaddr.py: -------------------------------------------------------------------------------- 1 | """ 2 | `ksymaddr` command test module 3 | """ 4 | 5 | 6 | from tests.base import RemoteGefUnitTestGeneric 7 | 8 | 9 | class KsymaddrCommand(RemoteGefUnitTestGeneric): 10 | """`ksymaddr` command test module""" 11 | 12 | cmd = "ksymaddr" 13 | 14 | def test_cmd_ksymaddr(self): 15 | gdb = self._gdb 16 | res = gdb.execute(f"{self.cmd} prepare_kernel_cred", to_string=True) 17 | self.assertIn("Found matching symbol for 'prepare_kernel_cred'", res) 18 | -------------------------------------------------------------------------------- /docs/commands/is-syscall.md: -------------------------------------------------------------------------------- 1 | ## Command is-syscall 2 | 3 | `gef` can be used to determine whether the instruction to be executed next is a system call. 4 | 5 | To use it, simply run 6 | 7 | ```text 8 | gef➤ is-syscall 9 | ``` 10 | 11 | If it is a system call, 12 | 13 | ```text 14 | gef➤ is-syscall 15 | [+] Current instruction is a syscall 16 | ``` 17 | 18 | Check this asciicast for visual example: 19 | 20 | [![asciicast](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6.png)](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6) 21 | -------------------------------------------------------------------------------- /structs/tcache_entry.py: -------------------------------------------------------------------------------- 1 | 2 | import ctypes 3 | 4 | TCACHE_MAX_BINS = 0x40 5 | 6 | class tcache_perthread_struct_t(ctypes.Structure): 7 | _values_ = [] 8 | 9 | class tcache_entry_t(ctypes.Structure): 10 | _values_ = [] 11 | 12 | tcache_entry_t._fields_ = [ 13 | ("next", ctypes.POINTER(tcache_entry_t)), 14 | ("key", ctypes.POINTER(tcache_perthread_struct_t)), 15 | ] 16 | 17 | tcache_perthread_struct_t._fields_ = [ 18 | ("counts", TCACHE_MAX_BINS * ctypes.c_uint16), 19 | ("entries", TCACHE_MAX_BINS * ctypes.POINTER(tcache_entry_t)) 20 | ] 21 | -------------------------------------------------------------------------------- /.github/workflows/docs-link-check.yml: -------------------------------------------------------------------------------- 1 | name: Documents Validation 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | link_check: 10 | name: Link Checker 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v4 17 | - name: Check links 18 | uses: lycheeverse/lychee-action@v1.4.1 19 | env: 20 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 21 | with: 22 | args: --exclude-mail --accept=401 --no-progress 'docs/**/*.md' 23 | fail: false 24 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | fail_fast: false 2 | repos: 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.4.0 5 | hooks: 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | 9 | - repo: https://github.com/pycqa/pylint 10 | rev: v3.0.0a6 11 | hooks: 12 | - id: pylint 13 | 14 | - repo: https://github.com/igorshubovych/markdownlint-cli 15 | rev: v0.35.0 16 | hooks: 17 | - id: markdownlint-docker 18 | args: 19 | - --config=docs/.markdownlint.yaml 20 | - --ignore=README.md 21 | - --ignore=docs/index.md 22 | - "docs/**/*.md" 23 | -------------------------------------------------------------------------------- /docs/commands/gef-openocd-remote.md: -------------------------------------------------------------------------------- 1 | ## Command gef-openocd-remote 2 | 3 | The `gef-openocd-command` is used with the [`ARMOpenOCD`](../../archs/arm-openocd.py] architecture. 4 | 5 | The [arm-openocd.py](../../archs/arm-openocd.py) script adds an easy way to extend the `gef-remote` 6 | functionality to easily debug ARM targets using a OpenOCD gdbserver. It creates a custom ARM-derived 7 | `Architecture`, as well as the `gef-openocd-remote` command, which lets you easily connect to the 8 | target, optionally loading the accompanying ELF binary. 9 | 10 | ### Usage 11 | 12 | ```bash 13 | gef-openocd-remote localhost 3333 --file /path/to/elf 14 | ``` 15 | -------------------------------------------------------------------------------- /archs/arm-cortex-m.py: -------------------------------------------------------------------------------- 1 | """ 2 | ARM Cortex-M support for GEF 3 | 4 | To use, source this file *after* gef 5 | 6 | Original PR: https://github.com/hugsy/gef/pull/651 7 | Author: SWW13 8 | """ 9 | 10 | 11 | class ARM_M(ARM): 12 | arch = "ARM-M" 13 | aliases = ("ARM-M", Elf.Abi.ARM) 14 | 15 | all_registers = ARM.all_registers[:-1] + ("$xpsr",) 16 | flag_register = "$xpsr" 17 | flags_table = { 18 | 31: "negative", 19 | 30: "zero", 20 | 29: "carry", 21 | 28: "overflow", 22 | 24: "thumb", 23 | } 24 | 25 | @staticmethod 26 | def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: 27 | return bool(re.search("^armv.*-m$", gdb_arch)) 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Had an idea of a new useful feature for GEF, but can't implement it? Here's 4 | your chance 5 | title: '' 6 | labels: enhancement, new feature 7 | assignees: '' 8 | 9 | --- 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | A clear and concise description of what you want to happen. 16 | 17 | **Describe alternatives you've considered** 18 | A clear and concise description of any alternative solutions or features you've considered. 19 | 20 | **Additional context** 21 | Add any other context or screenshots about the feature request here. 22 | -------------------------------------------------------------------------------- /docs/commands/capstone-disassemble.md: -------------------------------------------------------------------------------- 1 | ## Command capstone-disassemble 2 | 3 | If you have installed the [`capstone`](http://capstone-engine.org) library and 4 | its Python bindings, you can use it to disassemble any memory in your debugging 5 | session. This plugin was created to offer an alternative to `GDB`'s disassemble 6 | function which sometimes gets things mixed up. 7 | 8 | You can use its alias `cs-disassemble` or just `cs` with the location to 9 | disassemble at. If not specified, it will use `$pc`. 10 | 11 | ```text 12 | gef➤ cs main+0x10 13 | ``` 14 | 15 | ![cs-disassemble](https://i.imgur.com/JG7aVRP.png) 16 | 17 | Disassemble more instructions 18 | 19 | ```text 20 | gef➤ cs --length 20 21 | ``` 22 | 23 | Show opcodes next to disassembly 24 | 25 | ```text 26 | gef➤ cs --show-opcodes 27 | ``` 28 | -------------------------------------------------------------------------------- /tests/binaries/set-permission.c: -------------------------------------------------------------------------------- 1 | /** 2 | * -*- mode: c -*- 3 | * -*- coding: utf-8 -*- 4 | * 5 | * set-permission.c 6 | * 7 | * @author: @_hugsy_ 8 | * @licence: WTFPL v.2 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "utils.h" 18 | 19 | int main(int argc, char** argv, char** envp) 20 | { 21 | void *p = mmap((void *)0x1337000, 22 | getpagesize(), 23 | PROT_READ|PROT_WRITE, 24 | MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, 25 | -1, 26 | 0); 27 | 28 | if( p == (void *)-1) 29 | return EXIT_FAILURE; 30 | 31 | DebugBreak(); 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /scripts/TEMPLATE: -------------------------------------------------------------------------------- 1 | """ 2 | Describe thoroughly what your command does. In addition, complete the documentation 3 | in /docs/ and adding the reference in /mkdocs.yml 4 | """ 5 | 6 | __AUTHOR__ = "your_name" 7 | __VERSION__ = 0.1 8 | __LICENSE__ = "MIT" 9 | 10 | from typing import TYPE_CHECKING, List 11 | 12 | if TYPE_CHECKING: 13 | from . import * # this will allow linting for GEF and GDB 14 | 15 | 16 | @register 17 | class MyCommand(GenericCommand): 18 | """Template of a new command.""" 19 | _cmdline_ = "my-command" 20 | _syntax_ = "{:s}".format(_cmdline_) 21 | 22 | def pre_load(self) -> None: 23 | super().pre_load() 24 | 25 | def __init__(self) -> None: 26 | super().__init__(complete=gdb.COMPLETE_NONE) 27 | 28 | def post_load(self) -> None: 29 | super().post_load() 30 | 31 | def do_invoke(self, argv: List[str]): 32 | return 33 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validation 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | pre_commit: 10 | name: Check formatting 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-python@v5.0.0 15 | with: 16 | python-version: "3.8" 17 | - uses: pre-commit/action@v3.0.0 18 | 19 | docs_link_check: 20 | name: Check URLs in docs 21 | runs-on: ubuntu-latest 22 | permissions: 23 | contents: read 24 | steps: 25 | - name: checkout 26 | uses: actions/checkout@v4 27 | - name: Check links 28 | uses: lycheeverse/lychee-action@v1.9.1 29 | env: 30 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 31 | with: 32 | args: --exclude-mail --accept=401 --no-progress 'docs/**/*.md' 33 | fail: false 34 | -------------------------------------------------------------------------------- /docs/commands/error.md: -------------------------------------------------------------------------------- 1 | ## Command `error` 2 | 3 | A basic equivalent to WinDbg `!error` command. 4 | 5 | If a debugging session is active, `error` can be used with no argument: the command will use the 6 | `return_register` of the current architecture associated to the binary. 7 | 8 | ```text 9 | [ Legend: Modified register | Code | Heap | Stack | String ] 10 | ─────────────────────────────────────────────────────────────────────────────────────── registers ──── 11 | $rax : 0x1 12 | [...] 13 | gef➤ error 14 | 1 (0x1) : Operation not permitted 15 | ``` 16 | 17 | Otherwise, an argument is expected: this argument can be a debugging symbol (for instance a 18 | register) or the integer holding the error code to translate: 19 | 20 | ```text 21 | gef➤ error 42 22 | 42 (0x2a) : No message of desired type 23 | ``` 24 | 25 | ```text 26 | gef➤ eq $sp 0x1337 27 | gef➤ error *(int*)$sp 28 | 4919 (0x1337) : Unknown error 4919 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/commands/ksymaddr.md: -------------------------------------------------------------------------------- 1 | ## Command `ksymaddr` 2 | 3 | `ksymaddr` helps locate a kernel symbol by its name. 4 | 5 | The syntax is straight forward: 6 | 7 | ```text 8 | ksymaddr 9 | ``` 10 | 11 | For example, 12 | 13 | ```text 14 | gef➤ ksymaddr commit_creds 15 | [+] Found matching symbol for 'commit_creds' at 0xffffffff8f495740 (type=T) 16 | [*] Found partial match for 'commit_creds' at 0xffffffff8f495740 (type=T): commit_creds 17 | [*] Found partial match for 'commit_creds' at 0xffffffff8fc71ee0 (type=R): __ksymtab_commit_creds 18 | [*] Found partial match for 'commit_creds' at 0xffffffff8fc8d008 (type=r): __kcrctab_commit_creds 19 | [*] Found partial match for 'commit_creds' at 0xffffffff8fc9bfcd (type=r): __kstrtab_commit_creds 20 | ``` 21 | 22 | Note that the debugging process needs to have the correct permissions for this command to show 23 | kernel addresses. For more information see also [this stackoverflow 24 | post](https://stackoverflow.com/a/55592796). 25 | -------------------------------------------------------------------------------- /.github/workflows/generate-docs.yml: -------------------------------------------------------------------------------- 1 | name: Generate GithubPages 2 | 3 | on: 4 | 5 | workflow_dispatch: 6 | 7 | push: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | if: github.event.repository.fork == false 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | - name: Install pre-requisite 20 | run: | 21 | sudo apt update && sudo apt install gdb-multiarch python3 python3-dev python3-wheel -y 22 | version=$(gdb -q -nx -ex 'pi print(f"{sys.version_info.major}.{sys.version_info.minor}", end="")' -ex quit) 23 | python${version} -m pip install --requirement docs/requirements.txt --upgrade 24 | - name: Build and publish the docs 25 | run: | 26 | git config --global user.name "hugsy" 27 | git config --global user.email "hugsy@users.noreply.github.com" 28 | mkdocs gh-deploy --force 29 | -------------------------------------------------------------------------------- /structs/socketaddr_in_t.py: -------------------------------------------------------------------------------- 1 | import ctypes as ct 2 | import socket 3 | import struct 4 | 5 | 6 | class socketaddr_in_t(ct.Structure): 7 | _fields_ = [ 8 | ("sin_family", ct.c_short), 9 | ("sin_port", ct.c_ushort), 10 | ("sin_addr.s_addr", ct.c_uint32), 11 | ] 12 | 13 | _values_ = [ 14 | ( 15 | "sin_family", 16 | [ 17 | (0, "AF_UNSPEC"), 18 | (1, "AF_UNIX"), 19 | (2, "AF_INET"), 20 | (3, "AF_AX25"), 21 | (4, "AF_IPX"), 22 | (5, "AF_APPLETALK"), 23 | (6, "AF_NETROM"), 24 | (7, "AF_BRIDGE"), 25 | (8, "AF_AAL5"), 26 | (9, "AF_X25"), 27 | (10, "AF_INET6"), 28 | (12, "AF_MAX"), 29 | ], 30 | ), 31 | ("sin_port", lambda p: socket.ntohs(p)), 32 | ("sin_addr.s_addr", lambda addr: socket.inet_ntoa(struct.pack(" None: 21 | self._target = debug_target("visualize_heap") 22 | return super().setUp() 23 | 24 | 25 | def test_cmd_got_audit(self): 26 | gdb = self._gdb 27 | 28 | self.assertEqual(ERROR_INACTIVE_SESSION_MESSAGE,gdb.execute("got-audit", to_string=True)) 29 | 30 | gdb.execute("run") 31 | res = gdb.execute("got-audit", to_string=True) 32 | self.assertIn("malloc", res) 33 | self.assertIn("puts", res) 34 | self.assertIn("/libc", res) 35 | 36 | res = gdb.execute("got-audit malloc", to_string=True) 37 | self.assertIn("malloc", res) 38 | self.assertNotIn("puts", res) 39 | -------------------------------------------------------------------------------- /tests/commands/visualize_heap.py: -------------------------------------------------------------------------------- 1 | """ 2 | `visualize-libc-heap-chunks` command test module 3 | """ 4 | 5 | 6 | import pytest 7 | from tests.base import RemoteGefUnitTestGeneric 8 | 9 | from tests.utils import ( 10 | ARCH, 11 | ERROR_INACTIVE_SESSION_MESSAGE, 12 | debug_target, 13 | ) 14 | 15 | 16 | class VisualizeLibcHeapChunksCommand(RemoteGefUnitTestGeneric): 17 | """`visualize-libc-heap-chunks` command test module""" 18 | 19 | def setUp(self) -> None: 20 | self._target = debug_target("visualize_heap") 21 | return super().setUp() 22 | 23 | @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") 24 | def test_cmd_heap_view(self): 25 | gdb = self._gdb 26 | cmd = "visualize-libc-heap-chunks" 27 | self.assertEqual( 28 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 29 | ) 30 | 31 | gdb.execute("run") 32 | res = gdb.execute(f"{cmd}", to_string=True) or "" 33 | assert res 34 | 35 | for i in range(4): 36 | self.assertIn(f"0x0000000000000000 ........ Chunk[{i}]", res) 37 | -------------------------------------------------------------------------------- /tests/commands/ropper.py: -------------------------------------------------------------------------------- 1 | """ 2 | `ropper` command test module 3 | """ 4 | 5 | 6 | import pytest 7 | from tests.base import RemoteGefUnitTestGeneric 8 | 9 | from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE 10 | 11 | 12 | class RopperCommand(RemoteGefUnitTestGeneric): 13 | """`ropper` command test module""" 14 | 15 | def setUp(self) -> None: 16 | try: 17 | import ropper # pylint: disable=W0611 18 | except ImportError: 19 | pytest.skip("ropper not available", allow_module_level=True) 20 | return super().setUp() 21 | 22 | @pytest.mark.skipif(ARCH not in ["x86_64", "i686"], reason=f"Skipped for {ARCH}") 23 | def test_cmd_ropper(self): 24 | gdb = self._gdb 25 | cmd = "ropper" 26 | self.assertEqual( 27 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 28 | ) 29 | 30 | gdb.execute("start") 31 | cmd = 'ropper --search "pop %; pop %; ret"' 32 | res = gdb.execute(cmd, to_string=True) or "" 33 | assert res 34 | self.assertNotIn(": error:", res) 35 | self.assertTrue(len(res.splitlines()) > 2) 36 | -------------------------------------------------------------------------------- /tests/binaries/syscall-args.c: -------------------------------------------------------------------------------- 1 | /** 2 | * syscall-args.c 3 | * -*- mode: c -*- 4 | * -*- coding: utf-8 -*- 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "utils.h" 16 | 17 | #define __NR_read 0 18 | 19 | void openfile() 20 | { 21 | int ret; 22 | size_t size = 256; 23 | char buf[256] = {0}; 24 | int fd = openat(AT_FDCWD, "/etc/passwd", O_RDONLY); 25 | if(fd != -1){ 26 | close(fd); 27 | #if defined(__x86_64__) || defined(__amd64__) || defined(__i386) || defined(i386) || defined(__i386__) 28 | __asm__ volatile 29 | ( 30 | #if defined(__i386) || defined(i386) || defined(__i386__) 31 | "int $0x80" 32 | #else 33 | "syscall" 34 | #endif 35 | : "=a" (ret) 36 | : "0"(__NR_read), "b"(fd), "c"(buf), "d"(size) 37 | : "memory" 38 | ); 39 | #else 40 | DebugBreak(); 41 | #endif 42 | } 43 | } 44 | 45 | 46 | int main(int argc, char** argv, char** envp) 47 | { 48 | openfile(); 49 | return EXIT_SUCCESS; 50 | } 51 | -------------------------------------------------------------------------------- /structs/malloc_arena_t.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | # 4 | # 2.31 -> https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L1655 5 | # 2.34 -> https://elixir.bootlin.com/glibc/glibc-2.34/source/malloc/malloc.c#L1831 6 | # 7 | 8 | NFASTBINS = 10 9 | NBINS = 254 10 | BINMAPSIZE = 0x10 11 | 12 | 13 | def malloc_state64_t(gef = None): 14 | pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32 15 | 16 | fields = [ 17 | ("mutex", ctypes.c_uint32), 18 | ("have_fastchunks", ctypes.c_uint32), 19 | ] 20 | 21 | if gef and gef.libc.version and gef.libc.version >= (2, 27): 22 | fields += [("fastbinsY", NFASTBINS * pointer), ] 23 | 24 | fields += [ 25 | ("top", pointer), 26 | ("last_remainder", pointer), 27 | ("bins", NBINS * pointer), 28 | ("binmap", BINMAPSIZE * ctypes.c_uint32), 29 | ("next", pointer), 30 | ("next_free", pointer), 31 | ("attached_threads", pointer), 32 | ("system_mem", pointer), 33 | ("max_system_mem", pointer), 34 | ] 35 | 36 | class malloc_state64_cls(ctypes.Structure): 37 | _fields_ = fields 38 | 39 | return malloc_state64_cls 40 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Description/Motivation/Screenshots 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ## How Has This Been Tested ? 11 | 12 | "Tested" indicates that the PR works *and* the unit test (i.e. `make test`) run passes without issue. 13 | 14 | * [ ] x86-32 15 | * [ ] x86-64 16 | * [ ] ARM 17 | * [ ] AARCH64 18 | * [ ] MIPS 19 | * [ ] POWERPC 20 | * [ ] SPARC 21 | * [ ] RISC-V 22 | 23 | ## Checklist 24 | 25 | 26 | 27 | * [ ] My code follows the code style of this project. 28 | * [ ] My change includes a change to the documentation, if required. 29 | * [ ] If my change adds new code, 30 | [adequate tests](https://hugsy.github.io/gef/testing) have been added. 31 | * [ ] I have read and agree to the 32 | [CONTRIBUTING](https://github.com/hugsy/gef/blob/main/.github/CONTRIBUTING.md) document. 33 | -------------------------------------------------------------------------------- /scripts/error/__init__.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "hugsy" 2 | __VERSION__ = 0.1 3 | 4 | import ctypes 5 | from typing import TYPE_CHECKING 6 | 7 | import gdb 8 | 9 | if TYPE_CHECKING: 10 | from .. import * 11 | from .. import gdb 12 | 13 | 14 | @register 15 | class ErrorCommand(GenericCommand): 16 | """WinDbg `!error` -like command""" 17 | 18 | _cmdline_ = "error" 19 | _syntax_ = f"{_cmdline_:s}" 20 | _aliases_ = ["perror", ] 21 | _example_ = f"{_cmdline_:s}" 22 | 23 | def __init__(self): 24 | super().__init__(complete=gdb.COMPLETE_LOCATION) 25 | return 26 | 27 | def do_invoke(self, argv): 28 | argc = len(argv) 29 | if argc == 0 and is_alive(): 30 | value = gef.arch.register(gef.arch.return_register) 31 | elif argv[0].isdigit(): 32 | value = int(argv[0]) 33 | else: 34 | value = parse_address(argv[0]) 35 | 36 | __libc = ctypes.CDLL("libc.so.6") 37 | __libc.strerror.restype = ctypes.c_char_p 38 | __libc.strerror.argtypes = [ctypes.c_int32, ] 39 | c_s = __libc.strerror(value).decode("utf8") 40 | info(f"{value:d} ({value:#x}) : {Color.greenify(c_s):s}") 41 | return 42 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 45 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. You can 15 | reopen it by adding a comment to this issue. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: > 18 | This issue has been automatically closed because it has not had recent activity. 19 | If you are the owner of this issue, you can either re-open it and provide a more 20 | complete description; or create a new issue. 21 | Thank you for your contributions. 22 | # Set to true to ignore issues in a milestone (defaults to false) 23 | exemptMilestones: true 24 | # Set to true to ignore issues with an assignee (defaults to false) 25 | exemptAssignees: true 26 | -------------------------------------------------------------------------------- /docs/commands/glibc_function_args.md: -------------------------------------------------------------------------------- 1 | ## Glibc function call arguments definition 2 | 3 | This directory holds json used to print better definition of glibc function arguments. 4 | Arguments' definitions are taken from glibc manual, and can be used as a kind reminder. 5 | 6 | For example, the arguments for a `read@plt` would currently look like this: 7 | 8 | ![read-plt-without](https://user-images.githubusercontent.com/1745802/98736103-aed90900-23a4-11eb-8c8d-f1ae41e772f8.png) 9 | 10 | But using this feature, it will instead look like this: 11 | 12 | ![read-plt-with](https://user-images.githubusercontent.com/1745802/98736838-a7662f80-23a5-11eb-89b4-7f732713d64b.png) 13 | 14 | Functions are detected if they end with `@plt`, which means that static binaries won't benefit from 15 | this. 16 | 17 | User has to set two context configurations: 18 | 19 | * `context.libc_args`: boolean, set to `True` to use this feature 20 | * `context.libc_args_path`: string, must be set to the directory where the libc definition json 21 | files can be found 22 | 23 | The script `generate_glibc_args_json.py` is used to create provided json files. It works by parsing 24 | glibc manual text, can be downloaded from 25 | and saved in the current directory. 26 | -------------------------------------------------------------------------------- /docs/commands/peekpointers.md: -------------------------------------------------------------------------------- 1 | ## Command `peekpointers` 2 | 3 | _Author_: [bkth](https://github.com/bkth) 4 | 5 | This command helps find pointers belonging to other memory regions 6 | helpful in case of OOB Read when looking for specific pointers 7 | 8 | Syntax: 9 | 10 | ```python 11 | gef➤ peek-pointers LOCATION [section-name] 12 | ``` 13 | 14 | Examples: 15 | 16 | ```python 17 | gef➤ peek-pointers 0x55555575c000 18 | cat pointer at 0x55555575c008, value 0x55555575c008 19 | [stack] pointer at 0x55555575c0c0, value 0x7fffffffe497 20 | libc-2.24.so pointer at 0x55555575c0c8, value 0x7ffff7dd2600 <_IO_2_1_stdout_> 21 | [heap] pointer at 0x55555575d038, value 0x55555575d010 22 | locale-archive pointer at 0x55555575d0b8, value 0x7ffff774e5c0 23 | Could not read from address 0x55555577e000, stopping. 24 | ``` 25 | 26 | ```python 27 | gef➤ peek-pointers 0x55555575c000 libc-2.24.so 28 | libc-2.24.so pointer at 0x55555575c0c8, value 0x7ffff7dd2600 <_IO_2_1_stdout_> 29 | gef➤ peek-pointers 0x55555575c000 libc-2.24.so all 30 | libc-2.24.so pointer at 0x55555575c0c8, value 0x7ffff7dd2600 <_IO_2_1_stdout_> 31 | libc-2.24.so pointer at 0x55555575c0e0, value 0x7ffff7dd2520 <_IO_2_1_stderr_> 32 | libc-2.24.so pointer at 0x55555575dfe8, value 0x7ffff7ba1b40 <_nl_default_dirname> 33 | Could not read from address 0x55555577e000, stopping. 34 | ``` 35 | -------------------------------------------------------------------------------- /tests/binaries/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | DEBUG = 1 3 | CFLAGS += -Wall 4 | SOURCES = $(wildcard *.c) 5 | LINKED = $(SOURCES:.c=.out) 6 | LDFLAGS = 7 | EXTRA_FLAGS = 8 | TMPDIR ?= /tmp 9 | 10 | ifeq ($(TARGET), i686) 11 | CFLAGS += -m32 12 | endif 13 | 14 | ifeq ($(DEBUG), 1) 15 | CFLAGS += -DDEBUG=1 -ggdb -O0 16 | else 17 | CFLAGS += -O1 18 | endif 19 | 20 | 21 | .PHONY : all clean 22 | 23 | all: $(LINKED) 24 | 25 | 26 | %.out : %.c 27 | @echo "[+] Building '$(TMPDIR)/$@'" 28 | @$(CC) $(CFLAGS) $(EXTRA_FLAGS) -o $(TMPDIR)/$@ $? $(LDFLAGS) 29 | 30 | clean : 31 | @echo "[+] Cleaning stuff" 32 | @cd $(TMPDIR) && rm -f $(LINKED) 33 | 34 | format-string-helper.out: EXTRA_FLAGS := -Wno-format-security 35 | 36 | checksec-no-canary.out: EXTRA_FLAGS := -fno-stack-protector 37 | 38 | # NOTE: If compiling with a older GCC (older than 4.8.4 maybe?) then use `-fno-pie` 39 | checksec-no-pie.out: EXTRA_FLAGS := -no-pie 40 | 41 | checksec-no-nx.out: EXTRA_FLAGS := -z execstack 42 | 43 | pattern.out: EXTRA_FLAGS := -D_FORTIFY_SOURCE=0 -fno-stack-protector 44 | 45 | canary.out: EXTRA_FLAGS := -fstack-protector-all 46 | 47 | heap-non-main.out heap-tcache.out heap-multiple-heaps.out: EXTRA_FLAGS := -pthread 48 | 49 | heap-bins.out: EXTRA_FLAGS := -Wno-unused-result 50 | 51 | default.out: EXTRA_FLAGS := -fstack-protector-all -fpie -pie 52 | -------------------------------------------------------------------------------- /docs/commands/unicorn-emulate.md: -------------------------------------------------------------------------------- 1 | ## Command unicorn-emulate 2 | 3 | If you have installed [`unicorn`](http://unicorn-engine.org) emulation engine 4 | and its Python bindings, GEF integrates a new command to emulate instructions 5 | of your current debugging context ! 6 | 7 | This `unicorn-emulate` command (or its alias `emu`) will replicate the current 8 | memory mapping (including the page permissions) for you, and by default (i.e. 9 | without any additional argument), it will emulate the execution of the 10 | instruction about to be executed (i.e. the one pointed by `$pc`). Furthermore 11 | the command will print out the state of the registers before and after the 12 | emulation. 13 | 14 | Use `-h` for help: 15 | 16 | ```text 17 | gef➤ emu -h 18 | ``` 19 | 20 | For example, the following command will emulate only the next 2 instructions: 21 | 22 | ```text 23 | gef➤ emu 2 24 | ``` 25 | 26 | And show this: 27 | 28 | ![emu](https://i.imgur.com/n4Oy5D0.png) 29 | 30 | In this example, we can see that after executing 31 | 32 | ```text 33 | 0x555555555171 sub rsp, 0x10 34 | 0x555555555175 mov edi, 0x100 35 | ``` 36 | 37 | The registers `rsp` and `rdi` are tainted (modified). 38 | 39 | A convenient option is `--output-file /path/to/file.py` that will generate a 40 | pure Python script embedding your current execution context, ready to be re-used 41 | outside GEF!! This can be useful for dealing with obfuscation or solve crackmes 42 | if powered with a SMT for instance. 43 | -------------------------------------------------------------------------------- /tests/binaries/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /** 4 | * Provide an cross-architecture way to break into the debugger. 5 | * On some architectures, we resort to `raise(SIGINT)` which is not 6 | * optimal, as it adds an extra frame to the stack. 7 | */ 8 | 9 | /* Intel x64 (x86_64) */ 10 | #if defined(__x86_64__) || defined(__amd64__) 11 | #define DebugBreak() __asm__("int $3") 12 | 13 | /* Intel x32 (i686) */ 14 | #elif defined(__i386) || defined(i386) || defined(__i386__) 15 | #define DebugBreak() __asm__("int $3") 16 | 17 | /* AARCH64 (aarch64) */ 18 | #elif defined(__aarch64__) 19 | #define DebugBreak() { raise( SIGINT ) ; } 20 | 21 | /* ARM (armv7le*/ 22 | #elif defined(__arm__) || defined(__arm) 23 | #define DebugBreak() { raise( SIGINT ) ; } 24 | 25 | /* MIPS */ 26 | /* MIPS64 (mips64el) */ 27 | #elif defined(mips) || defined(__mips__) || defined(__mips) 28 | #define DebugBreak() { raise( SIGINT ) ; } 29 | 30 | /* PowerPC */ 31 | /* PowerPC64 (ppc64le) */ 32 | #elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__POWERPC__) || defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC) 33 | #define DebugBreak() { raise( SIGINT ) ; } 34 | 35 | /* SPARC */ 36 | /* SPARC64 */ 37 | // #elif defined(__sparc) || defined(__sparc64__) || defined(__sparc__) 38 | // #define DebugBreak() { raise( SIGINT ) ; } 39 | 40 | /* RISC V */ 41 | #elif defined(__riscv) 42 | #define DebugBreak() { raise( SIGINT ) ; } 43 | 44 | /* the rest */ 45 | #else 46 | #error "Unsupported architecture" 47 | // #define DebugBreak() __builtin_trap() 48 | #endif 49 | -------------------------------------------------------------------------------- /structs/elf32_t.py: -------------------------------------------------------------------------------- 1 | # 2 | # ELF (32b) parsing from http://www.skyfree.org/linux/references/ELF_Format.pdf 3 | # 4 | # @_hugsy_ 5 | # 6 | 7 | import ctypes as ct 8 | 9 | 10 | class elf32_t(ct.Structure): 11 | 12 | _fields_ = [ 13 | ("ei_magic", ct.c_char * 4), 14 | ("ei_class", ct.c_uint8), 15 | ("ei_data", ct.c_uint8), 16 | ("ei_version", ct.c_uint8), 17 | ("ei_padd", ct.c_char * 9), 18 | ("e_type", ct.c_uint16), 19 | ("e_machine", ct.c_uint16), 20 | ("e_version", ct.c_uint32), 21 | ("e_entry", ct.c_uint32), 22 | ] 23 | 24 | _values_ = [ 25 | ( 26 | "ei_magic", 27 | [ 28 | (b"\x7fELF", "Correct ELF header"), 29 | (None, "Incorrect ELF header"), # None -> default case 30 | ], 31 | ), 32 | ( 33 | "e_type", 34 | [ 35 | (0, "ET_NONE"), 36 | (1, "ET_REL"), 37 | (2, "ET_EXEC"), 38 | (3, "ET_DYN"), 39 | (None, "Unknown type"), 40 | ], 41 | ), 42 | ( 43 | "e_machine", 44 | [ 45 | (0, "EM_NONE"), 46 | (1, "EM_M32"), 47 | (2, "EM_SPARC"), 48 | (3, "EM_386"), 49 | (4, "EM_68K"), 50 | (5, "EM_88K"), 51 | (7, "EM_860"), 52 | (8, "EM_MIPS"), 53 | (40, "EM_ARM"), 54 | (21, "EM_ALPHA"), 55 | (None, "Unknown machine"), 56 | ], 57 | ), 58 | ] 59 | -------------------------------------------------------------------------------- /docs/commands/windbg.md: -------------------------------------------------------------------------------- 1 | ## WinDbg compatibility layer 2 | 3 | This plugin is a set of commands, aliases and extensions to mimic some of the most common WinDbg 4 | commands into GEF. 5 | 6 | ### Commands 7 | 8 | - `hh` - open GEF help in web browser 9 | - `sxe` (set-exception-enable): break on loading libraries 10 | - `tc` - trace to next call 11 | - `pc` - run until call. 12 | - `g` - go. 13 | - `u` - disassemble. 14 | - `x` - search symbol. 15 | - `r` - register info 16 | 17 | 18 | ### Settings 19 | 20 | - `gef.use-windbg-prompt` - set to `True` to change the prompt like `0:000 ➤` 21 | 22 | 23 | ### Aliases 24 | 25 | Loading this plugin will automatically define the following aliases 26 | 27 | | Alias | Command | 28 | | ----- | --------------------- | 29 | | `da` | `display s` | 30 | | `dt` | `pcustom` | 31 | | `dq` | `hexdump qword` | 32 | | `dd` | `hexdump dword` | 33 | | `dw` | `hexdump word` | 34 | | `db` | `hexdump byte` | 35 | | `eq` | `patch qword` | 36 | | `ed` | `patch dword` | 37 | | `ew` | `patch word` | 38 | | `eb` | `patch byte` | 39 | | `ea` | `patch string` | 40 | | `dps` | `dereference` | 41 | | `bp` | `break` | 42 | | `bl` | `info breakpoints` | 43 | | `bd` | `disable breakpoints` | 44 | | `bc` | `delete breakpoints` | 45 | | `be` | `enable breakpoints` | 46 | | `tbp` | `tbreak` | 47 | | `s` | `grep` | 48 | | `pa` | `advance` | 49 | | `kp` | `info stack` | 50 | | `ptc` | `finish` | 51 | | `uf` | `disassemble` | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | .vscode 92 | .benchmarks 93 | .pytest_cache 94 | 95 | scripts/libc_functions_args/tables/libc.txt.gz 96 | scripts/libc_function_args/tables/x86_64.json 97 | scripts/libc_function_args/tables/x86_32.json 98 | -------------------------------------------------------------------------------- /scripts/ropper.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import ropper 3 | import gdb 4 | import sys 5 | __AUTHOR__ = "hugsy" 6 | __VERSION__ = 0.3 7 | __NAME__ = "ropper" 8 | 9 | 10 | @register 11 | class RopperCommand(GenericCommand): 12 | """Ropper (https://scoding.de/ropper/) plugin.""" 13 | 14 | _cmdline_ = "ropper" 15 | _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" 16 | 17 | def __init__(self) -> None: 18 | super().__init__(complete=gdb.COMPLETE_NONE) 19 | self.__readline = None 20 | return 21 | 22 | @only_if_gdb_running 23 | def do_invoke(self, argv: List[str]) -> None: 24 | if not self.__readline: 25 | self.__readline = __import__("readline") 26 | ropper = sys.modules["ropper"] 27 | if "--file" not in argv: 28 | path = gef.session.file 29 | if not path: 30 | err("No file provided") 31 | return 32 | path = str(path) 33 | sect = next(filter(lambda x: x.path == path, gef.memory.maps)) 34 | argv.append("--file") 35 | argv.append(path) 36 | argv.append("-I") 37 | argv.append(f"{sect.page_start:#x}") 38 | 39 | # ropper set up own autocompleter after which gdb/gef autocomplete don't work 40 | old_completer_delims = self.__readline.get_completer_delims() 41 | old_completer = self.__readline.get_completer() 42 | 43 | try: 44 | ropper.start(argv) 45 | except RuntimeWarning: 46 | return 47 | 48 | self.__readline.set_completer(old_completer) 49 | self.__readline.set_completer_delims(old_completer_delims) 50 | return 51 | -------------------------------------------------------------------------------- /docs/commands/set-permission.md: -------------------------------------------------------------------------------- 1 | ## Command set-permission 2 | 3 | This command was added to facilitate the exploitation process, by changing the 4 | permissions on a specific memory page directly from the debugger. 5 | 6 | By default, GDB does not allow you to do that, so the command will modify a 7 | code section of the binary being debugged, and add a native `mprotect` syscall 8 | stub. For example, for x86, the following stub will be inserted: 9 | 10 | ```text 11 | pushad 12 | pushfd 13 | mov eax, mprotect_syscall_num 14 | mov ebx, address_of_the_page 15 | mov ecx, size_of_the_page 16 | mov edx, permission_to_set 17 | int 0x80 18 | popfd 19 | popad 20 | ``` 21 | 22 | A breakpoint is added following this stub, which when hit will restore the 23 | original context, allowing you to resume execution. 24 | 25 | The usage is 26 | 27 | ```text 28 | gef➤ set-permission address [permission] 29 | ``` 30 | 31 | The `permission` can be set using a bitmask as integer with read (1), write (2) 32 | and execute (4). For combinations of these permissions they can just be added: 33 | Read and Execute permission would be 1 + 4 = 5. 34 | 35 | `mprotect` is an alias for `set-permission`. As an example, to set the `stack` 36 | as READ|WRITE|EXECUTE on this binary, 37 | 38 | ![mprotect-before](https://i.imgur.com/RRYHxzW.png) 39 | 40 | Simply run 41 | 42 | ```text 43 | gef➤ mprotect 0xfffdd000 44 | ``` 45 | 46 | Et voilà! GEF will use the memory runtime information to correctly adjust the 47 | permissions of the entire section. 48 | 49 | ![mprotect-after](https://i.imgur.com/9MvyQi8.png) 50 | 51 | Or for a full demo video on an AARCH64 VM: 52 | 53 | [![set-permission-aarch64](https://img.youtube.com/vi/QqmfxIGzbmM/0.jpg)](https://www.youtube.com/watch?v=QqmfxIGzbmM) 54 | -------------------------------------------------------------------------------- /os/macho.py: -------------------------------------------------------------------------------- 1 | """ 2 | MachO compatibility layer 3 | """ 4 | 5 | @lru_cache() 6 | def inferior_is_macho(): 7 | """Return True if the current file is a Mach-O binary.""" 8 | for x in gdb.execute("info files", to_string=True).splitlines(): 9 | if "file type mach-o" in x: 10 | return True 11 | return False 12 | 13 | 14 | @lru_cache() 15 | def is_macho(filename): 16 | """Return True if the specified file is a Mach-O binary.""" 17 | file_bin = gef.session.constants["file"] 18 | cmd = [file_bin, filename] 19 | out = gef_execute_external(cmd) 20 | if "Mach-O" in out: 21 | return True 22 | return False 23 | 24 | 25 | def get_mach_regions(): 26 | sp = gef.arch.sp 27 | for line in gdb.execute("info mach-regions", to_string=True).splitlines(): 28 | line = line.strip() 29 | addr, perm, _ = line.split(" ", 2) 30 | addr_start, addr_end = [int(x, 16) for x in addr.split("-")] 31 | perm = Permission.from_process_maps(perm.split("/")[0]) 32 | 33 | zone = file_lookup_address(addr_start) 34 | if zone: 35 | path = zone.filename 36 | else: 37 | path = "[stack]" if sp >= addr_start and sp < addr_end else "" 38 | 39 | yield Section(page_start=addr_start, 40 | page_end=addr_end, 41 | offset=0, 42 | permission=perm, 43 | inode=None, 44 | path=path) 45 | return 46 | 47 | 48 | def get_process_maps(): 49 | return list(get_mach_regions()) 50 | 51 | 52 | def checksec(filename): 53 | return { 54 | "Canary": False, 55 | "NX": False, 56 | "PIE": False, 57 | "Fortify": False, 58 | "Partial RelRO": False, 59 | } 60 | -------------------------------------------------------------------------------- /docs/commands/gef-bmp-remote.md: -------------------------------------------------------------------------------- 1 | ## Command gef-bmp-remote 2 | 3 | The `gef-bmp-command` is used with the [`ARMBlackMagicProbe`](../../archs/arm-blackmagicprobe.py] 4 | architecture. 5 | 6 | The [Black Magic Probe](https://black-magic.org/) is a JTAG/SWD debugger that handles communicating 7 | with your device and exposes a _gdbserver_ for GDB to connect to. This allows you to connect to it 8 | via GDB with the `target extended-remote` command. However, because this is exposed via a tty, GEF 9 | cannot handle it with its `gef-remote` command (which assumes a _host:port_ connection). The 10 | [arm-blackmagicprobe.py](../../archs/arm-blackmagicprobe.py) script offers a way around this. It 11 | creates a custom ARM-derived `Architecture`, as well as the `gef-bmp-remote` command, which lets you 12 | scan for devices, power the target, and ultimately connect to the target device. 13 | 14 | ### Scan for devices 15 | 16 | ```bash 17 | gef➤ gef-bmp-remote --scan /dev/ttyUSB1" 18 | [=] [remote] Executing 'monitor swdp_scan' 19 | Target voltage: 3.3V 20 | Available Targets: 21 | No. Att Driver 22 | 1 Raspberry RP2040 M0+ 23 | 2 Raspberry RP2040 M0+ 24 | 3 Raspberry RP2040 Rescue (Attach to reset!) 25 | ``` 26 | 27 | This will connect to the BMP and use its scan feature to find valid targets connected. They will be 28 | numbered. Use the appropriate number to later `--attach`. 29 | 30 | If you are powering the device through the BMP, then make sure to add the `--power` arguments, 31 | otherwise the target may not be powered up when you attempt the scan. 32 | 33 | If you want to keep power between scanning and attaching, then use `--keep-power`. 34 | 35 | ```bash 36 | gef➤ gef-bmp-remote --file /path/to/binary.elf --attach 1 /dev/ttyUSB1", 37 | gef➤ gef-bmp-remote --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1", 38 | ``` 39 | -------------------------------------------------------------------------------- /tests/commands/unicorn_emulate.py: -------------------------------------------------------------------------------- 1 | """ 2 | unicorn-emulate command test module 3 | """ 4 | 5 | 6 | import pytest 7 | from tests.base import RemoteGefUnitTestGeneric 8 | 9 | from tests.utils import ARCH, ERROR_INACTIVE_SESSION_MESSAGE, debug_target 10 | 11 | 12 | @pytest.mark.skipif( 13 | ARCH not in ("i686", "x86_64", "armv7l", "aarch64"), reason=f"Skipped for {ARCH}" 14 | ) 15 | class UnicornEmulateCommand(RemoteGefUnitTestGeneric): 16 | """`unicorn-emulate` command test module""" 17 | 18 | def setUp(self) -> None: 19 | try: 20 | import unicorn # pylint: disable=W0611 21 | except ImportError: 22 | pytest.skip("unicorn-engine not available", allow_module_level=True) 23 | 24 | self._target = debug_target("unicorn") 25 | return super().setUp() 26 | 27 | @pytest.mark.skipif(ARCH not in ["x86_64"], reason=f"Skipped for {ARCH}") 28 | def test_cmd_unicorn_emulate(self): 29 | gdb = self._gdb 30 | nb_insn = 4 31 | 32 | cmd = f"emu {nb_insn}" 33 | self.assertEqual( 34 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 35 | ) 36 | 37 | res = gdb.execute(cmd, to_string=True) or "" 38 | assert res 39 | 40 | gdb.execute("break function1") 41 | gdb.execute("run") 42 | 43 | start_marker = "= Starting emulation =" 44 | end_marker = "Final registers" 45 | res = gdb.execute(cmd, to_string=True) or "" 46 | assert res 47 | 48 | self.assertNotIn("Emulation failed", res) 49 | self.assertIn(start_marker, res) 50 | self.assertIn(end_marker, res) 51 | 52 | insn_executed = len( 53 | res[res.find(start_marker) : res.find(end_marker)].splitlines()[1:-1] 54 | ) 55 | self.assertGreaterEqual(insn_executed, nb_insn) 56 | -------------------------------------------------------------------------------- /docs/commands/ida-rpyc.md: -------------------------------------------------------------------------------- 1 | ## Command ida-interact 2 | 3 | `gef` provides a simple XML-RPC client designed to communicate with a server 4 | running inside a specific IDA Python plugin, called `ida_gef.py`. 5 | 6 | Simply download this script, and run it inside IDA. When the server is running, 7 | you should see some output: 8 | 9 | ```text 10 | [+] Creating new thread for XMLRPC server: Thread-1 11 | [+] Starting XMLRPC server: 0.0.0.0:1337 12 | [+] Registered 12 functions. 13 | ``` 14 | 15 | This indicates that IDA is ready to work with `gef`! 16 | 17 | `gef` can interact with it via the command `ida-interact` (alias `ida`). This 18 | command expects the name of the function to execute as the first argument, all the 19 | other arguments are the arguments of the remote function. 20 | 21 | To enumerate the functions available, simply run 22 | 23 | ```text 24 | gef➤ ida-interact -h 25 | ``` 26 | 27 | ![gef-ida-help](https://i.imgur.com/JFNBfjY.png) 28 | 29 | Now, to execute an RPC, invoke the command `ida-interact` on the desired method, 30 | with its arguments (if required). 31 | 32 | For example: 33 | 34 | ```text 35 | gef➤ ida setcolor 0x40061E 36 | ``` 37 | 38 | will edit the remote IDB and set the background color of the location 0x40061E 39 | with the color 0x005500 (default value). 40 | 41 | Another convenient example is to add comment inside IDA directly from `gef`: 42 | 43 | ```text 44 | gef➤ ida makecomm 0x40060C "<<<--- stack overflow" 45 | [+] Success 46 | ``` 47 | 48 | Result: 49 | 50 | ![gef-ida-example](https://i.imgur.com/jZ2eWG4.png) 51 | 52 | Please use the `-h` argument to see all the methods available and their syntax. 53 | 54 | It is also note-worthy that [Binary Ninja](https://binary.ninja) support has be added: 55 | ![gef-binja-add-bkp](https://pbs.twimg.com/media/CzSso9bUAAArL1f.jpg:large), by using the 56 | Binary Ninja plugin [`gef-binja.py`](https://github.com/hugsy/gef-binja). 57 | -------------------------------------------------------------------------------- /scripts/kernel/symbols.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collection of functions and commands to manipulate kernel symbols 3 | """ 4 | 5 | __AUTHOR__ = "hugsy" 6 | __VERSION__ = 0.1 7 | __LICENSE__ = "MIT" 8 | 9 | import argparse 10 | from typing import TYPE_CHECKING, Any, List 11 | 12 | if TYPE_CHECKING: 13 | from .. import * # this will allow linting for GEF and GDB 14 | 15 | 16 | @register 17 | class SolveKernelSymbolCommand(GenericCommand): 18 | """Solve kernel symbols from kallsyms table.""" 19 | 20 | _cmdline_ = "ksymaddr" 21 | _syntax_ = f"{_cmdline_} SymbolToSearch" 22 | _example_ = f"{_cmdline_} prepare_creds" 23 | 24 | @parse_arguments({"symbol": ""}, {}) 25 | def do_invoke(self, _: List[str], **kwargs: Any) -> None: 26 | def hex_to_int(num): 27 | try: 28 | return int(num, 16) 29 | except ValueError: 30 | return 0 31 | 32 | args: argparse.Namespace = kwargs["arguments"] 33 | if not args.symbol: 34 | self.usage() 35 | return 36 | sym = args.symbol 37 | with open("/proc/kallsyms", "r") as f: 38 | syms = [line.strip().split(" ", 2) for line in f] 39 | matches = [ 40 | (hex_to_int(addr), sym_t, " ".join(name.split())) 41 | for addr, sym_t, name in syms 42 | if sym in name 43 | ] 44 | for addr, sym_t, name in matches: 45 | if sym == name.split()[0]: 46 | ok(f"Found matching symbol for '{name}' at {addr:#x} (type={sym_t})") 47 | else: 48 | warn( 49 | f"Found partial match for '{sym}' at {addr:#x} (type={sym_t}): {name}" 50 | ) 51 | if not matches: 52 | err(f"No match for '{sym}'") 53 | elif matches[0][0] == 0: 54 | err( 55 | "Check that you have the correct permissions to view kernel symbol addresses" 56 | ) 57 | return 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Help us improve GEF by filling up this report correctly 4 | title: '' 5 | labels: triage 6 | assignees: '' 7 | 8 | --- 9 | ## Bug Report 10 | 11 | 13 | 14 | * [ ] Is your bug specific to GEF (not GDB)? - Try to reproduce it running `gdb -nx` 15 | * [ ] Did you search through the [documentation](https://github.com/hugsy/gef/) first? 16 | * [ ] Did you check [issues](https://github.com/hugsy/gef/issues) (including 17 | the closed ones) - and the [PR](https://github.com/hugsy/gef/pulls)? 18 | 19 | 20 | ### Step 1: Describe your environment 21 | 22 | * Operating System / Distribution: 23 | * Architecture: 24 | * GEF version (including the Python library version) run `version` in GEF. 25 | 26 | 27 | ### Step 2: Describe your problem 28 | 29 | #### Steps to reproduce 30 | 31 | 1. 32 | 33 | #### Minimalist test case 34 | 35 | 39 | 40 | ```c 41 | // compile with gcc -fPIE -pic -o my_issue.out my_issue.c 42 | int main(){ return 0; } 43 | ``` 44 | 45 | #### Observed Results 46 | 47 | * What happened? This could be a description, log output, etc. 48 | 49 | 50 | #### Expected results 51 | 52 | * What did you expect to happen? 53 | 54 | #### Traces 55 | 56 | 66 | -------------------------------------------------------------------------------- /docs/commands/retdec.md: -------------------------------------------------------------------------------- 1 | ## Command `retdec` 2 | 3 | `gef` uses the RetDec decompiler () 4 | to decompile parts of or entire binary. The command, `retdec`, also has a 5 | default alias, `decompile` to make it easier to remember. 6 | 7 | To use the command, you need to provide `gef` the path to a retdec installation. The compiled 8 | source can be found on the [releases](https://github.com/avast/retdec/releases) page. 9 | 10 | ```text 11 | cd /opt 12 | wget https://github.com/avast/retdec/releases/download/v4.0/retdec-v4.0-ubuntu-64b.tar.xz 13 | tar xvf retdec-v4.0-ubuntu-64b.tar.xz 14 | ``` 15 | 16 | Then enter the path the `gef config` command: 17 | 18 | ```text 19 | gef➤ gef config retdec.retdec_path /opt/retdec 20 | ``` 21 | 22 | You can have `gef` save this path by saving the current configuration settings. 23 | 24 | ```text 25 | gef➤ gef save 26 | ``` 27 | 28 | `retdec` can be used in 3 modes: 29 | 30 | * By providing the option `-a`, `gef` will submit the entire binary being 31 | debugged to RetDec. For example, 32 | 33 | ```text 34 | gef➤ decompile -a 35 | ``` 36 | 37 | ![gef-retdec-full](https://i.imgur.com/58VSHt0.png) 38 | 39 | * By providing the option `-r START:END`, `gef` will submit only the raw 40 | bytes contained within the range specified as argument. 41 | 42 | * By providing the option `-s SYMBOL`, `gef` will attempt to reach a specific 43 | function symbol, dump the function in a temporary file, and submit it to 44 | RetDec. For example, 45 | 46 | ```text 47 | gef➤ decompile -s main 48 | ``` 49 | 50 | ![gef-retdec-symbol-main](https://i.imgur.com/QXaTqyM.png) 51 | 52 | 53 | ## Syntax Highlighting 54 | 55 | Retdec now supports syntax highlighting for all C decompilations with the use of Pygments. 56 | 57 | Available themes can be found 58 | [here](https://github.com/pygments/pygments/blob/8e9f9cbec9ff496a1846602e32af27ffba52f5f7/pygments/styles/__init__.py). 59 | 60 | You can change themes by running 61 | 62 | ```py 63 | gef config retdec.theme THEME_NAME 64 | gef save # remember to save your config! 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/commands/assemble.md: -------------------------------------------------------------------------------- 1 | ## Command assemble 2 | 3 | If you have installed [`keystone`](https://www.keystone-engine.org/), then `gef` 4 | will provide a convenient command to assemble native instructions directly to 5 | opcodes of the architecture you are currently debugging. 6 | 7 | Call it via `assemble` or its alias `asm`: 8 | 9 | ```text 10 | gef➤ asm [INSTRUCTION [; INSTRUCTION ...]] 11 | ``` 12 | 13 | ![gef-assemble](https://i.imgur.com/ShuPF6h.png) 14 | 15 | By setting the `--arch ARCH` and `--mode MODE` the target platform for the 16 | assembly can be changed. Available architectures and modes can be displayed 17 | with `--list-archs`. 18 | 19 | ```text 20 | gef➤ asm --list-archs 21 | Available architectures/modes (with endianness): 22 | - ARM 23 | * ARM (little, big) 24 | * THUMB (little, big) 25 | * ARMV8 (little, big) 26 | * THUMBV8 (little, big) 27 | - ARM64 28 | * AARCH64 (little) 29 | - MIPS 30 | * MIPS32 (little, big) 31 | * MIPS64 (little, big) 32 | - PPC 33 | * PPC32 (big) 34 | * PPC64 (little, big) 35 | - SPARC 36 | * SPARC32 (little, big) 37 | * SPARC64 (big) 38 | - SYSTEMZ 39 | * SYSTEMZ (little, big) 40 | - X86 41 | * 16 (little) 42 | * 32 (little) 43 | * 64 (little) 44 | ``` 45 | 46 | ```text 47 | gef➤ asm --arch x86 --mode 32 [INSTRUCTION [; INSTRUCTION ...]] 48 | gef➤ asm --arch arm [INSTRUCTION [; INSTRUCTION ...]] 49 | ``` 50 | 51 | To choose the endianness use `--endian ENDIANNESS` (by default, `little`): 52 | 53 | ```text 54 | gef➤ asm --endian big [INSTRUCTION [; INSTRUCTION ...]] 55 | ``` 56 | 57 | Using the `--overwrite-location LOCATION` option, `gef` will write the assembly 58 | code generated by `keystone` directly to the memory location specified. This 59 | makes it extremely convenient to simply overwrite opcodes. 60 | 61 | ![gef-assemble-overwrite](https://i.imgur.com/BsbGXNC.png) 62 | 63 | Another convenient option is `--as-shellcode` which outputs the generated 64 | shellcode as an escaped python string. It can then easily be used in your 65 | python scripts. 66 | 67 | ![gef-assemble-shellcode](https://i.imgur.com/E2fpFuH.png) 68 | -------------------------------------------------------------------------------- /docs/commands/syscall-args.md: -------------------------------------------------------------------------------- 1 | ## Command syscall-args 2 | 3 | Often it is troublesome to have to refer to syscall tables every time we 4 | encounter a system call instruction. `gef` can be used to determine the system 5 | call being invoked and the arguments being passed to it. Requires 6 | [gef-extras](https://github.com/hugsy/gef-extras). 7 | 8 | To use it, simply run 9 | 10 | ```text 11 | gef➤ syscall-args 12 | ``` 13 | 14 | For instance, 15 | 16 | ```text 17 | ───────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]──── 18 | $rax : 0x0000000000000001 19 | $rbx : 0x0000000000000045 20 | $rcx : 0x00000000fbad2a84 21 | $rdx : 0x0000000000000045 22 | $rsp : 0x00007fffffffdbf8 → 0x00007ffff786f4bd → <_IO_file_write+45> test rax, rax 23 | $rbp : 0x0000555555775510 → "alarm@192.168.0.100\t\t how2heap\t\t\t\t\t\t\t [...]" 24 | $rsi : 0x0000555555775510 → "alarm@192.168.0.100\t\t how2heap\t\t\t\t\t\t\t [...]" 25 | $rdi : 0x0000000000000001 26 | $rip : 0x00007ffff78de132 → syscall 27 | $r8 : 0x0000555555783b44 → 0x0000000000000066 ("f"?) 28 | $r9 : 0x0000000000000000 29 | $r10 : 0x0000000000002000 30 | $r11 : 0x00007fffffffb940 → 0x7669006666757473 ("stuff"?) 31 | $r12 : 0x00007ffff7bab760 → 0x00000000fbad2a84 32 | $r13 : 0x0000000000000045 33 | $r14 : 0x00007ffff7ba6760 → 0x0000000000000000 34 | $r15 : 0x0000000000000045 35 | $eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification] 36 | $cs: 0x0033 $gs: 0x0000 $ss: 0x002b $es: 0x0000 $fs: 0x0000 $ds: 0x0000 37 | 38 | ... 39 | 40 | gef➤ syscall-args 41 | [+] Detected syscall write 42 | write(unsigned int fd, const char *buf, size_t count) 43 | [+] Parameter Register Value 44 | fd $rdi 0x1 45 | buf $rsi 0x555555775510 → "file1\t\t file2\t\t\t\t\t\t\t [...]" 46 | count $rdx 0x45 47 | ``` 48 | 49 | Check this asciicast for visual example: 50 | 51 | [![asciicast](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6.png)](https://asciinema.org/a/BlrpsfzdLqNdycoxHuGkscYu6) 52 | -------------------------------------------------------------------------------- /scripts/remote.py: -------------------------------------------------------------------------------- 1 | """ 2 | A slightly better way to remote with GDB/GEF 3 | 4 | gdb -ex 'source /path/to/gef-extras/scripts/remote.py' -ex rpyc-remote -ex quit 5 | """ 6 | 7 | __AUTHOR__ = "hugsy" 8 | __VERSION__ = 0.3 9 | 10 | import argparse 11 | 12 | import gdb 13 | 14 | import rpyc 15 | 16 | from typing import TYPE_CHECKING, Any, List 17 | 18 | if TYPE_CHECKING: 19 | import rpyc.core.protocol 20 | import rpyc.utils.server 21 | from . import * 22 | from . import gdb 23 | 24 | RPYC_DEFAULT_HOST = "0.0.0.0" 25 | RPYC_DEFAULT_PORT = 12345 26 | 27 | 28 | class RemoteDebugService(rpyc.Service): 29 | def on_connect(self, conn: rpyc.core.protocol.Connection): 30 | ok(f"connect open: {str(conn)}") 31 | return 32 | 33 | def on_disconnect(self, conn: rpyc.core.protocol.Connection): 34 | ok(f"connection closed: {str(conn)}") 35 | return 36 | 37 | def exposed_eval(self, cmd): 38 | return eval(cmd) 39 | 40 | exposed_gdb = gdb 41 | 42 | exposed_gef = gef 43 | 44 | 45 | @register 46 | class GefRemoteCommand(GenericCommand): 47 | """A better way of remoting to GDB, using rpyc""" 48 | 49 | _cmdline_ = "rpyc-remote" 50 | _aliases_ = [] 51 | _syntax_ = f"{_cmdline_:s} --port=[PORT]" 52 | _example_ = f"{_cmdline_:s} --port=1234" 53 | 54 | def __init__(self) -> None: 55 | super().__init__(prefix=False) 56 | self["host"] = (RPYC_DEFAULT_HOST, "The interface to listen on") 57 | self["port"] = (RPYC_DEFAULT_PORT, "The port to listen on") 58 | return 59 | 60 | def do_invoke(self, _: List[str]) -> None: 61 | old_value = gef.config["gef.buffer"] 62 | if gef.config["gef.buffer"]: 63 | gef.config["gef.buffer"] = False 64 | warn(f"TTY buffer must be disable for {self._cmdline_} to work") 65 | 66 | info(f"RPYC service listening on tcp/{self['host']}:{self['port']}") 67 | svc = rpyc.utils.server.OneShotServer( 68 | RemoteDebugService, 69 | port=self["port"], 70 | protocol_config={ 71 | "allow_public_attrs": True, 72 | }, 73 | ) 74 | svc.start() 75 | gef.config["gef.buffer"] = old_value 76 | -------------------------------------------------------------------------------- /scripts/stack.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "hugsy" 2 | __VERSION__ = 0.2 3 | 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from . import * 8 | from . import gdb 9 | 10 | 11 | @register 12 | class CurrentFrameStack(GenericCommand): 13 | """Show the entire stack of the current frame.""" 14 | _cmdline_ = "current-stack-frame" 15 | _syntax_ = f"{_cmdline_}" 16 | _aliases_ = ["stack-view",] 17 | _example_ = f"{_cmdline_}" 18 | 19 | @only_if_gdb_running 20 | def do_invoke(self, argv): 21 | ptrsize = gef.arch.ptrsize 22 | frame = gdb.selected_frame() 23 | 24 | if frame.older(): 25 | saved_ip = frame.older().pc() 26 | stack_hi = align_address(int(frame.older().read_register("sp"))) 27 | # This ensures that frame.older() does not return None due to another error 28 | elif frame.level() == 0: 29 | saved_ip = None 30 | stack_hi = align_address(int(frame.read_register("bp"))) 31 | else: 32 | #reason = frame.unwind_stop_reason() 33 | reason_str = gdb.frame_stop_reason_string( frame.unwind_stop_reason() ) 34 | warn(f"Cannot determine frame boundary, reason: {reason_str}") 35 | return 36 | 37 | stack_lo = align_address(int(frame.read_register("sp"))) 38 | should_stack_grow_down = gef.config["context.grow_stack_down"] == True 39 | results = [] 40 | 41 | for offset, address in enumerate(range(stack_lo, stack_hi, ptrsize)): 42 | pprint_str = DereferenceCommand.pprint_dereferenced(stack_lo, offset) 43 | if saved_ip and dereference(address) == saved_ip: 44 | pprint_str += " " + Color.colorify("($savedip)", attrs="gray underline") 45 | results.append(pprint_str) 46 | 47 | if should_stack_grow_down: 48 | results.reverse() 49 | gef_print(titlify("Stack top (higher address)")) 50 | else: 51 | gef_print(titlify("Stack bottom (lower address)")) 52 | 53 | gef_print("\n".join(results)) 54 | 55 | if should_stack_grow_down: 56 | gef_print(titlify("Stack bottom (lower address)")) 57 | else: 58 | gef_print(titlify("Stack top (higher address)")) 59 | return 60 | -------------------------------------------------------------------------------- /structs/README.md: -------------------------------------------------------------------------------- 1 | ## Custom structures 2 | 3 | Open repositories of custom structures for GDB Enhanced Features (GEF). Structures are available 4 | via the `pcustom` command (often aliased as `dt`) and allow to recreate and parse at runtime a 5 | segment of memory as-if you had the structure defined in symbols. 6 | 7 | Example: 8 | 9 | ```text 10 | gef➤ pcustom list 11 | [+] Listing custom structures from '/tmp/structs' 12 | → /tmp/structs/elf64_t.py (elf64_t) 13 | 14 | gef➤ vmmap libc 15 | [ Legend: Code | Heap | Stack ] 16 | Start End Offset Perm Path 17 | 0x00007ffff7d9e000 0x00007ffff7dc3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so 18 | [...] 19 | 20 | 0:000 ➤ pucstom elf64_t 0x00007ffff7d9e000 21 | 0x7ffff7d9e000+0x0000 ei_magic : b'\x7fELF' (c_char_Array_4) → Correct ELF header 22 | 0x7ffff7d9e000+0x0004 ei_class : 2 (c_ubyte) → ELFCLASS64 23 | 0x7ffff7d9e000+0x0005 ei_data : 1 (c_ubyte) 24 | 0x7ffff7d9e000+0x0006 ei_version : 1 (c_ubyte) 25 | 0x7ffff7d9e000+0x0007 ei_padd : b'\x03' (c_char_Array_9) 26 | 0x7ffff7d9e000+0x0010 e_type : 3 (c_ushort) → ET_DYN 27 | 0x7ffff7d9e000+0x0012 e_machine : 62 (c_ushort) → EM_AMD64 28 | 0x7ffff7d9e000+0x0014 e_version : 1 (c_int) 29 | 0x7ffff7d9e000+0x0018 e_entry : 0x00000000000271f0 (c_ulong) 30 | 0x7ffff7d9e000+0x0020 e_phoff : 0x0000000000000040 (c_ulong) 31 | 0x7ffff7d9e000+0x0028 e_shoff : 0x00000000001ee568 (c_ulong) 32 | 0x7ffff7d9e000+0x0030 e_flags : 0 (c_int) 33 | 0x7ffff7d9e000+0x0034 e_ehsize : 64 (c_ushort) 34 | 0x7ffff7d9e000+0x0036 e_phentsize : 56 (c_ushort) 35 | 0x7ffff7d9e000+0x0038 e_phnum : 14 (c_ushort) 36 | 0x7ffff7d9e000+0x003a e_shentsize : 64 (c_ushort) 37 | 0x7ffff7d9e000+0x003c e_shnum : 69 (c_ushort) 38 | 0x7ffff7d9e000+0x003e e_shstrndx : 68 (c_ushort) 39 | ``` 40 | 41 | To add a new structure, use the skeleton below (refer to the 42 | [`ctypes` documentation](https://docs.python.org/3/library/ctypes.html) for the syntax) 43 | 44 | ```python 45 | from ctypes import * 46 | 47 | class MyStructure(Structure): 48 | _fields_ = [ 49 | ("Item1", c_short), 50 | ("Item2", c_void_p), 51 | ("Item3", 16 * c_uint32), 52 | ] 53 | _values_ = [] 54 | ``` 55 | 56 | See [`pcustom`](https://hugsy.github.io/commands/pcustom/) for complete documentation 57 | -------------------------------------------------------------------------------- /.github/workflows/discord-notify.yml: -------------------------------------------------------------------------------- 1 | name: "Discord Notification" 2 | 3 | 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | 9 | push: 10 | branches: 11 | - main 12 | 13 | pull_request: 14 | branches: 15 | - main 16 | 17 | env: 18 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 19 | 20 | jobs: 21 | notify: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: GEF-Extras Push Notification 29 | if: github.event_name == 'push' && github.repository_owner == 'hugsy' 30 | uses: sarisia/actions-status-discord@v1 31 | with: 32 | nodetail: true 33 | title: "[${{ github.repository }}] ${{ github.actor }} pushed to `${{ github.ref }}`" 34 | description: | 35 | **Commits**: 36 | ● ${{ join(github.event.commits.*.message, ' 37 | ● ') }} 38 | --- 39 | **Link**: ${{ github.event.compare }} 40 | color: 0x0000ff 41 | username: ${{ github.actor }} via GithubBot 42 | avatar_url: ${{ github.actor.avatar_url }} 43 | 44 | - name: GEF-Extras Pull Request Notification 45 | if: github.event_name == 'pull_request' && github.event.action == 'opened' 46 | uses: sarisia/actions-status-discord@v1 47 | with: 48 | nodetail: true 49 | title: "[${{ github.repository }}] ${{ github.actor }} created a new Pull Request (`#${{ github.event.pull_request.number }}`)" 50 | description: | 51 | **${{ github.event.pull_request.title }}** 52 | --- 53 | **Link**: ${{ github.event.pull_request.html_url }} 54 | color: 0x00ff00 55 | username: ${{ github.actor }} via GithubBot 56 | avatar_url: ${{ github.actor.avatar_url }} 57 | 58 | - name: GEF-Extras Issue Notification 59 | if: github.event_name == 'issues' && github.repository_owner == 'hugsy' 60 | uses: sarisia/actions-status-discord@v1 61 | with: 62 | nodetail: true 63 | title: "[${{ github.repository }}] ${{ github.actor }} created a new Issue (`#${{ github.event.issue.number }}`)" 64 | description: | 65 | **${{ github.event.issue.title }}** 66 | --- 67 | **Link**: ${{ github.event.issue.html_url }} 68 | color: 0xff0000 69 | username: ${{ github.actor }} via GithubBot 70 | avatar_url: ${{ github.actor.avatar_url }} 71 | -------------------------------------------------------------------------------- /tests/commands/keystone_assemble.py: -------------------------------------------------------------------------------- 1 | """ 2 | keystone-assemble command test module 3 | """ 4 | 5 | import pytest 6 | 7 | from tests.base import RemoteGefUnitTestGeneric 8 | 9 | from tests.utils import ( 10 | ARCH, 11 | ) 12 | 13 | 14 | @pytest.mark.skipif( 15 | ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}" 16 | ) 17 | class KeystoneAssembleCommand(RemoteGefUnitTestGeneric): 18 | """`keystone-assemble` command test module""" 19 | 20 | def setUp(self) -> None: 21 | try: 22 | import keystone # pylint: disable=W0611 23 | except ImportError: 24 | pytest.skip("keystone-engine not available", allow_module_level=True) 25 | return super().setUp() 26 | 27 | def test_cmd_keystone_assemble(self): 28 | gdb = self._gdb 29 | self.assertNotIn("keystone", gdb.execute("gef missing", to_string=True)) 30 | cmds = [ 31 | "assemble --arch arm --mode arm add r0, r1, r2", 32 | "assemble --arch arm --mode arm --endian big add r0, r1, r2", 33 | "assemble --arch arm --mode thumb add r0, r1, r2", 34 | "assemble --arch arm --mode thumb --endian big add r0, r1, r2", 35 | "assemble --arch arm64 --mode 0 add x29, sp, 0; mov w0, 0; ret", 36 | "assemble --arch mips --mode mips32 add $v0, 1", 37 | "assemble --arch mips --mode mips32 --endian big add $v0, 1", 38 | "assemble --arch mips --mode mips64 add $v0, 1", 39 | "assemble --arch mips --mode mips64 --endian big add $v0, 1", 40 | "assemble --arch ppc --mode ppc32 --endian big ori 0, 0, 0", 41 | "assemble --arch ppc --mode ppc64 ori 0, 0, 0", 42 | "assemble --arch ppc --mode ppc64 --endian big ori 0, 0, 0", 43 | "assemble --arch sparc --mode sparc32 set 0, %o0", 44 | "assemble --arch sparc --mode sparc32 --endian big set 0, %o0", 45 | "assemble --arch sparc --mode sparc64 --endian big set 0, %o0", 46 | "assemble --arch x86 --mode 16 mov ax, 0x42", 47 | "assemble --arch x86 --mode 32 mov eax, 0x42", 48 | "assemble --arch x86 --mode 64 mov rax, 0x42", 49 | ] 50 | for cmd in cmds: 51 | res = gdb.execute(cmd, to_string=True) or "" 52 | assert res 53 | lines = res.splitlines() 54 | self.assertGreater(len(lines), 1) 55 | -------------------------------------------------------------------------------- /scripts/skel.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "hugsy" 2 | __VERSION__ = 1.0 3 | 4 | import os 5 | import tempfile 6 | from typing import TYPE_CHECKING 7 | import gdb 8 | 9 | if TYPE_CHECKING: 10 | from gef import * 11 | 12 | TEMPLATE = """#!/usr/bin/env python3 13 | import sys, os 14 | from pwn import * 15 | context.update( 16 | arch="{arch}", 17 | endian="{endian}", 18 | os="linux", 19 | log_level="debug", 20 | terminal=["tmux", "split-window", "-h", "-p 65"], 21 | ) 22 | 23 | REMOTE = False 24 | TARGET=os.path.realpath("{filepath}") 25 | elf = ELF(TARGET) 26 | 27 | def attach(r): 28 | if not REMOTE: 29 | bkps = {bkps} 30 | cmds = [] 31 | gdb.attach(r, '\\n'.join(["break {{}}".format(x) for x in bkps] + cmds)) 32 | return 33 | 34 | def exploit(r): 35 | attach(r) 36 | # r.sendlineafter(b"> ", b"HelloPwn" ) 37 | r.interactive() 38 | return 39 | 40 | if __name__ == "__main__": 41 | if len(sys.argv)==2 and sys.argv[1]=="remote": 42 | REMOTE = True 43 | r = remote("{target}", {port}) 44 | else: 45 | REMOTE = False 46 | r = process([TARGET,]) 47 | exploit(r) 48 | exit(0) 49 | """ 50 | 51 | 52 | @register 53 | class ExploitTemplateCommand(GenericCommand): 54 | """Generates a exploit template.""" 55 | _cmdline_ = "exploit-template" 56 | _syntax_ = f"{_cmdline_} [local|remote TARGET:PORT]" 57 | _aliases_ = ["skeleton", ] 58 | 59 | @only_if_gdb_running 60 | def do_invoke(self, args): 61 | if len(args) < 1: 62 | self.usage() 63 | return 64 | 65 | scope = args[0].lower() 66 | if scope not in ("local", "remote"): 67 | self.usage() 68 | return 69 | 70 | target, port = "127.0.0.1", "1337" 71 | if scope == "remote": 72 | if len(args) < 2: 73 | self.usage() 74 | return 75 | 76 | target, port = args[1].split(":", 1) 77 | port = int(port) 78 | 79 | bkps = [b.location for b in gdb.breakpoints()] 80 | temp = TEMPLATE.format( 81 | target=target, 82 | port=port, 83 | arch="amd64" if isinstance(gef.arch, X86_64) else "i386", 84 | endian="big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "little", 85 | filepath=gef.binary.path, 86 | bkps=bkps 87 | ) 88 | fd, fname = tempfile.mkstemp(suffix='.py', prefix='gef_') 89 | with os.fdopen(fd, "w") as f: 90 | f.write(temp) 91 | 92 | ok("Exploit generated in '{:s}'".format(fname)) 93 | return 94 | -------------------------------------------------------------------------------- /tests/commands/capstone_disassemble.py: -------------------------------------------------------------------------------- 1 | """ 2 | capstone-disassemble command test module 3 | """ 4 | 5 | import pytest 6 | from tests.base import RemoteGefUnitTestGeneric 7 | 8 | from tests.utils import ( 9 | ARCH, 10 | ERROR_INACTIVE_SESSION_MESSAGE, 11 | removeuntil, 12 | ) 13 | 14 | 15 | @pytest.mark.skipif( 16 | ARCH in ("mips64el", "ppc64le", "riscv64"), reason=f"Skipped for {ARCH}" 17 | ) 18 | class CapstoneDisassembleCommand(RemoteGefUnitTestGeneric): 19 | """`capstone-disassemble` command test module""" 20 | 21 | def setUp(self) -> None: 22 | try: 23 | import capstone # pylint: disable=W0611 24 | except ImportError: 25 | pytest.skip("capstone-engine not available", allow_module_level=True) 26 | return super().setUp() 27 | 28 | def test_cmd_capstone_disassemble(self): 29 | gdb = self._gdb 30 | cmd = "capstone-disassemble" 31 | 32 | self.assertEqual( 33 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 34 | ) 35 | 36 | gdb.execute("start") 37 | res = gdb.execute("capstone-disassemble", to_string=True) or "" 38 | assert res 39 | 40 | cmd = "capstone-disassemble --show-opcodes" 41 | res = gdb.execute(cmd, to_string=True) or "" 42 | assert res 43 | 44 | cmd = "capstone-disassemble --show-opcodes --length 5 $pc" 45 | res = gdb.execute(cmd, to_string=True) or "" 46 | assert res 47 | 48 | lines = res.splitlines() 49 | self.assertGreaterEqual(len(lines), 5) 50 | 51 | # jump to the output buffer 52 | res = removeuntil("→ ", res, included=True) 53 | addr, opcode, symbol, *_ = [x.strip() for x in lines[2].strip().split()] 54 | 55 | # match the correct output format: [] mnemonic [operands,] 56 | # gef➤ cs --show-opcodes --length 5 $pc 57 | # → 0xaaaaaaaaa840 80000090 adrp x0, #0xaaaaaaaba000 58 | # 0xaaaaaaaaa844 00f047f9 ldr x0, [x0, #0xfe0] 59 | # 0xaaaaaaaaa848 010040f9 ldr x1, [x0] 60 | # 0xaaaaaaaaa84c e11f00f9 str x1, [sp, #0x38] 61 | # 0xaaaaaaaaa850 010080d2 movz x1, #0 62 | 63 | self.assertTrue(addr.startswith("0x")) 64 | self.assertTrue(int(addr, 16)) 65 | self.assertTrue(int(opcode, 16)) 66 | self.assertTrue(symbol.startswith("<") and symbol.endswith(">")) 67 | 68 | cmd = "cs --show-opcodes &__libc_start_main" 69 | res = gdb.execute(cmd, to_string=True) or "" 70 | assert res 71 | self.assertGreater(len(res.splitlines()), 1) 72 | -------------------------------------------------------------------------------- /tests/commands/set_permission.py: -------------------------------------------------------------------------------- 1 | """ 2 | `set-permission` command test module 3 | """ 4 | 5 | import pytest 6 | from tests.base import RemoteGefUnitTestGeneric 7 | 8 | from tests.utils import ARCH, debug_target, ERROR_INACTIVE_SESSION_MESSAGE 9 | 10 | 11 | @pytest.mark.skipif( 12 | ARCH not in ("i686", "x86_64", "armv7l", "aarch64"), reason=f"Skipped for {ARCH}" 13 | ) 14 | class SetPermissionCommand(RemoteGefUnitTestGeneric): 15 | """`set-permission` command test module""" 16 | 17 | def setUp(self) -> None: 18 | try: 19 | import keystone # pylint: disable=W0611 20 | import unicorn # pylint: disable=W0611 21 | except ImportError: 22 | pytest.skip("keystone-engine not available", allow_module_level=True) 23 | 24 | self._target = debug_target("set-permission") 25 | return super().setUp() 26 | 27 | def test_cmd_set_permission_basic(self): 28 | gdb = self._gdb 29 | cmd = "set-permission" 30 | 31 | self.assertEqual( 32 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 33 | ) 34 | 35 | # get the initial stack address 36 | gdb.execute("start") 37 | res = gdb.execute("vmmap", to_string=True) or "" 38 | assert res 39 | stack_line = [l.strip() for l in res.splitlines() if "[stack]" in l][0] 40 | stack_address = int(stack_line.split()[0], 0) 41 | 42 | # compare the new permissions 43 | gdb.execute(f"set-permission {stack_address:#x}") 44 | res = gdb.execute(f"xinfo {stack_address:#x}", to_string=True) or "" 45 | assert res 46 | line = [l.strip() for l in res.splitlines() if l.startswith("Permissions: ")][0] 47 | self.assertEqual(line.split()[1], "rwx") 48 | 49 | res = gdb.execute("set-permission 0x1338000", to_string=True) or "" 50 | assert res 51 | self.assertIn("Unmapped address", res) 52 | 53 | def test_cmd_set_permission_no_clobber(self): 54 | """Make sure set-permission command doesn't clobber any register""" 55 | gdb = self._gdb 56 | gef = self._gef 57 | 58 | gdb.execute("start") 59 | 60 | # 61 | # Collect registers pre-execution 62 | # 63 | before_register_state = [ 64 | (name, gef.arch.register(name)) for name in gef.arch.registers 65 | ] 66 | res = gdb.execute("set-permission $sp", to_string=True) or "" 67 | 68 | # 69 | # Collect registers post-execution 70 | # 71 | assert res 72 | after_register_state = [ 73 | (name, gef.arch.register(name)) for name in gef.arch.registers 74 | ] 75 | 76 | # 77 | # Compare their values 78 | # 79 | assert before_register_state == after_register_state 80 | -------------------------------------------------------------------------------- /structs/io_file64_t.py: -------------------------------------------------------------------------------- 1 | import ctypes as ct 2 | from ctypes import POINTER 3 | 4 | # http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libio.h;hb=765de945efc5d5602999b2999fe8abdf04881370#l67 5 | _IO_MAGIC = 0xFBAD0000 6 | 7 | 8 | class io_file64_plus_t(ct.Structure): 9 | _fields_ = [ 10 | ("_p1", ct.c_uint64), 11 | ("_p2", ct.c_uint64), 12 | ("_IO_file_finish", ct.c_uint64), 13 | ("_IO_file_overflow", ct.c_uint64), 14 | ("_IO_file_underflow", ct.c_uint64), 15 | ("_IO_default_uflow", ct.c_uint64), 16 | ("_IO_default_pbackfail", ct.c_uint64), 17 | ("_IO_file_xsputn", ct.c_uint64), 18 | ("_IO_Unk1", ct.c_uint64), 19 | ("_IO_file_seekoff", ct.c_uint64), 20 | ("_IO_Unk1", ct.c_uint64), 21 | ("_IO_file_setbuf", ct.c_uint64), 22 | ("_IO_file_sync", ct.c_uint64), 23 | ("_IO_file_doallocate", ct.c_uint64), 24 | ("_IO_file_read", ct.c_uint64), 25 | ("_IO_file_write", ct.c_uint64), 26 | ("_IO_file_seek", ct.c_uint64), 27 | ("_IO_file_close", ct.c_uint64), 28 | ("_IO_file_stat", ct.c_uint64), 29 | ] 30 | 31 | _values_ = [] 32 | 33 | 34 | class io_file64_t(ct.Structure): 35 | # http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libio.h;h=3cf1712ea98d3c253f418feb1ef881c4a44649d5;hb=HEAD#l245 36 | _fields_ = [ 37 | ("_flags", ct.c_uint16), 38 | ("_magic", ct.c_uint16), # should be equal to _IO_MAGIC 39 | ("_IO_read_ptr", ct.c_uint64), # /* Current read pointer */ 40 | ("_IO_read_end", ct.c_uint64), # /* End of get area. */ 41 | ("_IO_read_base", ct.c_uint64), # /* Start of putback+get area. */ 42 | ("_IO_write_base", ct.c_uint64), # /* Start of put area. */ 43 | ("_IO_write_ptr", ct.c_uint64), # /* Current put pointer. */ 44 | ("_IO_write_end", ct.c_uint64), # /* End of put area. */ 45 | ("_IO_buf_base", ct.c_uint64), # /* Start of reserve area. */ 46 | ("_IO_buf_end", ct.c_uint64), # /* End of reserve area. */ 47 | ( 48 | "_IO_save_base", 49 | ct.c_uint64, 50 | ), # /* Pointer to start of non-current get area. */ 51 | ( 52 | "_IO_backup_base", 53 | ct.c_uint64, 54 | ), # /* Pointer to first valid character of backup area */ 55 | ("_IO_save_end", ct.c_uint64), # /* Pointer to end of non-current get area. */ 56 | ("_markers", ct.c_char_p), 57 | ("_chain", POINTER(io_file64_plus_t)), 58 | # TODO: some fields are missing, add them 59 | ] 60 | 61 | _values_ = [ 62 | ( 63 | "_magic", 64 | [ 65 | (_IO_MAGIC >> 16, "Correct magic"), 66 | (None, "Incorrect magic (corrupted?)"), 67 | ], 68 | ), 69 | ] 70 | -------------------------------------------------------------------------------- /structs/elf64_t.py: -------------------------------------------------------------------------------- 1 | # 2 | # ELF (64b) parsing from https://www.uclibc.org/docs/elf-64-gen.pdf 3 | # 4 | # @_hugsy_ 5 | # 6 | 7 | import ctypes as ct 8 | 9 | Elf64_Addr = ct.c_uint64 10 | Elf64_Off = ct.c_uint64 11 | Elf64_Half = ct.c_uint16 12 | Elf64_Word = ct.c_int32 13 | Elf64_Sword = ct.c_int32 14 | Elf64_Xword = ct.c_uint64 15 | Elf64_Sxword = ct.c_int64 16 | 17 | 18 | class elf64_t(ct.Structure): 19 | _fields_ = [ 20 | ("ei_magic", ct.c_char * 4), # ELF identification 21 | ("ei_class", ct.c_uint8), 22 | ("ei_data", ct.c_uint8), 23 | ("ei_version", ct.c_uint8), 24 | ("ei_padd", ct.c_char * 9), 25 | ("e_type", Elf64_Half), # Object file type 26 | ("e_machine", Elf64_Half), # Machine type 27 | ("e_version", Elf64_Word), # Object file version 28 | ("e_entry", Elf64_Addr), # Entry point address 29 | ("e_phoff", Elf64_Off), # Program header offset 30 | ("e_shoff", Elf64_Off), # Section header offset 31 | ("e_flags", Elf64_Word), # Processor-specific flags 32 | ("e_ehsize", Elf64_Half), # ELF header size 33 | ("e_phentsize", Elf64_Half), # Size of program header entry 34 | ("e_phnum", Elf64_Half), # Number of program header entries 35 | ("e_shentsize", Elf64_Half), # Size of section header entry 36 | ("e_shnum", Elf64_Half), # Number of section header entries 37 | ("e_shstrndx", Elf64_Half), # Section name string table index 38 | ] 39 | 40 | _values_ = [ 41 | ( 42 | "ei_magic", 43 | [ 44 | (b"\x7fELF", "Correct ELF header"), 45 | (None, "Incorrect ELF header"), # None -> default case 46 | ], 47 | ), 48 | ( 49 | "ei_class", 50 | [ 51 | (0, "ELFCLASSNONE"), 52 | (1, "ELFCLASS32"), 53 | (2, "ELFCLASS64"), 54 | (None, "Incorrect ELF class"), 55 | ], 56 | ), 57 | ( 58 | "e_type", 59 | [ 60 | (0, "ET_NONE"), 61 | (1, "ET_REL"), 62 | (2, "ET_EXEC"), 63 | (3, "ET_DYN"), 64 | (4, "ET_CORE"), 65 | (None, "Unknown type"), 66 | ], 67 | ), 68 | ( 69 | "e_machine", 70 | [ 71 | (0, "EM_NONE"), 72 | (1, "EM_M32"), 73 | (2, "EM_SPARC"), 74 | (3, "EM_386"), 75 | (4, "EM_68K"), 76 | (5, "EM_88K"), 77 | (7, "EM_860"), 78 | (8, "EM_MIPS"), 79 | (40, "EM_ARM"), 80 | (21, "EM_ALPHA"), 81 | (62, "EM_AMD64"), 82 | (None, "Unknown machine"), 83 | ], 84 | ), 85 | ] 86 | -------------------------------------------------------------------------------- /scripts/xref-telescope.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "io12" 2 | __VERSION__ = 0.2 3 | 4 | from typing import TYPE_CHECKING 5 | 6 | if TYPE_CHECKING: 7 | from . import * 8 | 9 | 10 | @register 11 | class XRefTelescopeCommand(SearchPatternCommand): 12 | """Recursively search for cross-references to a pattern in memory""" 13 | 14 | _cmdline_ = "xref-telescope" 15 | _syntax_ = f"{_cmdline_} PATTERN [depth]" 16 | _example_ = [ 17 | f"{_cmdline_} AAAAAAAA", 18 | f"{_cmdline_} 0x555555554000 15" 19 | ] 20 | 21 | def xref_telescope_(self, pattern, depth, tree_heading): 22 | """Recursively search a pattern within the whole userland memory.""" 23 | 24 | if depth <= 0: 25 | return 26 | 27 | if is_hex(pattern): 28 | if gef.arch.endianness == Endianness.BIG_ENDIAN: 29 | pattern = "".join(["\\\\x" + pattern[i:i + 2] 30 | for i in range(2, len(pattern), 2)]) 31 | else: 32 | pattern = "".join(["\\\\x" + pattern[i:i + 2] 33 | for i in range(len(pattern) - 2, 0, -2)]) 34 | 35 | locs = [] 36 | for section in gef.memory.maps: 37 | if not section.permission & Permission.READ: 38 | continue 39 | if section.path == "[vvar]": 40 | continue 41 | 42 | start = section.page_start 43 | end = section.page_end - 1 44 | 45 | locs += self.search_pattern_by_address(pattern, start, end) 46 | if tree_heading == "": 47 | gef_print(" .") 48 | for i, loc in enumerate(locs): 49 | addr_loc_start = lookup_address(loc[0]) 50 | path = addr_loc_start.section.path 51 | perm = addr_loc_start.section.permission 52 | if i == len(locs) - 1: 53 | tree_suffix_pre = " └──" 54 | tree_suffix_post = " " 55 | else: 56 | tree_suffix_pre = " ├──" 57 | tree_suffix_post = " │ " 58 | 59 | line = f'{tree_heading + tree_suffix_pre} {loc[0]:#x} {Color.blueify(path)} {perm} "{Color.pinkify(loc[2])}"' 60 | gef_print(line) 61 | self.xref_telescope_(hex(loc[0]), depth - 1, 62 | tree_heading + tree_suffix_post) 63 | 64 | def xref_telescope(self, pattern, depth): 65 | self.xref_telescope_(pattern, depth, "") 66 | 67 | @only_if_gdb_running 68 | def do_invoke(self, argv): 69 | argc = len(argv) 70 | if argc < 1: 71 | self.usage() 72 | return 73 | 74 | pattern = argv[0] 75 | try: 76 | depth = int(argv[1]) 77 | except (IndexError, ValueError): 78 | depth = 3 79 | 80 | info( 81 | f"Recursively searching '{Color.yellowify(pattern):s}' in memory") 82 | self.xref_telescope(pattern, depth) 83 | -------------------------------------------------------------------------------- /scripts/peekpointers.py: -------------------------------------------------------------------------------- 1 | 2 | __AUTHOR__ = "bkth" 3 | __VERSION__ = 0.2 4 | __LICENSE__ = "MIT" 5 | 6 | import pathlib 7 | from typing import TYPE_CHECKING, List 8 | 9 | if TYPE_CHECKING: 10 | from . import * 11 | 12 | 13 | @register 14 | class PeekPointers(GenericCommand): 15 | """Command to help find pointers belonging to other memory regions helpful in case 16 | of OOB Read when looking for specific pointers""" 17 | 18 | _cmdline_ = "peek-pointers" 19 | _syntax_ = f"{_cmdline_} starting_address " 20 | 21 | @only_if_gdb_running 22 | def do_invoke(self, argv: List[str]): 23 | argc = len(argv) 24 | if argc not in (1, 2, 3): 25 | self.usage() 26 | return 27 | 28 | addr = lookup_address(int(argv[0], 16)) 29 | if (addr.value % DEFAULT_PAGE_SIZE): 30 | err(" must be aligned to a page") 31 | return 32 | 33 | unique = True if "all" not in argv else False 34 | vmmap = gef.memory.maps 35 | 36 | if argc >= 2: 37 | section_name = argv[1].lower() 38 | if section_name == "stack": 39 | sections = [(s.path, s.page_start, s.page_end) 40 | for s in vmmap if s.path == "[stack]"] 41 | elif section_name == "heap": 42 | sections = [(s.path, s.page_start, s.page_end) 43 | for s in vmmap if s.path == "[heap]"] 44 | elif section_name != "all": 45 | sections = [(s.path, s.page_start, s.page_end) 46 | for s in vmmap if section_name in s.path] 47 | else: 48 | sections = [(s.path, s.page_start, s.page_end) for s in vmmap] 49 | else: 50 | sections = [(s.path, s.page_start, s.page_end) for s in vmmap] 51 | 52 | while addr.valid: 53 | addr_value = gef.memory.read_integer(addr.value) 54 | 55 | if lookup_address(addr_value): 56 | for i, section in enumerate(sections): 57 | name, start_addr, end_addr = section 58 | if start_addr <= addr_value < end_addr: 59 | sym = gdb_get_location_from_symbol(addr_value) 60 | sym = "<{:s}+{:04x}>".format(*sym) if sym else '' 61 | if name.startswith("/"): 62 | name = pathlib.Path(name) 63 | elif not name: 64 | name = gef.session.file 65 | 66 | msg = f" Found pointer at 0x{addr.value:x} to 0x{addr_value:x} {sym} ('{name}', perm: {str(addr.section.permission)})" 67 | ok(msg) 68 | 69 | if unique: 70 | del sections[i] 71 | break 72 | 73 | addr = lookup_address(addr.value + gef.arch.ptrsize) 74 | return 75 | -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import random 4 | import subprocess 5 | import tempfile 6 | import time 7 | import unittest 8 | 9 | import rpyc 10 | 11 | from .utils import GEF_EXTRAS_SCRIPTS_PATH, debug_target 12 | 13 | COVERAGE_DIR = os.getenv("COVERAGE_DIR", "") 14 | GEF_PATH = pathlib.Path(os.getenv("GEF_PATH", "../gef/gef.py")).absolute() 15 | RPYC_GEF_PATH = GEF_PATH.parent / "scripts/remote_debug.py" 16 | RPYC_HOST = "localhost" 17 | RPYC_PORT = 18812 18 | RPYC_SPAWN_TIME = 1.0 19 | 20 | 21 | class RemoteGefUnitTestGeneric(unittest.TestCase): 22 | """ 23 | The base class for GEF test cases. This will create the `rpyc` environment to programmatically interact with 24 | GDB and GEF in the test. 25 | """ 26 | 27 | def setUp(self) -> None: 28 | self._coverage_file = None 29 | if not hasattr(self, "_target"): 30 | setattr(self, "_target", debug_target("default")) 31 | else: 32 | assert isinstance(self._target, pathlib.Path) # type: ignore pylint: disable=E1101 33 | assert self._target.exists() # type: ignore pylint: disable=E1101 34 | self._port = random.randint(1025, 65535) 35 | self._commands = "" 36 | 37 | if COVERAGE_DIR: 38 | self._coverage_file = pathlib.Path(COVERAGE_DIR) / os.getenv( 39 | "PYTEST_XDIST_WORKER", "gw0" 40 | ) 41 | self._commands += f""" 42 | pi import coverage 43 | pi cov = coverage.Coverage(data_file="{self._coverage_file}", auto_data=True, branch=True) 44 | pi cov.start() 45 | """ 46 | 47 | self._commands += f""" 48 | source {GEF_PATH} 49 | gef config gef.debug True 50 | gef config gef.propagate_debug_exception True 51 | gef config gef.disable_color True 52 | 53 | gef config gef.extra_plugins_dir {GEF_EXTRAS_SCRIPTS_PATH} 54 | 55 | source {RPYC_GEF_PATH} 56 | pi start_rpyc_service({self._port}) 57 | """ 58 | 59 | self._initfile = tempfile.NamedTemporaryFile(mode="w", delete=False) 60 | self._initfile.write(self._commands) 61 | self._initfile.flush() 62 | self._command = [ 63 | "gdb", 64 | "-q", 65 | "-nx", 66 | "-ex", 67 | f"source {self._initfile.name}", 68 | "--", 69 | str(self._target.absolute()), # type: ignore pylint: disable=E1101 70 | ] 71 | self._process = subprocess.Popen(self._command) 72 | assert self._process.pid > 0 73 | time.sleep(RPYC_SPAWN_TIME) 74 | self._conn = rpyc.connect( 75 | RPYC_HOST, 76 | self._port, 77 | ) 78 | self._gdb = self._conn.root.gdb 79 | self._gef = self._conn.root.gef 80 | return super().setUp() 81 | 82 | def tearDown(self) -> None: 83 | if COVERAGE_DIR: 84 | self._gdb.execute("pi cov.stop()") 85 | self._gdb.execute("pi cov.save()") 86 | self._conn.close() 87 | self._process.terminate() 88 | return super().tearDown() 89 | -------------------------------------------------------------------------------- /docs/commands/bytearray.md: -------------------------------------------------------------------------------- 1 | ## Command bytearray 2 | 3 | The `bytearray` generate a binary with data between 0x01 and 0xff. In general the created file is 4 | used to compare (using the `bincompare` command) with a memory data in order to check badchars. 5 | 6 | 7 | `bytearray` also accepts one option: 8 | 9 | * `-b` (for `badchar`) will exclude the bytes to generated byte array. 10 | 11 | Example without excluding bytes: 12 | 13 | ```text 14 | gef➤ bytearray 15 | [+] Generating table, excluding 0 bad chars... 16 | [+] Dumping table to file 17 | "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" 18 | "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" 19 | "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" 20 | "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" 21 | "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" 22 | "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" 23 | "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" 24 | "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" 25 | [+] Done, wrote 256 bytes to file bytearray.txt 26 | 27 | [+] Binary output saved in bytearray.bin 28 | ``` 29 | 30 | Example excluding bytes (0x00, 0x0a and 0x0d): 31 | 32 | ```text 33 | gef➤ bytearray -b "\x00\x0a\x0d" 34 | [+] Generating table, excluding 3 bad chars... 35 | [+] Dumping table to file 36 | "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22" 37 | "\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42" 38 | "\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62" 39 | "\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82" 40 | "\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2" 41 | "\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2" 42 | "\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2" 43 | "\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" 44 | [+] Done, wrote 253 bytes to file bytearray.txt 45 | [+] Binary output saved in bytearray.bin 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 |

6 | Discord 7 | Docs 8 | Try GEF 9 |

10 | 11 | ## Extra goodies for [`GEF`](https://github.com/hugsy/gef) 12 | 13 | This is an open repository of external scripts and structures to be used by 14 | [GEF](https://github.com/hugsy/gef). As GEF aims to stay a one-file battery-included plugin for 15 | GDB, it doesn't allow by nature to be extended with external Python library. GEF-Extras remediates 16 | that providing some more extensibility to GEF through: 17 | 18 | - more commands and functions 19 | - publicly shared structures for the `pcustom` command 20 | - more operating system support 21 | - more file format support 22 | 23 | 24 | ## Quick start 25 | 26 | The biggest requirement for GEF-Extras to work is of course `GEF`. Please refer to GEF 27 | documentation to have it set up (spoiler alert: it's pretty easy 😉). Once GEF is up and running, 28 | you can install GEF-Extras. 29 | 30 | ### Automated installation 31 | 32 | Execute and run the installation script from GEF repository. 33 | 34 | ```bash 35 | wget -q -O- https://github.com/hugsy/gef/raw/main/scripts/gef-extras.sh | sh 36 | ``` 37 | 38 | The script will download (via git) GEF-Extras, and set up your `~/.gef.rc` file so that you can 39 | start straight away. 40 | 41 | Refer to the [installation page](install.md) for more installation methods. 42 | 43 | 44 | ## Contribution 45 | 46 | ### Through Pull-Requests 47 | 48 | This repository is open for anyone to contribute! Simply 49 | [drop a PR](https://github.com/hugsy/gef-scripts/pulls) with the new command/function/feature. One 50 | thing to note, GEF and GEF-Extras have become what they are today thanks to an up-to-date 51 | documentation, so considering attaching a simple Markdown file to the `docs` folder explaining your 52 | update. **IF** your code is complex and/or requires further scrutiny, adding CI tests would also be 53 | asked during the review process of your PR. 54 | 55 | For a complete rundown of the commands/functions GEF allows to use out of the box, check out 56 | [GEF API page](https://gef.github.io/gef/api/) to start writing powerful GDB commands using GEF! 57 | 58 | As a reward, your Github avatar will be immortalize in the list below of contributors to GEF-Extras 59 | 60 | [![contributors-img](https://contrib.rocks/image?repo=hugsy/gef-extras)](https://github.com/hugsy/gef-extras/graphs/contributors) 61 | 62 | 63 | 64 | ### Feature requests 65 | 66 | Well, that's ok! Just create an [Issue](https://github.com/hugsy/gef-extras/issues) explaining what 67 | cool feature/idea/command you had in mind! Even better, write the documentation (Markdown format) 68 | for your command. It'll make easier for people who wants to integrate it! 69 | 70 | 71 | ### Sponsoring 72 | 73 | Sponsoring is another way to help projects to thrive. You can sponsor GEF and GEF-Extras by 74 | following [this link](https://github.com/sponsors/hugsy). 75 | 76 | 77 | ## Happy hacking 🍻 78 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | ## Installing GEF-Extras 2 | 3 | This page explains to how set up GEF-Extras to work alongside of GEF. 4 | 5 | ## Prerequisites 6 | 7 | ### GDB 8 | 9 | Only [GDB 8 and higher](https://www.gnu.org/s/gdb) is required. It must be compiled with Python 3.6 10 | or higher support. For most people, simply using your distribution package manager should be enough. 11 | 12 | GEF will then only work for Python 3. If you absolutely require GDB + Python 2, please use 13 | [GEF-Legacy](https://github.com/hugsy/gef-legacy) instead. Note that `gef-legacy` won't provide new 14 | features, and only functional bugs will be handled. 15 | 16 | You can verify it with the following command: 17 | 18 | ```bash 19 | b -nx -ex 'pi print(sys.version)' -ex quit 20 | ``` 21 | 22 | This should display your version of Python compiled with `gdb`. 23 | 24 | ```bash 25 | $ gdb -nx -ex 'pi print(sys.version)' -ex quit 26 | 3.6.9 (default, Nov 7 2019, 10:44:02) 27 | [GCC 8.3.0] 28 | ``` 29 | 30 | ### GEF 31 | 32 | For a quick installation of GEF, you can get started with the following commands: 33 | 34 | ```bash 35 | # via the install script 36 | ## using curl 37 | $ bash -c "$(curl -fsSL https://gef.blah.cat/sh)" 38 | 39 | ## using wget 40 | $ bash -c "$(wget https://gef.blah.cat/sh -O -)" 41 | ``` 42 | 43 | For more advanced installation methods, refer 44 | [the installation chapter of the GEF documentation](https://hugsy.github.io/gef/install). 45 | 46 | ### Python dependencies 47 | 48 | Because GEF-Extras allows external dependencies, you must make sure to have the adequate Python 49 | libraries installed before you can use the features. 50 | 51 | Thankfully this is easily done in Python, as such: 52 | 53 | ```text 54 | wget -O /tmp/requirements.txt https://raw.githubusercontent.com/hugsy/gef-extras/main/requirements.txt 55 | python -m pip install --user --upgrade -r /tmp/requirements.txt 56 | ``` 57 | 58 | 59 | ### Installation using Git 60 | 61 | Start with cloning this repo: 62 | 63 | ```bash 64 | git clone https://github.com/hugsy/gef-extras 65 | ``` 66 | 67 | Add syscall_args and libc_function_args to context layout: 68 | 69 | ```text 70 | gef➤ pi gef.config['context.layout'] += ' syscall_args' 71 | gef➤ pi gef.config['context.layout'] += ' libc_function_args' 72 | ``` 73 | 74 | Add the path to the external scripts to GEF's config: 75 | 76 | ```text 77 | gef➤ gef config gef.extra_plugins_dir /path/to/gef-extras/scripts 78 | ``` 79 | 80 | And same for the structures (to be used by 81 | [`pcustom` command](https://hugsy.github.io/gef/commands/pcustom/)): 82 | 83 | ```text 84 | gef➤ gef config pcustom.struct_path /path/to/gef-extras/structs 85 | ``` 86 | 87 | And for the syscall tables: 88 | 89 | ```text 90 | gef➤ gef config syscall-args.path /path/to/gef-extras/syscall-tables 91 | ``` 92 | 93 | And finally for the glibc function call args definition: 94 | 95 | ```text 96 | gef➤ gef config context.libc_args True 97 | gef➤ gef config context.libc_args_path /path/to/gef-extras/glibc-function-args 98 | ``` 99 | 100 | And don't forget to save your settings. 101 | 102 | ```text 103 | gef➤ gef save 104 | ``` 105 | 106 | Check out the [complete documentation](commands/glibc_function_args.md) on libc argument support. 107 | 108 | Note that it is possible to specify multiple directories, separating the paths with 109 | a semi-colon: 110 | 111 | ```text 112 | gef➤ gef config gef.extra_plugins_dir /path/to/dir1;/path/to/dir2 113 | ``` 114 | 115 | Now run and enjoy all the fun! 116 | -------------------------------------------------------------------------------- /scripts/ftrace.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "hugsy" 2 | __VERSION__ = 0.1 3 | 4 | __AUTHOR__ = "your_name" 5 | __VERSION__ = 0.1 6 | __LICENSE__ = "MIT" 7 | 8 | import collections 9 | import pathlib 10 | from typing import TYPE_CHECKING, Optional 11 | 12 | if TYPE_CHECKING: 13 | from . import * 14 | from . import gdb 15 | 16 | 17 | PLUGIN_FTRACE_DEFAULT_OUTPUT = "/dev/stderr" 18 | 19 | 20 | class FtraceEnterBreakpoint(gdb.Breakpoint): 21 | def __init__(self, location: str, nb_args: int): 22 | super().__init__(location, gdb.BP_BREAKPOINT, internal=True) 23 | self.silent: bool = True 24 | self.nb_args: int = nb_args 25 | self.retbp: Optional[FtraceExitBreakpoint] = None 26 | return 27 | 28 | def stop(self): 29 | regs = collections.OrderedDict() 30 | for idx, r in enumerate(gef.arch.function_parameters): 31 | if idx >= self.nb_args: 32 | break 33 | regs[r] = gef.arch.register(r) 34 | self.retbp = FtraceExitBreakpoint( 35 | location=self.location, regs=regs) 36 | return False 37 | 38 | 39 | class FtraceExitBreakpoint(gdb.FinishBreakpoint): 40 | def __init__(self, *args, **kwargs): 41 | super(FtraceExitBreakpoint, self).__init__( 42 | gdb.newest_frame(), internal=True) 43 | self.silent = True 44 | self.args = kwargs 45 | return 46 | 47 | def stop(self): 48 | if self.return_value: 49 | retval = format_address(abs(self.return_value)) 50 | else: 51 | retval = gef.arch.register(gef.arch.return_register) 52 | 53 | output = pathlib.Path(gef.config["ftrace.output"]) 54 | use_color = PLUGIN_FTRACE_DEFAULT_OUTPUT == gef.config["ftrace.output"] 55 | 56 | with output.open("w") as fd: 57 | if use_color: 58 | fd.write("{:s}() = {} {{\n".format( 59 | Color.yellowify(self.args["location"]), retval)) 60 | else: 61 | fd.write("{:s}() = {} {{\n".format( 62 | self.args["location"], retval)) 63 | for reg in self.args["regs"].keys(): 64 | regval = self.args["regs"][reg] 65 | fd.write("\t{} {} {}\n".format(reg, RIGHT_ARROW, 66 | RIGHT_ARROW.join(dereference_from(regval)))) 67 | fd.write("}\n") 68 | fd.flush() 69 | return False 70 | 71 | 72 | @register 73 | class FtraceCommand(GenericCommand): 74 | """Tracks a function given in parameter for arguments and return code.""" 75 | _cmdline_ = "ftrace" 76 | _syntax_ = "{:s} , [, ...]".format( 77 | _cmdline_) 78 | 79 | def __init__(self) -> None: 80 | super().__init__() 81 | self["output"] = (PLUGIN_FTRACE_DEFAULT_OUTPUT, 82 | "Path to store/load the syscall tables files") 83 | return 84 | 85 | def do_invoke(self, args): 86 | if len(args) < 1: 87 | self.usage() 88 | return 89 | 90 | self.bkps = [] 91 | 92 | for item in args: 93 | funcname, nb_args = item.split(",") 94 | self.bkps.append(FtraceEnterBreakpoint(funcname, int(nb_args))) 95 | ok("added '{}()' (with {} args) to tracking list".format(funcname, nb_args)) 96 | 97 | gdb.events.exited.connect(self.cleanup) 98 | return 99 | 100 | def cleanup(self, _: gdb.ExitedEvent): 101 | for bp in self.bkps: 102 | if bp.retbp: 103 | bp.retbp.delete() 104 | bp.delete() 105 | gdb.events.exited.disconnect(self.cleanup) 106 | return 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 | 5 |

6 | Discord 7 | Docs 8 | Try GEF 9 |

10 | 11 | ## Extra goodies for [`GEF`](https://github.com/hugsy/gef) 12 | 13 | 14 | This is an open repository of external scripts and structures to be used by 15 | [GDB Enhanced Features (GEF)](https://github.com/hugsy/gef). To use those scripts once `gef` is 16 | setup, simply clone this repository and update your GEF settings like this: 17 | 18 | 19 | ## Get Started 20 | 21 | Getting started with GEF-Extras couldn't be easier: make sure you have a working GDB and GEF 22 | already installed, then run the following command: 23 | 24 | ```bash 25 | wget -q -O- https://github.com/hugsy/gef/raw/main/scripts/gef-extras.sh | sh 26 | ``` 27 | 28 | 29 | ## Documentation 30 | 31 | Just like [GEF](https://hugsy.github.io/gef), GEF-Extras aims to have and keep to-date 32 | [a through documentation](https://hugsy.github.io/gef-extras/). Users are recommended to refer to 33 | it as it may help them in their attempts to use GEF. In particular, new users should navigate 34 | through it (see the [FAQ](https://hugsy.github.io/gef/faq/) for common installation problems), and 35 | the problem persists, try to reach out for help on the Discord channel or submit an issue. 36 | 37 | 38 | ## Current status 39 | 40 | | Documentation | License | Compatibility | CI Tests (`main`) | 41 | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 42 | | [![Documentation](https://github.com/hugsy/gef-extras/actions/workflows/generate-docs.yml/badge.svg)](https://github.com/hugsy/gef-extras/actions/workflows/generate-docs.yml) | [![MIT](https://img.shields.io/packagist/l/doctrine/orm.svg?maxAge=2592000?style=plastic)](https://github.com/hugsy/gef-extras/blob/main/LICENSE) | [![Python 3](https://img.shields.io/badge/Python-3-green.svg)](https://github.com/hugsy/gef-extras/) | [![CI Test for GEF-Extras](https://github.com/hugsy/gef-extras/actions/workflows/tests.yml/badge.svg)](https://github.com/hugsy/gef-extras/actions/workflows/tests.yml) | 43 | 44 | 45 | ## Contribute 46 | 47 | To get involved, refer to the 48 | [Contribution documentation](https://hugsy.github.io/gef-extras/#contribution) and the 49 | [GEF guidelines](https://github.com/hugsy/gef/blob/main/.github/CONTRIBUTING.md) to start. 50 | 51 | ## Sponsors 52 | 53 | Another way to contribute to keeping the project alive is by sponsoring it! Check out 54 | [the sponsoring documentation](https://hugsy.github.io/gef/#sponsors) for details so you can be 55 | part of the list of those [awesome sponsors](https://github.com/sponsors/hugsy). 56 | 57 | 58 | ## Happy Hacking 🍻 59 | -------------------------------------------------------------------------------- /tests/commands/syscall_args.py: -------------------------------------------------------------------------------- 1 | """ 2 | `syscall-args` command test module 3 | """ 4 | 5 | import pathlib 6 | import tempfile 7 | 8 | import pytest 9 | from tests.base import RemoteGefUnitTestGeneric 10 | 11 | from tests.utils import ( 12 | ARCH, 13 | ERROR_INACTIVE_SESSION_MESSAGE, 14 | GEF_DEFAULT_TEMPDIR, 15 | debug_target, 16 | download_file, 17 | removeafter, 18 | removeuntil, 19 | ) 20 | 21 | 22 | @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") 23 | class SyscallArgsCommand(RemoteGefUnitTestGeneric): 24 | """`syscall-args` command test module""" 25 | 26 | @pytest.mark.online 27 | def setUp(self) -> None: 28 | # 29 | # `syscall-args.out` only work for x86_64 and i686 architectures 30 | # 31 | self.tempdirfd = tempfile.TemporaryDirectory(prefix=GEF_DEFAULT_TEMPDIR) 32 | self.tempdirpath = pathlib.Path(self.tempdirfd.name).absolute() 33 | # download some syscall tables from gef-extras 34 | base = "https://raw.githubusercontent.com/hugsy/gef-extras/main/scripts/syscall_args/syscall-tables" 35 | # todo: maybe add "PowerPC", "PowerPC64", "SPARC", "SPARC64" 36 | for arch in ("ARM", "ARM_OABI", "X86", "X86_64"): 37 | url = f"{base}/{arch}.py" 38 | data = download_file(url) 39 | if not data: 40 | raise Exception(f"Failed to download {arch}.py ({url})") 41 | fpath = self.tempdirpath / f"{arch}.py" 42 | with fpath.open("wb") as fd: 43 | fd.write(data) 44 | 45 | self._target = debug_target("syscall-args") 46 | return super().setUp() 47 | 48 | def tearDown(self) -> None: 49 | self.tempdirfd.cleanup() 50 | return 51 | 52 | def test_cmd_syscall_args(self): 53 | gdb = self._gdb 54 | cmd = "syscall-args" 55 | self.assertEqual( 56 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 57 | ) 58 | 59 | gdb.execute(f"gef config syscall-args.path {self.tempdirpath.absolute()}") 60 | 61 | res = gdb.execute("catch syscall openat", to_string=True) or "" 62 | assert res 63 | self.assertIn("(syscall 'openat' ", res) 64 | 65 | gdb.execute("run") 66 | 67 | res = gdb.execute("syscall-args", to_string=True) or "" 68 | self.assertIn("Detected syscall open", res) 69 | 70 | 71 | @pytest.mark.skipif(ARCH not in ("i686", "x86_64"), reason=f"Skipped for {ARCH}") 72 | class IsSyscallCommand(RemoteGefUnitTestGeneric): 73 | """`is-syscall` command test module""" 74 | 75 | def setUp(self) -> None: 76 | self._target = debug_target("syscall-args") 77 | self.syscall_location = None 78 | return super().setUp() 79 | 80 | def test_cmd_is_syscall(self): 81 | gdb = self._gdb 82 | cmd = "is-syscall" 83 | self.assertEqual( 84 | ERROR_INACTIVE_SESSION_MESSAGE, gdb.execute(cmd, to_string=True) 85 | ) 86 | 87 | res = gdb.execute("disassemble openfile", to_string=True) or "" 88 | start_str = "Dump of assembler code for function main:\n" 89 | end_str = "End of assembler dump." 90 | disass_code = removeafter(end_str, res) 91 | disass_code = removeuntil(start_str, disass_code) 92 | lines = disass_code.splitlines() 93 | for line in lines: 94 | parts = [x.strip() for x in line.split(maxsplit=3)] 95 | self.assertGreaterEqual(len(parts), 3) 96 | if ARCH == "x86_64" and parts[2] == "syscall": 97 | self.syscall_location = parts[1].lstrip("<").rstrip(">:") 98 | break 99 | if ARCH == "i686" and parts[2] == "int" and parts[3] == "0x80": 100 | self.syscall_location = parts[1].lstrip("<").rstrip(">:") 101 | break 102 | assert self.syscall_location 103 | 104 | gdb.execute(f"break *(openfile{self.syscall_location})") 105 | gdb.execute("run") 106 | 107 | res = gdb.execute("is-syscall", to_string=True) or "" 108 | assert res 109 | self.assertIn("Current instruction is a syscall", res) 110 | -------------------------------------------------------------------------------- /archs/m68k.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | M68K support for GEF 4 | 5 | To use, source this file *after* gef 6 | 7 | Original PR: https://github.com/hugsy/gef/pull/453 8 | Author: zhuyifei1999 9 | """ 10 | 11 | class M68K(Architecture): 12 | arch = "M68K" 13 | aliases = ("M68K", ) 14 | mode = "" 15 | 16 | nop_insn = b"\x4e\x71" 17 | flag_register = "$ps" 18 | all_registers = ("$d0", "$d1", "$d2", "$d3", "$d4", "$d5", "$d6", "$d7", 19 | "$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$fp", "$sp", 20 | "$ps", "$pc") 21 | instruction_length = None 22 | return_register = "$d0" 23 | function_parameters = ("$sp",) 24 | flags_table = { 25 | 0: "carry", 26 | 1: "overflow", 27 | 2: "zero", 28 | 3: "negative", 29 | 4: "extend", 30 | 12: "master", 31 | 13: "supervisor", 32 | } 33 | syscall_register = "$d0" 34 | syscall_instructions = ("trap #0",) 35 | 36 | def flag_register_to_human(self, val=None): 37 | reg = self.flag_register 38 | if not val: 39 | val = get_register(reg) 40 | return flags_to_human(val, self.flags_table) 41 | 42 | def is_call(self, insn): 43 | mnemo = insn.mnemonic 44 | call_mnemos = {"jsr", "bsrb", "bsrw", "bsrl"} 45 | return mnemo in call_mnemos 46 | 47 | def is_ret(self, insn): 48 | return insn.mnemonic == "rts" 49 | 50 | def is_conditional_branch(self, insn): 51 | mnemo = insn.mnemonic 52 | branch_mnemos = { 53 | "bccb", "bcsb", "beqb", "bgeb", "bgtb", "bhib", "bleb", 54 | "blsb", "bltb", "bmib", "bneb", "bplb", "bvcb", "bvsb", 55 | "bccw", "bcsw", "beqw", "bgew", "bgtw", "bhiw", "blew", 56 | "blsw", "bltw", "bmiw", "bnew", "bplw", "bvcw", "bvsw", 57 | "bccl", "bcsl", "beql", "bgel", "bgtl", "bhil", "blel", 58 | "blsl", "bltl", "bmil", "bnel", "bpll", "bvcl", "bvsl", 59 | } 60 | return mnemo in branch_mnemos 61 | 62 | def is_branch_taken(self, insn): 63 | mnemo = insn.mnemonic 64 | flags = dict((self.flags_table[k], k) for k in self.flags_table) 65 | val = get_register(self.flag_register) 66 | 67 | taken, reason = False, "" 68 | 69 | if mnemo in ("bccs", "bccw", "bccl"): 70 | taken, reason = not val&(1<> $GITHUB_ENV 32 | echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV 33 | echo GEF_PATH_DIR=${HOME}/gef >> $GITHUB_ENV 34 | echo GEF_EXTRAS_PATH=${HOME}/gef-extras >> $GITHUB_ENV 35 | 36 | - name: Checkout GEF & GEF-Extras 37 | run: | 38 | mkdir -p ~/.config/pip 39 | echo '[global]' > ~/.config/pip/pip.conf 40 | echo 'break-system-packages = true' >> ~/.config/pip/pip.conf 41 | mkdir -p ${{ env.GEF_PATH_DIR }} ${{ env.GEF_EXTRAS_PATH }} 42 | wget -O ${{ env.GEF_PATH }} https://raw.githubusercontent.com/hugsy/gef/${{ env.BRANCH }}/gef.py 43 | echo "source ${{ env.GEF_PATH }}" > ~/.gdbinit 44 | wget -O ./gef-extras.sh https://github.com/hugsy/gef/raw/${{ env.BRANCH }}/scripts/gef-extras.sh 45 | chmod +x ./gef-extras.sh 46 | ./gef-extras.sh -b ${{ env.BRANCH }} -p ${HOME} 47 | gdb -q -ex 'gef missing' -ex quit 48 | 49 | tests: 50 | needs: [installers, ] 51 | strategy: 52 | fail-fast: false 53 | matrix: 54 | runner: [ubuntu-24.04, ubuntu-22.04] 55 | name: "Run Unit tests on ${{ matrix.runner }}" 56 | runs-on: ${{ matrix.runner }} 57 | defaults: 58 | run: 59 | shell: bash 60 | 61 | steps: 62 | - uses: actions/checkout@v4 63 | 64 | - name: Install python and toolchain 65 | run: | 66 | sudo apt-get update 67 | sudo apt-get install -y wget gdb-multiarch python3-dev python3-pip python3-wheel python3-setuptools git cmake gcc g++ pkg-config libglib2.0-dev gdbserver 68 | 69 | - name: Install python and toolchain 70 | if: matrix.runner == 'ubuntu-24.04' 71 | run: | 72 | mkdir -p ~/.config/pip 73 | echo '[global]' > ~/.config/pip/pip.conf 74 | echo 'break-system-packages = true' >> ~/.config/pip/pip.conf 75 | sudo apt-get install -y python3-full 76 | 77 | - name: Install python and toolchain 78 | if: matrix.runner != 'ubuntu-24.04' 79 | run: | 80 | python3 -m pip install pip -U 81 | 82 | - name: Set runtime environment variables 83 | run: | 84 | echo PY_VER=`gdb -q -nx -ex "pi print('.'.join(map(str, sys.version_info[:2])))" -ex quit` >> $GITHUB_ENV 85 | echo GEF_CI_NB_CPU=`grep -c ^processor /proc/cpuinfo` >> $GITHUB_ENV 86 | echo GEF_CI_ARCH=`uname --processor` >> $GITHUB_ENV 87 | echo GEF_CI_CACHE_DIR=`python3 -m pip cache dir` >> $GITHUB_ENV 88 | echo GEF_PATH_DIR=${HOME}/gef >> $GITHUB_ENV 89 | echo GEF_PATH="${HOME}/gef/gef.py" >> $GITHUB_ENV 90 | 91 | - name: Cache dependencies 92 | uses: actions/cache@v4 93 | id: cache-deps 94 | env: 95 | cache-name: cache-deps 96 | with: 97 | key: ${{ matrix.runner }}-pip-${{ hashFiles('**/requirements.txt') }} 98 | path: | 99 | ${{ env.GEF_CI_CACHE_DIR }} 100 | restore-keys: 101 | ${{ matrix.runner }}-pip-${{ env.cache-name }}- 102 | ${{ matrix.runner }}-pip- 103 | ${{ matrix.runner }}-${{ env.cache-name }}- 104 | ${{ matrix.runner }}- 105 | 106 | - name: Install requirements 107 | run: | 108 | mkdir -p ${{ env.GEF_CI_CACHE_DIR }} 109 | python${{ env.PY_VER }} -m pip install --user --upgrade -r ./requirements.txt -r ./tests/requirements.txt 110 | 111 | - name: Checkout GEF 112 | run: | 113 | git clone -b ${{ env.BRANCH }} https://github.com/hugsy/gef ${{ env.GEF_PATH_DIR }} 114 | echo "source ${{ env.GEF_PATH }}" > ~/.gdbinit 115 | gdb -q -ex 'gef missing' -ex 'gef help' -ex 'gef config' -ex start -ex continue -ex quit /bin/pwd 116 | 117 | - name: Setup Tests 118 | run: | 119 | make -C tests/binaries -j ${{ env.GEF_CI_NB_CPU }} 120 | 121 | - name: Run Tests 122 | run: | 123 | python${{ env.PY_VER }} -m pytest --forked -n ${{ env.GEF_CI_NB_CPU }} -v -m "not benchmark" tests/ 124 | -------------------------------------------------------------------------------- /scripts/bincompare.py: -------------------------------------------------------------------------------- 1 | # 2 | # compare an binary file with the memory position looking for badchars 3 | # 4 | # @helviojunior 5 | # https://www.helviojunior.com.br/ 6 | # 7 | # Load with: 8 | # gef> source /path/to/bincompare.py 9 | # 10 | # Use with 11 | # gef> bincompare -f /path/to/bytearray.bin -a memory_address 12 | # 13 | 14 | import pathlib 15 | from typing import TYPE_CHECKING, Any, List 16 | 17 | import gdb 18 | 19 | if TYPE_CHECKING: 20 | from . import * 21 | 22 | __AUTHOR__ = "@helviojunior" 23 | __VERSION__ = 0.2 24 | __LICENSE__ = "MIT" 25 | 26 | 27 | @register 28 | class BincompareCommand(GenericCommand): 29 | """Compare an binary file with the memory position looking for badchars.""" 30 | _cmdline_ = "bincompare" 31 | _syntax_ = f"{_cmdline_} MEMORY_ADDRESS FILE" 32 | 33 | def __init__(self): 34 | super().__init__(complete=gdb.COMPLETE_FILENAME) 35 | return 36 | 37 | def usage(self): 38 | h = (self._syntax_ + "\n" + 39 | "\tMEMORY_ADDRESS sepecifies the memory address.\n" + 40 | "\tFILE specifies the binary file to be compared.") 41 | info(h) 42 | return 43 | 44 | @only_if_gdb_running 45 | @parse_arguments({"address": "", "filename": ""}, {}) 46 | def do_invoke(self, _: List[str], **kwargs: Any) -> None: 47 | size = 0 48 | file_data = None 49 | memory_data = None 50 | 51 | args = kwargs["arguments"] 52 | if not args.address or not args.filename: 53 | err("No file and/or address specified") 54 | return 55 | 56 | start_addr = parse_address(args.address) 57 | filename = pathlib.Path(args.filename) 58 | 59 | if not filename.exists(): 60 | err(f"Specified file '{filename}' not exists") 61 | return 62 | 63 | file_data = filename.open("rb").read() 64 | size = len(file_data) 65 | 66 | if size < 8: 67 | err("Error - file does not contain enough bytes (min 8 bytes needed)") 68 | return 69 | 70 | try: 71 | memory_data = gef.memory.read(start_addr, size) 72 | except gdb.MemoryError: 73 | err("Cannot reach memory {:#x}".format(start_addr)) 74 | return 75 | 76 | result_table = [] 77 | badchars = "" 78 | cnt = 0 79 | corrupted = -1 80 | for eachByte in file_data: 81 | hexchar = "{:02x}".format(eachByte) 82 | if cnt > len(memory_data): 83 | result_table.append((hexchar, "--")) 84 | corrupted = -1 85 | elif eachByte == memory_data[cnt]: 86 | result_table.append((hexchar, " ")) 87 | corrupted = -1 88 | else: 89 | result_table.append( 90 | (hexchar, "{:02x}".format(memory_data[cnt]))) 91 | if len(badchars) == 0: 92 | badchars = hexchar 93 | else: 94 | badchars += ", " + hexchar 95 | if corrupted == -1: 96 | corrupted = cnt 97 | cnt += 1 98 | 99 | line = 0 100 | 101 | info("Comparison result:") 102 | gef_print(" +-----------------------------------------------+") 103 | for line in range(0, len(result_table), 16): 104 | pdata1 = [] 105 | pdata2 = [] 106 | for i in range(line, line + 16): 107 | if i < len(result_table): 108 | pdata1.append(result_table[i][0]) 109 | pdata2.append(result_table[i][1]) 110 | 111 | self.print_line("{:02x}".format(line), pdata1, "file") 112 | self.print_line(" ", pdata2, "memory") 113 | 114 | gef_print(" +-----------------------------------------------+") 115 | gef_print("") 116 | 117 | if corrupted > -1: 118 | info("Corruption after {:d} bytes".format(corrupted)) 119 | 120 | if badchars == "": 121 | info("No badchars found!") 122 | else: 123 | info("Badchars found: {:s}".format(badchars)) 124 | 125 | return 126 | 127 | def print_line(self, line, data, label): 128 | l = [] 129 | for d in data: 130 | l.append(d) 131 | r = 16 - len(l) 132 | for i in range(0, r): 133 | l.append("--") 134 | 135 | gef_print(" {:s} |{:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s} {:s}| {:s}" 136 | .format(line, l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], 137 | l[9], l[10], l[11], l[12], l[13], l[14], l[15], label)) 138 | -------------------------------------------------------------------------------- /scripts/libc_function_args/tables/generator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | __AUTHOR__ = "theguly" 3 | __VERSION__ = 0.2 4 | 5 | import inspect 6 | import gzip 7 | import json 8 | import logging 9 | import pathlib 10 | from typing import Dict, List 11 | 12 | import requests 13 | 14 | function_dict: Dict[str, List[str]] = {} 15 | 16 | 17 | def __get_function_name(l: str) -> str: 18 | pre_args = l.split(" (")[0] 19 | _function_name = pre_args.split(" ")[-1] 20 | return _function_name 21 | 22 | 23 | def __get_function_args(l: str) -> List[str]: 24 | _function_args = " (".join(l.split(" (")[1:]) 25 | _function_args = ")".join(_function_args.split(")")[:-1]) 26 | _function_args = _function_args.split(",") 27 | ret_function_args = [_function_arg.strip() for _function_arg in _function_args] 28 | return ret_function_args 29 | 30 | 31 | def generate_json_file( 32 | function_dict: dict[str, List[str]], _params: List[str], outfile_name: pathlib.Path 33 | ) -> bool: 34 | _dict = {} 35 | for _key, _value in function_dict.items(): 36 | _dict[_key] = {} 37 | for i in range(0, len(_value)): 38 | _dict[_key][_params[i]] = _value[i] 39 | 40 | outfile_path = pathlib.Path(inspect.getfile(inspect.currentframe())).parent.resolve() / outfile_name 41 | with outfile_path.open("w") as outfile: 42 | json.dump(_dict, outfile) 43 | 44 | logging.info(f"{outfile_name} written") 45 | return True 46 | 47 | 48 | def generate_all_json_files() -> bool: 49 | curdir = pathlib.Path(inspect.getfile(inspect.currentframe())).parent.resolve() 50 | libc_file_path = curdir / "libc.txt.gz" 51 | libc_x86_funcdef_fpath = curdir / "x86_32.json" 52 | libc_x64_funcdef_fpath = curdir / "x86_64.json" 53 | libc_funcdef_list = [libc_x86_funcdef_fpath, libc_x64_funcdef_fpath] 54 | 55 | if not libc_file_path.exists(): 56 | # 57 | # Try once to download if missing 58 | # 59 | url = "https://www.gnu.org/software/libc/manual/text/libc.txt.gz" 60 | res = requests.get(url, stream=True) 61 | if res.status_code == 200: 62 | with libc_file_path.open("wb") as fd: 63 | for chunk in res.iter_content(chunk_size=128): 64 | fd.write(chunk) 65 | 66 | for f in (libc_x86_funcdef_fpath, libc_x64_funcdef_fpath): 67 | if f.exists(): 68 | overwrite = input(f"File {f} exists, overwrite? [y/N]") 69 | if "y" in overwrite.lower(): 70 | logging.debug(f"{f} will be overwritten") 71 | else: 72 | logging.info(f"Not overwriting {f}") 73 | libc_funcdef_list.remove(f) 74 | 75 | if libc_funcdef_list: 76 | # 77 | # looks like gzip.open doesn't raise any exception if opening a non-gzipped file. you'll end up with empty json 78 | # 79 | try: 80 | fh = gzip.open(libc_file_path, "r") 81 | except FileNotFoundError: 82 | logging.error(f"Missing {libc_file_path}.") 83 | return False 84 | 85 | old_pos = fh.tell() 86 | 87 | while True: 88 | line = fh.readline().decode("utf-8") 89 | 90 | # check for EoF 91 | current_pos = fh.tell() 92 | if current_pos == old_pos: 93 | break 94 | else: 95 | old_pos = current_pos 96 | 97 | if " -- Function: " in line: 98 | # some function def span two lines, join them 99 | line = line.replace(" -- Function: ", "").rstrip().rstrip(";").lstrip() 100 | if not line.endswith(")"): 101 | next_line = fh.readline() 102 | next_line = next_line.decode("utf-8") 103 | next_line = next_line.rstrip().lstrip() 104 | line = f"{line} {next_line}" 105 | 106 | function_name = __get_function_name(line) 107 | function_args = __get_function_args(line) 108 | 109 | # there are two dupes as of now, get around the issue by using the last one found 110 | if function_name in function_dict: 111 | logging.warning(f"Found dupe for {function_name}") 112 | 113 | function_dict[function_name] = [] 114 | 115 | for x in function_args: 116 | function_dict[function_name].append(x) 117 | 118 | # generate x86_64 119 | if libc_x64_funcdef_fpath in libc_funcdef_list and not generate_json_file( 120 | function_dict, 121 | ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"], 122 | libc_x64_funcdef_fpath, 123 | ): 124 | logging.error( 125 | f"An error occurred while generating {libc_x64_funcdef_fpath}" 126 | ) 127 | 128 | # generate x86_32 129 | if libc_x86_funcdef_fpath in libc_funcdef_list and not generate_json_file( 130 | function_dict, 131 | [ 132 | "[sp + 0x0]", 133 | "[sp + 0x4]", 134 | "[sp + 0x8]", 135 | "[sp + 0xc]", 136 | "[sp + 0x10]", 137 | "[sp + 0x14]", 138 | ], 139 | libc_x86_funcdef_fpath, 140 | ): 141 | logging.error( 142 | f"An error occurred while generating {libc_x86_funcdef_fpath}" 143 | ) 144 | 145 | return True 146 | -------------------------------------------------------------------------------- /scripts/bytearray.py: -------------------------------------------------------------------------------- 1 | # 2 | # Generate a bytearray to be compared with possible badchars. 3 | # 4 | # @helviojunior 5 | # https://www.helviojunior.com.br/ 6 | # 7 | # Load with: 8 | # gef> source /path/to/bytearray.py 9 | # 10 | # Use with 11 | # gef> bytearray -b "\x00\x0a\x0d" 12 | # 13 | 14 | import binascii 15 | import getopt 16 | import re 17 | 18 | import gdb 19 | 20 | 21 | @register 22 | class BytearrayCommand(GenericCommand): 23 | """BytearrayCommand: Generate a bytearray to be compared with possible badchars. 24 | Function ported from mona.py""" 25 | 26 | _cmdline_ = "bytearray" 27 | _syntax_ = "{:s} [-b badchars]".format(_cmdline_) 28 | 29 | def __init__(self): 30 | super(BytearrayCommand, self).__init__(complete=gdb.COMPLETE_FILENAME) 31 | return 32 | 33 | def usage(self): 34 | h = self._syntax_ 35 | h += "\n\t-b badchars specifies the excluded badchars.\n" 36 | info(h) 37 | return 38 | 39 | def do_invoke(self, argv): 40 | badchars = "" 41 | bytesperline = 32 42 | startval = 0 43 | endval = 255 44 | 45 | opts, args = getopt.getopt(argv, "b:ch") 46 | for o, a in opts: 47 | if o == "-b": 48 | badchars = a 49 | elif o == "-h": 50 | self.usage() 51 | return 52 | 53 | badchars = self.cleanHex(badchars) 54 | 55 | # see if we need to expand .. 56 | bpos = 0 57 | newbadchars = "" 58 | while bpos < len(badchars): 59 | curchar = badchars[bpos] + badchars[bpos + 1] 60 | if curchar == "..": 61 | pos = bpos 62 | if pos > 1 and pos <= len(badchars) - 4: 63 | # get byte before and after .. 64 | bytebefore = badchars[pos - 2] + badchars[pos - 1] 65 | byteafter = badchars[pos + 2] + badchars[pos + 3] 66 | bbefore = int(bytebefore, 16) 67 | bafter = int(byteafter, 16) 68 | insertbytes = "" 69 | bbefore += 1 70 | while bbefore < bafter: 71 | insertbytes += "%02x" % bbefore 72 | bbefore += 1 73 | newbadchars += insertbytes 74 | else: 75 | newbadchars += curchar 76 | bpos += 2 77 | badchars = newbadchars 78 | 79 | cnt = 0 80 | excluded = [] 81 | while cnt < len(badchars): 82 | excluded.append(self.hex2bin(badchars[cnt] + badchars[cnt + 1])) 83 | cnt = cnt + 2 84 | 85 | info("Generating table, excluding {:d} bad chars...".format(len(excluded))) 86 | arraytable = [] 87 | binarray = bytearray() 88 | 89 | # handle range() last value 90 | if endval > startval: 91 | increment = 1 92 | endval += 1 93 | else: 94 | endval += -1 95 | increment = -1 96 | 97 | # create bytearray 98 | for thisval in range(startval, endval, increment): 99 | hexbyte = "{:02x}".format(thisval) 100 | binbyte = self.hex2bin(hexbyte) 101 | intbyte = self.hex2int(hexbyte) 102 | if binbyte not in excluded: 103 | arraytable.append(hexbyte) 104 | binarray.append(intbyte) 105 | 106 | info("Dumping table to file") 107 | output = "" 108 | cnt = 0 109 | outputline = '"' 110 | totalbytes = len(arraytable) 111 | tablecnt = 0 112 | while tablecnt < totalbytes: 113 | if cnt < bytesperline: 114 | outputline += "\\x" + arraytable[tablecnt] 115 | else: 116 | outputline += '"\n' 117 | cnt = 0 118 | output += outputline 119 | outputline = '"\\x' + arraytable[tablecnt] 120 | tablecnt += 1 121 | cnt += 1 122 | if (cnt - 1) < bytesperline: 123 | outputline += '"\n' 124 | output += outputline 125 | 126 | gef_print(output) 127 | 128 | binfilename = "bytearray.bin" 129 | arrayfile = "bytearray.txt" 130 | binfile = open(binfilename, "wb") 131 | binfile.write(binarray) 132 | binfile.close() 133 | 134 | txtfile = open(arrayfile, "w+") 135 | txtfile.write(output) 136 | txtfile.close() 137 | 138 | info("Done, wrote {:d} bytes to file {:s}".format(len(arraytable), arrayfile)) 139 | info("Binary output saved in {:s}".format(binfilename)) 140 | 141 | return 142 | 143 | def hex2bin(self, pattern): 144 | """ 145 | Converts a hex string (\\x??\\x??\\x??\\x??) to real hex bytes 146 | 147 | Arguments: 148 | pattern - A string representing the bytes to convert 149 | 150 | Return: 151 | the bytes 152 | """ 153 | pattern = pattern.replace("\\x", "") 154 | pattern = pattern.replace('"', "") 155 | pattern = pattern.replace("'", "") 156 | return binascii.unhexlify(pattern) 157 | 158 | def cleanHex(self, hex): 159 | return "".join(filter(self.permitted_char, hex)) 160 | 161 | def hex2int(self, hex): 162 | return int(hex, 16) 163 | 164 | def permitted_char(self, s): 165 | if bool(re.match("^[A-Fa-f0-9]", s)): 166 | return True 167 | else: 168 | return False 169 | -------------------------------------------------------------------------------- /scripts/syscall_args/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | If the $PC register is on a syscall, this command will display the arguments to that syscall from the known syscall definition. 3 | """ 4 | 5 | __AUTHOR__ = "daniellimws" 6 | __VERSION__ = 0.1 7 | __LICENSE__ = "MIT" 8 | 9 | import inspect 10 | import pathlib 11 | import re 12 | from importlib.machinery import SourceFileLoader 13 | from typing import TYPE_CHECKING, Any, Dict, List, Optional 14 | 15 | if TYPE_CHECKING: 16 | from .. import * 17 | 18 | CURRENT_FILE = pathlib.Path(inspect.getfile(inspect.currentframe())).resolve() 19 | CURRENT_DIRECTORY = pathlib.Path(inspect.getfile(inspect.currentframe())).parent.resolve() 20 | CONTEXT_PANE_INDEX = "syscall_args" 21 | CONTEXT_PANE_DESCRIPTION = "Syscall Arguments" 22 | 23 | 24 | @register 25 | class IsSyscallCommand(GenericCommand): 26 | """Tells whether the next instruction is a system call.""" 27 | _cmdline_ = "is-syscall" 28 | _syntax_ = _cmdline_ 29 | 30 | @only_if_gdb_running 31 | def do_invoke(self, _: List[str]) -> None: 32 | ok(f"Current instruction is{' ' if is_syscall(gef.arch.pc) else ' not '}a syscall") 33 | return 34 | 35 | 36 | @register 37 | class SyscallArgsCommand(GenericCommand): 38 | """Gets the syscall name and arguments based on the register values in the current state.""" 39 | 40 | _cmdline_ = "syscall-args" 41 | _syntax_ = f"{_cmdline_:s}" 42 | _example_ = f"{_cmdline_:s}" 43 | 44 | def __init__(self) -> None: 45 | super().__init__(prefix=False, complete=gdb.COMPLETE_NONE) 46 | self.__path: Optional[pathlib.Path] = None 47 | path = CURRENT_DIRECTORY / "syscall-tables" 48 | self["path"] = (str(path), 49 | "Path to store/load the syscall tables files") 50 | return 51 | 52 | @property 53 | def path(self) -> pathlib.Path: 54 | if not self.__path: 55 | path = pathlib.Path(self["path"]).expanduser() 56 | if not path.is_dir(): 57 | raise FileNotFoundError( 58 | f"'{self.__path}' is not valid directory") 59 | self.__path = path 60 | return self.__path 61 | 62 | @only_if_gdb_running 63 | def do_invoke(self, _: List[str]) -> None: 64 | syscall_register = gef.arch.syscall_register 65 | if not syscall_register: 66 | err( 67 | f"System call register not defined for architecture {gef.arch.arch}") 68 | return 69 | 70 | color = gef.config["theme.table_heading"] 71 | arch = gef.arch.__class__.__name__ 72 | syscall_table = self.__get_syscall_table(arch) 73 | 74 | if is_syscall(gef.arch.pc): 75 | # if $pc is before the `syscall` instruction is executed: 76 | reg_value = gef.arch.register(syscall_register) 77 | else: 78 | # otherwise, try the previous instruction (case of using `catch syscall`) 79 | previous_insn_addr = gdb_get_nth_previous_instruction_address( 80 | gef.arch.pc, 1) 81 | if not previous_insn_addr or not is_syscall(previous_insn_addr): 82 | return 83 | reg_value = gef.arch.register( 84 | f"$orig_{syscall_register.lstrip('$')}") 85 | 86 | if reg_value not in syscall_table: 87 | warn(f"There is no system call for {reg_value:#x}") 88 | return 89 | syscall_entry = syscall_table[reg_value] 90 | 91 | values = [gef.arch.register(param.reg) 92 | for param in syscall_entry.params] 93 | parameters = [s.param for s in syscall_entry.params] 94 | registers = [s.reg for s in syscall_entry.params] 95 | 96 | info(f"Detected syscall {Color.colorify(syscall_entry.name, color)}") 97 | gef_print(f" {syscall_entry.name}({', '.join(parameters)})") 98 | 99 | headers = ["Parameter", "Register", "Value"] 100 | param_names = [re.split(r" |\*", p)[-1] for p in parameters] 101 | info(Color.colorify("{:<20} {:<20} {}".format(*headers), color)) 102 | for name, register, value in zip(param_names, registers, values): 103 | line = f" {name:<20} {register:<20} {value:#x}" 104 | addrs = dereference_from(value) 105 | if len(addrs) > 1: 106 | line += RIGHT_ARROW + RIGHT_ARROW.join(addrs[1:]) 107 | gef_print(line) 108 | return 109 | 110 | def __get_syscall_table(self, modname: str) -> Dict[str, Any]: 111 | def load_module(modname: str) -> Any: 112 | _fpath = self.path / f"{modname}.py" 113 | if not _fpath.is_file(): 114 | raise FileNotFoundError 115 | _fullname = str(_fpath.absolute()) 116 | return SourceFileLoader(modname, _fullname).load_module(None) 117 | 118 | _mod = load_module(modname) 119 | return getattr(_mod, "syscall_table") 120 | 121 | 122 | def __syscall_args_pane_condition() -> bool: 123 | insn = gef_current_instruction(gef.arch.pc) 124 | return is_syscall(insn) 125 | 126 | 127 | def __syscall_args_pane_content() -> None: 128 | gdb.execute("syscall-args") 129 | return 130 | 131 | 132 | def __syscall_args_pane_title() -> str: 133 | return CONTEXT_PANE_DESCRIPTION 134 | 135 | if CONTEXT_PANE_INDEX in gef.config["context.layout"]: 136 | # 137 | # Register a callback to `syscall-args` to automatically detect when a syscall is hit 138 | # 139 | register_external_context_layout_mapping( 140 | CONTEXT_PANE_INDEX, __syscall_args_pane_content, pane_title_function=__syscall_args_pane_title, condition=__syscall_args_pane_condition) 141 | -------------------------------------------------------------------------------- /scripts/libc_function_args/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Conditional pane that will be shown only when a call is hit: it will try to collect the 3 | libc parameter names of the function, and display them 4 | """ 5 | 6 | __AUTHOR__ = "daniellimws" 7 | __VERSION__ = 0.1 8 | __LICENSE__ = "MIT" 9 | 10 | import inspect 11 | import json 12 | import pathlib 13 | import re 14 | from typing import TYPE_CHECKING, Dict 15 | 16 | if TYPE_CHECKING: 17 | from .. import * 18 | 19 | 20 | GLIBC_FUNCTION_ARGS_CURRENT_FILE = pathlib.Path(inspect.getfile(inspect.currentframe())).resolve() 21 | GLIBC_FUNCTION_ARGS_CURRENT_DIRECTORY = pathlib.Path(inspect.getfile(inspect.currentframe())).parent.resolve() 22 | GLIBC_FUNCTION_ARGS_CONTEXT_PANE_INDEX = "libc_function_args" 23 | GLIBC_FUNCTION_ARGS_CONTEXT_PANE_DESCRIPTION = "Glibc Function Arguments" 24 | 25 | 26 | class GlibcFunctionArguments: 27 | # 28 | # This table will be populate lazily 29 | # 30 | argument_table: Dict[str, Dict[str, Dict[str, str]]] = {} 31 | 32 | @staticmethod 33 | def load_libc_args() -> bool: 34 | """Load the LIBC function arguments. Returns `True` on success, `False` or an Exception otherwise.""" 35 | 36 | # load libc function arguments' definitions 37 | path = GLIBC_FUNCTION_ARGS_CURRENT_DIRECTORY 38 | if not path.exists(): 39 | raise RuntimeError( 40 | "Config `context.libc_args_path` set but it's not a directory" 41 | ) 42 | 43 | _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" 44 | _libc_args_file = path / f"tables/{_arch_mode}.json" 45 | 46 | if not _libc_args_file.exists(): 47 | # Try to generate the json table files 48 | from .tables.generator import generate_all_json_files 49 | 50 | if not generate_all_json_files(): 51 | raise RuntimeError("Failed to generate JSON table files") 52 | 53 | # current arch and mode already loaded 54 | if _arch_mode in GlibcFunctionArguments.argument_table: 55 | return True 56 | 57 | GlibcFunctionArguments.argument_table[_arch_mode] = {} 58 | try: 59 | with _libc_args_file.open() as _libc_args: 60 | GlibcFunctionArguments.argument_table[_arch_mode] = json.load( 61 | _libc_args 62 | ) 63 | return True 64 | except FileNotFoundError: 65 | warn( 66 | f"Config context.libc_args is set but definition cannot be loaded: file {_libc_args_file} not found" 67 | ) 68 | except json.decoder.JSONDecodeError as e: 69 | warn( 70 | f"Config context.libc_args is set but definition cannot be loaded from file {_libc_args_file}: {e}" 71 | ) 72 | GlibcFunctionArguments.argument_table[_arch_mode] = {} 73 | return False 74 | 75 | @staticmethod 76 | def only_if_call() -> bool: 77 | insn = gef_current_instruction(gef.arch.pc) 78 | return gef.arch.is_call(insn) 79 | 80 | @staticmethod 81 | def pane_title() -> str: 82 | return GLIBC_FUNCTION_ARGS_CONTEXT_PANE_DESCRIPTION 83 | 84 | @staticmethod 85 | def pane_content() -> None: 86 | function_name = GlibcFunctionArguments.extract_called_function_name() 87 | 88 | if not GlibcFunctionArguments.argument_table: 89 | # 90 | # The table has been populated, do it now 91 | # 92 | GlibcFunctionArguments.load_libc_args() 93 | 94 | nb_argument = None 95 | _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" 96 | function_basename = None 97 | if not function_name.endswith("@plt") or function_name.endswith("@got.plt"): 98 | return 99 | function_basename = function_name.split("@")[0] 100 | nb_argument = len( 101 | GlibcFunctionArguments.argument_table[_arch_mode][function_basename] 102 | ) 103 | 104 | args = [] 105 | for i in range(nb_argument): 106 | _key, _values = gef.arch.get_ith_parameter(i, in_func=False) 107 | if not _values: 108 | args.append(f"{_key}: ") 109 | continue 110 | _values = RIGHT_ARROW.join(dereference_from(_values)) 111 | args.append( 112 | f"\t{_key} = {_values} /* {GlibcFunctionArguments.argument_table[_arch_mode][function_basename][_key]}) */" 113 | ) 114 | 115 | gef_print(f"{function_name} (\n", "\n".join(args), "\n)") 116 | return 117 | 118 | @staticmethod 119 | def extract_called_function_name() -> str: 120 | pc = gef.arch.pc 121 | insn = gef_current_instruction(pc) 122 | if not gef.arch.is_call(insn): 123 | raise RuntimeError("Not a call") 124 | 125 | size2type = { 126 | 1: "BYTE", 127 | 2: "WORD", 128 | 4: "DWORD", 129 | 8: "QWORD", 130 | } 131 | 132 | if insn.operands[-1].startswith(size2type[gef.arch.ptrsize] + " PTR"): 133 | function_name = "*" + insn.operands[-1].split()[-1] 134 | elif "$" + insn.operands[0] in gef.arch.all_registers: 135 | function_name = f"*{gef.arch.register('$' + insn.operands[0]):#x}" 136 | else: 137 | ops = " ".join(insn.operands) 138 | if "<" in ops and ">" in ops: 139 | function_name = re.sub(r".*<([^\(> ]*).*", r"\1", ops) 140 | else: 141 | function_name = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops) 142 | 143 | return function_name 144 | 145 | if GLIBC_FUNCTION_ARGS_CONTEXT_PANE_INDEX in gef.config["context.layout"]: 146 | # 147 | # Register the context pane 148 | # 149 | register_external_context_layout_mapping( 150 | GLIBC_FUNCTION_ARGS_CONTEXT_PANE_INDEX, 151 | GlibcFunctionArguments.pane_content, 152 | pane_title_function=GlibcFunctionArguments.pane_title, 153 | condition=GlibcFunctionArguments.only_if_call, 154 | ) 155 | -------------------------------------------------------------------------------- /scripts/retdec.py: -------------------------------------------------------------------------------- 1 | __AUTHOR__ = "Minato-TW" 2 | __VERSION__ = 0.1 3 | 4 | import getopt 5 | import subprocess 6 | import gdb 7 | import os 8 | import re 9 | import tempfile 10 | from pygments import highlight 11 | from pygments.lexers import CLexer 12 | from pygments.formatters import Terminal256Formatter 13 | 14 | @register 15 | class RetDecCommand(GenericCommand): 16 | """Decompile code from GDB context using RetDec API.""" 17 | 18 | _cmdline_ = "retdec" 19 | _syntax_ = "{:s} [-r RANGE1-RANGE2] [-s SYMBOL] [-a] [-h]".format(_cmdline_) 20 | _aliases_ = ["decompile"] 21 | _example_ = "{:s} -s main".format(_cmdline_) 22 | 23 | def __init__(self): 24 | super(RetDecCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL) 25 | self["path"] = (GEF_TEMP_DIR, "Path to store the decompiled code") 26 | self["retdec_path"] = ("", "Path to the retdec installation") 27 | self["theme"] = ("default", "Theme for pygments syntax highlighting") 28 | return 29 | 30 | @only_if_gdb_running 31 | def do_invoke(self, argv): 32 | arch = gef.arch.arch.lower() 33 | if not arch: 34 | err("RetDec does not decompile '{:s}'".format(get_arch())) 35 | return 36 | 37 | retdec_path = self["retdec_path"].strip() 38 | if not retdec_path: 39 | msg = "Path to retdec installation not provided, use `gef config` to set the path" 40 | err(msg) 41 | return 42 | 43 | retdec_decompiler = "{}/bin/retdec-decompiler.py".format(retdec_path) 44 | if not os.path.exists(retdec_decompiler): 45 | msg = "Retdec decompiler not found! Verify your installation" 46 | err(msg) 47 | return 48 | 49 | params = { 50 | "architecture": arch, 51 | "target_language": "c", 52 | "raw_endian": "big" if is_big_endian() else "little", 53 | } 54 | 55 | opts = getopt.getopt(argv, "r:s:ah")[0] 56 | if not opts: 57 | self.usage() 58 | return 59 | 60 | for opt, arg in opts: 61 | if opt == "-r": 62 | range_from, range_to = map(lambda x: int(x, 16), arg.split("-", 1)) 63 | fd, filename = tempfile.mkstemp(dir=self["path"]) 64 | with os.fdopen(fd, "wb") as f: 65 | length = range_to - range_from 66 | f.write(read_memory(range_from, length)) 67 | params["mode"] = "raw" 68 | params["file_format"] = "elf" 69 | params["raw_section_vma"] = hex(range_from) 70 | params["raw_entry_point"] = hex(range_from) 71 | elif opt == "-s": 72 | try: 73 | value = gdb.parse_and_eval(arg) 74 | except gdb.error: 75 | err("No symbol named '{:s}'".format(arg)) 76 | return 77 | range_from = int(value.address) 78 | fd, filename = tempfile.mkstemp(dir=self["path"]) 79 | with os.fdopen(fd, "wb") as f: 80 | f.write(read_memory(range_from, get_function_length(arg))) 81 | params["mode"] = "raw" 82 | params["file_format"] = "elf" 83 | params["raw_section_vma"] = hex(range_from) 84 | params["raw_entry_point"] = hex(range_from) 85 | elif opt == "-a": 86 | filename = get_filepath() 87 | params["mode"] = "bin" 88 | else: 89 | self.usage() 90 | return 91 | 92 | # Set up variables 93 | path = self["path"] 94 | theme = self["theme"] 95 | params["input_file"] = filename 96 | fname = "{}/{}.{}".format(path, os.path.basename(params["input_file"]), params["target_language"]) 97 | logfile = "{}/{}.log".format(path, os.path.basename(params["input_file"])) 98 | if params["mode"] == "bin": 99 | cmd = [ 100 | retdec_decompiler, 101 | "-m", params["mode"], 102 | "-e", params["raw_endian"], 103 | "-f", "plain", 104 | "-a", params["architecture"], 105 | "-o", fname, 106 | "-l", params["target_language"], 107 | params["input_file"], 108 | "--cleanup" 109 | ] 110 | 111 | else: 112 | cmd = [ 113 | retdec_decompiler, 114 | "-m", params["mode"], 115 | "--raw-section-vma", params["raw_section_vma"], 116 | "--raw-entry-point", params["raw_entry_point"], 117 | "-e", params["raw_endian"], 118 | "-f", "plain", 119 | "-a", params["architecture"], 120 | "-o", fname, 121 | "-l", params["target_language"], 122 | params["input_file"], 123 | "--cleanup" 124 | ] 125 | if self.send_to_retdec(params, cmd, logfile) is False: 126 | return 127 | 128 | ok("Saved as '{:s}'".format(fname)) 129 | with open(fname, "r") as f: 130 | # only keep relevant parts of decompilation 131 | # trim first 6 lines of watermark, last 5 lines of metainfo 132 | lines = f.readlines()[6:-5] 133 | 134 | pattern = re.compile(r"unknown_([a-f0-9]+)") 135 | for line in lines: 136 | # try to name unknown functions based on current program context 137 | for match in pattern.finditer(line): 138 | s = match.group(1) 139 | pc = int(s, 16) 140 | insn = gef_current_instruction(pc) 141 | if insn.location: 142 | line = line.replace("unknown_{:s}".format(s), insn.location) 143 | gef_print(highlight(line, CLexer(), Terminal256Formatter(style=theme)), end="") 144 | return 145 | 146 | def send_to_retdec(self, params, cmd, logfile): 147 | try: 148 | with open(logfile, "wb") as log: 149 | subprocess.run(cmd, stdout=log) 150 | except Exception: 151 | msg = "Error encountered during decompilation. Check the log file at {}".format(logfile) 152 | err(msg) 153 | return False 154 | 155 | return True 156 | -------------------------------------------------------------------------------- /archs/arm-openocd.py: -------------------------------------------------------------------------------- 1 | """ 2 | ARM through OpenOCD support for GEF 3 | 4 | To use, source this file *after* gef 5 | 6 | Author: Grazfather 7 | """ 8 | 9 | from typing import Optional 10 | from pathlib import Path 11 | 12 | import gdb 13 | 14 | assert 'gef' in globals(), "This file must be source after gef.py" 15 | 16 | 17 | class ARMOpenOCD(ARM): 18 | arch = "ARMOpenOCD" 19 | aliases = ("ARMOpenOCD",) 20 | all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", 21 | "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", 22 | "$lr", "$pc", "$xPSR") 23 | flag_register = "$xPSR" 24 | @staticmethod 25 | def supports_gdb_arch(arch: str) -> Optional[bool]: 26 | if "arm" in arch and arch.endswith("-m"): 27 | return True 28 | return None 29 | 30 | @staticmethod 31 | def maps(): 32 | yield from GefMemoryManager.parse_info_mem() 33 | 34 | 35 | @register 36 | class OpenOCDRemoteCommand(GenericCommand): 37 | """This command is intended to replace `gef-remote` to connect to an 38 | OpenOCD-hosted gdbserver. It uses a special session manager that knows how 39 | to connect and manage the server.""" 40 | 41 | _cmdline_ = "gef-openocd-remote" 42 | _syntax_ = f"{_cmdline_} [OPTIONS] HOST PORT" 43 | _example_ = [f"{_cmdline_} --file /path/to/binary.elf localhost 3333", 44 | f"{_cmdline_} localhost 3333"] 45 | 46 | def __init__(self) -> None: 47 | super().__init__(prefix=False) 48 | return 49 | 50 | @parse_arguments({"host": "", "port": 0}, {"--file": ""}) 51 | def do_invoke(self, _: list[str], **kwargs: Any) -> None: 52 | if gef.session.remote is not None: 53 | err("You're already in a remote session. Close it first before opening a new one...") 54 | return 55 | 56 | # argument check 57 | args: argparse.Namespace = kwargs["arguments"] 58 | if not args.host or not args.port: 59 | err("Missing parameters") 60 | return 61 | 62 | # Try to establish the remote session, throw on error 63 | # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which 64 | # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None 65 | # This prevents some spurious errors being thrown during startup 66 | gef.session.remote_initializing = True 67 | session = GefOpenOCDRemoteSessionManager(args.host, args.port, args.file) 68 | 69 | dbg(f"[remote] initializing remote session with {session.target} under {session.root}") 70 | 71 | # Connect can return false if it wants us to disconnect 72 | if not session.connect(): 73 | gef.session.remote = None 74 | gef.session.remote_initializing = False 75 | return 76 | if not session.setup(): 77 | gef.session.remote = None 78 | gef.session.remote_initializing = False 79 | raise EnvironmentError("Failed to setup remote target") 80 | 81 | gef.session.remote_initializing = False 82 | gef.session.remote = session 83 | reset_all_caches() 84 | gdb.execute("context") 85 | return 86 | 87 | 88 | # We CANNOT use the normal session manager because it assumes we have a PID 89 | class GefOpenOCDRemoteSessionManager(GefRemoteSessionManager): 90 | """This subclass of GefRemoteSessionManager specially handles the 91 | intricacies involved with connecting to an OpenOCD-hosted GDB server. 92 | Specifically, it does not have the concept of PIDs which we need to work 93 | around.""" 94 | def __init__(self, host: str, port: str, file: str="") -> None: 95 | self.__host = host 96 | self.__port = port 97 | self.__file = file 98 | self.__local_root_fd = tempfile.TemporaryDirectory() 99 | self.__local_root_path = Path(self.__local_root_fd.name) 100 | class OpenOCDMode(): 101 | def prompt_string(self) -> str: 102 | return Color.boldify("(OpenOCD) ") 103 | 104 | self._mode = OpenOCDMode() 105 | 106 | def __str__(self) -> str: 107 | return f"OpenOCDRemoteSessionManager(='{self.__tty}', file='{self.__file}', attach={self.__attach})" 108 | 109 | def close(self) -> None: 110 | self.__local_root_fd.cleanup() 111 | try: 112 | gef_on_new_unhook(self.remote_objfile_event_handler) 113 | gef_on_new_hook(new_objfile_handler) 114 | except Exception as e: 115 | warn(f"Exception while restoring local context: {str(e)}") 116 | return 117 | 118 | @property 119 | def target(self) -> str: 120 | return f"{self.__host}:{self.__port}" 121 | 122 | @property 123 | def root(self) -> Path: 124 | return self.__local_root_path.absolute() 125 | 126 | def sync(self, src: str, dst: Optional[str] = None) -> bool: 127 | # We cannot sync from this target 128 | return None 129 | 130 | @property 131 | def file(self) -> Optional[Path]: 132 | if self.__file: 133 | return Path(self.__file).expanduser() 134 | return None 135 | 136 | def connect(self) -> bool: 137 | """Connect to remote target. If in extended mode, also attach to the given PID.""" 138 | # before anything, register our new hook to download files from the remote target 139 | dbg(f"[remote] Installing new objfile handlers") 140 | try: 141 | gef_on_new_unhook(new_objfile_handler) 142 | except SystemError: 143 | # the default objfile handler might already have been removed, ignore failure 144 | pass 145 | 146 | gef_on_new_hook(self.remote_objfile_event_handler) 147 | 148 | # Connect 149 | with DisableContextOutputContext(): 150 | self._gdb_execute(f"target extended-remote {self.target}") 151 | 152 | try: 153 | with DisableContextOutputContext(): 154 | if self.file: 155 | self._gdb_execute(f"file '{self.file}'") 156 | except Exception as e: 157 | err(f"Failed to connect to {self.target}: {e}") 158 | # a failure will trigger the cleanup, deleting our hook 159 | return False 160 | 161 | return True 162 | 163 | def setup(self) -> bool: 164 | dbg(f"Setting up as remote session") 165 | 166 | # refresh gef to consider the binary 167 | reset_all_caches() 168 | if self.file: 169 | gef.binary = Elf(self.file) 170 | # We'd like to set this earlier, but we can't because of this bug 171 | # https://sourceware.org/bugzilla/show_bug.cgi?id=31303 172 | reset_architecture("ARMOpenOCD") 173 | return True 174 | 175 | def _gdb_execute(self, cmd): 176 | dbg(f"[remote] Executing '{cmd}'") 177 | gdb.execute(cmd) 178 | -------------------------------------------------------------------------------- /docs/.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Rules: https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md 3 | # 4 | # Default state for all rules 5 | default: true 6 | 7 | # Path to configuration file to extend 8 | extends: null 9 | 10 | # MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time 11 | MD001: true 12 | 13 | # MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading 14 | MD002: 15 | # Heading level 16 | level: 2 17 | 18 | # MD003/heading-style/header-style - Heading style 19 | MD003: 20 | # Heading style 21 | style: "consistent" 22 | 23 | # MD004/ul-style - Unordered list style 24 | MD004: 25 | # List style 26 | style: "consistent" 27 | 28 | # MD005/list-indent - Inconsistent indentation for list items at the same level 29 | MD005: true 30 | 31 | # MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line 32 | MD006: true 33 | 34 | # MD007/ul-indent - Unordered list indentation 35 | MD007: 36 | # Spaces for indent 37 | indent: 2 38 | # Whether to indent the first level of the list 39 | start_indented: false 40 | # Spaces for first level indent (when start_indented is set) 41 | start_indent: 2 42 | 43 | # MD009/no-trailing-spaces - Trailing spaces 44 | MD009: 45 | # Spaces for line break 46 | br_spaces: 2 47 | # Allow spaces for empty lines in list items 48 | list_item_empty_lines: false 49 | # Include unnecessary breaks 50 | strict: false 51 | 52 | # MD010/no-hard-tabs - Hard tabs 53 | MD010: 54 | # Include code blocks 55 | code_blocks: false 56 | # Fenced code languages to ignore 57 | ignore_code_languages: [] 58 | # Number of spaces for each hard tab 59 | spaces_per_tab: 4 60 | 61 | # MD011/no-reversed-links - Reversed link syntax 62 | MD011: true 63 | 64 | # MD012/no-multiple-blanks - Multiple consecutive blank lines 65 | MD012: 66 | # Consecutive blank lines 67 | maximum: 2 68 | 69 | # MD013/line-length - Line length 70 | MD013: 71 | # Number of characters 72 | line_length: 100 73 | # Number of characters for headings 74 | heading_line_length: 100 75 | # Number of characters for code blocks 76 | code_block_line_length: 100 77 | # Include code blocks 78 | code_blocks: false 79 | # Include tables 80 | tables: false 81 | # Include headings 82 | headings: true 83 | # Include headings 84 | headers: true 85 | # Strict length checking 86 | strict: false 87 | # Stern length checking 88 | stern: false 89 | 90 | # MD014/commands-show-output - Dollar signs used before commands without showing output 91 | MD014: true 92 | 93 | # MD018/no-missing-space-atx - No space after hash on atx style heading 94 | MD018: true 95 | 96 | # MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading 97 | MD019: true 98 | 99 | # MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading 100 | MD020: true 101 | 102 | # MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading 103 | MD021: true 104 | 105 | # MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines 106 | MD022: 107 | # Blank lines above heading 108 | lines_above: 1 109 | # Blank lines below heading 110 | lines_below: 1 111 | 112 | # MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line 113 | MD023: true 114 | 115 | # MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content 116 | MD024: 117 | # Only check sibling headings 118 | allow_different_nesting: false 119 | # Only check sibling headings 120 | siblings_only: false 121 | 122 | # MD025/single-title/single-h1 - Multiple top-level headings in the same document 123 | MD025: 124 | # Heading level 125 | level: 1 126 | # RegExp for matching title in front matter 127 | front_matter_title: "^\\s*title\\s*[:=]" 128 | 129 | # MD026/no-trailing-punctuation - Trailing punctuation in heading 130 | MD026: 131 | # Punctuation characters not allowed at end of headings 132 | punctuation: ".,;:!。,;:!" 133 | 134 | # MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol 135 | MD027: true 136 | 137 | # MD028/no-blanks-blockquote - Blank line inside blockquote 138 | MD028: true 139 | 140 | # MD029/ol-prefix - Ordered list item prefix 141 | MD029: 142 | # List style 143 | style: "one_or_ordered" 144 | 145 | # MD030/list-marker-space - Spaces after list markers 146 | MD030: 147 | # Spaces for single-line unordered list items 148 | ul_single: 2 149 | # Spaces for single-line ordered list items 150 | ol_single: 2 151 | # Spaces for multi-line unordered list items 152 | ul_multi: 2 153 | # Spaces for multi-line ordered list items 154 | ol_multi: 2 155 | 156 | # MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines 157 | MD031: 158 | # Include list items 159 | list_items: true 160 | 161 | # MD032/blanks-around-lists - Lists should be surrounded by blank lines 162 | MD032: true 163 | 164 | # MD033/no-inline-html - Inline HTML 165 | MD033: 166 | # Allowed elements 167 | allowed_elements: ["img"] 168 | 169 | # MD034/no-bare-urls - Bare URL used 170 | MD034: true 171 | 172 | # MD035/hr-style - Horizontal rule style 173 | MD035: 174 | # Horizontal rule style 175 | style: "consistent" 176 | 177 | # MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading 178 | MD036: 179 | # Punctuation characters 180 | punctuation: ".,;:!?。,;:!?" 181 | 182 | # MD037/no-space-in-emphasis - Spaces inside emphasis markers 183 | MD037: true 184 | 185 | # MD038/no-space-in-code - Spaces inside code span elements 186 | MD038: true 187 | 188 | # MD039/no-space-in-links - Spaces inside link text 189 | MD039: true 190 | 191 | # MD040/fenced-code-language - Fenced code blocks should have a language specified 192 | MD040: 193 | # List of languages 194 | allowed_languages: [] 195 | # Require language only 196 | language_only: false 197 | 198 | # MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading 199 | MD041: 200 | # Heading level 201 | level: 2 202 | # RegExp for matching title in front matter 203 | front_matter_title: "^\\s*title\\s*[:=]" 204 | 205 | # MD042/no-empty-links - No empty links 206 | MD042: true 207 | 208 | # MD043/required-headings/required-headers - Required heading structure 209 | MD043: false 210 | 211 | # MD044/proper-names - Proper names should have the correct capitalization 212 | MD044: 213 | # List of proper names 214 | names: [] 215 | # Include code blocks 216 | code_blocks: false 217 | # Include HTML elements 218 | html_elements: false 219 | 220 | # MD045/no-alt-text - Images should have alternate text (alt text) 221 | MD045: true 222 | 223 | # MD046/code-block-style - Code block style 224 | MD046: 225 | # Block style 226 | style: "consistent" 227 | 228 | # MD047/single-trailing-newline - Files should end with a single newline character 229 | MD047: true 230 | 231 | # MD048/code-fence-style - Code fence style 232 | MD048: 233 | # Code fence style 234 | style: "consistent" 235 | 236 | # MD049/emphasis-style - Emphasis style should be consistent 237 | MD049: 238 | # Emphasis style should be consistent 239 | style: "consistent" 240 | 241 | # MD050/strong-style - Strong style should be consistent 242 | MD050: 243 | # Strong style should be consistent 244 | style: "consistent" 245 | 246 | # MD051/link-fragments - Link fragments should be valid 247 | MD051: true 248 | 249 | # MD052/reference-links-images - Reference links and images should use a label that is defined 250 | MD052: true 251 | 252 | # MD053/link-image-reference-definitions - Link and image reference definitions should be needed 253 | MD053: 254 | # Ignored definitions 255 | ignored_definitions: 256 | - "//" 257 | -------------------------------------------------------------------------------- /docs/commands/bincompare.md: -------------------------------------------------------------------------------- 1 | ## Command bincompare 2 | 3 | The `bincompare` command will compare a provided binary file with process memory in order to find 4 | differences between the two. 5 | 6 | `bincompare` requires args: 7 | 8 | * `-f` (for `file`) - the full path of binary file to be compared. 9 | * `-a` (for `address`) - the memory address to be compared with the file data. 10 | 11 | You can use the `bytearray` command to generate the binary file. 12 | 13 | Example without badchars: 14 | 15 | ```text 16 | gef➤ bincompare -f bytearray.bin -a 0x56557008 17 | [+] Comparison result: 18 | +-----------------------------------------------+ 19 | 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file 20 | | | memory 21 | 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file 22 | | | memory 23 | 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file 24 | | | memory 25 | 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file 26 | | | memory 27 | 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file 28 | | | memory 29 | 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file 30 | | | memory 31 | 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file 32 | | | memory 33 | 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file 34 | | | memory 35 | 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file 36 | | | memory 37 | 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file 38 | | | memory 39 | a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file 40 | | | memory 41 | b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file 42 | | | memory 43 | c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file 44 | | | memory 45 | d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file 46 | | | memory 47 | e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file 48 | | | memory 49 | f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file 50 | | | memory 51 | +-----------------------------------------------+ 52 | [+] No badchars found! 53 | ``` 54 | 55 | Example with badchars and no truncateed buffer: 56 | 57 | ```text 58 | gef➤ bincompare -f bytearray.bin -a 0x56557008 59 | [+] Comparison result: 60 | +-----------------------------------------------+ 61 | 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file 62 | | 10 | memory 63 | 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file 64 | | 10| memory 65 | 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file 66 | | | memory 67 | 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file 68 | | 2f| memory 69 | 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file 70 | | | memory 71 | 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file 72 | | | memory 73 | 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file 74 | | | memory 75 | 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file 76 | | | memory 77 | 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file 78 | | | memory 79 | 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file 80 | | | memory 81 | a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file 82 | | | memory 83 | b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file 84 | | | memory 85 | c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file 86 | | | memory 87 | d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file 88 | | | memory 89 | e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file 90 | | | memory 91 | f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file 92 | | | memory 93 | +-----------------------------------------------+ 94 | [+] Badchars found: 05, 1f, 3f 95 | ``` 96 | 97 | Example with badchars and truncated buffer: 98 | 99 | ```text 100 | gef➤ bincompare -f bytearray.bin -a 0x56557008 101 | [+] Comparison result: 102 | +-----------------------------------------------+ 103 | 00 |00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f| file 104 | | 10 | memory 105 | 10 |10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f| file 106 | | 10| memory 107 | 20 |20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f| file 108 | | | memory 109 | 30 |30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f| file 110 | | 2f| memory 111 | 40 |40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f| file 112 | | 00 00 01 1b 03 3b 38 00 00 00 06 00 00 00| memory 113 | 50 |50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f| file 114 | |d4 ef ff ff 80 00 00 00 f4 ef ff ff a4 00 00 00| memory 115 | 60 |60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f| file 116 | |04 f0 ff ff 54 00 00 00 74 f1 ff ff b8 00 00 00| memory 117 | 70 |70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f| file 118 | |d4 f1 ff ff 04 01 00 00 d5 f1 ff ff 18 01 00 00| memory 119 | 80 |80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f| file 120 | |14 00 00 00 00 00 00 00 01 7a 52 00 01 7c 08 01| memory 121 | 90 |90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f| file 122 | |1b 0c 04 04 88 01 07 08 10 00 00 00 1c 00 00 00| memory 123 | a0 |a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af| file 124 | |a8 ef ff ff 36 00 00 00 00 00 00 00 14 00 00 00| memory 125 | b0 |b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf| file 126 | |00 00 00 00 01 7a 52 00 01 7c 08 01 1b 0c 04 04| memory 127 | c0 |c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf| file 128 | |88 01 00 00 20 00 00 00 1c 00 00 00 4c ef ff ff| memory 129 | d0 |d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df| file 130 | |20 00 00 00 00 0e 08 46 0e 0c 4a 0f 0b 74 04 78| memory 131 | e0 |e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef| file 132 | |00 3f 1a 3b 2a 32 24 22 10 00 00 00 40 00 00 00| memory 133 | f0 |f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff| file 134 | |48 ef ff ff 08 00 00 00 00 00 00 00 48 00 00 00| memory 135 | +-----------------------------------------------+ 136 | [+] Corruption after 66 bytes 137 | [+] Badchars found: 05, 1f, 3f, 42, 43, 44, 45, 46, 47, 48, 49, 4a, 4b, 4c, 4d, 4e, 4f, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 5a, 5b, 5c, 5d, 5e, 5f, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 6a, 6b, 6c, 6d, 6e, 6f, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 7a, 7b, 7c, 7d, 7e, 7f, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 8a, 8b, 8c, 8d, 8e, 8f, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 9a, 9b, 9c, 9d, 9e, 9f, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df, e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef, f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, fa, fb, fc, fd, fe, ff 138 | ``` 139 | -------------------------------------------------------------------------------- /archs/arm-blackmagicprobe.py: -------------------------------------------------------------------------------- 1 | """ 2 | ARM through the Black Magic Probe support for GEF 3 | 4 | To use, source this file *after* gef 5 | 6 | Author: Grazfather 7 | """ 8 | 9 | from typing import Optional 10 | 11 | import gdb 12 | 13 | assert 'gef' in globals(), "This file must be source after gef.py" 14 | 15 | 16 | class ARMBlackMagicProbe(ARM): 17 | arch = "ARMBlackMagicProbe" 18 | aliases = ("ARMBlackMagicProbe",) 19 | all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", 20 | "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", 21 | "$lr", "$pc", "$xpsr") 22 | flag_register = "$xpsr" 23 | @staticmethod 24 | def supports_gdb_arch(arch: str) -> Optional[bool]: 25 | if "arm" in arch and arch.endswith("-m"): 26 | return True 27 | return None 28 | 29 | @staticmethod 30 | def maps(): 31 | yield from GefMemoryManager.parse_info_mem() 32 | 33 | 34 | @register 35 | class BMPRemoteCommand(GenericCommand): 36 | """This command is intended to replace `gef-remote` to connect to a 37 | BlackMagicProbe. It uses a special session manager that knows how to 38 | connect and manage the server running over a tty.""" 39 | 40 | _cmdline_ = "gef-bmp-remote" 41 | _syntax_ = f"{_cmdline_} [OPTIONS] TTY" 42 | _example_ = [f"{_cmdline_} --scan /dev/ttyUSB1", 43 | f"{_cmdline_} --scan /dev/ttyUSB1 --power", 44 | f"{_cmdline_} --scan /dev/ttyUSB1 --power --keep-power", 45 | f"{_cmdline_} --file /path/to/binary.elf --attach 1 /dev/ttyUSB1", 46 | f"{_cmdline_} --file /path/to/binary.elf --attach 1 --power /dev/ttyUSB1"] 47 | 48 | def __init__(self) -> None: 49 | super().__init__(prefix=False) 50 | return 51 | 52 | @parse_arguments({"tty": ""}, {"--file": "", "--attach": "", "--power": False, 53 | "--keep-power": False, "--scan": False}) 54 | def do_invoke(self, _: list[str], **kwargs: Any) -> None: 55 | if gef.session.remote is not None: 56 | err("You're already in a remote session. Close it first before opening a new one...") 57 | return 58 | 59 | # argument check 60 | args: argparse.Namespace = kwargs["arguments"] 61 | if not args.tty: 62 | err("Missing parameters") 63 | return 64 | 65 | if not args.scan and not args.attach: 66 | err("Must provide target to attach to if not scanning") 67 | return 68 | 69 | # Try to establish the remote session, throw on error 70 | # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which 71 | # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None 72 | # This prevents some spurious errors being thrown during startup 73 | gef.session.remote_initializing = True 74 | session = GefBMPRemoteSessionManager(args.tty, args.file, args.attach, args.scan, args.power) 75 | 76 | dbg(f"[remote] initializing remote session with {session.target} under {session.root}") 77 | 78 | # Connect can return false if it wants us to disconnect 79 | if not session.connect(): 80 | gef.session.remote = None 81 | gef.session.remote_initializing = False 82 | return 83 | if not session.setup(): 84 | gef.session.remote = None 85 | gef.session.remote_initializing = False 86 | raise EnvironmentError("Failed to setup remote target") 87 | 88 | gef.session.remote_initializing = False 89 | gef.session.remote = session 90 | reset_all_caches() 91 | gdb.execute("context") 92 | return 93 | 94 | 95 | class GefBMPRemoteSessionManager(GefRemoteSessionManager): 96 | """This subclass of GefRemoteSessionManager specially handles the 97 | intricacies involved with connecting to a BlackMagicProbe.""" 98 | def __init__(self, tty: str="", file: str="", attach: int=1, 99 | scan: bool=False, power: bool=False, keep_power: bool=False) -> None: 100 | self.__tty = tty 101 | self.__file = file 102 | self.__attach = attach 103 | self.__scan = scan 104 | self.__power = power 105 | self.__keep_power = keep_power 106 | self.__local_root_fd = tempfile.TemporaryDirectory() 107 | self.__local_root_path = Path(self.__local_root_fd.name) 108 | class BMPMode(): 109 | def prompt_string(self) -> str: 110 | return Color.boldify("(BMP) ") 111 | 112 | self._mode = BMPMode() 113 | 114 | def __str__(self) -> str: 115 | return f"BMPRemoteSessionManager(tty='{self.__tty}', file='{self.__file}', attach={self.__attach})" 116 | 117 | def close(self) -> None: 118 | self.__local_root_fd.cleanup() 119 | try: 120 | gef_on_new_unhook(self.remote_objfile_event_handler) 121 | gef_on_new_hook(new_objfile_handler) 122 | if self.__power and not self.__keep_power: 123 | self._power_off() 124 | except Exception as e: 125 | warn(f"Exception while restoring local context: {str(e)}") 126 | return 127 | 128 | @property 129 | def root(self) -> Path: 130 | return self.__local_root_path.absolute() 131 | 132 | @property 133 | def target(self) -> str: 134 | return f"{self.__tty} (attach {self.__attach})" 135 | 136 | def sync(self, src: str, dst: Optional[str] = None) -> bool: 137 | # We cannot sync from this target 138 | return None 139 | 140 | @property 141 | def file(self) -> Optional[Path]: 142 | if self.__file: 143 | return Path(self.__file).expanduser() 144 | return None 145 | 146 | def connect(self) -> bool: 147 | """Connect to remote target. If in extended mode, also attach to the given PID.""" 148 | # before anything, register our new hook to download files from the remote target 149 | dbg(f"[remote] Installing new objfile handlers") 150 | try: 151 | gef_on_new_unhook(new_objfile_handler) 152 | except SystemError: 153 | # the default objfile handler might already have been removed, ignore failure 154 | pass 155 | 156 | gef_on_new_hook(self.remote_objfile_event_handler) 157 | 158 | # Connect 159 | with DisableContextOutputContext(): 160 | self._gdb_execute(f"target extended-remote {self.__tty}") 161 | 162 | # Optionally enable target-powering 163 | if self.__power: 164 | self._power_on() 165 | 166 | # We must always scan, but with --scan we are done here 167 | self._gdb_execute("monitor swdp_scan") 168 | if self.__scan: 169 | self._gdb_execute("disconnect") 170 | 171 | # Returning false cleans up the session 172 | return False 173 | 174 | try: 175 | with DisableContextOutputContext(): 176 | if self.file: 177 | self._gdb_execute(f"file {self.file}") 178 | self._gdb_execute(f"attach {self.__attach or 1}") 179 | except Exception as e: 180 | err(f"Failed to connect to {self.target}: {e}") 181 | # a failure will trigger the cleanup, deleting our hook 182 | return False 183 | 184 | return True 185 | 186 | def setup(self) -> bool: 187 | dbg(f"Setting up as remote session") 188 | 189 | # refresh gef to consider the binary 190 | reset_all_caches() 191 | if self.file: 192 | gef.binary = Elf(self.file) 193 | # We'd like to set this earlier, but we can't because of this bug 194 | # https://sourceware.org/bugzilla/show_bug.cgi?id=31303 195 | reset_architecture("ARMBlackMagicProbe") 196 | return True 197 | 198 | def _power_off(self): 199 | self._gdb_execute("monitor tpwr disable") 200 | 201 | def _power_on(self): 202 | self._gdb_execute("monitor tpwr enable") 203 | 204 | def _gdb_execute(self, cmd): 205 | dbg(f"[remote] Executing '{cmd}'") 206 | gdb.execute(cmd) 207 | -------------------------------------------------------------------------------- /os/pe.py: -------------------------------------------------------------------------------- 1 | """ 2 | PE format support 3 | 4 | To use: 5 | 6 | ```text 7 | gef➤ source /path/to/gef-extras/os/pe.py 8 | gef➤ pi gef.binary = PE(get_filepath()) 9 | gef➤ pi reset_architecture() 10 | ``` 11 | """ 12 | 13 | import enum 14 | import os 15 | import pathlib 16 | import struct 17 | 18 | from functools import lru_cache 19 | from typing import Dict, Tuple, Any, Optional 20 | 21 | 22 | class PE: 23 | """Basic PE parsing. 24 | Ref: 25 | - https://hshrzd.wordpress.com/pe-bear/ 26 | - https://blog.kowalczyk.info/articles/pefileformat.html 27 | """ 28 | 29 | class Constants(enum.IntEnum): 30 | DOS_MAGIC = 0x4D5A 31 | NT_MAGIC = 0x4550 32 | 33 | class MachineType(enum.IntEnum): 34 | X86_64 = 0x8664 35 | X86_32 = 0x14C 36 | ARM = 0x1C0 37 | ARM64 = 0xAA64 38 | 39 | class DosHeader: 40 | e_magic: int 41 | e_cblp: int 42 | e_cp: int 43 | e_crlc: int 44 | e_cparhdr: int 45 | e_minalloc: int 46 | e_maxalloc: int 47 | e_ss: int 48 | e_sp: int 49 | e_csum: int 50 | e_ip: int 51 | e_cs: int 52 | e_lfarlc: int 53 | e_ovno: int 54 | e_res: bytes 55 | e_oemid: int 56 | e_oeminfo: int 57 | e_res2: bytes 58 | e_lfanew: int 59 | 60 | class ImageFileHeader: 61 | Machine: "PE.MachineType" 62 | NumberOfSections: int 63 | TimeDateStamp: int 64 | PointerToSymbolTable: int 65 | NumberOfSymbols: int 66 | SizeOfOptionalHeader: int 67 | Characteristics: "PE.DllCharacteristics" 68 | 69 | class OptionalHeader: 70 | Magic: int 71 | MajorLinkerVersion: int 72 | MinorLinkerVersion: int 73 | SizeOfCode: int 74 | SizeOfInitializedData: int 75 | SizeOfUninitializedData: int 76 | AddressOfEntryPoint: int 77 | BaseOfCode: int 78 | BaseOfData: int 79 | ImageBase: int 80 | SectionAlignment: int 81 | FileAlignment: int 82 | MajorOperatingSystemVersion: int 83 | MinorOperatingSystemVersion: int 84 | MajorImageVersion: int 85 | MinorImageVersion: int 86 | MajorSubsystemVersion: int 87 | MinorSubsystemVersion: int 88 | Reserved1: int 89 | SizeOfImage: int 90 | SizeOfHeaders: int 91 | CheckSum: int 92 | Subsystem: int 93 | DllCharacteristics: int 94 | SizeOfStackReserve: int 95 | SizeOfStackCommit: int 96 | SizeOfHeapReserve: int 97 | SizeOfHeapCommit: int 98 | LoaderFlags: int 99 | NumberOfRvaAndSizes: int 100 | DataDirectory: Tuple["PE.DataDirectory"] 101 | 102 | class DataDirectory: 103 | VirtualAddress: int 104 | Size: int 105 | 106 | class DllCharacteristics(enum.IntFlag): 107 | IMAGE_FILE_RELOCS_STRIPPED = 0x0001 108 | IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002 109 | IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004 110 | IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008 111 | IMAGE_FILE_AGGRESSIVE_WS_TRIM = 0x0010 112 | IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020 113 | IMAGE_FILE_BYTES_REVERSED_LO = 0x0080 114 | IMAGE_FILE_32BIT_MACHINE = 0x0100 115 | IMAGE_FILE_DEBUG_STRIPPED = 0x0200 116 | IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400 117 | IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800 118 | IMAGE_FILE_SYSTEM = 0x1000 119 | IMAGE_FILE_DLL = 0x2000 120 | IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000 121 | IMAGE_FILE_BYTES_REVERSED_HI = 0x8000 122 | 123 | def __str__(self) -> str: 124 | return super().__str__().lstrip(self.__class__.__name__ + ".") 125 | 126 | dos: DosHeader 127 | file_header: ImageFileHeader 128 | optional_header: OptionalHeader 129 | entry_point: int 130 | 131 | def __init__(self, pe: str) -> None: 132 | self.fpath = pathlib.Path(pe).expanduser().resolve() 133 | 134 | if not os.access(self.fpath, os.R_OK): 135 | raise FileNotFoundError( 136 | f"'{self.fpath}' not found/readable, most gef features will not work" 137 | ) 138 | 139 | endian = gef.arch.endianness 140 | with self.fpath.open("rb") as self.fd: 141 | # Parse IMAGE_DOS_HEADER 142 | self.dos = self.DosHeader() 143 | 144 | self.dos.e_magic = self.read_and_unpack("!H")[0] 145 | if self.dos.e_magic != PE.Constants.DOS_MAGIC: 146 | raise Exception( 147 | f"Corrupted PE file (bad DOS magic, expected '{PE.Constants.DOS_MAGIC:x}', got '{self.dos.e_magic:x}'" 148 | ) 149 | 150 | self.fd.seek(0x3C, 0) 151 | self.dos.e_lfanew = u32(self.fd.read(4)) 152 | 153 | self.fd.seek(self.dos.e_lfanew, 0) 154 | pe_magic = u32(self.fd.read(4)) 155 | if pe_magic != PE.Constants.NT_MAGIC: 156 | raise Exception("Corrupted PE file (bad PE magic)") 157 | 158 | # Parse IMAGE_FILE_HEADER 159 | self.file_header = self.ImageFileHeader() 160 | 161 | machine, self.file_header.NumberOfSections = self.read_and_unpack( 162 | f"{endian}HH" 163 | ) 164 | self.file_header.Machine = PE.MachineType(machine) 165 | 166 | ( 167 | self.file_header.TimeDateStamp, 168 | self.file_header.PointerToSymbolTable, 169 | self.file_header.NumberOfSymbols, 170 | self.file_header.SizeOfOptionalHeader, 171 | dll_characteristics, 172 | ) = self.read_and_unpack(f"{endian}IIIHH") 173 | 174 | self.file_header.Characteristics = PE.DllCharacteristics( 175 | dll_characteristics 176 | ) 177 | 178 | # Parse IMAGE_OPTIONAL_HEADER 179 | self.optional_header = self.OptionalHeader() 180 | 181 | self.fd.seek(0x10, 1) 182 | 183 | ( 184 | self.optional_header.AddressOfEntryPoint, 185 | self.optional_header.BaseOfCode, 186 | self.optional_header.BaseOfData, 187 | self.optional_header.ImageBase, 188 | ) = self.read_and_unpack(f"{endian}IIII") 189 | 190 | self.entry_point = self.optional_header.AddressOfEntryPoint 191 | return 192 | 193 | def __str__(self) -> str: 194 | return f"PE('{self.fpath.absolute()}', {self.file_header.Machine.name}, {str(self.file_header.Characteristics)})" 195 | 196 | def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: 197 | size = struct.calcsize(fmt) 198 | data = self.fd.read(size) 199 | return struct.unpack(fmt, data) 200 | 201 | 202 | # 203 | # redirect calls 204 | # 205 | @lru_cache() 206 | def get_elf_headers(filename: str = None) -> PE: 207 | """Return an PE object with info from `filename`. If not provided, will return 208 | the currently debugged file.""" 209 | return PE(filename) if filename else PE(str(gef.session.file.absolute())) 210 | 211 | 212 | def checksec(filename: str) -> Dict[str, bool]: 213 | warn("`checksec` doesn't apply for PE files") 214 | return {"Canary": False, "NX": False, "PIE": False, "Fortify": False} 215 | 216 | 217 | elf_reset_architecture = reset_architecture 218 | 219 | 220 | def pe_reset_architecture( 221 | arch: Optional[str] = None, default: Optional[str] = None 222 | ) -> None: 223 | if gef.binary.file_header.Machine == PE.MachineType.X86_32: 224 | gef.arch = __registered_architectures__.get(Elf.Abi.X86_32)() 225 | elif gef.binary.file_header.Machine == PE.MachineType.X86_64: 226 | gef.arch = __registered_architectures__.get(Elf.Abi.X86_64)() 227 | else: 228 | raise Exception("unknown architecture") 229 | return 230 | 231 | 232 | def reset_architecture( 233 | arch: Optional[str] = None, default: Optional[str] = None 234 | ) -> None: 235 | if isinstance(gef.binary, PE): 236 | pe_reset_architecture(arch, default) 237 | elif isinstance(gef.binary, ELF): 238 | elf_reset_architecture(arch, default) 239 | else: 240 | raise Exception("unknown architecture") 241 | return 242 | -------------------------------------------------------------------------------- /scripts/got_audit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Print a list of symbols in the GOT and the files that provide them. 3 | 4 | Errors will be printed if a symbol is provided by multiple shared 5 | libraries, or if a symbol points to a library that doesn't export 6 | it. 7 | """ 8 | 9 | __AUTHOR__ = "gordonmessmer" 10 | __VERSION__ = 1.0 11 | __LICENSE__ = "MIT" 12 | 13 | import collections 14 | import pathlib 15 | from typing import TYPE_CHECKING 16 | 17 | import gdb 18 | 19 | if TYPE_CHECKING: 20 | from . import * 21 | from . import gdb 22 | 23 | @register 24 | class GotAuditCommand(GotCommand, GenericCommand): 25 | """Display current status of the got inside the process with paths providing functions.""" 26 | 27 | _cmdline_ = "got-audit" 28 | _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] " 29 | _example_ = "got-audit read printf exit" 30 | _symbols_: dict[str, list[str]] = collections.defaultdict(list) 31 | _paths_: dict[str, list[str]] = collections.defaultdict(list) 32 | 33 | _expected_dups_ = { 34 | "__cxa_finalize", 35 | 36 | # Symbols that appear in both GNU's libc.so and libm.so 37 | "copysign", "copysignf", "copysignl", "__finite", "finite", 38 | "__finitef", "finitef", "__finitel", "finitel", "frexp", 39 | "frexpf", "frexpl", "ldexp", "ldexpf", "ldexpl", "modf", 40 | "modff", "modfl", "scalbn", "scalbnf", "scalbnl", "__signbit", 41 | "__signbitf", "__signbitl", 42 | 43 | # Symbols that appear in both GNU's libc.so and libattr.so 44 | "fgetxattr", "flistxattr", "fremovexattr", "fsetxattr", 45 | "getxattr", "lgetxattr", "listxattr", "llistxattr", 46 | "lremovexattr", "lsetxattr", "removexattr", "setxattr", 47 | 48 | # Symbols that appear in both GNU's libc.so and libtirpc.so 49 | "authdes_create", "authdes_pk_create", "_authenticate", 50 | "authnone_create", "authunix_create", 51 | "authunix_create_default", "bindresvport", "callrpc", 52 | "clnt_broadcast", "clnt_create", "clnt_pcreateerror", 53 | "clnt_perrno", "clnt_perror", "clntraw_create", 54 | "clnt_spcreateerror", "clnt_sperrno", "clnt_sperror", 55 | "clnttcp_create", "clntudp_bufcreate", "clntudp_create", 56 | "clntunix_create", "get_myaddress", "getnetname", 57 | "getpublickey", "getrpcport", "host2netname", 58 | "key_decryptsession", "key_decryptsession_pk", 59 | "key_encryptsession", "key_encryptsession_pk", "key_gendes", 60 | "key_get_conv", "key_secretkey_is_set", "key_setnet", 61 | "key_setsecret", "__libc_clntudp_bufcreate", "netname2host", 62 | "netname2user", "pmap_getmaps", "pmap_getport", 63 | "pmap_rmtcall", "pmap_set", "pmap_unset", "registerrpc", 64 | "_rpc_dtablesize", "rtime", "_seterr_reply", "svcerr_auth", 65 | "svcerr_decode", "svcerr_noproc", "svcerr_noprog", 66 | "svcerr_progvers", "svcerr_systemerr", "svcerr_weakauth", 67 | "svc_exit", "svcfd_create", "svc_getreq", "svc_getreq_common", 68 | "svc_getreq_poll", "svc_getreqset", "svcraw_create", 69 | "svc_register", "svc_run", "svc_sendreply", "svctcp_create", 70 | "svcudp_bufcreate", "svcudp_create", "svcunix_create", 71 | "svcunixfd_create", "svc_unregister", "user2netname", 72 | "xdr_accepted_reply", "xdr_array", "xdr_authunix_parms", 73 | "xdr_bool", "xdr_bytes", "xdr_callhdr", "xdr_callmsg", 74 | "xdr_char", "xdr_cryptkeyarg", "xdr_cryptkeyarg2", 75 | "xdr_cryptkeyres", "xdr_des_block", "xdr_double", "xdr_enum", 76 | "xdr_float", "xdr_free", "xdr_getcredres", "xdr_hyper", 77 | "xdr_int", "xdr_int16_t", "xdr_int32_t", "xdr_int64_t", 78 | "xdr_int8_t", "xdr_keybuf", "xdr_key_netstarg", 79 | "xdr_key_netstres", "xdr_keystatus", "xdr_long", 80 | "xdr_longlong_t", "xdrmem_create", "xdr_netnamestr", 81 | "xdr_netobj", "xdr_opaque", "xdr_opaque_auth", "xdr_pmap", 82 | "xdr_pmaplist", "xdr_pointer", "xdr_quad_t", "xdrrec_create", 83 | "xdrrec_endofrecord", "xdrrec_eof", "xdrrec_skiprecord", 84 | "xdr_reference", "xdr_rejected_reply", "xdr_replymsg", 85 | "xdr_rmtcall_args", "xdr_rmtcallres", "xdr_short", 86 | "xdr_sizeof", "xdrstdio_create", "xdr_string", "xdr_u_char", 87 | "xdr_u_hyper", "xdr_u_int", "xdr_uint16_t", "xdr_uint32_t", 88 | "xdr_uint64_t", "xdr_uint8_t", "xdr_u_long", 89 | "xdr_u_longlong_t", "xdr_union", "xdr_unixcred", 90 | "xdr_u_quad_t", "xdr_u_short", "xdr_vector", "xdr_void", 91 | "xdr_wrapstring", "xprt_register", "xprt_unregister", 92 | 93 | # Symbols that appear in libsasl2 and in its related libs 94 | "_plug_buf_alloc", "_plug_challenge_prompt", "_plug_decode", 95 | "_plug_decode_free", "_plug_decode_init", "_plug_find_prompt", 96 | "_plug_free_secret", "_plug_free_string", 97 | "_plug_get_error_message", "_plug_get_password", 98 | "_plug_get_realm", "_plug_get_simple", "_plug_iovec_to_buf", 99 | "_plug_ipfromstring", "_plug_make_fulluser", 100 | "_plug_make_prompts", "_plug_parseuser", 101 | "_plug_snprintf_os_info", "_plug_strdup", 102 | 103 | # Symbols that appear in libresolv and libvncserver 104 | "__b64_ntop", "__b64_pton", 105 | } 106 | 107 | def get_symbols_from_path(self, elf_file): 108 | nm = gef.session.constants["nm"] 109 | # retrieve symbols using nm 110 | lines = gef_execute_external([nm, "-D", elf_file], as_list=True) 111 | for line in lines: 112 | words = line.split() 113 | # Record the symbol if it is in the text section or 114 | # an indirect function or weak symbol 115 | if len(words) == 3 and words[-2] in ("T", "i", "I", "v", "V", "w", "W"): 116 | sym = words[-1].split("@")[0] 117 | if elf_file not in self._symbols_[sym]: 118 | self._symbols_[sym].append(elf_file) 119 | self._paths_[elf_file].append(sym) 120 | 121 | @only_if_gdb_running 122 | def do_invoke(self, argv: list[str]) -> None: 123 | # Build a list of the symbols provided by each library path, and 124 | # a list of paths that provide each symbol. 125 | for section in gef.memory.maps: 126 | if (section.path not in self._paths_ 127 | and pathlib.Path(section.path).is_file() 128 | and section.permission & Permission.EXECUTE): 129 | self.get_symbols_from_path(section.path) 130 | return super().do_invoke(argv) 131 | 132 | def build_line(self, name: str, path: str, color: str, address_val: int, got_address: int) -> str: 133 | line = Color.colorify(f"{name}", color) 134 | found = 0 135 | for section in gef.memory.maps: 136 | if not section.contains(got_address): 137 | continue 138 | line += f" : {section.path}" 139 | found = 1 140 | short_name = name.split("@")[0] 141 | # Symbol duplication isn't a strong signal for namespace tampering, but it should not be 142 | # allowed without review. Developers should register the symbols that multiple libraries 143 | # export. (Though the current implementation of hard-coding them in this tool should be 144 | # replaced with a more flexible approach.) 145 | if (len(self._symbols_[short_name]) > 1 146 | and short_name not in self._expected_dups_): 147 | line += f" :: ERROR {short_name} found in multiple paths ({str(self._symbols_[short_name])})" 148 | # Symbols within a Section are allowed to resolve to an address within the same Section. 149 | # This is usually an unresolved symbol. In any case, we aren't concerned that a library 150 | # will subvert its own functionality through namespace tampering. 151 | if (section.path != "[vdso]" 152 | and section.path != path 153 | and short_name not in self._paths_[section.path]): 154 | line += f" :: ERROR {short_name} not exported by {section.path}" 155 | break 156 | if not found: 157 | line += " : no mapping found" 158 | return line 159 | -------------------------------------------------------------------------------- /scripts/visualize_heap.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provide an ascii-based graphical representation of the heap layout. 3 | 4 | Note: Mostly done for x64, other architectures were not throughly tested. 5 | """ 6 | 7 | __AUTHOR__ = "hugsy" 8 | __VERSION__ = 0.4 9 | __LICENSE__ = "MIT" 10 | 11 | import os 12 | from functools import lru_cache 13 | from typing import TYPE_CHECKING, Dict, List, Tuple 14 | 15 | import gdb 16 | 17 | if TYPE_CHECKING: 18 | from . import * 19 | from . import gdb 20 | 21 | 22 | def fastbin_index(sz): 23 | return (sz >> 4) - 2 if gef.arch.ptrsize == 8 else (sz >> 3) - 2 24 | 25 | 26 | def nfastbins(): 27 | return fastbin_index((80 * gef.arch.ptrsize // 4)) - 1 28 | 29 | 30 | def get_tcache_count(): 31 | version = gef.libc.version 32 | if version is None: 33 | raise RuntimeError("Cannot get the libc version") 34 | if version < (2, 27): 35 | return 0 36 | base = gef.heap.base_address 37 | if not base: 38 | raise RuntimeError( 39 | "Failed to get the heap base address. Heap not initialized?") 40 | count_addr = base + 2*gef.arch.ptrsize 41 | count = p8(count_addr) if version < (2, 30) else p16(count_addr) 42 | return count 43 | 44 | 45 | @lru_cache(128) 46 | def collect_known_values() -> Dict[int, str]: 47 | arena = gef.heap.main_arena 48 | if not arena: 49 | raise RuntimeError 50 | 51 | version = gef.libc.version 52 | if not version: 53 | raise RuntimeError 54 | 55 | result: Dict[int, str] = {} # format is { 0xaddress : "name" ,} 56 | 57 | # tcache 58 | if version >= (2, 27): 59 | tcache_addr = GlibcHeapTcachebinsCommand.find_tcache() 60 | for i in range(GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS): 61 | chunk, _ = GlibcHeapTcachebinsCommand.tcachebin(tcache_addr, i) 62 | j = 0 63 | while True: 64 | if chunk is None: 65 | break 66 | sz = (i+1)*0x10+0x10 67 | result[chunk.data_address] = f"tcachebins[{i}/{j}] (size={sz:#x})" 68 | next_chunk_address = chunk.get_fwd_ptr(True) 69 | if not next_chunk_address: 70 | break 71 | next_chunk = GlibcChunk(next_chunk_address) 72 | j += 1 73 | chunk = next_chunk 74 | 75 | # fastbins 76 | for i in range(nfastbins()): 77 | chunk = arena.fastbin(i) 78 | j = 0 79 | while True: 80 | if chunk is None: 81 | break 82 | result[chunk.data_address] = f"fastbins[{i}/{j}]" 83 | next_chunk_address = chunk.get_fwd_ptr(True) 84 | if not next_chunk_address: 85 | break 86 | next_chunk = GlibcChunk(next_chunk_address) 87 | j += 1 88 | chunk = next_chunk 89 | 90 | # other bins 91 | for name in ["unorderedbins", "smallbins", "largebins"]: 92 | i = 0 93 | while True: 94 | (fw, bk) = arena.bin(i) 95 | if (fw, bk) == (0, 0): 96 | break 97 | 98 | head = GlibcChunk(bk, from_base=True).fwd 99 | if head == fw: 100 | continue 101 | 102 | chunk = GlibcChunk(head, from_base=True) 103 | j = 0 104 | while True: 105 | if chunk is None: 106 | break 107 | result[chunk.data_address] = f"{name}[{i}/{j}]" 108 | next_chunk_address = chunk.get_fwd_ptr(True) 109 | if not next_chunk_address: 110 | break 111 | next_chunk = GlibcChunk(next_chunk_address, from_base=True) 112 | j += 1 113 | chunk = next_chunk 114 | 115 | return result 116 | 117 | 118 | @lru_cache(128) 119 | def collect_known_ranges() -> List[Tuple[range, str]]: 120 | result = [] 121 | for entry in gef.memory.maps: 122 | if not entry.path: 123 | continue 124 | path = os.path.basename(entry.path) 125 | result.append((range(entry.page_start, entry.page_end), path)) 126 | return result 127 | 128 | 129 | def is_corrupted(chunk: GlibcChunk, arena: GlibcArena) -> bool: 130 | """Various checks to see if a chunk is corrupted""" 131 | 132 | if chunk.base_address > chunk.data_address: 133 | return False 134 | 135 | if chunk.base_address > arena.top: 136 | return True 137 | 138 | if chunk.size == 0: 139 | return True 140 | 141 | return False 142 | 143 | 144 | @register 145 | class VisualizeHeapChunksCommand(GenericCommand): 146 | """Visual helper for glibc heap chunks""" 147 | 148 | _cmdline_ = "visualize-libc-heap-chunks" 149 | _syntax_ = f"{_cmdline_:s}" 150 | _aliases_ = ["heap-view", ] 151 | _example_ = f"{_cmdline_:s}" 152 | 153 | def __init__(self): 154 | super().__init__(complete=gdb.COMPLETE_SYMBOL) 155 | return 156 | 157 | @only_if_gdb_running 158 | def do_invoke(self, _): 159 | if not gef.heap.main_arena or not gef.heap.base_address: 160 | err("The heap has not been initialized") 161 | return 162 | 163 | ptrsize = gef.arch.ptrsize 164 | arena = gef.heap.main_arena 165 | 166 | colors = ("cyan", "red", "yellow", "blue", "green") 167 | color_idx = 0 168 | chunk_idx = 0 169 | 170 | known_ranges = collect_known_ranges() 171 | known_values = [] # collect_known_values() 172 | 173 | for chunk in gef.heap.chunks: 174 | if is_corrupted(chunk, arena): 175 | err("Corrupted heap, cannot continue.") 176 | return 177 | 178 | aggregate_nuls = 0 179 | base = chunk.base_address 180 | 181 | if base == arena.top: 182 | gef_print( 183 | f"{format_address(base)} {format_address(gef.memory.read_integer(base))} {Color.colorify(LEFT_ARROW + 'Top Chunk', 'red bold')}\n" 184 | f"{format_address(base+ptrsize)} {format_address(gef.memory.read_integer(base+ptrsize))} {Color.colorify(LEFT_ARROW + 'Top Chunk Size', 'red bold')}" 185 | ) 186 | break 187 | 188 | for current in range(base, base + chunk.size, ptrsize): 189 | value = gef.memory.read_integer(current) 190 | if value == 0: 191 | if current != base and current != (base + chunk.size - ptrsize): 192 | # Only aggregate null bytes that are not starting/finishing the chunk 193 | aggregate_nuls += 1 194 | if aggregate_nuls > 1: 195 | continue 196 | 197 | if aggregate_nuls > 2: 198 | # If here, we have some aggregated null bytes, print a small thing to mention that 199 | gef_print(" ↓ [...] ↓") 200 | aggregate_nuls = 0 201 | 202 | # Read the context in a hexdump-like format 203 | hexdump = "".join(map(lambda b: chr(b) if 0x20 <= b < 0x7F else ".", 204 | gef.memory.read(current, ptrsize))) 205 | 206 | if gef.arch.endianness == Endianness.LITTLE_ENDIAN: 207 | hexdump = hexdump[::-1] 208 | 209 | line = f"{format_address(current)} {Color.colorify(format_address(value), colors[color_idx])}" 210 | line += f" {hexdump}" 211 | derefs = dereference_from(current) 212 | if len(derefs) > 2: 213 | line += f" [{LEFT_ARROW}{derefs[-1]}]" 214 | 215 | # The first entry of the chunk gets added some extra info about the chunk itself 216 | if current == base: 217 | line += f" Chunk[{chunk_idx}], Flag={chunk.flags!s}" 218 | chunk_idx += 1 219 | 220 | # Populate information for known ranges, if any 221 | for _range, _value in known_ranges: 222 | if value in _range: 223 | line += f" (in {Color.redify(_value)})" 224 | 225 | # Populate information from other chunks/bins, if any 226 | if value in known_values: 227 | line += f"{RIGHT_ARROW}{Color.cyanify(known_values[value])}" 228 | 229 | # All good, print it out 230 | gef_print(line) 231 | 232 | color_idx = (color_idx + 1) % len(colors) 233 | 234 | return 235 | --------------------------------------------------------------------------------