diff options
author | Matt Strapp <matt@mattstrapp.net> | 2022-05-23 16:48:35 -0500 |
---|---|---|
committer | Matt Strapp <matt@mattstrapp.net> | 2022-05-23 16:48:35 -0500 |
commit | b46d66e03e33842a61260520146a34768056561f (patch) | |
tree | cc2a890fe7cc2da0eef89b096d163a41b58f4680 | |
download | dotfiles-b46d66e03e33842a61260520146a34768056561f.tar dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.gz dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.bz2 dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.lz dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.xz dotfiles-b46d66e03e33842a61260520146a34768056561f.tar.zst dotfiles-b46d66e03e33842a61260520146a34768056561f.zip |
Initial commit
-rw-r--r-- | LICENSE | 3 | ||||
-rw-r--r-- | alacritty/.config/alacritty.yml | 590 | ||||
-rw-r--r-- | alacritty/.config/alacritty/nord.yml | 58 | ||||
-rw-r--r-- | gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py | 11629 | ||||
-rw-r--r-- | gdb/.config/gdb/init | 1 | ||||
-rw-r--r-- | lvim/.config/lvim/config.lua | 168 | ||||
-rw-r--r-- | sh/.config/shell/aliasrc | 63 | ||||
-rw-r--r-- | sh/.config/shell/profile | 157 | ||||
-rw-r--r-- | tmux/.config/tmux/tmux.conf | 23 | ||||
-rw-r--r-- | zsh/.config/zsh/.p10k.zsh | 1600 | ||||
-rw-r--r-- | zsh/.config/zsh/.zcompdump | 2007 | ||||
l--------- | zsh/.config/zsh/.zprofile | 1 | ||||
-rw-r--r-- | zsh/.config/zsh/.zshopts | 71 | ||||
-rw-r--r-- | zsh/.config/zsh/.zshrc | 65 | ||||
-rw-r--r-- | zsh/.config/zsh/keybindings.zsh | 138 |
15 files changed, 16574 insertions, 0 deletions
@@ -0,0 +1,3 @@ +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/alacritty/.config/alacritty.yml b/alacritty/.config/alacritty.yml new file mode 100644 index 0000000..fe044c7 --- /dev/null +++ b/alacritty/.config/alacritty.yml @@ -0,0 +1,590 @@ +# Configuration for Alacritty, the GPU enhanced terminal emulator. +import: + - /home/ross/.config/alacritty/nord.yml + +# Any items in the `env` entry below will be added as +# environment variables. Some entries may override variables +# set by alacritty itself. +#env: + # TERM variable + # + # This value is used to set the `$TERM` environment variable for + # each instance of Alacritty. If it is not present, alacritty will + # check the local terminfo database and use `alacritty` if it is + # available, otherwise `xterm-256color` is used. + #TERM: xterm-256color + +window: + # Window dimensions (changes require restart) + # + # Specified in number of columns/lines, not pixels. + # If both are `0`, this setting is ignored. + dimensions: + columns: 100 + lines: 24 + + # Window position (changes require restart) + # + # Specified in number of pixels. + # If the position is not set, the window manager will handle the placement. + #position: + # x: 0 + # y: 0 + + # Window padding (changes require restart) + # + # Blank space added around the window in pixels. This padding is scaled + # by DPI and the specified value is always added at both opposing sides. + #padding: + # x: 6 + # y: 6 + + # Spread additional padding evenly around the terminal content. + dynamic_padding: false + #opacity: 1.0 + opacity: 0.90 + # Window decorations + # + # Values for `decorations`: + # - full: Borders and title bar + # - none: Neither borders nor title bar + # + # Values for `decorations` (macOS only): + # - transparent: Title bar, transparent background and title bar buttons + # - buttonless: Title bar, transparent background, but no title bar buttons + #decorations: full + + # Startup Mode (changes require restart) + # + # Values for `startup_mode`: + # - Windowed + # - Maximized + # - Fullscreen + # + # Values for `startup_mode` (macOS only): + # - SimpleFullscreen + #startup_mode: Windowed + + # Window title + title: Terminal + + # Window class (Linux/BSD only): + class: + # Application instance name + instance: Alacritty + # General application class + general: Alacritty + + # GTK theme variant (Linux/BSD only) + # + # Override the variant of the GTK theme. Commonly supported values are `dark` and `light`. + # Set this to `None` to use the default theme variant. + gtk_theme_variant: None + +scrolling: + # Maximum number of lines in the scrollback buffer. + # Specifying '0' will disable scrolling. + history: 5000 + + # Number of lines the viewport will move for every line scrolled when + # scrollback is enabled (history > 0). + #multiplier: 3 + + # Scroll to the bottom when new text is written to the terminal. + #auto_scroll: false + +# Spaces per Tab (changes require restart) +# +# This setting defines the width of a tab in cells. +# +# Some applications, like Emacs, rely on knowing about the width of a tab. +# To prevent unexpected behavior in these applications, it's also required to +# change the `it` value in terminfo when altering this setting. +#tabspaces: 8 + +# Font configuration +font: + # Normal (roman) font face + normal: + # Font family + # + # Default: + # - (macOS) Menlo + # - (Linux/BSD) monospace + # - (Windows) Consolas + family: Terminus + #family: Source Code Pro + #Family: TerminessTTF Nerd Font Mono + # family: CodeNewRoman Nerd Font + # family: RobotoMono Nerd Font + # family: Hack + # family: JetBrains Mono + # family: UbuntuMono Nerd Font + # family: Monofur Nerd Font + #family: TerminessTTF Nerd Font + # family: Mononoki Nerd Font + + # The `style` can be specified to pick a specific face. + style: Regular + + # Bold font face + bold: + # Font family + # + # If the bold family is not specified, it will fall back to the + # value specified for the normal font. + family: Terminus + #Family: TerminessTTF Nerd Font Mono + # family: Source Code Pro + # family: CodeNewRoman Nerd Font + # family: RobotoMono Nerd Font + # family: Hack + # family: JetBrains Mono + # family: UbuntuMono Nerd Font + # family: Monofur Nerd Font + #family: TerminessTTF Nerd Font + # family: Mononoki Nerd Font + + # The `style` can be specified to pick a specific face. + style: Bold + + # Italic font face + italic: + # Font family + # + # If the italic family is not specified, it will fall back to the + # value specified for the normal font. + family: Terminus + #family: TerminessTTF Nerd Font Mono + # family: CodeNewRoman Nerd Font + # family: RobotoMono Nerd Font + # family: Hack + # family: JetBrains Mono + # family: UbuntuMono Nerd Font + # family: Monofuritalic Nerd Font Mono + # family: TerminessTTF Nerd Font + # family: Mononoki Nerd Font + + # The `style` can be specified to pick a specific face. + style: Italic + + # Bold italic font face + bold_italic: + # Font family + # + # If the bold italic family is not specified, it will fall back to the + # value specified for the normal font. + family: Terminus + # family: Source Code Pro + # family: CodeNewRoman Nerd Font + # family: RobotoMono Nerd Font + # family: Hack + # family: JetBrains Mono + # family: UbuntuMono Nerd Font + # family: Monofuritalic Nerd Font Mono + # family: TerminessTTF Nerd Font + # family: Mononoki Nerd Font + + # The `style` can be specified to pick a specific face. + style: Bold Italic + + # Point size + size: 7.0 + + # Offset is the extra space around each character. `offset.y` can be thought of + # as modifying the line spacing, and `offset.x` as modifying the letter spacing. + offset: + x: 0 + y: 1 + + # Glyph offset determines the locations of the glyphs within their cells with + # the default being at the bottom. Increasing `x` moves the glyph to the right, + # increasing `y` moves the glyph upwards. + #glyph_offset: + # x: 0 + # y: 0 + + # Thin stroke font rendering (macOS only) + # + # Thin strokes are suitable for retina displays, but for non-retina screens + # it is recommended to set `use_thin_strokes` to `false` + # + # macOS >= 10.14.x: + # + # If the font quality on non-retina display looks bad then set + # `use_thin_strokes` to `true` and enable font smoothing by running the + # following command: + # `defaults write -g CGFontRenderingFontSmoothingDisabled -bool NO` + # + # This is a global setting and will require a log out or restart to take + # effect. + #use_thin_strokes: true + +# If `true`, bold text is drawn using the bright color variants. +draw_bold_text_with_bright_colors: true + +# Colors (Tomorrow Night Bright) +colors: + # Default colors + primary: + background: '0x0c0f14' + foreground: '0xbbc2cf' + + # Bright and dim foreground colors + # + # The dimmed foreground color is calculated automatically if it is not present. + # If the bright foreground color is not set, or `draw_bold_text_with_bright_colors` + # is `false`, the normal foreground color will be used. + #dim_foreground: '0x9a9a9a' + #bright_foreground: '0xffffff' + + # Cursor colors + # + # Colors which should be used to draw the terminal cursor. If these are unset, + # the cursor color will be the inverse of the cell color. + #cursor: + # text: '0x000000' + # cursor: '0xffffff' + + # Selection colors + # + # Colors which should be used to draw the selection area. If selection + # background is unset, selection color will be the inverse of the cell colors. + # If only text is unset the cell text color will remain the same. + selection: + text: '0xbbc2cf' + + # Normal colors + # normal: + # black: '0x1c1f24' + # red: '0xff6c6b' + # green: '0x98be65' + # yellow: '0xda8548' + # blue: '0x51afef' + # magenta: '0xc678dd' + # cyan: '0x5699af' + # white: '0x202328' + + # Bright colors + # bright: + # black: '0x5b6268' + # red: '0xda8548' + # green: '0x4db5bd' + # yellow: '0xecbe7b' + # blue: '0x3071db' # This is 2257a0 in Doom Emacs but I lightened it. + # magenta: '0xa9a1e1' + # cyan: '0x46d9ff' + # white: '0xdfdfdf' + + # Dim colors + # + # If the dim colors are not set, they will be calculated automatically based + # on the `normal` colors. + #dim: + # black: '0x000000' + # red: '0x8c3336' + # green: '0x7a8530' + # yellow: '0x97822e' + # blue: '0x506d8f' + # magenta: '0x80638e' + # cyan: '0x497e7a' + # white: '0x9a9a9a' + + # Indexed Colors + # + # The indexed colors include all colors from 16 to 256. + # When these are not set, they're filled with sensible defaults. + # + # Example: + # `- { index: 16, color: '0xff00ff' }` + # + #indexed_colors: [] + +# Visual Bell +# +# Any time the BEL code is received, Alacritty "rings" the visual bell. Once +# rung, the terminal background will be set to white and transition back to the +# default background color. You can control the rate of this transition by +# setting the `duration` property (represented in milliseconds). You can also +# configure the transition function by setting the `animation` property. +# +# Values for `animation`: +# - Ease +# - EaseOut +# - EaseOutSine +# - EaseOutQuad +# - EaseOutCubic +# - EaseOutQuart +# - EaseOutQuint +# - EaseOutExpo +# - EaseOutCirc +# - Linear +# +# Specifying a `duration` of `0` will disable the visual bell. +#visual_bell: +# animation: EaseOutExpo +# duration: 0 +# color: '0xffffff' + +# Background opacity +# +# Window opacity as a floating point number from `0.0` to `1.0`. +# The value `0.0` is completely transparent and `1.0` is opaque. +window.opacity: 0.85 + + +#selection: + #semantic_escape_chars: ",│`|:\"' ()[]{}<>\t" + + # When set to `true`, selected text will be copied to the primary clipboard. + #save_to_clipboard: false + +# Allow terminal applications to change Alacritty's window title. +#dynamic_title: true + +cursor: + # Cursor style + # + # Values for `style`: + # - ▇ Block + # - _ Underline + # - | Beam + #style: Block + + # If this is `true`, the cursor will be rendered as a hollow box when the + # window is not focused. + unfocused_hollow: true + +# Live config reload (changes require restart) +live_config_reload: true + +# Shell +# +# You can set `shell.program` to the path of your favorite shell, e.g. `/bin/fish`. +# Entries in `shell.args` are passed unmodified as arguments to the shell. +# +# Default: +# - (macOS) /bin/bash --login +# - (Linux/BSD) user login shell +# - (Windows) powershell +shell: + program: /usr/bin/zsh + args: + - -l + - -c + - "tmux" + +# Startup directory +# +# Directory the shell is started in. If this is unset, or `None`, the working +# directory of the parent process will be used. +#working_directory: None + +# WinPTY backend (Windows only) +# +# Alacritty defaults to using the newer ConPTY backend if it is available, +# since it resolves a lot of bugs and is quite a bit faster. If it is not +# available, the the WinPTY backend will be used instead. +# +# Setting this option to `true` makes Alacritty use the legacy WinPTY backend, +# even if the ConPTY backend is available. +#winpty_backend: false + +# Send ESC (\x1b) before characters when alt is pressed. +#alt_send_esc: true + +#debug: + # Display the time it takes to redraw each frame. + #render_timer: false + + # Keep the log file after quitting Alacritty. + #persistent_logging: false + + # Log level + # + # Values for `log_level`: + # - None + # - Error + # - Warn + # - Info + # - Debug + # - Trace + #log_level: Warn + + # Print all received window events. + #print_events: false + + # Record all characters and escape sequences as test data. + #ref_test: false + +#mouse: + # Click settings + # + # The `double_click` and `triple_click` settings control the time + # alacritty should wait for accepting multiple clicks as one double + # or triple click. + #double_click: { threshold: 300 } + #triple_click: { threshold: 300 } + + # If this is `true`, the cursor is temporarily hidden when typing. + hide_when_typing: true + + url: + # URL launcher + # + # This program is executed when clicking on a text which is recognized as a URL. + # The URL is always added to the command as the last parameter. + # + # When set to `None`, URL launching will be disabled completely. + # + # Default: + # - (macOS) open + # - (Linux/BSD) xdg-open + # - (Windows) explorer + launcher: + - program: xdg-open + - args: [] + + # URL modifiers + # + # These are the modifiers that need to be held down for opening URLs when clicking + # on them. The available modifiers are documented in the key binding section. + modifiers: Control + +# Mouse bindings +# +# Mouse bindings are specified as a list of objects, much like the key +# bindings further below. +# +# Each mouse binding will specify a: +# +# - `mouse`: +# +# - Middle +# - Left +# - Right +# - Numeric identifier such as `5` +# +# - `action` (see key bindings) +# +# And optionally: +# +# - `mods` (see key bindings) +#mouse_bindings: +# - { mouse: Middle, action: PasteSelection } + +# Key bindings +# +# Key bindings are specified as a list of objects. For example, this is the +# default paste binding: +# +# `- { key: V, mods: Control|Shift, action: Paste }` +# +# Each key binding will specify a: +# +# - `key`: Identifier of the key pressed +# +# - A-Z +# - F1-F24 +# - Key0-Key9 +# +# A full list with available key codes can be found here: +# https://docs.rs/glutin/*/glutin/event/enum.VirtualKeyCode.html#variants +# +# Instead of using the name of the keys, the `key` field also supports using +# the scancode of the desired key. Scancodes have to be specified as a +# decimal number. This command will allow you to display the hex scancodes +# for certain keys: +# +# `showkey --scancodes`. +# +# Then exactly one of: +# +# - `chars`: Send a byte sequence to the running application +# +# The `chars` field writes the specified string to the terminal. This makes +# it possible to pass escape sequences. To find escape codes for bindings +# like `PageUp` (`"\x1b[5~"`), you can run the command `showkey -a` outside +# of tmux. Note that applications use terminfo to map escape sequences back +# to keys. It is therefore required to update the terminfo when changing an +# escape sequence. +# +# - `action`: Execute a predefined action +# +# - Copy +# - Paste +# - PasteSelection +# - IncreaseFontSize +# - DecreaseFontSize +# - ResetFontSize +# - ScrollPageUp +# - ScrollPageDown +# - ScrollLineUp +# - ScrollLineDown +# - ScrollToTop +# - ScrollToBottom +# - ClearHistory +# - Hide +# - Minimize +# - Quit +# - ToggleFullscreen +# - SpawnNewInstance +# - ClearLogNotice +# - ReceiveChar +# - None +# +# (macOS only): +# - ToggleSimpleFullscreen: Enters fullscreen without occupying another space +# +# - `command`: Fork and execute a specified command plus arguments +# +# The `command` field must be a map containing a `program` string and an +# `args` array of command line parameter strings. For example: +# `{ program: "alacritty", args: ["-e", "vttest"] }` +# +# And optionally: +# +# - `mods`: Key modifiers to filter binding actions +# +# - Command +# - Control +# - Option +# - Super +# - Shift +# - Alt +# +# Multiple `mods` can be combined using `|` like this: +# `mods: Control|Shift`. +# Whitespace and capitalization are relevant and must match the example. +# +# - `mode`: Indicate a binding for only specific terminal reported modes +# +# This is mainly used to send applications the correct escape sequences +# when in different modes. +# +# - AppCursor +# - AppKeypad +# - Alt +# +# A `~` operator can be used before a mode to apply the binding whenever +# the mode is *not* active, e.g. `~Alt`. +# +# Bindings are always filled by default, but will be replaced when a new +# binding with the same triggers is defined. To unset a default binding, it can +# be mapped to the `ReceiveChar` action. Alternatively, you can use `None` for +# a no-op if you do not wish to receive input characters for that binding. +key_bindings: + # (Windows, Linux, and BSD only) + - { key: V, mods: Control|Shift, action: Paste } + - { key: C, mods: Control|Shift, action: Copy } + - { key: Insert, mods: Shift, action: PasteSelection } + - { key: Key0, mods: Control, action: ResetFontSize } + - { key: Equals, mods: Control, action: IncreaseFontSize } + - { key: Plus, mods: Control, action: IncreaseFontSize } + - { key: Minus, mods: Control, action: DecreaseFontSize } + - { key: Minus, mods: Control, action: DecreaseFontSize } + - { key: F11, mods: None, action: ToggleFullscreen } + - { key: Paste, mods: None, action: Paste } + - { key: Copy, mods: None, action: Copy } + - { key: L, mods: Control, action: ClearLogNotice } + - { key: L, mods: Control, chars: "\x0c" } diff --git a/alacritty/.config/alacritty/nord.yml b/alacritty/.config/alacritty/nord.yml new file mode 100644 index 0000000..b370cb1 --- /dev/null +++ b/alacritty/.config/alacritty/nord.yml @@ -0,0 +1,58 @@ +# Copyright (c) 2017-present Arctic Ice Studio <development@arcticicestudio.com> +# Copyright (c) 2017-present Sven Greb <code@svengreb.de> + +# Project: Nord Alacritty +# Version: 0.1.0 +# Repository: https://github.com/arcticicestudio/nord-alacritty +# License: MIT +# References: +# https://github.com/alacritty/alacritty + +colors: + primary: + background: '#2e3440' + foreground: '#d8dee9' + dim_foreground: '#a5abb6' + cursor: + text: '#2e3440' + cursor: '#d8dee9' + vi_mode_cursor: + text: '#2e3440' + cursor: '#d8dee9' + selection: + text: CellForeground + background: '#4c566a' + search: + matches: + foreground: CellBackground + background: '#88c0d0' + bar: + background: '#434c5e' + foreground: '#d8dee9' + normal: + black: '#3b4252' + red: '#bf616a' + green: '#a3be8c' + yellow: '#ebcb8b' + blue: '#81a1c1' + magenta: '#b48ead' + cyan: '#88c0d0' + white: '#e5e9f0' + bright: + black: '#4c566a' + red: '#bf616a' + green: '#a3be8c' + yellow: '#ebcb8b' + blue: '#81a1c1' + magenta: '#b48ead' + cyan: '#8fbcbb' + white: '#eceff4' + dim: + black: '#373e4d' + red: '#94545d' + green: '#809575' + yellow: '#b29e75' + blue: '#68809a' + magenta: '#8c738c' + cyan: '#6d96a5' + white: '#aeb3bb' diff --git a/gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py b/gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py new file mode 100644 index 0000000..8386154 --- /dev/null +++ b/gdb/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py @@ -0,0 +1,11629 @@ +####################################################################################### +# GEF - Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers +# +# by @_hugsy_ +####################################################################################### +# +# GEF is a kick-ass set of commands for X86, ARM, MIPS, PowerPC and SPARC to +# make GDB cool again for exploit dev. It is aimed to be used mostly by exploit +# devs and reversers, to provides additional features to GDB using the Python +# API to assist during the process of dynamic analysis. +# +# GEF fully relies on GDB API and other Linux-specific sources of information +# (such as /proc/<pid>). As a consequence, some of the features might not work +# on custom or hardened systems such as GrSec. +# +# Since January 2020, GEF solely support GDB compiled with Python3 and was tested on +# * x86-32 & x86-64 +# * arm v5,v6,v7 +# * aarch64 (armv8) +# * mips & mips64 +# * powerpc & powerpc64 +# * sparc & sparc64(v9) +# +# For GEF with Python2 (only) support was moved to the GEF-Legacy +# (https://github.com/hugsy/gef-legacy) +# +# To start: in gdb, type `source /path/to/gef.py` +# +####################################################################################### +# +# gef is distributed under the MIT License (MIT) +# Copyright (c) 2013-2022 crazy rabbidz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import abc +import argparse +import binascii +import codecs +import collections +import configparser +import ctypes +import enum +import functools +import hashlib +import importlib +import inspect +import itertools +import json +import os +import pathlib +import platform +import re +import shutil +import site +import socket +import string +import struct +import subprocess +import sys +import tempfile +import time +import traceback +import warnings +import xmlrpc.client as xmlrpclib +from functools import lru_cache +from io import StringIO, TextIOWrapper +from types import ModuleType +from typing import (Any, ByteString, Callable, Dict, Generator, IO, Iterator, List, + NoReturn, Optional, Sequence, Tuple, Type, Union) +from urllib.request import urlopen + + +def http_get(url: str) -> Optional[bytes]: + """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK, + otherwise return None.""" + try: + http = urlopen(url) + if http.getcode() != 200: + return None + return http.read() + except Exception: + return None + + +def update_gef(argv: List[str]) -> int: + """Try to update `gef` to the latest version pushed on GitHub master branch. + Return 0 on success, 1 on failure. """ + ver = "dev" if "--dev" in argv[2:] else "master" + latest_gef_data = http_get(f"https://raw.githubusercontent.com/hugsy/gef/{ver}/scripts/gef.sh") + if latest_gef_data is None: + print("[-] Failed to get remote gef") + return 1 + + fd, fname = tempfile.mkstemp(suffix=".sh") + os.write(fd, latest_gef_data) + os.close(fd) + retcode = subprocess.run(["bash", fname, ver], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode + os.unlink(fname) + return retcode + + +try: + import gdb # pylint: disable= +except ImportError: + # if out of gdb, the only action allowed is to update gef.py + if len(sys.argv) == 2 and sys.argv[1].lower() in ("--update", "--upgrade"): + sys.exit(update_gef(sys.argv)) + print("[-] gef cannot run as standalone") + sys.exit(0) + + +GDB_MIN_VERSION = (8, 0) +GDB_VERSION = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) +PYTHON_MIN_VERSION = (3, 6) +PYTHON_VERSION = sys.version_info[0:2] + +DEFAULT_PAGE_ALIGN_SHIFT = 12 +DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT + +GEF_RC = (pathlib.Path(os.getenv("GEF_RC")).absolute() + if os.getenv("GEF_RC") + else pathlib.Path().home() / ".gef.rc") +GEF_TEMP_DIR = os.path.join(tempfile.gettempdir(), "gef") +GEF_MAX_STRING_LENGTH = 50 + +LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena" +ANSI_SPLIT_RE = r"(\033\[[\d;]*m)" + +LEFT_ARROW = " \u2190 " +RIGHT_ARROW = " \u2192 " +DOWN_ARROW = "\u21b3" +HORIZONTAL_LINE = "\u2500" +VERTICAL_LINE = "\u2502" +CROSS = "\u2718 " +TICK = "\u2713 " +BP_GLYPH = "\u25cf" +GEF_PROMPT = "gef\u27a4 " +GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002" +GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002" + +PATTERN_LIBC_VERSION = re.compile(rb"glibc (\d+)\.(\d+)") + + +gef : "Gef" = None +__registered_commands__ : List[Type["GenericCommand"]] = [] +__registered_functions__ : List[Type["GenericFunction"]] = [] +__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {} + + +def reset_all_caches() -> None: + """Free all caches. If an object is cached, it will have a callable attribute `cache_clear` + which will be invoked to purge the function cache.""" + + for mod in dir(sys.modules["__main__"]): + obj = getattr(sys.modules["__main__"], mod) + if hasattr(obj, "cache_clear"): + obj.cache_clear() + + gef.reset_caches() + return + + +def reset() -> None: + global gef + + arch = None + if gef: + reset_all_caches() + arch = gef.arch + del gef + + gef = Gef() + gef.setup() + + if arch: + gef.arch = arch + return + + +def highlight_text(text: str) -> str: + """ + Highlight text using `gef.ui.highlight_table` { match -> color } settings. + + If RegEx is enabled it will create a match group around all items in the + `gef.ui.highlight_table` and wrap the specified color in the `gef.ui.highlight_table` + around those matches. + + If RegEx is disabled, split by ANSI codes and 'colorify' each match found + within the specified string. + """ + global gef + + if not gef.ui.highlight_table: + return text + + if gef.config["highlight.regex"]: + for match, color in gef.ui.highlight_table.items(): + text = re.sub("(" + match + ")", Color.colorify("\\1", color), text) + return text + + ansiSplit = re.split(ANSI_SPLIT_RE, text) + + for match, color in gef.ui.highlight_table.items(): + for index, val in enumerate(ansiSplit): + found = val.find(match) + if found > -1: + ansiSplit[index] = val.replace(match, Color.colorify(match, color)) + break + text = "".join(ansiSplit) + ansiSplit = re.split(ANSI_SPLIT_RE, text) + + return "".join(ansiSplit) + + +def gef_print(*args: str, end="\n", sep=" ", **kwargs: Any) -> None: + """Wrapper around print(), using string buffering feature.""" + parts = [highlight_text(a) for a in args] + if gef.ui.stream_buffer and not is_debug(): + gef.ui.stream_buffer.write(sep.join(parts) + end) + return + + print(*parts, sep=sep, end=end, **kwargs) + return + + +def bufferize(f: Callable) -> Callable: + """Store the content to be printed for a function in memory, and flush it on function exit.""" + + @functools.wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> Any: + global gef + + if gef.ui.stream_buffer: + return f(*args, **kwargs) + + gef.ui.stream_buffer = StringIO() + try: + rv = f(*args, **kwargs) + finally: + redirect = gef.config["context.redirect"] + if redirect.startswith("/dev/pts/"): + if not gef.ui.redirect_fd: + # if the FD has never been open, open it + fd = open(redirect, "wt") + gef.ui.redirect_fd = fd + elif redirect != gef.ui.redirect_fd.name: + # if the user has changed the redirect setting during runtime, update the state + gef.ui.redirect_fd.close() + fd = open(redirect, "wt") + gef.ui.redirect_fd = fd + else: + # otherwise, keep using it + fd = gef.ui.redirect_fd + else: + fd = sys.stdout + gef.ui.redirect_fd = None + + if gef.ui.redirect_fd and fd.closed: + # if the tty was closed, revert back to stdout + fd = sys.stdout + gef.ui.redirect_fd = None + gef.config["context.redirect"] = "" + + fd.write(gef.ui.stream_buffer.getvalue()) + fd.flush() + gef.ui.stream_buffer = None + return rv + + return wrapper + + +# +# Helpers +# + +def p8(x: int, s: bool = False) -> bytes: + """Pack one byte respecting the current architecture endianness.""" + return struct.pack(f"{gef.arch.endianness}B", x) if not s else struct.pack(f"{gef.arch.endianness}b", x) + + +def p16(x: int, s: bool = False) -> bytes: + """Pack one word respecting the current architecture endianness.""" + return struct.pack(f"{gef.arch.endianness}H", x) if not s else struct.pack(f"{gef.arch.endianness}h", x) + + +def p32(x: int, s: bool = False) -> bytes: + """Pack one dword respecting the current architecture endianness.""" + return struct.pack(f"{gef.arch.endianness}I", x) if not s else struct.pack(f"{gef.arch.endianness}i", x) + + +def p64(x: int, s: bool = False) -> bytes: + """Pack one qword respecting the current architecture endianness.""" + return struct.pack(f"{gef.arch.endianness}Q", x) if not s else struct.pack(f"{gef.arch.endianness}q", x) + + +def u8(x: bytes, s: bool = False) -> int: + """Unpack one byte respecting the current architecture endianness.""" + return struct.unpack(f"{gef.arch.endianness}B", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}b", x)[0] + + +def u16(x: bytes, s: bool = False) -> int: + """Unpack one word respecting the current architecture endianness.""" + return struct.unpack(f"{gef.arch.endianness}H", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}h", x)[0] + + +def u32(x: bytes, s: bool = False) -> int: + """Unpack one dword respecting the current architecture endianness.""" + return struct.unpack(f"{gef.arch.endianness}I", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}i", x)[0] + + +def u64(x: bytes, s: bool = False) -> int: + """Unpack one qword respecting the current architecture endianness.""" + return struct.unpack(f"{gef.arch.endianness}Q", x)[0] if not s else struct.unpack(f"{gef.arch.endianness}q", x)[0] + + +def is_ascii_string(address: int) -> bool: + """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)""" + try: + return gef.memory.read_ascii_string(address) is not None + except Exception: + return False + + +def is_alive() -> bool: + """Check if GDB is running.""" + try: + return gdb.selected_inferior().pid > 0 + except Exception: + return False + + +# +# Decorators +# +def only_if_gdb_running(f: Callable) -> Callable: + """Decorator wrapper to check if GDB is running.""" + + @functools.wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> Any: + if is_alive(): + return f(*args, **kwargs) + else: + warn("No debugging session active") + + return wrapper + + +def only_if_gdb_target_local(f: Callable) -> Callable: + """Decorator wrapper to check if GDB is running locally (target not remote).""" + + @functools.wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> Any: + if not is_remote_debug(): + return f(*args, **kwargs) + else: + warn("This command cannot work for remote sessions.") + + return wrapper + + +def deprecated(solution: str = "") -> Callable: + """Decorator to add a warning when a command is obsolete and will be removed.""" + def decorator(f: Callable) -> Callable: + @functools.wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> Any: + if gef.config["gef.show_deprecation_warnings"] is True: + msg = f"'{f.__name__}' is deprecated and will be removed in a feature release. " + if solution: + msg += solution + warn(msg) + return f(*args, **kwargs) + + if not wrapper.__doc__: + wrapper.__doc__ = "" + wrapper.__doc__ += f"\r\n`{f.__name__}` is **DEPRECATED** and will be removed in the future.\r\n{solution}" + return wrapper + return decorator + + +def experimental_feature(f: Callable) -> Callable: + """Decorator to add a warning when a feature is experimental.""" + + @functools.wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> Any: + warn("This feature is under development, expect bugs and unstability...") + return f(*args, **kwargs) + + return wrapper + + +def only_if_gdb_version_higher_than(required_gdb_version: Tuple[int, ...]) -> Callable: + """Decorator to check whether current GDB version requirements.""" + + def wrapper(f: Callable) -> Callable: + def inner_f(*args: Any, **kwargs: Any) -> None: + if GDB_VERSION >= required_gdb_version: + f(*args, **kwargs) + else: + reason = f"GDB >= {required_gdb_version} for this command" + raise OSError(reason) + return inner_f + return wrapper + + +def only_if_current_arch_in(valid_architectures: List["Architecture"]) -> Callable: + """Decorator to allow commands for only a subset of the architectured supported by GEF. + This decorator is to use lightly, as it goes against the purpose of GEF to support all + architectures GDB does. However in some cases, it is necessary.""" + + def wrapper(f: Callable) -> Callable: + def inner_f(*args: Any, **kwargs: Any) -> None: + if gef.arch in valid_architectures: + f(*args, **kwargs) + else: + reason = f"This command cannot work for the '{gef.arch.arch}' architecture" + raise OSError(reason) + return inner_f + return wrapper + + +def only_if_events_supported(event_type: str) -> Callable: + """Checks if GDB supports events without crashing.""" + def wrap(f: Callable) -> Callable: + def wrapped_f(*args: Any, **kwargs: Any) -> Any: + if getattr(gdb, "events") and getattr(gdb.events, event_type): + return f(*args, **kwargs) + warn("GDB events cannot be set") + return wrapped_f + return wrap + + +class classproperty(property): + """Make the attribute a `classproperty`.""" + def __get__(self, cls, owner): + return classmethod(self.fget).__get__(None, owner)() + + +def FakeExit(*args: Any, **kwargs: Any) -> NoReturn: + raise RuntimeWarning + + +sys.exit = FakeExit + + +def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any], + optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Optional[Callable]: + """Argument parsing decorator.""" + + def int_wrapper(x: str) -> int: return int(x, 0) + + def decorator(f: Callable) -> Optional[Callable]: + def wrapper(*args: Any, **kwargs: Any) -> Optional[Callable]: + parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True) + for argname in required_arguments: + argvalue = required_arguments[argname] + argtype = type(argvalue) + if argtype is int: + argtype = int_wrapper + + argname_is_list = isinstance(argname, list) or isinstance(argname, tuple) + if not argname_is_list and argname.startswith("-"): + # optional args + if argtype is bool: + parser.add_argument(argname, action="store_true" if argvalue else "store_false") + else: + parser.add_argument(argname, type=argtype, required=True, default=argvalue) + else: + if argtype in (list, tuple): + nargs = "*" + argtype = type(argvalue[0]) + else: + nargs = "?" + # positional args + parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs) + + for argname in optional_arguments: + argname_is_list = isinstance(argname, list) or isinstance(argname, tuple) + if not argname_is_list and not argname.startswith("-"): + # refuse positional arguments + continue + argvalue = optional_arguments[argname] + argtype = type(argvalue) + if not argname_is_list: + argname = [argname,] + if argtype is int: + argtype = int_wrapper + if argtype is bool: + parser.add_argument(*argname, action="store_true" if argvalue else "store_false") + else: + parser.add_argument(*argname, type=argtype, default=argvalue) + + try: + parsed_args = parser.parse_args(*(args[1:])) + except RuntimeWarning: + return + kwargs["arguments"] = parsed_args + return f(*args, **kwargs) + return wrapper + return decorator + + +class Color: + """Used to colorify terminal output.""" + colors = { + "normal" : "\033[0m", + "gray" : "\033[1;38;5;240m", + "light_gray" : "\033[0;37m", + "red" : "\033[31m", + "green" : "\033[32m", + "yellow" : "\033[33m", + "blue" : "\033[34m", + "pink" : "\033[35m", + "cyan" : "\033[36m", + "bold" : "\033[1m", + "underline" : "\033[4m", + "underline_off" : "\033[24m", + "highlight" : "\033[3m", + "highlight_off" : "\033[23m", + "blink" : "\033[5m", + "blink_off" : "\033[25m", + } + + @staticmethod + def redify(msg: str) -> str: return Color.colorify(msg, "red") + @staticmethod + def greenify(msg: str) -> str: return Color.colorify(msg, "green") + @staticmethod + def blueify(msg: str) -> str: return Color.colorify(msg, "blue") + @staticmethod + def yellowify(msg: str) -> str: return Color.colorify(msg, "yellow") + @staticmethod + def grayify(msg: str) -> str: return Color.colorify(msg, "gray") + @staticmethod + def light_grayify(msg: str) -> str: return Color.colorify(msg, "light_gray") + @staticmethod + def pinkify(msg: str) -> str: return Color.colorify(msg, "pink") + @staticmethod + def cyanify(msg: str) -> str: return Color.colorify(msg, "cyan") + @staticmethod + def boldify(msg: str) -> str: return Color.colorify(msg, "bold") + @staticmethod + def underlinify(msg: str) -> str: return Color.colorify(msg, "underline") + @staticmethod + def highlightify(msg: str) -> str: return Color.colorify(msg, "highlight") + @staticmethod + def blinkify(msg: str) -> str: return Color.colorify(msg, "blink") + + @staticmethod + def colorify(text: str, attrs: str) -> str: + """Color text according to the given attributes.""" + if gef.config["gef.disable_color"] is True: return text + + colors = Color.colors + msg = [colors[attr] for attr in attrs.split() if attr in colors] + msg.append(str(text)) + if colors["highlight"] in msg: msg.append(colors["highlight_off"]) + if colors["underline"] in msg: msg.append(colors["underline_off"]) + if colors["blink"] in msg: msg.append(colors["blink_off"]) + msg.append(colors["normal"]) + return "".join(msg) + + +class Address: + """GEF representation of memory addresses.""" + def __init__(self, **kwargs: Any) -> None: + self.value: int = kwargs.get("value", 0) + self.section: "Section" = kwargs.get("section", None) + self.info: "Zone" = kwargs.get("info", None) + self.valid: bool = kwargs.get("valid", True) + return + + def __str__(self) -> str: + value = format_address(self.value) + code_color = gef.config["theme.address_code"] + stack_color = gef.config["theme.address_stack"] + heap_color = gef.config["theme.address_heap"] + if self.is_in_text_segment(): + return Color.colorify(value, code_color) + if self.is_in_heap_segment(): + return Color.colorify(value, heap_color) + if self.is_in_stack_segment(): + return Color.colorify(value, stack_color) + return value + + def __int__(self) -> int: + return self.value + + def is_in_text_segment(self) -> bool: + return (hasattr(self.info, "name") and ".text" in self.info.name) or \ + (hasattr(self.section, "path") and get_filepath() == self.section.path and self.section.is_executable()) + + def is_in_stack_segment(self) -> bool: + return hasattr(self.section, "path") and "[stack]" == self.section.path + + def is_in_heap_segment(self) -> bool: + return hasattr(self.section, "path") and "[heap]" == self.section.path + + def dereference(self) -> Optional[int]: + addr = align_address(int(self.value)) + derefed = dereference(addr) + return None if derefed is None else int(derefed) + + +class Permission(enum.Flag): + """GEF representation of Linux permission.""" + NONE = 0 + EXECUTE = 1 + WRITE = 2 + READ = 4 + ALL = 7 + + def __str__(self) -> str: + perm_str = "" + perm_str += "r" if self & Permission.READ else "-" + perm_str += "w" if self & Permission.WRITE else "-" + perm_str += "x" if self & Permission.EXECUTE else "-" + return perm_str + + @staticmethod + def from_info_sections(*args: str) -> "Permission": + perm = Permission(0) + for arg in args: + if "READONLY" in arg: perm |= Permission.READ + if "DATA" in arg: perm |= Permission.WRITE + if "CODE" in arg: perm |= Permission.EXECUTE + return perm + + @staticmethod + def from_process_maps(perm_str: str) -> "Permission": + perm = Permission(0) + if perm_str[0] == "r": perm |= Permission.READ + if perm_str[1] == "w": perm |= Permission.WRITE + if perm_str[2] == "x": perm |= Permission.EXECUTE + return perm + + +class Section: + """GEF representation of process memory sections.""" + + def __init__(self, **kwargs: Any) -> None: + self.page_start: int = kwargs.get("page_start", 0) + self.page_end: int = kwargs.get("page_end", 0) + self.offset: int = kwargs.get("offset", 0) + self.permission: Permission = kwargs.get("permission", Permission(0)) + self.inode: int = kwargs.get("inode", 0) + self.path: str = kwargs.get("path", "") + return + + def is_readable(self) -> bool: + return (self.permission & Permission.READ) != 0 + + def is_writable(self) -> bool: + return (self.permission & Permission.WRITE) != 0 + + def is_executable(self) -> bool: + return (self.permission & Permission.EXECUTE) != 0 + + @property + def size(self) -> int: + if self.page_end is None or self.page_start is None: + return -1 + return self.page_end - self.page_start + + @property + def realpath(self) -> str: + # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote + return self.path if gef.session.remote is None else f"/tmp/gef/{gef.session.remote:d}/{self.path}" + + def __str__(self) -> str: + return (f"Section(page_start={self.page_start:#x}, page_end={self.page_end:#x}, " + f"permissions={self.permission!s})") + + +Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"]) + + +class Endianness(enum.Enum): + LITTLE_ENDIAN = 1 + BIG_ENDIAN = 2 + + def __str__(self) -> str: + if self == Endianness.LITTLE_ENDIAN: + return "<" + return ">" + + def __repr__(self) -> str: + return self.name + + def __int__(self) -> int: + return self.value + + +class Elf: + """Basic ELF parsing. + Ref: + - http://www.skyfree.org/linux/references/ELF_Format.pdf + - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf + - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html + """ + class Class(enum.Enum): + ELF_32_BITS = 0x01 + ELF_64_BITS = 0x02 + + ELF_MAGIC = 0x7f454c46 + + class Abi(enum.Enum): + X86_64 = 0x3e + X86_32 = 0x03 + ARM = 0x28 + MIPS = 0x08 + POWERPC = 0x14 + POWERPC64 = 0x15 + SPARC = 0x02 + SPARC64 = 0x2b + AARCH64 = 0xb7 + RISCV = 0xf3 + IA64 = 0x32 + M68K = 0x04 + + class Type(enum.Enum): + ET_RELOC = 1 + ET_EXEC = 2 + ET_DYN = 3 + ET_CORE = 4 + + class OsAbi(enum.Enum): + SYSTEMV = 0x00 + HPUX = 0x01 + NETBSD = 0x02 + LINUX = 0x03 + SOLARIS = 0x06 + AIX = 0x07 + IRIX = 0x08 + FREEBSD = 0x09 + OPENBSD = 0x0C + + e_magic: int = ELF_MAGIC + e_class: Class = Class.ELF_32_BITS + e_endianness: Endianness = Endianness.LITTLE_ENDIAN + e_eiversion: int + e_osabi: OsAbi + e_abiversion: int + e_pad: bytes + e_type: Type = Type.ET_EXEC + e_machine: Abi = Abi.X86_32 + e_version: int + e_entry: int + e_phoff: int + e_shoff: int + e_flags: int + e_ehsize: int + e_phentsize: int + e_phnum: int + e_shentsize: int + e_shnum: int + e_shstrndx: int + + path: Optional[pathlib.Path] = None + + def __init__(self, path: str = "", minimalist: bool = False) -> None: + """Instantiate an ELF object. The default behavior is to create the object by parsing the ELF file. + But in some cases (QEMU-stub), we may just want a simple minimal object with default values.""" + if minimalist: + return + + self.fpath = pathlib.Path(path).expanduser() + if not os.access(self.fpath, os.R_OK): + raise FileNotFoundError(f"'{self.fpath}' not found/readable, most gef features will not work") + + with self.fpath.open("rb") as self.fd: + # off 0x0 + self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB") + if self.e_magic != Elf.ELF_MAGIC: + # The ELF is corrupted, GDB won't handle it, no point going further + raise RuntimeError("Not a valid ELF file (magic)") + + self.e_class, self.e_endianness = Elf.Class(e_class), Endianness(e_endianness) + + if self.e_endianness != gef.arch.endianness: + warn("Unexpected endianness for architecture") + + endian = self.e_endianness + + # off 0x7 + e_osabi, self.e_abiversion = self.read_and_unpack(f"{endian}BB") + self.e_osabi = Elf.OsAbi(e_osabi) + + # off 0x9 + self.e_pad = self.read(7) + + # off 0x10 + e_type, e_machine, self.e_version = self.read_and_unpack(f"{endian}HHI") + self.e_type, self.e_machine = Elf.Type(e_type), Elf.Abi(e_machine) + + # off 0x18 + if self.e_class == Elf.Class.ELF_64_BITS: + self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}QQQ") + else: + self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}III") + + self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = self.read_and_unpack(f"{endian}IHHH") + self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack(f"{endian}HHH") + + self.phdrs : List["Phdr"] = [] + for i in range(self.e_phnum): + self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i)) + + self.shdrs : List["Shdr"] = [] + for i in range(self.e_shnum): + self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i)) + return + + def read(self, size: int) -> bytes: + return self.fd.read(size) + + def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]: + size = struct.calcsize(fmt) + data = self.fd.read(size) + return struct.unpack(fmt, data) + + def seek(self, off: int) -> None: + self.fd.seek(off, 0) + + def __str__(self) -> str: + return f"ELF('{self.fpath.absolute()}', {self.e_class.name}, {self.e_machine.name})" + + @property + def entry_point(self) -> int: + return self.e_entry + + @classproperty + @deprecated("use `Elf.Abi.X86_64`") + def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.X86_32`") + def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.ARM`") + def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.MIPS`") + def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.POWERPC`") + def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.POWERPC64`") + def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.SPARC`") + def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.SPARC64`") + def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.AARCH64`") + def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value # pylint: disable=no-self-argument + + @classproperty + @deprecated("use `Elf.Abi.RISCV`") + def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint: disable=no-self-argument + + +class Phdr: + class Type(enum.IntEnum): + PT_NULL = 0 + PT_LOAD = 1 + PT_DYNAMIC = 2 + PT_INTERP = 3 + PT_NOTE = 4 + PT_SHLIB = 5 + PT_PHDR = 6 + PT_TLS = 7 + PT_LOOS = 0x60000000 + PT_GNU_EH_FRAME = 0x6474e550 + PT_GNU_STACK = 0x6474e551 + PT_GNU_RELRO = 0x6474e552 + PT_GNU_PROPERTY = 0x6474e553 + PT_LOSUNW = 0x6ffffffa + PT_SUNWBSS = 0x6ffffffa + PT_SUNWSTACK = 0x6ffffffb + PT_HISUNW = PT_HIOS = 0x6fffffff + PT_LOPROC = 0x70000000 + PT_ARM_EIDX = 0x70000001 + PT_MIPS_ABIFLAGS= 0x70000003 + PT_HIPROC = 0x7fffffff + UNKNOWN_PHDR = 0xffffffff + + @classmethod + def _missing_(cls, _:int) -> Type: + return cls.UNKNOWN_PHDR + + class Flags(enum.IntFlag): + PF_X = 1 + PF_W = 2 + PF_R = 4 + + p_type: Type + p_flags: Flags + p_offset: int + p_vaddr: int + p_paddr: int + p_filesz: int + p_memsz: int + p_align: int + + def __init__(self, elf: Elf, off: int) -> None: + if not elf: + return + elf.seek(off) + self.offset = off + endian = gef.arch.endianness + if elf.e_class == Elf.Class.ELF_64_BITS: + p_type, p_flags, self.p_offset = elf.read_and_unpack(f"{endian}IIQ") + self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}QQ") + self.p_filesz, self.p_memsz, self.p_align = elf.read_and_unpack(f"{endian}QQQ") + else: + p_type, self.p_offset = elf.read_and_unpack(f"{endian}II") + self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}II") + self.p_filesz, self.p_memsz, p_flags, self.p_align = elf.read_and_unpack(f"{endian}IIII") + + self.p_type, self.p_flags = Phdr.Type(p_type), Phdr.Flags(p_flags) + return + + def __str__(self) -> str: + return (f"Phdr(offset={self.offset}, type={self.p_type.name}, flags={self.p_flags.name}, " + f"vaddr={self.p_vaddr}, paddr={self.p_paddr}, filesz={self.p_filesz}, " + f"memsz={self.p_memsz}, align={self.p_align})") + + +class Shdr: + class Type(enum.IntEnum): + SHT_NULL = 0 + SHT_PROGBITS = 1 + SHT_SYMTAB = 2 + SHT_STRTAB = 3 + SHT_RELA = 4 + SHT_HASH = 5 + SHT_DYNAMIC = 6 + SHT_NOTE = 7 + SHT_NOBITS = 8 + SHT_REL = 9 + SHT_SHLIB = 10 + SHT_DYNSYM = 11 + SHT_NUM = 12 + SHT_INIT_ARRAY = 14 + SHT_FINI_ARRAY = 15 + SHT_PREINIT_ARRAY = 16 + SHT_GROUP = 17 + SHT_SYMTAB_SHNDX = 18 + SHT_LOOS = 0x60000000 + SHT_GNU_ATTRIBUTES = 0x6ffffff5 + SHT_GNU_HASH = 0x6ffffff6 + SHT_GNU_LIBLIST = 0x6ffffff7 + SHT_CHECKSUM = 0x6ffffff8 + SHT_LOSUNW = 0x6ffffffa + SHT_SUNW_move = 0x6ffffffa + SHT_SUNW_COMDAT = 0x6ffffffb + SHT_SUNW_syminfo = 0x6ffffffc + SHT_GNU_verdef = 0x6ffffffd + SHT_GNU_verneed = 0x6ffffffe + SHT_GNU_versym = 0x6fffffff + SHT_LOPROC = 0x70000000 + SHT_ARM_EXIDX = 0x70000001 + SHT_X86_64_UNWIND = 0x70000001 + SHT_ARM_ATTRIBUTES = 0x70000003 + SHT_MIPS_OPTIONS = 0x7000000d + DT_MIPS_INTERFACE = 0x7000002a + SHT_HIPROC = 0x7fffffff + SHT_LOUSER = 0x80000000 + SHT_HIUSER = 0x8fffffff + UNKNOWN_SHDR = 0xffffffff + + @classmethod + def _missing_(cls, _:int) -> Type: + return cls.UNKNOWN_SHDR + + class Flags(enum.IntFlag): + WRITE = 1 + ALLOC = 2 + EXECINSTR = 4 + MERGE = 0x10 + STRINGS = 0x20 + INFO_LINK = 0x40 + LINK_ORDER = 0x80 + OS_NONCONFORMING = 0x100 + GROUP = 0x200 + TLS = 0x400 + COMPRESSED = 0x800 + RELA_LIVEPATCH = 0x00100000 + RO_AFTER_INIT = 0x00200000 + ORDERED = 0x40000000 + EXCLUDE = 0x80000000 + UNKNOWN_FLAG = 0xffffffff + + @classmethod + def _missing_(cls, _:int): + return cls.UNKNOWN_FLAG + + sh_name: int + sh_type: Type + sh_flags: Flags + sh_addr: int + sh_offset: int + sh_size: int + sh_link: int + sh_info: int + sh_addralign: int + sh_entsize: int + name: str + + def __init__(self, elf: Optional[Elf], off: int) -> None: + if elf is None: + return + elf.seek(off) + endian = gef.arch.endianness + if elf.e_class == Elf.Class.ELF_64_BITS: + self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}IIQ") + self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}QQ") + self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}QII") + self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}QQ") + else: + self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}III") + self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}II") + self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}III") + self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}II") + + self.sh_type = Shdr.Type(sh_type) + self.sh_flags = Shdr.Flags(sh_flags) + stroff = elf.e_shoff + elf.e_shentsize * elf.e_shstrndx + + if elf.e_class == Elf.Class.ELF_64_BITS: + elf.seek(stroff + 16 + 8) + offset = u64(elf.read(8)) + else: + elf.seek(stroff + 12 + 4) + offset = u32(elf.read(4)) + elf.seek(offset + self.sh_name) + self.name = "" + while True: + c = u8(elf.read(1)) + if c == 0: + break + self.name += chr(c) + return + + def __str__(self) -> str: + return (f"Shdr(name={self.name}, type={self.sh_type.name}, flags={self.sh_flags.name}, " + f"addr={self.sh_addr:#x}, offset={self.sh_offset}, size={self.sh_size}, link={self.sh_link}, " + f"info={self.sh_info}, addralign={self.sh_addralign}, entsize={self.sh_entsize})") + + +class Instruction: + """GEF representation of a CPU instruction.""" + + def __init__(self, address: int, location: str, mnemo: str, operands: List[str], opcodes: bytearray) -> None: + self.address, self.location, self.mnemonic, self.operands, self.opcodes = \ + address, location, mnemo, operands, opcodes + return + + # Allow formatting an instruction with {:o} to show opcodes. + # The number of bytes to display can be configured, e.g. {:4o} to only show 4 bytes of the opcodes + def __format__(self, format_spec: str) -> str: + if len(format_spec) == 0 or format_spec[-1] != "o": + return str(self) + + if format_spec == "o": + opcodes_len = len(self.opcodes) + else: + opcodes_len = int(format_spec[:-1]) + + opcodes_text = "".join(f"{b:02x}" for b in self.opcodes[:opcodes_len]) + if opcodes_len < len(self.opcodes): + opcodes_text += "..." + return (f"{self.address:#10x} {opcodes_text:{opcodes_len * 2 + 3:d}s} {self.location:16} " + f"{self.mnemonic:6} {', '.join(self.operands)}") + + def __str__(self) -> str: + return f"{self.address:#10x} {self.location:16} {self.mnemonic:6} {', '.join(self.operands)}" + + def is_valid(self) -> bool: + return "(bad)" not in self.mnemonic + + +@lru_cache() +def search_for_main_arena() -> int: + """A helper function to find the libc `main_arena` address, either from symbol or from its offset + from `__malloc_hook`.""" + try: + addr = parse_address(f"&{LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME}") + + except gdb.error: + malloc_hook_addr = parse_address("(void *)&__malloc_hook") + + if is_x86(): + addr = align_address_to_size(malloc_hook_addr + gef.arch.ptrsize, 0x20) + elif is_arch(Elf.Abi.AARCH64): + addr = malloc_hook_addr - gef.arch.ptrsize*2 - MallocStateStruct("*0").struct_size + elif is_arch(Elf.Abi.ARM): + addr = malloc_hook_addr - gef.arch.ptrsize - MallocStateStruct("*0").struct_size + else: + raise OSError(f"Cannot find main_arena for {gef.arch.arch}") + + return addr + + +class MallocStateStruct: + """GEF representation of malloc_state + from https://github.com/bminor/glibc/blob/glibc-2.28/malloc/malloc.c#L1658""" + + def __init__(self, addr: str) -> None: + try: + self.__addr = parse_address(f"&{addr}") + except gdb.error: + self.__addr = search_for_main_arena() + # if `search_for_main_arena` throws `gdb.error` on symbol lookup: + # it means the session is not started, so just propagate the exception + + self.num_fastbins = 10 + self.num_bins = 254 + + self.int_size = cached_lookup_type("int").sizeof + self.size_t = cached_lookup_type("size_t") + if not self.size_t: + ptr_type = "unsigned long" if gef.arch.ptrsize == 8 else "unsigned int" + self.size_t = cached_lookup_type(ptr_type) + + # Account for separation of have_fastchunks flag into its own field + # within the malloc_state struct in GLIBC >= 2.27 + # https://sourceware.org/git/?p=glibc.git;a=commit;h=e956075a5a2044d05ce48b905b10270ed4a63e87 + # Be aware you could see this change backported into GLIBC release + # branches. + if get_libc_version() >= (2, 27): + self.fastbin_offset = align_address_to_size(self.int_size * 3, 8) + else: + self.fastbin_offset = self.int_size * 2 + return + + # struct offsets + @property + def addr(self) -> int: + return self.__addr + + @property + def fastbins_addr(self) -> int: + return self.__addr + self.fastbin_offset + + @property + def top_addr(self) -> int: + return self.fastbins_addr + self.num_fastbins * gef.arch.ptrsize + + @property + def last_remainder_addr(self) -> int: + return self.top_addr + gef.arch.ptrsize + + @property + def bins_addr(self) -> int: + return self.last_remainder_addr + gef.arch.ptrsize + + @property + def next_addr(self) -> int: + return self.bins_addr + self.num_bins * gef.arch.ptrsize + self.int_size * 4 + + @property + def next_free_addr(self) -> int: + return self.next_addr + gef.arch.ptrsize + + @property + def system_mem_addr(self) -> int: + return self.next_free_addr + gef.arch.ptrsize * 2 + + @property + def struct_size(self) -> int: + return self.system_mem_addr + gef.arch.ptrsize * 2 - self.__addr + + # struct members + @property + def fastbinsY(self) -> "gdb.Value": + return self.get_size_t_array(self.fastbins_addr, self.num_fastbins) + + @property + def top(self) -> "gdb.Value": + return self.get_size_t_pointer(self.top_addr) + + @property + def last_remainder(self) -> "gdb.Value": + return self.get_size_t_pointer(self.last_remainder_addr) + + @property + def bins(self) -> "gdb.Value": + return self.get_size_t_array(self.bins_addr, self.num_bins) + + @property + def next(self) -> "gdb.Value": + return self.get_size_t_pointer(self.next_addr) + + @property + def next_free(self) -> "gdb.Value": + return self.get_size_t_pointer(self.next_free_addr) + + @property + def system_mem(self) -> "gdb.Value": + return self.get_size_t(self.system_mem_addr) + + # helper methods + def get_size_t(self, addr: int) -> "gdb.Value": + return dereference(addr).cast(self.size_t) + + def get_size_t_pointer(self, addr: int) -> "gdb.Value": + size_t_pointer = self.size_t.pointer() + return dereference(addr).cast(size_t_pointer) + + def get_size_t_array(self, addr: int, length: int) -> "gdb.Value": + size_t_array = self.size_t.array(length) + return dereference(addr).cast(size_t_array) + + def __getitem__(self, item: str) -> Any: + return getattr(self, item) + + +class GlibcHeapInfo: + """Glibc heap_info struct + See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L64""" + + def __init__(self, addr: Union[int, str]) -> None: + self.__addr = addr if type(addr) is int else parse_address(addr) + self.size_t = cached_lookup_type("size_t") + if not self.size_t: + ptr_type = "unsigned long" if gef.arch.ptrsize == 8 else "unsigned int" + self.size_t = cached_lookup_type(ptr_type) + + @property + def addr(self) -> int: + return self.__addr + + @property + def ar_ptr_addr(self) -> int: + return self.addr + + @property + def prev_addr(self) -> int: + return self.ar_ptr_addr + gef.arch.ptrsize + + @property + def size_addr(self) -> int: + return self.prev_addr + gef.arch.ptrsize + + @property + def mprotect_size_addr(self) -> int: + return self.size_addr + self.size_t.sizeof + + @property + def ar_ptr(self) -> "gdb.Value": + return self._get_size_t_pointer(self.ar_ptr_addr) + + @property + def prev(self) -> "gdb.Value": + return self._get_size_t_pointer(self.prev_addr) + + @property + def size(self) -> "gdb.Value": + return self._get_size_t(self.size_addr) + + @property + def mprotect_size(self) -> "gdb.Value": + return self._get_size_t(self.mprotect_size_addr) + + # helper methods + def _get_size_t_pointer(self, addr: int) -> "gdb.Value": + size_t_pointer = self.size_t.pointer() + return dereference(addr).cast(size_t_pointer) + + def _get_size_t(self, addr: int) -> "gdb.Value": + return dereference(addr).cast(self.size_t) + + +class GlibcArena: + """Glibc arena class + Ref: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1671""" + + def __init__(self, addr: str) -> None: + self.__arena: Union["gdb.Value", MallocStateStruct] + try: + arena = gdb.parse_and_eval(addr) + malloc_state_t = cached_lookup_type("struct malloc_state") + self.__arena = arena.cast(malloc_state_t) # here __arena becomes a "gdb.Value" + self.__addr = int(arena.address) + self.struct_size: int = malloc_state_t.sizeof + except: + self.__arena = MallocStateStruct(addr) # here __arena becomes MallocStateStruct + self.__addr = self.__arena.addr + + try: + self.top = int(self.top) + self.last_remainder = int(self.last_remainder) + self.n = int(self.next) + self.nfree = int(self.next_free) + self.sysmem = int(self.system_mem) + except gdb.error as e: + err("Glibc arena: {}".format(e)) + return + + def __getitem__(self, item: Any) -> Any: + return self.__arena[item] + + def __getattr__(self, item: Any) -> Any: + return self.__arena[item] + + def __int__(self) -> int: + return self.__addr + + def __iter__(self) -> Generator["GlibcArena", None, None]: + yield self + current_arena = self + + while True: + next_arena_address = int(current_arena.next) + if next_arena_address == int(gef.heap.main_arena): + break + + current_arena = GlibcArena(f"*{next_arena_address:#x} ") + yield current_arena + return + + def __eq__(self, other: "GlibcArena") -> bool: + # You cannot have 2 arenas at the same address, so this check should be enough + return self.__addr == int(other) + + def fastbin(self, i: int) -> Optional["GlibcChunk"]: + """Return head chunk in fastbinsY[i].""" + addr = int(self.fastbinsY[i]) + if addr == 0: + return None + return GlibcChunk(addr + 2 * gef.arch.ptrsize) + + def bin(self, i: int) -> Tuple[int, int]: + idx = i * 2 + fd = int(self.bins[idx]) + bw = int(self.bins[idx + 1]) + return fd, bw + + def is_main_arena(self) -> bool: + return int(self) == int(gef.heap.main_arena) + + def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]: + if self.is_main_arena(): + heap_section = gef.heap.base_address + if not heap_section: + return None + return heap_section + _addr = int(self) + self.struct_size + if allow_unaligned: + return _addr + return malloc_align_address(_addr) + + def get_heap_info_list(self) -> Optional[List[GlibcHeapInfo]]: + if self.is_main_arena(): + return None + heap_addr = self.get_heap_for_ptr(self.top) + heap_infos = [GlibcHeapInfo(heap_addr)] + while heap_infos[-1].prev != 0: + prev = int(heap_infos[-1].prev) + heap_info = GlibcHeapInfo(prev) + heap_infos.append(heap_info) + return heap_infos[::-1] + + @staticmethod + def get_heap_for_ptr(ptr: int) -> int: + """Find the corresponding heap for a given pointer (int). + See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L129""" + if is_32bit(): + default_mmap_threshold_max = 512 * 1024 + else: # 64bit + default_mmap_threshold_max = 4 * 1024 * 1024 * cached_lookup_type("long").sizeof + heap_max_size = 2 * default_mmap_threshold_max + return ptr & ~(heap_max_size - 1) + + def __str__(self) -> str: + return (f"{Color.colorify('Arena', 'blue bold underline')}(base={self.__addr:#x}, top={self.top:#x}, " + f"last_remainder={self.last_remainder:#x}, next={self.n:#x}, next_free={self.nfree:#x}, " + f"system_mem={self.sysmem:#x})") + + @property + def addr(self) -> int: + return int(self) + + +class GlibcChunk: + """Glibc chunk class. The default behavior (from_base=False) is to interpret the data starting at the memory + address pointed to as the chunk data. Setting from_base to True instead treats that data as the chunk header. + Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/.""" + + def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None: + self.ptrsize = gef.arch.ptrsize + if from_base: + self.data_address = addr + 2 * self.ptrsize + else: + self.data_address = addr + if not allow_unaligned: + self.data_address = malloc_align_address(self.data_address) + self.base_address = addr - 2 * self.ptrsize + + self.size_addr = int(self.data_address - self.ptrsize) + self.prev_size_addr = self.base_address + return + + def get_chunk_size(self) -> int: + return gef.memory.read_integer(self.size_addr) & (~0x07) + + @property + def size(self) -> int: + return self.get_chunk_size() + + def get_usable_size(self) -> int: + # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537 + cursz = self.get_chunk_size() + if cursz == 0: return cursz + if self.has_m_bit(): return cursz - 2 * self.ptrsize + return cursz - self.ptrsize + + @property + def usable_size(self) -> int: + return self.get_usable_size() + + def get_prev_chunk_size(self) -> int: + return gef.memory.read_integer(self.prev_size_addr) + + def __iter__(self) -> Generator["GlibcChunk", None, None]: + current_chunk = self + top = gef.heap.main_arena.top + + while True: + yield current_chunk + + if current_chunk.base_address == top: + break + + if current_chunk.size == 0: + break + + next_chunk_addr = current_chunk.get_next_chunk_addr() + + if not Address(value=next_chunk_addr).valid: + break + + next_chunk = current_chunk.get_next_chunk() + if next_chunk is None: + break + + current_chunk = next_chunk + return + + def get_next_chunk(self, allow_unaligned: bool = False) -> "GlibcChunk": + addr = self.get_next_chunk_addr() + return GlibcChunk(addr, allow_unaligned=allow_unaligned) + + def get_next_chunk_addr(self) -> int: + return self.data_address + self.get_chunk_size() + + # if free-ed functions + def get_fwd_ptr(self, sll: bool) -> int: + # Not a single-linked-list (sll) or no Safe-Linking support yet + if not sll or get_libc_version() < (2, 32): + return gef.memory.read_integer(self.data_address) + # Unmask ("reveal") the Safe-Linking pointer + else: + return gef.memory.read_integer(self.data_address) ^ (self.data_address >> 12) + + @property + def fwd(self) -> int: + return self.get_fwd_ptr(False) + + fd = fwd # for compat + + def get_bkw_ptr(self) -> int: + return gef.memory.read_integer(self.data_address + self.ptrsize) + + @property + def bck(self) -> int: + return self.get_bkw_ptr() + + bk = bck # for compat + # endif free-ed functions + + def has_p_bit(self) -> bool: + return bool(gef.memory.read_integer(self.size_addr) & 0x01) + + def has_m_bit(self) -> bool: + return bool(gef.memory.read_integer(self.size_addr) & 0x02) + + def has_n_bit(self) -> bool: + return bool(gef.memory.read_integer(self.size_addr) & 0x04) + + def is_used(self) -> bool: + """Check if the current block is used by: + - checking the M bit is true + - or checking that next chunk PREV_INUSE flag is true""" + if self.has_m_bit(): + return True + + next_chunk = self.get_next_chunk() + return True if next_chunk.has_p_bit() else False + + def str_chunk_size_flag(self) -> str: + msg = [] + msg.append(f"PREV_INUSE flag: {Color.greenify('On') if self.has_p_bit() else Color.redify('Off')}") + msg.append(f"IS_MMAPPED flag: {Color.greenify('On') if self.has_m_bit() else Color.redify('Off')}") + msg.append(f"NON_MAIN_ARENA flag: {Color.greenify('On') if self.has_n_bit() else Color.redify('Off')}") + return "\n".join(msg) + + def _str_sizes(self) -> str: + msg = [] + failed = False + + try: + msg.append("Chunk size: {0:d} ({0:#x})".format(self.get_chunk_size())) + msg.append("Usable size: {0:d} ({0:#x})".format(self.get_usable_size())) + failed = True + except gdb.MemoryError: + msg.append(f"Chunk size: Cannot read at {self.size_addr:#x} (corrupted?)") + + try: + msg.append("Previous chunk size: {0:d} ({0:#x})".format(self.get_prev_chunk_size())) + failed = True + except gdb.MemoryError: + msg.append(f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)") + + if failed: + msg.append(self.str_chunk_size_flag()) + + return "\n".join(msg) + + def _str_pointers(self) -> str: + fwd = self.data_address + bkw = self.data_address + self.ptrsize + + msg = [] + try: + msg.append(f"Forward pointer: {self.get_fwd_ptr(False):#x}") + except gdb.MemoryError: + msg.append(f"Forward pointer: {fwd:#x} (corrupted?)") + + try: + msg.append(f"Backward pointer: {self.get_bkw_ptr():#x}") + except gdb.MemoryError: + msg.append(f"Backward pointer: {bkw:#x} (corrupted?)") + + return "\n".join(msg) + + def str_as_alloced(self) -> str: + return self._str_sizes() + + def str_as_freed(self) -> str: + return f"{self._str_sizes()}\n\n{self._str_pointers()}" + + def flags_as_string(self) -> str: + flags = [] + if self.has_p_bit(): + flags.append(Color.colorify("PREV_INUSE", "red bold")) + else: + flags.append(Color.colorify("! PREV_INUSE", "green bold")) + if self.has_m_bit(): + flags.append(Color.colorify("IS_MMAPPED", "red bold")) + if self.has_n_bit(): + flags.append(Color.colorify("NON_MAIN_ARENA", "red bold")) + return "|".join(flags) + + def __str__(self) -> str: + return (f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, " + f"size={self.get_chunk_size():#x}, flags={self.flags_as_string()})") + + def psprint(self) -> str: + msg = [] + msg.append(str(self)) + if self.is_used(): + msg.append(self.str_as_alloced()) + else: + msg.append(self.str_as_freed()) + + return "\n".join(msg) + "\n" + + +@lru_cache() +def get_libc_version() -> Tuple[int, ...]: + sections = gef.memory.maps + for section in sections: + match = re.search(r"libc6?[-_](\d+)\.(\d+)\.so", section.path) + if match: + return tuple(int(_) for _ in match.groups()) + if "libc" in section.path: + try: + with open(section.path, "rb") as f: + data = f.read() + except OSError: + continue + match = re.search(PATTERN_LIBC_VERSION, data) + if match: + return tuple(int(_) for _ in match.groups()) + return 0, 0 + + +def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str: + """Print a centered title.""" + cols = get_terminal_size()[1] + nb = (cols - len(text) - 2) // 2 + if color is None: + color = gef.config["theme.default_title_line"] + if msg_color is None: + msg_color = gef.config["theme.default_title_message"] + + msg = [Color.colorify(f"{HORIZONTAL_LINE * nb} ", color), + Color.colorify(text, msg_color), + Color.colorify(f" {HORIZONTAL_LINE * nb}", color)] + return "".join(msg) + + +def err(msg: str) -> None: + gef_print(f"{Color.colorify('[!]', 'bold red')} {msg}") + return + + +def warn(msg: str) -> None: + gef_print(f"{Color.colorify('[*]', 'bold yellow')} {msg}") + return + + +def ok(msg: str) -> None: + gef_print(f"{Color.colorify('[+]', 'bold green')} {msg}") + return + + +def info(msg: str) -> None: + gef_print(f"{Color.colorify('[+]', 'bold blue')} {msg}") + return + + +def push_context_message(level: str, message: str) -> None: + """Push the message to be displayed the next time the context is invoked.""" + if level not in ("error", "warn", "ok", "info"): + err(f"Invalid level '{level}', discarding message") + return + gef.ui.context_messages.append((level, message)) + return + + +def show_last_exception() -> None: + """Display the last Python exception.""" + + def _show_code_line(fname: str, idx: int) -> str: + fname = os.path.expanduser(os.path.expandvars(fname)) + with open(fname, "r") as f: + __data = f.readlines() + return __data[idx - 1] if 0 < idx < len(__data) else "" + + gef_print("") + exc_type, exc_value, exc_traceback = sys.exc_info() + + gef_print(" Exception raised ".center(80, HORIZONTAL_LINE)) + gef_print(f"{Color.colorify(exc_type.__name__, 'bold underline red')}: {exc_value}") + gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE)) + + for fs in traceback.extract_tb(exc_traceback)[::-1]: + filename, lineno, method, code = fs + + if not code or not code.strip(): + code = _show_code_line(filename, lineno) + + gef_print(f"""{DOWN_ARROW} File "{Color.yellowify(filename)}", line {lineno:d}, in {Color.greenify(method)}()""") + gef_print(f" {RIGHT_ARROW} {code}") + + gef_print(" Version ".center(80, HORIZONTAL_LINE)) + gdb.execute("version full") + gef_print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE)) + gdb.execute("show commands") + gef_print(" Runtime environment ".center(80, HORIZONTAL_LINE)) + gef_print(f"* GDB: {gdb.VERSION}") + gef_print(f"* Python: {sys.version_info.major:d}.{sys.version_info.minor:d}.{sys.version_info.micro:d} - {sys.version_info.releaselevel}") + gef_print(f"* OS: {platform.system()} - {platform.release()} ({platform.machine()})") + + try: + lsb_release = which("lsb_release") + gdb.execute(f"!{lsb_release} -a") + except FileNotFoundError: + gef_print("lsb_release is missing, cannot collect additional debug information") + + gef_print(HORIZONTAL_LINE*80) + gef_print("") + return + + +def gef_pystring(x: bytes) -> str: + """Returns a sanitized version as string of the bytes list given in input.""" + res = str(x, encoding="utf-8") + substs = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t"), ("\v", "\\v"), ("\b", "\\b"), ] + for x, y in substs: res = res.replace(x, y) + return res + + +def gef_pybytes(x: str) -> bytes: + """Returns an immutable bytes list from the string given as input.""" + return bytes(str(x), encoding="utf-8") + + +@lru_cache() +def which(program: str) -> Optional[pathlib.Path]: + """Locate a command on the filesystem.""" + for path in os.environ["PATH"].split(os.pathsep): + dirname = pathlib.Path(path) + fpath = dirname / program + if os.access(fpath, os.X_OK): + return fpath + + raise FileNotFoundError(f"Missing file `{program}`") + + +def style_byte(b: int, color: bool = True) -> str: + style = { + "nonprintable": "yellow", + "printable": "white", + "00": "gray", + "0a": "blue", + "ff": "green", + } + sbyte = f"{b:02x}" + if not color or gef.config["highlight.regex"]: + return sbyte + + if sbyte in style: + st = style[sbyte] + elif chr(b) in (string.ascii_letters + string.digits + string.punctuation + " "): + st = style.get("printable") + else: + st = style.get("nonprintable") + if st: + sbyte = Color.colorify(sbyte, st) + return sbyte + + +def hexdump(source: ByteString, length: int = 0x10, separator: str = ".", show_raw: bool = False, show_symbol: bool = True, base: int = 0x00) -> str: + """Return the hexdump of `src` argument. + @param source *MUST* be of type bytes or bytearray + @param length is the length of items per line + @param separator is the default character to use if one byte is not printable + @param show_raw if True, do not add the line nor the text translation + @param base is the start address of the block being hexdump + @return a string with the hexdump""" + result = [] + align = gef.arch.ptrsize * 2 + 2 if is_alive() else 18 + + for i in range(0, len(source), length): + chunk = bytearray(source[i : i + length]) + hexa = " ".join([style_byte(b, color=not show_raw) for b in chunk]) + + if show_raw: + result.append(hexa) + continue + + text = "".join([chr(b) if 0x20 <= b < 0x7F else separator for b in chunk]) + if show_symbol: + sym = gdb_get_location_from_symbol(base + i) + sym = "<{:s}+{:04x}>".format(*sym) if sym else "" + else: + sym = "" + + result.append(f"{base + i:#0{align}x} {sym} {hexa:<{3 * length}} {text}") + return "\n".join(result) + + +def is_debug() -> bool: + """Check if debug mode is enabled.""" + return gef.config["gef.debug"] is True + + +def hide_context() -> bool: + """Helper function to hide the context pane.""" + gef.ui.context_hidden = True + return True + + +def unhide_context() -> bool: + """Helper function to unhide the context pane.""" + gef.ui.context_hidden = False + return True + + +class RedirectOutputContext(): + def __init__(self, to: str = "/dev/null") -> None: + self.redirection_target_file = to + return + + def __enter__(self) -> None: + """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" + gdb.execute("set logging overwrite") + gdb.execute(f"set logging file {self.redirection_target_file}") + gdb.execute("set logging redirect on") + gdb.execute("set logging on") + return + + def __exit__(self, *exc: Any) -> None: + """Disable the output redirection, if any.""" + gdb.execute("set logging off") + gdb.execute("set logging redirect off") + return + + +def enable_redirect_output(to_file: str = "/dev/null") -> None: + """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`.""" + gdb.execute("set logging overwrite") + gdb.execute(f"set logging file {to_file}") + gdb.execute("set logging redirect on") + gdb.execute("set logging on") + return + + +def disable_redirect_output() -> None: + """Disable the output redirection, if any.""" + gdb.execute("set logging off") + gdb.execute("set logging redirect off") + return + + +def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path: + """Recursive mkdir() creation. If successful, return the absolute path of the directory created.""" + fpath = pathlib.Path(path) + if not fpath.is_dir(): + fpath.mkdir(mode=mode, exist_ok=True, parents=True) + return fpath.absolute() + + +@lru_cache() +def gdb_lookup_symbol(sym: str) -> Optional[Tuple[Optional[str], Optional[Tuple[gdb.Symtab_and_line, ...]]]]: + """Fetch the proper symbol or None if not defined.""" + try: + return gdb.decode_line(sym)[1] + except gdb.error: + return None + + +@lru_cache(maxsize=512) +def gdb_get_location_from_symbol(address: int) -> Optional[Tuple[str, int]]: + """Retrieve the location of the `address` argument from the symbol table. + Return a tuple with the name and offset if found, None otherwise.""" + # this is horrible, ugly hack and shitty perf... + # find a *clean* way to get gdb.Location from an address + sym = gdb.execute(f"info symbol {address:#x}", to_string=True) + if sym.startswith("No symbol matches"): + return None + + i = sym.find(" in section ") + sym = sym[:i].split() + name, offset = sym[0], 0 + if len(sym) == 3 and sym[2].isdigit(): + offset = int(sym[2]) + return name, offset + + +def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None, None]: + """Disassemble instructions from `start_pc` (Integer). Accepts the following named parameters: + - `end_pc` (Integer) only instructions whose start address fall in the interval from start_pc to end_pc are returned. + - `count` (Integer) list at most this many disassembled instructions + If `end_pc` and `count` are not provided, the function will behave as if `count=1`. + Return an iterator of Instruction objects + """ + frame = gdb.selected_frame() + arch = frame.architecture() + + for insn in arch.disassemble(start_pc, **kwargs): + address = insn["addr"] + asm = insn["asm"].rstrip().split(None, 1) + if len(asm) > 1: + mnemo, operands = asm + operands = operands.split(",") + else: + mnemo, operands = asm[0], [] + + loc = gdb_get_location_from_symbol(address) + location = "<{}+{}>".format(*loc) if loc else "" + + opcodes = gef.memory.read(insn["addr"], insn["length"]) + + yield Instruction(address, location, mnemo, operands, opcodes) + + +def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int]: + """Return the address (Integer) of the `n`-th instruction before `addr`.""" + # fixed-length ABI + if gef.arch.instruction_length: + return max(0, addr - n * gef.arch.instruction_length) + + # variable-length ABI + cur_insn_addr = gef_current_instruction(addr).address + + # we try to find a good set of previous instructions by "guessing" disassembling backwards + # the 15 comes from the longest instruction valid size + for i in range(15 * n, 0, -1): + try: + insns = list(gdb_disassemble(addr - i, end_pc=cur_insn_addr)) + except gdb.MemoryError: + # this is because we can hit an unmapped page trying to read backward + break + + # 1. check that the disassembled instructions list size can satisfy + if len(insns) < n + 1: # we expect the current instruction plus the n before it + continue + + # If the list of instructions is longer than what we need, then we + # could get lucky and already have more than what we need, so slice down + insns = insns[-n - 1 :] + + # 2. check that the sequence ends with the current address + if insns[-1].address != cur_insn_addr: + continue + + # 3. check all instructions are valid + if all(insn.is_valid() for insn in insns): + return insns[0].address + + return None + + +def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int: + """Return the address (Integer) of the `n`-th instruction after `addr`.""" + # fixed-length ABI + if gef.arch.instruction_length: + return addr + n * gef.arch.instruction_length + + # variable-length ABI + insn = list(gdb_disassemble(addr, count=n))[-1] + return insn.address + + +def gef_instruction_n(addr: int, n: int) -> Instruction: + """Return the `n`-th instruction after `addr` as an Instruction object.""" + return list(gdb_disassemble(addr, count=n + 1))[n] + + +def gef_get_instruction_at(addr: int) -> Instruction: + """Return the full Instruction found at the specified address.""" + insn = next(gef_disassemble(addr, 1)) + return insn + + +def gef_current_instruction(addr: int) -> Instruction: + """Return the current instruction as an Instruction object.""" + return gef_instruction_n(addr, 0) + + +def gef_next_instruction(addr: int) -> Instruction: + """Return the next instruction as an Instruction object.""" + return gef_instruction_n(addr, 1) + + +def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Instruction, None, None]: + """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr`. + Return an iterator of Instruction objects.""" + nb_insn = max(1, nb_insn) + + if nb_prev: + start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev) + if start_addr: + for insn in gdb_disassemble(start_addr, count=nb_prev): + if insn.address == addr: break + yield insn + + for insn in gdb_disassemble(addr, count=nb_insn): + yield insn + + +def capstone_disassemble(location: int, nb_insn: int, **kwargs: Any) -> Generator[Instruction, None, None]: + """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before + `addr` using the Capstone-Engine disassembler, if available. + Return an iterator of Instruction objects.""" + + def cs_insn_to_gef_insn(cs_insn: "capstone.CsInsn") -> Instruction: + sym_info = gdb_get_location_from_symbol(cs_insn.address) + loc = "<{}+{}>".format(*sym_info) if sym_info else "" + ops = [] + cs_insn.op_str.split(", ") + return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops, cs_insn.bytes) + + capstone = sys.modules["capstone"] + arch, mode = get_capstone_arch(arch=kwargs.get("arch"), mode=kwargs.get("mode"), endian=kwargs.get("endian")) + cs = capstone.Cs(arch, mode) + cs.detail = True + + page_start = align_address_to_page(location) + offset = location - page_start + pc = gef.arch.pc + + skip = int(kwargs.get("skip", 0)) + nb_prev = int(kwargs.get("nb_prev", 0)) + if nb_prev > 0: + location = gdb_get_nth_previous_instruction_address(pc, nb_prev) + nb_insn += nb_prev + + code = kwargs.get("code", gef.memory.read(location, gef.session.pagesize - offset - 1)) + for insn in cs.disasm(code, location): + if skip: + skip -= 1 + continue + nb_insn -= 1 + yield cs_insn_to_gef_insn(insn) + if nb_insn == 0: + break + return + + +def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, List[str]]: + """Execute an external command and return the result.""" + res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False)) + return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res) + + +def gef_execute_gdb_script(commands: str) -> None: + """Execute the parameter `source` as GDB command. This is done by writing `commands` to + a temporary file, which is then executed via GDB `source` command. The tempfile is then deleted.""" + fd, fname = tempfile.mkstemp(suffix=".gdb", prefix="gef_") + with os.fdopen(fd, "w") as f: + f.write(commands) + f.flush() + + fname = pathlib.Path(fname) + if fname.is_file() and os.access(fname, os.R_OK): + gdb.execute(f"source {fname}") + fname.unlink() + return + + +@lru_cache(32) +def checksec(filename: str) -> Dict[str, bool]: + """Check the security property of the ELF binary. The following properties are: + - Canary + - NX + - PIE + - Fortify + - Partial/Full RelRO. + Return a dict() with the different keys mentioned above, and the boolean + associated whether the protection was found.""" + readelf = gef.session.constants["readelf"] + + def __check_security_property(opt: str, filename: str, pattern: str) -> bool: + cmd = [readelf,] + cmd += opt.split() + cmd += [filename,] + lines = gef_execute_external(cmd, as_list=True) + for line in lines: + if re.search(pattern, line): + return True + return False + + results = collections.OrderedDict() + results["Canary"] = __check_security_property("-s", filename, r"__stack_chk_fail") is True + has_gnu_stack = __check_security_property("-W -l", filename, r"GNU_STACK") is True + if has_gnu_stack: + results["NX"] = __check_security_property("-W -l", filename, r"GNU_STACK.*RWE") is False + else: + results["NX"] = False + results["PIE"] = __check_security_property("-h", filename, r":.*EXEC") is False + results["Fortify"] = __check_security_property("-s", filename, r"_chk@GLIBC") is True + results["Partial RelRO"] = __check_security_property("-l", filename, r"GNU_RELRO") is True + results["Full RelRO"] = results["Partial RelRO"] and __check_security_property("-d", filename, r"BIND_NOW") is True + return results + + +@lru_cache() +def get_arch() -> str: + """Return the binary's architecture.""" + if is_alive(): + arch = gdb.selected_frame().architecture() + return arch.name() + + arch_str = gdb.execute("show architecture", to_string=True).strip() + if "The target architecture is set automatically (currently " in arch_str: + arch_str = arch_str.split("(currently ", 1)[1] + arch_str = arch_str.split(")", 1)[0] + elif "The target architecture is assumed to be " in arch_str: + arch_str = arch_str.replace("The target architecture is assumed to be ", "") + elif "The target architecture is set to " in arch_str: + # GDB version >= 10.1 + arch_str = re.findall(r"\"(.+)\"", arch_str)[0] + else: + # Unknown, we throw an exception to be safe + raise RuntimeError(f"Unknown architecture: {arch_str}") + return arch_str + + +@deprecated("Use `gef.binary.entry_point` instead") +def get_entry_point() -> Optional[int]: + """Return the binary entry point.""" + return gef.binary.entry_point if gef.binary else None + + +def is_pie(fpath: str) -> bool: + return checksec(fpath)["PIE"] + + +@deprecated("Prefer `gef.arch.endianness == Endianness.BIG_ENDIAN`") +def is_big_endian() -> bool: + return gef.arch.endianness == Endianness.BIG_ENDIAN + + +@deprecated("gef.arch.endianness == Endianness.LITTLE_ENDIAN") +def is_little_endian() -> bool: + return gef.arch.endianness == Endianness.LITTLE_ENDIAN + + +def flags_to_human(reg_value: int, value_table: Dict[int, str]) -> str: + """Return a human readable string showing the flag states.""" + flags = [] + for i in value_table: + flag_str = Color.boldify(value_table[i].upper()) if reg_value & (1<<i) else value_table[i].lower() + flags.append(flag_str) + return f"[{' '.join(flags)}]" + + +@lru_cache() +def get_section_base_address(name: str) -> Optional[int]: + section = process_lookup_path(name) + return section.page_start if section else None + + +@lru_cache() +def get_zone_base_address(name: str) -> Optional[int]: + zone = file_lookup_name_path(name, get_filepath()) + return zone.zone_start if zone else None + + +# +# Architecture classes +# +def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]: + """Class decorator for declaring an architecture to GEF.""" + global __registered_architectures__ + for key in cls.aliases: + __registered_architectures__[key] = cls + return cls + + +class Architecture(metaclass=abc.ABCMeta): + """Generic metaclass for the architecture supported by GEF.""" + + @abc.abstractproperty + def all_registers(self) -> List[str]: pass + @abc.abstractproperty + def instruction_length(self) -> Optional[int]: pass + @abc.abstractproperty + def nop_insn(self) -> bytes: pass + @abc.abstractproperty + def return_register(self) -> str: pass + @abc.abstractproperty + def flag_register(self) -> Optional[str]: pass + @abc.abstractproperty + def flags_table(self) -> Optional[Dict[int, str]]: pass + @abc.abstractproperty + def function_parameters(self) -> List[str]: pass + @abc.abstractmethod + def flag_register_to_human(self, val: Optional[int] = None) -> str: pass + @abc.abstractmethod + def is_call(self, insn: Instruction) -> bool: pass + @abc.abstractmethod + def is_ret(self, insn: Instruction) -> bool: pass + @abc.abstractmethod + def is_conditional_branch(self, insn: Instruction) -> bool: pass + @abc.abstractmethod + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: pass + @abc.abstractmethod + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: pass + @classmethod + @abc.abstractmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: pass + + arch = "" + mode = "" + aliases: Tuple[Union[str, int], ...] = [] + special_registers: List[str] = [] + + def reset_caches(self) -> None: + self.__get_register_for_selected_frame.cache_clear() + return + + def __get_register(self, regname: str) -> Optional[int]: + """Return a register's value.""" + curframe = gdb.selected_frame() + key = curframe.pc() ^ int(curframe.read_register('sp')) # todo: check when/if gdb.Frame implements `level()` + return self.__get_register_for_selected_frame(regname, key) + + @lru_cache() + def __get_register_for_selected_frame(self, regname: str, hash_key: int) -> Optional[int]: + # 1st chance + try: + return parse_address(regname) + except gdb.error: + pass + + # 2nd chance + try: + regname = regname.lstrip("$") + value = gdb.selected_frame().read_register(regname) + return int(value) + except (ValueError, gdb.error): + pass + return None + + def register(self, name: str) -> Optional[int]: + return self.__get_register(name) + + @property + def registers(self) -> Generator[str, None, None]: + yield from self.all_registers + + @property + def pc(self) -> Optional[int]: + return self.register("$pc") + + @property + def sp(self) -> Optional[int]: + return self.register("$sp") + + @property + def fp(self) -> Optional[int]: + return self.register("$fp") + + _ptrsize = None + @property + def ptrsize(self) -> int: + if not self._ptrsize: + res = cached_lookup_type("size_t") + if res is not None: + self._ptrsize = res.sizeof + else: + self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof + return self._ptrsize + + _endianness = None + @property + def endianness(self) -> Endianness: + if not self._endianness: + output = gdb.execute("show endian", to_string=True).strip().lower() + if "little endian" in output: + self._endianness = Endianness.LITTLE_ENDIAN + elif "big endian" in output: + self._endianness = Endianness.BIG_ENDIAN + else: + raise OSError(f"No valid endianess found in '{output}'") + return self._endianness + + def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: + """Retrieves the correct parameter used for the current function call.""" + reg = self.function_parameters[i] + val = self.register(reg) + key = reg + return key, val + + +class GenericArchitecture(Architecture): + + arch = "Generic" + mode = "" + all_registers = () + instruction_length = 0 + ptrsize = 0 + return_register = "" + function_parameters = () + syscall_register = "" + syscall_instructions = () + nop_insn = b"" + flag_register = None + flags_table = None + def flag_register_to_human(self, val: Optional[int] = None) -> str: return "" + def is_call(self, insn: Instruction) -> bool: return False + def is_ret(self, insn: Instruction) -> bool: return False + def is_conditional_branch(self, insn: Instruction) -> bool: return False + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: return False, "" + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: return 0 + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + raise OSError(f"Architecture {cls.arch} not supported") + + +@register_architecture +class RISCV(Architecture): + arch = "RISCV" + mode = "RISCV" + aliases = ("RISCV",) + + all_registers = ["$zero", "$ra", "$sp", "$gp", "$tp", "$t0", "$t1", + "$t2", "$fp", "$s1", "$a0", "$a1", "$a2", "$a3", + "$a4", "$a5", "$a6", "$a7", "$s2", "$s3", "$s4", + "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11", + "$t3", "$t4", "$t5", "$t6",] + return_register = "$a0" + function_parameters = ["$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7"] + syscall_register = "$a7" + syscall_instructions = ["ecall"] + nop_insn = b"\x00\x00\x00\x13" + # RISC-V has no flags registers + flag_register = None + flag_register_to_human = None + flags_table = None + + @property + def instruction_length(self) -> int: + return 4 + + def is_call(self, insn: Instruction) -> bool: + return insn.mnemonic == "call" + + def is_ret(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + if mnemo == "ret": + return True + elif (mnemo == "jalr" and insn.operands[0] == "zero" and + insn.operands[1] == "ra" and insn.operands[2] == 0): + return True + elif (mnemo == "c.jalr" and insn.operands[0] == "ra"): + return True + return False + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + raise OSError(f"Architecture {cls.arch} not supported yet") + + def is_conditional_branch(self, insn: Instruction) -> bool: + return insn.mnemonic.startswith("b") + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + def long_to_twos_complement(v: int) -> int: + """Convert a python long value to its two's complement.""" + if is_32bit(): + if v & 0x80000000: + return v - 0x100000000 + elif is_64bit(): + if v & 0x8000000000000000: + return v - 0x10000000000000000 + else: + raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported") + return v + + mnemo = insn.mnemonic + condition = mnemo[1:] + + if condition.endswith("z"): + # r2 is the zero register if we are comparing to 0 + rs1 = gef.arch.register(insn.operands[0]) + rs2 = gef.arch.register("$zero") + condition = condition[:-1] + elif len(insn.operands) > 2: + # r2 is populated with the second operand + rs1 = gef.arch.register(insn.operands[0]) + rs2 = gef.arch.register(insn.operands[1]) + else: + raise OSError(f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`") + + # If the conditional operation is not unsigned, convert the python long into + # its two's complement + if not condition.endswith("u"): + rs2 = long_to_twos_complement(rs2) + rs1 = long_to_twos_complement(rs1) + else: + condition = condition[:-1] + + if condition == "eq": + if rs1 == rs2: taken, reason = True, f"{rs1}={rs2}" + else: taken, reason = False, f"{rs1}!={rs2}" + elif condition == "ne": + if rs1 != rs2: taken, reason = True, f"{rs1}!={rs2}" + else: taken, reason = False, f"{rs1}={rs2}" + elif condition == "lt": + if rs1 < rs2: taken, reason = True, f"{rs1}<{rs2}" + else: taken, reason = False, f"{rs1}>={rs2}" + elif condition == "ge": + if rs1 < rs2: taken, reason = True, f"{rs1}>={rs2}" + else: taken, reason = False, f"{rs1}<{rs2}" + else: + raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet") + + return taken, reason + + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int: + ra = None + if self.is_ret(insn): + ra = gef.arch.register("$ra") + elif frame.older(): + ra = frame.older().pc() + return ra + + +@register_architecture +class ARM(Architecture): + aliases = ("ARM", Elf.Abi.ARM) + arch = "ARM" + all_registers = ["$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", + "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp", + "$lr", "$pc", "$cpsr",] + + # https://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0041c/Caccegih.html + nop_insn = b"\x01\x10\xa0\xe1" # mov r1, r1 + return_register = "$r0" + flag_register = "$cpsr" + flags_table = { + 31: "negative", + 30: "zero", + 29: "carry", + 28: "overflow", + 7: "interrupt", + 6: "fast", + 5: "thumb", + } + function_parameters = ["$r0", "$r1", "$r2", "$r3"] + syscall_register = "$r7" + syscall_instructions = ["swi 0x0", "swi NR"] + endianness = Endianness.LITTLE_ENDIAN + + def is_thumb(self) -> bool: + """Determine if the machine is currently in THUMB mode.""" + return is_alive() and gef.arch.register(self.flag_register) & (1 << 5) + + @property + def pc(self) -> Optional[int]: + pc = gef.arch.register("$pc") + if self.is_thumb(): + pc += 1 + return pc + + @property + def mode(self) -> str: + return "THUMB" if self.is_thumb() else "ARM" + + @property + def instruction_length(self) -> Optional[int]: + # Thumb instructions have variable-length (2 or 4-byte) + return None if self.is_thumb() else 4 + + @property + def ptrsize(self) -> int: + return 2 if self.is_thumb() else 4 + + def is_call(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + call_mnemos = {"bl", "blx"} + return mnemo in call_mnemos + + def is_ret(self, insn: Instruction) -> bool: + pop_mnemos = {"pop"} + branch_mnemos = {"bl", "bx"} + write_mnemos = {"ldr", "add"} + if insn.mnemonic in pop_mnemos: + return insn.operands[-1] == " pc}" + if insn.mnemonic in branch_mnemos: + return insn.operands[-1] == "lr" + if insn.mnemonic in write_mnemos: + return insn.operands[0] == "pc" + return False + + def flag_register_to_human(self, val: Optional[int] = None) -> str: + # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1 + if val is None: + reg = self.flag_register + val = gef.arch.register(reg) + return flags_to_human(val, self.flags_table) + + def is_conditional_branch(self, insn: Instruction) -> bool: + conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"} + return insn.mnemonic[-2:] in conditions + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + mnemo = insn.mnemonic + # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html + flags = dict((self.flags_table[k], k) for k in self.flags_table) + val = gef.arch.register(self.flag_register) + taken, reason = False, "" + + if mnemo.endswith("eq"): taken, reason = bool(val&(1<<flags["zero"])), "Z" + elif mnemo.endswith("ne"): taken, reason = not val&(1<<flags["zero"]), "!Z" + elif mnemo.endswith("lt"): + taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V" + elif mnemo.endswith("le"): + taken, reason = val&(1<<flags["zero"]) or \ + bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V" + elif mnemo.endswith("gt"): + taken, reason = val&(1<<flags["zero"]) == 0 and \ + bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V" + elif mnemo.endswith("ge"): + taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V" + elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V" + elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V" + elif mnemo.endswith("mi"): + taken, reason = bool(val&(1<<flags["negative"])), "N" + elif mnemo.endswith("pl"): + taken, reason = not val&(1<<flags["negative"]), "N==0" + elif mnemo.endswith("hi"): + taken, reason = val&(1<<flags["carry"]) and not val&(1<<flags["zero"]), "C && !Z" + elif mnemo.endswith("ls"): + taken, reason = not val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "!C || Z" + elif mnemo.endswith("cs"): taken, reason = bool(val&(1<<flags["carry"])), "C" + elif mnemo.endswith("cc"): taken, reason = not val&(1<<flags["carry"]), "!C" + return taken, reason + + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int: + ra = None + if self.is_ret(insn): + # If it's a pop, we have to peek into the stack, otherwise use lr + if insn.mnemonic == "pop": + ra_addr = gef.arch.sp + (len(insn.operands)-1) * self.ptrsize + ra = to_unsigned_long(dereference(ra_addr)) + elif insn.mnemonic == "ldr": + return to_unsigned_long(dereference(gef.arch.sp)) + else: # 'bx lr' or 'add pc, lr, #0' + return gef.arch.register("$lr") + elif frame.older(): + ra = frame.older().pc() + return ra + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + _NR_mprotect = 125 + insns = [ + "push {r0-r2, r7}", + f"mov r1, {addr & 0xffff:d}", + f"mov r0, {(addr & 0xffff0000) >> 16:d}", + "lsl r0, r0, 16", + "add r0, r0, r1", + f"mov r1, {size & 0xffff:d}", + f"mov r2, {perm.value & 0xff:d}", + f"mov r7, {_NR_mprotect:d}", + "svc 0", + "pop {r0-r2, r7}", + ] + return "; ".join(insns) + + +@register_architecture +class AARCH64(ARM): + aliases = ("ARM64", "AARCH64", Elf.Abi.AARCH64) + arch = "ARM64" + mode = "" + + all_registers = [ + "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7", + "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14","$x15", + "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23", + "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp", + "$pc", "$cpsr", "$fpsr", "$fpcr",] + return_register = "$x0" + flag_register = "$cpsr" + flags_table = { + 31: "negative", + 30: "zero", + 29: "carry", + 28: "overflow", + 7: "interrupt", + 6: "fast", + } + function_parameters = ["$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7"] + syscall_register = "$x8" + syscall_instructions = ["svc $x0"] + ptrsize = 8 + + def is_call(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + call_mnemos = {"bl", "blr"} + return mnemo in call_mnemos + + def flag_register_to_human(self, val: Optional[int] = None) -> str: + # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf + reg = self.flag_register + if not val: + val = gef.arch.register(reg) + return flags_to_human(val, self.flags_table) + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + _NR_mprotect = 226 + insns = [ + "str x8, [sp, -16]!", + "str x0, [sp, -16]!", + "str x1, [sp, -16]!", + "str x2, [sp, -16]!", + f"mov x8, {_NR_mprotect:d}", + f"movz x0, {addr & 0xFFFF:#x}", + f"movk x0, {(addr >> 16) & 0xFFFF:#x}, lsl 16", + f"movk x0, {(addr >> 32) & 0xFFFF:#x}, lsl 32", + f"movk x0, {(addr >> 48) & 0xFFFF:#x}, lsl 48", + f"movz x1, {size & 0xFFFF:#x}", + f"movk x1, {(size >> 16) & 0xFFFF:#x}, lsl 16", + f"mov x2, {perm.value:d}", + "svc 0", + "ldr x2, [sp], 16", + "ldr x1, [sp], 16", + "ldr x0, [sp], 16", + "ldr x8, [sp], 16", + ] + return "; ".join(insns) + + def is_conditional_branch(self, insn: Instruction) -> bool: + # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf + # sect. 5.1.1 + mnemo = insn.mnemonic + branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"} + return mnemo.startswith("b.") or mnemo in branch_mnemos + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + mnemo, operands = insn.mnemonic, insn.operands + taken, reason = False, "" + + if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}: + reg = f"${operands[0]}" + op = gef.arch.register(reg) + if mnemo == "cbnz": + if op!=0: taken, reason = True, f"{reg}!=0" + else: taken, reason = False, f"{reg}==0" + elif mnemo == "cbz": + if op == 0: taken, reason = True, f"{reg}==0" + else: taken, reason = False, f"{reg}!=0" + elif mnemo == "tbnz": + # operands[1] has one or more white spaces in front, then a #, then the number + # so we need to eliminate them + i = int(operands[1].strip().lstrip("#")) + if (op & 1<<i) != 0: taken, reason = True, f"{reg}&1<<{i}!=0" + else: taken, reason = False, f"{reg}&1<<{i}==0" + elif mnemo == "tbz": + # operands[1] has one or more white spaces in front, then a #, then the number + # so we need to eliminate them + i = int(operands[1].strip().lstrip("#")) + if (op & 1<<i) == 0: taken, reason = True, f"{reg}&1<<{i}==0" + else: taken, reason = False, f"{reg}&1<<{i}!=0" + + if not reason: + taken, reason = super().is_branch_taken(insn) + return taken, reason + + +@register_architecture +class X86(Architecture): + aliases: Tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32) + arch = "X86" + mode = "32" + + nop_insn = b"\x90" + flag_register = "$eflags" + special_registers = ["$cs", "$ss", "$ds", "$es", "$fs", "$gs", ] + gpr_registers = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", ] + all_registers = gpr_registers + [ flag_register, ] + special_registers + instruction_length = None + return_register = "$eax" + function_parameters = ["$esp", ] + flags_table = { + 6: "zero", + 0: "carry", + 2: "parity", + 4: "adjust", + 7: "sign", + 8: "trap", + 9: "interrupt", + 10: "direction", + 11: "overflow", + 16: "resume", + 17: "virtualx86", + 21: "identification", + } + syscall_register = "$eax" + syscall_instructions = ["sysenter", "int 0x80"] + ptrsize = 4 + endianness = Endianness.LITTLE_ENDIAN + + def flag_register_to_human(self, val: Optional[int] = None) -> str: + reg = self.flag_register + if not val: + val = gef.arch.register(reg) + return flags_to_human(val, self.flags_table) + + def is_call(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + call_mnemos = {"call", "callq"} + return mnemo in call_mnemos + + def is_ret(self, insn: Instruction) -> bool: + return insn.mnemonic == "ret" + + def is_conditional_branch(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + branch_mnemos = { + "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna", + "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl", + "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns", + "jo", "jp", "jpe", "js" + } + return mnemo in branch_mnemos + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + mnemo = insn.mnemonic + # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654) + flags = dict((self.flags_table[k], k) for k in self.flags_table) + val = gef.arch.register(self.flag_register) + + taken, reason = False, "" + + if mnemo in ("ja", "jnbe"): + taken, reason = not val&(1<<flags["carry"]) and not val&(1<<flags["zero"]), "!C && !Z" + elif mnemo in ("jae", "jnb", "jnc"): + taken, reason = not val&(1<<flags["carry"]), "!C" + elif mnemo in ("jb", "jc", "jnae"): + taken, reason = val&(1<<flags["carry"]), "C" + elif mnemo in ("jbe", "jna"): + taken, reason = val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "C || Z" + elif mnemo in ("jcxz", "jecxz", "jrcxz"): + cx = gef.arch.register("$rcx") if self.mode == 64 else gef.arch.register("$ecx") + taken, reason = cx == 0, "!$CX" + elif mnemo in ("je", "jz"): + taken, reason = val&(1<<flags["zero"]), "Z" + elif mnemo in ("jne", "jnz"): + taken, reason = not val&(1<<flags["zero"]), "!Z" + elif mnemo in ("jg", "jnle"): + taken, reason = not val&(1<<flags["zero"]) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O" + elif mnemo in ("jge", "jnl"): + taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O" + elif mnemo in ("jl", "jnge"): + taken, reason = val&(1<<flags["overflow"]) != val&(1<<flags["sign"]), "S!=O" + elif mnemo in ("jle", "jng"): + taken, reason = val&(1<<flags["zero"]) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O" + elif mnemo in ("jo",): + taken, reason = val&(1<<flags["overflow"]), "O" + elif mnemo in ("jno",): + taken, reason = not val&(1<<flags["overflow"]), "!O" + elif mnemo in ("jpe", "jp"): + taken, reason = val&(1<<flags["parity"]), "P" + elif mnemo in ("jnp", "jpo"): + taken, reason = not val&(1<<flags["parity"]), "!P" + elif mnemo in ("js",): + taken, reason = val&(1<<flags["sign"]), "S" + elif mnemo in ("jns",): + taken, reason = not val&(1<<flags["sign"]), "!S" + return taken, reason + + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + ra = None + if self.is_ret(insn): + ra = to_unsigned_long(dereference(gef.arch.sp)) + if frame.older(): + ra = frame.older().pc() + + return ra + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + _NR_mprotect = 125 + insns = [ + "pushad", + f"mov eax, {_NR_mprotect:d}", + f"mov ebx, {addr:d}", + f"mov ecx, {size:d}", + f"mov edx, {perm.value:d}", + "int 0x80", + "popad", + ] + return "; ".join(insns) + + def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]: + if in_func: + i += 1 # Account for RA being at the top of the stack + sp = gef.arch.sp + sz = gef.arch.ptrsize + loc = sp + (i * sz) + val = gef.memory.read_integer(loc) + key = f"[sp + {i * sz:#x}]" + return key, val + + +@register_architecture +class X86_64(X86): + aliases = ("X86_64", Elf.Abi.X86_64, "i386:x86-64") + arch = "X86" + mode = "64" + + gpr_registers = [ + "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip", + "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", ] + all_registers = gpr_registers + [ X86.flag_register, ] + X86.special_registers + return_register = "$rax" + function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"] + syscall_register = "$rax" + syscall_instructions = ["syscall"] + # We don't want to inherit x86's stack based param getter + get_ith_parameter = Architecture.get_ith_parameter + ptrsize = 8 + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + _NR_mprotect = 10 + insns = [ + "push rax", + "push rdi", + "push rsi", + "push rdx", + "push rcx", + "push r11", + f"mov rax, {_NR_mprotect:d}", + f"mov rdi, {addr:d}", + f"mov rsi, {size:d}", + f"mov rdx, {perm.value:d}", + "syscall", + "pop r11", + "pop rcx", + "pop rdx", + "pop rsi", + "pop rdi", + "pop rax", + ] + return "; ".join(insns) + + +@register_architecture +class PowerPC(Architecture): + aliases = ("PowerPC", Elf.Abi.POWERPC, "PPC") + arch = "PPC" + mode = "PPC32" + + all_registers = [ + "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7", + "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", + "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23", + "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31", + "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",] + instruction_length = 4 + nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/ + return_register = "$r0" + flag_register = "$cr" + flags_table = { + 3: "negative[0]", + 2: "positive[0]", + 1: "equal[0]", + 0: "overflow[0]", + # cr7 + 31: "less[7]", + 30: "greater[7]", + 29: "equal[7]", + 28: "overflow[7]", + } + function_parameters = ["$i0", "$i1", "$i2", "$i3", "$i4", "$i5"] + syscall_register = "$r0" + syscall_instructions = ["sc"] + + def flag_register_to_human(self, val: Optional[int] = None) -> str: + # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3) + if not val: + reg = self.flag_register + val = gef.arch.register(reg) + return flags_to_human(val, self.flags_table) + + def is_call(self, insn: Instruction) -> bool: + return False + + def is_ret(self, insn: Instruction) -> bool: + return insn.mnemonic == "blr" + + def is_conditional_branch(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"} + return mnemo in branch_mnemos + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + mnemo = insn.mnemonic + flags = dict((self.flags_table[k], k) for k in self.flags_table) + val = gef.arch.register(self.flag_register) + taken, reason = False, "" + if mnemo == "beq": taken, reason = val&(1<<flags["equal[7]"]), "E" + elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E" + elif mnemo == "ble": taken, reason = val&(1<<flags["equal[7]"]) or val&(1<<flags["less[7]"]), "E || L" + elif mnemo == "blt": taken, reason = val&(1<<flags["less[7]"]), "L" + elif mnemo == "bge": taken, reason = val&(1<<flags["equal[7]"]) or val&(1<<flags["greater[7]"]), "E || G" + elif mnemo == "bgt": taken, reason = val&(1<<flags["greater[7]"]), "G" + return taken, reason + + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + ra = None + if self.is_ret(insn): + ra = gef.arch.register("$lr") + elif frame.older(): + ra = frame.older().pc() + return ra + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + # Ref: https://developer.ibm.com/articles/l-ppc/ + _NR_mprotect = 125 + insns = [ + "addi 1, 1, -16", # 1 = r1 = sp + "stw 0, 0(1)", + "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args + "stw 4, 8(1)", + "stw 5, 12(1)", + f"li 0, {_NR_mprotect:d}", + f"lis 3, {addr:#x}@h", + f"ori 3, 3, {addr:#x}@l", + f"lis 4, {size:#x}@h", + f"ori 4, 4, {size:#x}@l", + f"li 5, {perm.value:d}", + "sc", + "lwz 0, 0(1)", + "lwz 3, 4(1)", + "lwz 4, 8(1)", + "lwz 5, 12(1)", + "addi 1, 1, 16", + ] + return ";".join(insns) + + +@register_architecture +class PowerPC64(PowerPC): + aliases = ("PowerPC64", Elf.Abi.POWERPC64, "PPC64") + arch = "PPC" + mode = "PPC64" + + +@register_architecture +class SPARC(Architecture): + """ Refs: + - https://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf + """ + aliases = ("SPARC", Elf.Abi.SPARC) + arch = "SPARC" + mode = "" + + all_registers = [ + "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", + "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", + "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", + "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", + "$pc", "$npc", "$sp ", "$fp ", "$psr",] + instruction_length = 4 + nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0 + return_register = "$i0" + flag_register = "$psr" + flags_table = { + 23: "negative", + 22: "zero", + 21: "overflow", + 20: "carry", + 7: "supervisor", + 5: "trap", + } + function_parameters = ["$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",] + syscall_register = "%g1" + syscall_instructions = ["t 0x10"] + + def flag_register_to_human(self, val: Optional[int] = None) -> str: + # https://www.gaisler.com/doc/sparcv8.pdf + reg = self.flag_register + if not val: + val = gef.arch.register(reg) + return flags_to_human(val, self.flags_table) + + def is_call(self, insn: Instruction) -> bool: + return False + + def is_ret(self, insn: Instruction) -> bool: + return insn.mnemonic == "ret" + + def is_conditional_branch(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html + branch_mnemos = { + "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu", + "bneg", "bpos", "bvs", "bvc", "bcs", "bcc" + } + return mnemo in branch_mnemos + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + mnemo = insn.mnemonic + flags = dict((self.flags_table[k], k) for k in self.flags_table) + val = gef.arch.register(self.flag_register) + taken, reason = False, "" + + if mnemo == "be": taken, reason = val&(1<<flags["zero"]), "Z" + elif mnemo == "bne": taken, reason = val&(1<<flags["zero"]) == 0, "!Z" + elif mnemo == "bg": taken, reason = val&(1<<flags["zero"]) == 0 and (val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0), "!Z && (!N || !O)" + elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O" + elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z" + elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C" + elif mnemo == "bl": taken, reason = val&(1<<flags["negative"]) and val&(1<<flags["overflow"]), "N && O" + elif mnemo == "blu": taken, reason = val&(1<<flags["carry"]), "C" + elif mnemo == "ble": taken, reason = val&(1<<flags["zero"]) or (val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)" + elif mnemo == "bleu": taken, reason = val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "C || Z" + elif mnemo == "bneg": taken, reason = val&(1<<flags["negative"]), "N" + elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N" + elif mnemo == "bvs": taken, reason = val&(1<<flags["overflow"]), "O" + elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O" + elif mnemo == "bcs": taken, reason = val&(1<<flags["carry"]), "C" + elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C" + return taken, reason + + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + ra = None + if self.is_ret(insn): + ra = gef.arch.register("$o7") + elif frame.older(): + ra = frame.older().pc() + return ra + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + hi = (addr & 0xffff0000) >> 16 + lo = (addr & 0x0000ffff) + _NR_mprotect = 125 + insns = ["add %sp, -16, %sp", + "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", + "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", + f"sethi %hi({hi}), %o0", + f"or %o0, {lo}, %o0", + "clr %o1", + "clr %o2", + f"mov {_NR_mprotect}, %g1", + "t 0x10", + "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", + "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", + "add %sp, 16, %sp",] + return "; ".join(insns) + + +@register_architecture +class SPARC64(SPARC): + """Refs: + - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf + - https://cr.yp.to/2005-590/sparcv9.pdf + """ + aliases = ("SPARC64", Elf.Abi.SPARC64) + arch = "SPARC" + mode = "V9" + + all_registers = [ + "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7", + "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7", + "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7", + "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7", + "$pc", "$npc", "$sp", "$fp", "$state", ] + + flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr) + flags_table = { + 35: "negative", + 34: "zero", + 33: "overflow", + 32: "carry", + } + + syscall_instructions = ["t 0x6d"] + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + hi = (addr & 0xffff0000) >> 16 + lo = (addr & 0x0000ffff) + _NR_mprotect = 125 + insns = ["add %sp, -16, %sp", + "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]", + "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]", + f"sethi %hi({hi}), %o0", + f"or %o0, {lo}, %o0", + "clr %o1", + "clr %o2", + f"mov {_NR_mprotect}, %g1", + "t 0x6d", + "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0", + "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2", + "add %sp, 16, %sp",] + return "; ".join(insns) + + +@register_architecture +class MIPS(Architecture): + aliases: Tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS) + arch = "MIPS" + mode = "MIPS32" + + # https://vhouten.home.xs4all.nl/mipsel/r3000-isa.html + all_registers = [ + "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", + "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", + "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", + "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi", + "$lo", "$fir", "$ra", "$gp", ] + instruction_length = 4 + ptrsize = 4 + nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0 + return_register = "$v0" + flag_register = "$fcsr" + flags_table = {} + function_parameters = ["$a0", "$a1", "$a2", "$a3"] + syscall_register = "$v0" + syscall_instructions = ["syscall"] + + def flag_register_to_human(self, val: Optional[int] = None) -> str: + return Color.colorify("No flag register", "yellow underline") + + def is_call(self, insn: Instruction) -> bool: + return False + + def is_ret(self, insn: Instruction) -> bool: + return insn.mnemonic == "jr" and insn.operands[0] == "ra" + + def is_conditional_branch(self, insn: Instruction) -> bool: + mnemo = insn.mnemonic + branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"} + return mnemo in branch_mnemos + + def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]: + mnemo, ops = insn.mnemonic, insn.operands + taken, reason = False, "" + + if mnemo == "beq": + taken, reason = gef.arch.register(ops[0]) == gef.arch.register(ops[1]), "{0[0]} == {0[1]}".format(ops) + elif mnemo == "bne": + taken, reason = gef.arch.register(ops[0]) != gef.arch.register(ops[1]), "{0[0]} != {0[1]}".format(ops) + elif mnemo == "beqz": + taken, reason = gef.arch.register(ops[0]) == 0, "{0[0]} == 0".format(ops) + elif mnemo == "bnez": + taken, reason = gef.arch.register(ops[0]) != 0, "{0[0]} != 0".format(ops) + elif mnemo == "bgtz": + taken, reason = gef.arch.register(ops[0]) > 0, "{0[0]} > 0".format(ops) + elif mnemo == "bgez": + taken, reason = gef.arch.register(ops[0]) >= 0, "{0[0]} >= 0".format(ops) + elif mnemo == "bltz": + taken, reason = gef.arch.register(ops[0]) < 0, "{0[0]} < 0".format(ops) + elif mnemo == "blez": + taken, reason = gef.arch.register(ops[0]) <= 0, "{0[0]} <= 0".format(ops) + return taken, reason + + def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]: + ra = None + if self.is_ret(insn): + ra = gef.arch.register("$ra") + elif frame.older(): + ra = frame.older().pc() + return ra + + @classmethod + def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str: + _NR_mprotect = 4125 + insns = ["addi $sp, $sp, -16", + "sw $v0, 0($sp)", "sw $a0, 4($sp)", + "sw $a3, 8($sp)", "sw $a3, 12($sp)", + f"li $v0, {_NR_mprotect:d}", + f"li $a0, {addr:d}", + f"li $a1, {size:d}", + f"li $a2, {perm.value:d}", + "syscall", + "lw $v0, 0($sp)", "lw $a1, 4($sp)", + "lw $a3, 8($sp)", "lw $a3, 12($sp)", + "addi $sp, $sp, 16",] + return "; ".join(insns) + + +@register_architecture +class MIPS64(MIPS): + aliases = ("MIPS64",) + arch = "MIPS" + mode = "MIPS64" + ptrsize = 8 + + +def copy_to_clipboard(data: str) -> None: + """Helper function to submit data to the clipboard""" + if sys.platform == "linux": + xclip = which("xclip") + prog = [xclip, "-selection", "clipboard", "-i"] + elif sys.platform == "darwin": + pbcopy = which("pbcopy") + prog = [pbcopy] + else: + raise NotImplementedError("copy: Unsupported OS") + + with subprocess.Popen(prog, stdin=subprocess.PIPE) as p: + p.stdin.write(data) + p.stdin.close() + p.wait() + return + + +def use_stdtype() -> str: + if is_32bit(): return "uint32_t" + elif is_64bit(): return "uint64_t" + return "uint16_t" + + +def use_default_type() -> str: + if is_32bit(): return "unsigned int" + elif is_64bit(): return "unsigned long" + return "unsigned short" + + +def use_golang_type() -> str: + if is_32bit(): return "uint32" + elif is_64bit(): return "uint64" + return "uint16" + + +def use_rust_type() -> str: + if is_32bit(): return "u32" + elif is_64bit(): return "u64" + return "u16" + + +def to_unsigned_long(v: gdb.Value) -> int: + """Cast a gdb.Value to unsigned long.""" + mask = (1 << 64) - 1 + return int(v.cast(gdb.Value(mask).type)) & mask + + +def get_path_from_info_proc() -> Optional[str]: + for x in gdb.execute("info proc", to_string=True).splitlines(): + if x.startswith("exe = "): + return x.split(" = ")[1].replace("'", "") + return None + + +@deprecated("Use `gef.session.os`") +def get_os() -> str: + return gef.session.os + + +@lru_cache() +def is_qemu() -> bool: + if not is_remote_debug(): + return False + response = gdb.execute('maintenance packet Qqemu.sstepbits', to_string=True, from_tty=False) + return 'ENABLE=' in response + + +@lru_cache() +def is_qemu_usermode() -> bool: + if not is_qemu(): + return False + response = gdb.execute('maintenance packet QOffsets', to_string=True, from_tty=False) + return "Text=" in response + + +@lru_cache() +def is_qemu_system() -> bool: + if not is_qemu(): + return False + response = gdb.execute('maintenance packet QOffsets', to_string=True, from_tty=False) + return 'received: ""' in response + + +@lru_cache() +def get_filepath() -> Optional[str]: + """Return the local absolute path of the file currently debugged.""" + filename = gdb.current_progspace().filename + + if is_remote_debug(): + # if no filename specified, try downloading target from /proc + if filename is None: + pid = gef.session.pid + if pid > 0: + return download_file(f"/proc/{pid:d}/exe", use_cache=True) + return None + + # if target is remote file, download + elif filename.startswith("target:"): + fname = filename[len("target:") :] + return download_file(fname, use_cache=True, local_name=fname) + + elif filename.startswith(".gnu_debugdata for target:"): + fname = filename[len(".gnu_debugdata for target:") :] + return download_file(fname, use_cache=True, local_name=fname) + + elif gef.session.remote is not None: + return f"/tmp/gef/{gef.session.remote:d}/{get_path_from_info_proc()}" + return filename + else: + if filename is not None: + return filename + # inferior probably did not have name, extract cmdline from info proc + return get_path_from_info_proc() + + +def download_file(remote_path: str, use_cache: bool = False, local_name: Optional[str] = None) -> Optional[str]: + """Download filename `remote_path` inside the mirror tree inside the `gef.config["gef.tempdir"]`. + The tree architecture must be `gef.config["gef.tempdir"]/gef/<local_pid>/<remote_filepath>`. + This allow a "chroot-like" tree format.""" + + local_root = pathlib.Path(gef.config["gef.tempdir"]) / str(gef.session.pid) + if local_name is None: + local_path = local_root / remote_path.strip(os.sep) + else: + local_path = local_root / local_name.strip(os.sep) + + if use_cache and local_path.exists(): + return str(local_path.absolute()) + + try: + local_path.parent.mkdir(parents=True, exist_ok=True) + gdb.execute(f"remote get {remote_path} {local_path.absolute()}") + local_path = str(local_path.absolute()) + except gdb.error: + # fallback memory view + with open(local_path, "w") as f: + if is_32bit(): + f.write(f"00000000-ffffffff rwxp 00000000 00:00 0 {get_filepath()}\n") + else: + f.write(f"0000000000000000-ffffffffffffffff rwxp 00000000 00:00 0 {get_filepath()}\n") + + except Exception as e: + err(f"download_file() failed: {e}") + local_path = None + + return local_path + + +def get_function_length(sym: str) -> int: + """Attempt to get the length of the raw bytes of a function.""" + dis = gdb.execute(f"disassemble {sym}", to_string=True).splitlines() + start_addr = int(dis[1].split()[0], 16) + end_addr = int(dis[-2].split()[0], 16) + return end_addr - start_addr + + +@lru_cache() +def get_info_files() -> List[Zone]: + """Retrieve all the files loaded by debuggee.""" + lines = gdb.execute("info files", to_string=True).splitlines() + infos = [] + for line in lines: + line = line.strip() + if not line: + break + + if not line.startswith("0x"): + continue + + blobs = [x.strip() for x in line.split(" ")] + addr_start = int(blobs[0], 16) + addr_end = int(blobs[2], 16) + section_name = blobs[4] + + if len(blobs) == 7: + filename = blobs[6] + else: + filename = get_filepath() + + infos.append(Zone(section_name, addr_start, addr_end, filename)) + return infos + + +def process_lookup_address(address: int) -> Optional[Section]: + """Look up for an address in memory. + Return an Address object if found, None otherwise.""" + if not is_alive(): + err("Process is not running") + return None + + if is_x86(): + if is_in_x86_kernel(address): + return None + + for sect in gef.memory.maps: + if sect.page_start <= address < sect.page_end: + return sect + + return None + + +@lru_cache() +def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Optional[Section]: + """Look up for a path in the process memory mapping. + Return a Section object if found, None otherwise.""" + if not is_alive(): + err("Process is not running") + return None + + for sect in gef.memory.maps: + if name in sect.path and sect.permission & perm: + return sect + + return None + + +@lru_cache() +def file_lookup_name_path(name: str, path: str) -> Optional[Zone]: + """Look up a file by name and path. + Return a Zone object if found, None otherwise.""" + for xfile in get_info_files(): + if path == xfile.filename and name == xfile.name: + return xfile + return None + + +@lru_cache() +def file_lookup_address(address: int) -> Optional[Zone]: + """Look up for a file by its address. + Return a Zone object if found, None otherwise.""" + for info in get_info_files(): + if info.zone_start <= address < info.zone_end: + return info + return None + + +@lru_cache() +def lookup_address(address: int) -> Address: + """Try to find the address in the process address space. + Return an Address object, with validity flag set based on success.""" + sect = process_lookup_address(address) + info = file_lookup_address(address) + if sect is None and info is None: + # i.e. there is no info on this address + return Address(value=address, valid=False) + return Address(value=address, section=sect, info=info) + + +def xor(data: ByteString, key: str) -> bytearray: + """Return `data` xor-ed with `key`.""" + key_raw = binascii.unhexlify(key.lstrip("0x")) + return bytearray(x ^ y for x, y in zip(data, itertools.cycle(key_raw))) + + +def is_hex(pattern: str) -> bool: + """Return whether provided string is a hexadecimal value.""" + if not pattern.lower().startswith("0x"): + return False + return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:]) + + +def ida_synchronize_handler(_: "gdb.Event") -> None: + gdb.execute("ida-interact sync", from_tty=True) + return + + +def continue_handler(_: "gdb.Event") -> None: + """GDB event handler for new object continue cases.""" + return + + +def hook_stop_handler(_: "gdb.Event") -> None: + """GDB event handler for stop cases.""" + reset_all_caches() + gdb.execute("context") + return + + +def new_objfile_handler(_: "gdb.Event") -> None: + """GDB event handler for new object file cases.""" + reset_all_caches() + reset_architecture() + load_libc_args() + return + + +def exit_handler(_: "gdb.Event") -> None: + """GDB event handler for exit cases.""" + reset_all_caches() + gef.session.qemu_mode = False + if gef.session.remote and gef.config["gef-remote.clean_on_exit"] is True: + shutil.rmtree(f"/tmp/gef/{gef.session.remote:d}") + gef.session.remote = None + return + + +def memchanged_handler(_: "gdb.Event") -> None: + """GDB event handler for mem changes cases.""" + reset_all_caches() + return + + +def regchanged_handler(_: "gdb.Event") -> None: + """GDB event handler for reg changes cases.""" + reset_all_caches() + return + + +def load_libc_args() -> bool: + """Load the LIBC function arguments. Returns `True` on success, `False` or an Exception otherwise.""" + global gef + # load libc function arguments' definitions + if not gef.config["context.libc_args"]: + return False + + path = gef.config["context.libc_args_path"] + if not path: + return False + + path = pathlib.Path(path).expanduser().absolute() + if not path.exists(): + raise RuntimeError("Config `context.libc_args_path` set but it's not a directory") + + _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" + _libc_args_file = path / f"{_arch_mode}.json" + + # current arch and mode already loaded + if _arch_mode in gef.ui.highlight_table: + return True + + gef.ui.highlight_table[_arch_mode] = {} + try: + with _libc_args_file.open() as _libc_args: + gef.ui.highlight_table[_arch_mode] = json.load(_libc_args) + return True + except FileNotFoundError: + del gef.ui.highlight_table[_arch_mode] + warn(f"Config context.libc_args is set but definition cannot be loaded: file {_libc_args_file} not found") + except json.decoder.JSONDecodeError as e: + del gef.ui.highlight_table[_arch_mode] + warn(f"Config context.libc_args is set but definition cannot be loaded from file {_libc_args_file}: {e}") + return False + + +def get_terminal_size() -> Tuple[int, int]: + """Return the current terminal size.""" + if is_debug(): + return 600, 100 + + if platform.system() == "Windows": + from ctypes import windll, create_string_buffer + hStdErr = -12 + herr = windll.kernel32.GetStdHandle(hStdErr) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(herr, csbi) + if res: + _, _, _, _, _, left, top, right, bottom, _, _ = struct.unpack("hhhhHhhhhhh", csbi.raw) + tty_columns = right - left + 1 + tty_rows = bottom - top + 1 + return tty_rows, tty_columns + else: + return 600, 100 + else: + import fcntl + import termios + try: + tty_rows, tty_columns = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234")) + return tty_rows, tty_columns + except OSError: + return 600, 100 + + +def get_generic_arch(module: ModuleType, prefix: str, arch: str, mode: Optional[str], big_endian: Optional[bool], to_string: bool = False) -> Tuple[str, Union[int, str]]: + """ + Retrieves architecture and mode from the arguments for use for the holy + {cap,key}stone/unicorn trinity. + """ + if to_string: + arch = f"{module.__name__}.{prefix}_ARCH_{arch}" + if mode: + mode = f"{module.__name__}.{prefix}_MODE_{mode}" + else: + mode = "" + if gef.arch.endianness == Endianness.BIG_ENDIAN: + mode += f" + {module.__name__}.{prefix}_MODE_BIG_ENDIAN" + else: + mode += f" + {module.__name__}.{prefix}_MODE_LITTLE_ENDIAN" + + else: + arch = getattr(module, f"{prefix}_ARCH_{arch}") + if mode: + mode = getattr(module, f"{prefix}_MODE_{mode}") + else: + mode = 0 + if big_endian: + mode |= getattr(module, f"{prefix}_MODE_BIG_ENDIAN") + else: + mode |= getattr(module, f"{prefix}_MODE_LITTLE_ENDIAN") + + return arch, mode + + +def get_generic_running_arch(module: ModuleType, prefix: str, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + """ + Retrieves architecture and mode from the current context. + """ + + if not is_alive(): + return None, None + + if gef.arch is not None: + arch, mode = gef.arch.arch, gef.arch.mode + else: + raise OSError("Emulation not supported for your OS") + + return get_generic_arch(module, prefix, arch, mode, gef.arch.endianness == Endianness.BIG_ENDIAN, to_string) + + +def get_unicorn_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + unicorn = sys.modules["unicorn"] + if (arch, mode, endian) == (None, None, None): + return get_generic_running_arch(unicorn, "UC", to_string) + return get_generic_arch(unicorn, "UC", arch, mode, endian, to_string) + + +def get_capstone_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + capstone = sys.modules["capstone"] + + # hacky patch to unify capstone/ppc syntax with keystone & unicorn: + # CS_MODE_PPC32 does not exist (but UC_MODE_32 & KS_MODE_32 do) + if is_arch(Elf.Abi.POWERPC64): + raise OSError("Capstone not supported for PPC64 yet.") + + if is_alive() and is_arch(Elf.Abi.POWERPC): + + arch = "PPC" + mode = "32" + endian = (gef.arch.endianness == Endianness.BIG_ENDIAN) + return get_generic_arch(capstone, "CS", + arch or gef.arch.arch, + mode or gef.arch.mode, + endian, + to_string) + + if (arch, mode, endian) == (None, None, None): + return get_generic_running_arch(capstone, "CS", to_string) + return get_generic_arch(capstone, "CS", + arch or gef.arch.arch, + mode or gef.arch.mode, + endian or gef.arch.endianness == Endianness.BIG_ENDIAN, + to_string) + + +def get_keystone_arch(arch: Optional[str] = None, mode: Optional[str] = None, endian: Optional[bool] = None, to_string: bool = False) -> Union[Tuple[None, None], Tuple[str, Union[int, str]]]: + keystone = sys.modules["keystone"] + if (arch, mode, endian) == (None, None, None): + return get_generic_running_arch(keystone, "KS", to_string) + + if arch in ["ARM64", "SYSTEMZ"]: + modes = [None] + elif arch == "ARM" and mode == "ARMV8": + modes = ["ARM", "V8"] + elif arch == "ARM" and mode == "THUMBV8": + modes = ["THUMB", "V8"] + else: + modes = [mode] + a = arch + if not to_string: + mode = 0 + for m in modes: + arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) + mode |= _mode + else: + mode = "" + for m in modes: + arch, _mode = get_generic_arch(keystone, "KS", a, m, endian, to_string) + mode += f"|{_mode}" + mode = mode[1:] + return arch, mode + + +def get_unicorn_registers(to_string: bool = False) -> Union[Dict[str, int], Dict[str, str]]: + "Return a dict matching the Unicorn identifier for a specific register." + unicorn = sys.modules["unicorn"] + regs = {} + + if gef.arch is not None: + arch = gef.arch.arch.lower() + else: + raise OSError("Oops") + + const = getattr(unicorn, f"{arch}_const") + for reg in gef.arch.all_registers: + regname = f"UC_{arch.upper()}_REG_{reg[1:].upper()}" + if to_string: + regs[reg] = f"{const.__name__}.{regname}" + else: + regs[reg] = getattr(const, regname) + return regs + + +def keystone_assemble(code: str, arch: int, mode: int, **kwargs: Any) -> Optional[Union[str, bytearray]]: + """Assembly encoding function based on keystone.""" + keystone = sys.modules["keystone"] + code = gef_pybytes(code) + addr = kwargs.get("addr", 0x1000) + + try: + ks = keystone.Ks(arch, mode) + enc, cnt = ks.asm(code, addr) + except keystone.KsError as e: + err(f"Keystone assembler error: {e}") + return None + + if cnt == 0: + return "" + + enc = bytearray(enc) + if "raw" not in kwargs: + s = binascii.hexlify(enc) + enc = b"\\x" + b"\\x".join([s[i : i + 2] for i in range(0, len(s), 2)]) + enc = enc.decode("utf-8") + + return enc + + +@lru_cache() +def get_elf_headers(filename: Optional[str] = None) -> Optional[Elf]: + """Return an Elf object with info from `filename`. If not provided, will return + the currently debugged file.""" + if not filename: + filename = get_filepath() + if not filename: + raise Exception("No file provided") + + if filename.startswith("target:"): + warn("Your file is remote, you should try using `gef-remote` instead") + return + + return Elf(filename) + + +@lru_cache() +def is_64bit() -> bool: + """Checks if current target is 64bit.""" + return gef.arch.ptrsize == 8 + + +@lru_cache() +def is_32bit() -> bool: + """Checks if current target is 32bit.""" + return gef.arch.ptrsize == 4 + + +@lru_cache() +def is_x86_64() -> bool: + """Checks if current target is x86-64""" + return Elf.Abi.X86_64 in gef.arch.aliases + + +@lru_cache() +def is_x86_32(): + """Checks if current target is an x86-32""" + return Elf.Abi.X86_32 in gef.arch.aliases + + +@lru_cache() +def is_x86() -> bool: + return is_x86_32() or is_x86_64() + + +@lru_cache() +def is_arch(arch: Elf.Abi) -> bool: + return arch in gef.arch.aliases + + +def reset_architecture(arch: Optional[str] = None, default: Optional[str] = None) -> None: + """Sets the current architecture. + If an arch is explicitly specified, use that one, otherwise try to parse it + out of the current target. If that fails, and default is specified, select and + set that arch. + Raise an exception if the architecture cannot be set. + Does not return a value. + """ + global gef + arches = __registered_architectures__ + + if arch: + try: + gef.arch = arches[arch.upper()]() + return + except KeyError: + raise OSError(f"Specified arch {arch.upper()} is not supported") + + if not gef.binary: + gef.binary = get_elf_headers() + + arch_name = gef.binary.e_machine if gef.binary else get_arch() + + if ((arch_name == "MIPS" or arch_name == Elf.Abi.MIPS) + and (gef.binary is not None and gef.binary.e_class == Elf.Class.ELF_64_BITS)): + # MIPS64 = arch(MIPS) + 64b flag + arch_name = "MIPS64" + + try: + gef.arch = arches[arch_name]() + except KeyError: + if default: + try: + gef.arch = arches[default.upper()]() + except KeyError: + raise OSError(f"CPU not supported, neither is default {default.upper()}") + else: + raise OSError(f"CPU type is currently not supported: {get_arch()}") + return + + +@lru_cache() +def cached_lookup_type(_type: str) -> Optional[gdb.Type]: + try: + return gdb.lookup_type(_type).strip_typedefs() + except RuntimeError: + return None + + +@deprecated("Use `gef.arch.ptrsize` instead") +def get_memory_alignment(in_bits: bool = False) -> int: + """Try to determine the size of a pointer on this system. + First, try to parse it out of the ELF header. + Next, use the size of `size_t`. + Finally, try the size of $pc. + If `in_bits` is set to True, the result is returned in bits, otherwise in + bytes.""" + res = cached_lookup_type("size_t") + if res is not None: + return res.sizeof if not in_bits else res.sizeof * 8 + + try: + return gdb.parse_and_eval("$pc").type.sizeof + except: + pass + + raise OSError("GEF is running under an unsupported mode") + + +def clear_screen(tty: str = "") -> None: + """Clear the screen.""" + global gef + if not tty: + gdb.execute("shell clear -x") + return + + # Since the tty can be closed at any time, a PermissionError exception can + # occur when `clear_screen` is called. We handle this scenario properly + try: + with open(tty, "wt") as f: + f.write("\x1b[H\x1b[J") + except PermissionError: + gef.ui.redirect_fd = None + gef.config["context.redirect"] = "" + return + + +def format_address(addr: int) -> str: + """Format the address according to its size.""" + memalign_size = gef.arch.ptrsize + addr = align_address(addr) + + if memalign_size == 4: + return f"{addr:#08x}" + + return f"{addr:#016x}" + + +def format_address_spaces(addr: int, left: bool = True) -> str: + """Format the address according to its size, but with spaces instead of zeroes.""" + width = gef.arch.ptrsize * 2 + 2 + addr = align_address(addr) + + if not left: + return f"{addr:#x}".rjust(width) + + return f"{addr:#x}".ljust(width) + + +def align_address(address: int) -> int: + """Align the provided address to the process's native length.""" + if gef.arch.ptrsize == 4: + return address & 0xFFFFFFFF + + return address & 0xFFFFFFFFFFFFFFFF + + +def align_address_to_size(address: int, align: int) -> int: + """Align the address to the given size.""" + return address + ((align - (address % align)) % align) + + +def align_address_to_page(address: int) -> int: + """Align the address to a page.""" + a = align_address(address) >> DEFAULT_PAGE_ALIGN_SHIFT + return a << DEFAULT_PAGE_ALIGN_SHIFT + + +def malloc_align_address(address: int) -> int: + """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github""" + __default_malloc_alignment = 0x10 + if is_x86_32() and get_libc_version() >= (2, 26): + # Special case introduced in Glibc 2.26: + # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/i386/malloc-alignment.h#L22 + malloc_alignment = __default_malloc_alignment + else: + # Generic case: + # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/generic/malloc-alignment.h#L22 + __alignof__long_double = int(safe_parse_and_eval("_Alignof(long double)") or __default_malloc_alignment) # fallback to default if the expression fails to evaluate + malloc_alignment = max(__alignof__long_double, 2 * gef.arch.ptrsize) + + ceil = lambda n: int(-1 * n // 1 * -1) + # align address to nearest next multiple of malloc_alignment + return malloc_alignment * ceil((address / malloc_alignment)) + + +def parse_address(address: str) -> int: + """Parse an address and return it as an Integer.""" + if is_hex(address): + return int(address, 16) + return to_unsigned_long(gdb.parse_and_eval(address)) + + +def is_in_x86_kernel(address: int) -> bool: + address = align_address(address) + memalign = gef.arch.ptrsize*8 - 1 + return (address >> memalign) == 0xF + + +@lru_cache() +def is_remote_debug() -> bool: + """"Return True is the current debugging session is running through GDB remote session.""" + return gef.session.remote is not None or "remote" in gdb.execute("maintenance print target-stack", to_string=True) + + +def de_bruijn(alphabet: bytes, n: int) -> Generator[str, None, None]: + """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib).""" + k = len(alphabet) + a = [0] * k * n + + def db(t: int, p: int) -> Generator[str, None, None]: + if t > n: + if n % p == 0: + for j in range(1, p + 1): + yield alphabet[a[j]] + else: + a[t] = a[t - p] + yield from db(t + 1, p) + + for j in range(a[t - p] + 1, k): + a[t] = j + yield from db(t + 1, t) + + return db(1, 1) + + +def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: + """Create a `length` byte bytearray of a de Bruijn cyclic pattern.""" + charset = bytearray(b"abcdefghijklmnopqrstuvwxyz") + return bytearray(itertools.islice(de_bruijn(charset, cycle), length)) + + +def safe_parse_and_eval(value: str) -> Optional["gdb.Value"]: + """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising + gdb.error if the eval failed.""" + try: + return gdb.parse_and_eval(value) + except gdb.error: + pass + return None + + +@lru_cache() +def dereference(addr: int) -> Optional["gdb.Value"]: + """GEF wrapper for gdb dereference function.""" + try: + ulong_t = cached_lookup_type(use_stdtype()) or \ + cached_lookup_type(use_default_type()) or \ + cached_lookup_type(use_golang_type()) or \ + cached_lookup_type(use_rust_type()) + unsigned_long_type = ulong_t.pointer() + res = gdb.Value(addr).cast(unsigned_long_type).dereference() + # GDB does lazy fetch by default so we need to force access to the value + res.fetch_lazy() + return res + except gdb.MemoryError: + pass + return None + + +def gef_convenience(value: str) -> str: + """Defines a new convenience value.""" + global gef + var_name = f"$_gef{gef.session.convenience_vars_index:d}" + gef.session.convenience_vars_index += 1 + gdb.execute(f"""set {var_name} = "{value}" """) + return var_name + + +def parse_string_range(s: str) -> Iterator[int]: + """Parses an address range (e.g. 0x400000-0x401000)""" + addrs = s.split("-") + return map(lambda x: int(x, 16), addrs) + + +@deprecated("Use `gef.session.pie_breakpoints[num]`") +def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint": + return gef.session.pie_breakpoints[num] + + +# +# Deprecated API +# +@deprecated("Use `str(gef.arch.endianness)` instead") +def endian_str() -> str: + return str(gef.arch.endianness) + + +@deprecated("Use `gef.config[key]`") +def get_gef_setting(name: str) -> Any: + return gef.config[name] + + +@deprecated("Use `gef.config[key] = value`") +def set_gef_setting(name: str, value: Any) -> None: + gef.config[name] = value + return + + +@deprecated("Use `gef.session.pagesize`") +def gef_getpagesize() -> int: + return gef.session.pagesize + + +@deprecated("Use `gef.session.canary`") +def gef_read_canary() -> Optional[Tuple[int, int]]: + return gef.session.canary + + +@deprecated("Use `gef.session.pid`") +def get_pid() -> int: + return gef.session.pid + + +@deprecated("Use `gef.session.file.name`") +def get_filename() -> str: + return gef.session.file.name + + +@deprecated("Use `gef.heap.main_arena`") +def get_glibc_arena() -> Optional[GlibcArena]: + return gef.heap.main_arena + + +@deprecated("Use `gef.arch.register(regname)`") +def get_register(regname) -> Optional[int]: + return gef.arch.register(regname) + + +@deprecated("Use `gef.memory.maps`") +def get_process_maps() -> List[Section]: + return gef.memory.maps + + +@deprecated("Use `reset_architecture`") +def set_arch(arch: Optional[str] = None, default: Optional[str] = None) -> None: + return reset_architecture(arch, default) + +# +# GDB event hooking +# + +@only_if_events_supported("cont") +def gef_on_continue_hook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.cont.connect(func) + + +@only_if_events_supported("cont") +def gef_on_continue_unhook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.cont.disconnect(func) + + +@only_if_events_supported("stop") +def gef_on_stop_hook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.stop.connect(func) + + +@only_if_events_supported("stop") +def gef_on_stop_unhook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.stop.disconnect(func) + + +@only_if_events_supported("exited") +def gef_on_exit_hook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.exited.connect(func) + + +@only_if_events_supported("exited") +def gef_on_exit_unhook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.exited.disconnect(func) + + +@only_if_events_supported("new_objfile") +def gef_on_new_hook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.new_objfile.connect(func) + + +@only_if_events_supported("new_objfile") +def gef_on_new_unhook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.new_objfile.disconnect(func) + + +@only_if_events_supported("memory_changed") +def gef_on_memchanged_hook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.memory_changed.connect(func) + + +@only_if_events_supported("memory_changed") +def gef_on_memchanged_unhook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.memory_changed.disconnect(func) + + +@only_if_events_supported("register_changed") +def gef_on_regchanged_hook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.register_changed.connect(func) + + +@only_if_events_supported("register_changed") +def gef_on_regchanged_unhook(func: Callable[["gdb.Event"], None]) -> None: + gdb.events.register_changed.disconnect(func) + + +# +# Virtual breakpoints +# + +class PieVirtualBreakpoint: + """PIE virtual breakpoint (not real breakpoint).""" + + def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> None: + # set_func(base): given a base address return a + # "set breakpoint" gdb command string + self.set_func = set_func + self.vbp_num = vbp_num + # breakpoint num, 0 represents not instantiated yet + self.bp_num = 0 + self.bp_addr = 0 + # this address might be a symbol, just to know where to break + if isinstance(addr, int): + self.addr: Union[int, str] = hex(addr) + else: + self.addr = addr + return + + def instantiate(self, base: int) -> None: + if self.bp_num: + self.destroy() + + try: + res = gdb.execute(self.set_func(base), to_string=True) + except gdb.error as e: + err(e) + return + + if "Breakpoint" not in res: + err(res) + return + res_list = res.split() + self.bp_num = res_list[1] + self.bp_addr = res_list[3] + return + + def destroy(self) -> None: + if not self.bp_num: + err("Destroy PIE breakpoint not even set") + return + gdb.execute(f"delete {self.bp_num}") + self.bp_num = 0 + return + + +# +# Breakpoints +# + +class FormatStringBreakpoint(gdb.Breakpoint): + """Inspect stack for format string.""" + def __init__(self, spec: str, num_args: int) -> None: + super().__init__(spec, type=gdb.BP_BREAKPOINT, internal=False) + self.num_args = num_args + self.enabled = True + return + + def stop(self) -> bool: + reset_all_caches() + msg = [] + ptr, addr = gef.arch.get_ith_parameter(self.num_args) + addr = lookup_address(addr) + + if not addr.valid: + return False + + if addr.section.is_writable(): + content = gef.memory.read_cstring(addr.value) + name = addr.info.name if addr.info else addr.section.path + msg.append(Color.colorify("Format string helper", "yellow bold")) + msg.append(f"Possible insecure format string: {self.location}('{ptr}' {RIGHT_ARROW} {addr.value:#x}: '{content}')") + msg.append(f"Reason: Call to '{self.location}()' with format string argument in position " + f"#{self.num_args:d} is in page {addr.section.page_start:#x} ({name}) that has write permission") + push_context_message("warn", "\n".join(msg)) + return True + + return False + + +class StubBreakpoint(gdb.Breakpoint): + """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.).""" + + def __init__(self, func: str, retval: Optional[int]) -> None: + super().__init__(func, gdb.BP_BREAKPOINT, internal=False) + self.func = func + self.retval = retval + + m = f"All calls to '{self.func}' will be skipped" + if self.retval is not None: + m += f" (with return value set to {self.retval:#x})" + info(m) + return + + def stop(self) -> bool: + gdb.execute(f"return (unsigned int){self.retval:#x}") + ok(f"Ignoring call to '{self.func}' " + f"(setting return value to {self.retval:#x})") + return False + + +class ChangePermissionBreakpoint(gdb.Breakpoint): + """When hit, this temporary breakpoint will restore the original code, and position + $pc correctly.""" + + def __init__(self, loc: str, code: ByteString, pc: int) -> None: + super().__init__(loc, gdb.BP_BREAKPOINT, internal=False) + self.original_code = code + self.original_pc = pc + return + + def stop(self) -> bool: + info("Restoring original context") + gef.memory.write(self.original_pc, self.original_code, len(self.original_code)) + info("Restoring $pc") + gdb.execute(f"set $pc = {self.original_pc:#x}") + return True + + +class TraceMallocBreakpoint(gdb.Breakpoint): + """Track allocations done with malloc() or calloc().""" + + def __init__(self, name: str) -> None: + super().__init__(name, gdb.BP_BREAKPOINT, internal=True) + self.silent = True + self.name = name + return + + def stop(self) -> bool: + reset_all_caches() + _, size = gef.arch.get_ith_parameter(0) + self.retbp = TraceMallocRetBreakpoint(size, self.name) + return False + + +class TraceMallocRetBreakpoint(gdb.FinishBreakpoint): + """Internal temporary breakpoint to retrieve the return value of malloc().""" + + def __init__(self, size: int, name: str) -> None: + super().__init__(gdb.newest_frame(), internal=True) + self.size = size + self.name = name + self.silent = True + return + + def stop(self) -> bool: + if self.return_value: + loc = int(self.return_value) + else: + loc = parse_address(gef.arch.return_register) + + size = self.size + ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - {self.name}({size})={loc:#x}") + check_heap_overlap = gef.config["heap-analysis-helper.check_heap_overlap"] + + # pop from free-ed list if it was in it + if gef.session.heap_freed_chunks: + idx = 0 + for item in gef.session.heap_freed_chunks: + addr = item[0] + if addr == loc: + gef.session.heap_freed_chunks.remove(item) + continue + idx += 1 + + # pop from uaf watchlist + if gef.session.heap_uaf_watchpoints: + idx = 0 + for wp in gef.session.heap_uaf_watchpoints: + wp_addr = wp.address + if loc <= wp_addr < loc + size: + gef.session.heap_uaf_watchpoints.remove(wp) + wp.enabled = False + continue + idx += 1 + + item = (loc, size) + + if check_heap_overlap: + # seek all the currently allocated chunks, read their effective size and check for overlap + msg = [] + align = gef.arch.ptrsize + for chunk_addr, _ in gef.session.heap_allocated_chunks: + current_chunk = GlibcChunk(chunk_addr) + current_chunk_size = current_chunk.get_chunk_size() + + if chunk_addr <= loc < chunk_addr + current_chunk_size: + offset = loc - chunk_addr - 2*align + if offset < 0: continue # false positive, discard + + msg.append(Color.colorify("Heap-Analysis", "yellow bold")) + msg.append("Possible heap overlap detected") + msg.append(f"Reason {RIGHT_ARROW} new allocated chunk {loc:#x} (of size {size:d}) overlaps in-used chunk {chunk_addr:#x} (of size {current_chunk_size:#x})") + msg.append(f"Writing {offset:d} bytes from {chunk_addr:#x} will reach chunk {loc:#x}") + msg.append(f"Payload example for chunk {chunk_addr:#x} (to overwrite {loc:#x} headers):") + msg.append(" data = 'A'*{0:d} + 'B'*{1:d} + 'C'*{1:d}".format(offset, align)) + push_context_message("warn", "\n".join(msg)) + return True + + # add it to alloc-ed list + gef.session.heap_allocated_chunks.append(item) + return False + + +class TraceReallocBreakpoint(gdb.Breakpoint): + """Track re-allocations done with realloc().""" + + def __init__(self) -> None: + super().__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True) + self.silent = True + return + + def stop(self) -> bool: + _, ptr = gef.arch.get_ith_parameter(0) + _, size = gef.arch.get_ith_parameter(1) + self.retbp = TraceReallocRetBreakpoint(ptr, size) + return False + + +class TraceReallocRetBreakpoint(gdb.FinishBreakpoint): + """Internal temporary breakpoint to retrieve the return value of realloc().""" + + def __init__(self, ptr: int, size: int) -> None: + super().__init__(gdb.newest_frame(), internal=True) + self.ptr = ptr + self.size = size + self.silent = True + return + + def stop(self) -> bool: + if self.return_value: + newloc = int(self.return_value) + else: + newloc = parse_address(gef.arch.return_register) + + if newloc != self: + ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"), + self.ptr, self.size, + Color.colorify(f"{newloc:#x}", "green"),)) + else: + ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"), + self.ptr, self.size, + Color.colorify(f"{newloc:#x}", "red"),)) + + item = (newloc, self.size) + + try: + # check if item was in alloc-ed list + idx = [x for x, y in gef.session.heap_allocated_chunks].index(self.ptr) + # if so pop it out + item = gef.session.heap_allocated_chunks.pop(idx) + except ValueError: + if is_debug(): + warn(f"Chunk {self.ptr:#x} was not in tracking list") + finally: + # add new item to alloc-ed list + gef.session.heap_allocated_chunks.append(item) + + return False + + +class TraceFreeBreakpoint(gdb.Breakpoint): + """Track calls to free() and attempts to detect inconsistencies.""" + + def __init__(self) -> None: + super().__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True) + self.silent = True + return + + def stop(self) -> bool: + reset_all_caches() + _, addr = gef.arch.get_ith_parameter(0) + msg = [] + check_free_null = gef.config["heap-analysis-helper.check_free_null"] + check_double_free = gef.config["heap-analysis-helper.check_double_free"] + check_weird_free = gef.config["heap-analysis-helper.check_weird_free"] + check_uaf = gef.config["heap-analysis-helper.check_uaf"] + + ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - free({addr:#x})") + if addr == 0: + if check_free_null: + msg.append(Color.colorify("Heap-Analysis", "yellow bold")) + msg.append(f"Attempting to free(NULL) at {gef.arch.pc:#x}") + msg.append("Reason: if NULL page is allocatable, this can lead to code execution.") + push_context_message("warn", "\n".join(msg)) + return True + return False + + if addr in [x for (x, y) in gef.session.heap_freed_chunks]: + if check_double_free: + msg.append(Color.colorify("Heap-Analysis", "yellow bold")) + msg.append(f"Double-free detected {RIGHT_ARROW} free({addr:#x}) is called at {gef.arch.pc:#x} but is already in the free-ed list") + msg.append("Execution will likely crash...") + push_context_message("warn", "\n".join(msg)) + return True + return False + + # if here, no error + # 1. move alloc-ed item to free list + try: + # pop from alloc-ed list + idx = [x for x, y in gef.session.heap_allocated_chunks].index(addr) + item = gef.session.heap_allocated_chunks.pop(idx) + + except ValueError: + if check_weird_free: + msg.append(Color.colorify("Heap-Analysis", "yellow bold")) + msg.append("Heap inconsistency detected:") + msg.append(f"Attempting to free an unknown value: {addr:#x}") + push_context_message("warn", "\n".join(msg)) + return True + return False + + # 2. add it to free-ed list + gef.session.heap_freed_chunks.append(item) + + self.retbp = None + if check_uaf: + # 3. (opt.) add a watchpoint on pointer + self.retbp = TraceFreeRetBreakpoint(addr) + return False + + +class TraceFreeRetBreakpoint(gdb.FinishBreakpoint): + """Internal temporary breakpoint to track free()d values.""" + + def __init__(self, addr: int) -> None: + super().__init__(gdb.newest_frame(), internal=True) + self.silent = True + self.addr = addr + return + + def stop(self) -> bool: + reset_all_caches() + wp = UafWatchpoint(self.addr) + gef.session.heap_uaf_watchpoints.append(wp) + return False + + +class UafWatchpoint(gdb.Breakpoint): + """Custom watchpoints set TraceFreeBreakpoint() to monitor free()d pointers being used.""" + + def __init__(self, addr: int) -> None: + super().__init__(f"*{addr:#x}", gdb.BP_WATCHPOINT, internal=True) + self.address = addr + self.silent = True + self.enabled = True + return + + def stop(self) -> bool: + """If this method is triggered, we likely have a UaF. Break the execution and report it.""" + reset_all_caches() + frame = gdb.selected_frame() + if frame.name() in ("_int_malloc", "malloc_consolidate", "__libc_calloc", ): + return False + + # software watchpoints stop after the next statement (see + # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html) + pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2) + insn = gef_current_instruction(pc) + msg = [] + msg.append(Color.colorify("Heap-Analysis", "yellow bold")) + msg.append(f"Possible Use-after-Free in '{get_filepath()}': " + f"pointer {self.address:#x} was freed, but is attempted to be used at {pc:#x}") + msg.append(f"{insn.address:#x} {insn.mnemonic} {Color.yellowify(', '.join(insn.operands))}") + push_context_message("warn", "\n".join(msg)) + return True + + +class EntryBreakBreakpoint(gdb.Breakpoint): + """Breakpoint used internally to stop execution at the most convenient entry point.""" + + def __init__(self, location: str) -> None: + super().__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True) + self.silent = True + return + + def stop(self) -> bool: + reset_all_caches() + return True + + +class NamedBreakpoint(gdb.Breakpoint): + """Breakpoint which shows a specified name, when hit.""" + + def __init__(self, location: str, name: str) -> None: + super().__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False) + self.name = name + self.loc = location + return + + def stop(self) -> bool: + reset_all_caches() + push_context_message("info", f"Hit breakpoint {self.loc} ({Color.colorify(self.name, 'red bold')})") + return True + + +# +# Context Panes +# + +def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]]) -> None: + """ + Registering function for new GEF Context View. + pane_name: a string that has no spaces (used in settings) + display_pane_function: a function that uses gef_print() to print strings + pane_title_function: a function that returns a string or None, which will be displayed as the title. + If None, no title line is displayed. + + Example Usage: + def display_pane(): gef_print("Wow, I am a context pane!") + def pane_title(): return "example:pane" + register_external_context_pane("example_pane", display_pane, pane_title) + """ + gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function) + return + + +# +# Commands +# + +def register_external_command(obj: "GenericCommand") -> Type["GenericCommand"]: + """Registering function for new GEF (sub-)command to GDB.""" + cls = obj.__class__ + __registered_commands__.append(cls) + gef.gdb.load(initial=False) + gef.gdb.doc.add_command_to_doc((cls._cmdline_, cls, None)) + gef.gdb.doc.refresh() + return cls + + +def register_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: + """Decorator for registering new GEF (sub-)command to GDB.""" + __registered_commands__.append(cls) + return cls + + +def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]: + """Decorator for registering new command with priority, meaning that it must + loaded before the other generic commands.""" + __registered_commands__.insert(0, cls) + return cls + + +def register_function(cls: Type["GenericFunction"]) -> Type["GenericFunction"]: + """Decorator for registering a new convenience function to GDB.""" + __registered_functions__.append(cls) + return cls + + +class GenericCommand(gdb.Command, metaclass=abc.ABCMeta): + """This is an abstract class for invoking commands, should not be instantiated.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + self.pre_load() + syntax = Color.yellowify("\nSyntax: ") + self._syntax_ + example = Color.yellowify("\nExample: ") + self._example_ if self._example_ else "" + self.__doc__ = self.__doc__.replace(" "*4, "") + syntax + example + self.repeat = False + self.repeat_count = 0 + self.__last_command = None + command_type = kwargs.setdefault("command", gdb.COMMAND_OBSCURE) + complete_type = kwargs.setdefault("complete", gdb.COMPLETE_NONE) + prefix = kwargs.setdefault("prefix", False) + super().__init__(self._cmdline_, command_type, complete_type, prefix) + self.post_load() + return + + def invoke(self, args: str, from_tty: bool) -> None: + try: + argv = gdb.string_to_argv(args) + self.__set_repeat_count(argv, from_tty) + bufferize(self.do_invoke)(argv) + except Exception as e: + # Note: since we are intercepting cleaning exceptions here, commands preferably should avoid + # catching generic Exception, but rather specific ones. This is allows a much cleaner use. + if is_debug(): + show_last_exception() + else: + err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}") + return + + def usage(self) -> None: + err(f"Syntax\n{self._syntax_}") + return + + @abc.abstractproperty + def _cmdline_(self) -> Optional[str]: pass + + @abc.abstractproperty + def _syntax_(self) -> Optional[str]: pass + + @abc.abstractproperty + def _example_(self) -> str: return "" + + @abc.abstractmethod + def do_invoke(self, argv: List[str]) -> None: pass + + def pre_load(self) -> None: pass + + def post_load(self) -> None: pass + + def __get_setting_name(self, name: str) -> str: + clsname = self.__class__._cmdline_.replace(" ", "-") + return f"{clsname}.{name}" + + def __iter__(self) -> Generator[str, None, None]: + for key in gef.config.keys(): + if key.startswith(self._cmdline_): + yield key.replace(f"{self._cmdline_}.", "", 1) + + @property + def settings(self) -> List[str]: + """Return the list of settings for this command.""" + return list(iter(self)) + + @deprecated("") + def get_setting(self, name: str) -> Any: + return self.__getitem__(name) + + def __getitem__(self, name: str) -> Any: + key = self.__get_setting_name(name) + return gef.config[key] + + @deprecated("") + def has_setting(self, name: str) -> bool: + return self.__contains__(name) + + def __contains__(self, name: str) -> bool: + return self.__get_setting_name(name) in gef.config + + @deprecated("") + def add_setting(self, name: str, value: Tuple[Any, type, str], description: str = "") -> None: + return self.__setitem__(name, (value, type(value), description)) + + def __setitem__(self, name: str, value: Union[Any, Tuple[Any, str]]) -> None: + # make sure settings are always associated to the root command (which derives from GenericCommand) + if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]: + return + key = self.__get_setting_name(name) + if key in gef.config: + setting = gef.config.raw_entry(key) + setting.value = value + else: + if len(value) == 1: + gef.config[key] = GefSetting(value[0]) + elif len(value) == 2: + gef.config[key] = GefSetting(value[0], description=value[1]) + return + + @deprecated("") + def del_setting(self, name: str) -> None: + return self.__delitem__(name) + + def __delitem__(self, name: str) -> None: + del gef.config[self.__get_setting_name(name)] + return + + def __set_repeat_count(self, argv: List[str], from_tty: bool) -> None: + if not from_tty: + self.repeat = False + self.repeat_count = 0 + return + + command = gdb.execute("show commands", to_string=True).strip().split("\n")[-1] + self.repeat = self.__last_command == command + self.repeat_count = self.repeat_count + 1 if self.repeat else 0 + self.__last_command = command + return + + +@register_command +class VersionCommand(GenericCommand): + """Display GEF version info.""" + + _cmdline_ = "version" + _syntax_ = f"{_cmdline_}" + _example_ = f"{_cmdline_}" + + def do_invoke(self, argv: List[str]) -> None: + gef_fpath = pathlib.Path(inspect.stack()[0][1]).expanduser().absolute() + gef_dir = gef_fpath.parent + with gef_fpath.open("rb") as f: + gef_hash = hashlib.sha256(f.read()).hexdigest() + + if os.access(f"{gef_dir}/.git", os.X_OK): + ver = subprocess.check_output("git log --format='%H' -n 1 HEAD", cwd=gef_dir, shell=True).decode("utf8").strip() + extra = "dirty" if len(subprocess.check_output("git ls-files -m", cwd=gef_dir, shell=True).decode("utf8").strip()) else "clean" + gef_print(f"GEF: rev:{ver} (Git - {extra})") + else: + gef_blob_hash = subprocess.check_output(f"git hash-object {gef_fpath}", shell=True).decode().strip() + gef_print("GEF: (Standalone)") + gef_print(f"Blob Hash({gef_fpath}): {gef_blob_hash}") + gef_print(f"SHA256({gef_fpath}): {gef_hash}") + gef_print(f"GDB: {gdb.VERSION}") + py_ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" + gef_print(f"GDB-Python: {py_ver}") + + if "full" in argv: + gef_print(f"Loaded commands: {', '.join(gef.gdb.loaded_command_names)}") + return + + +@register_command +class PrintFormatCommand(GenericCommand): + """Print bytes format in commonly used formats, such as literals in high level languages.""" + + valid_formats = ("py", "c", "js", "asm", "hex") + valid_bitness = (8, 16, 32, 64) + + _cmdline_ = "print-format" + _aliases_ = ["pf",] + _syntax_ = (f"{_cmdline_} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION" + f"\t--lang LANG specifies the output format for programming language (available: {valid_formats!s}, default 'py')." + f"\t--bitlen SIZE specifies size of bit (possible values: {valid_bitness!s}, default is 8)." + "\t--length LENGTH specifies length of array (default is 256)." + "\t--clip The output data will be copied to clipboard" + "\tLOCATION specifies where the address of bytes is stored.") + _example_ = f"{_cmdline_} --lang py -l 16 $rsp" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @property + def format_matrix(self) -> Dict[int, Tuple[str, str, str]]: + # `gef.arch.endianness` is a runtime property, should not be defined as a class property + return { + 8: (f"{gef.arch.endianness}B", "char", "db"), + 16: (f"{gef.arch.endianness}H", "short", "dw"), + 32: (f"{gef.arch.endianness}I", "int", "dd"), + 64: (f"{gef.arch.endianness}Q", "long long", "dq"), + } + + @only_if_gdb_running + @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": True,}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + """Default value for print-format command.""" + args = kwargs["arguments"] + args.bitlen = args.bitlen or gef.arch.ptrsize * 2 + + valid_bitlens = self.format_matrix.keys() + if args.bitlen not in valid_bitlens: + err(f"Size of bit must be in: {valid_bitlens!s}") + return + + if args.lang not in self.valid_formats: + err(f"Language must be in: {self.valid_formats!s}") + return + + start_addr = parse_address(args.location) + size = int(args.bitlen / 8) + end_addr = start_addr + args.length * size + fmt = self.format_matrix[args.bitlen][0] + data = [] + + for addr in range(start_addr, end_addr, size): + value = struct.unpack(fmt, gef.memory.read(addr, size))[0] + data += [value] + sdata = ", ".join(map(hex, data)) + + if args.lang == "py": + out = f"buf = [{sdata}]" + elif args.lang == "c": + c_type = self.format_matrix[args.bitlen][1] + out = f"unsigned {c_type} buf[{args.length}] = {{{sdata}}};" + elif args.lang == "js": + out = f"var buf = [{sdata}]" + elif args.lang == "asm": + asm_type = self.format_matrix[args.bitlen][2] + out = "buf {0} {1}".format(asm_type, sdata) + elif args.lang == "hex": + out = binascii.hexlify(gef.memory.read(start_addr, end_addr-start_addr)).decode() + + if args.clip: + if copy_to_clipboard(gef_pybytes(out)): + info("Copied to clipboard") + else: + warn("There's a problem while copying") + + gef_print(out) + return + + +@register_command +class PieCommand(GenericCommand): + """PIE breakpoint support.""" + + _cmdline_ = "pie" + _syntax_ = f"{_cmdline_} (breakpoint|info|delete|run|attach|remote)" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + def do_invoke(self, argv: List[str]) -> None: + if not argv: + self.usage() + return + + +@register_command +class PieBreakpointCommand(GenericCommand): + """Set a PIE breakpoint at an offset from the target binaries base address.""" + + _cmdline_ = "pie breakpoint" + _syntax_ = f"{_cmdline_} OFFSET" + + @parse_arguments({"offset": ""}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + if not args.offset: + self.usage() + return + + addr = parse_address(args.offset) + self.set_pie_breakpoint(lambda base: f"b *{base + addr}", addr) + + # When the process is already on, set real breakpoints immediately + if is_alive(): + vmmap = gef.memory.maps + base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] + for bp_ins in gef.session.pie_breakpoints.values(): + bp_ins.instantiate(base_address) + return + + @staticmethod + def set_pie_breakpoint(set_func: Callable[[int], str], addr: int) -> None: + gef.session.pie_breakpoints[gef.session.pie_counter] = PieVirtualBreakpoint(set_func, gef.session.pie_counter, addr) + gef.session.pie_counter += 1 + return + + +@register_command +class PieInfoCommand(GenericCommand): + """Display breakpoint info.""" + + _cmdline_ = "pie info" + _syntax_ = f"{_cmdline_} BREAKPOINT" + + @parse_arguments({"breakpoints": [-1,]}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + if args.breakpoints[0] == -1: + # No breakpoint info needed + bps = [gef.session.pie_breakpoints[x] for x in gef.session.pie_breakpoints] + else: + bps = [gef.session.pie_breakpoints[x] for x in args.breakpoints] + + lines = [] + lines.append("VNum\tNum\tAddr") + lines += [ + f"{x.vbp_num}\t{x.bp_num if x.bp_num else 'N/A'}\t{x.addr}" for x in bps + ] + gef_print("\n".join(lines)) + return + + +@register_command +class PieDeleteCommand(GenericCommand): + """Delete a PIE breakpoint.""" + + _cmdline_ = "pie delete" + _syntax_ = f"{_cmdline_} [BREAKPOINT]" + + @parse_arguments({"breakpoints": [-1,]}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + global gef + args = kwargs["arguments"] + if args.breakpoints[0] == -1: + # no arg, delete all + to_delete = [gef.session.pie_breakpoints[x] for x in gef.session.pie_breakpoints] + self.delete_bp(to_delete) + else: + self.delete_bp([gef.session.pie_breakpoints[x] for x in args.breakpoints]) + return + + + @staticmethod + def delete_bp(breakpoints: List) -> None: + global gef + for bp in breakpoints: + # delete current real breakpoints if exists + if bp.bp_num: + gdb.execute(f"delete {bp.bp_num}") + # delete virtual breakpoints + del gef.session.pie_breakpoints[bp.vbp_num] + return + + +@register_command +class PieRunCommand(GenericCommand): + """Run process with PIE breakpoint support.""" + + _cmdline_ = "pie run" + _syntax_ = _cmdline_ + + def do_invoke(self, argv: List[str]) -> None: + global gef + fpath = get_filepath() + if fpath is None: + warn("No executable to debug, use `file` to load a binary") + return + + if not os.access(fpath, os.X_OK): + warn(f"The file '{fpath}' is not executable.") + return + + if is_alive(): + warn("gdb is already running. Restart process.") + + # get base address + gdb.execute("set stop-on-solib-events 1") + hide_context() + gdb.execute(f"run {' '.join(argv)}") + unhide_context() + gdb.execute("set stop-on-solib-events 0") + vmmap = gef.memory.maps + base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] + info(f"base address {hex(base_address)}") + + # modify all breakpoints + for bp_ins in gef.session.pie_breakpoints.values(): + bp_ins.instantiate(base_address) + + try: + gdb.execute("continue") + except gdb.error as e: + err(e) + gdb.execute("kill") + return + + +@register_command +class PieAttachCommand(GenericCommand): + """Do attach with PIE breakpoint support.""" + + _cmdline_ = "pie attach" + _syntax_ = f"{_cmdline_} PID" + + def do_invoke(self, argv: List[str]) -> None: + try: + gdb.execute(f"attach {' '.join(argv)}", to_string=True) + except gdb.error as e: + err(e) + return + # after attach, we are stopped so that we can + # get base address to modify our breakpoint + vmmap = gef.memory.maps + base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] + + for bp_ins in gef.session.pie_breakpoints.values(): + bp_ins.instantiate(base_address) + gdb.execute("context") + return + + +@register_command +class PieRemoteCommand(GenericCommand): + """Attach to a remote connection with PIE breakpoint support.""" + + _cmdline_ = "pie remote" + _syntax_ = f"{_cmdline_} REMOTE" + + def do_invoke(self, argv: List[str]) -> None: + try: + gdb.execute(f"gef-remote {' '.join(argv)}") + except gdb.error as e: + err(e) + return + # after remote attach, we are stopped so that we can + # get base address to modify our breakpoint + vmmap = gef.memory.maps + base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0] + + for bp_ins in gef.session.pie_breakpoints.values(): + bp_ins.instantiate(base_address) + gdb.execute("context") + return + + +@register_command +class SmartEvalCommand(GenericCommand): + """SmartEval: Smart eval (vague approach to mimic WinDBG `?`).""" + + _cmdline_ = "$" + _syntax_ = f"{_cmdline_} EXPR\n{_cmdline_} ADDRESS1 ADDRESS2" + _example_ = (f"\n{_cmdline_} $pc+1" + f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000") + + def do_invoke(self, argv: List[str]) -> None: + argc = len(argv) + if argc == 1: + self.evaluate(argv) + return + + if argc == 2: + self.distance(argv) + return + + def evaluate(self, expr: List[str]) -> None: + def show_as_int(i: int) -> None: + off = gef.arch.ptrsize*8 + def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}" + def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}" + + try: + s_i = comp2_x(res) + s_i = s_i.rjust(len(s_i)+1, "0") if len(s_i)%2 else s_i + gef_print(f"{i:d}") + gef_print("0x" + comp2_x(res)) + gef_print("0b" + comp2_b(res)) + gef_print(f"{binascii.unhexlify(s_i)}") + gef_print(f"{binascii.unhexlify(s_i)[::-1]}") + except: + pass + return + + parsed_expr = [] + for xp in expr: + try: + xp = gdb.parse_and_eval(xp) + xp = int(xp) + parsed_expr.append(f"{xp:d}") + except gdb.error: + parsed_expr.append(str(xp)) + + try: + res = eval(" ".join(parsed_expr)) + if isinstance(res, int): + show_as_int(res) + else: + gef_print(f"{res}") + except SyntaxError: + gef_print(" ".join(parsed_expr)) + return + + def distance(self, args: Tuple[str, str]) -> None: + try: + x = int(args[0], 16) if is_hex(args[0]) else int(args[0]) + y = int(args[1], 16) if is_hex(args[1]) else int(args[1]) + gef_print(f"{abs(x - y)}") + except ValueError: + warn(f"Distance requires 2 numbers: {self._cmdline_} 0 0xffff") + return + + +@register_command +class CanaryCommand(GenericCommand): + """Shows the canary value of the current process.""" + + _cmdline_ = "canary" + _syntax_ = _cmdline_ + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + self.dont_repeat() + + has_canary = checksec(get_filepath())["Canary"] + if not has_canary: + warn("This binary was not compiled with SSP.") + return + + res = gef.session.canary + if not res: + err("Failed to get the canary") + return + + canary, location = res + info(f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}") + return + + +@register_command +class ProcessStatusCommand(GenericCommand): + """Extends the info given by GDB `info proc`, by giving an exhaustive description of the + process status (file descriptors, ancestor, descendants, etc.).""" + + _cmdline_ = "process-status" + _syntax_ = _cmdline_ + _aliases_ = ["status", ] + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_NONE) + return + + @only_if_gdb_running + @only_if_gdb_target_local + def do_invoke(self, argv: List[str]) -> None: + self.show_info_proc() + self.show_ancestor() + self.show_descendants() + self.show_fds() + self.show_connections() + return + + def get_state_of(self, pid: int) -> Dict[str, str]: + res = {} + with open(f"/proc/{pid}/status", "r") as f: + file = f.readlines() + for line in file: + key, value = line.split(":", 1) + res[key.strip()] = value.strip() + return res + + def get_cmdline_of(self, pid: int) -> str: + with open(f"/proc/{pid}/cmdline", "r") as f: + return f.read().replace("\x00", "\x20").strip() + + def get_process_path_of(self, pid: int) -> str: + return os.readlink(f"/proc/{pid}/exe") + + def get_children_pids(self, pid: int) -> List[int]: + cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"] + try: + return [int(x) for x in gef_execute_external(cmd, as_list=True)] + except Exception: + return [] + + def show_info_proc(self) -> None: + info("Process Information") + pid = gef.session.pid + cmdline = self.get_cmdline_of(pid) + gef_print(f"\tPID {RIGHT_ARROW} {pid}", + f"\tExecutable {RIGHT_ARROW} {self.get_process_path_of(pid)}", + f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") + return + + def show_ancestor(self) -> None: + info("Parent Process Information") + ppid = int(self.get_state_of(gef.session.pid)["PPid"]) + state = self.get_state_of(ppid) + cmdline = self.get_cmdline_of(ppid) + gef_print(f"\tParent PID {RIGHT_ARROW} {state['Pid']}", + f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n") + return + + def show_descendants(self) -> None: + info("Children Process Information") + children = self.get_children_pids(gef.session.pid) + if not children: + gef_print("\tNo child process") + return + + for child_pid in children: + state = self.get_state_of(child_pid) + pid = state["Pid"] + gef_print(f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}', CmdLine: '{self.get_cmdline_of(pid)}')") + return + + def show_fds(self) -> None: + pid = gef.session.pid + path = f"/proc/{pid:d}/fd" + + info("File Descriptors:") + items = os.listdir(path) + if not items: + gef_print("\tNo FD opened") + return + + for fname in items: + fullpath = os.path.join(path, fname) + if os.path.islink(fullpath): + gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}") + return + + def list_sockets(self, pid: int) -> List[int]: + sockets = [] + path = f"/proc/{pid:d}/fd" + items = os.listdir(path) + for fname in items: + fullpath = os.path.join(path, fname) + if os.path.islink(fullpath) and os.readlink(fullpath).startswith("socket:"): + p = os.readlink(fullpath).replace("socket:", "")[1:-1] + sockets.append(int(p)) + return sockets + + def parse_ip_port(self, addr: str) -> Tuple[str, int]: + ip, port = addr.split(":") + return socket.inet_ntoa(struct.pack("<I", int(ip, 16))), int(port, 16) + + def show_connections(self) -> None: + # https://github.com/torvalds/linux/blob/v4.7/include/net/tcp_states.h#L16 + tcp_states_str = { + 0x01: "TCP_ESTABLISHED", + 0x02: "TCP_SYN_SENT", + 0x03: "TCP_SYN_RECV", + 0x04: "TCP_FIN_WAIT1", + 0x05: "TCP_FIN_WAIT2", + 0x06: "TCP_TIME_WAIT", + 0x07: "TCP_CLOSE", + 0x08: "TCP_CLOSE_WAIT", + 0x09: "TCP_LAST_ACK", + 0x0A: "TCP_LISTEN", + 0x0B: "TCP_CLOSING", + 0x0C: "TCP_NEW_SYN_RECV", + } + + udp_states_str = { + 0x07: "UDP_LISTEN", + } + + info("Network Connections") + pid = gef.session.pid + sockets = self.list_sockets(pid) + if not sockets: + gef_print("\tNo open connections") + return + + entries = dict() + with open(f"/proc/{pid:d}/net/tcp", "r") as tcp: + entries["TCP"] = [x.split() for x in tcp.readlines()[1:]] + with open(f"/proc/{pid:d}/net/udp", "r") as udp: + entries["UDP"] = [x.split() for x in udp.readlines()[1:]] + + for proto in entries: + for entry in entries[proto]: + local, remote, state = entry[1:4] + inode = int(entry[9]) + if inode in sockets: + local = self.parse_ip_port(local) + remote = self.parse_ip_port(remote) + state = int(state, 16) + state_str = tcp_states_str[state] if proto == "TCP" else udp_states_str[state] + + gef_print(f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})") + return + + +@register_priority_command +class GefThemeCommand(GenericCommand): + """Customize GEF appearance.""" + + _cmdline_ = "theme" + _syntax_ = f"{_cmdline_} [KEY [VALUE]]" + + def __init__(self) -> None: + super().__init__(self._cmdline_) + self["context_title_line"] = ("gray", "Color of the borders in context window") + self["context_title_message"] = ("cyan", "Color of the title in context window") + self["default_title_line"] = ("gray", "Default color of borders") + self["default_title_message"] = ("cyan", "Default color of title") + self["table_heading"] = ("blue", "Color of the column headings to tables (e.g. vmmap)") + self["old_context"] = ("gray", "Color to use to show things such as code that is not immediately relevant") + self["disassemble_current_instruction"] = ("green", "Color to use to highlight the current $pc when disassembling") + self["dereference_string"] = ("yellow", "Color of dereferenced string") + self["dereference_code"] = ("gray", "Color of dereferenced code") + self["dereference_base_address"] = ("cyan", "Color of dereferenced address") + self["dereference_register_value"] = ("bold blue", "Color of dereferenced register") + self["registers_register_name"] = ("blue", "Color of the register name in the register window") + self["registers_value_changed"] = ("bold red", "Color of the changed register in the register window") + self["address_stack"] = ("pink", "Color to use when a stack address is found") + self["address_heap"] = ("green", "Color to use when a heap address is found") + self["address_code"] = ("red", "Color to use when a code address is found") + self["source_current_line"] = ("green", "Color to use for the current code line in the source window") + return + + def do_invoke(self, args: List[str]) -> None: + self.dont_repeat() + argc = len(args) + + if argc == 0: + for key in self.settings: + setting = self[key] + value = Color.colorify(setting, setting) + gef_print(f"{key:40s}: {value}") + return + + setting_name = args[0] + if not setting_name in self: + err("Invalid key") + return + + if argc == 1: + value = self[setting_name] + gef_print(f"{setting_name:40s}: {Color.colorify(value, value)}") + return + + colors = [color for color in args[1:] if color in Color.colors] + self[setting_name] = " ".join(colors) + return + + +class ExternalStructureManager: + class Structure: + def __init__(self, manager: "ExternalStructureManager", mod_path: pathlib.Path, struct_name: str) -> None: + self.manager = manager + self.module_path = mod_path + self.name = struct_name + self.class_type = self.__get_structure_class() + return + + def __str__(self) -> str: + return self.name + + def pprint(self) -> None: + res = [] + for _name, _type in self.class_type._fields_: + size = ctypes.sizeof(_type) + name = Color.colorify(_name, gef.config["pcustom.structure_name"]) + type = Color.colorify(_type.__name__, gef.config["pcustom.structure_type"]) + size = Color.colorify(hex(size), gef.config["pcustom.structure_size"]) + offset = Color.boldify(f"{getattr(self.class_type, _name).offset:04x}") + res.append(f"{offset} {name:32s} {type:16s} /* size={size} */") + gef_print("\n".join(res)) + return + + def __get_structure_class(self) -> Type: + """Returns a tuple of (class, instance) if modname!classname exists""" + fpath = self.module_path + spec = importlib.util.spec_from_file_location(fpath.stem, fpath) + module = importlib.util.module_from_spec(spec) + sys.modules[fpath.stem] = module + spec.loader.exec_module(module) + _class = getattr(module, self.name) + return _class + + def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None: + """Apply (recursively if possible) the structure format to the given address.""" + if depth >= max_depth: + warn("maximum recursion level reached") + return + + # read the data at the specified address + _structure = self.class_type() + _sizeof_structure = ctypes.sizeof(_structure) + + try: + data = gef.memory.read(address, _sizeof_structure) + except gdb.MemoryError: + err(f"{' ' * depth}Cannot read memory {address:#x}") + return + + # deserialize the data + length = min(len(data), _sizeof_structure) + ctypes.memmove(ctypes.addressof(_structure), data, length) + + # pretty print all the fields (and call recursively if possible) + ptrsize = gef.arch.ptrsize + unpack = u32 if ptrsize == 4 else u64 + for field in _structure._fields_: + _name, _type = field + _value = getattr(_structure, _name) + _offset = getattr(self.class_type, _name).offset + + if ((ptrsize == 4 and _type is ctypes.c_uint32) + or (ptrsize == 8 and _type is ctypes.c_uint64) + or (ptrsize == ctypes.sizeof(ctypes.c_void_p) and _type is ctypes.c_void_p)): + # try to dereference pointers + _value = RIGHT_ARROW.join(dereference_from(_value)) + + line = f"{' ' * depth}" + line += f"{address:#x}+{_offset:#04x} {_name} : ".ljust(40) + line += f"{_value} ({_type.__name__})" + parsed_value = self.__get_ctypes_value(_structure, _name, _value) + if parsed_value: + line += f"{RIGHT_ARROW} {parsed_value}" + gef_print(line) + + if issubclass(_type, ctypes.Structure): + self.apply_at(address + _offset, max_depth, depth + 1) + elif _type.__name__.startswith("LP_"): + # Pointer to a structure of a different type + __sub_type_name = _type.__name__.lstrip("LP_") + result = self.manager.find(__sub_type_name) + if result: + _, __structure = result + __address = unpack(gef.memory.read(address + _offset, ptrsize)) + __structure.apply_at(__address, max_depth, depth + 1) + return + + def __get_ctypes_value(self, struct, item, value) -> str: + if not hasattr(struct, "_values_"): return "" + default = "" + for name, values in struct._values_: + if name != item: continue + if callable(values): + return values(value) + try: + for val, desc in values: + if value == val: return desc + if val is None: default = desc + except Exception as e: + err(f"Error parsing '{name}': {e}") + return default + + class Module(dict): + def __init__(self, manager: "ExternalStructureManager", path: pathlib.Path) -> None: + self.manager = manager + self.path = path + self.name = path.stem + self.raw = self.__load() + + for entry in self: + structure = ExternalStructureManager.Structure(manager, self.path, entry) + self[structure.name] = structure + return + + def __load(self) -> ModuleType: + """Load a custom module, and return it.""" + fpath = self.path + spec = importlib.util.spec_from_file_location(fpath.stem, fpath) + module = importlib.util.module_from_spec(spec) + sys.modules[fpath.stem] = module + spec.loader.exec_module(module) + return module + + def __str__(self) -> str: + return self.name + + def __iter__(self) -> Generator[str, None, None]: + _invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"} + _structs = {x for x in dir(self.raw) \ + if inspect.isclass(getattr(self.raw, x)) \ + and issubclass(getattr(self.raw, x), ctypes.Structure)} + for entry in (_structs - _invalid): + yield entry + return + + class Modules(dict): + def __init__(self, manager: "ExternalStructureManager") -> None: + self.manager: "ExternalStructureManager" = manager + self.root: pathlib.Path = manager.path + + for entry in self.root.iterdir(): + if not entry.is_file(): continue + if entry.suffix != ".py": continue + if entry.name == "__init__.py": continue + module = ExternalStructureManager.Module(manager, entry) + self[module.name] = module + return + + def __contains__(self, structure_name: str) -> bool: + """Return True if the structure name is found in any of the modules""" + for module in self.values(): + if structure_name in module: + return True + return False + + def __init__(self) -> None: + self.clear_caches() + return + + def clear_caches(self) -> None: + self._path = None + self._modules = None + return + + @property + def modules(self) -> "ExternalStructureManager.Modules": + if not self._modules: + self._modules = ExternalStructureManager.Modules(self) + return self._modules + + @property + def path(self) -> pathlib.Path: + if not self._path: + self._path = pathlib.Path(gef.config["pcustom.struct_path"]).expanduser().absolute() + return self._path + + @property + def structures(self) -> Generator[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]: + for module in self.modules.values(): + for structure in module.values(): + yield module, structure + return + + @lru_cache() + def find(self, structure_name: str) -> Optional[Tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"]]: + """Return the module and structure for the given structure name; `None` if the structure name was not found.""" + for module in self.modules.values(): + if structure_name in module: + return module, module[structure_name] + return None + + +@register_command +class PCustomCommand(GenericCommand): + """Dump user defined structure. + This command attempts to reproduce WinDBG awesome `dt` command for GDB and allows + to apply structures (from symbols or custom) directly to an address. + Custom structures can be defined in pure Python using ctypes, and should be stored + in a specific directory, whose path must be stored in the `pcustom.struct_path` + configuration setting.""" + + _cmdline_ = "pcustom" + _syntax_ = f"{_cmdline_} [list|edit <StructureName>|show <StructureName>]|<StructureName> 0xADDRESS]" + + def __init__(self) -> None: + super().__init__(prefix=True) + self["struct_path"] = (str(pathlib.Path(gef.config["gef.tempdir"]) / "structs"), "Path to store/load the structure ctypes files") + self["max_depth"] = (4, "Maximum level of recursion supported") + self["structure_name"] = ("bold blue", "Color of the structure name") + self["structure_type"] = ("bold red", "Color of the attribute type") + self["structure_size"] = ("green", "Color of the attribute size") + return + + @parse_arguments({"type": "", "address": ""}, {}) + def do_invoke(self, *_: Any, **kwargs: Dict[str, Any]) -> None: + args = kwargs["arguments"] + if not args.type: + gdb.execute("pcustom list") + return + + _, structname = self.explode_type(args.type) + + if not args.address: + gdb.execute(f"pcustom show {structname}") + return + + if not is_alive(): + err("Session is not active") + return + + manager = ExternalStructureManager() + address = parse_address(args.address) + result = manager.find(structname) + if not result: + err(f"No structure named '{structname}' found") + return + + _, structure = result + structure.apply_at(address, self["max_depth"]) + return + + def explode_type(self, arg: str) -> Tuple[str, str]: + modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg) + structname = structname.split(".", 1)[0] if "." in structname else structname + return modname, structname + + +@register_command +class PCustomListCommand(PCustomCommand): + """PCustom: list available structures""" + + _cmdline_ = "pcustom list" + _syntax_ = f"{_cmdline_}" + + def __init__(self) -> None: + super().__init__() + return + + def do_invoke(self, _: List) -> None: + """Dump the list of all the structures and their respective.""" + manager = ExternalStructureManager() + info(f"Listing custom structures from '{manager.path}'") + struct_color = gef.config["pcustom.structure_type"] + filename_color = gef.config["pcustom.structure_name"] + for module in manager.modules.values(): + __modules = ", ".join([Color.colorify(structure_name, struct_color) for structure_name in module.values()]) + __filename = Color.colorify(str(module.path), filename_color) + gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})") + return + + +@register_command +class PCustomShowCommand(PCustomCommand): + """PCustom: show the content of a given structure""" + + _cmdline_ = "pcustom show" + _syntax_ = f"{_cmdline_} StructureName" + __aliases__ = ["pcustom create", "pcustom update"] + + def __init__(self) -> None: + super().__init__() + return + + def do_invoke(self, argv: List[str]) -> None: + if len(argv) == 0: + self.usage() + return + + _, structname = self.explode_type(argv[0]) + manager = ExternalStructureManager() + result = manager.find(structname) + if result: + _, structure = result + structure.pprint() + else: + err(f"No structure named '{structname}' found") + return + + +@register_command +class PCustomEditCommand(PCustomCommand): + """PCustom: edit the content of a given structure""" + + _cmdline_ = "pcustom edit" + _syntax_ = f"{_cmdline_} StructureName" + __aliases__ = ["pcustom create", "pcustom new", "pcustom update"] + + def __init__(self) -> None: + super().__init__() + return + + def do_invoke(self, argv: List[str]) -> None: + if len(argv) == 0: + self.usage() + return + + modname, structname = self.explode_type(argv[0]) + self.__create_or_edit_structure(modname, structname) + return + + def __create_or_edit_structure(self, mod_name: str, struct_name: str) -> int: + path = pathlib.Path(gef.config["pcustom.struct_path"]).expanduser() / f"{mod_name}.py" + if path.is_file(): + info(f"Editing '{path}'") + else: + ok(f"Creating '{path}' from template") + self.__create_template(struct_name, path) + + cmd = (os.getenv("EDITOR") or "nano").split() + cmd.append(str(path.absolute())) + return subprocess.call(cmd) + + def __create_template(self, structname: str, fpath: pathlib.Path) -> None: + template = f"""from ctypes import * + +class {structname}(Structure): + _fields_ = [] + + _values_ = [] +""" + with fpath.open("w") as f: + f.write(template) + return + + +@register_command +class ChangeFdCommand(GenericCommand): + """ChangeFdCommand: redirect file descriptor during runtime.""" + + _cmdline_ = "hijack-fd" + _syntax_ = f"{_cmdline_} FD_NUM NEW_OUTPUT" + _example_ = f"{_cmdline_} 2 /tmp/stderr_output.txt" + + @only_if_gdb_running + @only_if_gdb_target_local + def do_invoke(self, argv: List[str]) -> None: + if len(argv) != 2: + self.usage() + return + + if not os.access(f"/proc/{gef.session.pid:d}/fd/{argv[0]}", os.R_OK): + self.usage() + return + + old_fd = int(argv[0]) + new_output = argv[1] + + if ":" in new_output: + address = socket.gethostbyname(new_output.split(":")[0]) + port = int(new_output.split(":")[1]) + + AF_INET = 2 + SOCK_STREAM = 1 + res = gdb.execute(f"""call (int)socket({AF_INET}, {SOCK_STREAM}, 0)""", to_string=True) + new_fd = self.get_fd_from_result(res) + + # fill in memory with sockaddr_in struct contents + # we will do this in the stack, since connect() wants a pointer to a struct + vmmap = gef.memory.maps + stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0] + original_contents = gef.memory.read(stack_addr, 8) + + gef.memory.write(stack_addr, b"\x02\x00", 2) + gef.memory.write(stack_addr + 0x2, struct.pack("<H", socket.htons(port)), 2) + gef.memory.write(stack_addr + 0x4, socket.inet_aton(address), 4) + + info(f"Trying to connect to {new_output}") + res = gdb.execute(f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True) + + # recover stack state + gef.memory.write(stack_addr, original_contents, 8) + + res = self.get_fd_from_result(res) + if res == -1: + err(f"Failed to connect to {address}:{port}") + return + + info(f"Connected to {new_output}") + else: + res = gdb.execute(f"""call (int)open("{new_output}", 66, 0666)""", to_string=True) + new_fd = self.get_fd_from_result(res) + + info(f"Opened '{new_output}' as fd #{new_fd:d}") + gdb.execute(f"""call (int)dup2({new_fd:d}, {old_fd:d})""", to_string=True) + info(f"Duplicated fd #{new_fd:d}{RIGHT_ARROW}#{old_fd:d}") + gdb.execute(f"""call (int)close({new_fd:d})""", to_string=True) + info(f"Closed extra fd #{new_fd:d}") + ok("Success") + return + + def get_fd_from_result(self, res: str) -> int: + # Output example: $1 = 3 + res = int(res.split()[2], 0) + res = gdb.execute(f"""p/d {res}""", to_string=True) + res = int(res.split()[2], 0) + return res + +@register_command +class IdaInteractCommand(GenericCommand): + """IDA Interact: set of commands to interact with IDA via a XML RPC service + deployed via the IDA script `ida_gef.py`. It should be noted that this command + can also be used to interact with Binary Ninja (using the script `binja_gef.py`) + using the same interface.""" + + _cmdline_ = "ida-interact" + _syntax_ = f"{_cmdline_} METHOD [ARGS]" + _aliases_ = ["binaryninja-interact", "bn", "binja"] + _example_ = f"\n{_cmdline_} Jump $pc\n{_cmdline_} SetColor $pc ff00ff" + + def __init__(self) -> None: + super().__init__(prefix=False) + self["host"] = ("127.0.0.1", "IP address to use connect to IDA/Binary Ninja script") + self["port"] = (1337, "Port to use connect to IDA/Binary Ninja script") + self["sync_cursor"] = (False, "Enable real-time $pc synchronization") + + self.sock = None + self.version = ("", "") + self.old_bps = set() + return + + def is_target_alive(self, host: str, port: int) -> bool: + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + s.connect((host, port)) + s.close() + except OSError: + return False + return True + + def connect(self, host: Optional[str] = None, port: Optional[int] = None) -> None: + """Connect to the XML-RPC service.""" + host = host or self["host"] + port = port or self["port"] + + try: + sock = xmlrpclib.ServerProxy(f"http://{host}:{port:d}") + gef_on_stop_hook(ida_synchronize_handler) + gef_on_continue_hook(ida_synchronize_handler) + self.version = sock.version() + except ConnectionRefusedError: + err(f"Failed to connect to '{host}:{port:d}'") + sock = None + self.sock = sock + return + + def disconnect(self) -> None: + gef_on_stop_unhook(ida_synchronize_handler) + gef_on_continue_unhook(ida_synchronize_handler) + self.sock = None + return + + @deprecated("") + def do_invoke(self, argv: List[str]) -> None: + def parsed_arglist(arglist: List[str]) -> List[str]: + args = [] + for arg in arglist: + try: + # try to solve the argument using gdb + argval = gdb.parse_and_eval(arg) + argval.fetch_lazy() + # check if value is addressable + argval = int(argval) if argval.address is None else int(argval.address) + # if the bin is PIE, we need to subtract the base address + if is_pie(get_filepath()) and main_base_address <= argval < main_end_address: + argval -= main_base_address + args.append(f"{argval:#x}") + except Exception: + # if gdb can't parse the value, let ida deal with it + args.append(arg) + return args + + if self.sock is None: + # trying to reconnect + self.connect() + if self.sock is None: + self.disconnect() + return + + if len(argv) == 0 or argv[0] in ("-h", "--help"): + method_name = argv[1] if len(argv) > 1 else None + self.usage(method_name) + return + + method_name = argv[0].lower() + if method_name == "version": + self.version = self.sock.version() + info(f"Enhancing {Color.greenify('gef')} with {Color.redify(self.version[0])} " + f"(SDK {Color.yellowify(self.version[1])})") + return + + if not is_alive(): + main_base_address = main_end_address = 0 + else: + vmmap = gef.memory.maps + main_base_address = min([x.page_start for x in vmmap if x.realpath == get_filepath()]) + main_end_address = max([x.page_end for x in vmmap if x.realpath == get_filepath()]) + + try: + if method_name == "sync": + self.synchronize() + else: + method = getattr(self.sock, method_name) + if len(argv) > 1: + args = parsed_arglist(argv[1:]) + res = method(*args) + else: + res = method() + + if method_name == "importstruct": + self.import_structures(res) + else: + gef_print(str(res)) + + if self["sync_cursor"] is True: + jump = getattr(self.sock, "jump") + jump(hex(gef.arch.pc-main_base_address),) + + except OSError: + self.disconnect() + return + + def synchronize(self) -> None: + """Submit all active breakpoint addresses to IDA/BN.""" + pc = gef.arch.pc + vmmap = gef.memory.maps + base_address = min([x.page_start for x in vmmap if x.path == get_filepath()]) + end_address = max([x.page_end for x in vmmap if x.path == get_filepath()]) + if not (base_address <= pc < end_address): + # do not sync in library + return + + breakpoints = gdb.breakpoints() or [] + gdb_bps = set() + for bp in breakpoints: + if bp.enabled and not bp.temporary: + if bp.location[0] == "*": # if it's an address i.e. location starts with "*" + addr = parse_address(bp.location[1:]) + else: # it is a symbol + addr = int(gdb.parse_and_eval(bp.location).address) + if not (base_address <= addr < end_address): + continue + gdb_bps.add(addr - base_address) + + added = gdb_bps - self.old_bps + removed = self.old_bps - gdb_bps + self.old_bps = gdb_bps + + try: + # it is possible that the server was stopped between now and the last sync + rc = self.sock.sync(f"{pc-base_address:#x}", list(added), list(removed)) + except ConnectionRefusedError: + self.disconnect() + return + + ida_added, ida_removed = rc + + # add new bp from IDA + for new_bp in ida_added: + location = base_address + new_bp + gdb.Breakpoint(f"*{location:#x}", type=gdb.BP_BREAKPOINT) + self.old_bps.add(location) + + # and remove the old ones + breakpoints = gdb.breakpoints() or [] + for bp in breakpoints: + if bp.enabled and not bp.temporary: + if bp.location[0] == "*": # if it's an address i.e. location starts with "*" + addr = parse_address(bp.location[1:]) + else: # it is a symbol + addr = int(gdb.parse_and_eval(bp.location).address) + + if not (base_address <= addr < end_address): + continue + + if (addr - base_address) in ida_removed: + if (addr - base_address) in self.old_bps: + self.old_bps.remove((addr - base_address)) + bp.delete() + return + + def usage(self, meth: Optional[str] = None) -> None: + if self.sock is None: + return + + if meth is not None: + gef_print(titlify(meth)) + gef_print(self.sock.system.methodHelp(meth)) + return + + info("Listing available methods and syntax examples: ") + for m in self.sock.system.listMethods(): + if m.startswith("system."): continue + gef_print(titlify(m)) + gef_print(self.sock.system.methodHelp(m)) + return + + def import_structures(self, structs: Dict[str, List[Tuple[int, str, int]]]) -> None: + if self.version[0] != "IDA Pro": + return + + path = gef.config["pcustom.struct_path"] + if path is None: + return + + if not os.path.isdir(path): + gef_makedirs(path) + + for struct_name in structs: + fullpath = pathlib.Path(path) / f"{struct_name}.py" + with fullpath.open("w") as f: + f.write("from ctypes import *\n\n") + f.write("class ") + f.write(struct_name) + f.write("(Structure):\n") + f.write(" _fields_ = [\n") + for _, name, size in structs[struct_name]: + name = bytes(name, encoding="utf-8") + if size == 1: csize = "c_uint8" + elif size == 2: csize = "c_uint16" + elif size == 4: csize = "c_uint32" + elif size == 8: csize = "c_uint64" + else: csize = f"c_byte * {size}" + m = f' (\"{name}\", {csize}),\n' + f.write(m) + f.write("]\n") + ok(f"Success, {len(structs):d} structure{'s' if len(structs) > 1 else ''} imported") + return + + +@register_command +class ScanSectionCommand(GenericCommand): + """Search for addresses that are located in a memory mapping (haystack) that belonging + to another (needle).""" + + _cmdline_ = "scan" + _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE" + _aliases_ = ["lookup",] + _example_ = f"\n{_cmdline_} stack libc" + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) != 2: + self.usage() + return + + haystack = argv[0] + needle = argv[1] + + info(f"Searching for addresses in '{Color.yellowify(haystack)}' " + f"that point to '{Color.yellowify(needle)}'") + + if haystack == "binary": + haystack = get_filepath() + + if needle == "binary": + needle = get_filepath() + + needle_sections = [] + haystack_sections = [] + + if "0x" in haystack: + start, end = parse_string_range(haystack) + haystack_sections.append((start, end, "")) + + if "0x" in needle: + start, end = parse_string_range(needle) + needle_sections.append((start, end)) + + for sect in gef.memory.maps: + if haystack in sect.path: + haystack_sections.append((sect.page_start, sect.page_end, os.path.basename(sect.path))) + if needle in sect.path: + needle_sections.append((sect.page_start, sect.page_end)) + + step = gef.arch.ptrsize + unpack = u32 if step == 4 else u64 + + for hstart, hend, hname in haystack_sections: + try: + mem = gef.memory.read(hstart, hend - hstart) + except gdb.MemoryError: + continue + + for i in range(0, len(mem), step): + target = unpack(mem[i:i+step]) + for nstart, nend in needle_sections: + if target >= nstart and target < nend: + deref = DereferenceCommand.pprint_dereferenced(hstart, int(i / step)) + if hname != "": + name = Color.colorify(hname, "yellow") + gef_print(f"{name}: {deref}") + else: + gef_print(f" {deref}") + + return + + +@register_command +class SearchPatternCommand(GenericCommand): + """SearchPatternCommand: search a pattern in memory. If given an hex value (starting with 0x) + the command will also try to look for upwards cross-references to this address.""" + + _cmdline_ = "search-pattern" + _syntax_ = f"{_cmdline_} PATTERN [little|big] [section]" + _aliases_ = ["grep", "xref"] + _example_ = (f"\n{_cmdline_} AAAAAAAA" + f"\n{_cmdline_} 0x555555554000 little stack" + f"\n{_cmdline_} AAAA 0x600000-0x601000") + + def print_section(self, section: Section) -> None: + title = "In " + if section.path: + title += f"'{Color.blueify(section.path)}'" + + title += f"({section.page_start:#x}-{section.page_end:#x})" + title += f", permission={section.permission}" + ok(title) + return + + def print_loc(self, loc: Tuple[int, int, str]) -> None: + gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """) + return + + def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> List[Tuple[int, int, Optional[str]]]: + """Search a pattern within a range defined by arguments.""" + _pattern = gef_pybytes(pattern) + step = 0x400 * 0x1000 + locations = [] + + for chunk_addr in range(start_address, end_address, step): + if chunk_addr + step > end_address: + chunk_size = end_address - chunk_addr + else: + chunk_size = step + + try: + mem = gef.memory.read(chunk_addr, chunk_size) + except gdb.error as e: + estr = str(e) + if estr.startswith("Cannot access memory "): + # + # This is a special case where /proc/$pid/maps + # shows virtual memory address with a read bit, + # but it cannot be read directly from userspace. + # + # See: https://github.com/hugsy/gef/issues/674 + # + err(estr) + return [] + else: + raise e + + for match in re.finditer(_pattern, mem): + start = chunk_addr + match.start() + if is_ascii_string(start): + ustr = gef.memory.read_ascii_string(start) + end = start + len(ustr) + else: + ustr = gef_pystring(_pattern) + "[...]" + end = start + len(_pattern) + locations.append((start, end, ustr)) + + del mem + + return locations + + def search_pattern(self, pattern: str, section_name: str) -> None: + """Search a pattern within the whole userland memory.""" + for section in gef.memory.maps: + if not section.permission & Permission.READ: continue + if section.path == "[vvar]": continue + if not section_name in section.path: continue + + start = section.page_start + end = section.page_end - 1 + old_section = None + + for loc in self.search_pattern_by_address(pattern, start, end): + addr_loc_start = lookup_address(loc[0]) + if addr_loc_start and addr_loc_start.section: + if old_section != addr_loc_start.section: + self.print_section(addr_loc_start.section) + old_section = addr_loc_start.section + + self.print_loc(loc) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + argc = len(argv) + if argc < 1: + self.usage() + return + + pattern = argv[0] + endian = gef.arch.endianness + + if argc >= 2: + if argv[1].lower() == "big": endian = Endianness.BIG_ENDIAN + elif argv[1].lower() == "little": endian = Endianness.LITTLE_ENDIAN + + if is_hex(pattern): + if endian == Endianness.BIG_ENDIAN: + pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)]) + else: + pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)]) + + if argc == 3: + info(f"Searching '{Color.yellowify(pattern)}' in {argv[2]}") + + if "0x" in argv[2]: + start, end = parse_string_range(argv[2]) + + loc = lookup_address(start) + if loc.valid: + self.print_section(loc.section) + + for loc in self.search_pattern_by_address(pattern, start, end): + self.print_loc(loc) + else: + section_name = argv[2] + if section_name == "binary": + section_name = get_filepath() + + self.search_pattern(pattern, section_name) + else: + info(f"Searching '{Color.yellowify(pattern)}' in memory") + self.search_pattern(pattern, "") + return + + +@register_command +class FlagsCommand(GenericCommand): + """Edit flags in a human friendly way.""" + + _cmdline_ = "edit-flags" + _syntax_ = f"{_cmdline_} [(+|-|~)FLAGNAME ...]" + _aliases_ = ["flags",] + _example_ = (f"\n{_cmdline_}" + f"\n{_cmdline_} +zero # sets ZERO flag") + + def do_invoke(self, argv: List[str]) -> None: + for flag in argv: + if len(flag) < 2: + continue + + action = flag[0] + name = flag[1:].lower() + + if action not in ("+", "-", "~"): + err(f"Invalid action for flag '{flag}'") + continue + + if name not in gef.arch.flags_table.values(): + err(f"Invalid flag name '{flag[1:]}'") + continue + + for off in gef.arch.flags_table: + if gef.arch.flags_table[off] == name: + old_flag = gef.arch.register(gef.arch.flag_register) + if action == "+": + new_flags = old_flag | (1 << off) + elif action == "-": + new_flags = old_flag & ~(1 << off) + else: + new_flags = old_flag ^ (1 << off) + + gdb.execute(f"set ({gef.arch.flag_register}) = {new_flags:#x}") + + gef_print(gef.arch.flag_register_to_human()) + return + + +@register_command +class ChangePermissionCommand(GenericCommand): + """Change a page permission. By default, it will change it to 7 (RWX).""" + + _cmdline_ = "set-permission" + _syntax_ = (f"{_cmdline_} address [permission]\n" + "\taddress\t\tan address within the memory page for which the permissions should be changed\n" + "\tpermission\ta 3-bit bitmask with read=1, write=2 and execute=4 as integer") + _aliases_ = ["mprotect"] + _example_ = f"{_cmdline_} $sp 7" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + def pre_load(self) -> None: + try: + __import__("keystone") + except ImportError: + msg = "Missing `keystone-engine` package, install with: `pip install keystone-engine`." + raise ImportWarning(msg) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) not in (1, 2): + err("Incorrect syntax") + self.usage() + return + + if len(argv) == 2: + perm = Permission(int(argv[1])) + else: + perm = Permission.ALL + + loc = safe_parse_and_eval(argv[0]) + if loc is None: + err("Invalid address") + return + + loc = int(loc) + sect = process_lookup_address(loc) + if sect is None: + err("Unmapped address") + return + + size = sect.page_end - sect.page_start + original_pc = gef.arch.pc + + info(f"Generating sys_mprotect({sect.page_start:#x}, {size:#x}, " + f"'{perm!s}') stub for arch {get_arch()}") + stub = self.get_stub_by_arch(sect.page_start, size, perm) + if stub is None: + err("Failed to generate mprotect opcodes") + return + + info("Saving original code") + original_code = gef.memory.read(original_pc, len(stub)) + + bp_loc = f"*{original_pc + len(stub):#x}" + info(f"Setting a restore breakpoint at {bp_loc}") + ChangePermissionBreakpoint(bp_loc, original_code, original_pc) + + info(f"Overwriting current memory at {loc:#x} ({len(stub)} bytes)") + gef.memory.write(original_pc, stub, len(stub)) + + info("Resuming execution") + gdb.execute("continue") + return + + def get_stub_by_arch(self, addr: int, size: int, perm: Permission) -> Union[str, bytearray, None]: + code = gef.arch.mprotect_asm(addr, size, perm) + arch, mode = get_keystone_arch() + raw_insns = keystone_assemble(code, arch, mode, raw=True) + return raw_insns + + +@register_command +class UnicornEmulateCommand(GenericCommand): + """Use Unicorn-Engine to emulate the behavior of the binary, without affecting the GDB runtime. + By default the command will emulate only the next instruction, but location and number of + instruction can be changed via arguments to the command line. By default, it will emulate + the next instruction from current PC.""" + + _cmdline_ = "unicorn-emulate" + _syntax_ = (f"{_cmdline_} [--start LOCATION] [--until LOCATION] [--skip-emulation] [--output-file PATH] [NB_INSTRUCTION]" + "\n\t--start LOCATION specifies the start address of the emulated run (default $pc)." + "\t--until LOCATION specifies the end address of the emulated run." + "\t--skip-emulation\t do not execute the script once generated." + "\t--output-file /PATH/TO/SCRIPT.py writes the persistent Unicorn script into this file." + "\tNB_INSTRUCTION indicates the number of instructions to execute" + "\nAdditional options can be setup via `gef config unicorn-emulate`") + _aliases_ = ["emulate",] + _example_ = f"{_cmdline_} --start $pc 10 --output-file /tmp/my-gef-emulation.py" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["verbose"] = (False, "Set unicorn-engine in verbose mode") + self["show_disassembly"] = (False, "Show every instruction executed") + return + + def pre_load(self) -> None: + try: + __import__("unicorn") + except ImportError: + msg = "Missing `unicorn` package for Python. Install with `pip install unicorn`." + raise ImportWarning(msg) + + try: + __import__("capstone") + except ImportError: + msg = "Missing `capstone` package for Python. Install with `pip install capstone`." + raise ImportWarning(msg) + return + + @only_if_gdb_running + @parse_arguments({"nb": 1}, {"--start": "", "--until": "", "--skip-emulation": True, "--output-file": ""}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + start_address = parse_address(str(args.start or gef.arch.pc)) + end_address = parse_address(str(args.until or self.get_unicorn_end_addr(start_address, args.nb))) + self.run_unicorn(start_address, end_address, skip_emulation=args.skip_emulation, to_file=args.output_file) + return + + def get_unicorn_end_addr(self, start_addr: int, nb: int) -> int: + dis = list(gef_disassemble(start_addr, nb + 1)) + last_insn = dis[-1] + return last_insn.address + + def run_unicorn(self, start_insn_addr: int, end_insn_addr: int, **kwargs: Any) -> None: + verbose = self["verbose"] or False + skip_emulation = kwargs.get("skip_emulation", False) + arch, mode = get_unicorn_arch(to_string=True) + unicorn_registers = get_unicorn_registers(to_string=True) + cs_arch, cs_mode = get_capstone_arch(to_string=True) + fname = gef.session.file.name + to_file = kwargs.get("to_file", None) + emulate_segmentation_block = "" + context_segmentation_block = "" + + if to_file: + tmp_filename = to_file + to_file = open(to_file, "w") + tmp_fd = to_file.fileno() + else: + tmp_fd, tmp_filename = tempfile.mkstemp(suffix=".py", prefix="gef-uc-") + + if is_x86(): + # need to handle segmentation (and pagination) via MSR + emulate_segmentation_block = """ +# from https://github.com/unicorn-engine/unicorn/blob/master/tests/regress/x86_64_msr.py +SCRATCH_ADDR = 0xf000 +SEGMENT_FS_ADDR = 0x5000 +SEGMENT_GS_ADDR = 0x6000 +FSMSR = 0xC0000100 +GSMSR = 0xC0000101 + +def set_msr(uc, msr, value, scratch=SCRATCH_ADDR): + buf = b"\\x0f\\x30" # x86: wrmsr + uc.mem_map(scratch, 0x1000) + uc.mem_write(scratch, buf) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RAX, value & 0xFFFFFFFF) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, (value >> 32) & 0xFFFFFFFF) + uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, msr & 0xFFFFFFFF) + uc.emu_start(scratch, scratch+len(buf), count=1) + uc.mem_unmap(scratch, 0x1000) + return + +def set_gs(uc, addr): return set_msr(uc, GSMSR, addr) +def set_fs(uc, addr): return set_msr(uc, FSMSR, addr) + +""" + + context_segmentation_block = """ + emu.mem_map(SEGMENT_FS_ADDR-0x1000, 0x3000) + set_fs(emu, SEGMENT_FS_ADDR) + set_gs(emu, SEGMENT_GS_ADDR) +""" + + content = """#!{pythonbin} -i +# +# Emulation script for "{fname}" from {start:#x} to {end:#x} +# +# Powered by gef, unicorn-engine, and capstone-engine +# +# @_hugsy_ +# +import collections +import capstone, unicorn + +registers = collections.OrderedDict(sorted({{{regs}}}.items(), key=lambda t: t[0])) +uc = None +verbose = {verbose} +syscall_register = "{syscall_reg}" + +def disassemble(code, addr): + cs = capstone.Cs({cs_arch}, {cs_mode}) + for i in cs.disasm(code, addr): + return i + +def hook_code(emu, address, size, user_data): + code = emu.mem_read(address, size) + insn = disassemble(code, address) + print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) + return + +def code_hook(emu, address, size, user_data): + code = emu.mem_read(address, size) + insn = disassemble(code, address) + print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str)) + return + +def intr_hook(emu, intno, data): + print(" \\-> interrupt={{:d}}".format(intno)) + return + +def syscall_hook(emu, user_data): + sysno = emu.reg_read(registers[syscall_register]) + print(" \\-> syscall={{:d}}".format(sysno)) + return + +def print_regs(emu, regs): + for i, r in enumerate(regs): + print("{{:7s}} = {{:#0{ptrsize}x}} ".format(r, emu.reg_read(regs[r])), end="") + if (i % 4 == 3) or (i == len(regs)-1): print("") + return + +{emu_block} + +def reset(): + emu = unicorn.Uc({arch}, {mode}) + +{context_block} +""".format(pythonbin=PYTHONBIN, fname=fname, start=start_insn_addr, end=end_insn_addr, + regs=",".join([f"'{k.strip()}': {unicorn_registers[k]}" for k in unicorn_registers]), + verbose="True" if verbose else "False", + syscall_reg=gef.arch.syscall_register, + cs_arch=cs_arch, cs_mode=cs_mode, + ptrsize=gef.arch.ptrsize * 2 + 2, # two hex chars per byte plus "0x" prefix + emu_block=emulate_segmentation_block if is_x86() else "", + arch=arch, mode=mode, + context_block=context_segmentation_block if is_x86() else "") + + if verbose: + info("Duplicating registers") + + for r in gef.arch.all_registers: + gregval = gef.arch.register(r) + content += f" emu.reg_write({unicorn_registers[r]}, {gregval:#x})\n" + + vmmap = gef.memory.maps + if not vmmap: + warn("An error occurred when reading memory map.") + return + + if verbose: + info("Duplicating memory map") + + for sect in vmmap: + if sect.path == "[vvar]": + # this section is for GDB only, skip it + continue + + page_start = sect.page_start + page_end = sect.page_end + size = sect.size + perm = sect.permission + + content += f" # Mapping {sect.path}: {page_start:#x}-{page_end:#x}\n" + content += f" emu.mem_map({page_start:#x}, {size:#x}, {perm.value:#o})\n" + + if perm & Permission.READ: + code = gef.memory.read(page_start, size) + loc = f"/tmp/gef-{fname}-{page_start:#x}.raw" + with open(loc, "wb") as f: + f.write(bytes(code)) + + content += f" emu.mem_write({page_start:#x}, open('{loc}', 'rb').read())\n" + content += "\n" + + content += " emu.hook_add(unicorn.UC_HOOK_CODE, code_hook)\n" + content += " emu.hook_add(unicorn.UC_HOOK_INTR, intr_hook)\n" + if is_x86_64(): + content += " emu.hook_add(unicorn.UC_HOOK_INSN, syscall_hook, None, 1, 0, unicorn.x86_const.UC_X86_INS_SYSCALL)\n" + content += " return emu\n" + + content += """ +def emulate(emu, start_addr, end_addr): + print("========================= Initial registers =========================") + print_regs(emu, registers) + + try: + print("========================= Starting emulation =========================") + emu.emu_start(start_addr, end_addr) + except Exception as e: + emu.emu_stop() + print("========================= Emulation failed =========================") + print("[!] Error: {{}}".format(e)) + + print("========================= Final registers =========================") + print_regs(emu, registers) + return + + +uc = reset() +emulate(uc, {start:#x}, {end:#x}) + +# unicorn-engine script generated by gef +""".format(start=start_insn_addr, end=end_insn_addr) + + os.write(tmp_fd, gef_pybytes(content)) + os.close(tmp_fd) + + if kwargs.get("to_file", None): + info(f"Unicorn script generated as '{tmp_filename}'") + os.chmod(tmp_filename, 0o700) + + if skip_emulation: + return + + ok(f"Starting emulation: {start_insn_addr:#x} {RIGHT_ARROW} {end_insn_addr:#x}") + + res = gef_execute_external([PYTHONBIN, tmp_filename], as_list=True) + gef_print("\n".join(res)) + + if not kwargs.get("to_file", None): + os.unlink(tmp_filename) + return + + +@register_command +class RemoteCommand(GenericCommand): + """gef wrapper for the `target remote` command. This command will automatically + download the target binary in the local temporary directory (defaut /tmp) and then + source it. Additionally, it will fetch all the /proc/PID/maps and loads all its + information.""" + + _cmdline_ = "gef-remote" + _syntax_ = f"{_cmdline_} [OPTIONS] TARGET" + _example_ = (f"\n{_cmdline_} --pid 6789 localhost:1234" + f"\n{_cmdline_} --qemu-mode localhost:4444 # when using qemu-user") + + def __init__(self) -> None: + super().__init__(prefix=False) + self.handler_connected = False + self["clean_on_exit"] = (False, "Clean the temporary data downloaded when the session exits.") + return + + @parse_arguments( + {"target": ""}, + {"--update-solib": True, + "--download-everything": True, + "--download-lib": "", + "--is-extended-remote": True, + "--pid": 0, + "--qemu-mode": True}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + if gef.session.remote is not None: + err("You already are in remote session. Close it first before opening a new one...") + return + + # argument check + args = kwargs["arguments"] + if not args.target or ":" not in args.target: + err("A target (HOST:PORT) must always be provided.") + return + + if args.is_extended_remote and not args.pid: + err("A PID (--pid) is required for extended remote debugging") + return + + target = args.target + self.download_all_libs = args.download_everything + + if args.qemu_mode: + # compat layer for qemu-user + self.prepare_qemu_stub(target) + return + + # lazily install handler on first use + if not self.handler_connected: + gef_on_new_hook(self.new_objfile_handler) + self.handler_connected = True + + if not self.connect_target(target, args.is_extended_remote): + return + + pid = args.pid if args.is_extended_remote and args.pid else gef.session.pid + if args.is_extended_remote: + ok(f"Attaching to {pid:d}") + hide_context() + gdb.execute(f"attach {pid:d}") + unhide_context() + + self.setup_remote_environment(pid, args.update_solib) + + if not is_remote_debug(): + err("Failed to establish remote target environment.") + return + + if self.download_all_libs: + vmmap = gef.memory.maps + success = 0 + for sect in vmmap: + if sect.path.startswith("/"): + _file = download_file(sect.path) + if _file is None: + err(f"Failed to download {sect.path}") + else: + success += 1 + + ok(f"Downloaded {success:d} files") + + elif args.download_lib: + _file = download_file(args.download_lib) + if _file is None: + err("Failed to download remote file") + return + + ok(f"Download success: {args.download_lib} {RIGHT_ARROW} {_file}") + + if args.update_solib: + self.refresh_shared_library_path() + + + # refresh the architecture setting + reset_architecture() + gef.session.remote = pid + return + + def new_objfile_handler(self, event: "gdb.Event") -> None: + """Hook that handles new_objfile events, will update remote environment accordingly.""" + if not is_remote_debug(): + return + + if self.download_all_libs and event.new_objfile.filename.startswith("target:"): + remote_lib = event.new_objfile.filename[len("target:"):] + local_lib = download_file(remote_lib, use_cache=True) + if local_lib: + ok(f"Download success: {remote_lib} {RIGHT_ARROW} {local_lib}") + return + + def setup_remote_environment(self, pid: int, update_solib: bool = False) -> None: + """Clone the remote environment locally in the temporary directory. + The command will duplicate the entries in the /proc/<pid> locally and then + source those information into the current gdb context to allow gef to use + all the extra commands as it was local debugging.""" + gdb.execute("reset-cache") + + infos = {} + for i in ("maps", "environ", "cmdline",): + infos[i] = self.load_from_remote_proc(pid, i) + if infos[i] is None: + err(f"Failed to load memory map of '{i}'") + return + + exepath = get_path_from_info_proc() + infos["exe"] = download_file(f"/proc/{pid:d}/exe", use_cache=False, local_name=exepath) + if not os.access(infos["exe"], os.R_OK): + err("Source binary is not readable") + return + + directory = os.path.sep.join([gef.config["gef.tempdir"], str(gef.session.pid)]) + # gdb.execute(f"file {infos['exe']}") + self["root"] = (directory, "Path to store the remote data") + ok(f"Remote information loaded to temporary path '{directory}'") + return + + def connect_target(self, target: str, is_extended_remote: bool) -> bool: + """Connect to remote target and get symbols. To prevent `gef` from requesting information + not fetched just yet, we disable the context disable when connection was successful.""" + hide_context() + try: + cmd = f"target {'extended-remote' if is_extended_remote else 'remote'} {target}" + gdb.execute(cmd) + ok(f"Connected to '{target}'") + ret = True + except Exception as e: + err(f"Failed to connect to {target}: {e}") + ret = False + unhide_context() + return ret + + def load_from_remote_proc(self, pid: int, info: str) -> Optional[str]: + """Download one item from /proc/pid.""" + remote_name = f"/proc/{pid:d}/{info}" + return download_file(remote_name, use_cache=False) + + def refresh_shared_library_path(self) -> None: + dirs = [r for r, d, f in os.walk(self["root"])] + path = ":".join(dirs) + gdb.execute(f"set solib-search-path {path}") + return + + def usage(self) -> None: + h = self._syntax_ + h += "\n\t TARGET (mandatory) specifies the host:port, serial port or tty to connect to.\n" + h += "\t-U will update gdb `solib-search-path` attribute to include the files downloaded from server (default: False).\n" + h += "\t-A will download *ALL* the remote shared libraries and store them in the new environment. " \ + "This command can take a few minutes to complete (default: False).\n" + h += "\t-D LIB will download the remote library called LIB.\n" + h += "\t-E Use 'extended-remote' to connect to the target.\n" + h += "\t-p PID (mandatory if -E is used) specifies PID of the debugged process on gdbserver's end.\n" + h += "\t-q Uses this option when connecting to a Qemu GDBserver.\n" + info(h) + return + + def prepare_qemu_stub(self, target: str) -> None: + global gef + + reset_all_caches() + arch = get_arch() + gef.binary = Elf(minimalist=True) + if arch.startswith("arm"): + gef.binary.e_machine = Elf.Abi.ARM + gef.arch = ARM() + elif arch.startswith("aarch64"): + gef.binary.e_machine = Elf.Abi.AARCH64 + gef.arch = AARCH64() + elif arch.startswith("i386:intel"): + gef.binary.e_machine = Elf.Abi.X86_32 + gef.arch = X86() + elif arch.startswith("i386:x86-64"): + gef.binary.e_machine = Elf.Abi.X86_64 + gef.binary.e_class = Elf.Class.ELF_64_BITS + gef.arch = X86_64() + elif arch.startswith("mips"): + gef.binary.e_machine = Elf.Abi.MIPS + gef.arch = MIPS() + elif arch.startswith("powerpc"): + gef.binary.e_machine = Elf.Abi.POWERPC + gef.arch = PowerPC() + elif arch.startswith("sparc"): + gef.binary.e_machine = Elf.Abi.SPARC + gef.arch = SPARC() + else: + raise RuntimeError(f"unsupported architecture: {arch}") + + ok(f"Setting Qemu-user stub for '{gef.arch.arch}' (memory mapping may be wrong)") + hide_context() + gdb.execute(f"target remote {target}") + unhide_context() + + if gef.session.pid == 1 and "ENABLE=1" in gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False): + gef.session.qemu_mode = True + reset_all_caches() + info("Note: By using Qemu mode, GEF will display the memory mapping of the Qemu process where the emulated binary resides") + gef.memory.maps + gdb.execute("context") + return + + +@register_command +class NopCommand(GenericCommand): + """Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture + aware.""" + + _cmdline_ = "nop" + _syntax_ = ("{_cmdline_} [LOCATION] [--nb NUM_BYTES]" + "\n\tLOCATION\taddress/symbol to patch" + "\t--nb NUM_BYTES\tInstead of writing one instruction, patch the specified number of bytes") + _example_ = f"{_cmdline_} $pc" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + def get_insn_size(self, addr: int) -> int: + cur_insn = gef_current_instruction(addr) + next_insn = gef_instruction_n(addr, 2) + return next_insn.address - cur_insn.address + + @parse_arguments({"address": "$pc"}, {"--nb": 0, }) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + address = parse_address(args.address) if args.address else gef.arch.pc + number_of_bytes = args.nb or 1 + self.nop_bytes(address, number_of_bytes) + return + + @only_if_gdb_running + def nop_bytes(self, loc: int, num_bytes: int) -> None: + size = self.get_insn_size(loc) if num_bytes == 0 else num_bytes + nops = gef.arch.nop_insn + + if len(nops) > size: + err(f"Cannot patch instruction at {loc:#x} " + f"(nop_size is:{len(nops)}, insn_size is:{size})") + return + + while len(nops) < size: + nops += gef.arch.nop_insn + + if len(nops) != size: + err(f"Cannot patch instruction at {loc:#x} " + "(nop instruction does not evenly fit in requested size)") + return + + ok(f"Patching {size:d} bytes from {format_address(loc)}") + gef.memory.write(loc, nops, size) + + return + + +@register_command +class StubCommand(GenericCommand): + """Stub out the specified function. This function is useful when needing to skip one + function to be called and disrupt your runtime flow (ex. fork).""" + + _cmdline_ = "stub" + _syntax_ = (f"{_cmdline_} [--retval RETVAL] [address]" + "\taddress\taddress/symbol to stub out" + "\t--retval RETVAL\tSet the return value") + _example_ = f"{_cmdline_} --retval 0 fork" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + @parse_arguments({"address": ""}, {("-r", "--retval"): 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + loc = args.address if args.address else f"*{gef.arch.pc:#x}" + StubBreakpoint(loc, args.retval) + return + + +@register_command +class CapstoneDisassembleCommand(GenericCommand): + """Use capstone disassembly framework to disassemble code.""" + + _cmdline_ = "capstone-disassemble" + _syntax_ = f"{_cmdline_} [-h] [--show-opcodes] [--length LENGTH] [LOCATION]" + _aliases_ = ["cs-dis"] + _example_ = f"{_cmdline_} --length 50 $pc" + + def pre_load(self) -> None: + try: + __import__("capstone") + except ImportError: + msg = "Missing `capstone` package for Python. Install with `pip install capstone`." + raise ImportWarning(msg) + return + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + @parse_arguments({("location"): "$pc"}, {("--show-opcodes", "-s"): True, "--length": 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + show_opcodes = args.show_opcodes + length = args.length or gef.config["context.nb_lines_code"] + location = parse_address(args.location) + if not location: + info(f"Can't find address for {args.location}") + return + + insns = [] + opcodes_len = 0 + for insn in capstone_disassemble(location, length, skip=length * self.repeat_count, **kwargs): + insns.append(insn) + opcodes_len = max(opcodes_len, len(insn.opcodes)) + + for insn in insns: + insn_fmt = f"{{:{opcodes_len}o}}" if show_opcodes else "{}" + text_insn = insn_fmt.format(insn) + msg = "" + + if insn.address == gef.arch.pc: + msg = Color.colorify(f"{RIGHT_ARROW} {text_insn}", "bold red") + reason = self.capstone_analyze_pc(insn, length)[0] + if reason: + gef_print(msg) + gef_print(reason) + break + else: + msg = f" {text_insn}" + + gef_print(msg) + return + + def capstone_analyze_pc(self, insn: Instruction, nb_insn: int) -> Tuple[bool, str]: + if gef.arch.is_conditional_branch(insn): + is_taken, reason = gef.arch.is_branch_taken(insn) + if is_taken: + reason = f"[Reason: {reason}]" if reason else "" + msg = Color.colorify(f"\tTAKEN {reason}", "bold green") + else: + reason = f"[Reason: !({reason})]" if reason else "" + msg = Color.colorify(f"\tNOT taken {reason}", "bold red") + return (is_taken, msg) + + if gef.arch.is_call(insn): + target_address = int(insn.operands[-1].split()[0], 16) + msg = [] + for i, new_insn in enumerate(capstone_disassemble(target_address, nb_insn)): + msg.append(f" {DOWN_ARROW if i == 0 else ' '} {new_insn!s}") + return (True, "\n".join(msg)) + + return (False, "") + + +@register_command +class GlibcHeapCommand(GenericCommand): + """Base command to get information about the Glibc heap structure.""" + + _cmdline_ = "heap" + _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + @only_if_gdb_running + def do_invoke(self, _: List[str]) -> None: + self.usage() + return + + +@register_command +class GlibcHeapSetArenaCommand(GenericCommand): + """Display information on a heap chunk.""" + + _cmdline_ = "heap set-arena" + _syntax_ = f"{_cmdline_} [address|&symbol]" + _example_ = f"{_cmdline_} 0x001337001337" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + global gef + + if not argv: + ok(f"Current arena set to: '{gef.heap.selected_arena}'") + return + + if is_hex(argv[0]): + new_arena_address = int(argv[0], 16) + else: + new_arena_symbol = safe_parse_and_eval(argv[0]) + if not new_arena_symbol: + err("Invalid symbol for arena") + return + new_arena_address = to_unsigned_long(new_arena_symbol) + + new_arena = GlibcArena( f"*{new_arena_address:#x}") + if new_arena not in gef.heap.arenas: + err("Invalid arena") + return + + gef.heap.selected_arena = new_arena + return + + +@register_command +class GlibcHeapArenaCommand(GenericCommand): + """Display information on a heap chunk.""" + + _cmdline_ = "heap arenas" + _syntax_ = _cmdline_ + + @only_if_gdb_running + def do_invoke(self, _: List[str]) -> None: + for arena in gef.heap.arenas: + gef_print(str(arena)) + return + + +@register_command +class GlibcHeapChunkCommand(GenericCommand): + """Display information on a heap chunk. + See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" + + _cmdline_ = "heap chunk" + _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @parse_arguments({"address": ""}, {"--allow-unaligned": True, "--number": 1}) + @only_if_gdb_running + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + if not args.address: + err("Missing chunk address") + self.usage() + return + + addr = parse_address(args.address) + current_chunk = GlibcChunk(addr, allow_unaligned=args.allow_unaligned) + + if args.number > 1: + for _ in range(args.number): + if current_chunk.size == 0: + break + + gef_print(str(current_chunk)) + next_chunk_addr = current_chunk.get_next_chunk_addr() + if not Address(value=next_chunk_addr).valid: + break + + next_chunk = current_chunk.get_next_chunk() + if next_chunk is None: + break + + current_chunk = next_chunk + else: + gef_print(current_chunk.psprint()) + return + + +@register_command +class GlibcHeapChunksCommand(GenericCommand): + """Display all heap chunks for the current arena. As an optional argument + the base address of a different arena can be passed""" + + _cmdline_ = "heap chunks" + _syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [arena_address]" + _example_ = (f"\n{_cmdline_}" + f"\n{_cmdline_} 0x555555775000") + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["peek_nb_byte"] = (16, "Hexdump N first byte(s) inside the chunk data (0 to disable)") + return + + @parse_arguments({"arena_address": ""}, {("--all", "-a"): True, "--allow-unaligned": True}) + @only_if_gdb_running + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + arenas = gef.heap.arenas + for arena in arenas: + self.dump_chunks_arena(arena, print_arena=args.all, allow_unaligned=args.allow_unaligned) + if not args.all: + break + + def dump_chunks_arena(self, arena: GlibcArena, print_arena: bool = False, allow_unaligned: bool = False) -> None: + top_chunk_addr = arena.top + heap_addr = arena.heap_addr(allow_unaligned=allow_unaligned) + if heap_addr is None: + err("Could not find heap for arena") + return + if print_arena: + gef_print(str(arena)) + if arena.is_main_arena(): + self.dump_chunks_heap(heap_addr, top=top_chunk_addr, allow_unaligned=allow_unaligned) + else: + heap_info_structs = arena.get_heap_info_list() + first_heap_info = heap_info_structs.pop(0) + heap_info_t_size = int(arena) - first_heap_info.addr + until = first_heap_info.addr + first_heap_info.size + self.dump_chunks_heap(heap_addr, until=until, top=top_chunk_addr, allow_unaligned=allow_unaligned) + for heap_info in heap_info_structs: + start = heap_info.addr + heap_info_t_size + until = heap_info.addr + heap_info.size + self.dump_chunks_heap(start, until=until, top=top_chunk_addr, allow_unaligned=allow_unaligned) + return + + def dump_chunks_heap(self, start: int, until: Optional[int] = None, top: Optional[int] = None, allow_unaligned: bool = False) -> None: + nb = self["peek_nb_byte"] + chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=allow_unaligned) + for chunk in chunk_iterator: + line = str(chunk) + if nb: + line += f"\n [{hexdump(gef.memory.read(chunk.data_address, nb), nb, base=chunk.data_address)}]" + gef_print(line) + + next_chunk_addr = chunk.get_next_chunk_addr() + if until and next_chunk_addr >= until: + break + + if chunk.base_address == top: + gef_print(f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}") + break + return + + +@register_command +class GlibcHeapBinsCommand(GenericCommand): + """Display information on the bins on an arena (default: main_arena). + See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" + + _bin_types_ = ["tcache", "fast", "unsorted", "small", "large"] + _cmdline_ = "heap bins" + _syntax_ = f"{_cmdline_} [{'|'.join(_bin_types_)}]" + + def __init__(self) -> None: + super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if not argv: + for bin_t in GlibcHeapBinsCommand._bin_types_: + gdb.execute(f"heap bins {bin_t}") + return + + bin_t = argv[0] + if bin_t not in GlibcHeapBinsCommand._bin_types_: + self.usage() + return + + gdb.execute(f"heap bins {bin_t}") + return + + @staticmethod + def pprint_bin(arena_addr: str, index: int, _type: str = "") -> int: + arena = GlibcArena(arena_addr) + fw, bk = arena.bin(index) + + if bk == 0x00 and fw == 0x00: + warn("Invalid backward and forward bin pointers(fw==bk==NULL)") + return -1 + + nb_chunk = 0 + head = GlibcChunk(bk, from_base=True).fwd + if fw == head: + return nb_chunk + + ok(f"{_type}bins[{index:d}]: fw={fw:#x}, bk={bk:#x}") + + m = [] + while fw != head: + chunk = GlibcChunk(fw, from_base=True) + m.append(f"{RIGHT_ARROW} {chunk!s}") + fw = chunk.fwd + nb_chunk += 1 + + if m: + gef_print(" ".join(m)) + return nb_chunk + + +@register_command +class GlibcHeapTcachebinsCommand(GenericCommand): + """Display information on the Tcachebins on an arena (default: main_arena). + See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc.""" + + _cmdline_ = "heap bins tcache" + _syntax_ = f"{_cmdline_} [all] [thread_ids...]" + + TCACHE_MAX_BINS = 0x40 + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + # Determine if we are using libc with tcache built in (2.26+) + if get_libc_version() < (2, 26): + info("No Tcache in this version of libc") + return + + current_thread = gdb.selected_thread() + if current_thread is None: + err("Couldn't find current thread") + return + + # As a nicety, we want to display threads in ascending order by gdb number + threads = sorted(gdb.selected_inferior().threads(), key=lambda t: t.num) + if argv: + if "all" in argv: + tids = [t.num for t in threads] + else: + tids = self.check_thread_ids(argv) + else: + tids = [current_thread.num] + + for thread in threads: + if thread.num not in tids: + continue + + thread.switch() + + tcache_addr = self.find_tcache() + if tcache_addr == 0: + info(f"Uninitialized tcache for thread {thread.num:d}") + continue + + gef_print(titlify(f"Tcachebins for thread {thread.num:d}")) + tcache_empty = True + for i in range(self.TCACHE_MAX_BINS): + chunk, count = self.tcachebin(tcache_addr, i) + chunks = set() + msg = [] + + # Only print the entry if there are valid chunks. Don't trust count + while True: + if chunk is None: + break + + try: + msg.append(f"{LEFT_ARROW} {chunk!s} ") + if chunk.data_address in chunks: + msg.append(f"{RIGHT_ARROW} [loop detected]") + break + + chunks.add(chunk.data_address) + + next_chunk = chunk.get_fwd_ptr(True) + if next_chunk == 0: + break + + chunk = GlibcChunk(next_chunk) + except gdb.MemoryError: + msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]") + break + + if msg: + tcache_empty = False + gef_print(f"Tcachebins[idx={i:d}, size={(i+2)*(gef.arch.ptrsize)*2:#x}] count={count:d} ", end="") + gef_print("".join(msg)) + + if tcache_empty: + gef_print("All tcachebins are empty") + + current_thread.switch() + return + + @staticmethod + def find_tcache() -> int: + """Return the location of the current thread's tcache.""" + try: + # For multithreaded binaries, the tcache symbol (in thread local + # storage) will give us the correct address. + tcache_addr = parse_address("(void *) tcache") + except gdb.error: + # In binaries not linked with pthread (and therefore there is only + # one thread), we can't use the tcache symbol, but we can guess the + # correct address because the tcache is consistently the first + # allocation in the main arena. + heap_base = gef.heap.base_address + if heap_base is None: + err("No heap section") + return 0x0 + tcache_addr = heap_base + 0x10 + return tcache_addr + + @staticmethod + def check_thread_ids(tids: List[int]) -> List[int]: + """Check the validity, dedup, and return all valid tids.""" + existing_tids = [t.num for t in gdb.selected_inferior().threads()] + valid_tids = set() + for tid in tids: + try: + tid = int(tid) + except ValueError: + err(f"Invalid thread id {tid:d}") + continue + if tid in existing_tids: + valid_tids.add(tid) + else: + err(f"Unknown thread {tid}") + + return list(valid_tids) + + @staticmethod + def tcachebin(tcache_base: int, i: int) -> Tuple[Optional[GlibcChunk], int]: + """Return the head chunk in tcache[i] and the number of chunks in the bin.""" + if i >= GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS: + err("Incorrect index value, index value must be between 0 and {}-1, given {}".format(GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS, i)) + return None, 0 + + tcache_chunk = GlibcChunk(tcache_base) + + # Glibc changed the size of the tcache in version 2.30; this fix has + # been backported inconsistently between distributions. We detect the + # difference by checking the size of the allocated chunk for the + # tcache. + # Minimum usable size of allocated tcache chunk = ? + # For new tcache: + # TCACHE_MAX_BINS * _2_ + TCACHE_MAX_BINS * ptrsize + # For old tcache: + # TCACHE_MAX_BINS * _1_ + TCACHE_MAX_BINS * ptrsize + new_tcache_min_size = ( + GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS * 2 + + GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS * gef.arch.ptrsize) + + if tcache_chunk.usable_size < new_tcache_min_size: + tcache_count_size = 1 + count = ord(gef.memory.read(tcache_base + tcache_count_size*i, 1)) + else: + tcache_count_size = 2 + count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2)) + + chunk = dereference(tcache_base + tcache_count_size*GlibcHeapTcachebinsCommand.TCACHE_MAX_BINS + i*gef.arch.ptrsize) + chunk = GlibcChunk(int(chunk)) if chunk else None + return chunk, count + + +@register_command +class GlibcHeapFastbinsYCommand(GenericCommand): + """Display information on the fastbinsY on an arena (default: main_arena). + See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123.""" + + _cmdline_ = "heap bins fast" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @parse_arguments({"arena_address": ""}, {}) + @only_if_gdb_running + def do_invoke(self, *_: Any, **kwargs: Any) -> None: + def fastbin_index(sz: int) -> int: + return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2 + + args = kwargs["arguments"] + if not gef.heap.main_arena: + err("Heap not initialized") + return + + SIZE_SZ = gef.arch.ptrsize + MAX_FAST_SIZE = 80 * SIZE_SZ // 4 + NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1 + + arena = GlibcArena(f"*{args.arena_address}") if args.arena_address else gef.heap.selected_arena + if arena is None: + err("Invalid Glibc arena") + return + + gef_print(titlify(f"Fastbins for arena at {arena.addr:#x}")) + for i in range(NFASTBINS): + gef_print(f"Fastbins[idx={i:d}, size={(i+2)*SIZE_SZ*2:#x}] ", end="") + chunk = arena.fastbin(i) + chunks = set() + + while True: + if chunk is None: + gef_print("0x00", end="") + break + + try: + gef_print(f"{LEFT_ARROW} {chunk!s} ", end="") + if chunk.data_address in chunks: + gef_print(f"{RIGHT_ARROW} [loop detected]", end="") + break + + if fastbin_index(chunk.get_chunk_size()) != i: + gef_print("[incorrect fastbin_index] ", end="") + + chunks.add(chunk.data_address) + + next_chunk = chunk.get_fwd_ptr(True) + if next_chunk == 0: + break + + chunk = GlibcChunk(next_chunk, from_base=True) + except gdb.MemoryError: + gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="") + break + gef_print() + return + + +@register_command +class GlibcHeapUnsortedBinsCommand(GenericCommand): + """Display information on the Unsorted Bins of an arena (default: main_arena). + See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689.""" + + _cmdline_ = "heap bins unsorted" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @parse_arguments({"arena_address": ""}, {}) + @only_if_gdb_running + def do_invoke(self, *_: Any, **kwargs: Any) -> None: + args = kwargs["arguments"] + if gef.heap.main_arena is None: + err("Heap not initialized") + return + arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" + gef_print(titlify(f"Unsorted Bin for arena at {arena_addr}")) + nb_chunk = GlibcHeapBinsCommand.pprint_bin(f"*{arena_addr}", 0, "unsorted_") + if nb_chunk >= 0: + info(f"Found {nb_chunk:d} chunks in unsorted bin.") + return + + +@register_command +class GlibcHeapSmallBinsCommand(GenericCommand): + """Convenience command for viewing small bins.""" + + _cmdline_ = "heap bins small" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @parse_arguments({"arena_address": ""}, {}) + @only_if_gdb_running + def do_invoke(self, *_: Any, **kwargs: Any) -> None: + args = kwargs["arguments"] + if not gef.heap.main_arena: + err("Heap not initialized") + return + + arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" + gef_print(titlify(f"Small Bins for arena at {arena_addr}")) + bins = {} + for i in range(1, 63): + nb_chunk = GlibcHeapBinsCommand.pprint_bin(f"*{arena_addr}", i, "small_") + if nb_chunk < 0: + break + if nb_chunk > 0: + bins[i] = nb_chunk + info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} small non-empty bins.") + return + + +@register_command +class GlibcHeapLargeBinsCommand(GenericCommand): + """Convenience command for viewing large bins.""" + + _cmdline_ = "heap bins large" + _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @parse_arguments({"arena_address": ""}, {}) + @only_if_gdb_running + def do_invoke(self, *_: Any, **kwargs: Any) -> None: + args = kwargs["arguments"] + if gef.heap.main_arena is None: + err("Heap not initialized") + return + + arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}" + gef_print(titlify(f"Large Bins for arena at {arena_addr}")) + bins = {} + for i in range(63, 126): + nb_chunk = GlibcHeapBinsCommand.pprint_bin(f"*{arena_addr}", i, "large_") + if nb_chunk < 0: + break + if nb_chunk > 0: + bins[i] = nb_chunk + info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} large non-empty bins.") + return + + +@register_command +class SolveKernelSymbolCommand(GenericCommand): + """Solve kernel symbols from kallsyms table.""" + + _cmdline_ = "ksymaddr" + _syntax_ = f"{_cmdline_} SymbolToSearch" + _example_ = f"{_cmdline_} prepare_creds" + + @parse_arguments({"symbol": ""}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + def hex_to_int(num): + try: + return int(num, 16) + except ValueError: + return 0 + args = kwargs["arguments"] + if not args.symbol: + self.usage() + return + sym = args.symbol + with open("/proc/kallsyms", "r") as f: + syms = [line.strip().split(" ", 2) for line in f] + matches = [(hex_to_int(addr), sym_t, " ".join(name.split())) for addr, sym_t, name in syms if sym in name] + for addr, sym_t, name in matches: + if sym == name.split()[0]: + ok(f"Found matching symbol for '{name}' at {addr:#x} (type={sym_t})") + else: + warn(f"Found partial match for '{sym}' at {addr:#x} (type={sym_t}): {name}") + if not matches: + err(f"No match for '{sym}'") + elif matches[0][0] == 0: + err("Check that you have the correct permissions to view kernel symbol addresses") + return + + +@register_command +class DetailRegistersCommand(GenericCommand): + """Display full details on one, many or all registers value from current architecture.""" + + _cmdline_ = "registers" + _syntax_ = f"{_cmdline_} [[Register1][Register2] ... [RegisterN]]" + _example_ = (f"\n{_cmdline_}" + f"\n{_cmdline_} $eax $eip $esp") + + @only_if_gdb_running + @parse_arguments({"registers": [""]}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + unchanged_color = gef.config["theme.registers_register_name"] + changed_color = gef.config["theme.registers_value_changed"] + string_color = gef.config["theme.dereference_string"] + regs = gef.arch.all_registers + + args = kwargs["arguments"] + if args.registers and args.registers[0]: + required_regs = set(args.registers) + valid_regs = [reg for reg in gef.arch.all_registers if reg in required_regs] + if valid_regs: + regs = valid_regs + invalid_regs = [reg for reg in required_regs if reg not in valid_regs] + if invalid_regs: + err(f"invalid registers for architecture: {', '.join(invalid_regs)}") + + memsize = gef.arch.ptrsize + endian = str(gef.arch.endianness) + charset = string.printable + widest = max(map(len, gef.arch.all_registers)) + special_line = "" + + for regname in regs: + reg = gdb.parse_and_eval(regname) + if reg.type.code == gdb.TYPE_CODE_VOID: + continue + + padreg = regname.ljust(widest, " ") + + if str(reg) == "<unavailable>": + gef_print(f"{Color.colorify(padreg, unchanged_color)}: " + f"{Color.colorify('no value', 'yellow underline')}") + continue + + value = align_address(int(reg)) + old_value = ContextCommand.old_registers.get(regname, 0) + if value == old_value: + color = unchanged_color + else: + color = changed_color + + # Special (e.g. segment) registers go on their own line + if regname in gef.arch.special_registers: + special_line += f"{Color.colorify(regname, color)}: " + special_line += f"{gef.arch.register(regname):#04x} " + continue + + line = f"{Color.colorify(padreg, color)}: " + + if regname == gef.arch.flag_register: + line += gef.arch.flag_register_to_human() + gef_print(line) + continue + + addr = lookup_address(align_address(int(value))) + if addr.valid: + line += str(addr) + else: + line += format_address_spaces(value) + addrs = dereference_from(value) + + if len(addrs) > 1: + sep = f" {RIGHT_ARROW} " + line += sep + line += sep.join(addrs[1:]) + + # check to see if reg value is ascii + try: + fmt = f"{endian}{'I' if memsize == 4 else 'Q'}" + last_addr = int(addrs[-1], 16) + val = gef_pystring(struct.pack(fmt, last_addr)) + if all([_ in charset for _ in val]): + line += f" (\"{Color.colorify(val, string_color)}\"?)" + except ValueError: + pass + + gef_print(line) + + if special_line: + gef_print(special_line) + return + + +@register_command +class ShellcodeCommand(GenericCommand): + """ShellcodeCommand uses @JonathanSalwan simple-yet-awesome shellcode API to + download shellcodes.""" + + _cmdline_ = "shellcode" + _syntax_ = f"{_cmdline_} (search|get)" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + def do_invoke(self, _: List[str]) -> None: + err("Missing sub-command (search|get)") + self.usage() + return + + +@register_command +class ShellcodeSearchCommand(GenericCommand): + """Search pattern in shell-storm's shellcode database.""" + + _cmdline_ = "shellcode search" + _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2" + _aliases_ = ["sc-search",] + + api_base = "http://shell-storm.org" + search_url = f"{api_base}/api/?s=" + + def do_invoke(self, argv: List[str]) -> None: + if not argv: + err("Missing pattern to search") + self.usage() + return + + self.search_shellcode(argv) + return + + def search_shellcode(self, search_options: List) -> None: + # API : http://shell-storm.org/shellcode/ + args = "*".join(search_options) + + res = http_get(self.search_url + args) + if res is None: + err("Could not query search page") + return + + ret = gef_pystring(res) + + # format: [author, OS/arch, cmd, id, link] + lines = ret.split("\\n") + refs = [line.split("::::") for line in lines] + + if refs: + info("Showing matching shellcodes") + info("\t".join(["Id", "Platform", "Description"])) + for ref in refs: + try: + _, arch, cmd, sid, _ = ref + gef_print("\t".join([sid, arch, cmd])) + except ValueError: + continue + + info("Use `shellcode get <id>` to fetch shellcode") + return + + +@register_command +class ShellcodeGetCommand(GenericCommand): + """Download shellcode from shell-storm's shellcode database.""" + + _cmdline_ = "shellcode get" + _syntax_ = f"{_cmdline_} SHELLCODE_ID" + _aliases_ = ["sc-get",] + + api_base = "http://shell-storm.org" + get_url = f"{api_base}/shellcode/files/shellcode-{{:d}}.php" + + def do_invoke(self, argv: List[str]) -> None: + if len(argv) != 1: + err("Missing ID to download") + self.usage() + return + + if not argv[0].isdigit(): + err("ID is not a number") + self.usage() + return + + self.get_shellcode(int(argv[0])) + return + + def get_shellcode(self, sid: int) -> None: + info(f"Downloading shellcode id={sid}") + res = http_get(self.get_url.format(sid)) + if res is None: + err(f"Failed to fetch shellcode #{sid}") + return + + ok("Downloaded, written to disk...") + tempdir = gef.config["gef.tempdir"] + fd, fname = tempfile.mkstemp(suffix=".txt", prefix="sc-", text=True, dir=tempdir) + shellcode = res.splitlines()[7:-11] + shellcode = b"\n".join(shellcode).replace(b""", b'"') + os.write(fd, shellcode) + os.close(fd) + ok(f"Shellcode written to '{fname}'") + return + + +@register_command +class RopperCommand(GenericCommand): + """Ropper (https://scoding.de/ropper/) plugin.""" + + _cmdline_ = "ropper" + _syntax_ = f"{_cmdline_} [ROPPER_OPTIONS]" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_NONE) + return + + def pre_load(self) -> None: + try: + __import__("ropper") + except ImportError: + msg = "Missing `ropper` package for Python, install with: `pip install ropper`." + raise ImportWarning(msg) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + ropper = sys.modules["ropper"] + if "--file" not in argv: + path = get_filepath() + sect = next(filter(lambda x: x.path == path, gef.memory.maps)) + argv.append("--file") + argv.append(path) + argv.append("-I") + argv.append(f"{sect.page_start:#x}") + + import readline + # ropper set up own autocompleter after which gdb/gef autocomplete don't work + old_completer_delims = readline.get_completer_delims() + old_completer = readline.get_completer() + try: + ropper.start(argv) + except RuntimeWarning: + return + readline.set_completer(old_completer) + readline.set_completer_delims(old_completer_delims) + return + + +@register_command +class AssembleCommand(GenericCommand): + """Inline code assemble. Architecture can be set in GEF runtime config. """ + + _cmdline_ = "assemble" + _syntax_ = f"{_cmdline_} [-h] [--list-archs] [--mode MODE] [--arch ARCH] [--overwrite-location LOCATION] [--endian ENDIAN] [--as-shellcode] instruction;[instruction;...instruction;])" + _aliases_ = ["asm",] + _example_ = (f"\n{_cmdline_} -a x86 -m 32 nop ; nop ; inc eax ; int3" + f"\n{_cmdline_} -a arm -m arm add r0, r0, 1") + + valid_arch_modes = { + # Format: ARCH = [MODES] with MODE = (NAME, HAS_LITTLE_ENDIAN, HAS_BIG_ENDIAN) + "ARM": [("ARM", True, True), ("THUMB", True, True), + ("ARMV8", True, True), ("THUMBV8", True, True)], + "ARM64": [("0", True, False)], + "MIPS": [("MIPS32", True, True), ("MIPS64", True, True)], + "PPC": [("PPC32", False, True), ("PPC64", True, True)], + "SPARC": [("SPARC32", True, True), ("SPARC64", False, True)], + "SYSTEMZ": [("SYSTEMZ", True, True)], + "X86": [("16", True, False), ("32", True, False), + ("64", True, False)] + } + valid_archs = valid_arch_modes.keys() + valid_modes = [_ for sublist in valid_arch_modes.values() for _ in sublist] + + def __init__(self) -> None: + super().__init__() + self["default_architecture"] = ("X86", "Specify the default architecture to use when assembling") + self["default_mode"] = ("64", "Specify the default architecture to use when assembling") + return + + def pre_load(self) -> None: + try: + __import__("keystone") + except ImportError: + msg = "Missing `keystone-engine` package for Python, install with: `pip install keystone-engine`." + raise ImportWarning(msg) + return + + def usage(self) -> None: + super().usage() + gef_print("") + self.list_archs() + return + + def list_archs(self) -> None: + gef_print("Available architectures/modes (with endianness):") + # for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h + for arch in self.valid_arch_modes: + gef_print(f"- {arch}") + for mode, le, be in self.valid_arch_modes[arch]: + if le and be: + endianness = "little, big" + elif le: + endianness = "little" + elif be: + endianness = "big" + gef_print(f" * {mode:<7} ({endianness})") + return + + @parse_arguments({"instructions": [""]}, {"--mode": "", "--arch": "", "--overwrite-location": 0, "--endian": "little", "--list-archs": True, "--as-shellcode": True}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + arch_s, mode_s, endian_s = self["default_architecture"], self["default_mode"], "" + + args = kwargs["arguments"] + if args.list_archs: + self.list_archs() + return + + if not args.instructions: + err("No instruction given.") + return + + if is_alive(): + arch_s, mode_s = gef.arch.arch, gef.arch.mode + endian_s = "big" if gef.arch.endianness == Endianness.BIG_ENDIAN else "" + + if args.arch: + arch_s = args.arch + arch_s = arch_s.upper() + + if args.mode: + mode_s = args.mode + mode_s = mode_s.upper() + + if args.endian == "big": + endian_s = "big" + endian_s = endian_s.upper() + + if arch_s not in self.valid_arch_modes: + raise AttributeError(f"invalid arch '{arch_s}'") + + valid_modes = self.valid_arch_modes[arch_s] + try: + mode_idx = [m[0] for m in valid_modes].index(mode_s) + except ValueError: + raise AttributeError(f"invalid mode '{mode_s}' for arch '{arch_s}'") + + if endian_s == "little" and not valid_modes[mode_idx][1] or endian_s == "big" and not valid_modes[mode_idx][2]: + raise AttributeError(f"invalid endianness '{endian_s}' for arch/mode '{arch_s}:{mode_s}'") + + arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=endian_s) + insns = [x.strip() for x in " ".join(args.instructions).split(";") if x] + info(f"Assembling {len(insns)} instruction(s) for {arch_s}:{mode_s}") + + if args.as_shellcode: + gef_print("""sc="" """) + + raw = b"" + for insn in insns: + res = keystone_assemble(insn, arch, mode, raw=True) + if res is None: + gef_print("(Invalid)") + continue + + if args.overwrite_location: + raw += res + continue + + s = binascii.hexlify(res) + res = b"\\x" + b"\\x".join([s[i:i + 2] for i in range(0, len(s), 2)]) + res = res.decode("utf-8") + + if args.as_shellcode: + res = f"""sc+="{res}" """ + + gef_print(f"{res!s:60s} # {insn}") + + if args.overwrite_location: + l = len(raw) + info(f"Overwriting {l:d} bytes at {format_address(args.overwrite_location)}") + gef.memory.write(args.overwrite_location, raw, l) + return + + +@register_command +class ProcessListingCommand(GenericCommand): + """List and filter process. If a PATTERN is given as argument, results shown will be grepped + by this pattern.""" + + _cmdline_ = "process-search" + _syntax_ = f"{_cmdline_} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]" + _aliases_ = ["ps"] + _example_ = f"{_cmdline_} gdb.*" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["ps_command"] = (f"{gef.session.constants['ps']} auxww", "`ps` command to get process information") + return + + @parse_arguments({"pattern": ""}, {"--attach": True, "--smart-scan": True}) + def do_invoke(self, _: List, **kwargs: Any) -> None: + args = kwargs["arguments"] + do_attach = args.attach + smart_scan = args.smart_scan + pattern = args.pattern + pattern = re.compile("^.*$") if not args else re.compile(pattern) + + for process in self.get_processes(): + pid = int(process["pid"]) + command = process["command"] + + if not re.search(pattern, command): + continue + + if smart_scan: + if command.startswith("[") and command.endswith("]"): continue + if command.startswith("socat "): continue + if command.startswith("grep "): continue + if command.startswith("gdb "): continue + + if args and do_attach: + ok(f"Attaching to process='{process['command']}' pid={pid:d}") + gdb.execute(f"attach {pid:d}") + return None + + line = [process[i] for i in ("pid", "user", "cpu", "mem", "tty", "command")] + gef_print("\t\t".join(line)) + + return None + + def get_processes(self) -> Generator[Dict[str, str], None, None]: + output = gef_execute_external(self["ps_command"].split(), True) + names = [x.lower().replace("%", "") for x in output[0].split()] + + for line in output[1:]: + fields = line.split() + t = {} + + for i, name in enumerate(names): + if i == len(names) - 1: + t[name] = " ".join(fields[i:]) + else: + t[name] = fields[i] + + yield t + + return + + +@register_command +class ElfInfoCommand(GenericCommand): + """Display a limited subset of ELF header information. If no argument is provided, the command will + show information about the current ELF being debugged.""" + + _cmdline_ = "elf-info" + _syntax_ = f"{_cmdline_} [FILE]" + _example_ = f"{_cmdline_} /bin/ls" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @parse_arguments({}, {"--filename": ""}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + + if is_qemu_system(): + err("Unsupported") + return + + filename = args.filename or get_filepath() + if filename is None: + return + + elf = get_elf_headers(filename) + if elf is None: + return + + data = [ + ("Magic", f"{hexdump(struct.pack('>I', elf.e_magic), show_raw=True)}"), + ("Class", f"{elf.e_class.value:#x} - {elf.e_class.name}"), + ("Endianness", f"{elf.e_endianness.value:#x} - {Endianness(elf.e_endianness).name}"), + ("Version", f"{elf.e_eiversion:#x}"), + ("OS ABI", f"{elf.e_osabi.value:#x} - {elf.e_osabi.name if elf.e_osabi else ''}"), + ("ABI Version", f"{elf.e_abiversion:#x}"), + ("Type", f"{elf.e_type.value:#x} - {elf.e_type.name}"), + ("Machine", f"{elf.e_machine.value:#x} - {elf.e_machine.name}"), + ("Program Header Table", f"{format_address(elf.e_phoff)}"), + ("Section Header Table", f"{format_address(elf.e_shoff)}"), + ("Header Table", f"{format_address(elf.e_phoff)}"), + ("ELF Version", f"{elf.e_version:#x}"), + ("Header size", "{0} ({0:#x})".format(elf.e_ehsize)), + ("Entry point", f"{format_address(elf.e_entry)}"), + ] + + for title, content in data: + gef_print(f"{Color.boldify(f'{title:<22}')}: {content}") + + gef_print("") + gef_print(titlify("Program Header")) + + gef_print(" [{:>2s}] {:12s} {:>8s} {:>10s} {:>10s} {:>8s} {:>8s} {:5s} {:>8s}".format( + "#", "Type", "Offset", "Virtaddr", "Physaddr", "FileSiz", "MemSiz", "Flags", "Align")) + + for i, p in enumerate(elf.phdrs): + p_type = p.p_type.name if p.p_type else "" + p_flags = str(p.p_flags.name).lstrip("Flag.") if p.p_flags else "???" + + gef_print(" [{:2d}] {:12s} {:#8x} {:#10x} {:#10x} {:#8x} {:#8x} {:5s} {:#8x}".format( + i, p_type, p.p_offset, p.p_vaddr, p.p_paddr, p.p_filesz, p.p_memsz, p_flags, p.p_align)) + + gef_print("") + gef_print(titlify("Section Header")) + gef_print(" [{:>2s}] {:20s} {:>15s} {:>10s} {:>8s} {:>8s} {:>8s} {:5s} {:4s} {:4s} {:>8s}".format( + "#", "Name", "Type", "Address", "Offset", "Size", "EntSiz", "Flags", "Link", "Info", "Align")) + + for i, s in enumerate(elf.shdrs): + sh_type = s.sh_type.name if s.sh_type else "UNKN" + sh_flags = str(s.sh_flags).lstrip("Flags.") if s.sh_flags else "UNKN" + + gef_print(f" [{i:2d}] {s.name:20s} {sh_type:>15s} {s.sh_addr:#10x} {s.sh_offset:#8x} " + f"{s.sh_size:#8x} {s.sh_entsize:#8x} {sh_flags:5s} {s.sh_link:#4x} {s.sh_info:#4x} {s.sh_addralign:#8x}") + return + + +@register_command +class EntryPointBreakCommand(GenericCommand): + """Tries to find best entry point and sets a temporary breakpoint on it. The command will test for + well-known symbols for entry points, such as `main`, `_main`, `__libc_start_main`, etc. defined by + the setting `entrypoint_symbols`.""" + + _cmdline_ = "entry-break" + _syntax_ = _cmdline_ + _aliases_ = ["start",] + + def __init__(self) -> None: + super().__init__() + self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points") + return + + def do_invoke(self, argv: List[str]) -> None: + fpath = get_filepath() + if fpath is None: + warn("No executable to debug, use `file` to load a binary") + return + + if not os.access(fpath, os.X_OK): + warn(f"The file '{fpath}' is not executable.") + return + + if is_alive() and not gef.session.qemu_mode: + warn("gdb is already running") + return + + bp = None + entrypoints = self["entrypoint_symbols"].split() + + for sym in entrypoints: + try: + value = parse_address(sym) + info(f"Breaking at '{value:#x}'") + bp = EntryBreakBreakpoint(sym) + gdb.execute(f"run {' '.join(argv)}") + return + + except gdb.error as gdb_error: + if 'The "remote" target does not support "run".' in str(gdb_error): + # this case can happen when doing remote debugging + gdb.execute("continue") + return + continue + + # if here, clear the breakpoint if any set + if bp: + bp.delete() + + # break at entry point + entry = gef.binary.entry_point + + if is_pie(fpath): + self.set_init_tbreak_pie(entry, argv) + gdb.execute("continue") + return + + self.set_init_tbreak(entry) + gdb.execute(f"run {' '.join(argv)}") + return + + def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint: + info(f"Breaking at entry-point: {addr:#x}") + bp = EntryBreakBreakpoint(f"*{addr:#x}") + return bp + + def set_init_tbreak_pie(self, addr: int, argv: List[str]) -> EntryBreakBreakpoint: + warn("PIC binary detected, retrieving text base address") + gdb.execute("set stop-on-solib-events 1") + hide_context() + gdb.execute(f"run {' '.join(argv)}") + unhide_context() + gdb.execute("set stop-on-solib-events 0") + vmmap = gef.memory.maps + base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0] + return self.set_init_tbreak(base_address + addr) + + +@register_command +class NamedBreakpointCommand(GenericCommand): + """Sets a breakpoint and assigns a name to it, which will be shown, when it's hit.""" + + _cmdline_ = "name-break" + _syntax_ = f"{_cmdline_} name [address]" + _aliases_ = ["nb",] + _example = f"{_cmdline_} main *0x4008a9" + + def __init__(self) -> None: + super().__init__() + return + + @parse_arguments({"name": "", "address": "*$pc"}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + if not args.name: + err("Missing name for breakpoint") + self.usage() + return + + NamedBreakpoint(args.address, args.name) + return + + +@register_command +class ContextCommand(GenericCommand): + """Displays a comprehensive and modular summary of runtime context. Unless setting `enable` is + set to False, this command will be spawned automatically every time GDB hits a breakpoint, a + watchpoint, or any kind of interrupt. By default, it will show panes that contain the register + states, the stack, and the disassembly code around $pc.""" + + _cmdline_ = "context" + _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]" + _aliases_ = ["ctx",] + + old_registers: Dict[str, Optional[int]] = {} + + def __init__(self) -> None: + super().__init__() + self["enable"] = (True, "Enable/disable printing the context when breaking") + self["show_source_code_variable_values"] = (True, "Show extra PC context info in the source code") + self["show_stack_raw"] = (False, "Show the stack pane as raw hexdump (no dereference)") + self["show_registers_raw"] = (False, "Show the registers pane with raw values (no dereference)") + self["show_opcodes_size"] = (0, "Number of bytes of opcodes to display next to the disassembly") + self["peek_calls"] = (True, "Peek into calls") + self["peek_ret"] = (True, "Peek at return address") + self["nb_lines_stack"] = (8, "Number of line in the stack pane") + self["grow_stack_down"] = (False, "Order of stack downward starts at largest down to stack pointer") + self["nb_lines_backtrace"] = (10, "Number of line in the backtrace pane") + self["nb_lines_backtrace_before"] = (2, "Number of line in the backtrace pane before selected frame") + self["nb_lines_threads"] = (-1, "Number of line in the threads pane") + self["nb_lines_code"] = (6, "Number of instruction after $pc") + self["nb_lines_code_prev"] = (3, "Number of instruction before $pc") + self["ignore_registers"] = ("", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')") + self["clear_screen"] = (True, "Clear the screen before printing the context") + self["layout"] = ("legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections") + self["redirect"] = ("", "Redirect the context information to another TTY") + self["libc_args"] = (False, "Show libc function call args description") + self["libc_args_path"] = ("", "Path to libc function call args json files, provided via gef-extras") + + if "capstone" in list(sys.modules.keys()): + self["use_capstone"] = (False, "Use capstone as disassembler in the code pane (instead of GDB)") + + self.layout_mapping = { + "legend": (self.show_legend, None), + "regs": (self.context_regs, None), + "stack": (self.context_stack, None), + "code": (self.context_code, None), + "args": (self.context_args, None), + "memory": (self.context_memory, None), + "source": (self.context_source, None), + "trace": (self.context_trace, None), + "threads": (self.context_threads, None), + "extra": (self.context_additional_information, None), + } + return + + def post_load(self) -> None: + gef_on_continue_hook(self.update_registers) + gef_on_continue_hook(self.empty_extra_messages) + return + + def show_legend(self) -> None: + if gef.config["gef.disable_color"] is True: + return + str_color = gef.config["theme.dereference_string"] + code_addr_color = gef.config["theme.address_code"] + stack_addr_color = gef.config["theme.address_stack"] + heap_addr_color = gef.config["theme.address_heap"] + changed_register_color = gef.config["theme.registers_value_changed"] + + gef_print("[ Legend: {} | {} | {} | {} | {} ]".format(Color.colorify("Modified register", changed_register_color), + Color.colorify("Code", code_addr_color), + Color.colorify("Heap", heap_addr_color), + Color.colorify("Stack", stack_addr_color), + Color.colorify("String", str_color))) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if not self["enable"] or gef.ui.context_hidden: + return + + if not all(_ in self.layout_mapping for _ in argv): + self.usage() + return + + if len(argv) > 0: + current_layout = argv + else: + current_layout = self["layout"].strip().split() + + if not current_layout: + return + + self.tty_rows, self.tty_columns = get_terminal_size() + + redirect = self["redirect"] + if redirect and os.access(redirect, os.W_OK): + enable_redirect_output(to_file=redirect) + + for section in current_layout: + if section[0] == "-": + continue + + try: + display_pane_function, pane_title_function = self.layout_mapping[section] + if pane_title_function: + self.context_title(pane_title_function()) + display_pane_function() + except gdb.MemoryError as e: + # a MemoryError will happen when $pc is corrupted (invalid address) + err(str(e)) + + self.context_title("") + + if self["clear_screen"] and len(argv) == 0: + clear_screen(redirect) + + if redirect and os.access(redirect, os.W_OK): + disable_redirect_output() + return + + def context_title(self, m: Optional[str]) -> None: + # allow for not displaying a title line + if m is None: + return + + line_color = gef.config["theme.context_title_line"] + msg_color = gef.config["theme.context_title_message"] + + # print an empty line in case of "" + if not m: + gef_print(Color.colorify(HORIZONTAL_LINE * self.tty_columns, line_color)) + return + + trail_len = len(m) + 6 + title = "" + title += Color.colorify("{:{padd}<{width}} ".format("", + width=max(self.tty_columns - trail_len, 0), + padd=HORIZONTAL_LINE), + line_color) + title += Color.colorify(m, msg_color) + title += Color.colorify(" {:{padd}<4}".format("", padd=HORIZONTAL_LINE), + line_color) + gef_print(title) + return + + def context_regs(self) -> None: + self.context_title("registers") + ignored_registers = set(self["ignore_registers"].split()) + + if self["show_registers_raw"] is False: + regs = set(gef.arch.all_registers) + printable_registers = " ".join(list(regs - ignored_registers)) + gdb.execute(f"registers {printable_registers}") + return + + widest = l = max(map(len, gef.arch.all_registers)) + l += 5 + l += gef.arch.ptrsize * 2 + nb = get_terminal_size()[1] // l + i = 1 + line = "" + changed_color = gef.config["theme.registers_value_changed"] + regname_color = gef.config["theme.registers_register_name"] + + for reg in gef.arch.all_registers: + if reg in ignored_registers: + continue + + try: + r = gdb.parse_and_eval(reg) + if r.type.code == gdb.TYPE_CODE_VOID: + continue + + new_value_type_flag = r.type.code == gdb.TYPE_CODE_FLAGS + new_value = int(r) + + except (gdb.MemoryError, gdb.error): + # If this exception is triggered, it means that the current register + # is corrupted. Just use the register "raw" value (not eval-ed) + new_value = gef.arch.register(reg) + new_value_type_flag = False + + except Exception: + new_value = 0 + new_value_type_flag = False + + old_value = self.old_registers.get(reg, 0) + + padreg = reg.ljust(widest, " ") + value = align_address(new_value) + old_value = align_address(old_value) + if value == old_value: + line += f"{Color.colorify(padreg, regname_color)}: " + else: + line += f"{Color.colorify(padreg, changed_color)}: " + if new_value_type_flag: + line += f"{format_address_spaces(value)} " + else: + addr = lookup_address(align_address(int(value))) + if addr.valid: + line += f"{addr!s} " + else: + line += f"{format_address_spaces(value)} " + + if i % nb == 0: + gef_print(line) + line = "" + i += 1 + + if line: + gef_print(line) + + gef_print(f"Flags: {gef.arch.flag_register_to_human()}") + return + + def context_stack(self) -> None: + self.context_title("stack") + + show_raw = self["show_stack_raw"] + nb_lines = self["nb_lines_stack"] + + try: + sp = gef.arch.sp + if show_raw is True: + mem = gef.memory.read(sp, 0x10 * nb_lines) + gef_print(hexdump(mem, base=sp)) + else: + gdb.execute(f"dereference -l {nb_lines:d} {sp:#x}") + + except gdb.MemoryError: + err("Cannot read memory from $SP (corrupted stack pointer?)") + + return + + def addr_has_breakpoint(self, address: int, bp_locations: List[str]) -> bool: + return any(hex(address) in b for b in bp_locations) + + def context_code(self) -> None: + nb_insn = self["nb_lines_code"] + nb_insn_prev = self["nb_lines_code_prev"] + use_capstone = "use_capstone" in self and self["use_capstone"] + show_opcodes_size = "show_opcodes_size" in self and self["show_opcodes_size"] + past_insns_color = gef.config["theme.old_context"] + cur_insn_color = gef.config["theme.disassemble_current_instruction"] + pc = gef.arch.pc + breakpoints = gdb.breakpoints() or [] + bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")] + + frame = gdb.selected_frame() + arch_name = f"{gef.arch.arch.lower()}:{gef.arch.mode}" + + self.context_title(f"code:{arch_name}") + + try: + instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble + + for insn in instruction_iterator(pc, nb_insn, nb_prev=nb_insn_prev): + line = [] + is_taken = False + target = None + bp_prefix = Color.redify(BP_GLYPH) if self.addr_has_breakpoint(insn.address, bp_locations) else " " + + if show_opcodes_size == 0: + text = str(insn) + else: + insn_fmt = f"{{:{show_opcodes_size}o}}" + text = insn_fmt.format(insn) + + if insn.address < pc: + line += f"{bp_prefix} {Color.colorify(text, past_insns_color)}" + + elif insn.address == pc: + line += f"{bp_prefix}{Color.colorify(f'{RIGHT_ARROW[1:]}{text}', cur_insn_color)}" + + if gef.arch.is_conditional_branch(insn): + is_taken, reason = gef.arch.is_branch_taken(insn) + if is_taken: + target = insn.operands[-1].split()[0] + reason = f"[Reason: {reason}]" if reason else "" + line += Color.colorify(f"\tTAKEN {reason}", "bold green") + else: + reason = f"[Reason: !({reason})]" if reason else "" + line += Color.colorify(f"\tNOT taken {reason}", "bold red") + elif gef.arch.is_call(insn) and self["peek_calls"] is True: + target = insn.operands[-1].split()[0] + elif gef.arch.is_ret(insn) and self["peek_ret"] is True: + target = gef.arch.get_ra(insn, frame) + + else: + line += f"{bp_prefix} {text}" + + gef_print("".join(line)) + + if target: + try: + target = int(target, 0) + except TypeError: # Already an int + pass + except ValueError: + # If the operand isn't an address right now we can't parse it + continue + for i, tinsn in enumerate(instruction_iterator(target, nb_insn)): + text= f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}" + gef_print(text) + break + + except gdb.MemoryError: + err("Cannot disassemble from $PC") + return + + def context_args(self) -> None: + insn = gef_current_instruction(gef.arch.pc) + if not gef.arch.is_call(insn): + return + + self.size2type = { + 1: "BYTE", + 2: "WORD", + 4: "DWORD", + 8: "QWORD", + } + + if insn.operands[-1].startswith(self.size2type[gef.arch.ptrsize]+" PTR"): + target = "*" + insn.operands[-1].split()[-1] + elif "$"+insn.operands[0] in gef.arch.all_registers: + target = f"*{gef.arch.register('$' + insn.operands[0]):#x}" + else: + # is there a symbol? + ops = " ".join(insn.operands) + if "<" in ops and ">" in ops: + # extract it + target = re.sub(r".*<([^\(> ]*).*", r"\1", ops) + else: + # it's an address, just use as is + target = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops) + + sym = gdb.lookup_global_symbol(target) + if sym is None: + self.print_guessed_arguments(target) + return + + if sym.type.code != gdb.TYPE_CODE_FUNC: + err(f"Symbol '{target}' is not a function: type={sym.type.code}") + return + + self.print_arguments_from_symbol(target, sym) + return + + def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") -> None: + """If symbols were found, parse them and print the argument adequately.""" + args = [] + + for i, f in enumerate(symbol.type.fields()): + _value = gef.arch.get_ith_parameter(i, in_func=False)[1] + _value = RIGHT_ARROW.join(dereference_from(_value)) + _name = f.name or f"var_{i}" + _type = f.type.name or self.size2type[f.type.sizeof] + args.append(f"{_type} {_name} = {_value}") + + self.context_title("arguments") + + if not args: + gef_print(f"{function_name} (<void>)") + return + + gef_print(f"{function_name} (\n "+",\n ".join(args)+"\n)") + return + + def print_guessed_arguments(self, function_name: str) -> None: + """When no symbol, read the current basic block and look for "interesting" instructions.""" + + def __get_current_block_start_address() -> Optional[int]: + pc = gef.arch.pc + try: + block = gdb.block_for_pc(pc) + block_start = block.start if block else gdb_get_nth_previous_instruction_address(pc, 5) + except RuntimeError: + block_start = gdb_get_nth_previous_instruction_address(pc, 5) + return block_start + + parameter_set = set() + pc = gef.arch.pc + block_start = __get_current_block_start_address() + if not block_start: + return + use_capstone = "use_capstone" in self and self["use_capstone"] + instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble + function_parameters = gef.arch.function_parameters + arg_key_color = gef.config["theme.registers_register_name"] + + for insn in instruction_iterator(block_start, pc - block_start): + if not insn.operands: + continue + + if is_x86_32(): + if insn.mnemonic == "push": + parameter_set.add(insn.operands[0]) + else: + op = "$" + insn.operands[0] + if op in function_parameters: + parameter_set.add(op) + + if is_x86_64(): + # also consider extended registers + extended_registers = {"$rdi": ["$edi", "$di"], + "$rsi": ["$esi", "$si"], + "$rdx": ["$edx", "$dx"], + "$rcx": ["$ecx", "$cx"], + } + for exreg in extended_registers: + if op in extended_registers[exreg]: + parameter_set.add(exreg) + + nb_argument = None + _arch_mode = f"{gef.arch.arch.lower()}_{gef.arch.mode}" + _function_name = None + if function_name.endswith("@plt"): + _function_name = function_name.split("@")[0] + try: + nb_argument = len(gef.ui.highlight_table[_arch_mode][_function_name]) + except KeyError: + pass + + if not nb_argument: + if is_x86_32(): + nb_argument = len(parameter_set) + else: + nb_argument = max([function_parameters.index(p)+1 for p in parameter_set], default=0) + + args = [] + for i in range(nb_argument): + _key, _values = gef.arch.get_ith_parameter(i, in_func=False) + _values = RIGHT_ARROW.join(dereference_from(_values)) + try: + args.append("{} = {} (def: {})".format(Color.colorify(_key, arg_key_color), _values, + gef.ui.highlight_table[_arch_mode][_function_name][_key])) + except KeyError: + args.append(f"{Color.colorify(_key, arg_key_color)} = {_values}") + + self.context_title("arguments (guessed)") + gef_print(f"{function_name} (") + if args: + gef_print(" " + ",\n ".join(args)) + gef_print(")") + return + + def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: List[str]) -> bool: + filename_line = f"{file_name}:{line_number}" + return any(filename_line in loc for loc in bp_locations) + + def context_source(self) -> None: + try: + pc = gef.arch.pc + symtabline = gdb.find_pc_line(pc) + symtab = symtabline.symtab + # we subtract one because the line number returned by gdb start at 1 + line_num = symtabline.line - 1 + if not symtab.is_valid(): + return + + fpath = symtab.fullname() + with open(fpath, "r") as f: + lines = [l.rstrip() for l in f.readlines()] + + except Exception: + return + + file_base_name = os.path.basename(symtab.filename) + breakpoints = gdb.breakpoints() or [] + bp_locations = [b.location for b in breakpoints if b.location and file_base_name in b.location] + past_lines_color = gef.config["theme.old_context"] + + nb_line = self["nb_lines_code"] + fn = symtab.filename + if len(fn) > 20: + fn = f"{fn[:15]}[...]{os.path.splitext(fn)[1]}" + title = f"source:{fn}+{line_num + 1}" + cur_line_color = gef.config["theme.source_current_line"] + self.context_title(title) + show_extra_info = self["show_source_code_variable_values"] + + for i in range(line_num - nb_line + 1, line_num + nb_line): + if i < 0: + continue + + bp_prefix = Color.redify(BP_GLYPH) if self.line_has_breakpoint(file_base_name, i + 1, bp_locations) else " " + + if i < line_num: + gef_print("{}{}".format(bp_prefix, Color.colorify(f" {i + 1:4d}\t {lines[i]}", past_lines_color))) + + if i == line_num: + prefix = f"{bp_prefix}{RIGHT_ARROW[1:]}{i + 1:4d}\t " + leading = len(lines[i]) - len(lines[i].lstrip()) + if show_extra_info: + extra_info = self.get_pc_context_info(pc, lines[i]) + if extra_info: + gef_print(f"{' ' * (len(prefix) + leading)}{extra_info}") + gef_print(Color.colorify(f"{prefix}{lines[i]}", cur_line_color)) + + if i > line_num: + try: + gef_print(f"{bp_prefix} {i + 1:4d}\t {lines[i]}") + except IndexError: + break + return + + def get_pc_context_info(self, pc: int, line: str) -> str: + try: + current_block = gdb.block_for_pc(pc) + if not current_block or not current_block.is_valid(): return "" + m = collections.OrderedDict() + while current_block and not current_block.is_static: + for sym in current_block: + symbol = sym.name + if not sym.is_function and re.search(fr"\W{symbol}\W", line): + val = gdb.parse_and_eval(symbol) + if val.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY): + addr = int(val.address) + addrs = dereference_from(addr) + if len(addrs) > 2: + addrs = [addrs[0], "[...]", addrs[-1]] + + f = f" {RIGHT_ARROW} " + val = f.join(addrs) + elif val.type.code == gdb.TYPE_CODE_INT: + val = hex(int(val)) + else: + continue + + if symbol not in m: + m[symbol] = val + current_block = current_block.superblock + + if m: + return "// " + ", ".join([f"{Color.yellowify(a)}={b}" for a, b in m.items()]) + except Exception: + pass + return "" + + def context_trace(self) -> None: + self.context_title("trace") + + nb_backtrace = self["nb_lines_backtrace"] + if nb_backtrace <= 0: + return + + # backward compat for gdb (gdb < 7.10) + if not hasattr(gdb, "FrameDecorator"): + gdb.execute(f"backtrace {nb_backtrace:d}") + return + + orig_frame = gdb.selected_frame() + current_frame = gdb.newest_frame() + frames = [current_frame] + while current_frame != orig_frame: + current_frame = current_frame.older() + frames.append(current_frame) + + nb_backtrace_before = self["nb_lines_backtrace_before"] + level = max(len(frames) - nb_backtrace_before - 1, 0) + current_frame = frames[level] + + while current_frame: + current_frame.select() + if not current_frame.is_valid(): + continue + + pc = current_frame.pc() + name = current_frame.name() + items = [] + items.append(f"{pc:#x}") + if name: + frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] + m = "{}({})".format(Color.greenify(name), + ", ".join(["{}={!s}".format(Color.yellowify(x.sym), + x.sym.value(current_frame)) for x in frame_args])) + items.append(m) + else: + try: + insn = next(gef_disassemble(pc, 1)) + except gdb.MemoryError: + break + + # check if the gdb symbol table may know the address + sym_found = gdb_get_location_from_symbol(pc) + symbol = "" + if sym_found: + sym_name, offset = sym_found + symbol = f" <{sym_name}+{offset:x}> " + + items.append(Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}")) + + gef_print("[{}] {}".format(Color.colorify(f"#{level}", "bold green" if current_frame == orig_frame else "bold pink"), + RIGHT_ARROW.join(items))) + current_frame = current_frame.older() + level += 1 + nb_backtrace -= 1 + if nb_backtrace == 0: + break + + orig_frame.select() + return + + def context_threads(self) -> None: + def reason() -> str: + res = gdb.execute("info program", to_string=True).splitlines() + if not res: + return "NOT RUNNING" + + for line in res: + line = line.strip() + if line.startswith("It stopped with signal "): + return line.replace("It stopped with signal ", "").split(",", 1)[0] + if line == "The program being debugged is not being run.": + return "NOT RUNNING" + if line == "It stopped at a breakpoint that has since been deleted.": + return "TEMPORARY BREAKPOINT" + if line.startswith("It stopped at breakpoint "): + return "BREAKPOINT" + if line == "It stopped after being stepped.": + return "SINGLE STEP" + + return "STOPPED" + + self.context_title("threads") + + threads = gdb.selected_inferior().threads()[::-1] + idx = self["nb_lines_threads"] + if idx > 0: + threads = threads[0:idx] + + if idx == 0: + return + + if not threads: + err("No thread selected") + return + + selected_thread = gdb.selected_thread() + selected_frame = gdb.selected_frame() + + for i, thread in enumerate(threads): + line = f"[{Color.colorify(f'#{i:d}', 'bold green' if thread == selected_thread else 'bold pink')}] Id {thread.num:d}, " + if thread.name: + line += f"""Name: "{thread.name}", """ + if thread.is_running(): + line += Color.colorify("running", "bold green") + elif thread.is_stopped(): + line += Color.colorify("stopped", "bold red") + thread.switch() + frame = gdb.selected_frame() + frame_name = frame.name() + + # check if the gdb symbol table may know the address + if not frame_name: + sym_found = gdb_get_location_from_symbol(frame.pc()) + if sym_found: + sym_name, offset = sym_found + frame_name = f"<{sym_name}+{offset:x}>" + + line += (f" {Color.colorify(f'{frame.pc():#x}', 'blue')} in " + f"{Color.colorify(frame_name or '??', 'bold yellow')} (), " + f"reason: {Color.colorify(reason(), 'bold pink')}") + elif thread.is_exited(): + line += Color.colorify("exited", "bold yellow") + gef_print(line) + i += 1 + + selected_thread.switch() + selected_frame.select() + return + + def context_additional_information(self) -> None: + if not gef.ui.context_messages: + return + + self.context_title("extra") + for level, text in gef.ui.context_messages: + if level == "error": err(text) + elif level == "warn": warn(text) + elif level == "success": ok(text) + else: info(text) + return + + def context_memory(self) -> None: + for address, opt in sorted(gef.ui.watches.items()): + sz, fmt = opt[0:2] + self.context_title(f"memory:{address:#x}") + if fmt == "pointers": + gdb.execute(f"dereference -l {sz:d} {address:#x}") + else: + gdb.execute(f"hexdump {fmt} -s {sz:d} {address:#x}") + + @classmethod + def update_registers(cls, _) -> None: + for reg in gef.arch.all_registers: + try: + cls.old_registers[reg] = gef.arch.register(reg) + except Exception: + cls.old_registers[reg] = 0 + return + + def empty_extra_messages(self, _) -> None: + gef.ui.context_messages.clear() + return + + +@register_command +class MemoryCommand(GenericCommand): + """Add or remove address ranges to the memory view.""" + _cmdline_ = "memory" + _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + self.usage() + return + + +@register_command +class MemoryWatchCommand(GenericCommand): + """Adds address ranges to the memory view.""" + _cmdline_ = "memory watch" + _syntax_ = f"{_cmdline_} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]" + _example_ = (f"\n{_cmdline_} 0x603000 0x100 byte" + f"\n{_cmdline_} $sp") + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) not in (1, 2, 3): + self.usage() + return + + address = parse_address(argv[0]) + size = parse_address(argv[1]) if len(argv) > 1 else 0x10 + group = "byte" + + if len(argv) == 3: + group = argv[2].lower() + if group not in ("qword", "dword", "word", "byte", "pointers"): + warn(f"Unexpected grouping '{group}'") + self.usage() + return + else: + if gef.arch.ptrsize == 4: + group = "dword" + elif gef.arch.ptrsize == 8: + group = "qword" + + gef.ui.watches[address] = (size, group) + ok(f"Adding memwatch to {address:#x}") + return + + +@register_command +class MemoryUnwatchCommand(GenericCommand): + """Removes address ranges to the memory view.""" + _cmdline_ = "memory unwatch" + _syntax_ = f"{_cmdline_} ADDRESS" + _example_ = (f"\n{_cmdline_} 0x603000" + f"\n{_cmdline_} $sp") + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if not argv: + self.usage() + return + + address = parse_address(argv[0]) + res = gef.ui.watches.pop(address, None) + if not res: + warn(f"You weren't watching {address:#x}") + else: + ok(f"Removed memwatch of {address:#x}") + return + + +@register_command +class MemoryWatchResetCommand(GenericCommand): + """Removes all watchpoints.""" + _cmdline_ = "memory reset" + _syntax_ = f"{_cmdline_}" + + @only_if_gdb_running + def do_invoke(self, _: List[str]) -> None: + gef.ui.watches.clear() + ok("Memory watches cleared") + return + + +@register_command +class MemoryWatchListCommand(GenericCommand): + """Lists all watchpoints to display in context layout.""" + _cmdline_ = "memory list" + _syntax_ = f"{_cmdline_}" + + @only_if_gdb_running + def do_invoke(self, _: List[str]) -> None: + if not gef.ui.watches: + info("No memory watches") + return + + info("Memory watches:") + for address, opt in sorted(gef.ui.watches.items()): + gef_print(f"- {address:#x} ({opt[0]}, {opt[1]})") + return + + +@register_command +class HexdumpCommand(GenericCommand): + """Display SIZE lines of hexdump from the memory location pointed by LOCATION.""" + + _cmdline_ = "hexdump" + _syntax_ = f"{_cmdline_} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]" + _example_ = f"{_cmdline_} byte $rsp --size 16 --reverse" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True) + self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump") + self.format: Optional[str] = None + self.__last_target = "$sp" + return + + @only_if_gdb_running + @parse_arguments({"address": "",}, {("--reverse", "-r"): True, ("--size", "-s"): 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + valid_formats = ["byte", "word", "dword", "qword"] + if not self.format or self.format not in valid_formats: + err("Invalid command") + return + + args = kwargs["arguments"] + target = args.address or self.__last_target + start_addr = parse_address(target) + read_from = align_address(start_addr) + + if self.format == "byte": + read_len = args.size or 0x40 + read_from += self.repeat_count * read_len + mem = gef.memory.read(read_from, read_len) + lines = hexdump(mem, base=read_from).splitlines() + else: + read_len = args.size or 0x10 + lines = self._hexdump(read_from, read_len, self.format, self.repeat_count * read_len) + + if args.reverse: + lines.reverse() + + self.__last_target = target + gef_print("\n".join(lines)) + return + + def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> List[str]: + endianness = gef.arch.endianness + + base_address_color = gef.config["theme.dereference_base_address"] + show_ascii = gef.config["hexdump.always_show_ascii"] + + formats = { + "qword": ("Q", 8), + "dword": ("I", 4), + "word": ("H", 2), + } + + r, l = formats[arrange_as] + fmt_str = f"{{base}}{VERTICAL_LINE}+{{offset:#06x}} {{sym}}{{val:#0{l*2+2}x}} {{text}}" + fmt_pack = f"{endianness!s}{r}" + lines = [] + + i = 0 + text = "" + while i < length: + cur_addr = start_addr + (i + offset) * l + sym = gdb_get_location_from_symbol(cur_addr) + sym = "<{:s}+{:04x}> ".format(*sym) if sym else "" + mem = gef.memory.read(cur_addr, l) + val = struct.unpack(fmt_pack, mem)[0] + if show_ascii: + text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem]) + lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color), + offset=(i + offset) * l, sym=sym, val=val, text=text)) + i += 1 + + return lines + + +@register_command +class HexdumpQwordCommand(HexdumpCommand): + """Display SIZE lines of hexdump as QWORD from the memory location pointed by ADDRESS.""" + + _cmdline_ = "hexdump qword" + _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" + _example_ = f"{_cmdline_} qword $rsp L16 REVERSE" + + def __init__(self) -> None: + super().__init__() + self.format = "qword" + return + + +@register_command +class HexdumpDwordCommand(HexdumpCommand): + """Display SIZE lines of hexdump as DWORD from the memory location pointed by ADDRESS.""" + + _cmdline_ = "hexdump dword" + _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" + _example_ = f"{_cmdline_} $esp L16 REVERSE" + + def __init__(self) -> None: + super().__init__() + self.format = "dword" + return + + +@register_command +class HexdumpWordCommand(HexdumpCommand): + """Display SIZE lines of hexdump as WORD from the memory location pointed by ADDRESS.""" + + _cmdline_ = "hexdump word" + _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" + _example_ = f"{_cmdline_} $esp L16 REVERSE" + + def __init__(self) -> None: + super().__init__() + self.format = "word" + return + + +@register_command +class HexdumpByteCommand(HexdumpCommand): + """Display SIZE lines of hexdump as BYTE from the memory location pointed by ADDRESS.""" + + _cmdline_ = "hexdump byte" + _syntax_ = f"{_cmdline_} [ADDRESS] [[L][SIZE]] [REVERSE]" + _example_ = f"{_cmdline_} $rsp L16" + + def __init__(self) -> None: + super().__init__() + self.format = "byte" + return + + +@register_command +class PatchCommand(GenericCommand): + """Write specified values to the specified address.""" + + _cmdline_ = "patch" + _syntax_ = (f"{_cmdline_} (qword|dword|word|byte) LOCATION VALUES\n" + f"{_cmdline_} string LOCATION \"double-escaped string\"") + SUPPORTED_SIZES = { + "qword": (8, "Q"), + "dword": (4, "L"), + "word": (2, "H"), + "byte": (1, "B"), + } + + def __init__(self) -> None: + super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION) + self.format: Optional[str] = None + return + + @only_if_gdb_running + @parse_arguments({"location": "", "values": ["", ]}, {}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + if not self.format or self.format not in self.SUPPORTED_SIZES: + self.usage() + return + + if not args.location or not args.values: + self.usage() + return + + addr = align_address(parse_address(args.location)) + size, fcode = self.SUPPORTED_SIZES[self.format] + + d = str(gef.arch.endianness) + for value in args.values: + value = parse_address(value) & ((1 << size * 8) - 1) + vstr = struct.pack(d + fcode, value) + gef.memory.write(addr, vstr, length=size) + addr += size + return + + +@register_command +class PatchQwordCommand(PatchCommand): + """Write specified QWORD to the specified address.""" + + _cmdline_ = "patch qword" + _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]" + _example_ = f"{_cmdline_} $rip 0x4141414141414141" + + def __init__(self) -> None: + super().__init__() + self.format = "qword" + return + + +@register_command +class PatchDwordCommand(PatchCommand): + """Write specified DWORD to the specified address.""" + + _cmdline_ = "patch dword" + _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]" + _example_ = f"{_cmdline_} $rip 0x41414141" + + def __init__(self) -> None: + super().__init__() + self.format = "dword" + return + + +@register_command +class PatchWordCommand(PatchCommand): + """Write specified WORD to the specified address.""" + + _cmdline_ = "patch word" + _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]" + _example_ = f"{_cmdline_} $rip 0x4141" + + def __init__(self) -> None: + super().__init__() + self.format = "word" + return + + +@register_command +class PatchByteCommand(PatchCommand): + """Write specified WORD to the specified address.""" + + _cmdline_ = "patch byte" + _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]" + _example_ = f"{_cmdline_} $pc 0x41 0x41 0x41 0x41 0x41" + + def __init__(self) -> None: + super().__init__() + self.format = "byte" + return + + +@register_command +class PatchStringCommand(GenericCommand): + """Write specified string to the specified memory location pointed by ADDRESS.""" + + _cmdline_ = "patch string" + _syntax_ = f"{_cmdline_} ADDRESS \"double backslash-escaped string\"" + _example_ = f"{_cmdline_} $sp \"GEFROCKS\"" + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + argc = len(argv) + if argc != 2: + self.usage() + return + + location, s = argv[0:2] + addr = align_address(parse_address(location)) + + try: + s = codecs.escape_decode(s)[0] + except binascii.Error: + gef_print(f"Could not decode '\\xXX' encoded string \"{s}\"") + return + + gef.memory.write(addr, s, len(s)) + return + + +@lru_cache() +def dereference_from(addr: int) -> List[str]: + if not is_alive(): + return [format_address(addr),] + + code_color = gef.config["theme.dereference_code"] + string_color = gef.config["theme.dereference_string"] + max_recursion = gef.config["dereference.max_recursion"] or 10 + addr = lookup_address(align_address(int(addr))) + msg = [format_address(addr.value),] + seen_addrs = set() + + while addr.section and max_recursion: + if addr.value in seen_addrs: + msg.append("[loop detected]") + break + seen_addrs.add(addr.value) + + max_recursion -= 1 + + # Is this value a pointer or a value? + # -- If it's a pointer, dereference + deref = addr.dereference() + if deref is None: + # if here, dereferencing addr has triggered a MemoryError, no need to go further + msg.append(str(addr)) + break + + new_addr = lookup_address(deref) + if new_addr.valid: + addr = new_addr + msg.append(str(addr)) + continue + + # -- Otherwise try to parse the value + if addr.section: + if addr.section.is_executable() and addr.is_in_text_segment() and not is_ascii_string(addr.value): + insn = gef_current_instruction(addr.value) + insn_str = f"{insn.location} {insn.mnemonic} {', '.join(insn.operands)}" + msg.append(Color.colorify(insn_str, code_color)) + break + + elif addr.section.permission & Permission.READ: + if is_ascii_string(addr.value): + s = gef.memory.read_cstring(addr.value) + if len(s) < gef.arch.ptrsize: + txt = f'{format_address(deref)} ("{Color.colorify(s, string_color)}"?)' + elif len(s) > 50: + txt = Color.colorify(f'"{s[:50]}[...]"', string_color) + else: + txt = Color.colorify(f'"{s}"', string_color) + + msg.append(txt) + break + + # if not able to parse cleanly, simply display and break + val = "{:#0{ma}x}".format(int(deref & 0xFFFFFFFFFFFFFFFF), ma=(gef.arch.ptrsize * 2 + 2)) + msg.append(val) + break + + return msg + + +@register_command +class DereferenceCommand(GenericCommand): + """Dereference recursively from an address and display information. This acts like WinDBG `dps` + command.""" + + _cmdline_ = "dereference" + _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]" + _aliases_ = ["telescope", ] + _example_ = f"{_cmdline_} --length 20 --reference $sp+0x10 $sp" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + self["max_recursion"] = (7, "Maximum level of pointer recursion") + return + + @staticmethod + def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str: + base_address_color = gef.config["theme.dereference_base_address"] + registers_color = gef.config["theme.dereference_register_value"] + + sep = f" {RIGHT_ARROW} " + memalign = gef.arch.ptrsize + + offset = idx * memalign + current_address = align_address(addr + offset) + addrs = dereference_from(current_address) + l = "" + addr_l = format_address(int(addrs[0], 16)) + l += "{}{}{:+#07x}: {:{ma}s}".format(Color.colorify(addr_l, base_address_color), + VERTICAL_LINE, base_offset+offset, + sep.join(addrs[1:]), ma=(memalign*2 + 2)) + + register_hints = [] + + for regname in gef.arch.all_registers: + regvalue = gef.arch.register(regname) + if current_address == regvalue: + register_hints.append(regname) + + if register_hints: + m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}" + l += Color.colorify(m, registers_color) + + offset += memalign + return l + + @only_if_gdb_running + @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + nb = args.length + + target = args.address + target_addr = parse_address(target) + + reference = args.reference or target + ref_addr = parse_address(reference) + + if process_lookup_address(target_addr) is None: + err(f"Unmapped address: '{target}'") + return + + if process_lookup_address(ref_addr) is None: + err(f"Unmapped address: '{reference}'") + return + + if gef.config["context.grow_stack_down"] is True: + from_insnum = nb * (self.repeat_count + 1) - 1 + to_insnum = self.repeat_count * nb - 1 + insnum_step = -1 + else: + from_insnum = 0 + self.repeat_count * nb + to_insnum = nb * (self.repeat_count + 1) + insnum_step = 1 + + start_address = align_address(target_addr) + base_offset = start_address - align_address(ref_addr) + + for i in range(from_insnum, to_insnum, insnum_step): + gef_print(DereferenceCommand.pprint_dereferenced(start_address, i, base_offset)) + + return + + +@register_command +class ASLRCommand(GenericCommand): + """View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not + attached). This command allows to change that setting.""" + + _cmdline_ = "aslr" + _syntax_ = f"{_cmdline_} [(on|off)]" + + def do_invoke(self, argv: List[str]) -> None: + argc = len(argv) + + if argc == 0: + ret = gdb.execute("show disable-randomization", to_string=True) + i = ret.find("virtual address space is ") + if i < 0: + return + + msg = "ASLR is currently " + if ret[i + 25:].strip() == "on.": + msg += Color.redify("disabled") + else: + msg += Color.greenify("enabled") + + gef_print(msg) + return + + elif argc == 1: + if argv[0] == "on": + info("Enabling ASLR") + gdb.execute("set disable-randomization off") + return + elif argv[0] == "off": + info("Disabling ASLR") + gdb.execute("set disable-randomization on") + return + + warn("Invalid command") + + self.usage() + return + + +@register_command +class ResetCacheCommand(GenericCommand): + """Reset cache of all stored data. This command is here for debugging and test purposes, GEF + handles properly the cache reset under "normal" scenario.""" + + _cmdline_ = "reset-cache" + _syntax_ = _cmdline_ + + def do_invoke(self, _: List[str]) -> None: + reset_all_caches() + return + + +@register_command +class VMMapCommand(GenericCommand): + """Display a comprehensive layout of the virtual memory mapping. If a filter argument, GEF will + filter out the mapping whose pathname do not match that filter.""" + + _cmdline_ = "vmmap" + _syntax_ = f"{_cmdline_} [FILTER]" + _example_ = f"{_cmdline_} libc" + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + vmmap = gef.memory.maps + if not vmmap: + err("No address mapping information found") + return + + if not gef.config["gef.disable_color"]: + self.show_legend() + + color = gef.config["theme.table_heading"] + + headers = ["Start", "End", "Offset", "Perm", "Path"] + gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) + + for entry in vmmap: + if not argv: + self.print_entry(entry) + continue + if argv[0] in entry.path: + self.print_entry(entry) + elif self.is_integer(argv[0]): + addr = int(argv[0], 0) + if addr >= entry.page_start and addr < entry.page_end: + self.print_entry(entry) + return + + def print_entry(self, entry: Section) -> None: + line_color = "" + if entry.path == "[stack]": + line_color = gef.config["theme.address_stack"] + elif entry.path == "[heap]": + line_color = gef.config["theme.address_heap"] + elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE: + line_color = gef.config["theme.address_code"] + + l = [ + Color.colorify(format_address(entry.page_start), line_color), + Color.colorify(format_address(entry.page_end), line_color), + Color.colorify(format_address(entry.offset), line_color), + ] + if entry.permission == Permission.ALL: + l.append(Color.colorify(str(entry.permission), "underline " + line_color)) + else: + l.append(Color.colorify(str(entry.permission), line_color)) + + l.append(Color.colorify(entry.path, line_color)) + line = " ".join(l) + + gef_print(line) + return + + def show_legend(self) -> None: + code_addr_color = gef.config["theme.address_code"] + stack_addr_color = gef.config["theme.address_stack"] + heap_addr_color = gef.config["theme.address_heap"] + + gef_print("[ Legend: {} | {} | {} ]".format(Color.colorify("Code", code_addr_color), + Color.colorify("Heap", heap_addr_color), + Color.colorify("Stack", stack_addr_color) + )) + return + + def is_integer(self, n: str) -> bool: + try: + int(n, 0) + except ValueError: + return False + return True + + +@register_command +class XFilesCommand(GenericCommand): + """Shows all libraries (and sections) loaded by binary. This command extends the GDB command + `info files`, by retrieving more information from extra sources, and providing a better + display. If an argument FILE is given, the output will grep information related to only that file. + If an argument name is also given, the output will grep to the name within FILE.""" + + _cmdline_ = "xfiles" + _syntax_ = f"{_cmdline_} [FILE [NAME]]" + _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables" + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + color = gef.config["theme.table_heading"] + headers = ["Start", "End", "Name", "File"] + gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color)) + + filter_by_file = argv[0] if argv and argv[0] else None + filter_by_name = argv[1] if len(argv) > 1 and argv[1] else None + + for xfile in get_info_files(): + if filter_by_file: + if filter_by_file not in xfile.filename: + continue + if filter_by_name and filter_by_name not in xfile.name: + continue + + l = [ + format_address(xfile.zone_start), + format_address(xfile.zone_end), + f"{xfile.name:<21s}", + xfile.filename, + ] + gef_print(" ".join(l)) + return + + +@register_command +class XAddressInfoCommand(GenericCommand): + """Retrieve and display runtime information for the location(s) given as parameter.""" + + _cmdline_ = "xinfo" + _syntax_ = f"{_cmdline_} LOCATION" + _example_ = f"{_cmdline_} $pc" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_LOCATION) + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if not argv: + err("At least one valid address must be specified") + self.usage() + return + + for sym in argv: + try: + addr = align_address(parse_address(sym)) + gef_print(titlify(f"xinfo: {addr:#x}")) + self.infos(addr) + + except gdb.error as gdb_err: + err(f"{gdb_err}") + return + + def infos(self, address: int) -> None: + addr = lookup_address(address) + if not addr.valid: + warn(f"Cannot reach {address:#x} in memory space") + return + + sect = addr.section + info = addr.info + + if sect: + gef_print(f"Page: {format_address(sect.page_start)} {RIGHT_ARROW} " + f"{format_address(sect.page_end)} (size={sect.page_end-sect.page_start:#x})" + f"\nPermissions: {sect.permission}" + f"\nPathname: {sect.path}" + f"\nOffset (from page): {addr.value-sect.page_start:#x}" + f"\nInode: {sect.inode}") + + if info: + gef_print(f"Segment: {info.name} " + f"({format_address(info.zone_start)}-{format_address(info.zone_end)})" + f"\nOffset (from segment): {addr.value-info.zone_start:#x}") + + sym = gdb_get_location_from_symbol(address) + if sym: + name, offset = sym + msg = f"Symbol: {name}" + if offset: + msg += f"+{offset:d}" + gef_print(msg) + + return + + +@register_command +class XorMemoryCommand(GenericCommand): + """XOR a block of memory. The command allows to simply display the result, or patch it + runtime at runtime.""" + + _cmdline_ = "xor-memory" + _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + def do_invoke(self, _: List[str]) -> None: + self.usage() + return + + +@register_command +class XorMemoryDisplayCommand(GenericCommand): + """Display a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be + provided in hexadecimal format.""" + + _cmdline_ = "xor-memory display" + _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" + _example_ = f"{_cmdline_} $sp 16 41414141" + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) != 3: + self.usage() + return + + address = parse_address(argv[0]) + length = int(argv[1], 0) + key = argv[2] + block = gef.memory.read(address, length) + info(f"Displaying XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") + + gef_print(titlify("Original block")) + gef_print(hexdump(block, base=address)) + + gef_print(titlify("XOR-ed block")) + gef_print(hexdump(xor(block, key), base=address)) + return + + +@register_command +class XorMemoryPatchCommand(GenericCommand): + """Patch a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be + provided in hexadecimal format.""" + + _cmdline_ = "xor-memory patch" + _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY" + _example_ = f"{_cmdline_} $sp 16 41414141" + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) != 3: + self.usage() + return + + address = parse_address(argv[0]) + length = int(argv[1], 0) + key = argv[2] + block = gef.memory.read(address, length) + info(f"Patching XOR-ing {address:#x}-{address + len(block):#x} with {key!r}") + xored_block = xor(block, key) + gef.memory.write(address, xored_block, length) + return + + +@register_command +class TraceRunCommand(GenericCommand): + """Create a runtime trace of all instructions executed from $pc to LOCATION specified. The + trace is stored in a text file that can be next imported in IDA Pro to visualize the runtime + path.""" + + _cmdline_ = "trace-run" + _syntax_ = f"{_cmdline_} LOCATION [MAX_CALL_DEPTH]" + _example_ = f"{_cmdline_} 0x555555554610" + + def __init__(self) -> None: + super().__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION) + self["max_tracing_recursion"] = (1, "Maximum depth of tracing") + self["tracefile_prefix"] = ("./gef-trace-", "Specify the tracing output file prefix") + return + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + if len(argv) not in (1, 2): + self.usage() + return + + if len(argv) == 2 and argv[1].isdigit(): + depth = int(argv[1]) + else: + depth = 1 + + try: + loc_start = gef.arch.pc + loc_end = parse_address(argv[0]) + except gdb.error as e: + err(f"Invalid location: {e}") + return + + self.trace(loc_start, loc_end, depth) + return + + def get_frames_size(self) -> int: + n = 0 + f = gdb.newest_frame() + while f: + n += 1 + f = f.older() + return n + + def trace(self, loc_start: int, loc_end: int, depth: int) -> None: + info(f"Tracing from {loc_start:#x} to {loc_end:#x} (max depth={depth:d})") + logfile = f"{self['tracefile_prefix']}{loc_start:#x}-{loc_end:#x}.txt" + with RedirectOutputContext(to=logfile): + hide_context() + self.start_tracing(loc_start, loc_end, depth) + unhide_context() + ok(f"Done, logfile stored as '{logfile}'") + info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path") + return + + def start_tracing(self, loc_start: int, loc_end: int, depth: int) -> None: + loc_cur = loc_start + frame_count_init = self.get_frames_size() + + gef_print("#", + f"# Execution tracing of {get_filepath()}", + f"# Start address: {format_address(loc_start)}", + f"# End address: {format_address(loc_end)}", + f"# Recursion level: {depth:d}", + "# automatically generated by gef.py", + "#\n", sep="\n") + + while loc_cur != loc_end: + try: + delta = self.get_frames_size() - frame_count_init + + if delta <= depth: + gdb.execute("stepi") + else: + gdb.execute("finish") + + loc_cur = gef.arch.pc + gdb.flush() + + except gdb.error as e: + gef_print("#", + f"# Execution interrupted at address {format_address(loc_cur)}", + f"# Exception: {e}", + "#\n", sep="\n") + break + + return + + +@register_command +class PatternCommand(GenericCommand): + """Generate or Search a De Bruijn Sequence of unique substrings of length N + and a total length of LENGTH. The default value of N is set to match the + currently loaded architecture.""" + + _cmdline_ = "pattern" + _syntax_ = f"{_cmdline_} (create|search) ARGS" + + def __init__(self) -> None: + super().__init__(prefix=True) + self["length"] = (1024, "Default length of a cyclic buffer to generate") + return + + def do_invoke(self, _: List[str]) -> None: + self.usage() + return + + +@register_command +class PatternCreateCommand(GenericCommand): + """Generate a De Bruijn Sequence of unique substrings of length N and a + total length of LENGTH. The default value of N is set to match the currently + loaded architecture.""" + + _cmdline_ = "pattern create" + _syntax_ = f"{_cmdline_} [-h] [-n N] [length]" + _example_ = f"{_cmdline_} 4096" + + @parse_arguments({"length": 0}, {("-n", "--n"): 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + length = args.length or gef.config["pattern.length"] + n = args.n or gef.arch.ptrsize + info(f"Generating a pattern of {length:d} bytes (n={n:d})") + pattern_str = gef_pystring(generate_cyclic_pattern(length, n)) + gef_print(pattern_str) + ok(f"Saved as '{gef_convenience(pattern_str)}'") + return + + +@register_command +class PatternSearchCommand(GenericCommand): + """Search a De Bruijn Sequence of unique substrings of length N and a + maximum total length of MAX_LENGTH. The default value of N is set to match + the currently loaded architecture. The PATTERN argument can be a GDB symbol + (such as a register name), a string or a hexadecimal value""" + + _cmdline_ = "pattern search" + _syntax_ = f"{_cmdline_} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]" + _example_ = (f"\n{_cmdline_} $pc" + f"\n{_cmdline_} 0x61616164" + f"\n{_cmdline_} aaab") + _aliases_ = ["pattern offset"] + + @only_if_gdb_running + @parse_arguments({"pattern": ""}, {("-n", "--n"): 0, ("-l", "--max-length"): 0}) + def do_invoke(self, _: List[str], **kwargs: Any) -> None: + args = kwargs["arguments"] + max_length = args.max_length or gef.config["pattern.length"] + n = args.n or gef.arch.ptrsize + info(f"Searching for '{args.pattern}'") + self.search(args.pattern, max_length, n) + return + + def search(self, pattern: str, size: int, period: int) -> None: + pattern_be, pattern_le = None, None + + # 1. check if it's a symbol (like "$sp" or "0x1337") + symbol = safe_parse_and_eval(pattern) + if symbol: + addr = int(symbol) + dereferenced_value = dereference(addr) + # 1-bis. try to dereference + if dereferenced_value: + addr = int(dereferenced_value) + struct_packsize = { + 2: "H", + 4: "I", + 8: "Q", + } + pattern_be = struct.pack(f">{struct_packsize[gef.arch.ptrsize]}", addr) + pattern_le = struct.pack(f"<{struct_packsize[gef.arch.ptrsize]}", addr) + else: + # 2. assume it's a plain string + pattern_be = gef_pybytes(pattern) + pattern_le = gef_pybytes(pattern[::-1]) + + cyclic_pattern = generate_cyclic_pattern(size, period) + found = False + off = cyclic_pattern.find(pattern_le) + if off >= 0: + ok(f"Found at offset {off:d} (little-endian search) " + f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}") + found = True + + off = cyclic_pattern.find(pattern_be) + if off >= 0: + ok(f"Found at offset {off:d} (big-endian search) " + f"{Color.colorify('likely', 'bold green') if gef.arch.endianness == Endianness.BIG_ENDIAN else ''}") + found = True + + if not found: + err(f"Pattern '{pattern}' not found") + return + + +@register_command +class ChecksecCommand(GenericCommand): + """Checksec the security properties of the current executable or passed as argument. The + command checks for the following protections: + - PIE + - NX + - RelRO + - Glibc Stack Canaries + - Fortify Source""" + + _cmdline_ = "checksec" + _syntax_ = f"{_cmdline_} [FILENAME]" + _example_ = f"{_cmdline_} /bin/ls" + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_FILENAME) + return + + def do_invoke(self, argv: List[str]) -> None: + argc = len(argv) + + if argc == 0: + filename = get_filepath() + if filename is None: + warn("No executable/library specified") + return + elif argc == 1: + filename = os.path.realpath(os.path.expanduser(argv[0])) + if not os.access(filename, os.R_OK): + err("Invalid filename") + return + else: + self.usage() + return + + info(f"{self._cmdline_} for '{filename}'") + self.print_security_properties(filename) + return + + def print_security_properties(self, filename: str) -> None: + sec = checksec(filename) + for prop in sec: + if prop in ("Partial RelRO", "Full RelRO"): continue + val = sec[prop] + msg = Color.greenify(Color.boldify(TICK)) if val is True else Color.redify(Color.boldify(CROSS)) + if val and prop == "Canary" and is_alive(): + canary = gef.session.canary[0] if gef.session.canary else 0 + msg += f"(value: {canary:#x})" + + gef_print(f"{prop:<30s}: {msg}") + + if sec["Full RelRO"]: + gef_print(f"{'RelRO':<30s}: {Color.greenify('Full')}") + elif sec["Partial RelRO"]: + gef_print(f"{'RelRO':<30s}: {Color.yellowify('Partial')}") + else: + gef_print(f"{'RelRO':<30s}: {Color.redify(Color.boldify(CROSS))}") + return + + +@register_command +class GotCommand(GenericCommand): + """Display current status of the got inside the process.""" + + _cmdline_ = "got" + _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] " + _example_ = "got read printf exit" + + def __init__(self): + super().__init__() + self["function_resolved"] = ("green", + "Line color of the got command output for resolved function") + self["function_not_resolved"] = ("yellow", + "Line color of the got command output for unresolved function") + return + + def get_jmp_slots(self, readelf: str, filename: str) -> List[str]: + cmd = [readelf, "--relocs", filename] + lines = gef_execute_external(cmd, as_list=True) + return [line for line in lines if "JUMP" in line] + + @only_if_gdb_running + def do_invoke(self, argv: List[str]) -> None: + try: + readelf = gef.session.constants["readelf"] + except OSError: + err("Missing `readelf`") + return + + # get the filtering parameter. + func_names_filter = [] + if argv: + func_names_filter = argv + + # getting vmmap to understand the boundaries of the main binary + # we will use this info to understand if a function has been resolved or not. + vmmap = gef.memory.maps + base_address = min(x.page_start for x in vmmap if x.path == get_filepath()) + end_address = max(x.page_end for x in vmmap if x.path == get_filepath()) + + # get the checksec output. + checksec_status = checksec(get_filepath()) + relro_status = "Full RelRO" + full_relro = checksec_status["Full RelRO"] + pie = checksec_status["PIE"] # if pie we will have offset instead of abs address. + + if not full_relro: + relro_status = "Partial RelRO" + partial_relro = checksec_status["Partial RelRO"] + + if not partial_relro: + relro_status = "No RelRO" + + # retrieve jump slots using readelf + jmpslots = self.get_jmp_slots(readelf, get_filepath()) + + gef_print(f"\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ") + + for line in jmpslots: + address, _, _, _, name = line.split()[:5] + + # if we have a filter let's skip the entries that are not requested. + if func_names_filter: + if not any(map(lambda x: x in name, func_names_filter)): + continue + + address_val = int(address, 16) + + # address_val is an offset from the base_address if we have PIE. + if pie: + address_val = base_address + address_val + + # read the address of the function. + got_address = gef.memory.read_integer(address_val) + + # for the swag: different colors if the function has been resolved or not. + if base_address < got_address < end_address: + color = self["function_not_resolved"] + else: + color = self["function_resolved"] + + line = f"[{hex(address_val)}] " + line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color) + gef_print(line) + + return + + +@register_command +class HighlightCommand(GenericCommand): + """Highlight user-defined text matches in GEF output universally.""" + _cmdline_ = "highlight" + _syntax_ = f"{_cmdline_} (add|remove|list|clear)" + _aliases_ = ["hl"] + + def __init__(self) -> None: + super().__init__(prefix=True) + self["regex"] = (False, "Enable regex highlighting") + + def do_invoke(self, _: List[str]) -> None: + return self.usage() + + +@register_command +class HighlightListCommand(GenericCommand): + """Show the current highlight table with matches to colors.""" + _cmdline_ = "highlight list" + _aliases_ = ["highlight ls", "hll"] + _syntax_ = _cmdline_ + + def print_highlight_table(self) -> None: + if not gef.ui.highlight_table: + err("no matches found") + return + + left_pad = max(map(len, gef.ui.highlight_table.keys())) + for match, color in sorted(gef.ui.highlight_table.items()): + print(f"{Color.colorify(match.ljust(left_pad), color)} {VERTICAL_LINE} " + f"{Color.colorify(color, color)}") + return + + def do_invoke(self, _: List[str]) -> None: + return self.print_highlight_table() + + +@register_command +class HighlightClearCommand(GenericCommand): + """Clear the highlight table, remove all matches.""" + _cmdline_ = "highlight clear" + _aliases_ = ["hlc"] + _syntax_ = _cmdline_ + + def do_invoke(self, _: List[str]) -> None: + return gef.ui.highlight_table.clear() + + +@register_command +class HighlightAddCommand(GenericCommand): + """Add a match to the highlight table.""" + _cmdline_ = "highlight add" + _syntax_ = f"{_cmdline_} MATCH COLOR" + _aliases_ = ["highlight set", "hla"] + _example_ = f"{_cmdline_} 41414141 yellow" + + def do_invoke(self, argv: List[str]) -> None: + if len(argv) < 2: + return self.usage() + + match, color = argv + gef.ui.highlight_table[match] = color + return + + +@register_command +class HighlightRemoveCommand(GenericCommand): + """Remove a match in the highlight table.""" + _cmdline_ = "highlight remove" + _syntax_ = f"{_cmdline_} MATCH" + _aliases_ = [ + "highlight delete", + "highlight del", + "highlight unset", + "highlight rm", + "hlr", + ] + _example_ = f"{_cmdline_} remove 41414141" + + def do_invoke(self, argv: List[str]) -> None: + if not argv: + return self.usage() + + gef.ui.highlight_table.pop(argv[0], None) + return + + +@register_command +class FormatStringSearchCommand(GenericCommand): + """Exploitable format-string helper: this command will set up specific breakpoints + at well-known dangerous functions (printf, snprintf, etc.), and check if the pointer + holding the format string is writable, and therefore susceptible to format string + attacks if an attacker can control its content.""" + _cmdline_ = "format-string-helper" + _syntax_ = _cmdline_ + _aliases_ = ["fmtstr-helper",] + + def do_invoke(self, _: List[str]) -> None: + dangerous_functions = { + "printf": 0, + "sprintf": 1, + "fprintf": 1, + "snprintf": 2, + "vsnprintf": 2, + } + + nb_installed_breaks = 0 + + with RedirectOutputContext(to="/dev/null"): + for function_name in dangerous_functions: + argument_number = dangerous_functions[function_name] + FormatStringBreakpoint(function_name, argument_number) + nb_installed_breaks += 1 + + ok(f"Enabled {nb_installed_breaks} FormatString " + f"breakpoint{'s' if nb_installed_breaks > 1 else ''}") + return + + +@register_command +class HeapAnalysisCommand(GenericCommand): + """Heap vulnerability analysis helper: this command aims to track dynamic heap allocation + done through malloc()/free() to provide some insights on possible heap vulnerabilities. The + following vulnerabilities are checked: + - NULL free + - Use-after-Free + - Double Free + - Heap overlap""" + _cmdline_ = "heap-analysis-helper" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__(complete=gdb.COMPLETE_NONE) + self["check_free_null"] = (False, "Break execution when a free(NULL) is encountered") + self["check_double_free"] = (True, "Break execution when a double free is encountered") + self["check_weird_free"] = (True, "Break execution when free() is called against a non-tracked pointer") + self["check_uaf"] = (True, "Break execution when a possible Use-after-Free condition is found") + self["check_heap_overlap"] = (True, "Break execution when a possible overlap in allocation is found") + + self.bp_malloc = None + self.bp_calloc = None + self.bp_free = None + self.bp_realloc = None + return + + @only_if_gdb_running + @experimental_feature + def do_invoke(self, argv: List[str]) -> None: + if not argv: + self.setup() + return + + if argv[0] == "show": + self.dump_tracked_allocations() + return + + def setup(self) -> None: + ok("Tracking malloc() & calloc()") + self.bp_malloc = TraceMallocBreakpoint("__libc_malloc") + self.bp_calloc = TraceMallocBreakpoint("__libc_calloc") + ok("Tracking free()") + self.bp_free = TraceFreeBreakpoint() + ok("Tracking realloc()") + self.bp_realloc = TraceReallocBreakpoint() + + ok("Disabling hardware watchpoints (this may increase the latency)") + gdb.execute("set can-use-hw-watchpoints 0") + + info("Dynamic breakpoints correctly setup, " + "GEF will break execution if a possible vulnerabity is found.") + warn(f"{Color.colorify('Note', 'bold underline yellow')}: " + "The heap analysis slows down the execution noticeably.") + + # when inferior quits, we need to clean everything for a next execution + gef_on_exit_hook(self.clean) + return + + def dump_tracked_allocations(self) -> None: + global gef + + if gef.session.heap_allocated_chunks: + ok("Tracked as in-use chunks:") + for addr, sz in gef.session.heap_allocated_chunks: + gef_print(f"{CROSS} malloc({sz:d}) = {addr:#x}") + else: + ok("No malloc() chunk tracked") + + if gef.session.heap_freed_chunks: + ok("Tracked as free-ed chunks:") + for addr, sz in gef.session.heap_freed_chunks: + gef_print(f"{TICK} free({sz:d}) = {addr:#x}") + else: + ok("No free() chunk tracked") + return + + def clean(self, _: "gdb.Event") -> None: + global gef + + ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up") + for bp in [self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc]: + if hasattr(bp, "retbp") and bp.retbp: + try: + bp.retbp.delete() + except RuntimeError: + # in some cases, gdb was found failing to correctly remove the retbp + # but they can be safely ignored since the debugging session is over + pass + + bp.delete() + + for wp in gef.session.heap_uaf_watchpoints: + wp.delete() + + gef.session.heap_allocated_chunks = [] + gef.session.heap_freed_chunks = [] + gef.session.heap_uaf_watchpoints = [] + + ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Re-enabling hardware watchpoints") + gdb.execute("set can-use-hw-watchpoints 1") + + gef_on_exit_unhook(self.clean) + return + + +@register_command +class IsSyscallCommand(GenericCommand): + """Tells whether the next instruction is a system call.""" + _cmdline_ = "is-syscall" + _syntax_ = _cmdline_ + + def do_invoke(self, _: List[str]) -> None: + insn = gef_current_instruction(gef.arch.pc) + ok(f"Current instruction is{' ' if self.is_syscall(gef.arch, insn) else ' not '}a syscall") + + return + + def is_syscall(self, arch: Architecture, instruction: Instruction) -> bool: + insn_str = instruction.mnemonic + " " + ", ".join(instruction.operands) + return insn_str.strip() in arch.syscall_instructions + + +@register_command +class SyscallArgsCommand(GenericCommand): + """Gets the syscall name and arguments based on the register values in the current state.""" + _cmdline_ = "syscall-args" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__() + path = pathlib.Path(gef.config["gef.tempdir"]) / "syscall-tables" + self["path"] = (str(path.absolute()), "Path to store/load the syscall tables files") + return + + def do_invoke(self, _: List[str]) -> None: + path = self.get_settings_path() + if not path: + err(f"Cannot open '{self['path']}': check directory and/or " + "`gef config syscall-args.path` setting.") + return + + color = gef.config["theme.table_heading"] + arch = gef.arch.__class__.__name__ + syscall_table = self.get_syscall_table(arch) + + reg_value = gef.arch.register(gef.arch.syscall_register) + if reg_value not in syscall_table: + warn(f"There is no system call for {reg_value:#x}") + return + syscall_entry = syscall_table[reg_value] + + values = [] + for param in syscall_entry.params: + values.append(gef.arch.register(param.reg)) + + parameters = [s.param for s in syscall_entry.params] + registers = [s.reg for s in syscall_entry.params] + + info(f"Detected syscall {Color.colorify(syscall_entry.name, color)}") + gef_print(f" {syscall_entry.name}({', '.join(parameters)})") + + headers = ["Parameter", "Register", "Value"] + param_names = [re.split(r" |\*", p)[-1] for p in parameters] + info(Color.colorify("{:<20} {:<20} {}".format(*headers), color)) + for name, register, value in zip(param_names, registers, values): + line = f" {name:<20} {register:<20} {value:#x}" + + addrs = dereference_from(value) + + if len(addrs) > 1: + sep = f" {RIGHT_ARROW} " + line += sep + line += sep.join(addrs[1:]) + + gef_print(line) + + return + + def get_syscall_table(self, modname: str) -> Dict[str, Any]: + _mod = self.get_module(modname) + return getattr(_mod, "syscall_table") + + def get_module(self, modname: str) -> Any: + _fullname = self.get_filepath(modname).absolute() + return importlib.machinery.SourceFileLoader(modname, _fullname).load_module(None) + + def get_filepath(self, x: str) -> Optional[pathlib.Path]: + p = self.get_settings_path() + if not p: return None + return p / f"{x}.py" + + def get_settings_path(self) -> Optional[pathlib.Path]: + path = pathlib.Path(self["path"]).expanduser() + return path if path.is_dir() else None + + +# +# GDB Function declaration +# +class GenericFunction(gdb.Function, metaclass=abc.ABCMeta): + """This is an abstract class for invoking convenience functions, should not be instantiated.""" + + _example_ = "" + + @abc.abstractproperty + def _function_(self) -> str: pass + + @property + def _syntax_(self) -> str: + return f"${self._function_}([offset])" + + def __init__(self) -> None: + super().__init__(self._function_) + + def invoke(self, *args: Any) -> int: + if not is_alive(): + raise gdb.GdbError("No debugging session active") + return self.do_invoke(args) + + def arg_to_long(self, args: List, index: int, default: int = 0) -> int: + try: + addr = args[index] + return int(addr) if addr.address is None else int(addr.address) + except IndexError: + return default + + @abc.abstractmethod + def do_invoke(self, args: List) -> int: pass + + +@register_function +class StackOffsetFunction(GenericFunction): + """Return the current stack base address plus an optional offset.""" + _function_ = "_stack" + + def do_invoke(self, args: List) -> int: + base = get_section_base_address("[stack]") + if not base: + raise gdb.GdbError("Stack not found") + + return self.arg_to_long(args, 0) + base + + +@register_function +class HeapBaseFunction(GenericFunction): + """Return the current heap base address plus an optional offset.""" + _function_ = "_heap" + + def do_invoke(self, args: List) -> int: + base = gef.heap.base_address + if not base: + base = get_section_base_address("[heap]") + if not base: + raise gdb.GdbError("Heap not found") + return self.arg_to_long(args, 0) + base + + +@register_function +class SectionBaseFunction(GenericFunction): + """Return the matching file's base address plus an optional offset. + Defaults to current file. Note that quotes need to be escaped""" + _function_ = "_base" + _syntax_ = "$_base([filepath])" + _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")" + + def do_invoke(self, args: List) -> int: + try: + name = args[0].string() + except IndexError: + name = gef.session.file.name + except gdb.error: + err(f"Invalid arg: {args[0]}") + return 0 + + try: + addr = int(get_section_base_address(name)) + except TypeError: + err(f"Cannot find section {name}") + return 0 + return addr + + +@register_function +class BssBaseFunction(GenericFunction): + """Return the current bss base address plus the given offset.""" + _function_ = "_bss" + _example_ = "deref $_bss(0x20)" + + def do_invoke(self, args: List) -> int: + base = get_zone_base_address(".bss") + if not base: + raise gdb.GdbError("BSS not found") + return self.arg_to_long(args, 0) + base + + +@register_function +class GotBaseFunction(GenericFunction): + """Return the current GOT base address plus the given offset.""" + _function_ = "_got" + _example_ = "deref $_got(0x20)" + + def do_invoke(self, args: List) -> int: + base = get_zone_base_address(".got") + if not base: + raise gdb.GdbError("GOT not found") + return base + self.arg_to_long(args, 0) + + +@register_command +class GefFunctionsCommand(GenericCommand): + """List the convenience functions provided by GEF.""" + _cmdline_ = "functions" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__() + self.docs = [] + self.setup() + return + + def setup(self) -> None: + global gef + for function in gef.gdb.loaded_functions: + self.add_function_to_doc(function) + self.__doc__ = "\n".join(sorted(self.docs)) + return + + def add_function_to_doc(self, function) -> None: + """Add function to documentation.""" + doc = getattr(function, "__doc__", "").lstrip() + doc = "\n ".join(doc.split("\n")) + syntax = getattr(function, "_syntax_", "").lstrip() + msg = f"{syntax:<25s} -- {Color.greenify(doc)}" + example = getattr(function, "_example_", "").strip() + if example: + msg += f"\n {'':27s} example: {Color.yellowify(example)}" + self.docs.append(msg) + return + + def do_invoke(self, argv) -> None: + self.dont_repeat() + gef_print(titlify("GEF - Convenience Functions")) + gef_print("These functions can be used as arguments to other " + "commands to dynamically calculate values\n") + gef_print(self.__doc__) + return + + +# +# GEF internal command classes +# +class GefCommand(gdb.Command): + """GEF main command: view all new commands by typing `gef`.""" + + _cmdline_ = "gef" + _syntax_ = f"{_cmdline_} (missing|config|save|restore|set|run)" + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, True) + gef.config["gef.follow_child"] = GefSetting(True, bool, "Automatically set GDB to follow child when forking") + gef.config["gef.readline_compat"] = GefSetting(False, bool, "Workaround for readline SOH/ETX issue (SEGV)") + gef.config["gef.debug"] = GefSetting(False, bool, "Enable debug mode for gef") + gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints") + gef.config["gef.extra_plugins_dir"] = GefSetting("", str, "Autoload additional GEF commands from external directory") + gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF") + gef.config["gef.tempdir"] = GefSetting(GEF_TEMP_DIR, str, "Directory to use for temporary/cache content") + gef.config["gef.show_deprecation_warnings"] = GefSetting(True, bool, "Toggle the display of the `deprecated` warnings") + self.loaded_commands: List[Tuple[str, Type[GenericCommand], Any]] = [] + self.loaded_functions: List[Type[GenericFunction]] = [] + self.missing_commands: Dict[str, Exception] = {} + return + + def setup(self) -> None: + self.load(initial=True) + # loading GEF sub-commands + self.doc = GefHelpCommand(self.loaded_commands) + self.cfg = GefConfigCommand(self.loaded_command_names) + GefSaveCommand() + GefRestoreCommand() + GefMissingCommand() + GefSetCommand() + GefRunCommand() + + # load the saved settings + gdb.execute("gef restore") + + # restore the autosave/autoreload breakpoints policy (if any) + self.__reload_auto_breakpoints() + + # load plugins from `extra_plugins_dir` + if self.__load_extra_plugins() > 0: + # if here, at least one extra plugin was loaded, so we need to restore + # the settings once more + gdb.execute("gef restore quiet") + return + + def __reload_auto_breakpoints(self) -> None: + bkp_fname = gef.config["gef.autosave_breakpoints_file"] + bkp_fname = bkp_fname[0] if bkp_fname else None + if bkp_fname: + # restore if existing + if os.access(bkp_fname, os.R_OK): + gdb.execute(f"source {bkp_fname}") + + # add hook for autosave breakpoints on quit command + source = [ + "define hook-quit", + f" save breakpoints {bkp_fname}", + "end", + ] + gef_execute_gdb_script("\n".join(source) + "\n") + return + + def __load_extra_plugins(self) -> int: + nb_added = -1 + try: + nb_inital = len(self.loaded_commands) + directories = gef.config["gef.extra_plugins_dir"] + if directories: + for directory in directories.split(";"): + directory = os.path.realpath(os.path.expanduser(directory)) + if os.path.isdir(directory): + sys.path.append(directory) + for fname in os.listdir(directory): + if not fname.endswith(".py"): continue + fpath = f"{directory}/{fname}" + if os.path.isfile(fpath): + gdb.execute(f"source {fpath}") + nb_added = len(self.loaded_commands) - nb_inital + if nb_added > 0: + ok(f"{Color.colorify(nb_added, 'bold green')} extra commands added from " + f"'{Color.colorify(directories, 'bold blue')}'") + except gdb.error as e: + err(f"failed: {e}") + return nb_added + + @property + def loaded_command_names(self) -> List[str]: + return [x[0] for x in self.loaded_commands] + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + gdb.execute("gef help") + return + + def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable) -> None: + """Add a new context pane to ContextCommand.""" + for _, _, class_instance in self.loaded_commands: + if isinstance(class_instance, ContextCommand): + context = class_instance + break + else: + err("Cannot find ContextCommand") + return + + # assure users can toggle the new context + corrected_settings_name = pane_name.replace(" ", "_") + gef.config["context.layout"] += f" {corrected_settings_name}" + + # overload the printing of pane title + context.layout_mapping[corrected_settings_name] = (display_pane_function, pane_title_function) + + def load(self, initial: bool = False) -> None: + """Load all the commands and functions defined by GEF into GDB.""" + nb_missing = 0 + self.commands = [(x._cmdline_, x) for x in __registered_commands__] + + # load all of the functions + for function_class_name in __registered_functions__: + self.loaded_functions.append(function_class_name()) + + def is_loaded(x: str) -> bool: + return any(u for u in self.loaded_commands if x == u[0]) + + for cmd, class_obj in self.commands: + if is_loaded(cmd): + continue + + try: + self.loaded_commands.append((cmd, class_obj, class_obj())) + + if hasattr(class_obj, "_aliases_"): + aliases = getattr(class_obj, "_aliases_") + for alias in aliases: + GefAlias(alias, cmd) + + except Exception as reason: + self.missing_commands[cmd] = reason + nb_missing += 1 + + # sort by command name + self.loaded_commands = sorted(self.loaded_commands, key=lambda x: x[1]._cmdline_) + + if initial: + gef_print(f"{Color.greenify('GEF')} for {gef.session.os} ready, " + f"type `{Color.colorify('gef', 'underline yellow')}' to start, " + f"`{Color.colorify('gef config', 'underline pink')}' to configure") + + ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}" + nb_cmds = len(self.loaded_commands) + gef_print(f"{Color.colorify(nb_cmds, 'bold green')} commands loaded for " + f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} " + f"using Python engine {Color.colorify(ver, 'bold red')}") + + if nb_missing: + warn(f"{Color.colorify(nb_missing, 'bold red')} " + f"command{'s' if nb_missing > 1 else ''} could not be loaded, " + f"run `{Color.colorify('gef missing', 'underline pink')}` to know why.") + return + + +class GefHelpCommand(gdb.Command): + """GEF help sub-command.""" + _cmdline_ = "gef help" + _syntax_ = _cmdline_ + + def __init__(self, commands: List[Tuple[str, Any, Any]]) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) + self.docs = [] + self.generate_help(commands) + self.refresh() + return + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + gef_print(titlify("GEF - GDB Enhanced Features")) + gef_print(self.__doc__ or "") + return + + def generate_help(self, commands: List[Tuple[str, Type[GenericCommand], Any]]) -> None: + """Generate builtin commands documentation.""" + for command in commands: + self.add_command_to_doc(command) + return + + def add_command_to_doc(self, command: Tuple[str, Type[GenericCommand], Any]) -> None: + """Add command to GEF documentation.""" + cmd, class_obj, _ = command + if " " in cmd: + # do not print subcommands in gef help + return + doc = getattr(class_obj, "__doc__", "").lstrip() + doc = "\n ".join(doc.split("\n")) + aliases = f" (alias: {', '.join(class_obj._aliases_)})" if hasattr(class_obj, "_aliases_") else "" + msg = f"{cmd:<25s} -- {doc}{aliases}" + self.docs.append(msg) + return + + def refresh(self) -> None: + """Refresh the documentation.""" + self.__doc__ = "\n".join(sorted(self.docs)) + return + + +class GefConfigCommand(gdb.Command): + """GEF configuration sub-command + This command will help set/view GEF settings for the current debugging session. + It is possible to make those changes permanent by running `gef save` (refer + to this command help), and/or restore previously saved settings by running + `gef restore` (refer help). + """ + _cmdline_ = "gef config" + _syntax_ = f"{_cmdline_} [setting_name] [setting_value]" + + def __init__(self, loaded_commands: List[str]) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False) + self.loaded_commands = loaded_commands + return + + def invoke(self, args: str, from_tty: bool) -> None: + self.dont_repeat() + argv = gdb.string_to_argv(args) + argc = len(argv) + + if not (0 <= argc <= 2): + err("Invalid number of arguments") + return + + if argc == 0: + gef_print(titlify("GEF configuration settings")) + self.print_settings() + return + + if argc == 1: + prefix = argv[0] + names = [x for x in gef.config.keys() if x.startswith(prefix)] + if names: + if len(names) == 1: + gef_print(titlify(f"GEF configuration setting: {names[0]}")) + self.print_setting(names[0], verbose=True) + else: + gef_print(titlify(f"GEF configuration settings matching '{argv[0]}'")) + for name in names: self.print_setting(name) + return + + self.set_setting(argv) + return + + def print_setting(self, plugin_name: str, verbose: bool = False) -> None: + res = gef.config.raw_entry(plugin_name) + string_color = gef.config["theme.dereference_string"] + misc_color = gef.config["theme.dereference_base_address"] + + if not res: + return + + _setting = Color.colorify(plugin_name, "green") + _type = res.type.__name__ + if _type == "str": + _value = f'"{Color.colorify(res.value, string_color)}"' + else: + _value = Color.colorify(res.value, misc_color) + + gef_print(f"{_setting} ({_type}) = {_value}") + + if verbose: + gef_print(Color.colorify("\nDescription:", "bold underline")) + gef_print(f"\t{res.description}") + return + + def print_settings(self) -> None: + for x in sorted(gef.config): + self.print_setting(x) + return + + def set_setting(self, argv: Tuple[str, Any]) -> None: + global gef + key, new_value = argv + + if "." not in key: + err("Invalid command format") + return + + loaded_commands = [ x[0] for x in gef.gdb.loaded_commands ] + ["gef"] + plugin_name = key.split(".", 1)[0] + if plugin_name not in loaded_commands: + err(f"Unknown plugin '{plugin_name}'") + return + + if key not in gef.config: + err(f"'{key}' is not a valid configuration setting") + return + + _type = gef.config.raw_entry(key).type + try: + if _type == bool: + _newval = True if new_value.upper() in ("TRUE", "T", "1") else False + else: + _newval = new_value + + gef.config[key] = _newval + except Exception: + err(f"{key} expects type '{_type.__name__}'") + return + + reset_all_caches() + return + + def complete(self, text: str, word: str) -> List[str]: + settings = sorted(gef.config) + + if text == "": + # no prefix: example: `gef config TAB` + return [s for s in settings if word in s] + + if "." not in text: + # if looking for possible prefix + return [s for s in settings if s.startswith(text.strip())] + + # finally, look for possible values for given prefix + return [s.split(".", 1)[1] for s in settings if s.startswith(text.strip())] + + +class GefSaveCommand(gdb.Command): + """GEF save sub-command. + Saves the current configuration of GEF to disk (by default in file '~/.gef.rc').""" + _cmdline_ = "gef save" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) + return + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + cfg = configparser.RawConfigParser() + old_sect = None + + # save the configuration + for key in sorted(gef.config): + sect, optname = key.split(".", 1) + value = gef.config[key] + + if old_sect != sect: + cfg.add_section(sect) + old_sect = sect + + cfg.set(sect, optname, value) + + # save the aliases + cfg.add_section("aliases") + for alias in gef.session.aliases: + cfg.set("aliases", alias._alias, alias._command) + + with GEF_RC.open("w") as fd: + cfg.write(fd) + + ok(f"Configuration saved to '{GEF_RC}'") + return + + +class GefRestoreCommand(gdb.Command): + """GEF restore sub-command. + Loads settings from file '~/.gef.rc' and apply them to the configuration of GEF.""" + _cmdline_ = "gef restore" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) + return + + def invoke(self, args: str, from_tty: bool) -> None: + self.dont_repeat() + if not os.access(GEF_RC, os.R_OK): + return + + quiet = args.lower() == "quiet" + cfg = configparser.ConfigParser() + cfg.read(GEF_RC) + + for section in cfg.sections(): + if section == "aliases": + # load the aliases + for key in cfg.options(section): + try: + GefAlias(key, cfg.get(section, key)) + except: + pass + continue + + # load the other options + for optname in cfg.options(section): + key = f"{section}.{optname}" + try: + setting = gef.config.raw_entry(key) + except Exception as e: + warn(f"Invalid setting '{key}': {e}") + continue + new_value = cfg.get(section, optname) + if setting.type == bool: + new_value = True if new_value.upper() in ("TRUE", "T", "1") else False + setting.value = setting.type(new_value) + + # ensure that the temporary directory always exists + gef_makedirs(gef.config["gef.tempdir"]) + + if not quiet: + ok(f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored") + return + + +class GefMissingCommand(gdb.Command): + """GEF missing sub-command + Display the GEF commands that could not be loaded, along with the reason of why + they could not be loaded. + """ + _cmdline_ = "gef missing" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False) + return + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + missing_commands = gef.gdb.missing_commands.keys() + if not missing_commands: + ok("No missing command") + return + + for missing_command in missing_commands: + reason = gef.gdb.missing_commands[missing_command] + warn(f"Command `{missing_command}` is missing, reason {RIGHT_ARROW} {reason}") + return + + +class GefSetCommand(gdb.Command): + """Override GDB set commands with the context from GEF.""" + _cmdline_ = "gef set" + _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]" + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False) + return + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + args = args.split() + cmd = ["set", args[0],] + for p in args[1:]: + if p.startswith("$_gef"): + c = gdb.parse_and_eval(p) + cmd.append(c.string()) + else: + cmd.append(p) + + gdb.execute(" ".join(cmd)) + return + + +class GefRunCommand(gdb.Command): + """Override GDB run commands with the context from GEF. + Simple wrapper for GDB run command to use arguments set from `gef set args`.""" + _cmdline_ = "gef run" + _syntax_ = f"{_cmdline_} [GDB_RUN_ARGUMENTS]" + + def __init__(self) -> None: + super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False) + return + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + if is_alive(): + gdb.execute("continue") + return + + argv = args.split() + gdb.execute(f"gef set args {' '.join(argv)}") + gdb.execute("run") + return + + +class GefAlias(gdb.Command): + """Simple aliasing wrapper because GDB doesn't do what it should.""" + + def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE_NONE, command_class: int = gdb.COMMAND_NONE) -> None: + p = command.split() + if not p: + return + + if any(x for x in gef.session.aliases if x._alias == alias): + return + + self._command = command + self._alias = alias + c = command.split()[0] + r = self.lookup_command(c) + self.__doc__ = f"Alias for '{Color.greenify(command)}'" + if r is not None: + _instance = r[2] + self.__doc__ += f": {_instance.__doc__}" + + if hasattr(_instance, "complete"): + self.complete = _instance.complete + + super().__init__(alias, command_class, completer_class=completer_class) + gef.session.aliases.append(self) + return + + def invoke(self, args: Any, from_tty: bool) -> None: + gdb.execute(f"{self._command} {args}", from_tty=from_tty) + return + + def lookup_command(self, cmd: str) -> Optional[Tuple[str, Type, Any]]: + global gef + for _name, _class, _instance in gef.gdb.loaded_commands: + if cmd == _name: + return _name, _class, _instance + + return None + + +@register_command +class AliasesCommand(GenericCommand): + """Base command to add, remove, or list aliases.""" + + _cmdline_ = "aliases" + _syntax_ = f"{_cmdline_} (add|rm|ls)" + + def __init__(self) -> None: + super().__init__(prefix=True) + return + + def do_invoke(self, _: List[str]) -> None: + self.usage() + return + + +@register_command +class AliasesAddCommand(AliasesCommand): + """Command to add aliases.""" + + _cmdline_ = "aliases add" + _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]" + _example_ = f"{_cmdline_} scope telescope" + + def __init__(self) -> None: + super().__init__() + return + + def do_invoke(self, argv: List[str]) -> None: + if len(argv) < 2: + self.usage() + return + GefAlias(argv[0], " ".join(argv[1:])) + return + + +@register_command +class AliasesRmCommand(AliasesCommand): + """Command to remove aliases.""" + + _cmdline_ = "aliases rm" + _syntax_ = f"{_cmdline_} [ALIAS]" + + def __init__(self) -> None: + super().__init__() + return + + def do_invoke(self, argv: List[str]) -> None: + global gef + if len(argv) != 1: + self.usage() + return + try: + alias_to_remove = next(filter(lambda x: x._alias == argv[0], gef.session.aliases)) + gef.session.aliases.remove(alias_to_remove) + except (ValueError, StopIteration): + err(f"{argv[0]} not found in aliases.") + return + gef_print("You must reload GEF for alias removals to apply.") + return + + +@register_command +class AliasesListCommand(AliasesCommand): + """Command to list aliases.""" + + _cmdline_ = "aliases ls" + _syntax_ = _cmdline_ + + def __init__(self) -> None: + super().__init__() + return + + def do_invoke(self, _: List[str]) -> None: + ok("Aliases defined:") + for a in gef.session.aliases: + gef_print(f"{a._alias:30s} {RIGHT_ARROW} {a._command}") + return + + +class GefTmuxSetup(gdb.Command): + """Setup a confortable tmux debugging environment.""" + + def __init__(self) -> None: + super().__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE) + GefAlias("screen-setup", "tmux-setup") + return + + def invoke(self, args: Any, from_tty: bool) -> None: + self.dont_repeat() + + tmux = os.getenv("TMUX") + if tmux: + self.tmux_setup() + return + + screen = os.getenv("TERM") + if screen is not None and screen == "screen": + self.screen_setup() + return + + warn("Not in a tmux/screen session") + return + + def tmux_setup(self) -> None: + """Prepare the tmux environment by vertically splitting the current pane, and + forcing the context to be redirected there.""" + tmux = which("tmux") + ok("tmux session found, splitting window...") + old_ptses = set(os.listdir("/dev/pts")) + gdb.execute(f"! {tmux} split-window -h 'clear ; cat'") + gdb.execute(f"! {tmux} select-pane -L") + new_ptses = set(os.listdir("/dev/pts")) + pty = list(new_ptses - old_ptses)[0] + pty = f"/dev/pts/{pty}" + ok(f"Setting `context.redirect` to '{pty}'...") + gdb.execute(f"gef config context.redirect {pty}") + ok("Done!") + return + + def screen_setup(self) -> None: + """Hackish equivalent of the tmux_setup() function for screen.""" + screen = which("screen") + sty = os.getenv("STY") + ok("screen session found, splitting window...") + fd_script, script_path = tempfile.mkstemp() + fd_tty, tty_path = tempfile.mkstemp() + os.close(fd_tty) + + with os.fdopen(fd_script, "w") as f: + f.write("startup_message off\n") + f.write("split -v\n") + f.write("focus right\n") + f.write(f"screen bash -c 'tty > {tty_path}; clear; cat'\n") + f.write("focus left\n") + + gdb.execute(f"! {screen} -r {sty} -m -d -X source {script_path}") + # artificial delay to make sure `tty_path` is populated + time.sleep(0.25) + with open(tty_path, "r") as f: + pty = f.read().strip() + ok(f"Setting `context.redirect` to '{pty}'...") + gdb.execute(f"gef config context.redirect {pty}") + ok("Done!") + os.unlink(script_path) + os.unlink(tty_path) + return + + +# +# GEF internal classes +# + +def __gef_prompt__(current_prompt: Any) -> str: + """GEF custom prompt function.""" + + if gef.config["gef.readline_compat"] is True: return GEF_PROMPT + if gef.config["gef.disable_color"] is True: return GEF_PROMPT + if is_alive(): return GEF_PROMPT_ON + return GEF_PROMPT_OFF + + +class GefManager(metaclass=abc.ABCMeta): + def reset_caches(self) -> None: + """Reset the LRU-cached attributes""" + for attr in dir(self): + try: + obj = getattr(self, attr) + if not hasattr(obj, "cache_clear"): + continue + obj.cache_clear() + except: # we're reseting the cache here, we don't care if (or which) exception triggers + continue + return + + +class GefMemoryManager(GefManager): + """Class that manages memory access for gef.""" + def __init__(self) -> None: + self.reset_caches() + return + + def reset_caches(self) -> None: + super().reset_caches() + self.__maps = None + return + + def write(self, address: int, buffer: ByteString, length: int = 0x10) -> None: + """Write `buffer` at address `address`.""" + gdb.selected_inferior().write_memory(address, buffer, length) + + def read(self, addr: int, length: int = 0x10) -> bytes: + """Return a `length` long byte array with the copy of the process memory at `addr`.""" + return gdb.selected_inferior().read_memory(addr, length).tobytes() + + def read_integer(self, addr: int) -> int: + """Return an integer read from memory.""" + sz = gef.arch.ptrsize + mem = self.read(addr, sz) + unpack = u32 if sz == 4 else u64 + return unpack(mem) + + def read_cstring(self, + address: int, + max_length: int = GEF_MAX_STRING_LENGTH, + encoding: Optional[str] = None) -> str: + """Return a C-string read from memory.""" + encoding = encoding or "unicode-escape" + length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1) + + try: + res_bytes = self.read(address, length) + except gdb.error: + err(f"Can't read memory at '{address}'") + return "" + try: + with warnings.catch_warnings(): + # ignore DeprecationWarnings (see #735) + warnings.simplefilter("ignore") + res = res_bytes.decode(encoding, "strict") + except UnicodeDecodeError: + # latin-1 as fallback due to its single-byte to glyph mapping + res = res_bytes.decode("latin-1", "replace") + + res = res.split("\x00", 1)[0] + ustr = res.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") + if max_length and len(res) > max_length: + return f"{ustr[:max_length]}[...]" + return ustr + + def read_ascii_string(self, address: int) -> Optional[str]: + """Read an ASCII string from memory""" + cstr = self.read_cstring(address) + if isinstance(cstr, str) and cstr and all(x in string.printable for x in cstr): + return cstr + return None + + @property + def maps(self) -> List[Section]: + if not self.__maps: + self.__maps = self.__parse_maps() + return self.__maps + + def __parse_maps(self) -> List[Section]: + """Return the mapped memory sections""" + try: + return list(self.__parse_procfs_maps()) + except FileNotFoundError: + return list(self.__parse_gdb_info_sections()) + + def __parse_procfs_maps(self) -> Generator[Section, None, None]: + """Get the memory mapping from procfs.""" + def open_file(path: str, use_cache: bool = False) -> IO: + """Attempt to open the given file, if remote debugging is active, download + it first to the mirror in /tmp/.""" + if is_remote_debug() and not gef.session.qemu_mode: + lpath = download_file(path, use_cache) + if not lpath: + raise IOError(f"cannot open remote path {path}") + path = lpath + return open(path, "r") + + __process_map_file = f"/proc/{gef.session.pid}/maps" + with open_file(__process_map_file, use_cache=False) as fd: + for line in fd: + line = line.strip() + addr, perm, off, _, rest = line.split(" ", 4) + rest = rest.split(" ", 1) + if len(rest) == 1: + inode = rest[0] + pathname = "" + else: + inode = rest[0] + pathname = rest[1].lstrip() + + addr_start, addr_end = [int(x, 16) for x in addr.split("-")] + off = int(off, 16) + perm = Permission.from_process_maps(perm) + inode = int(inode) + yield Section(page_start=addr_start, + page_end=addr_end, + offset=off, + permission=perm, + inode=inode, + path=pathname) + return + + def __parse_gdb_info_sections(self) -> Generator[Section, None, None]: + """Get the memory mapping from GDB's command `maintenance info sections` (limited info).""" + stream = StringIO(gdb.execute("maintenance info sections", to_string=True)) + + for line in stream: + if not line: + break + + try: + parts = [x for x in line.split()] + addr_start, addr_end = [int(x, 16) for x in parts[1].split("->")] + off = int(parts[3][:-1], 16) + path = parts[4] + perm = Permission.from_info_sections(parts[5:]) + yield Section( + page_start=addr_start, + page_end=addr_end, + offset=off, + permission=perm, + inode="", + path=path + ) + + except IndexError: + continue + except ValueError: + continue + return + + +class GefHeapManager(GefManager): + """Class managing session heap.""" + def __init__(self) -> None: + self.reset_caches() + return + + def reset_caches(self) -> None: + self.__libc_main_arena: Optional[GlibcArena] = None + self.__libc_selected_arena: Optional[GlibcArena] = None + self.__heap_base = None + return + + @property + def main_arena(self) -> Optional[GlibcArena]: + if not self.__libc_main_arena: + try: + __main_arena_addr = search_for_main_arena() + self.__libc_main_arena = GlibcArena(f"&{__main_arena_addr:#x}") + # the initialization of `main_arena` also defined `selected_arena`, so + # by default, `main_arena` == `selected_arena` + self.selected_arena = self.__libc_main_arena + except: + # the search for arena can fail when the session is not started + pass + return self.__libc_main_arena + + @property + def selected_arena(self) -> Optional[GlibcArena]: + if not self.__libc_selected_arena: + # `selected_arena` must default to `main_arena` + self.__libc_selected_arena = self.__libc_main_arena + return self.__libc_selected_arena + + @selected_arena.setter + def selected_arena(self, value: GlibcArena) -> None: + self.__libc_selected_arena = value + return + + @property + def arenas(self) -> Union[List, Iterator[GlibcArena]]: + if not self.main_arena: + return [] + return iter(self.main_arena) + + @property + def base_address(self) -> Optional[int]: + if not self.__heap_base: + base = 0 + try: + base = parse_address("mp_->sbrk_base") + except gdb.error: + # missing symbol, try again + base = 0 + if not base: + base = get_section_base_address("[heap]") + self.__heap_base = base + return self.__heap_base + + @property + def chunks(self) -> Union[List, Iterator]: + if not self.base_address: + return [] + return iter(GlibcChunk(self.base_address, from_base=True)) + + +class GefSetting: + """Basic class for storing gef settings as objects""" + def __init__(self, value: Any, cls: Optional[type] = None, description: Optional[str] = None) -> None: + self.value = value + self.type = cls or type(value) + self.description = description or "" + return + + +class GefSettingsManager(dict): + """ + GefSettings acts as a dict where the global settings are stored and can be read, written or deleted as any other dict. + For instance, to read a specific command setting: `gef.config[mycommand.mysetting]` + """ + def __getitem__(self, name: str) -> Any: + return dict.__getitem__(self, name).value + + def __setitem__(self, name: str, value: Any) -> None: + # check if the key exists + if dict.__contains__(self, name): + # if so, update its value directly + setting = dict.__getitem__(self, name) + setting.value = setting.type(value) + dict.__setitem__(self, name, setting) + else: + # if not, `value` must be a GefSetting + if not isinstance(value, GefSetting): raise Exception("Invalid argument") + if not value.type: raise Exception("Invalid type") + if not value.description: raise Exception("Invalid description") + dict.__setitem__(self, name, value) + return + + def __delitem__(self, name: str) -> None: + dict.__delitem__(self, name) + return + + def raw_entry(self, name: str) -> GefSetting: + return dict.__getitem__(self, name) + + +class GefSessionManager(GefManager): + """Class managing the runtime properties of GEF. """ + def __init__(self) -> None: + self.reset_caches() + self.remote = None + self.qemu_mode = False + self.convenience_vars_index = 0 + self.heap_allocated_chunks: List[Tuple[int, int]] = [] + self.heap_freed_chunks: List[Tuple[int, int]] = [] + self.heap_uaf_watchpoints: List[UafWatchpoint] = [] + self.pie_breakpoints: Dict[int, PieVirtualBreakpoint] = {} + self.pie_counter = 1 + self.aliases: List[GefAlias] = [] + self.constants = {} # a dict for runtime constants (like 3rd party file paths) + # add a few extra runtime constants to avoid lookups + # those must be found, otherwise IOError will be raised + for constant in ("python3", "readelf", "file", "ps"): + self.constants[constant] = which(constant) + return + + def reset_caches(self) -> None: + super().reset_caches() + self.__auxiliary_vector = None + self.__pagesize = None + self.__os = None + self.__pid = None + self.__file = None + self.__canary = None + return + + @property + def auxiliary_vector(self) -> Optional[Dict[str, int]]: + if not is_alive(): + return None + + if not self.__auxiliary_vector: + auxiliary_vector = {} + auxv_info = gdb.execute("info auxv", to_string=True) + if "failed" in auxv_info: + err(auxv_info) # print GDB error + return None + for line in auxv_info.splitlines(): + line = line.split('"')[0].strip() # remove the ending string (if any) + line = line.split() # split the string by whitespace(s) + if len(line) < 4: + continue # a valid entry should have at least 4 columns + __av_type = line[1] + __av_value = line[-1] + auxiliary_vector[__av_type] = int(__av_value, base=0) + self.__auxiliary_vector = auxiliary_vector + return self.__auxiliary_vector + + @property + def os(self) -> str: + """Return the current OS.""" + if not self.__os: + self.__os = platform.system().lower() + return self.__os + + @property + def pid(self) -> int: + """Return the PID of the target process.""" + if not self.__pid: + pid = gdb.selected_inferior().pid if not gef.session.qemu_mode else gdb.selected_thread().ptid[1] + if not pid: + raise RuntimeError("cannot retrieve PID for target process") + self.__pid = pid + return self.__pid + + @property + def file(self) -> pathlib.Path: + """Return a Path object of the target process.""" + if not self.__file: + self.__file = pathlib.Path(gdb.current_progspace().filename) + return self.__file + + @property + def pagesize(self) -> int: + """Get the system page size""" + auxval = self.auxiliary_vector + if not auxval: + return DEFAULT_PAGE_SIZE + self.__pagesize = auxval["AT_PAGESZ"] + return self.__pagesize + + @property + def canary(self) -> Optional[Tuple[int, int]]: + """Returns a tuple of the canary address and value, read from the auxiliary vector.""" + auxval = self.auxiliary_vector + if not auxval: + return None + canary_location = auxval["AT_RANDOM"] + canary = gef.memory.read_integer(canary_location) + canary &= ~0xFF + self.__canary = (canary, canary_location) + return self.__canary + + +class GefUiManager(GefManager): + """Class managing UI settings.""" + def __init__(self) -> None: + self.redirect_fd : Optional[TextIOWrapper] = None + self.context_hidden = False + self.stream_buffer : Optional[StringIO] = None + self.highlight_table: Dict[str, str] = {} + self.watches: Dict[int, Tuple[int, str]] = {} + self.context_messages: List[str] = [] + return + + +class Gef: + """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture, + memory, settings, etc.).""" + def __init__(self) -> None: + self.binary: Optional[Elf] = None + self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler` + self.config = GefSettingsManager() + self.ui = GefUiManager() + return + + def reinitialize_managers(self) -> None: + """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred""" + self.memory = GefMemoryManager() + self.heap = GefHeapManager() + self.session = GefSessionManager() + return + + def setup(self) -> None: + """Setup initialize the runtime setup, which may require for the `gef` to be not None.""" + self.reinitialize_managers() + self.gdb = GefCommand() + self.gdb.setup() + tempdir = self.config["gef.tempdir"] + gef_makedirs(tempdir) + gdb.execute(f"save gdb-index {tempdir}") + return + + def reset_caches(self) -> None: + """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache` + is preferred""" + for mgr in (self.memory, self.heap, self.session, self.arch): + mgr.reset_caches() + return + + +if __name__ == "__main__": + if sys.version_info[0] == 2: + err("GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01.") + err("If you require GEF for GDB+Python2, use https://github.com/hugsy/gef-legacy.") + exit(1) + + if GDB_VERSION < GDB_MIN_VERSION or PYTHON_VERSION < PYTHON_MIN_VERSION: + err("You're using an old version of GDB. GEF will not work correctly. " + f"Consider updating to GDB {'.'.join(map(str, GDB_MIN_VERSION))} or higher " + f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).") + exit(1) + + try: + pyenv = which("pyenv") + PYENV_ROOT = gef_pystring(subprocess.check_output([pyenv, "root"]).strip()) + PYENV_VERSION = gef_pystring(subprocess.check_output([pyenv, "version-name"]).strip()) + site_packages_dir = os.path.join(PYENV_ROOT, "versions", PYENV_VERSION, "lib", + f"python{PYENV_VERSION[:3]}", "site-packages") + site.addsitedir(site_packages_dir) + except FileNotFoundError: + pass + + # When using a Python virtual environment, GDB still loads the system-installed Python + # so GEF doesn't load site-packages dir from environment + # In order to fix it, from the shell with venv activated we run the python binary, + # take and parse its path, add the path to the current python process using sys.path.extend + PYTHONBIN = which("python3") + PREFIX = gef_pystring(subprocess.check_output([PYTHONBIN, '-c', 'import os, sys;print((sys.prefix))'])).strip("\\n") + if PREFIX != sys.base_prefix: + SITE_PACKAGES_DIRS = subprocess.check_output( + [PYTHONBIN, "-c", "import os, sys;print(os.linesep.join(sys.path).strip())"]).decode("utf-8").split() + sys.path.extend(SITE_PACKAGES_DIRS) + + # setup prompt + gdb.prompt_hook = __gef_prompt__ + + # setup config + gdb_initial_settings = ( + "set confirm off", + "set verbose off", + "set pagination off", + "set print elements 0", + "set history save on", + "set history filename ~/.gdb_history", + "set output-radix 0x10", + "set print pretty on", + "set disassembly-flavor intel", + "handle SIGALRM print nopass", + ) + for cmd in gdb_initial_settings: + try: + gdb.execute(cmd) + except gdb.error: + pass + + # load GEF + reset() + + # gdb events configuration + gef_on_continue_hook(continue_handler) + gef_on_stop_hook(hook_stop_handler) + gef_on_new_hook(new_objfile_handler) + gef_on_exit_hook(exit_handler) + gef_on_memchanged_hook(memchanged_handler) + gef_on_regchanged_hook(regchanged_handler) + + if gdb.current_progspace().filename is not None: + # if here, we are sourcing gef from a gdb session already attached + # we must force a call to the new_objfile handler (see issue #278) + new_objfile_handler(None) + + GefTmuxSetup() diff --git a/gdb/.config/gdb/init b/gdb/.config/gdb/init new file mode 100644 index 0000000..dc69f24 --- /dev/null +++ b/gdb/.config/gdb/init @@ -0,0 +1 @@ +source ~/.config/gdb/.gef-283690ae9bfcecbb3deb80cd275d327c46b276b5.py diff --git a/lvim/.config/lvim/config.lua b/lvim/.config/lvim/config.lua new file mode 100644 index 0000000..f738d0d --- /dev/null +++ b/lvim/.config/lvim/config.lua @@ -0,0 +1,168 @@ +--[[ +lvim is the global options object + +Linters should be +filled in as strings with either +a global executable or a path to +an executable +]] +-- THESE ARE EXAMPLE CONFIGS FEEL FREE TO CHANGE TO WHATEVER YOU WANT + +-- general +lvim.log.level = "warn" +lvim.format_on_save = true +lvim.colorscheme = "nord" +-- to disable icons and use a minimalist setup, uncomment the following +-- lvim.use_icons = false + +-- keymappings [view all the defaults by pressing <leader>Lk] +lvim.leader = "space" +-- add your own keymapping +lvim.keys.normal_mode["<C-s>"] = ":w<cr>" +-- unmap a default keymapping +-- lvim.keys.normal_mode["<C-Up>"] = false +-- edit a default keymapping +-- lvim.keys.normal_mode["<C-q>"] = ":q<cr>" + +-- Change Telescope navigation to use j and k for navigation and n and p for history in both input and normal mode. +-- we use protected-mode (pcall) just in case the plugin wasn't loaded yet. +-- local _, actions = pcall(require, "telescope.actions") +-- lvim.builtin.telescope.defaults.mappings = { +-- -- for input mode +-- i = { +-- ["<C-j>"] = actions.move_selection_next, +-- ["<C-k>"] = actions.move_selection_previous, +-- ["<C-n>"] = actions.cycle_history_next, +-- ["<C-p>"] = actions.cycle_history_prev, +-- }, +-- -- for normal mode +-- n = { +-- ["<C-j>"] = actions.move_selection_next, +-- ["<C-k>"] = actions.move_selection_previous, +-- }, +-- } + +-- Use which-key to add extra bindings with the leader-key prefix +-- lvim.builtin.which_key.mappings["P"] = { "<cmd>Telescope projects<CR>", "Projects" } +-- lvim.builtin.which_key.mappings["t"] = { +-- name = "+Trouble", +-- r = { "<cmd>Trouble lsp_references<cr>", "References" }, +-- f = { "<cmd>Trouble lsp_definitions<cr>", "Definitions" }, +-- d = { "<cmd>Trouble document_diagnostics<cr>", "Diagnostics" }, +-- q = { "<cmd>Trouble quickfix<cr>", "QuickFix" }, +-- l = { "<cmd>Trouble loclist<cr>", "LocationList" }, +-- w = { "<cmd>Trouble workspace_diagnostics<cr>", "Wordspace Diagnostics" }, +-- } + +-- TODO: User Config for predefined plugins +-- After changing plugin config exit and reopen LunarVim, Run :PackerInstall :PackerCompile +lvim.builtin.alpha.active = true +lvim.builtin.alpha.mode = "dashboard" +lvim.builtin.notify.active = true +lvim.builtin.terminal.active = true +lvim.builtin.nvimtree.setup.view.side = "left" +lvim.builtin.nvimtree.show_icons.git = 0 + +-- if you don't want all the parsers change this to a table of the ones you want +lvim.builtin.treesitter.ensure_installed = { + "bash", + "c", + "javascript", + "json", + "lua", + "python", + "typescript", + "tsx", + "css", + "rust", + "java", + "yaml", +} + +lvim.builtin.treesitter.ignore_install = { "haskell" } +lvim.builtin.treesitter.highlight.enabled = true + +-- generic LSP settings + +-- ---@usage disable automatic installation of servers +-- lvim.lsp.automatic_servers_installation = false + +-- ---configure a server manually. !!Requires `:LvimCacheReset` to take effect!! +-- ---see the full default list `:lua print(vim.inspect(lvim.lsp.automatic_configuration.skipped_servers))` +-- vim.list_extend(lvim.lsp.automatic_configuration.skipped_servers, { "pyright" }) +-- local opts = {} -- check the lspconfig documentation for a list of all possible options +-- require("lvim.lsp.manager").setup("pyright", opts) + +-- ---remove a server from the skipped list, e.g. eslint, or emmet_ls. !!Requires `:LvimCacheReset` to take effect!! +-- ---`:LvimInfo` lists which server(s) are skiipped for the current filetype +-- vim.tbl_map(function(server) +-- return server ~= "emmet_ls" +-- end, lvim.lsp.automatic_configuration.skipped_servers) + +-- -- you can set a custom on_attach function that will be used for all the language servers +-- -- See <https://github.com/neovim/nvim-lspconfig#keybindings-and-completion> +-- lvim.lsp.on_attach_callback = function(client, bufnr) +-- local function buf_set_option(...) +-- vim.api.nvim_buf_set_option(bufnr, ...) +-- end +-- --Enable completion triggered by <c-x><c-o> +-- buf_set_option("omnifunc", "v:lua.vim.lsp.omnifunc") +-- end + +-- -- set a formatter, this will override the language server formatting capabilities (if it exists) +-- local formatters = require "lvim.lsp.null-ls.formatters" +-- formatters.setup { +-- { command = "black", filetypes = { "python" } }, +-- { command = "isort", filetypes = { "python" } }, +-- { +-- -- each formatter accepts a list of options identical to https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md#Configuration +-- command = "prettier", +-- ---@usage arguments to pass to the formatter +-- -- these cannot contain whitespaces, options such as `--line-width 80` become either `{'--line-width', '80'}` or `{'--line-width=80'}` +-- extra_args = { "--print-with", "100" }, +-- ---@usage specify which filetypes to enable. By default a providers will attach to all the filetypes it supports. +-- filetypes = { "typescript", "typescriptreact" }, +-- }, +-- } + +-- -- set additional linters +-- local linters = require "lvim.lsp.null-ls.linters" +-- linters.setup { +-- { command = "flake8", filetypes = { "python" } }, +-- { +-- -- each linter accepts a list of options identical to https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md#Configuration +-- command = "shellcheck", +-- ---@usage arguments to pass to the formatter +-- -- these cannot contain whitespaces, options such as `--line-width 80` become either `{'--line-width', '80'}` or `{'--line-width=80'}` +-- extra_args = { "--severity", "warning" }, +-- }, +-- { +-- command = "codespell", +-- ---@usage specify which filetypes to enable. By default a providers will attach to all the filetypes it supports. +-- filetypes = { "javascript", "python" }, +-- }, +-- } + +-- Additional Plugins +lvim.plugins = { + {"shaunsingh/nord.nvim"} +-- {"folke/tokyonight.nvim"}, +-- { +-- "folke/trouble.nvim", +-- cmd = "TroubleToggle", +-- }, +} + +vim.g.nord_disable_background=true +vim.g.nord_borders=true +vim.g.nord_contrast=true +vim.g.nord_cursorline_transparent=true +vim.g.nord_enable_sidebar_background=true + +-- Autocommands (https://neovim.io/doc/user/autocmd.html) +-- lvim.autocommands.custom_groups = { +-- { "BufWinEnter", "*.lua", "setlocal ts=8 sw=8" }, +-- } + +vim.o.guifont = "Terminus" +vim.g.gui_font_default_size = 8 diff --git a/sh/.config/shell/aliasrc b/sh/.config/shell/aliasrc new file mode 100644 index 0000000..c959afa --- /dev/null +++ b/sh/.config/shell/aliasrc @@ -0,0 +1,63 @@ +#!/bin/sh + +# Use neovim for vim if present. +[ -x "$(command -v nvim)" ] && alias vim="nvim" vimdiff="nvim -d" + +# Use $XINITRC variable if file exists. +[ -f "$XINITRC" ] && alias startx="startx $XINITRC" + +# sudo not required for some system commands +for x in mount umount sv pacman updatedb su ; do + alias $x="sudo $x" +done + +# Verbosity and settings that you pretty much just always are going to want. +alias \ + cp="cp -iv" \ + mv="mv -iv" \ + rm="rm -vI" \ + bc="bc -ql" \ + info="pinfo"\ + mkdir="mkdir -pv" \ + yt="yt-dlp --add-metadata -i" \ + nvim="lvim" \ + yta="yt -x -f bestaudio/best" \ + ffmpeg="ffmpeg -hide_banner" \ + df="df -h" \ + du="dust -r" \ + free="free -m" \ + gdb="gdb -q -nh -x "$XDG_CONFIG_HOME"/gdb/init" \ + paru="yay" \ + yarn='yarn --use-yarnrc "$XDG_CONFIG_HOME/yarn/config"' \ +# Colorize commands when possible. +alias \ + ls="exa -aF --group-directories-first" \ + ll="exa -alg --group-directories-first" \ + lt='exa -aTF --group-directories-first' \ + lr='exa -aRF' \ + grep="rg --color=auto" \ + diff="batdiff --delta" \ + cat='bat --paging=never --style=plain' \ + #bat='prettybat' \ + #ccat="highlight --out-format=ansi" \ + #ssh="mosh" \ + +# These common commands are just too long! Abbreviate them. +alias \ + ka="killall" \ + g="git" \ + trem="transmission-remote" \ + YT="youtube-viewer" \ + sdn="sudo shutdown -h now" \ + e="$EDITOR" \ + v="$EDITOR" \ + p="sudo pacman" \ + xi="sudo xbps-install" \ + xr="sudo xbps-remove -R" \ + xq="xbps-query" \ + z="zathura" + +#alias \ +# magit="nvim -c MagitOnly" \ +# ref="shortcuts >/dev/null; source ${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutrc ; source ${XDG_CONFIG_HOME:-$HOME/.config}/shell/zshnameddirrc" \ +# weath="less -S ${XDG_DATA_HOME:-$HOME/.local/share}/weatherreport" \ diff --git a/sh/.config/shell/profile b/sh/.config/shell/profile new file mode 100644 index 0000000..86ac8f7 --- /dev/null +++ b/sh/.config/shell/profile @@ -0,0 +1,157 @@ +#!/bin/zsh + +# profile file. Runs on login. Environmental variables are set here. + +# If you don't plan on reverting to bash, you can remove the link in ~/.profile +# to clean up. + +# Adds `~/.local/bin` to $PATH +export PATH="$PATH:${$(find ~/.local/bin -type d -printf %p:)%%:}" + +unsetopt PROMPT_SP + +# Default programs: +export EDITOR="lvim" +#export TERMINAL="xterm-256color" +export BROWSER="chromium" + +# ~/ Clean-up: +export USERXSESSION="$XDG_CACHE_HOME/X11/xsession" +export USERXSESSIONRC="$XDG_CACHE_HOME/X11/xsessionrc" +export ALTUSERXSESSION="$XDG_CACHE_HOME/X11/Xsession" +export ERRFILE="$XDG_CACHE_HOME/X11/xsession-errors" +export GPG_TTY=$(tty) +export XDG_CONFIG_HOME="$HOME/.config" +export XDG_DATA_HOME="$HOME/.local/share" +export XDG_CACHE_HOME="$HOME/.cache" +export XINITRC="${XDG_CONFIG_HOME:-$HOME/.config}/x11/xinitrc" +#export XAUTHORITY="$XDG_RUNTIME_DIR/Xauthority" # This line will break some DMs. +export NOTMUCH_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/notmuch-config" +export GTK2_RC_FILES="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-2.0/gtkrc-2.0" +export LESSHISTFILE="-" +export WGETRC="${XDG_CONFIG_HOME:-$HOME/.config}/wget/wgetrc" +export INPUTRC="${XDG_CONFIG_HOME:-$HOME/.config}/shell/inputrc" +export ZDOTDIR="${XDG_CONFIG_HOME:-$HOME/.config}/zsh" +export NUGET_PACKAGES="$XDG_CACHE_HOME/NuGetPackages" +export ALSA_CONFIG_PATH="$XDG_CONFIG_HOME/alsa/asoundrc" +export GNUPGHOME="${XDG_DATA_HOME:-$HOME/.local/share}/gnupg" +export WINEPREFIX="${XDG_DATA_HOME:-$HOME/.local/share}/wineprefixes/default" +export KODI_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/kodi" +export PASSWORD_STORE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/password-store" +export TMUX_TMPDIR="$XDG_RUNTIME_DIR" +export ANDROID_SDK_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/android" +export CARGO_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/cargo" +export GOPATH="${XDG_DATA_HOME:-$HOME/.local/share}/go" +export MANPAGER="nvimpager" +export ANSIBLE_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/ansible/ansible.cfg" +export UNISON="${XDG_DATA_HOME:-$HOME/.local/share}/unison" +export HISTFILE="${XDG_DATA_HOME:-$HOME/.local/share}/history" +export WEECHAT_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/weechat" +export MBSYNCRC="${XDG_CONFIG_HOME:-$HOME/.config}/mbsync/config" +export ELECTRUMDIR="${XDG_DATA_HOME:-$HOME/.local/share}/electrum" +export OCTAVE_SITE_INITFILE="$XDG_CONFIG_HOME/octave/octaverc" +export OCTAVE_HISTFILE="/home/ross/.cache/octave/octave_hist" +export ICEAUTHORITY="$XDG_CACHE_HOME"/ICEauthority +#export TERM="xterm-256color" # getting proper colors +export HISTORY_IGNORE="(ls|cd|pwd|exit|sudo reboot|history|cd -|cd ..|clear|fg)" +export GDBHISTFILE="$XDG_DATA_HOME"/gdb/history + +# Other program settings: +export DICS="/usr/share/stardict/dic/" +export SUDO_ASKPASS="$HOME/.local/bin/dmenupass" +export FZF_DEFAULT_OPTS="--layout=reverse --height 40%" +export LESS=-R +export LESS_TERMCAP_mb="$(printf '%b' '[1;31m')" +export LESS_TERMCAP_md="$(printf '%b' '[1;36m')" +export LESS_TERMCAP_me="$(printf '%b' '[0m')" +export LESS_TERMCAP_so="$(printf '%b' '[01;44;33m')" +export LESS_TERMCAP_se="$(printf '%b' '[0m')" +export LESS_TERMCAP_us="$(printf '%b' '[1;32m')" +export LESS_TERMCAP_ue="$(printf '%b' '[0m')" +export LESSOPEN="| /usr/bin/highlight -O ansi %s 2>/dev/null" +export QT_QPA_PLATFORMTHEME="qt5ct" # Have QT use gtk2 theme. +export MOZ_USE_XINPUT2="1" # Mozilla smooth scrolling/touchpads. +export AWT_TOOLKIT="MToolkit wmname LG3D" #May have to install wmname +export _JAVA_AWT_WM_NONREPARENTING=1 # Fix for Java applications in dwm +export _JAVA_OPTIONS=-Djava.util.prefs.userRoot="$XDG_CONFIG_HOME"/java +export _JAVA_OPTIONS=-Djava.util.prefs.userRoot="$XDG_CONFIG_HOME"/java + +# This is the list for lf icons: +export LF_ICONS="di=📁:\ +fi=📃:\ +tw=🤝:\ +ow=📂:\ +ln=⛓:\ +or=❌:\ +ex=🎯:\ +*.txt=✍:\ +*.mom=✍:\ +*.me=✍:\ +*.ms=✍:\ +*.png=🖼:\ +*.webp=🖼:\ +*.ico=🖼:\ +*.jpg=📸:\ +*.jpe=📸:\ +*.jpeg=📸:\ +*.gif=🖼:\ +*.svg=🗺:\ +*.tif=🖼:\ +*.tiff=🖼:\ +*.xcf=🖌:\ +*.html=🌎:\ +*.xml=📰:\ +*.gpg=🔒:\ +*.css=🎨:\ +*.pdf=📚:\ +*.djvu=📚:\ +*.epub=📚:\ +*.csv=📓:\ +*.xlsx=📓:\ +*.tex=📜:\ +*.md=📘:\ +*.r=📊:\ +*.R=📊:\ +*.rmd=📊:\ +*.Rmd=📊:\ +*.m=📊:\ +*.mp3=🎵:\ +*.opus=🎵:\ +*.ogg=🎵:\ +*.m4a=🎵:\ +*.flac=🎼:\ +*.wav=🎼:\ +*.mkv=🎥:\ +*.mp4=🎥:\ +*.webm=🎥:\ +*.mpeg=🎥:\ +*.avi=🎥:\ +*.mov=🎥:\ +*.mpg=🎥:\ +*.wmv=🎥:\ +*.m4b=🎥:\ +*.flv=🎥:\ +*.zip=📦:\ +*.rar=📦:\ +*.7z=📦:\ +*.tar.gz=📦:\ +*.z64=🎮:\ +*.v64=🎮:\ +*.n64=🎮:\ +*.gba=🎮:\ +*.nes=🎮:\ +*.gdi=🎮:\ +*.1=ℹ:\ +*.nfo=ℹ:\ +*.info=ℹ:\ +*.log=📙:\ +*.iso=📀:\ +*.img=📀:\ +*.bib=🎓:\ +*.ged=👪:\ +*.part=💔:\ +*.torrent=🔽:\ +*.jar=♨:\ +*.java=♨:\ +" +#[ ! -f ${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutrc ] && shortcuts >/dev/null 2>&1 & diff --git a/tmux/.config/tmux/tmux.conf b/tmux/.config/tmux/tmux.conf new file mode 100644 index 0000000..b04eb3a --- /dev/null +++ b/tmux/.config/tmux/tmux.conf @@ -0,0 +1,23 @@ +set -g mouse on +set -ga terminal-overrides ",xterm-256color:Tc" +set -g default-terminal "tmux-256color" +set -g @plugin "arcticicestudio/nord-tmux" +set -g @plugin 'tmux-plugins/tmux-sensible' +set -g @plugin 'tmux-plugins/tmux-yank' +set -g default-command "${SHELL} -l" +set -g @plugin 'jaclu/tmux-menus' +set -g @plugin 'schasse/tmux-jump' +set -g @plugin 'MunifTanjim/tmux-mode-indicator' + +#ctrl-a for prefix +unbind C-b +set-option -g prefix C-a +bind-key C-a send-prefix + +bind -n M-Left select-pane -L +bind -n M-Right select-pane -R +bind -n M-Up select-pane -U +bind -n M-Down select-pane -D + +#Make sure you have tmux-plugin-manager installed! +run '/usr/share/tmux-plugin-manager/tpm' diff --git a/zsh/.config/zsh/.p10k.zsh b/zsh/.config/zsh/.p10k.zsh new file mode 100644 index 0000000..cf788f7 --- /dev/null +++ b/zsh/.config/zsh/.p10k.zsh @@ -0,0 +1,1600 @@ +# Generated by Powerlevel10k configuration wizard on 2021-10-11 at 15:25 CDT. +# Based on romkatv/powerlevel10k/config/p10k-lean.zsh, checksum 46236. +# Wizard options: nerdfont-complete + powerline, large icons, unicode, lean, 24h time, +# 1 line, compact, few icons, concise, transient_prompt, instant_prompt=quiet. +# Type `p10k configure` to generate another config. +# +# Config for Powerlevel10k with lean prompt style. Type `p10k configure` to generate +# your own config based on it. +# +# Tip: Looking for a nice color? Here's a one-liner to print colormap. +# +# for i in {0..255}; do print -Pn "%K{$i} %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done + +# Temporarily change options. +'builtin' 'local' '-a' 'p10k_config_opts' +[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases') +[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob') +[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand') +'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' + +() { + emulate -L zsh -o extended_glob + + # Unset all configuration options. This allows you to apply configuration changes without + # restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`. + unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' + + # Zsh >= 5.1 is required. + autoload -Uz is-at-least && is-at-least 5.1 || return + + # The list of segments shown on the left. Fill it with the most important segments. + typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( + # os_icon # os identifier + dir # current directory + vcs # git status + prompt_char # prompt symbol + ) + + # The list of segments shown on the right. Fill it with less important segments. + # Right prompt on the last prompt line (where you are typing your commands) gets + # automatically hidden when the input line reaches it. Right prompt above the + # last prompt line gets hidden if it would overlap with left prompt. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( + status # exit code of the last command + command_execution_time # duration of the last command + background_jobs # presence of background jobs + direnv # direnv status (https://direnv.net/) + asdf # asdf version manager (https://github.com/asdf-vm/asdf) + virtualenv # python virtual environment (https://docs.python.org/3/library/venv.html) + anaconda # conda environment (https://conda.io/) + pyenv # python environment (https://github.com/pyenv/pyenv) + goenv # go environment (https://github.com/syndbg/goenv) + nodenv # node.js version from nodenv (https://github.com/nodenv/nodenv) + nvm # node.js version from nvm (https://github.com/nvm-sh/nvm) + nodeenv # node.js environment (https://github.com/ekalinin/nodeenv) + # node_version # node.js version + # go_version # go version (https://golang.org) + # rust_version # rustc version (https://www.rust-lang.org) + # dotnet_version # .NET version (https://dotnet.microsoft.com) + # php_version # php version (https://www.php.net/) + # laravel_version # laravel php framework version (https://laravel.com/) + # java_version # java version (https://www.java.com/) + # package # name@version from package.json (https://docs.npmjs.com/files/package.json) + rbenv # ruby version from rbenv (https://github.com/rbenv/rbenv) + rvm # ruby version from rvm (https://rvm.io) + fvm # flutter version management (https://github.com/leoafarias/fvm) + luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv) + jenv # java version from jenv (https://github.com/jenv/jenv) + plenv # perl version from plenv (https://github.com/tokuhirom/plenv) + phpenv # php version from phpenv (https://github.com/phpenv/phpenv) + scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv) + haskell_stack # haskell version from stack (https://haskellstack.org/) + kubecontext # current kubernetes context (https://kubernetes.io/) + terraform # terraform workspace (https://www.terraform.io) + # terraform_version # terraform version (https://www.terraform.io) + aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) + aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) + azure # azure account name (https://docs.microsoft.com/en-us/cli/azure) + gcloud # google cloud cli account and project (https://cloud.google.com/) + google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production) + context # user@hostname + nordvpn # nordvpn connection status, linux only (https://nordvpn.com/) + ranger # ranger shell (https://github.com/ranger/ranger) + nnn # nnn shell (https://github.com/jarun/nnn) + xplr # xplr shell (https://github.com/sayanarijit/xplr) + vim_shell # vim shell indicator (:sh) + midnight_commander # midnight commander shell (https://midnight-commander.org/) + nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) + # vpn_ip # virtual private network indicator + # load # CPU load + # disk_usage # disk usage + # ram # free RAM + # swap # used swap + todo # todo items (https://github.com/todotxt/todo.txt-cli) + timewarrior # timewarrior tracking status (https://timewarrior.net/) + taskwarrior # taskwarrior task count (https://taskwarrior.org/) + # time # current time + # ip # ip address and bandwidth usage for a specified network interface + # public_ip # public IP address + # proxy # system-wide http/https/ftp proxy + battery # internal battery + # wifi # wifi speed + # example # example user-defined segment (see prompt_example function below) + ) + + # Defines character set used by powerlevel10k. It's best to let `p10k configure` set it for you. + typeset -g POWERLEVEL9K_MODE=nerdfont-complete + # When set to `moderate`, some icons will have an extra space after them. This is meant to avoid + # icon overlap when using non-monospace fonts. When set to `none`, spaces are not added. + typeset -g POWERLEVEL9K_ICON_PADDING=moderate + + # Basic style options that define the overall look of your prompt. You probably don't want to + # change them. + typeset -g POWERLEVEL9K_BACKGROUND= # transparent background + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space + typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol + + # When set to true, icons appear before content on both sides of the prompt. When set + # to false, icons go after content. If empty or not set, icons go before content in the left + # prompt and after content in the right prompt. + # + # You can also override it for a specific segment: + # + # POWERLEVEL9K_STATUS_ICON_BEFORE_CONTENT=false + # + # Or for a specific segment in specific state: + # + # POWERLEVEL9K_DIR_NOT_WRITABLE_ICON_BEFORE_CONTENT=false + typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=true + + # Add an empty line before each prompt. + typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=false + + # Connect left prompt lines with these symbols. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX= + typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX= + typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX= + # Connect right prompt lines with these symbols. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX= + typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX= + typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX= + + # The left end of left prompt. + typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= + # The right end of right prompt. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL= + + # Ruler, a.k.a. the horizontal line before each prompt. If you set it to true, you'll + # probably want to set POWERLEVEL9K_PROMPT_ADD_NEWLINE=false above and + # POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' below. + typeset -g POWERLEVEL9K_SHOW_RULER=false + typeset -g POWERLEVEL9K_RULER_CHAR='─' # reasonable alternative: '·' + typeset -g POWERLEVEL9K_RULER_FOREGROUND=242 + + # Filler between left and right prompt on the first prompt line. You can set it to '·' or '─' + # to make it easier to see the alignment between left and right prompt and to separate prompt + # from command output. It serves the same purpose as ruler (see above) without increasing + # the number of prompt lines. You'll probably want to set POWERLEVEL9K_SHOW_RULER=false + # if using this. You might also like POWERLEVEL9K_PROMPT_ADD_NEWLINE=false for more compact + # prompt. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' + if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then + # The color of the filler. + typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=242 + # Add a space between the end of left prompt and the filler. + typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=' ' + # Add a space between the filler and the start of right prompt. + typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL=' ' + # Start filler from the edge of the screen if there are no left segments on the first line. + typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}' + # End filler on the edge of the screen if there are no right segments on the first line. + typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}' + fi + + #################################[ os_icon: os identifier ]################################## + # OS identifier color. + typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND= + # Custom icon. + # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='⭐' + + ################################[ prompt_char: prompt symbol ]################################ + # Green prompt symbol if the last command succeeded. + typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76 + # Red prompt symbol if the last command failed. + typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196 + # Default prompt symbol. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' + # Prompt symbol in command vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' + # Prompt symbol in visual vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V' + # Prompt symbol in overwrite vi mode. + typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶' + typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true + # No line terminator if prompt_char is the last segment. + typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='' + # No line introducer if prompt_char is the first segment. + typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= + + ##################################[ dir: current directory ]################################## + # Default current directory color. + typeset -g POWERLEVEL9K_DIR_FOREGROUND=31 + # If directory is too long, shorten some of its segments to the shortest possible unique + # prefix. The shortened directory can be tab-completed to the original. + typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique + # Replace removed segment suffixes with this symbol. + typeset -g POWERLEVEL9K_SHORTEN_DELIMITER= + # Color of the shortened directory segments. + typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103 + # Color of the anchor directory segments. Anchor segments are never shortened. The first + # segment is always an anchor. + typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39 + # Display anchor directory segments in bold. + typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true + # Don't shorten directories that contain any of these files. They are anchors. + local anchor_files=( + .bzr + .citc + .git + .hg + .node-version + .python-version + .go-version + .ruby-version + .lua-version + .java-version + .perl-version + .php-version + .tool-version + .shorten_folder_marker + .svn + .terraform + CVS + Cargo.toml + composer.json + go.mod + package.json + stack.yaml + ) + typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})" + # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains + # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is + # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first) + # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers + # and other directories don't. + # + # Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer. + # This moves the truncation point to the right (positive offset) or to the left (negative offset) + # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0" + # respectively. + typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false + # Don't shorten this many last directory segments. They are anchors. + typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1 + # Shorten directory if it's longer than this even if there is space for it. The value can + # be either absolute (e.g., '80') or a percentage of terminal width (e.g, '50%'). If empty, + # directory will be shortened only when prompt doesn't fit or when other parameters demand it + # (see POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS and POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT below). + # If set to `0`, directory will always be shortened to its minimum length. + typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80 + # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least this + # many columns for typing commands. + typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40 + # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least + # COLUMNS * POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT * 0.01 columns for typing commands. + typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50 + # If set to true, embed a hyperlink into the directory. Useful for quickly + # opening a directory in the file manager simply by clicking the link. + # Can also be handy when the directory is shortened, as it allows you to see + # the full directory that was used in previous commands. + typeset -g POWERLEVEL9K_DIR_HYPERLINK=false + + # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON + # and POWERLEVEL9K_DIR_CLASSES below. + typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3 + + # The default icon shown next to non-writable and non-existent directories when + # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3. + # typeset -g POWERLEVEL9K_LOCK_ICON='⭐' + + # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different + # directories. It must be an array with 3 * N elements. Each triplet consists of: + # + # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with + # extended_glob option enabled. + # 2. Directory class for the purpose of styling. + # 3. An empty string. + # + # Triplets are tried in order. The first triplet whose pattern matches $PWD wins. + # + # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories + # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_DIR_CLASSES=( + # '~/work(|/*)' WORK '' + # '~(|/*)' HOME '' + # '*' DEFAULT '') + # + # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one + # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or + # WORK_NON_EXISTENT. + # + # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an + # option to define custom colors and icons for different directory classes. + # + # # Styling for WORK. + # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31 + # typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103 + # typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39 + # + # # Styling for WORK_NOT_WRITABLE. + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31 + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103 + # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39 + # + # # Styling for WORK_NON_EXISTENT. + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31 + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103 + # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39 + # + # If a styling parameter isn't explicitly defined for some class, it falls back to the classless + # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls + # back to POWERLEVEL9K_DIR_FOREGROUND. + # + typeset -g POWERLEVEL9K_DIR_CLASSES=() + + # Custom prefix. + # typeset -g POWERLEVEL9K_DIR_PREFIX='%fin ' + + #####################################[ vcs: git status ]###################################### + # Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon. + typeset -g POWERLEVEL9K_VCS_BRANCH_ICON= + + # Untracked files icon. It's really a question mark, your font isn't broken. + # Change the value of this parameter to show a different icon. + typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?' + + # Formatter for Git status. + # + # Example output: master wip ⇣42⇡42 *42 merge ~42 +42 !42 ?42. + # + # You can edit the function to customize how Git status looks. + # + # VCS_STATUS_* parameters are set by gitstatus plugin. See reference: + # https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh. + function my_git_formatter() { + emulate -L zsh + + if [[ -n $P9K_CONTENT ]]; then + # If P9K_CONTENT is not empty, use it. It's either "loading" or from vcs_info (not from + # gitstatus plugin). VCS_STATUS_* parameters are not available in this case. + typeset -g my_git_format=$P9K_CONTENT + return + fi + + if (( $1 )); then + # Styling for up-to-date Git status. + local meta='%f' # default foreground + local clean='%76F' # green foreground + local modified='%178F' # yellow foreground + local untracked='%39F' # blue foreground + local conflicted='%196F' # red foreground + else + # Styling for incomplete and stale Git status. + local meta='%244F' # grey foreground + local clean='%244F' # grey foreground + local modified='%244F' # grey foreground + local untracked='%244F' # grey foreground + local conflicted='%244F' # grey foreground + fi + + local res + + if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then + local branch=${(V)VCS_STATUS_LOCAL_BRANCH} + # If local branch name is at most 32 characters long, show it in full. + # Otherwise show the first 12 … the last 12. + # Tip: To always show local branch name in full without truncation, delete the next line. + (( $#branch > 32 )) && branch[13,-13]="…" # <-- this line + res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}" + fi + + if [[ -n $VCS_STATUS_TAG + # Show tag only if not on a branch. + # Tip: To always show tag, delete the next line. + && -z $VCS_STATUS_LOCAL_BRANCH # <-- this line + ]]; then + local tag=${(V)VCS_STATUS_TAG} + # If tag name is at most 32 characters long, show it in full. + # Otherwise show the first 12 … the last 12. + # Tip: To always show tag name in full without truncation, delete the next line. + (( $#tag > 32 )) && tag[13,-13]="…" # <-- this line + res+="${meta}#${clean}${tag//\%/%%}" + fi + + # Display the current Git commit if there is no branch and no tag. + # Tip: To always display the current Git commit, delete the next line. + [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line + res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}" + + # Show tracking branch name if it differs from local branch. + if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then + res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" + fi + + # Display "wip" if the latest commit's summary contains "wip" or "WIP". + if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then + res+=" ${modified}wip" + fi + + # ⇣42 if behind the remote. + (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}" + # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42. + (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" " + (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}" + # ⇠42 if behind the push remote. + (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}" + (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" " + # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42. + (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}" + # *42 if have stashes. + (( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}" + # 'merge' if the repo is in an unusual state. + [[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}" + # ~42 if have merge conflicts. + (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}" + # +42 if have staged changes. + (( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}" + # !42 if have unstaged changes. + (( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}" + # ?42 if have untracked files. It's really a question mark, your font isn't broken. + # See POWERLEVEL9K_VCS_UNTRACKED_ICON above if you want to use a different icon. + # Remove the next line if you don't want to see untracked files at all. + (( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}" + # "─" if the number of unstaged files is unknown. This can happen due to + # POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY (see below) being set to a non-negative number lower + # than the number of files in the Git index, or due to bash.showDirtyState being set to false + # in the repository config. The number of staged and untracked files may also be unknown + # in this case. + (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─" + + typeset -g my_git_format=$res + } + functions -M my_git_formatter 2>/dev/null + + # Don't count the number of unstaged, untracked and conflicted files in Git repositories with + # more than this many files in the index. Negative value means infinity. + # + # If you are working in Git repositories with tens of millions of files and seeing performance + # sagging, try setting POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY to a number lower than the output + # of `git ls-files | wc -l`. Alternatively, add `bash.showDirtyState = false` to the repository's + # config: `git config bash.showDirtyState false`. + typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1 + + # Don't show Git status in prompt for repositories whose workdir matches this pattern. + # For example, if set to '~', the Git repository at $HOME/.git will be ignored. + # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'. + typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~' + + # Disable the default Git status formatting. + typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true + # Install our own Git status formatter. + typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}' + typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}' + # Enable counters for staged, unstaged, etc. + typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1 + + # Icon color. + typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76 + typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244 + # Custom icon. + typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_EXPANSION= + # Custom prefix. + # typeset -g POWERLEVEL9K_VCS_PREFIX='%fon ' + + # Show status of repositories of these types. You can add svn and/or hg if you are + # using them. If you do, your prompt may become slow even when your current directory + # isn't in an svn or hg reposotiry. + typeset -g POWERLEVEL9K_VCS_BACKENDS=(git) + + # These settings are used for repositories other than Git or when gitstatusd fails and + # Powerlevel10k has to fall back to using vcs_info. + typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76 + typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76 + typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178 + + ##########################[ status: exit code of the last command ]########################### + # Enable OK_PIPE, ERROR_PIPE and ERROR_SIGNAL status states to allow us to enable, disable and + # style them independently from the regular OK and ERROR state. + typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true + + # Status on success. No content, just an icon. No need to show it if prompt_char is enabled as + # it will signify success by turning green. + typeset -g POWERLEVEL9K_STATUS_OK=false + typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=70 + typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔' + + # Status when some part of a pipe command fails but the overall exit status is zero. It may look + # like this: 1|0. + typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true + typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=70 + typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔' + + # Status when it's just an error code (e.g., '1'). No need to show it if prompt_char is enabled as + # it will signify error by turning red. + typeset -g POWERLEVEL9K_STATUS_ERROR=false + typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=160 + typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘' + + # Status when the last command was terminated by a signal. + typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true + typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=160 + # Use terse signal names: "INT" instead of "SIGINT(2)". + typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false + typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘' + + # Status when some part of a pipe command fails and the overall exit status is also non-zero. + # It may look like this: 1|0. + typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true + typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=160 + typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘' + + ###################[ command_execution_time: duration of the last command ]################### + # Show duration of the last command if takes at least this many seconds. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3 + # Show this many fractional digits. Zero means round to seconds. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 + # Execution time color. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=101 + # Duration format: 1d 2h 3m 4s. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' + # Custom icon. + typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_VISUAL_IDENTIFIER_EXPANSION= + # Custom prefix. + # typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PREFIX='%ftook ' + + #######################[ background_jobs: presence of background jobs ]####################### + # Don't show the number of background jobs. + typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false + # Background jobs color. + typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=70 + # Custom icon. + # typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######################[ direnv: direnv status (https://direnv.net/) ]######################## + # Direnv color. + typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_DIRENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]############### + # Default asdf color. Only used to display tools for which there is no color override (see below). + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND. + typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66 + + # There are four parameters that can be used to hide asdf tools. Each parameter describes + # conditions under which a tool gets hidden. Parameters can hide tools but not unhide them. If at + # least one parameter decides to hide a tool, that tool gets hidden. If no parameter decides to + # hide a tool, it gets shown. + # + # Special note on the difference between POWERLEVEL9K_ASDF_SOURCES and + # POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW. Consider the effect of the following commands: + # + # asdf local python 3.8.1 + # asdf global python 3.8.1 + # + # After running both commands the current python version is 3.8.1 and its source is "local" as + # it takes precedence over "global". If POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW is set to false, + # it'll hide python version in this case because 3.8.1 is the same as the global version. + # POWERLEVEL9K_ASDF_SOURCES will hide python version only if the value of this parameter doesn't + # contain "local". + + # Hide tool versions that don't come from one of these sources. + # + # Available sources: + # + # - shell `asdf current` says "set by ASDF_${TOOL}_VERSION environment variable" + # - local `asdf current` says "set by /some/not/home/directory/file" + # - global `asdf current` says "set by /home/username/file" + # + # Note: If this parameter is set to (shell local global), it won't hide tools. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SOURCES. + typeset -g POWERLEVEL9K_ASDF_SOURCES=(shell local global) + + # If set to false, hide tool versions that are the same as global. + # + # Note: The name of this parameter doesn't reflect its meaning at all. + # Note: If this parameter is set to true, it won't hide tools. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_PROMPT_ALWAYS_SHOW. + typeset -g POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW=false + + # If set to false, hide tool versions that are equal to "system". + # + # Note: If this parameter is set to true, it won't hide tools. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_SYSTEM. + typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true + + # If set to non-empty value, hide tools unless there is a file matching the specified file pattern + # in the current directory, or its parent directory, or its grandparent directory, and so on. + # + # Note: If this parameter is set to empty value, it won't hide tools. + # Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments. + # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_ON_UPGLOB. + # + # Example: Hide nodejs version when there is no package.json and no *.js files in the current + # directory, in `..`, in `../..` and so on. + # + # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.js|package.json' + typeset -g POWERLEVEL9K_ASDF_SHOW_ON_UPGLOB= + + # Ruby version from asdf. + typeset -g POWERLEVEL9K_ASDF_RUBY_FOREGROUND=168 + # typeset -g POWERLEVEL9K_ASDF_RUBY_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_RUBY_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Python version from asdf. + typeset -g POWERLEVEL9K_ASDF_PYTHON_FOREGROUND=37 + # typeset -g POWERLEVEL9K_ASDF_PYTHON_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_PYTHON_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Go version from asdf. + typeset -g POWERLEVEL9K_ASDF_GOLANG_FOREGROUND=37 + # typeset -g POWERLEVEL9K_ASDF_GOLANG_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_GOLANG_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Node.js version from asdf. + typeset -g POWERLEVEL9K_ASDF_NODEJS_FOREGROUND=70 + # typeset -g POWERLEVEL9K_ASDF_NODEJS_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Rust version from asdf. + typeset -g POWERLEVEL9K_ASDF_RUST_FOREGROUND=37 + # typeset -g POWERLEVEL9K_ASDF_RUST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_RUST_SHOW_ON_UPGLOB='*.foo|*.bar' + + # .NET Core version from asdf. + typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_FOREGROUND=134 + # typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_DOTNET_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Flutter version from asdf. + typeset -g POWERLEVEL9K_ASDF_FLUTTER_FOREGROUND=38 + # typeset -g POWERLEVEL9K_ASDF_FLUTTER_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_FLUTTER_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Lua version from asdf. + typeset -g POWERLEVEL9K_ASDF_LUA_FOREGROUND=32 + # typeset -g POWERLEVEL9K_ASDF_LUA_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_LUA_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Java version from asdf. + typeset -g POWERLEVEL9K_ASDF_JAVA_FOREGROUND=32 + # typeset -g POWERLEVEL9K_ASDF_JAVA_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_JAVA_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Perl version from asdf. + typeset -g POWERLEVEL9K_ASDF_PERL_FOREGROUND=67 + # typeset -g POWERLEVEL9K_ASDF_PERL_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_PERL_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Erlang version from asdf. + typeset -g POWERLEVEL9K_ASDF_ERLANG_FOREGROUND=125 + # typeset -g POWERLEVEL9K_ASDF_ERLANG_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_ERLANG_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Elixir version from asdf. + typeset -g POWERLEVEL9K_ASDF_ELIXIR_FOREGROUND=129 + # typeset -g POWERLEVEL9K_ASDF_ELIXIR_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_ELIXIR_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Postgres version from asdf. + typeset -g POWERLEVEL9K_ASDF_POSTGRES_FOREGROUND=31 + # typeset -g POWERLEVEL9K_ASDF_POSTGRES_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_POSTGRES_SHOW_ON_UPGLOB='*.foo|*.bar' + + # PHP version from asdf. + typeset -g POWERLEVEL9K_ASDF_PHP_FOREGROUND=99 + # typeset -g POWERLEVEL9K_ASDF_PHP_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_PHP_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Haskell version from asdf. + typeset -g POWERLEVEL9K_ASDF_HASKELL_FOREGROUND=172 + # typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar' + + # Julia version from asdf. + typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70 + # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar' + + ##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]########### + # NordVPN connection indicator color. + typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39 + # Hide NordVPN connection indicator when not connected. + typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION= + typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION= + # Custom icon. + # typeset -g POWERLEVEL9K_NORDVPN_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #################[ ranger: ranger shell (https://github.com/ranger/ranger) ]################## + # Ranger shell color. + typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]####################### + # Nnn shell color. + typeset -g POWERLEVEL9K_NNN_FOREGROUND=72 + # Custom icon. + # typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]################## + # xplr shell color. + typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72 + # Custom icon. + # typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########################[ vim_shell: vim shell indicator (:sh) ]########################### + # Vim shell indicator color. + typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34 + # Custom icon. + # typeset -g POWERLEVEL9K_VIM_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######[ midnight_commander: midnight commander shell (https://midnight-commander.org/) ]###### + # Midnight Commander shell color. + typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=178 + # Custom icon. + # typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #[ nix_shell: nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) ]## + # Nix shell color. + typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74 + + # Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line. + # typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION= + + # Custom icon. + # typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################################[ disk_usage: disk usage ]################################## + # Colors for different levels of disk usage. + typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35 + typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220 + typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=160 + # Thresholds for different levels of disk usage (percentage points). + typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90 + typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95 + # If set to true, hide disk usage when below $POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL percent. + typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false + # Custom icon. + # typeset -g POWERLEVEL9K_DISK_USAGE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################################[ ram: free RAM ]####################################### + # RAM color. + typeset -g POWERLEVEL9K_RAM_FOREGROUND=66 + # Custom icon. + # typeset -g POWERLEVEL9K_RAM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #####################################[ swap: used swap ]###################################### + # Swap color. + typeset -g POWERLEVEL9K_SWAP_FOREGROUND=96 + # Custom icon. + # typeset -g POWERLEVEL9K_SWAP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ######################################[ load: CPU load ]###################################### + # Show average CPU load over this many last minutes. Valid values are 1, 5 and 15. + typeset -g POWERLEVEL9K_LOAD_WHICH=5 + # Load color when load is under 50%. + typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=66 + # Load color when load is between 50% and 70%. + typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=178 + # Load color when load is over 70%. + typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=166 + # Custom icon. + # typeset -g POWERLEVEL9K_LOAD_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################[ todo: todo items (https://github.com/todotxt/todo.txt-cli) ]################ + # Todo color. + typeset -g POWERLEVEL9K_TODO_FOREGROUND=110 + # Hide todo when the total number of tasks is zero. + typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true + # Hide todo when the number of tasks after filtering is zero. + typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false + + # Todo format. The following parameters are available within the expansion. + # + # - P9K_TODO_TOTAL_TASK_COUNT The total number of tasks. + # - P9K_TODO_FILTERED_TASK_COUNT The number of tasks after filtering. + # + # These variables correspond to the last line of the output of `todo.sh -p ls`: + # + # TODO: 24 of 42 tasks shown + # + # Here 24 is P9K_TODO_FILTERED_TASK_COUNT and 42 is P9K_TODO_TOTAL_TASK_COUNT. + # + # typeset -g POWERLEVEL9K_TODO_CONTENT_EXPANSION='$P9K_TODO_FILTERED_TASK_COUNT' + + # Custom icon. + # typeset -g POWERLEVEL9K_TODO_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ timewarrior: timewarrior tracking status (https://timewarrior.net/) ]############ + # Timewarrior color. + typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=110 + # If the tracked task is longer than 24 characters, truncate and append "…". + # Tip: To always display tasks without truncation, delete the following parameter. + # Tip: To hide task names and display just the icon when time tracking is enabled, set the + # value of the following parameter to "". + typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}' + + # Custom icon. + # typeset -g POWERLEVEL9K_TIMEWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]############## + # Taskwarrior color. + typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74 + + # Taskwarrior segment format. The following parameters are available within the expansion. + # + # - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`. + # - P9K_TASKWARRIOR_OVERDUE_COUNT The number of overdue tasks: `task +OVERDUE count`. + # + # Zero values are represented as empty parameters. + # + # The default format: + # + # '${P9K_TASKWARRIOR_OVERDUE_COUNT:+"!$P9K_TASKWARRIOR_OVERDUE_COUNT/"}$P9K_TASKWARRIOR_PENDING_COUNT' + # + # typeset -g POWERLEVEL9K_TASKWARRIOR_CONTENT_EXPANSION='$P9K_TASKWARRIOR_PENDING_COUNT' + + # Custom icon. + # typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##################################[ context: user@hostname ]################################## + # Context color when running with privileges. + typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178 + # Context color in SSH without privileges. + typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=180 + # Default context color (no privileges, no SSH). + typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=180 + + # Context format when running with privileges: bold user@hostname. + typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%B%n@%m' + # Context format when in SSH without privileges: user@hostname. + typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m' + # Default context format (no privileges, no SSH): user@hostname. + typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m' + + # Don't show context unless running with privileges or in SSH. + # Tip: Remove the next line to always show context. + typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION= + + # Custom icon. + # typeset -g POWERLEVEL9K_CONTEXT_VISUAL_IDENTIFIER_EXPANSION='⭐' + # Custom prefix. + # typeset -g POWERLEVEL9K_CONTEXT_PREFIX='%fwith ' + + ###[ virtualenv: python virtual environment (https://docs.python.org/3/library/venv.html) ]### + # Python virtual environment color. + typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37 + # Don't show Python version next to the virtual environment name. + typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false + # If set to "false", won't show virtualenv if pyenv is already shown. + # If set to "if-different", won't show virtualenv if it's the same as pyenv. + typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false + # Separate environment name from Python version only with a space. + typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER= + # Custom icon. + # typeset -g POWERLEVEL9K_VIRTUALENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #####################[ anaconda: conda environment (https://conda.io/) ]###################### + # Anaconda environment color. + typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37 + + # Anaconda segment format. The following parameters are available within the expansion. + # + # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment. + # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment. + # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below). + # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version). + # + # CONDA_PROMPT_MODIFIER can be configured with the following command: + # + # conda config --set env_prompt '({default_env}) ' + # + # The last argument is a Python format string that can use the following variables: + # + # - prefix The same as CONDA_PREFIX. + # - default_env The same as CONDA_DEFAULT_ENV. + # - name The last segment of CONDA_PREFIX. + # - stacked_env Comma-separated list of names in the environment stack. The first element is + # always the same as default_env. + # + # Note: '({default_env}) ' is the default value of env_prompt. + # + # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER + # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former + # is empty. + typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}' + + # Custom icon. + # typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################[ pyenv: python environment (https://github.com/pyenv/pyenv) ]################ + # Pyenv color. + typeset -g POWERLEVEL9K_PYENV_FOREGROUND=37 + # Hide python version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global) + # If set to false, hide python version if it's the same as global: + # $(pyenv version-name) == $(pyenv global). + typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide python version if it's equal to "system". + typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true + + # Pyenv segment format. The following parameters are available within the expansion. + # + # - P9K_CONTENT Current pyenv environment (pyenv version-name). + # - P9K_PYENV_PYTHON_VERSION Current python version (python --version). + # + # The default format has the following logic: + # + # 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or + # starts with "$P9K_PYENV_PYTHON_VERSION/". + # 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION". + typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}' + + # Custom icon. + # typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################[ goenv: go environment (https://github.com/syndbg/goenv) ]################ + # Goenv color. + typeset -g POWERLEVEL9K_GOENV_FOREGROUND=37 + # Hide go version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global) + # If set to false, hide go version if it's the same as global: + # $(goenv version-name) == $(goenv global). + typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide go version if it's equal to "system". + typeset -g POWERLEVEL9K_GOENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_GOENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ nodenv: node.js version from nodenv (https://github.com/nodenv/nodenv) ]########## + # Nodenv color. + typeset -g POWERLEVEL9K_NODENV_FOREGROUND=70 + # Hide node version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global) + # If set to false, hide node version if it's the same as global: + # $(nodenv version-name) == $(nodenv global). + typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide node version if it's equal to "system". + typeset -g POWERLEVEL9K_NODENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_NODENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]############### + # Nvm color. + typeset -g POWERLEVEL9K_NVM_FOREGROUND=70 + # Custom icon. + # typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ############[ nodeenv: node.js environment (https://github.com/ekalinin/nodeenv) ]############ + # Nodeenv color. + typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=70 + # Don't show Node version next to the environment name. + typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false + # Separate environment name from Node version only with a space. + typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER= + # Custom icon. + # typeset -g POWERLEVEL9K_NODEENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##############################[ node_version: node.js version ]############################### + # Node version color. + typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=70 + # Show node version only when in a directory tree containing package.json. + typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_NODE_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######################[ go_version: go version (https://golang.org) ]######################## + # Go version color. + typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=37 + # Show go version only when in a go project subdirectory. + typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_GO_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #################[ rust_version: rustc version (https://www.rust-lang.org) ]################## + # Rust version color. + typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=37 + # Show rust version only when in a rust project subdirectory. + typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_RUST_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###############[ dotnet_version: .NET version (https://dotnet.microsoft.com) ]################ + # .NET version color. + typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=134 + # Show .NET version only when in a .NET project subdirectory. + typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_DOTNET_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #####################[ php_version: php version (https://www.php.net/) ]###################### + # PHP version color. + typeset -g POWERLEVEL9K_PHP_VERSION_FOREGROUND=99 + # Show PHP version only when in a PHP project subdirectory. + typeset -g POWERLEVEL9K_PHP_VERSION_PROJECT_ONLY=true + # Custom icon. + # typeset -g POWERLEVEL9K_PHP_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ laravel_version: laravel php framework version (https://laravel.com/) ]########### + # Laravel version color. + typeset -g POWERLEVEL9K_LARAVEL_VERSION_FOREGROUND=161 + # Custom icon. + # typeset -g POWERLEVEL9K_LARAVEL_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ####################[ java_version: java version (https://www.java.com/) ]#################### + # Java version color. + typeset -g POWERLEVEL9K_JAVA_VERSION_FOREGROUND=32 + # Show java version only when in a java project subdirectory. + typeset -g POWERLEVEL9K_JAVA_VERSION_PROJECT_ONLY=true + # Show brief version. + typeset -g POWERLEVEL9K_JAVA_VERSION_FULL=false + # Custom icon. + # typeset -g POWERLEVEL9K_JAVA_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###[ package: name@version from package.json (https://docs.npmjs.com/files/package.json) ]#### + # Package color. + typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=117 + # Package format. The following parameters are available within the expansion. + # + # - P9K_PACKAGE_NAME The value of `name` field in package.json. + # - P9K_PACKAGE_VERSION The value of `version` field in package.json. + # + # typeset -g POWERLEVEL9K_PACKAGE_CONTENT_EXPANSION='${P9K_PACKAGE_NAME//\%/%%}@${P9K_PACKAGE_VERSION//\%/%%}' + # Custom icon. + # typeset -g POWERLEVEL9K_PACKAGE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #############[ rbenv: ruby version from rbenv (https://github.com/rbenv/rbenv) ]############## + # Rbenv color. + typeset -g POWERLEVEL9K_RBENV_FOREGROUND=168 + # Hide ruby version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global) + # If set to false, hide ruby version if it's the same as global: + # $(rbenv version-name) == $(rbenv global). + typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide ruby version if it's equal to "system". + typeset -g POWERLEVEL9K_RBENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_RBENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######################[ rvm: ruby version from rvm (https://rvm.io) ]######################## + # Rvm color. + typeset -g POWERLEVEL9K_RVM_FOREGROUND=168 + # Don't show @gemset at the end. + typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false + # Don't show ruby- at the front. + typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false + # Custom icon. + # typeset -g POWERLEVEL9K_RVM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ fvm: flutter version management (https://github.com/leoafarias/fvm) ]############ + # Fvm color. + typeset -g POWERLEVEL9K_FVM_FOREGROUND=38 + # Custom icon. + # typeset -g POWERLEVEL9K_FVM_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ luaenv: lua version from luaenv (https://github.com/cehoffman/luaenv) ]########### + # Lua color. + typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=32 + # Hide lua version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global) + # If set to false, hide lua version if it's the same as global: + # $(luaenv version-name) == $(luaenv global). + typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide lua version if it's equal to "system". + typeset -g POWERLEVEL9K_LUAENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_LUAENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###############[ jenv: java version from jenv (https://github.com/jenv/jenv) ]################ + # Java color. + typeset -g POWERLEVEL9K_JENV_FOREGROUND=32 + # Hide java version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global) + # If set to false, hide java version if it's the same as global: + # $(jenv version-name) == $(jenv global). + typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide java version if it's equal to "system". + typeset -g POWERLEVEL9K_JENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_JENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ plenv: perl version from plenv (https://github.com/tokuhirom/plenv) ]############ + # Perl color. + typeset -g POWERLEVEL9K_PLENV_FOREGROUND=67 + # Hide perl version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global) + # If set to false, hide perl version if it's the same as global: + # $(plenv version-name) == $(plenv global). + typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide perl version if it's equal to "system". + typeset -g POWERLEVEL9K_PLENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############ + # PHP color. + typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99 + # Hide php version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global) + # If set to false, hide php version if it's the same as global: + # $(phpenv version-name) == $(phpenv global). + typeset -g POWERLEVEL9K_PHPENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide php version if it's equal to "system". + typeset -g POWERLEVEL9K_PHPENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]####### + # Scala color. + typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160 + # Hide scala version if it doesn't come from one of these sources. + typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global) + # If set to false, hide scala version if it's the same as global: + # $(scalaenv version-name) == $(scalaenv global). + typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false + # If set to false, hide scala version if it's equal to "system". + typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true + # Custom icon. + # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]########### + # Haskell color. + typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172 + # Hide haskell version if it doesn't come from one of these sources. + # + # shell: version is set by STACK_YAML + # local: version is set by stack.yaml up the directory tree + # global: version is set by the implicit global project (~/.stack/global-project/stack.yaml) + typeset -g POWERLEVEL9K_HASKELL_STACK_SOURCES=(shell local) + # If set to false, hide haskell version if it's the same as in the implicit global project. + typeset -g POWERLEVEL9K_HASKELL_STACK_ALWAYS_SHOW=true + # Custom icon. + # typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]############# + # Show kubecontext only when the the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show kubecontext. + typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern' + + # Kubernetes context classes for the purpose of using different colors, icons and expansions with + # different contexts. + # + # POWERLEVEL9K_KUBECONTEXT_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current kubernetes context gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_KUBECONTEXT_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_KUBECONTEXT_CLASSES defines the context class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' DEFAULT) + # + # If your current kubernetes context is "deathray-testing/default", its class is TEST + # because "deathray-testing/default" doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' DEFAULT) + typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=134 + # typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Use POWERLEVEL9K_KUBECONTEXT_CONTENT_EXPANSION to specify the content displayed by kubecontext + # segment. Parameter expansions are very flexible and fast, too. See reference: + # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion. + # + # Within the expansion the following parameters are always available: + # + # - P9K_CONTENT The content that would've been displayed if there was no content + # expansion defined. + # - P9K_KUBECONTEXT_NAME The current context's name. Corresponds to column NAME in the + # output of `kubectl config get-contexts`. + # - P9K_KUBECONTEXT_CLUSTER The current context's cluster. Corresponds to column CLUSTER in the + # output of `kubectl config get-contexts`. + # - P9K_KUBECONTEXT_NAMESPACE The current context's namespace. Corresponds to column NAMESPACE + # in the output of `kubectl config get-contexts`. If there is no + # namespace, the parameter is set to "default". + # - P9K_KUBECONTEXT_USER The current context's user. Corresponds to column AUTHINFO in the + # output of `kubectl config get-contexts`. + # + # If the context points to Google Kubernetes Engine (GKE) or Elastic Kubernetes Service (EKS), + # the following extra parameters are available: + # + # - P9K_KUBECONTEXT_CLOUD_NAME Either "gke" or "eks". + # - P9K_KUBECONTEXT_CLOUD_ACCOUNT Account/project ID. + # - P9K_KUBECONTEXT_CLOUD_ZONE Availability zone. + # - P9K_KUBECONTEXT_CLOUD_CLUSTER Cluster. + # + # P9K_KUBECONTEXT_CLOUD_* parameters are derived from P9K_KUBECONTEXT_CLUSTER. For example, + # if P9K_KUBECONTEXT_CLUSTER is "gke_my-account_us-east1-a_my-cluster-01": + # + # - P9K_KUBECONTEXT_CLOUD_NAME=gke + # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=my-account + # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east1-a + # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01 + # + # If P9K_KUBECONTEXT_CLUSTER is "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster-01": + # + # - P9K_KUBECONTEXT_CLOUD_NAME=eks + # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=123456789012 + # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east-1 + # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01 + typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION= + # Show P9K_KUBECONTEXT_CLOUD_CLUSTER if it's not empty and fall back to P9K_KUBECONTEXT_NAME. + POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}' + # Append the current context's namespace if it's not "default". + POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}' + + # Custom prefix. + # typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat ' + + ################[ terraform: terraform workspace (https://www.terraform.io) ]################# + # Don't show terraform workspace if it's literally "default". + typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false + # POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current terraform workspace gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_TERRAFORM_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_TERRAFORM_CLASSES defines the workspace class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' OTHER) + # + # If your current terraform workspace is "project_test", its class is TEST because "project_test" + # doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_TERRAFORM_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_TERRAFORM_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_TERRAFORM_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' OTHER) + typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38 + # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #############[ terraform_version: terraform version (https://www.terraform.io) ]############## + # Terraform version color. + typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38 + # Custom icon. + # typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]# + # Show aws only when the the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show aws. + typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi|terragrunt' + + # POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element + # in each pair defines a pattern against which the current AWS profile gets matched. + # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below) + # that gets matched. If you unset all POWERLEVEL9K_AWS_*CONTENT_EXPANSION parameters, + # you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_AWS_CLASSES defines the profile class. Patterns are tried in order. The + # first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_AWS_CLASSES=( + # '*prod*' PROD + # '*test*' TEST + # '*' DEFAULT) + # + # If your current AWS profile is "company_test", its class is TEST + # because "company_test" doesn't match the pattern '*prod*' but does match '*test*'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_AWS_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_AWS_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_AWS_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <' + typeset -g POWERLEVEL9K_AWS_CLASSES=( + # '*prod*' PROD # These values are examples that are unlikely + # '*test*' TEST # to match your needs. Customize them as needed. + '*' DEFAULT) + typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208 + # typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # AWS segment format. The following parameters are available within the expansion. + # + # - P9K_AWS_PROFILE The name of the current AWS profile. + # - P9K_AWS_REGION The region associated with the current AWS profile. + typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}' + + #[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]# + # AWS Elastic Beanstalk environment color. + typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70 + # Custom icon. + # typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]########## + # Show azure only when the the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show azure. + typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt' + # Azure account name color. + typeset -g POWERLEVEL9K_AZURE_FOREGROUND=32 + # Custom icon. + # typeset -g POWERLEVEL9K_AZURE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]########### + # Show gcloud only when the the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show gcloud. + typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs' + # Google cloud color. + typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32 + + # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or + # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative + # enough. You can use the following parameters in the expansions. Each of them corresponds to the + # output of `gcloud` tool. + # + # Parameter | Source + # -------------------------|-------------------------------------------------------------------- + # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)' + # P9K_GCLOUD_ACCOUNT | gcloud config get-value account + # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project + # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)' + # + # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'. + # + # Obtaining project name requires sending a request to Google servers. This can take a long time + # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud + # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets + # set and gcloud prompt segment transitions to state COMPLETE. + # + # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL + # and COMPLETE. You can also hide gcloud in state PARTIAL by setting + # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and + # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty. + typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}' + typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}' + + # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name + # this often. Negative value disables periodic polling. In this mode project name is retrieved + # only when the current configuration, account or project id changes. + typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60 + + # Custom icon. + # typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]# + # Show google_app_cred only when the the command you are typing invokes one of these tools. + # Tip: Remove the next line to always show google_app_cred. + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt' + + # Google application credentials classes for the purpose of using different colors, icons and + # expansions with different credentials. + # + # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES is an array with even number of elements. The first + # element in each pair defines a pattern against which the current kubernetes context gets + # matched. More specifically, it's P9K_CONTENT prior to the application of context expansion + # (see below) that gets matched. If you unset all POWERLEVEL9K_GOOGLE_APP_CRED_*CONTENT_EXPANSION + # parameters, you'll see this value in your prompt. The second element of each pair in + # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES defines the context class. Patterns are tried in order. + # The first match wins. + # + # For example, given these settings: + # + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=( + # '*:*prod*:*' PROD + # '*:*test*:*' TEST + # '*' DEFAULT) + # + # If your current Google application credentials is "service_account deathray-testing x@y.com", + # its class is TEST because it doesn't match the pattern '* *prod* *' but does match '* *test* *'. + # + # You can define different colors, icons and content expansions for different classes: + # + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_FOREGROUND=28 + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐' + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_CONTENT_EXPANSION='$P9K_GOOGLE_APP_CRED_PROJECT_ID' + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=( + # '*:*prod*:*' PROD # These values are examples that are unlikely + # '*:*test*:*' TEST # to match your needs. Customize them as needed. + '*' DEFAULT) + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=32 + # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Use POWERLEVEL9K_GOOGLE_APP_CRED_CONTENT_EXPANSION to specify the content displayed by + # google_app_cred segment. Parameter expansions are very flexible and fast, too. See reference: + # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion. + # + # You can use the following parameters in the expansion. Each of them corresponds to one of the + # fields in the JSON file pointed to by GOOGLE_APPLICATION_CREDENTIALS. + # + # Parameter | JSON key file field + # ---------------------------------+--------------- + # P9K_GOOGLE_APP_CRED_TYPE | type + # P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id + # P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email + # + # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'. + typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}' + + ###############################[ public_ip: public IP address ]############################### + # Public IP color. + typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94 + # Custom icon. + # typeset -g POWERLEVEL9K_PUBLIC_IP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ########################[ vpn_ip: virtual private network indicator ]######################### + # VPN IP color. + typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=81 + # When on VPN, show just an icon without the IP address. + # Tip: To display the private IP address when on VPN, remove the next line. + typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION= + # Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN + # to see the name of the interface. + typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*' + # If set to true, show one segment per matching network interface. If set to false, show only + # one segment corresponding to the first matching network interface. + # Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION. + typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false + # Custom icon. + # typeset -g POWERLEVEL9K_VPN_IP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ###########[ ip: ip address and bandwidth usage for a specified network interface ]########### + # IP color. + typeset -g POWERLEVEL9K_IP_FOREGROUND=38 + # The following parameters are accessible within the expansion: + # + # Parameter | Meaning + # ----------------------+------------------------------------------- + # P9K_IP_IP | IP address + # P9K_IP_INTERFACE | network interface + # P9K_IP_RX_BYTES | total number of bytes received + # P9K_IP_TX_BYTES | total number of bytes sent + # P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt + # P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt + # P9K_IP_RX_RATE | receive rate (since last prompt) + # P9K_IP_TX_RATE | send rate (since last prompt) + typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215F⇡$P9K_IP_TX_RATE}' + # Show information for the first network interface whose name matches this regular expression. + # Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces. + typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*' + # Custom icon. + # typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐' + + #########################[ proxy: system-wide http/https/ftp proxy ]########################## + # Proxy color. + typeset -g POWERLEVEL9K_PROXY_FOREGROUND=68 + # Custom icon. + # typeset -g POWERLEVEL9K_PROXY_VISUAL_IDENTIFIER_EXPANSION='⭐' + + ################################[ battery: internal battery ]################################# + # Show battery in red when it's below this level and not connected to power supply. + typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20 + typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=160 + # Show battery in green when it's charging or fully charged. + typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=70 + # Show battery in yellow when it's discharging. + typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=178 + # Battery pictograms going from low to high level of charge. + typeset -g POWERLEVEL9K_BATTERY_STAGES='\uf58d\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf578' + # Don't show the remaining time to charge/discharge. + typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false + + #####################################[ wifi: wifi speed ]##################################### + # WiFi color. + typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68 + # Custom icon. + # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Use different colors and icons depending on signal strength ($P9K_WIFI_BARS). + # + # # Wifi colors and icons for different signal strength levels (low to high). + # typeset -g my_wifi_fg=(68 68 68 68 68) # <-- change these values + # typeset -g my_wifi_icon=('WiFi' 'WiFi' 'WiFi' 'WiFi' 'WiFi') # <-- change these values + # + # typeset -g POWERLEVEL9K_WIFI_CONTENT_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}$P9K_WIFI_LAST_TX_RATE Mbps' + # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}${my_wifi_icon[P9K_WIFI_BARS+1]}' + # + # The following parameters are accessible within the expansions: + # + # Parameter | Meaning + # ----------------------+--------------- + # P9K_WIFI_SSID | service set identifier, a.k.a. network name + # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown + # P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second + # P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0 + # P9K_WIFI_NOISE | noise in dBm, from -120 to 0 + # P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE) + + ####################################[ time: current time ]#################################### + # Current time color. + typeset -g POWERLEVEL9K_TIME_FOREGROUND=66 + # Format for the current time: 09:51:02. See `man 3 strftime`. + typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' + # If set to true, time will update when you hit enter. This way prompts for the past + # commands will contain the start times of their commands as opposed to the default + # behavior where they contain the end times of their preceding commands. + typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false + # Custom icon. + typeset -g POWERLEVEL9K_TIME_VISUAL_IDENTIFIER_EXPANSION= + # Custom prefix. + # typeset -g POWERLEVEL9K_TIME_PREFIX='%fat ' + + # Example of a user-defined prompt segment. Function prompt_example will be called on every + # prompt if `example` prompt segment is added to POWERLEVEL9K_LEFT_PROMPT_ELEMENTS or + # POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS. It displays an icon and orange text greeting the user. + # + # Type `p10k help segment` for documentation and a more sophisticated example. + function prompt_example() { + p10k segment -f 208 -i '⭐' -t 'hello, %n' + } + + # User-defined prompt segments may optionally provide an instant_prompt_* function. Its job + # is to generate the prompt segment for display in instant prompt. See + # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt. + # + # Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function + # and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k + # will replay these calls without actually calling instant_prompt_*. It is imperative that + # instant_prompt_* always makes the same `p10k segment` calls regardless of environment. If this + # rule is not observed, the content of instant prompt will be incorrect. + # + # Usually, you should either not define instant_prompt_* or simply call prompt_* from it. If + # instant_prompt_* is not defined for a segment, the segment won't be shown in instant prompt. + function instant_prompt_example() { + # Since prompt_example always makes the same `p10k segment` calls, we can call it from + # instant_prompt_example. This will give us the same `example` prompt segment in the instant + # and regular prompts. + prompt_example + } + + # User-defined prompt segments can be customized the same way as built-in segments. + # typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=208 + # typeset -g POWERLEVEL9K_EXAMPLE_VISUAL_IDENTIFIER_EXPANSION='⭐' + + # Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt + # when accepting a command line. Supported values: + # + # - off: Don't change prompt when accepting a command line. + # - always: Trim down prompt when accepting a command line. + # - same-dir: Trim down prompt when accepting a command line unless this is the first command + # typed after changing current working directory. + typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=always + + # Instant prompt mode. + # + # - off: Disable instant prompt. Choose this if you've tried instant prompt and found + # it incompatible with your zsh configuration files. + # - quiet: Enable instant prompt and don't print warnings when detecting console output + # during zsh initialization. Choose this if you've read and understood + # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt. + # - verbose: Enable instant prompt and print a warning when detecting console output during + # zsh initialization. Choose this if you've never tried instant prompt, haven't + # seen the warning, or if you are unsure what this all means. + typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet + + # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized. + # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload + # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you + # really need it. + typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true + + # If p10k is already loaded, reload configuration. + # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true. + (( ! $+functions[p10k] )) || p10k reload +} + +# Tell `p10k configure` which file it should overwrite. +typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} + +(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} +'builtin' 'unset' 'p10k_config_opts' diff --git a/zsh/.config/zsh/.zcompdump b/zsh/.config/zsh/.zcompdump new file mode 100644 index 0000000..ae202fb --- /dev/null +++ b/zsh/.config/zsh/.zcompdump @@ -0,0 +1,2007 @@ +#files: 944 version: 5.9 + +_comps=( +'-' '_precommand' +'-*-' '_loginctl' +'.' '_source' +'5g' '_go' +'5l' '_go' +'6g' '_go' +'6l' '_go' +'8g' '_go' +'8l' '_go' +'a2ps' '_a2ps' +'aaaa' '_hosts' +'aap' '_aap' +'abcde' '_abcde' +'ack' '_ack' +'ack2' '_ack' +'ack-grep' '_ack' +'ack-standalone' '_ack' +'acpi' '_acpi' +'acpitool' '_acpitool' +'acroread' '_acroread' +'adb' '_adb' +'add-zle-hook-widget' '_add-zle-hook-widget' +'add-zsh-hook' '_add-zsh-hook' +'admin' '_sccs' +'afew' '_afew' +'alacritty' '_alacritty' +'ali' '_mh' +'alias' '_alias' +'amaya' '_webbrowser' +'analyseplugin' '_analyseplugin' +'android' '_android' +'animate' '_imagemagick' +'anno' '_mh' +'ansible' '_ansible' +'ansible-config' '_ansible' +'ansible-console' '_ansible' +'ansible-doc' '_ansible' +'ansible-galaxy' '_ansible' +'ansible-inventory' '_ansible' +'ansible-playbook' '_ansible' +'ansible-pull' '_ansible' +'ansible-vault' '_ansible' +'ant' '_ant' +'antiword' '_antiword' +'aodh' '_openstack' +'aoss' '_precommand' +'apache2ctl' '_apachectl' +'apachectl' '_apachectl' +'aplay' '_alsa-utils' +'apm' '_apm' +'appletviewer' '_java' +'apropos' '_man' +'apvlv' '_pdf' +'archlinux-java' '_archlinux-java' +'arduino-ctags' '_ctags' +'arecord' '_alsa-utils' +'arena' '_webbrowser' +'_arguments' '__arguments' +'arp' '_arp' +'arping' '_arping' +'-array-value-' '_value' +'artisan' '_artisan' +'asciidoctor' '_asciidoctor' +'asciinema' '_asciinema' +'ash' '_sh' +'-assign-parameter-' '_assign' +'at' '_at' +'atach' '_atach' +'atq' '_at' +'atrm' '_at' +'attr' '_attr' +'augtool' '_augeas' +'autoload' '_typeset' +'avahi-browse' '_avahi' +'avahi-browse-domains' '_avahi' +'avahi-resolve' '_avahi' +'avahi-resolve-address' '_avahi' +'avahi-resolve-host-name' '_avahi' +'awk' '_awk' +'b2sum' '_md5sum' +'barbican' '_openstack' +'base32' '_base64' +'base64' '_base64' +'basename' '_basename' +'basenc' '_basenc' +'bash' '_bash' +'bat' '_bat' +'batch' '_at' +'baz' '_baz' +'beep' '_beep' +'bg' '_jobs_bg' +'bibtex' '_bibtex' +'bindkey' '_bindkey' +'bison' '_bison' +'bitcoin-cli' '_bitcoin-cli' +'bluetoothctl' '_bluetoothctl' +'bmake' '_make' +'bogofilter' '_bogofilter' +'bogotune' '_bogofilter' +'bogoutil' '_bogofilter' +'bower' '_bower' +'bpython' '_bpython' +'bpython2' '_bpython' +'bpython2-gtk' '_bpython' +'bpython2-urwid' '_bpython' +'bpython3' '_bpython' +'bpython3-gtk' '_bpython' +'bpython3-urwid' '_bpython' +'bpython-gtk' '_bpython' +'bpython-urwid' '_bpython' +'-brace-parameter-' '_brace_parameter' +'brctl' '_brctl' +'bsdgrep' '_grep' +'bsdtar' '_tar' +'btdownloadcurses' '_bittorrent' +'btdownloadgui' '_bittorrent' +'btdownloadheadless' '_bittorrent' +'btlaunchmany' '_bittorrent' +'btlaunchmanycurses' '_bittorrent' +'btm' '_btm' +'btmakemetafile' '_bittorrent' +'btreannounce' '_bittorrent' +'btrename' '_bittorrent' +'btrfs' '_btrfs' +'btshowmetainfo' '_bittorrent' +'bttrack' '_bittorrent' +'buildhash' '_ispell' +'builtin' '_builtin' +'bundle' '_bundle' +'bunzip2' '_bzip2' +'burst' '_mh' +'bwrap' '_bwrap' +'bzcat' '_bzip2' +'bzegrep' '_grep' +'bzfgrep' '_grep' +'bzgrep' '_grep' +'bzip2' '_bzip2' +'bzip2recover' '_bzip2' +'bzr' '_bzr' +'c++' '_gcc' +'cabal' '_cabal' +'caffeinate' '_caffeinate' +'cal' '_cal' +'calendar' '_calendar' +'calibre' '_calibre' +'calibredb' '_calibre' +'calibre-debug' '_calibre' +'calibre-server' '_calibre' +'calibre-smtp' '_calibre' +'cap' '_cap' +'cargo' '_cargo' +'cask' '_cask' +'cat' '_cat' +'catchsegv' '_precommand' +'cc' '_gcc' +'ccache' '_ccache' +'ccal' '_ccal' +'cd' '_cd' +'cdc' '_sccs' +'cdcd' '_cdcd' +'cdr' '_cdr' +'cdrdao' '_cdrdao' +'cdrecord' '_cdrecord' +'ceilometer' '_openstack' +'certtool' '_gnutls' +'cf' '_cf' +'cftp' '_twisted' +'chage' '_users' +'chattr' '_chattr' +'chcon' '_chcon' +'chdir' '_cd' +'chfn' '_users' +'chgrp' '_chown' +'chimera' '_webbrowser' +'chkconfig' '_chkconfig' +'chkstow' '_stow' +'chmod' '_chmod' +'choc' '_choc' +'choom' '_choom' +'chown' '_chown' +'chpass' '_chsh' +'chromium' '_chromium' +'chroot' '_chroot' +'chrt' '_chrt' +'chsh' '_chsh' +'ci' '_rcs' +'cifsiostat' '_sysstat' +'cinder' '_openstack' +'ckeygen' '_twisted' +'cksum' '_cksum' +'clang' '_gcc' +'clang++' '_gcc' +'clay' '_clay' +'clear' '_nothing' +'cloudkitty' '_openstack' +'clusterdb' '_postgresql' +'cmake' '_cmake' +'cmp' '_cmp' +'co' '_rcs' +'code' '_code' +'coffee' '_coffee' +'column' '_column' +'comb' '_sccs' +'combine' '_imagemagick' +'combinediff' '_patchutils' +'comm' '_comm' +'-command-' '_autocd' +'command' '_command' +'-command-line-' '_normal' +'comp' '_mh' +'compadd' '_compadd' +'compdef' '_compdef' +'composer' '_composer' +'composite' '_imagemagick' +'compress' '_compress' +'conan' '_conan' +'conch' '_twisted' +'concourse' '_concourse' +'-condition-' '_condition' +'config.status' '_configure' +'configure' '_configure' +'console' '_console' +'convert' '_imagemagick' +'cowsay' '_cowsay' +'cowthink' '_cowsay' +'cp' '_cp' +'cpio' '_cpio' +'cplay' '_cplay' +'cppcheck' '_cppcheck' +'cpupower' '_cpupower' +'createdb' '_pgsql_utils' +'createuser' '_pgsql_utils' +'crontab' '_crontab' +'crsh' '_cssh' +'cryptsetup' '_cryptsetup' +'cscope' '_cscope' +'csh' '_sh' +'csplit' '_csplit' +'cssh' '_cssh' +'ctags' '_ctags' +'ctags-exuberant' '_ctags' +'ctags-universal' '_ctags' +'curl' '_curl' +'cut' '_cut' +'cvs' '_cvs' +'dad' '_dad' +'darcs' '_darcs' +'dash' '_sh' +'date' '_date' +'dbus-launch' '_dbus' +'dbus-monitor' '_dbus' +'dbus-send' '_dbus' +'dconf' '_dconf' +'dcop' '_dcop' +'dcopclient' '_dcop' +'dcopfind' '_dcop' +'dcopobject' '_dcop' +'dcopref' '_dcop' +'dcopstart' '_dcop' +'dd' '_dd' +'debuild' '_debuild' +'declare' '_typeset' +'-default-' '_default' +'delta' '_sccs' +'deno' '_deno' +'designate' '_openstack' +'devtodo' '_devtodo' +'df' '_df' +'dget' '_dget' +'dhclient' '_dhclient' +'dhclient3' '_dhclient' +'dhcpcd' '_dhcpcd' +'diana' '_diana' +'dict' '_dict' +'diff' '_diff' +'diff3' '_diff3' +'diffstat' '_diffstat' +'dig' '_dig' +'dillo' '_webbrowser' +'dircmp' '_directories' +'dirs' '_dirs' +'disable' '_disable' +'disown' '_jobs_fg' +'display' '_imagemagick' +'dist' '_mh' +'django-admin' '_django' +'django-admin.py' '_django' +'dkms' '_dkms' +'dmake' '_make' +'dmesg' '_dmesg' +'dmidecode' '_dmidecode' +'doas' '_doas' +'docpad' '_docpad' +'domainname' '_yp' +'dos2unix' '_dos2unix' +'drill' '_drill' +'dropbox' '_dropbox' +'dropdb' '_pgsql_utils' +'dropuser' '_pgsql_utils' +'drush' '_drush' +'dsh' '_dsh' +'dtruss' '_dtruss' +'du' '_du' +'dvibook' '_dvi' +'dviconcat' '_dvi' +'dvicopy' '_dvi' +'dvidvi' '_dvi' +'dvipdf' '_dvi' +'dvips' '_dvi' +'dviselect' '_dvi' +'dvitodvi' '_dvi' +'dvitype' '_dvi' +'dwb' '_webbrowser' +'e2label' '_e2label' +'eatmydata' '_precommand' +'ebook-convert' '_calibre' +'ebook-edit' '_calibre' +'ebook-meta' '_calibre' +'ebook-polish' '_calibre' +'ebook-viewer' '_calibre' +'ecasound' '_ecasound' +'ecdsautil' '_ecdsautil' +'echotc' '_echotc' +'echoti' '_echoti' +'ed' '_ed' +'egrep' '_grep' +'elfdump' '_elfdump' +'elinks' '_elinks' +'emulate' '_emulate' +'emulator' '_emulator' +'enable' '_enable' +'enscript' '_enscript' +'entr' '_entr' +'env' '_env' +'envdir' '_envdir' +'eog' '_eog' +'epdfview' '_pdf' +'epsffit' '_psutils' +'-equal-' '_equal' +'erb' '_ruby' +'espeak' '_espeak' +'etags' '_etags' +'ethtool' '_ethtool' +'eu-nm' '_nm' +'eu-objdump' '_objdump' +'eu-readelf' '_readelf' +'eu-strings' '_strings' +'eval' '_precommand' +'eview' '_vim' +'evim' '_vim' +'evince' '_evince' +'ex' '_vi' +'exa' '_exa' +'exec' '_exec' +'expand' '_unexpand' +'export' '_typeset' +'exportfs' '_exportfs' +'express' '_webbrowser' +'extcheck' '_java' +'extractres' '_psutils' +'fab' '_fab' +'fail2ban-client' '_fail2ban-client' +'fakeroot' '_fakeroot' +'false' '_nothing' +'fc' '_fc' +'fc-list' '_xft_fonts' +'fc-match' '_xft_fonts' +'fd' '_fd' +'feh' '_feh' +'fetch-ebook-metadata' '_calibre' +'fetchmail' '_fetchmail' +'ffind' '_ffind' +'ffmpeg' '_ffmpeg' +'fg' '_jobs_fg' +'fgrep' '_grep' +'figlet' '_figlet' +'filterdiff' '_patchutils' +'find' '_find' +'findaffix' '_ispell' +'findmnt' '_findmnt' +'finger' '_finger' +'firefox' '_mozilla' +'-first-' '_first' +'fixdlsrps' '_psutils' +'fixfmps' '_psutils' +'fixmacps' '_psutils' +'fixpsditps' '_psutils' +'fixpspps' '_psutils' +'fixscribeps' '_psutils' +'fixtpps' '_psutils' +'fixwfwps' '_psutils' +'fixwpps' '_psutils' +'fixwwps' '_psutils' +'flac' '_flac' +'fleetctl' '_fleetctl' +'flex' '_flex' +'flex++' '_flex' +'flipdiff' '_patchutils' +'flist' '_mh' +'flists' '_mh' +'float' '_typeset' +'flutter' '_flutter' +'fly' '_concourse' +'fmt' '_fmt' +'fmttest' '_mh' +'fned' '_zed' +'fnext' '_mh' +'fold' '_fold' +'folder' '_mh' +'folders' '_mh' +'force' '_force' +'fortune' '_fortune' +'forw' '_mh' +'fprev' '_mh' +'free' '_free' +'freebsd-make' '_make' +'freezer' '_openstack' +'fsh' '_fsh' +'ftp' '_hosts' +'functions' '_typeset' +'fuser' '_fuser' +'fusermount' '_fusermount' +'fwhois' '_whois' +'fwupdmgr' '_fwupdmgr' +'g++' '_gcc' +'galeon' '_webbrowser' +'gas' '_gas' +'gawk' '_awk' +'gb2sum' '_md5sum' +'gbase32' '_base64' +'gbase64' '_base64' +'gbasename' '_basename' +'gcat' '_cat' +'gcc' '_gcc' +'gccgo' '_go' +'gchgrp' '_chown' +'gchmod' '_chmod' +'gchown' '_chown' +'gchroot' '_chroot' +'gcksum' '_cksum' +'gcmp' '_cmp' +'gcomm' '_comm' +'gcore' '_gcore' +'gcp' '_cp' +'gcut' '_cut' +'gdate' '_date' +'gdb' '_gdb' +'gdd' '_dd' +'gdf' '_df' +'gdiff' '_diff' +'gdu' '_du' +'geany' '_geany' +'gegrep' '_grep' +'gem' '_gem' +'genisoimage' '_genisoimage' +'genv' '_env' +'get' '_sccs' +'getafm' '_psutils' +'getconf' '_getconf' +'getent' '_getent' +'getfacl' '_getfacl' +'getfacl.exe' '_getfacl' +'getfattr' '_attr' +'getmail' '_getmail' +'getopt' '_getopt' +'getopts' '_vars' +'gex' '_vim' +'gexpand' '_unexpand' +'gfgrep' '_grep' +'gfind' '_find' +'gfmt' '_fmt' +'gfold' '_fold' +'ggetopt' '_getopt' +'ggrep' '_grep' +'ggv' '_gnome-gv' +'ghc' '_ghc' +'ghci' '_ghc' +'ghc-pkg' '_ghc' +'ghead' '_head' +'ghostscript' '_ghostscript' +'ghostview' '_pspdf' +'gid' '_id' +'ginstall' '_install' +'gist' '_gist' +'git' '_git' +'git-cvsserver' '_git' +'git-flow' '_git-flow' +'git-journal' '_git-journal' +'gitk' '_git' +'git-pulls' '_git-pulls' +'git-receive-pack' '_git' +'git-revise' '_git-revise' +'git-shell' '_git' +'git-upload-archive' '_git' +'git-upload-pack' '_git' +'git-wtf' '_git-wtf' +'gjoin' '_join' +'glance' '_openstack' +'glances' '_glances' +'gln' '_ln' +'global' '_global' +'glocate' '_locate' +'gls' '_ls' +'gm' '_graphicsmagick' +'gmake' '_make' +'gmd5sum' '_md5sum' +'gmkdir' '_mkdir' +'gmkfifo' '_mkfifo' +'gmknod' '_mknod' +'gmktemp' '_mktemp' +'gmplayer' '_mplayer' +'gmv' '_mv' +'gnl' '_nl' +'gnocchi' '_openstack' +'gnome-gv' '_gnome-gv' +'gnumfmt' '_numfmt' +'gnupod_addsong' '_gnupod' +'gnupod_addsong.pl' '_gnupod' +'gnupod_check' '_gnupod' +'gnupod_check.pl' '_gnupod' +'gnupod_INIT' '_gnupod' +'gnupod_INIT.pl' '_gnupod' +'gnupod_search' '_gnupod' +'gnupod_search.pl' '_gnupod' +'gnutls-cli' '_gnutls' +'gnutls-cli-debug' '_gnutls' +'gnutls-serv' '_gnutls' +'go' '_golang' +'god' '_od' +'gofmt' '_go' +'google' '_google' +'gpasswd' '_gpasswd' +'gpaste' '_paste' +'gpatch' '_patch' +'gpg' '_gpg' +'gpg2' '_gpg' +'gpgconf' '_gpgconf' +'gpgv' '_gpg' +'gpg-zip' '_gpg' +'gphoto2' '_gphoto2' +'gprintenv' '_printenv' +'gprof' '_gprof' +'gqview' '_gqview' +'gradle' '_gradle' +'gradlew' '_gradle' +'grail' '_webbrowser' +'greadlink' '_readlink' +'grep' '_grep' +'grepdiff' '_patchutils' +'grm' '_rm' +'grmdir' '_rmdir' +'groff' '_groff' +'groupadd' '_user_admin' +'groupdel' '_groups' +'groupmod' '_user_admin' +'groups' '_users' +'growisofs' '_growisofs' +'gs' '_ghostscript' +'gsbj' '_pspdf' +'gsdj' '_pspdf' +'gsdj500' '_pspdf' +'gsed' '_sed' +'gseq' '_seq' +'gsettings' '_gsettings' +'gsha1sum' '_md5sum' +'gsha224sum' '_md5sum' +'gsha256sum' '_md5sum' +'gsha384sum' '_md5sum' +'gsha512sum' '_md5sum' +'gshred' '_shred' +'gshuf' '_shuf' +'gslj' '_pspdf' +'gslp' '_pspdf' +'gsnd' '_pspdf' +'gsort' '_sort' +'gsplit' '_split' +'gstat' '_stat' +'gstdbuf' '_stdbuf' +'gstrings' '_strings' +'gstty' '_stty' +'gsum' '_cksum' +'gtac' '_tac' +'gtail' '_tail' +'gtar' '_tar' +'gtee' '_tee' +'gtimeout' '_timeout' +'gtk-launch' '_gtk-launch' +'gtouch' '_touch' +'gtr' '_tr' +'gtty' '_tty' +'guilt' '_guilt' +'guilt-add' '_guilt' +'guilt-applied' '_guilt' +'guilt-delete' '_guilt' +'guilt-files' '_guilt' +'guilt-fold' '_guilt' +'guilt-fork' '_guilt' +'guilt-header' '_guilt' +'guilt-help' '_guilt' +'guilt-import' '_guilt' +'guilt-import-commit' '_guilt' +'guilt-init' '_guilt' +'guilt-new' '_guilt' +'guilt-next' '_guilt' +'guilt-patchbomb' '_guilt' +'guilt-pop' '_guilt' +'guilt-prev' '_guilt' +'guilt-push' '_guilt' +'guilt-rebase' '_guilt' +'guilt-refresh' '_guilt' +'guilt-rm' '_guilt' +'guilt-series' '_guilt' +'guilt-status' '_guilt' +'guilt-top' '_guilt' +'guilt-unapplied' '_guilt' +'guname' '_uname' +'gunexpand' '_unexpand' +'guniq' '_uniq' +'gunzip' '_gzip' +'guptime' '_uptime' +'gv' '_gv' +'gview' '_vim' +'gvim' '_vim' +'gvimdiff' '_vim' +'gwc' '_wc' +'gwho' '_who' +'gxargs' '_xargs' +'gzcat' '_gzip' +'gzegrep' '_grep' +'gzfgrep' '_grep' +'gzgrep' '_grep' +'gzilla' '_webbrowser' +'gzip' '_gzip' +'hash' '_hash' +'hd' '_hexdump' +'head' '_head' +'heat' '_openstack' +'help' '_sccs' +'hexdump' '_hexdump' +'hilite' '_precommand' +'histed' '_zed' +'history' '_fc' +'hledger' '_hledger' +'homestead' '_homestead' +'host' '_host' +'hostname' '_hostname' +'hotjava' '_webbrowser' +'htop' '_htop' +'http' '_httpie' +'ibus' '_ibus' +'iceweasel' '_mozilla' +'icombine' '_ispell' +'iconv' '_iconv' +'iconvconfig' '_iconvconfig' +'id' '_id' +'identify' '_imagemagick' +'ifconfig' '_ifconfig' +'ifdown' '_net_interfaces' +'iftop' '_iftop' +'ifup' '_net_interfaces' +'ijoin' '_ispell' +'import' '_imagemagick' +'inc' '_mh' +'includeres' '_psutils' +'include-what-you-use' '_include-what-you-use' +'info' '_texinfo' +'infocmp' '_terminals' +'initctl' '_initctl' +'initdb' '_pgsql_utils' +'insmod' '_modutils' +'install' '_install' +'install-info' '_texinfo' +'integer' '_typeset' +'interdiff' '_patchutils' +'inxi' '_inxi' +'ionice' '_ionice' +'iostat' '_iostat' +'ip' '_ip' +'ip6tables' '_iptables' +'ip6tables-restore' '_iptables' +'ip6tables-save' '_iptables' +'ipkg' '_opkg' +'ipsec' '_ipsec' +'ipset' '_ipset' +'iptables' '_iptables' +'iptables-restore' '_iptables' +'iptables-save' '_iptables' +'irb' '_ruby' +'ironic' '_openstack' +'irssi' '_irssi' +'isag' '_sysstat' +'ispell' '_ispell' +'iwconfig' '_iwconfig' +'jadetex' '_tex' +'jar' '_java' +'jarsigner' '_java' +'java' '_java' +'javac' '_java' +'javadoc' '_java' +'javah' '_java' +'javap' '_java' +'jdb' '_java' +'jmeter' '_jmeter' +'jmeter-plugins' '_jmeter-plugins' +'jobs' '_jobs_builtin' +'joe' '_joe' +'join' '_join' +'jonas' '_jonas' +'jq' '_jq' +'jrnl' '_jrnl' +'kak' '_kak' +'kdeconnect-cli' '_kdeconnect' +'keystone' '_openstack' +'keytool' '_java' +'kfmclient' '_kfmclient' +'kill' '_kill' +'killall' '_killall' +'killall5' '_killall' +'kioclient' '_kfmclient' +'kitchen' '_kitchen' +'kitty' '_kitty' +'knife' '_knife' +'knock' '_knock' +'konqueror' '_webbrowser' +'kpartx' '_kpartx' +'kpdf' '_pdf' +'ksh' '_sh' +'ksh88' '_sh' +'ksh93' '_sh' +'kvno' '_kvno' +'last' '_last' +'lastb' '_last' +'latex' '_tex' +'latexmk' '_tex' +'ldconfig' '_ldconfig' +'ldconfig.real' '_ldconfig' +'ldd' '_ldd' +'less' '_less' +'let' '_math' +'lf' '_lf' +'lftp' '_ncftp' +'lha' '_lha' +'libinput' '_libinput' +'light' '_webbrowser' +'lilypond' '_lilypond' +'limit' '_limit' +'links' '_links' +'links2' '_links' +'linux' '_uml' +'lldb' '_lldb' +'llvm-g++' '_gcc' +'llvm-gcc' '_gcc' +'llvm-objdump' '_objdump' +'ln' '_ln' +'loadkeys' '_loadkeys' +'local' '_typeset' +'locale' '_locale' +'localedef' '_localedef' +'locate' '_locate' +'log' '_nothing' +'logger' '_logger' +'loginctl' '_loginctl' +'logname' '_nothing' +'look' '_look' +'losetup' '_losetup' +'lp' '_lp' +'lpadmin' '_lp' +'lpinfo' '_lp' +'lpoptions' '_lp' +'lpq' '_lp' +'lpr' '_lp' +'lprm' '_lp' +'lpstat' '_lp' +'lrf2lrs' '_calibre' +'lrfviewer' '_calibre' +'ls' '_ls' +'lsattr' '_lsattr' +'lsblk' '_lsblk' +'lsdiff' '_patchutils' +'lsinitcpio' '_mkinitcpio' +'lsmod' '_modutils' +'lsns' '_lsns' +'lsof' '_lsof' +'lsusb' '_lsusb' +'ltrace' '_ltrace' +'lua' '_lua' +'luarocks' '_luarocks' +'lunchy' '_lunchy' +'lynx' '_lynx' +'lz4' '_lz4' +'lz4c' '_lz4' +'lz4c32' '_lz4' +'lz4cat' '_lz4' +'lzcat' '_xz' +'lzma' '_xz' +'lzop' '_lzop' +'mac2unix' '_dos2unix' +'magnum' '_openstack' +'mail' '_mail' +'Mail' '_mail' +'mailx' '_mail' +'make' '_make' +'makeinfo' '_texinfo' +'makepkg' '_pacman' +'man' '_man' +'manage.py' '_django' +'manila' '_openstack' +'mark' '_mh' +'mat' '_mat' +'mat2' '_mat2' +'-math-' '_math' +'matlab' '_matlab' +'mattrib' '_mtools' +'mc' '_mc' +'mcd' '_mtools' +'mcopy' '_mtools' +'md2' '_cksum' +'md4' '_cksum' +'md5' '_cksum' +'md5sum' '_md5sum' +'mdadm' '_mdadm' +'mdel' '_mtools' +'mdeltree' '_mtools' +'mdir' '_mtools' +'mdu' '_mtools' +'mencal' '_mencal' +'mere' '_mere' +'merge' '_rcs' +'metaflac' '_flac' +'mformat' '_mtools' +'mgv' '_pspdf' +'mhfixmsg' '_mh' +'mhlist' '_mh' +'mhmail' '_mh' +'mhn' '_mh' +'mhparam' '_mh' +'mhpath' '_mh' +'mhshow' '_mh' +'mhstore' '_mh' +'middleman' '_middleman' +'mii-tool' '_mii-tool' +'mina' '_mina' +'mistral' '_openstack' +'mix' '_mix' +'mkdir' '_mkdir' +'mkfifo' '_mkfifo' +'mkinitcpio' '_mkinitcpio' +'mkisofs' '_growisofs' +'mknod' '_mknod' +'mksh' '_sh' +'mktemp' '_mktemp' +'mktunes' '_gnupod' +'mktunes.pl' '_gnupod' +'mlabel' '_mtools' +'mlocate' '_locate' +'mmd' '_mtools' +'mmm' '_webbrowser' +'mmount' '_mtools' +'mmove' '_mtools' +'modinfo' '_modutils' +'modprobe' '_modutils' +'module' '_module' +'mogrify' '_imagemagick' +'monasca' '_openstack' +'mondoarchive' '_mondo' +'montage' '_imagemagick' +'moosic' '_moosic' +'Mosaic' '_webbrowser' +'mosh' '_mosh' +'mount' '_mount' +'mozilla' '_mozilla' +'mozilla-firefox' '_mozilla' +'mozilla-xremote-client' '_mozilla' +'mpc' '_mpc' +'mplayer' '_mplayer' +'mpstat' '_sysstat' +'mpv' '_mpv' +'mr' '_myrepos' +'mrd' '_mtools' +'mread' '_mtools' +'mren' '_mtools' +'msgchk' '_mh' +'mssh' '_mssh' +'mt' '_mt' +'mtn' '_monotone' +'mtoolstest' '_mtools' +'mtr' '_mtr' +'mtype' '_mtools' +'munchlist' '_ispell' +'mupdf' '_mupdf' +'murano' '_openstack' +'mush' '_mail' +'mussh' '_mussh' +'mutt' '_mutt' +'mux' '_tmuxinator' +'mv' '_mv' +'mvim' '_vim' +'mvn' '_mvn' +'mvnDebug' '_mvn' +'mx' '_hosts' +'mysql' '_mysql_utils' +'mysqladmin' '_mysql_utils' +'mysqldiff' '_mysqldiff' +'mysqldump' '_mysql_utils' +'mysqlimport' '_mysql_utils' +'mysqlshow' '_mysql_utils' +'nail' '_mail' +'nano' '_nano' +'nanoc' '_nanoc' +'native2ascii' '_java' +'nautilus' '_nautilus' +'nawk' '_awk' +'nc' '_netcat' +'ncal' '_cal' +'ncftp' '_ncftp' +'ncl' '_nedit' +'nedit' '_nedit' +'nedit-nc' '_nedit' +'netcat' '_netcat' +'netrik' '_webbrowser' +'netscape' '_netscape' +'netstat' '_netstat' +'neutron' '_openstack' +'new' '_mh' +'newgrp' '_groups' +'next' '_mh' +'nft' '_nftables' +'nginx' '_nginx' +'ngrep' '_ngrep' +'nice' '_nice' +'nkf' '_nkf' +'nl' '_nl' +'nm' '_nm' +'nmap' '_nmap' +'nmblookup' '_samba' +'nmcli' '_networkmanager' +'nocorrect' '_precommand' +'node' '_node' +'noglob' '_precommand' +'nohup' '_precommand' +'nova' '_openstack' +'npm' '_npm' +'ns' '_hosts' +'nsenter' '_nsenter' +'nslookup' '_nslookup' +'ntalk' '_other_accounts' +'numfmt' '_numfmt' +'nvim' '_vim' +'nvimpager' '_nvimpager' +'nvm' '_nvm' +'objdump' '_objdump' +'od' '_od' +'ogg123' '_vorbis' +'oggdec' '_vorbis' +'oggenc' '_vorbis' +'ogginfo' '_vorbis' +'oksh' '_sh' +'okular' '_okular' +'openrc' '_openrc' +'openssl' '_openssl' +'openstack' '_openstack' +'openvpn3' '_openvpn3' +'opera' '_webbrowser' +'opera-next' '_webbrowser' +'opkg' '_opkg' +'optirun' '_optirun' +'opusdec' '_opustools' +'opusenc' '_opustools' +'opusinfo' '_opustools' +'p4' '_perforce' +'p4d' '_perforce' +'pacat' '_pulseaudio' +'pack' '_pack' +'packf' '_mh' +'pacman' '_pacman' +'pacman-conf' '_pacman' +'pacman-key' '_pacman' +'pacman.static' '_pacman' +'pacmd' '_pulseaudio' +'pactl' '_pulseaudio' +'padsp' '_pulseaudio' +'pandoc' '_pandoc' +'paplay' '_pulseaudio' +'-parameter-' '_parameter' +'parec' '_pulseaudio' +'parecord' '_pulseaudio' +'passwd' '_users' +'paste' '_paste' +'pasuspender' '_pulseaudio' +'patch' '_patch' +'patool' '_patool' +'pax' '_pax' +'pcat' '_pack' +'pcp-htop' '_htop' +'pcred' '_pids' +'pdf2dsc' '_pdf' +'pdf2ps' '_pdf' +'pdffonts' '_pdf' +'pdfimages' '_pdf' +'pdfinfo' '_pdf' +'pdfjadetex' '_tex' +'pdflatex' '_tex' +'pdfopt' '_pdf' +'pdftex' '_tex' +'pdftexi2dvi' '_texinfo' +'pdftk' '_pdftk' +'pdftopbm' '_pdf' +'pdftops' '_pdf' +'pdftotext' '_pdf' +'pdksh' '_sh' +'perf' '_perf' +'periscope' '_periscope' +'perl' '_perl' +'perldoc' '_perldoc' +'pfiles' '_pids' +'pflags' '_pids' +'pg_config' '_postgresql' +'pg_ctl' '_postgresql' +'pg_dump' '_pgsql_utils' +'pg_dumpall' '_pgsql_utils' +'pg_isready' '_postgresql' +'pgrep' '_pgrep' +'pg_restore' '_pgsql_utils' +'pg_upgrade' '_postgresql' +'phing' '_phing' +'php' '_php' +'pick' '_mh' +'picocom' '_picocom' +'pidof' '_pidof' +'pidstat' '_sysstat' +'pigz' '_gzip' +'pine' '_pine' +'pinef' '_pine' +'pinfo' '_texinfo' +'ping' '_ping' +'ping6' '_ping' +'pixz' '_pixz' +'pkcon' '_pkcon' +'pkgadd' '_pkgadd' +'pkg-config' '_pkg-config' +'pkginfo' '_pkginfo' +'pkgrm' '_pkgrm' +'pkill' '_pgrep' +'play' '_play' +'pldd' '_pids' +'pm2' '_pm2' +'pmake' '_make' +'pman' '_perl_modules' +'pmap' '_pmap' +'pmcat' '_perl_modules' +'pmdesc' '_perl_modules' +'pmeth' '_perl_modules' +'pmexp' '_perl_modules' +'pmfunc' '_perl_modules' +'pmload' '_perl_modules' +'pmls' '_perl_modules' +'pmpath' '_perl_modules' +'pmvers' '_perl_modules' +'podgrep' '_perl_modules' +'podpath' '_perl_modules' +'podtoc' '_perl_modules' +'poff' '_pon' +'policytool' '_java' +'pon' '_pon' +'popd' '_directory_stack' +'port' '_port' +'postconf' '_postfix' +'postgres' '_postgresql' +'postmaster' '_postgresql' +'postqueue' '_postfix' +'postsuper' '_postfix' +'pr' '_pr' +'prev' '_mh' +'print' '_print' +'printenv' '_printenv' +'printf' '_print' +'procs' '_procs' +'prompt' '_prompt' +'protoc' '_protoc' +'prove' '_prove' +'prs' '_sccs' +'prt' '_sccs' +'prun' '_pids' +'ps' '_ps' +'ps2ascii' '_pspdf' +'ps2epsi' '_postscript' +'ps2pdf' '_postscript' +'ps2pdf12' '_postscript' +'ps2pdf13' '_postscript' +'ps2pdf14' '_postscript' +'ps2pdfwr' '_postscript' +'ps2ps' '_postscript' +'psbook' '_psutils' +'psed' '_sed' +'psig' '_pids' +'psmerge' '_psutils' +'psmulti' '_postscript' +'psnup' '_psutils' +'psql' '_pgsql_utils' +'psresize' '_psutils' +'psselect' '_psutils' +'pstack' '_pids' +'pstoedit' '_pspdf' +'pstop' '_pids' +'pstops' '_psutils' +'pstotgif' '_pspdf' +'pswrap' '_postscript' +'ptx' '_ptx' +'pulseaudio' '_pulseaudio' +'pump' '_pump' +'pushd' '_cd' +'pv' '_pv' +'pwait' '_pids' +'pwdx' '_pids' +'pwgen' '_pwgen' +'pygmentize' '_pygmentize' +'pyhtmlizer' '_twisted' +'qdbus' '_qdbus' +'qiv' '_qiv' +'qmk' '_qmk' +'quilt' '_quilt' +'r' '_fc' +'rails' '_rails' +'rake' '_rake' +'ralio' '_ralio' +'ranlib' '_ranlib' +'rar' '_rar' +'rbw' '_rbw' +'rc' '_sh' +'rclone' '_rclone' +'rcp' '_rlogin' +'rcs' '_rcs' +'rcsdiff' '_rcs' +'rc-service' '_rc-service' +'rc-status' '_rc-status' +'rc-update' '_rc-update' +'rdesktop' '_rdesktop' +'read' '_read' +'readelf' '_readelf' +'readlink' '_readlink' +'readonly' '_typeset' +'rec' '_redis-cli' +'-redirect-' '_redirect' +'-redirect-,<,bunzip2' '_bzip2' +'-redirect-,<,bzip2' '_bzip2' +'-redirect-,>,bzip2' '_bzip2' +'-redirect-,<,compress' '_compress' +'-redirect-,>,compress' '_compress' +'-redirect-,-default-,-default-' '_files' +'-redirect-,<,gunzip' '_gzip' +'-redirect-,<,gzip' '_gzip' +'-redirect-,>,gzip' '_gzip' +'-redirect-,<,uncompress' '_compress' +'-redirect-,<,unxz' '_xz' +'-redirect-,<,xz' '_xz' +'-redirect-,>,xz' '_xz' +'redis-cli' '_redis-cli' +'refile' '_mh' +'rehash' '_hash' +'reindexdb' '_postgresql' +'reload' '_initctl' +'remsh' '_rlogin' +'renice' '_renice' +'repl' '_mh' +'restart' '_initctl' +'retawq' '_webbrowser' +'rfkill' '_rfkill' +'rg' '_rg' +'rgrep' '_grep' +'rgview' '_vim' +'rgvim' '_vim' +'ri' '_ri' +'rkt' '_rkt' +'rlogin' '_rlogin' +'rm' '_rm' +'rmd160' '_cksum' +'rmdel' '_sccs' +'rmdir' '_rmdir' +'rmf' '_mh' +'rmic' '_java' +'rmid' '_java' +'rmiregistry' '_java' +'rmlint' '_rmlint' +'rmlint.sh' '_rmlint' +'rmm' '_mh' +'rmmod' '_modutils' +'route' '_route' +'rrdtool' '_rrdtool' +'rsh' '_rlogin' +'rslsync' '_rslsync' +'rspec' '_rspec' +'rsvm' '_rsvm' +'rsync' '_rsync' +'rtin' '_tin' +'rubber' '_rubber' +'rubber-info' '_rubber' +'rubber-pipe' '_rubber' +'rubocop' '_rubocop' +'ruby' '_ruby' +'ruby-mri' '_ruby' +'run-help' '_run-help' +'rup' '_hosts' +'rusage' '_precommand' +'rview' '_vim' +'rvim' '_vim' +'rwho' '_hosts' +'rxvt' '_urxvt' +'s2p' '_sed' +'sact' '_sccs' +'sadf' '_sysstat' +'sahara' '_openstack' +'sar' '_sysstat' +'sbt' '_sbt' +'scala' '_scala' +'scalac' '_scala' +'scan' '_mh' +'sccs' '_sccs' +'sccsdiff' '_sccs' +'sched' '_sched' +'schedtool' '_schedtool' +'scons' '_scons' +'scp' '_ssh' +'screen' '_screen' +'script' '_script' +'scriptreplay' '_script' +'scrub' '_scrub' +'sdd' '_sdd' +'seaf-cli' '_seafile' +'sed' '_sed' +'senlin' '_openstack' +'seq' '_seq' +'serialver' '_java' +'service' '_service' +'set' '_set' +'setcap' '_setcap' +'setfacl' '_setfacl' +'setfacl.exe' '_setfacl' +'setfattr' '_attr' +'setopt' '_setopt' +'setpriv' '_setpriv' +'setsid' '_setsid' +'setup.py' '_setup.py' +'setxkbmap' '_setxkbmap' +'sfdx' '_sfdx' +'sftp' '_ssh' +'sh' '_sh' +'sha1' '_cksum' +'sha1sum' '_md5sum' +'sha224sum' '_md5sum' +'sha256' '_cksum' +'sha256sum' '_md5sum' +'sha384' '_cksum' +'sha384sum' '_md5sum' +'sha512' '_cksum' +'sha512sum' '_md5sum' +'sha512t256' '_cksum' +'shasum' '_shasum' +'shellcheck' '_shellcheck' +'shell-script' '_loginctl' +'shift' '_arrays' +'show' '_mh' +'showchar' '_psutils' +'showmount' '_showmount' +'showoff' '_showoff' +'shred' '_shred' +'shuf' '_shuf' +'shutdown' '_shutdown' +'sisu' '_sisu' +'skein1024' '_cksum' +'skein256' '_cksum' +'skein512' '_cksum' +'skipstone' '_webbrowser' +'slabtop' '_slabtop' +'slitex' '_tex' +'slocate' '_locate' +'slogin' '_ssh' +'slrn' '_slrn' +'smartctl' '_smartmontools' +'smbclient' '_samba' +'smbcontrol' '_samba' +'smbstatus' '_samba' +'soa' '_hosts' +'socket' '_socket' +'sort' '_sort' +'sortm' '_mh' +'source' '_source' +'spamassassin' '_spamassassin' +'split' '_split' +'splitdiff' '_patchutils' +'sqlite' '_sqlite' +'sqlite3' '_sqlite' +'sqsh' '_sqsh' +'sr' '_surfraw' +'srm' '_srm' +'srptool' '_gnutls' +'ss' '_ss' +'ssh' '_ssh' +'ssh-add' '_ssh' +'ssh-agent' '_ssh' +'ssh-copy-id' '_ssh' +'sshfs' '_sshfs' +'ssh-keygen' '_ssh' +'ssh-keyscan' '_ssh' +'stack' '_stack' +'star' '_tar' +'start' '_initctl' +'stat' '_stat' +'status' '_initctl' +'stdbuf' '_stdbuf' +'stg' '_stgit' +'stop' '_initctl' +'stow' '_stow' +'strace' '_strace' +'strace64' '_strace' +'strftime' '_strftime' +'strings' '_strings' +'strip' '_strip' +'strongswan' '_ipsec' +'stty' '_stty' +'su' '_su' +'subl' '_subl' +'subliminal' '_subliminal' +'-subscript-' '_subscript' +'sudo' '_sudo' +'sudoedit' '_sudo' +'sum' '_cksum' +'supervisorctl' '_supervisorctl' +'surfraw' '_surfraw' +'sv' '_runit' +'svm' '_svm' +'svn' '_subversion' +'svnadmin' '_subversion' +'svnadmin-static' '_subversion' +'svnlite' '_subversion' +'svnliteadmin' '_subversion' +'swaks' '_swaks' +'swanctl' '_swanctl' +'swift' '_swift' +'swiftc' '_swift' +'sync' '_nothing' +'sysctl' '_sysctl' +'tac' '_tac' +'tacker' '_openstack' +'tail' '_tail' +'talk' '_other_accounts' +'tar' '_tar' +'tardy' '_tardy' +'tarsnap' '_tarsnap' +'tcpdump' '_tcpdump' +'tcp_open' '_tcpsys' +'tcptraceroute' '_tcptraceroute' +'tcsh' '_sh' +'tda' '_devtodo' +'tdd' '_devtodo' +'tde' '_devtodo' +'tdr' '_devtodo' +'teamocil' '_teamocil' +'tee' '_tee' +'telnet' '_telnet' +'tex' '_tex' +'texi2any' '_texinfo' +'texi2dvi' '_texinfo' +'texi2pdf' '_texinfo' +'texindex' '_texinfo' +'tg' '_topgit' +'thor' '_thor' +'tidy' '_tidy' +'tig' '_git' +'-tilde-' '_tilde' +'time' '_precommand' +'timeout' '_timeout' +'times' '_nothing' +'tin' '_tin' +'tkconch' '_twisted' +'tkinfo' '_texinfo' +'tla' '_tla' +'tldr' '_tldr' +'tload' '_tload' +'tmux' '_tmux' +'tmuxinator' '_tmuxinator' +'tmuxp' '_tmuxp' +'todo' '_devtodo' +'todo.sh' '_todo.sh' +'toilet' '_toilet' +'top' '_top' +'totdconfig' '_totd' +'touch' '_touch' +'tox' '_tox' +'tpb' '_tpb' +'tput' '_tput' +'tr' '_tr' +'tracepath' '_tracepath' +'tracepath6' '_tracepath' +'traceroute' '_hosts' +'transmission-remote' '_transmission' +'trap' '_trap' +'trash' '_trash-put' +'trash-empty' '_trash-empty' +'trash-list' '_trash-list' +'trash-put' '_trash-put' +'trash-restore' '_trash-restore' +'tree' '_tree' +'trial' '_twisted' +'trove' '_openstack' +'true' '_nothing' +'truncate' '_truncate' +'truss' '_truss' +'tryaffix' '_ispell' +'tty' '_tty' +'ttyctl' '_ttyctl' +'tunctl' '_uml' +'tune2fs' '_tune2fs' +'tunes2pod' '_gnupod' +'tunes2pod.pl' '_gnupod' +'twidge' '_twidge' +'twist' '_twisted' +'twistd' '_twisted' +'txt' '_hosts' +'type' '_which' +'typeset' '_typeset' +'udisksctl' '_udisksctl' +'ufw' '_ufw' +'ulimit' '_ulimit' +'uml_mconsole' '_uml' +'uml_moo' '_uml' +'uml_switch' '_uml' +'umount' '_mount' +'unace' '_unace' +'unalias' '_aliases' +'uname' '_uname' +'uncompress' '_compress' +'unexpand' '_unexpand' +'unfunction' '_functions' +'unget' '_sccs' +'unhash' '_unhash' +'uniq' '_uniq' +'unison' '_unison' +'units' '_units' +'unix2dos' '_dos2unix' +'unix2mac' '_dos2unix' +'unlimit' '_limits' +'unlz4' '_lz4' +'unlzma' '_xz' +'unpack' '_pack' +'unpigz' '_gzip' +'unrar' '_rar' +'unset' '_vars' +'unsetopt' '_setopt' +'unshare' '_unshare' +'unwrapdiff' '_patchutils' +'unxz' '_xz' +'unzip' '_zip' +'uptime' '_uptime' +'urxvt' '_urxvt' +'urxvt256c' '_urxvt' +'urxvt256cc' '_urxvt' +'urxvt256c-ml' '_urxvt' +'urxvt256c-mlc' '_urxvt' +'urxvtc' '_urxvt' +'useradd' '_user_admin' +'userdel' '_users' +'usermod' '_user_admin' +'vacuumdb' '_pgsql_utils' +'vagrant' '_vagrant' +'val' '_sccs' +'valgrind' '_valgrind' +'-value-' '_value' +'-value-,ADB_TRACE,-default-' '_adb' +'-value-,ANDROID_LOG_TAGS,-default-' '_adb' +'-value-,ANDROID_SERIAL,-default-' '_adb' +'-value-,ANSIBLE_INVENTORY_ENABLED,-default-' '_ansible' +'-value-,ANSIBLE_STDOUT_CALLBACK,-default-' '_ansible' +'-value-,ANT_ARGS,-default-' '_ant' +'-value-,CFLAGS,-default-' '_gcc' +'-value-,CMAKE_GENERATOR,-default-' '_cmake' +'-value-,CPPFLAGS,-default-' '_gcc' +'-value-,CXXFLAGS,-default-' '_gcc' +'-value-,-default-,-command-' '_zargs' +'-value-,-default-,-default-' '_value' +'-value-,DISPLAY,-default-' '_x_display' +'-value-,GREP_OPTIONS,-default-' '_grep' +'-value-,GZIP,-default-' '_gzip' +'-value-,LANG,-default-' '_locales' +'-value-,LANGUAGE,-default-' '_locales' +'-value-,LD_DEBUG,-default-' '_ld_debug' +'-value-,LDFLAGS,-default-' '_gcc' +'-value-,LESSCHARSET,-default-' '_less' +'-value-,LESS,-default-' '_less' +'-value-,LOOPDEV_DEBUG,-default-' '_losetup' +'-value-,LPDEST,-default-' '_printers' +'-value-,MPD_HOST,-default' '_mpc' +'-value-,P4CLIENT,-default-' '_perforce' +'-value-,P4MERGE,-default-' '_perforce' +'-value-,P4PORT,-default-' '_perforce' +'-value-,P4USER,-default-' '_perforce' +'-value-,PERLDOC,-default-' '_perldoc' +'-value-,PRINTER,-default-' '_printers' +'-value-,PROMPT2,-default-' '_ps1234' +'-value-,PROMPT3,-default-' '_ps1234' +'-value-,PROMPT4,-default-' '_ps1234' +'-value-,PROMPT,-default-' '_ps1234' +'-value-,PS1,-default-' '_ps1234' +'-value-,PS2,-default-' '_ps1234' +'-value-,PS3,-default-' '_ps1234' +'-value-,PS4,-default-' '_ps1234' +'-value-,RPROMPT2,-default-' '_ps1234' +'-value-,RPROMPT,-default-' '_ps1234' +'-value-,RPS1,-default-' '_ps1234' +'-value-,RPS2,-default-' '_ps1234' +'-value-,SPROMPT,-default-' '_ps1234' +'-value-,TERM,-default-' '_terminals' +'-value-,TERMINFO_DIRS,-default-' '_dir_list' +'-value-,TZ,-default-' '_time_zone' +'-value-,VALGRIND_OPTS,-default-' '_valgrind' +'-value-,WWW_HOME,-default-' '_urls' +'-value-,XML_CATALOG_FILES,-default-' '_xmlsoft' +'-value-,XZ_DEFAULTS,-default-' '_xz' +'-value-,XZ_OPT,-default-' '_xz' +'-vared-' '_in_vared' +'vared' '_vared' +'VBoxHeadless' '_virtualbox' +'vboxmanage' '_virtualbox' +'VBoxManage' '_virtualbox' +'vcs_info_hookadd' '_vcs_info' +'vcs_info_hookdel' '_vcs_info' +'vi' '_vi' +'view' '_vi' +'vim' '_vim' +'vimdiff' '_vim' +'virsh' '_libvirt' +'virt-admin' '_libvirt' +'virt-host-validate' '_libvirt' +'virt-pki-validate' '_libvirt' +'virt-xml-validate' '_libvirt' +'visudo' '_visudo' +'vitrage' '_openstack' +'vmstat' '_vmstat' +'vncserver' '_vnc' +'vncviewer' '_vnc' +'vnstat' '_vnstat' +'vorbiscomment' '_vorbis' +'vpnc' '_vpnc' +'vpnc-connect' '_vpnc' +'vserver' '_vserver' +'w' '_w' +'w3m' '_w3m' +'wait' '_wait' +'watch' '_watch' +'watcher' '_openstack' +'wc' '_wc' +'wemux' '_wemux' +'wget' '_wget' +'wg-quick' '_wg-quick' +'what' '_sccs' +'whatis' '_man' +'whence' '_which' +'where' '_which' +'whereis' '_whereis' +'which' '_which' +'who' '_who' +'whoami' '_nothing' +'whois' '_whois' +'whom' '_mh' +'wiggle' '_wiggle' +'wipefs' '_wipefs' +'wodim' '_cdrecord' +'wpa_cli' '_wpa_cli' +'write' '_users_on' +'www' '_webbrowser' +'xargs' '_xargs' +'xattr' '_attr' +'xauth' '_xauth' +'xautolock' '_xautolock' +'xclip' '_xclip' +'xdpyinfo' '_x_utils' +'xdvi' '_xdvi' +'xelatex' '_tex' +'xetex' '_tex' +'xev' '_x_utils' +'xfd' '_x_utils' +'xfig' '_xfig' +'xfontsel' '_x_utils' +'xfreerdp' '_rdesktop' +'xhost' '_x_utils' +'xinput' '_xinput' +'xkill' '_x_utils' +'xli' '_xloadimage' +'xloadimage' '_xloadimage' +'xlsatoms' '_x_utils' +'xlsclients' '_x_utils' +'xml' '_xmlstarlet' +'xmllint' '_xmlsoft' +'xmlstarlet' '_xmlstarlet' +'xmms2' '_xmms2' +'xmodmap' '_xmodmap' +'xmosaic' '_webbrowser' +'xon' '_x_utils' +'xournal' '_xournal' +'xpdf' '_xpdf' +'xping' '_hosts' +'xprop' '_x_utils' +'xrandr' '_xrandr' +'xrdb' '_x_utils' +'xscreensaver-command' '_xscreensaver' +'xsel' '_xsel' +'xset' '_xset' +'xsetbg' '_xloadimage' +'xsetroot' '_x_utils' +'xsltproc' '_xmlsoft' +'xterm' '_xterm' +'xtightvncviewer' '_vnc' +'xtp' '_imagemagick' +'xv' '_xv' +'xview' '_xloadimage' +'xvnc4viewer' '_vnc' +'xvncviewer' '_vnc' +'xwd' '_x_utils' +'xwininfo' '_x_utils' +'xwit' '_xwit' +'xwud' '_x_utils' +'xxd' '_xxd' +'xz' '_xz' +'xzcat' '_xz' +'yafc' '_yafc' +'yaourt' '_yaourt' +'yaourt.static' '_yaourt' +'yarn' '_yarn' +'yash' '_sh' +'yay' '_yay' +'ypbind' '_yp' +'ypcat' '_yp' +'ypmatch' '_yp' +'yppasswd' '_yp' +'yppoll' '_yp' +'yppush' '_yp' +'ypserv' '_yp' +'ypset' '_yp' +'ypwhich' '_yp' +'ypxfr' '_yp' +'ytalk' '_other_accounts' +'yt-dlp' '_yt-dlp' +'zargs' '_zargs' +'zcalc' '_zcalc' +'-zcalc-line-' '_zcalc_line' +'zcash-cli' '_zcash-cli' +'zcat' '_zcat' +'zcompile' '_zcompile' +'zcp' '_zmv' +'zdb' '_zfs' +'zdelattr' '_zattr' +'zdump' '_zdump' +'zeal' '_zeal' +'zed' '_zed' +'zegrep' '_grep' +'zen' '_webbrowser' +'zf_chgrp' '_chown' +'zf_chmod' '_chmod' +'zf_chown' '_chown' +'zfgrep' '_grep' +'zf_ln' '_ln' +'zf_mkdir' '_mkdir' +'zf_mv' '_mv' +'zf_rm' '_rm' +'zf_rmdir' '_rmdir' +'zfs' '_zfs' +'zgetattr' '_zattr' +'zgrep' '_grep' +'zip' '_zip' +'zipinfo' '_zip' +'zle' '_zle' +'zlistattr' '_zattr' +'zln' '_zmv' +'zmail' '_mail' +'zmodload' '_zmodload' +'zmv' '_zmv' +'zone' '_hosts' +'zparseopts' '_zparseopts' +'zpool' '_zfs' +'zpty' '_zpty' +'zsetattr' '_zattr' +'zsh' '_zsh' +'zsh-mime-handler' '_zsh-mime-handler' +'zsocket' '_zsocket' +'zstat' '_stat' +'zstyle' '_zstyle' +'ztodo' '_ztodo' +'zun' '_openstack' +'zxpdf' '_xpdf' +) + +_services=( +'bzcat' 'bunzip2' +'gchgrp' 'chgrp' +'gchown' 'chown' +'gnupod_addsong.pl' 'gnupod_addsong' +'gnupod_check.pl' 'gnupod_check' +'gnupod_INIT.pl' 'gnupod_INIT' +'gnupod_search.pl' 'gnupod_search' +'gpg2' 'gpg' +'gzcat' 'gunzip' +'iceweasel' 'firefox' +'lzcat' 'unxz' +'lzma' 'xz' +'Mail' 'mail' +'mailx' 'mail' +'mktunes.pl' 'mktunes' +'nail' 'mail' +'ncl' 'nc' +'nedit-nc' 'nc' +'pacman.static' 'pacman' +'pcat' 'unpack' +'-redirect-,<,bunzip2' 'bunzip2' +'-redirect-,<,bzip2' 'bzip2' +'-redirect-,>,bzip2' 'bunzip2' +'-redirect-,<,compress' 'compress' +'-redirect-,>,compress' 'uncompress' +'-redirect-,<,gunzip' 'gunzip' +'-redirect-,<,gzip' 'gzip' +'-redirect-,>,gzip' 'gunzip' +'-redirect-,<,uncompress' 'uncompress' +'-redirect-,<,unxz' 'unxz' +'-redirect-,<,xz' 'xz' +'-redirect-,>,xz' 'unxz' +'remsh' 'rsh' +'slogin' 'ssh' +'svnadmin-static' 'svnadmin' +'svnlite' 'svn' +'svnliteadmin' 'svnadmin' +'tunes2pod.pl' 'tunes2pod' +'unlzma' 'unxz' +'VBoxHeadless' 'vboxheadless' +'vboxmanage' 'vboxmanage' +'VBoxManage' 'vboxmanage' +'xelatex' 'latex' +'xetex' 'tex' +'xzcat' 'unxz' +'yaourt.static' 'yaourt' +'zf_chgrp' 'chgrp' +'zf_chown' 'chown' +) + +_patcomps=( +'*/(init|rc[0-9S]#).d/*' '_init_d' +) + +_postpatcomps=( +'_*' '_compadd' +'c++-*' '_gcc' +'g++-*' '_gcc' +'gcc-*' '_gcc' +'gem[0-9.]#' '_gem' +'lua[0-9.-]##' '_lua' +'(p[bgpn]m*|*top[bgpn]m)' '_pbm' +'php[0-9.-]' '_php' +'pip[0-9.]#' '_pip' +'pydoc[0-9.]#' '_pydoc' +'python[0-9.]#' '_python' +'qemu(|-system-*)' '_qemu' +'rmlint.*.sh' '_rmlint' +'(ruby|[ei]rb)[0-9.]#' '_ruby' +'shasum(|5).*' '_shasum' +'(texi(2*|ndex))' '_texi' +'(tiff*|*2tiff|pal2rgb)' '_tiff' +'-value-,CCACHE_*,-default-' '_ccache' +'-value-,CGO*,-default-' '_golang' +'-value-,(ftp|http(|s))_proxy,-default-' '_urls' +'-value-,GO*,-default-' '_golang' +'-value-,LC_*,-default-' '_locales' +'-value-,*path,-default-' '_directories' +'-value-,*PATH,-default-' '_dir_list' +'-value-,RUBY(LIB|OPT|PATH),-default-' '_ruby' +'*/X11(|R<4->)/*' '_x_arguments' +'yodl(|2*)' '_yodl' +'zf*' '_zftp' +) + +_compautos=( +'_call_program' '+X' +) + +zle -C _bash_complete-word .complete-word _bash_completions +zle -C _bash_list-choices .list-choices _bash_completions +zle -C _complete_debug .complete-word _complete_debug +zle -C _complete_help .complete-word _complete_help +zle -C _complete_tag .complete-word _complete_tag +zle -C _correct_filename .complete-word _correct_filename +zle -C _correct_word .complete-word _correct_word +zle -C _expand_alias .complete-word _expand_alias +zle -C _expand_word .complete-word _expand_word +zle -C _history-complete-newer .complete-word _history_complete_word +zle -C _history-complete-older .complete-word _history_complete_word +zle -C _list_expansions .list-choices _expand_word +zle -C _most_recent_file .complete-word _most_recent_file +zle -C _next_tags .list-choices _next_tags +zle -C _read_comp .complete-word _read_comp +bindkey '^X^R' _read_comp +bindkey '^X?' _complete_debug +bindkey '^XC' _correct_filename +bindkey '^Xa' _expand_alias +bindkey '^Xc' _correct_word +bindkey '^Xd' _list_expansions +bindkey '^Xe' _expand_word +bindkey '^Xh' _complete_help +bindkey '^Xm' _most_recent_file +bindkey '^Xn' _next_tags +bindkey '^Xt' _complete_tag +bindkey '^X~' _bash_list-choices +bindkey '^[,' _history-complete-newer +bindkey '^[/' _history-complete-older +bindkey '^[~' _bash_complete-word + +autoload -Uz _afew _alacritty _android _archlinux-java _artisan \ + _atach _bat _bitcoin-cli _bluetoothctl _bower \ + _btm _bundle _bwrap _caffeinate _calibre \ + _cap _cargo _cask _ccache _cf \ + _choc _chromium _cmake _code _coffee \ + _composer _conan _concourse _console _cppcheck \ + _curl _dad _debuild _deno _dget \ + _dhcpcd _diana _docpad _drush _ecdsautil \ + _emulator _envdir _exa _exportfs _fab \ + _fail2ban-client _fd _ffind _fleetctl _flutter \ + _force _fwupdmgr _gas _ghc _gist \ + _git-flow _git-journal _git-pulls _git-revise _git-wtf \ + _glances _golang _google _gpgconf _gtk-launch \ + _hledger _homestead _httpie _ibus _include-what-you-use \ + _inxi _jmeter _jmeter-plugins _jonas _jrnl \ + _kak _kdeconnect _kitchen _kitty _knife \ + _language_codes _less _lf _libinput _lilypond \ + _loginctl _lunchy _mc _middleman _mina \ + _mix _mkinitcpio _mpv _mssh _mussh \ + _mvn _nano _nanoc _nftables _node \ + _nvimpager _nvm _openrc _openssl _openvpn3 \ + _optirun _opustools _pacman _patool _perf \ + _periscope _pgsql_utils _phing _pixz _pkcon \ + _play _pm2 _port _procs _protoc \ + _pulseaudio _pygmentize _qmk _rails _ralio \ + _rbw _rc-service _rc-status _rc-update _redis-cli \ + _rfkill _rg _rkt _rmlint _rslsync \ + _rspec _rsvm _rubocop _sbt _scala \ + _scrub _sdd _setcap _setup.py _sfdx \ + _shellcheck _showoff _srm _stack _subl \ + _subliminal _supervisorctl _svm _tarsnap _teamocil \ + _thor _tldr _tmuxinator _tmuxp _tox \ + _trash-empty _trash-list _trash-put _trash-restore _udisksctl \ + _ufw _vagrant _virtualbox _vnstat _wemux \ + _wg-quick _xinput _xsel _yaourt _yarn \ + _yay _yt-dlp _zcash-cli _cdr _all_labels \ + _all_matches _alternative _approximate _arg_compile _arguments \ + _bash_completions _cache_invalid _call_function _combination _complete \ + _complete_debug _complete_help _complete_help_generic _complete_tag _comp_locale \ + _correct _correct_filename _correct_word _describe _description \ + _dispatch _expand _expand_alias _expand_word _extensions \ + _external_pwds _generic _guard _history _history_complete_word \ + _ignored _list _main_complete _match _menu \ + _message _most_recent_file _multi_parts _next_label _next_tags \ + _normal _nothing _numbers _oldlist _pick_variant \ + _prefix _read_comp _regex_arguments _regex_words _requested \ + _retrieve_cache _sep_parts _sequence _set_command _setup \ + _store_cache _sub_commands _tags _user_expand _values \ + _wanted _acpi _acpitool _alsa-utils _analyseplugin \ + _basenc _brctl _btrfs _capabilities _chattr \ + _chcon _choom _chrt _cpupower _cryptsetup \ + _dkms _e2label _ethtool _findmnt _free \ + _fuse_arguments _fusermount _fuse_values _gpasswd _htop \ + _iconvconfig _ionice _ipset _iptables _iwconfig \ + _kpartx _losetup _lsattr _lsblk _lsns \ + _lsusb _ltrace _mat _mat2 _mdadm \ + _mii-tool _modutils _mondo _networkmanager _nsenter \ + _opkg _perf _pidof _pmap _qdbus \ + _schedtool _selinux_contexts _selinux_roles _selinux_types _selinux_users \ + _setpriv _setsid _slabtop _ss _sshfs \ + _strace _sysstat _tload _tpb _tracepath \ + _tune2fs _uml _unshare _valgrind _vserver \ + _wakeup_capable_devices _wipefs _wpa_cli _a2ps _aap \ + _abcde _absolute_command_paths _ack _adb _ansible \ + _ant _antiword _apachectl _apm _arch_archives \ + _arch_namespace _arp _arping _asciidoctor _asciinema \ + _at _attr _augeas _avahi _awk \ + _base64 _basename _bash _baudrates _baz \ + _beep _bibtex _bind_addresses _bison _bittorrent \ + _bogofilter _bpf_filters _bpython _bzip2 _bzr \ + _cabal _cal _calendar _canonical_paths _cat \ + _ccal _cdcd _cdrdao _cdrecord _chkconfig \ + _chmod _chown _chroot _chsh _cksum \ + _clay _cmdambivalent _cmdstring _cmp _column \ + _comm _composer _compress _configure _cowsay \ + _cp _cpio _cplay _crontab _cscope \ + _csplit _cssh _ctags _ctags_tags _curl \ + _cut _cvs _darcs _date _date_formats \ + _dates _dbus _dconf _dd _devtodo \ + _df _dhclient _dict _dict_words _diff \ + _diff3 _diff_options _diffstat _dig _directories \ + _dir_list _django _dmesg _dmidecode _dns_types \ + _doas _domains _dos2unix _drill _dropbox \ + _dsh _dtruss _du _dvi _ecasound \ + _ed _elfdump _elinks _email_addresses _enscript \ + _entr _env _espeak _etags _fakeroot \ + _feh _fetchmail _ffmpeg _figlet _file_modes \ + _files _file_systems _find _find_net_interfaces _finger \ + _flac _flex _fmt _fold _fortune \ + _fsh _fuser _gcc _gcore _gdb \ + _gem _genisoimage _getconf _getent _getfacl \ + _getmail _getopt _ghostscript _git _global \ + _global_tags _gnu_generic _gnupod _gnutls _go \ + _gpg _gphoto2 _gprof _gradle _graphicsmagick \ + _grep _groff _groups _growisofs _gsettings \ + _guilt _gzip _have_glob_qual _head _hexdump \ + _host _hostname _hosts _iconv _id \ + _ifconfig _iftop _imagemagick _initctl _init_d \ + _install _iostat _ip _ipsec _irssi \ + _ispell _java _java_class _joe _join \ + _jq _killall _knock _kvno _last \ + _ldconfig _ldd _ld_debug _less _lha \ + _libvirt _links _list_files _lldb _ln \ + _loadkeys _locale _localedef _locales _locate \ + _logger _look _lp _ls _lsof \ + _lua _luarocks _lynx _lz4 _lzop \ + _mail _mailboxes _make _man _md5sum \ + _mencal _mh _mime_types _mkdir _mkfifo \ + _mknod _mktemp _module _monotone _moosic \ + _mosh _mount _mpc _mt _mtools \ + _mtr _mutt _mv _my_accounts _myrepos \ + _mysqldiff _mysql_utils _ncftp _netcat _net_interfaces \ + _netstat _newsgroups _nginx _ngrep _nice \ + _nkf _nl _nm _nmap _npm \ + _nslookup _numfmt _objdump _object_files _od \ + _openstack _opustools _other_accounts _pack _pandoc \ + _paste _patch _patchutils _path_commands _path_files \ + _pax _pbm _pdf _perforce _perl \ + _perl_basepods _perldoc _perl_modules _pgids _pgrep \ + _php _picocom _pids _pine _ping \ + _pip _pkgadd _pkg-config _pkginfo _pkg_instance \ + _pkgrm _pon _ports _postfix _postgresql \ + _postscript _pr _printenv _printers _process_names \ + _prove _ps _pspdf _psutils _ptx \ + _pump _pv _pwgen _pydoc _python \ + _python_modules _qemu _quilt _rake _ranlib \ + _rar _rclone _rcs _readelf _readlink \ + _remote_files _renice _ri _rlogin _rm \ + _rmdir _route _rrdtool _rsync _rubber \ + _ruby _runit _samba _sccs _scons \ + _screen _script _seafile _sed _seq \ + _service _services _setfacl _sh _shasum \ + _showmount _shred _shuf _shutdown _signals \ + _sisu _slrn _smartmontools _socket _sort \ + _spamassassin _split _sqlite _sqsh _ssh \ + _ssh_hosts _stat _stdbuf _stgit _stow \ + _strings _strip _stty _su _subversion \ + _sudo _surfraw _swaks _swanctl _swift \ + _sys_calls _sysctl _tac _tail _tar \ + _tar_archive _tardy _tcpdump _tcptraceroute _tee \ + _telnet _terminals _tex _texi _texinfo \ + _tidy _tiff _tilde_files _timeout _time_zone \ + _tin _tla _tmux _todo.sh _toilet \ + _top _topgit _totd _touch _tput \ + _tr _transmission _tree _truncate _truss \ + _tty _ttys _twidge _twisted _umountable \ + _unace _uname _unexpand _uniq _unison \ + _units _uptime _urls _user_admin _user_at_host \ + _users _users_on _vi _vim _visudo \ + _vmstat _vorbis _vpnc _w _w3m \ + _watch _wc _webbrowser _wget _whereis \ + _who _whois _wiggle _xargs _xmlsoft \ + _xmlstarlet _xmms2 _xxd _xz _yafc \ + _yodl _yp _zcat _zdump _zfs \ + _zfs_dataset _zfs_pool _zip _zsh _acroread \ + _code _dcop _eog _evince _geany \ + _gnome-gv _gqview _gv _kdeconnect _kfmclient \ + _matlab _mozilla _mplayer _mupdf _nautilus \ + _nedit _netscape _okular _pdftk _qiv \ + _rdesktop _setxkbmap _sublimetext _urxvt _vnc \ + _x_arguments _xauth _xautolock _x_borderwidth _xclip \ + _x_color _x_colormapid _x_cursor _x_display _xdvi \ + _x_extension _xfig _x_font _xft_fonts _x_geometry \ + _xinput _x_keysym _xloadimage _x_locale _x_modifier \ + _xmodmap _x_name _xournal _xpdf _xrandr \ + _x_resource _xscreensaver _x_selection_timeout _xset _xt_arguments \ + _xterm _x_title _xt_session_id _x_utils _xv \ + _x_visual _x_window _xwit _zeal _add-zle-hook-widget \ + _add-zsh-hook _alias _aliases __arguments _arrays \ + _assign _autocd _bindkey _brace_parameter _builtin \ + _cd _command _command_names _compadd _compdef \ + _completers _condition _default _delimiters _directory_stack \ + _dirs _disable _dynamic_directory_name _echotc _echoti \ + _emulate _enable _equal _exec _fc \ + _file_descriptors _first _functions _globflags _globqual_delims \ + _globquals _hash _history_modifiers _in_vared _jobs \ + _jobs_bg _jobs_builtin _jobs_fg _kill _limit \ + _limits _math _math_params _mere _module_math_func \ + _options _options_set _options_unset _parameter _parameters \ + _precommand _print _prompt _ps1234 _read \ + _redirect _run-help _sched _set _setopt \ + _source _strftime _subscript _suffix_alias_files _tcpsys \ + _tilde _trap _ttyctl _typeset _ulimit \ + _unhash _user_math_func _value _vared _vars \ + _vcs_info _vcs_info_hooks _wait _which _widgets \ + _zargs _zattr _zcalc _zcalc_line _zcompile \ + _zed _zftp _zle _zmodload _zmv \ + _zparseopts _zpty _zsh-mime-handler _zsocket _zstyle \ + _ztodo +autoload -Uz +X _call_program + +typeset -gUa _comp_assocs +_comp_assocs=( '' ) diff --git a/zsh/.config/zsh/.zprofile b/zsh/.config/zsh/.zprofile new file mode 120000 index 0000000..555eb07 --- /dev/null +++ b/zsh/.config/zsh/.zprofile @@ -0,0 +1 @@ +/home/ross/.config/shell/profile
\ No newline at end of file diff --git a/zsh/.config/zsh/.zshopts b/zsh/.config/zsh/.zshopts new file mode 100644 index 0000000..810e2d1 --- /dev/null +++ b/zsh/.config/zsh/.zshopts @@ -0,0 +1,71 @@ +# History settings +HISTSIZE=50000 +SAVEHIST=10000 +HISTFILE=$HOME/.zsh_history + +setopt append_history # append the history +setopt inc_append_history # append to history in the current + # session and not just when the session ends +setopt share_history # share history between sessions +#setopt extended_history # include statistics of when/how long/etc a + # command has run +#setopt hist_ignore_dups # do not store dupes executed after eachother +setopt hist_ignore_all_dups # removes copies of the same line +setopt hist_expire_dups_first # removes copies when the histfile fills up +setopt hist_save_no_dups # don't save dupes from the same session +setopt hist_find_no_dups # if we find dupes in the history, don't show + # them in editor commands) +setopt hist_reduce_blanks # remove blank lines from the command which + # mean nothing to the shell + +# Disable this on boxes that are affected by bug +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=924736 +is-at-least 5.5 && unsetopt hist_reduce_blanks +# bugfix is incoming, lets see what it does +is-at-least 5.7.2 && setopt hist_reduce_blanks + +setopt hist_ignore_space # lines starting with space don't go into the + # history +setopt no_hist_beep # silence..! +setopt hist_verify +setopt hist_no_store # don't store history/fc commands +#setopt hist_no_functions # don't show history of function definitions + +setopt bg_nice # nice bg commands +setopt notify # notify when a command returns exit code + +setopt no_beep # silence..! + +unsetopt auto_cd # disable $ ./bin as cd ./bin +setopt extendedglob # ls ^bla.* will not show ^bla.txt for example + +setopt correct # correct incorrent cmd's +setopt correctall # correct everything, use + # `nocorrect mv foo bar` to negate this feature + # for a command + +setopt hash_list_all # fill the lookup table for tab completions + +unsetopt promptcr # prevent the prompt overwriting output when + # there is no newline + +unsetopt nomatch # +setopt prompt_subst # Enable prompt substition + +setopt glob_subst # global substitution +#setopt globdots # Also look for . files + +setopt longlistjobs +setopt completeinword + +# Directories +setopt auto_pushd # cd foo == pushd foo +setopt pushd_ignore_dups # no duplicates in the list +setopt pushdminus +setopt auto_name_dirs # foo=/path/to/foo is the same as + # hash -d foo=/path/to/foo + +# Misc +setopt interactivecomments # $ # foo doesn't become an error when hitting + # enter + diff --git a/zsh/.config/zsh/.zshrc b/zsh/.config/zsh/.zshrc new file mode 100644 index 0000000..7bb97bf --- /dev/null +++ b/zsh/.config/zsh/.zshrc @@ -0,0 +1,65 @@ +eval "$(antidot init)" +eval "$(lesspipe.sh)" +# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.config/zsh/.zshrc. +# Initialization code that may require console input (password prompts, [y/n] +# confirmations, etc.) must go above this block; everything else may go below. +if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then + source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" +fi + +# Luke's config for the Zoomer Shell + +source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme + +# Enable colors and change prompt: +autoload -U colors && colors # Load colors +setopt autocd # Automatically cd into typed directory. +#stty stop undef # Disable ctrl-s to freeze terminal. +setopt interactive_comments +setopt complete_aliases + +# History in cache directory: +HISTSIZE=10000000 +SAVEHIST=10000000 +HISTFILE=~/.cache/zsh/history + + +# Load aliases and shortcuts if existent. +[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutrc" ] && source "${XDG_CONFIG_HOME:-$HOME/.config}/shell/shortcutrc" +[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/shell/aliasrc" ] && source "${XDG_CONFIG_HOME:-$HOME/.config}/shell/aliasrc" +[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/shell/zshnameddirrc" ] && source "${XDG_CONFIG_HOME:-$HOME/.config}/shell/zshnameddirrc" +[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/.zshopts" ] && source +"${XDG_CONFIG_HOME:-$HOME/.config}/zsh/.zshopts" + + + +# Basic auto/tab complete: +fpath=(~/.config/zsh/completions $fpath) +autoload -U compinit +zstyle ':completion:*' menu select +zmodload zsh/complist +compinit +#compdef cat='bat' +_comp_options+=(globdots) # Include hidden files. + +PROG=tea _CLI_ZSH_AUTOCOMPLETE_HACK=1 source "/home/ross/.config/tea/autocomplete.zsh" +# To customize prompt, run `p10k configure` or edit ~/.config/zsh/.p10k.zsh. +[[ ! -f ~/.config/zsh/.p10k.zsh ]] || source ~/.config/zsh/.p10k.zsh +[[ ! -f ~/.config/zsh/keybindings.zsh ]] || source ~/.config/zsh/keybindings.zsh + +#source /usr/share/nvm/init-nvm.sh + +# zsh parameter completion for the dotnet CLI + +_dotnet_zsh_complete() +{ + local completions=("$(dotnet complete "$words")") + + reply=( "${(ps:\n:)completions}" ) +} + +compctl -K _dotnet_zsh_complete dotnet + +# Load syntax highlighting; should be last. +source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh +source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.plugin.zsh 2>/dev/null diff --git a/zsh/.config/zsh/keybindings.zsh b/zsh/.config/zsh/keybindings.zsh new file mode 100644 index 0000000..33b8195 --- /dev/null +++ b/zsh/.config/zsh/keybindings.zsh @@ -0,0 +1,138 @@ +# http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html +# http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Zle-Builtins +# http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Standard-Widgets + +# Make sure that the terminal is in application mode when zle is active, since +# only then values from $terminfo are valid +if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then + function zle-line-init() { + echoti smkx + } + function zle-line-finish() { + echoti rmkx + } + zle -N zle-line-init + zle -N zle-line-finish +fi + +# Use emacs key bindings +bindkey -e + +# [PageUp] - Up a line of history +if [[ -n "${terminfo[kpp]}" ]]; then + bindkey -M emacs "${terminfo[kpp]}" up-line-or-history + bindkey -M viins "${terminfo[kpp]}" up-line-or-history + bindkey -M vicmd "${terminfo[kpp]}" up-line-or-history +fi +# [PageDown] - Down a line of history +if [[ -n "${terminfo[knp]}" ]]; then + bindkey -M emacs "${terminfo[knp]}" down-line-or-history + bindkey -M viins "${terminfo[knp]}" down-line-or-history + bindkey -M vicmd "${terminfo[knp]}" down-line-or-history +fi + +# Start typing + [Up-Arrow] - fuzzy find history forward +if [[ -n "${terminfo[kcuu1]}" ]]; then + autoload -U up-line-or-beginning-search + zle -N up-line-or-beginning-search + + bindkey -M emacs "${terminfo[kcuu1]}" up-line-or-beginning-search + bindkey -M viins "${terminfo[kcuu1]}" up-line-or-beginning-search + bindkey -M vicmd "${terminfo[kcuu1]}" up-line-or-beginning-search +fi +# Start typing + [Down-Arrow] - fuzzy find history backward +if [[ -n "${terminfo[kcud1]}" ]]; then + autoload -U down-line-or-beginning-search + zle -N down-line-or-beginning-search + + bindkey -M emacs "${terminfo[kcud1]}" down-line-or-beginning-search + bindkey -M viins "${terminfo[kcud1]}" down-line-or-beginning-search + bindkey -M vicmd "${terminfo[kcud1]}" down-line-or-beginning-search +fi + +# [Home] - Go to beginning of line +if [[ -n "${terminfo[khome]}" ]]; then + bindkey -M emacs "${terminfo[khome]}" beginning-of-line + bindkey -M viins "${terminfo[khome]}" beginning-of-line + bindkey -M vicmd "${terminfo[khome]}" beginning-of-line +fi +# [End] - Go to end of line +if [[ -n "${terminfo[kend]}" ]]; then + bindkey -M emacs "${terminfo[kend]}" end-of-line + bindkey -M viins "${terminfo[kend]}" end-of-line + bindkey -M vicmd "${terminfo[kend]}" end-of-line +fi + +# [Shift-Tab] - move through the completion menu backwards +if [[ -n "${terminfo[kcbt]}" ]]; then + bindkey -M emacs "${terminfo[kcbt]}" reverse-menu-complete + bindkey -M viins "${terminfo[kcbt]}" reverse-menu-complete + bindkey -M vicmd "${terminfo[kcbt]}" reverse-menu-complete +fi + +# [Backspace] - delete backward +bindkey -M emacs '^?' backward-delete-char +bindkey -M viins '^?' backward-delete-char +bindkey -M vicmd '^?' backward-delete-char +# [Delete] - delete forward +if [[ -n "${terminfo[kdch1]}" ]]; then + bindkey -M emacs "${terminfo[kdch1]}" delete-char + bindkey -M viins "${terminfo[kdch1]}" delete-char + bindkey -M vicmd "${terminfo[kdch1]}" delete-char +else + bindkey -M emacs "^[[3~" delete-char + bindkey -M viins "^[[3~" delete-char + bindkey -M vicmd "^[[3~" delete-char + + bindkey -M emacs "^[3;5~" delete-char + bindkey -M viins "^[3;5~" delete-char + bindkey -M vicmd "^[3;5~" delete-char +fi + +# [Ctrl-Delete] - delete whole forward-word +bindkey -M emacs '^[[3;5~' kill-word +bindkey -M viins '^[[3;5~' kill-word +bindkey -M vicmd '^[[3;5~' kill-word + +# [Ctrl-RightArrow] - move forward one word +bindkey -M emacs '^[[1;5C' forward-word +bindkey -M viins '^[[1;5C' forward-word +bindkey -M vicmd '^[[1;5C' forward-word +# [Ctrl-LeftArrow] - move backward one word +bindkey -M emacs '^[[1;5D' backward-word +bindkey -M viins '^[[1;5D' backward-word +bindkey -M vicmd '^[[1;5D' backward-word + + +bindkey '\ew' kill-region # [Esc-w] - Kill from the cursor to the mark +bindkey -s '\el' 'ls\n' # [Esc-l] - run command: ls +bindkey '^r' history-incremental-search-backward # [Ctrl-r] - Search backward incrementally for a specified string. The string may begin with ^ to anchor the search to the beginning of the line. +bindkey ' ' magic-space # [Space] - don't do history expansion + + +# Edit the current command line in $EDITOR +autoload -U edit-command-line +zle -N edit-command-line +bindkey '\C-x\C-e' edit-command-line + +# file rename magick +bindkey "^[m" copy-prev-shell-word + +# consider emacs keybindings: + +#bindkey -e ## emacs key bindings +# +#bindkey '^[[A' up-line-or-search +#bindkey '^[[B' down-line-or-search +#bindkey '^[^[[C' emacs-forward-word +#bindkey '^[^[[D' emacs-backward-word +# +#bindkey -s '^X^Z' '%-^M' +#bindkey '^[e' expand-cmd-path +#bindkey '^[^I' reverse-menu-complete +#bindkey '^X^N' accept-and-infer-next-history +#bindkey '^W' kill-region +#bindkey '^I' complete-word +## Fix weird sequence that rxvt produces +#bindkey -s '^[[Z' '\t' +#
\ No newline at end of file |