Coverage for gef.py: 71.0775%
8062 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-15 16:07 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-15 16:07 +0000
1#######################################################################################
2# GEF - Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers
3#
4# by @_hugsy_
5#######################################################################################
6#
7# GEF is a kick-ass set of commands for X86, ARM, MIPS, PowerPC and SPARC to
8# make GDB cool again for exploit dev. It is aimed to be used mostly by exploit
9# devs and reversers, to provides additional features to GDB using the Python
10# API to assist during the process of dynamic analysis.
11#
12# GEF fully relies on GDB API and other Linux-specific sources of information
13# (such as /proc/<pid>). As a consequence, some of the features might not work
14# on custom or hardened systems such as GrSec.
15#
16# Since January 2020, GEF solely support GDB compiled with Python3 and was tested on
17# * x86-32 & x86-64
18# * arm v5,v6,v7
19# * aarch64 (armv8)
20# * mips & mips64
21# * powerpc & powerpc64
22# * sparc & sparc64(v9)
23#
24# For GEF with Python2 (only) support was moved to the GEF-Legacy
25# (https://github.com/hugsy/gef-legacy)
26#
27# To start: in gdb, type `source /path/to/gef.py`
28#
29#######################################################################################
30#
31# gef is distributed under the MIT License (MIT)
32# Copyright (c) 2013-2024 crazy rabbidz
33#
34# Permission is hereby granted, free of charge, to any person obtaining a copy
35# of this software and associated documentation files (the "Software"), to deal
36# in the Software without restriction, including without limitation the rights
37# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38# copies of the Software, and to permit persons to whom the Software is
39# furnished to do so, subject to the following conditions:
40#
41# The above copyright notice and this permission notice shall be included in all
42# copies or substantial portions of the Software.
43#
44# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50# SOFTWARE.
52import abc
53import argparse
54import ast
55import atexit
56import binascii
57import codecs
58import collections
59import configparser
60import ctypes
61import enum
62import functools
63import hashlib
64import importlib
65import importlib.util
66import inspect
67import itertools
68import os
69import pathlib
70import platform
71import re
72import shutil
73import socket
74import string
75import struct
76import subprocess
77import sys
78import tempfile
79import time
80import traceback
81import warnings
82from functools import lru_cache
83from io import StringIO, TextIOWrapper
84from types import ModuleType
85from typing import (Any, ByteString, Callable, Generator, Iterable, Iterator,
86 NoReturn, Sequence, Type, TypeVar, cast)
87from urllib.request import urlopen
90GEF_DEFAULT_BRANCH = "main"
91GEF_EXTRAS_DEFAULT_BRANCH = "main"
93def http_get(url: str) -> bytes | None:
94 """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK,
95 otherwise return None."""
96 try:
97 http = urlopen(url)
98 return http.read() if http.getcode() == 200 else None
99 except Exception:
100 return None
103def update_gef(argv: list[str]) -> int:
104 """Obsolete. Use `gef.sh`."""
105 return -1
108try:
109 import gdb # type:ignore
110except ImportError:
111 if len(sys.argv) >= 2 and sys.argv[1].lower() in ("--update", "--upgrade"):
112 print("[-] `update_gef` is obsolete. Use the `gef.sh` script to update gef from the command line.")
113 print("[-] gef cannot run as standalone")
114 sys.exit(1)
117GDB_MIN_VERSION: tuple[int, int] = (10, 0)
118PYTHON_MIN_VERSION: tuple[int, int] = (3, 10)
119PYTHON_VERSION: tuple[int, int] = sys.version_info[0:2]
120GDB_VERSION: tuple[int, int] = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups())) # type:ignore
122DEFAULT_PAGE_ALIGN_SHIFT = 12
123DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT
125GEF_RC = (pathlib.Path(os.getenv("GEF_RC", "")).absolute()
126 if os.getenv("GEF_RC")
127 else pathlib.Path().home() / ".gef.rc")
128GEF_TEMP_DIR = pathlib.Path(tempfile.gettempdir())/ "gef"
129GEF_MAX_STRING_LENGTH = 50
131LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena"
132ANSI_SPLIT_RE = r"(\033\[[\d;]*m)"
134LEFT_ARROW = " ← "
135RIGHT_ARROW = " → "
136DOWN_ARROW = "↳"
137HORIZONTAL_LINE = "─"
138VERTICAL_LINE = "│"
139CROSS = "✘ "
140TICK = "✓ "
141BP_GLYPH = "●"
142GEF_PROMPT = "gef➤ "
143GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002"
144GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002"
146__registered_commands__ : set[Type["GenericCommand"]] = set()
147__registered_functions__ : set[Type["GenericFunction"]] = set()
148__registered_architectures__ : dict["Elf.Abi | str", Type["Architecture"]] = {}
149__registered_file_formats__ : set[ Type["FileFormat"] ] = set()
151GefMemoryMapProvider = Callable[[], Generator["Section", None, None]]
154def reset_all_caches() -> None:
155 """Free all caches. If an object is cached, it will have a callable attribute `cache_clear`
156 which will be invoked to purge the function cache."""
158 for mod in dir(sys.modules["__main__"]):
159 obj = getattr(sys.modules["__main__"], mod)
160 if hasattr(obj, "cache_clear"):
161 obj.cache_clear()
163 gef.reset_caches()
164 return
167def reset() -> None:
168 global gef
170 arch = None
171 if "gef" in locals().keys(): 171 ↛ 172line 171 didn't jump to line 172 because the condition on line 171 was never true
172 reset_all_caches()
173 arch = gef.arch
174 del gef
176 gef = Gef()
177 gef.setup()
179 if arch: 179 ↛ 180line 179 didn't jump to line 180 because the condition on line 179 was never true
180 gef.arch = arch
181 return
184def highlight_text(text: str) -> str:
185 """
186 Highlight text using `gef.ui.highlight_table` { match -> color } settings.
188 If RegEx is enabled it will create a match group around all items in the
189 `gef.ui.highlight_table` and wrap the specified color in the `gef.ui.highlight_table`
190 around those matches.
192 If RegEx is disabled, split by ANSI codes and 'colorify' each match found
193 within the specified string.
194 """
195 global gef
197 if not gef.ui.highlight_table:
198 return text
200 if gef.config["highlight.regex"]: 200 ↛ 201line 200 didn't jump to line 201 because the condition on line 200 was never true
201 for match, color in gef.ui.highlight_table.items():
202 text = re.sub("(" + match + ")", Color.colorify("\\1", color), text)
203 return text
205 ansiSplit = re.split(ANSI_SPLIT_RE, text)
207 for match, color in gef.ui.highlight_table.items():
208 for index, val in enumerate(ansiSplit): 208 ↛ 213line 208 didn't jump to line 213 because the loop on line 208 didn't complete
209 found = val.find(match)
210 if found > -1:
211 ansiSplit[index] = val.replace(match, Color.colorify(match, color))
212 break
213 text = "".join(ansiSplit)
214 ansiSplit = re.split(ANSI_SPLIT_RE, text)
216 return "".join(ansiSplit)
219def gef_print(*args: str, end="\n", sep=" ", **kwargs: Any) -> None:
220 """Wrapper around print(), using string buffering feature."""
221 parts = [highlight_text(a) for a in args]
222 if buffer_output() and gef.ui.stream_buffer and not is_debug(): 222 ↛ 223line 222 didn't jump to line 223 because the condition on line 222 was never true
223 gef.ui.stream_buffer.write(sep.join(parts) + end)
224 return
226 print(*parts, sep=sep, end=end, **kwargs)
227 return
230def bufferize(f: Callable) -> Callable:
231 """Store the content to be printed for a function in memory, and flush it on function exit."""
233 @functools.wraps(f)
234 def wrapper(*args: Any, **kwargs: Any) -> Any:
235 global gef
237 if gef.ui.stream_buffer:
238 return f(*args, **kwargs)
240 gef.ui.stream_buffer = StringIO()
241 try:
242 rv = f(*args, **kwargs)
243 finally:
244 redirect = gef.config["context.redirect"]
245 if redirect.startswith("/dev/pts/"): 245 ↛ 246line 245 didn't jump to line 246 because the condition on line 245 was never true
246 if not gef.ui.redirect_fd:
247 # if the FD has never been open, open it
248 fd = open(redirect, "wt")
249 gef.ui.redirect_fd = fd
250 elif redirect != gef.ui.redirect_fd.name:
251 # if the user has changed the redirect setting during runtime, update the state
252 gef.ui.redirect_fd.close()
253 fd = open(redirect, "wt")
254 gef.ui.redirect_fd = fd
255 else:
256 # otherwise, keep using it
257 fd = gef.ui.redirect_fd
258 else:
259 fd = sys.stdout
260 gef.ui.redirect_fd = None
262 if gef.ui.redirect_fd and fd.closed: 262 ↛ 264line 262 didn't jump to line 264 because the condition on line 262 was never true
263 # if the tty was closed, revert back to stdout
264 fd = sys.stdout
265 gef.ui.redirect_fd = None
266 gef.config["context.redirect"] = ""
268 fd.write(gef.ui.stream_buffer.getvalue())
269 fd.flush()
270 gef.ui.stream_buffer = None
271 return rv
273 return wrapper
276class ValidationError(Exception): pass
278#
279# Helpers
280#
282class ObsoleteException(Exception): pass
284class AlreadyRegisteredException(Exception): pass
286def p8(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes:
287 """Pack one byte respecting the current architecture endianness."""
288 endian = e or gef.arch.endianness
289 return struct.pack(f"{endian}B", x) if not s else struct.pack(f"{endian:s}b", x)
292def p16(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes:
293 """Pack one word respecting the current architecture endianness."""
294 endian = e or gef.arch.endianness
295 return struct.pack(f"{endian}H", x) if not s else struct.pack(f"{endian:s}h", x)
298def p32(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes:
299 """Pack one dword respecting the current architecture endianness."""
300 endian = e or gef.arch.endianness
301 return struct.pack(f"{endian}I", x) if not s else struct.pack(f"{endian:s}i", x)
304def p64(x: int, s: bool = False, e: "Endianness | None" = None) -> bytes:
305 """Pack one qword respecting the current architecture endianness."""
306 endian = e or gef.arch.endianness
307 return struct.pack(f"{endian}Q", x) if not s else struct.pack(f"{endian:s}q", x)
310def u8(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int:
311 """Unpack one byte respecting the current architecture endianness."""
312 endian = e or gef.arch.endianness
313 return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0]
316def u16(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int:
317 """Unpack one word respecting the current architecture endianness."""
318 endian = e or gef.arch.endianness
319 return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0]
322def u32(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int:
323 """Unpack one dword respecting the current architecture endianness."""
324 endian = e or gef.arch.endianness
325 return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0]
328def u64(x: bytes, s: bool = False, e: "Endianness | None" = None) -> int:
329 """Unpack one qword respecting the current architecture endianness."""
330 endian = e or gef.arch.endianness
331 return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0]
334def is_ascii_string(address: int) -> bool:
335 """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)"""
336 try:
337 return gef.memory.read_ascii_string(address) is not None
338 except Exception:
339 return False
342def is_alive() -> bool:
343 """Check if GDB is running."""
344 try:
345 return gdb.selected_inferior().pid > 0
346 except Exception:
347 return False
350def calling_function() -> str | None:
351 """Return the name of the calling function"""
352 try:
353 stack_info = traceback.extract_stack()[-3]
354 return stack_info.name
355 except Exception as e:
356 dbg(f"traceback failed with {str(e)}")
357 return None
360#
361# Decorators
362#
363def only_if_gdb_running(f: Callable) -> Callable:
364 """Decorator wrapper to check if GDB is running."""
365 @functools.wraps(f)
366 def wrapper(*args: Any, **kwargs: Any) -> Any:
367 if is_alive():
368 return f(*args, **kwargs)
369 else:
370 warn("No debugging session active")
372 return wrapper
375def only_if_gdb_target_local(f: Callable) -> Callable:
376 """Decorator wrapper to check if GDB is running locally (target not remote)."""
378 @functools.wraps(f)
379 def wrapper(*args: Any, **kwargs: Any) -> Any:
380 if not is_remote_debug(): 380 ↛ 383line 380 didn't jump to line 383 because the condition on line 380 was always true
381 return f(*args, **kwargs)
382 else:
383 warn("This command cannot work for remote sessions.")
385 return wrapper
388def deprecated(solution: str = "") -> Callable:
389 """Decorator to add a warning when a command is obsolete and will be removed."""
390 def decorator(f: Callable) -> Callable:
391 @functools.wraps(f)
392 def wrapper(*args: Any, **kwargs: Any) -> Any:
393 caller = inspect.stack()[1]
394 caller_file = pathlib.Path(caller.filename)
395 caller_loc = caller.lineno
396 msg = f"{caller_file.name}:L{caller_loc} '{f.__name__}' is deprecated and will be removed in a feature release. "
397 if not gef: 397 ↛ 398line 397 didn't jump to line 398 because the condition on line 397 was never true
398 print(msg)
399 elif gef.config["gef.show_deprecation_warnings"] is True: 399 ↛ 403line 399 didn't jump to line 403 because the condition on line 399 was always true
400 if solution:
401 msg += solution
402 warn(msg)
403 return f(*args, **kwargs)
405 if not wrapper.__doc__:
406 wrapper.__doc__ = ""
407 wrapper.__doc__ += f"\r\n`{f.__name__}` is **DEPRECATED** and will be removed in the future.\r\n{solution}"
408 return wrapper
409 return decorator
412def experimental_feature(f: Callable) -> Callable:
413 """Decorator to add a warning when a feature is experimental."""
415 @functools.wraps(f)
416 def wrapper(*args: Any, **kwargs: Any) -> Any:
417 warn("This feature is under development, expect bugs and unstability...")
418 return f(*args, **kwargs)
420 return wrapper
423def only_if_events_supported(event_type: str) -> Callable:
424 """Checks if GDB supports events without crashing."""
425 def wrap(f: Callable) -> Callable:
426 def wrapped_f(*args: Any, **kwargs: Any) -> Any:
427 if getattr(gdb, "events") and getattr(gdb.events, event_type): 427 ↛ 429line 427 didn't jump to line 429 because the condition on line 427 was always true
428 return f(*args, **kwargs)
429 warn("GDB events cannot be set")
430 return wrapped_f
431 return wrap
434class classproperty(property):
435 """Make the attribute a `classproperty`."""
436 def __get__(self, cls, owner):
437 assert self.fget
438 return classmethod(self.fget).__get__(None, owner)()
441def FakeExit(*args: Any, **kwargs: Any) -> NoReturn:
442 raise RuntimeWarning
445sys.exit = FakeExit
448def parse_arguments(required_arguments: dict[str | tuple[str, str], Any],
449 optional_arguments: dict[str | tuple[str, str], Any]) -> Callable:
450 """Argument parsing decorator."""
452 def int_wrapper(x: str) -> int: return int(x, 0)
454 def decorator(f: Callable) -> Callable | None:
455 def wrapper(*args: Any, **kwargs: Any) -> Callable:
456 parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True)
457 for argname in required_arguments:
458 argvalue = required_arguments[argname]
459 argtype = type(argvalue)
460 if argtype is int:
461 argtype = int_wrapper
463 argname_is_list = not isinstance(argname, str)
464 assert not argname_is_list and isinstance(argname, str)
465 if not argname_is_list and argname.startswith("-"): 465 ↛ 467line 465 didn't jump to line 467 because the condition on line 465 was never true
466 # optional args
467 if argtype is bool:
468 parser.add_argument(argname, action="store_true" if argvalue else "store_false")
469 else:
470 parser.add_argument(argname, type=argtype, required=True, default=argvalue)
471 else:
472 if argtype in (list, tuple):
473 nargs = "*"
474 argtype = type(argvalue[0])
475 else:
476 nargs = "?"
477 # positional args
478 parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs)
480 for argname in optional_arguments:
481 if isinstance(argname, str) and not argname.startswith("-"): 481 ↛ 483line 481 didn't jump to line 483 because the condition on line 481 was never true
482 # refuse positional arguments
483 continue
484 argvalue = optional_arguments[argname]
485 argtype = type(argvalue)
486 if isinstance(argname, str):
487 argname = [argname,]
488 if argtype is int:
489 argtype = int_wrapper
490 elif argtype is bool:
491 parser.add_argument(*argname, action="store_false" if argvalue else "store_true")
492 continue
493 elif argtype in (list, tuple):
494 parser.add_argument(*argname, type=type(argvalue[0]),
495 default=[], action="append")
496 continue
497 parser.add_argument(*argname, type=argtype, default=argvalue)
499 parsed_args = parser.parse_args(*(args[1:]))
500 kwargs["arguments"] = parsed_args
501 return f(*args, **kwargs)
502 return wrapper
503 return decorator
506class Color:
507 """Used to colorify terminal output."""
509 ### Special chars:
510 # \001 -> Tell the readline library that we start a special sequence
511 # which won't be displayed (takes no column in the output)
512 # \002 -> Tell the readline library that we end a special sequence
513 # started with \001
514 # \033 -> Start an ANSI escape code for displaying colors
515 colors = {
516 "normal" : "\001\033[0m\002",
517 "gray" : "\001\033[1;38;5;240m\002",
518 "light_gray" : "\001\033[0;37m\002",
519 "red" : "\001\033[31m\002",
520 "green" : "\001\033[32m\002",
521 "yellow" : "\001\033[33m\002",
522 "blue" : "\001\033[34m\002",
523 "pink" : "\001\033[35m\002",
524 "cyan" : "\001\033[36m\002",
525 "bold" : "\001\033[1m\002",
526 "underline" : "\001\033[4m\002",
527 "underline_off" : "\001\033[24m\002",
528 "highlight" : "\001\033[3m\002",
529 "highlight_off" : "\001\033[23m\002",
530 "blink" : "\001\033[5m\002",
531 "blink_off" : "\001\033[25m\002",
532 }
534 @staticmethod
535 def redify(msg: str) -> str: return Color.colorify(msg, "red")
536 @staticmethod
537 def greenify(msg: str) -> str: return Color.colorify(msg, "green")
538 @staticmethod
539 def blueify(msg: str) -> str: return Color.colorify(msg, "blue")
540 @staticmethod
541 def yellowify(msg: str) -> str: return Color.colorify(msg, "yellow")
542 @staticmethod
543 def grayify(msg: str) -> str: return Color.colorify(msg, "gray") 543 ↛ exitline 543 didn't return from function 'grayify' because the return on line 543 wasn't executed
544 @staticmethod
545 def light_grayify(msg: str) -> str: return Color.colorify(msg, "light_gray") 545 ↛ exitline 545 didn't return from function 'light_grayify' because the return on line 545 wasn't executed
546 @staticmethod
547 def pinkify(msg: str) -> str: return Color.colorify(msg, "pink")
548 @staticmethod
549 def cyanify(msg: str) -> str: return Color.colorify(msg, "cyan") 549 ↛ exitline 549 didn't return from function 'cyanify' because the return on line 549 wasn't executed
550 @staticmethod
551 def boldify(msg: str) -> str: return Color.colorify(msg, "bold")
552 @staticmethod
553 def underlinify(msg: str) -> str: return Color.colorify(msg, "underline") 553 ↛ exitline 553 didn't return from function 'underlinify' because the return on line 553 wasn't executed
554 @staticmethod
555 def highlightify(msg: str) -> str: return Color.colorify(msg, "highlight") 555 ↛ exitline 555 didn't return from function 'highlightify' because the return on line 555 wasn't executed
556 @staticmethod
557 def blinkify(msg: str) -> str: return Color.colorify(msg, "blink") 557 ↛ exitline 557 didn't return from function 'blinkify' because the return on line 557 wasn't executed
559 @staticmethod
560 def colorify(text: str, attrs: str) -> str:
561 """Color text according to the given attributes."""
562 if gef.config["gef.disable_color"] is True: return text
564 colors = Color.colors
565 msg = [colors[attr] for attr in attrs.split() if attr in colors]
566 msg.append(str(text))
567 if colors["highlight"] in msg: msg.append(colors["highlight_off"])
568 if colors["underline"] in msg: msg.append(colors["underline_off"])
569 if colors["blink"] in msg: msg.append(colors["blink_off"])
570 msg.append(colors["normal"])
571 return "".join(msg)
574class Address:
575 """GEF representation of memory addresses."""
576 def __init__(self, **kwargs: Any) -> None:
577 self.value: int = kwargs.get("value", 0)
578 self.section: "Section" = kwargs.get("section", None)
579 self.info: "Zone" = kwargs.get("info", None)
580 return
582 def __str__(self) -> str:
583 value = format_address(self.value)
584 code_color = gef.config["theme.address_code"]
585 stack_color = gef.config["theme.address_stack"]
586 heap_color = gef.config["theme.address_heap"]
587 if self.is_in_text_segment():
588 return Color.colorify(value, code_color)
589 if self.is_in_heap_segment():
590 return Color.colorify(value, heap_color)
591 if self.is_in_stack_segment():
592 return Color.colorify(value, stack_color)
593 return value
595 def __int__(self) -> int:
596 return self.value
598 def is_in_text_segment(self) -> bool:
599 return (hasattr(self.info, "name") and ".text" in self.info.name) or \
600 (hasattr(self.section, "path") and get_filepath() == self.section.path and self.section.is_executable())
602 def is_in_stack_segment(self) -> bool:
603 return hasattr(self.section, "path") and "[stack]" == self.section.path
605 def is_in_heap_segment(self) -> bool:
606 return hasattr(self.section, "path") and "[heap]" == self.section.path
608 def dereference(self) -> int | None:
609 addr = align_address(int(self.value))
610 derefed = dereference(addr)
611 return None if derefed is None else int(derefed)
613 @property
614 def valid(self) -> bool:
615 return any(map(lambda x: x.page_start <= self.value < x.page_end, gef.memory.maps))
618class Permission(enum.Flag):
619 """GEF representation of Linux permission."""
620 NONE = 0
621 EXECUTE = 1
622 WRITE = 2
623 READ = 4
624 ALL = 7
626 def __str__(self) -> str:
627 perm_str = ""
628 perm_str += "r" if self & Permission.READ else "-"
629 perm_str += "w" if self & Permission.WRITE else "-"
630 perm_str += "x" if self & Permission.EXECUTE else "-"
631 return perm_str
633 @classmethod
634 def from_info_sections(cls, *args: str) -> "Permission":
635 perm = cls(0)
636 for arg in args:
637 if "READONLY" in arg: perm |= Permission.READ
638 if "DATA" in arg: perm |= Permission.WRITE
639 if "CODE" in arg: perm |= Permission.EXECUTE
640 return perm
642 @classmethod
643 def from_process_maps(cls, perm_str: str) -> "Permission":
644 perm = cls(0)
645 if perm_str[0] == "r": perm |= Permission.READ
646 if perm_str[1] == "w": perm |= Permission.WRITE
647 if perm_str[2] == "x": perm |= Permission.EXECUTE
648 return perm
650 @classmethod
651 def from_monitor_info_mem(cls, perm_str: str) -> "Permission":
652 perm = cls(0)
653 # perm_str[0] shows if this is a user page, which
654 # we don't track
655 if perm_str[1] == "r": perm |= Permission.READ
656 if perm_str[2] == "w": perm |= Permission.WRITE
657 return perm
659 @classmethod
660 def from_info_mem(cls, perm_str: str) -> "Permission":
661 perm = cls(0)
662 if "r" in perm_str: perm |= Permission.READ
663 if "w" in perm_str: perm |= Permission.WRITE
664 if "x" in perm_str: perm |= Permission.EXECUTE
665 return perm
668class Section:
669 """GEF representation of process memory sections."""
671 def __init__(self, **kwargs: Any) -> None:
672 self.page_start: int = kwargs.get("page_start", 0)
673 self.page_end: int = kwargs.get("page_end", 0)
674 self.offset: int = kwargs.get("offset", 0)
675 self.permission: Permission = kwargs.get("permission", Permission(0))
676 self.inode: int = kwargs.get("inode", 0)
677 self.path: str = kwargs.get("path", "")
678 return
680 def is_readable(self) -> bool:
681 return bool(self.permission & Permission.READ)
683 def is_writable(self) -> bool:
684 return bool(self.permission & Permission.WRITE)
686 def is_executable(self) -> bool:
687 return bool(self.permission & Permission.EXECUTE)
689 @property
690 def size(self) -> int:
691 if self.page_end is None or self.page_start is None: 691 ↛ 692line 691 didn't jump to line 692 because the condition on line 691 was never true
692 raise AttributeError
693 return self.page_end - self.page_start
695 def _search_for_realpath_without_versions(self, path: pathlib.Path) -> str | None:
696 """Given a path, search for a file that exists without numeric suffixes."""
698 # Match the path string against a regex that will remove a suffix
699 # consisting of a dot followed by numbers.
700 candidate = re.match(r"^(.*)\.(\d*)$", str(path))
701 while candidate:
702 candidate = candidate.group(1)
703 # If the prefix from the regex match is a file, return that path.
704 if pathlib.Path(candidate).is_file(): 704 ↛ 705line 704 didn't jump to line 705 because the condition on line 704 was never true
705 return candidate
706 # Otherwise, try to match again.
707 candidate = re.match(r"^(.*)\.(\d*)$", candidate)
708 return None
710 def _search_for_realpath(self) -> str | None:
711 """This function is a workaround for gdb bug #23764
713 path might be wrong for remote sessions, so try a simple search for files
714 that aren't found at the path indicated, which should be canonical.
715 """
717 assert gef.session.remote
718 remote_path = pathlib.Path(self.path)
719 # First, try the canonical path in the remote session root.
720 candidate1 = gef.session.remote.root / remote_path.relative_to(remote_path.anchor)
721 if candidate1.is_file(): 721 ↛ 722line 721 didn't jump to line 722 because the condition on line 721 was never true
722 return str(candidate1)
723 # Also try that path without version suffixes.
724 candidate = self._search_for_realpath_without_versions(candidate1)
725 if candidate: 725 ↛ 726line 725 didn't jump to line 726 because the condition on line 725 was never true
726 return candidate
728 # On some systems, /lib(64) might be a symlink to /usr/lib(64), so try removing
729 # the /usr prefix.
730 if self.path.startswith("/usr"): 730 ↛ 740line 730 didn't jump to line 740 because the condition on line 730 was always true
731 candidate = gef.session.remote.root / remote_path.relative_to("/usr")
732 if candidate.is_file(): 732 ↛ 735line 732 didn't jump to line 735 because the condition on line 732 was always true
733 return str(candidate)
734 # Also try that path without version suffixes.
735 candidate = self._search_for_realpath_without_versions(candidate)
736 if candidate:
737 return candidate
739 # Base case, return the original realpath
740 return str(candidate1)
742 @property
743 def realpath(self) -> str:
744 # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote
745 if gef.session.remote is None:
746 return self.path
747 default = self._search_for_realpath()
748 if default: 748 ↛ 750line 748 didn't jump to line 750 because the condition on line 748 was always true
749 return default
750 raise FileNotFoundError
752 def __str__(self) -> str:
753 return (f"Section(start={self.page_start:#x}, end={self.page_end:#x}, "
754 f"perm={self.permission!s})")
756 def __repr__(self) -> str:
757 return str(self)
759 def __eq__(self, other: "Section") -> bool:
760 return other and \
761 self.page_start == other.page_start and \
762 self.size == other.size and \
763 self.permission == other.permission and \
764 self.path == other.path
766 def overlaps(self, other: "Section") -> bool:
767 return max(self.page_start, other.page_start) <= min(self.page_end, other.page_end)
769 def contains(self, addr: int) -> bool:
770 return addr in range(self.page_start, self.page_end)
773Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"])
776class Endianness(enum.Enum):
777 LITTLE_ENDIAN = 1
778 BIG_ENDIAN = 2
780 def __str__(self) -> str:
781 return "<" if self == Endianness.LITTLE_ENDIAN else ">"
783 def __repr__(self) -> str:
784 return self.name
786 def __int__(self) -> int:
787 return self.value
790class FileFormatSection:
791 misc: Any
794class FileFormat:
795 name: str
796 path: pathlib.Path
797 entry_point: int
798 checksec: dict[str, bool]
799 sections: list[FileFormatSection]
801 def __init__(self, path: str | pathlib.Path) -> None:
802 raise NotImplementedError
804 def __init_subclass__(cls: Type["FileFormat"], **kwargs):
805 global __registered_file_formats__
806 super().__init_subclass__(**kwargs)
807 required_attributes = ("name", "entry_point", "is_valid", "checksec",)
808 for attr in required_attributes:
809 if not hasattr(cls, attr): 809 ↛ 810line 809 didn't jump to line 810 because the condition on line 809 was never true
810 raise NotImplementedError(f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'")
811 __registered_file_formats__.add(cls)
812 return
814 @classmethod
815 def is_valid(cls, _: pathlib.Path) -> bool:
816 raise NotImplementedError
818 def __str__(self) -> str:
819 return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})"
822class Elf(FileFormat):
823 """Basic ELF parsing.
824 Ref:
825 - http://www.skyfree.org/linux/references/ELF_Format.pdf
826 - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf
827 - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
828 """
829 class Class(enum.Enum):
830 ELF_32_BITS = 0x01
831 ELF_64_BITS = 0x02
833 ELF_MAGIC = 0x7f454c46
835 class Abi(enum.Enum):
836 X86_64 = 0x3e
837 X86_32 = 0x03
838 ARM = 0x28
839 MIPS = 0x08
840 POWERPC = 0x14
841 POWERPC64 = 0x15
842 SPARC = 0x02
843 SPARC64 = 0x2b
844 AARCH64 = 0xb7
845 RISCV = 0xf3
846 IA64 = 0x32
847 M68K = 0x04
849 class Type(enum.Enum):
850 ET_RELOC = 1
851 ET_EXEC = 2
852 ET_DYN = 3
853 ET_CORE = 4
855 class OsAbi(enum.Enum):
856 SYSTEMV = 0x00
857 HPUX = 0x01
858 NETBSD = 0x02
859 LINUX = 0x03
860 SOLARIS = 0x06
861 AIX = 0x07
862 IRIX = 0x08
863 FREEBSD = 0x09
864 OPENBSD = 0x0C
866 e_magic: int = ELF_MAGIC
867 e_class: "Elf.Class" = Class.ELF_32_BITS
868 e_endianness: Endianness = Endianness.LITTLE_ENDIAN
869 e_eiversion: int
870 e_osabi: "Elf.OsAbi"
871 e_abiversion: int
872 e_pad: bytes
873 e_type: "Elf.Type" = Type.ET_EXEC
874 e_machine: Abi = Abi.X86_32
875 e_version: int
876 e_entry: int
877 e_phoff: int
878 e_shoff: int
879 e_flags: int
880 e_ehsize: int
881 e_phentsize: int
882 e_phnum: int
883 e_shentsize: int
884 e_shnum: int
885 e_shstrndx: int
887 path: pathlib.Path
888 phdrs : list["Phdr"]
889 shdrs : list["Shdr"]
890 name: str = "ELF"
892 __checksec : dict[str, bool]
894 def __init__(self, path: str | pathlib.Path) -> None:
895 """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown."""
897 if isinstance(path, str):
898 self.path = pathlib.Path(path).expanduser()
899 elif isinstance(path, pathlib.Path): 899 ↛ 902line 899 didn't jump to line 902 because the condition on line 899 was always true
900 self.path = path
901 else:
902 raise TypeError
904 if not self.path.exists(): 904 ↛ 905line 904 didn't jump to line 905 because the condition on line 904 was never true
905 raise FileNotFoundError(f"'{self.path}' not found/readable, most gef features will not work")
907 self.__checksec = {}
909 with self.path.open("rb") as self.fd:
910 # off 0x0
911 self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB")
912 if self.e_magic != Elf.ELF_MAGIC: 912 ↛ 914line 912 didn't jump to line 914 because the condition on line 912 was never true
913 # The ELF is corrupted, GDB won't handle it, no point going further
914 raise RuntimeError("Not a valid ELF file (magic)")
916 self.e_class, self.e_endianness = Elf.Class(e_class), Endianness(e_endianness)
918 if self.e_endianness != gef.arch.endianness: 918 ↛ 919line 918 didn't jump to line 919 because the condition on line 918 was never true
919 warn("Unexpected endianness for architecture")
921 endian = self.e_endianness
923 # off 0x7
924 e_osabi, self.e_abiversion = self.read_and_unpack(f"{endian}BB")
925 self.e_osabi = Elf.OsAbi(e_osabi)
927 # off 0x9
928 self.e_pad = self.read(7)
930 # off 0x10
931 e_type, e_machine, self.e_version = self.read_and_unpack(f"{endian}HHI")
932 self.e_type, self.e_machine = Elf.Type(e_type), Elf.Abi(e_machine)
934 # off 0x18
935 if self.e_class == Elf.Class.ELF_64_BITS: 935 ↛ 938line 935 didn't jump to line 938 because the condition on line 935 was always true
936 self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}QQQ")
937 else:
938 self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}III")
940 self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = self.read_and_unpack(f"{endian}IHHH")
941 self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack(f"{endian}HHH")
943 self.phdrs = []
944 for i in range(self.e_phnum):
945 self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i))
947 self.shdrs = []
948 for i in range(self.e_shnum):
949 self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i))
950 return
952 def read(self, size: int) -> bytes:
953 return self.fd.read(size)
955 def read_and_unpack(self, fmt: str) -> tuple[Any, ...]:
956 size = struct.calcsize(fmt)
957 data = self.fd.read(size)
958 return struct.unpack(fmt, data)
960 def seek(self, off: int) -> None:
961 self.fd.seek(off, 0)
963 def __str__(self) -> str:
964 return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})"
966 def __repr__(self) -> str:
967 return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})"
969 @property
970 def entry_point(self) -> int:
971 return self.e_entry
973 @classmethod
974 def is_valid(cls, path: pathlib.Path) -> bool:
975 return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC
977 @property
978 def checksec(self) -> dict[str, bool]:
979 """Check the security property of the ELF binary. The following properties are:
980 - Canary
981 - NX
982 - PIE
983 - Fortify
984 - Partial/Full RelRO.
985 Return a dict() with the different keys mentioned above, and the boolean
986 associated whether the protection was found."""
987 if not self.__checksec: 987 ↛ 1010line 987 didn't jump to line 1010 because the condition on line 987 was always true
988 def __check_security_property(opt: str, filename: str, pattern: str) -> bool:
989 cmd = [readelf,]
990 cmd += opt.split()
991 cmd += [filename,]
992 lines = gef_execute_external(cmd, as_list=True)
993 for line in lines:
994 if re.search(pattern, line):
995 return True
996 return False
998 abspath = str(self.path.absolute())
999 readelf = gef.session.constants["readelf"]
1000 self.__checksec["Canary"] = __check_security_property("-rs", abspath, r"__stack_chk_fail") is True
1001 has_gnu_stack = __check_security_property("-W -l", abspath, r"GNU_STACK") is True
1002 if has_gnu_stack: 1002 ↛ 1005line 1002 didn't jump to line 1005 because the condition on line 1002 was always true
1003 self.__checksec["NX"] = __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") is False
1004 else:
1005 self.__checksec["NX"] = False
1006 self.__checksec["PIE"] = __check_security_property("-h", abspath, r":.*EXEC") is False
1007 self.__checksec["Fortify"] = __check_security_property("-s", abspath, r"_chk@GLIBC") is True
1008 self.__checksec["Partial RelRO"] = __check_security_property("-l", abspath, r"GNU_RELRO") is True
1009 self.__checksec["Full RelRO"] = self.__checksec["Partial RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True
1010 return self.__checksec
1012 @classproperty
1013 @deprecated("use `Elf.Abi.X86_64`")
1014 def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument
1016 @classproperty
1017 @deprecated("use `Elf.Abi.X86_32`")
1018 def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint: disable=no-self-argument
1020 @classproperty
1021 @deprecated("use `Elf.Abi.ARM`")
1022 def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint: disable=no-self-argument
1024 @classproperty
1025 @deprecated("use `Elf.Abi.MIPS`")
1026 def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint: disable=no-self-argument
1028 @classproperty
1029 @deprecated("use `Elf.Abi.POWERPC`")
1030 def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument
1032 @classproperty
1033 @deprecated("use `Elf.Abi.POWERPC64`")
1034 def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument
1036 @classproperty
1037 @deprecated("use `Elf.Abi.SPARC`")
1038 def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint: disable=no-self-argument
1040 @classproperty
1041 @deprecated("use `Elf.Abi.SPARC64`")
1042 def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument
1044 @classproperty
1045 @deprecated("use `Elf.Abi.AARCH64`")
1046 def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value # pylint: disable=no-self-argument
1048 @classproperty
1049 @deprecated("use `Elf.Abi.RISCV`")
1050 def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint: disable=no-self-argument
1053class Phdr:
1054 class Type(enum.IntEnum):
1055 PT_NULL = 0
1056 PT_LOAD = 1
1057 PT_DYNAMIC = 2
1058 PT_INTERP = 3
1059 PT_NOTE = 4
1060 PT_SHLIB = 5
1061 PT_PHDR = 6
1062 PT_TLS = 7
1063 PT_LOOS = 0x60000000
1064 PT_GNU_EH_FRAME = 0x6474e550
1065 PT_GNU_STACK = 0x6474e551
1066 PT_GNU_RELRO = 0x6474e552
1067 PT_GNU_PROPERTY = 0x6474e553
1068 PT_LOSUNW = 0x6ffffffa
1069 PT_SUNWBSS = 0x6ffffffa
1070 PT_SUNWSTACK = 0x6ffffffb
1071 PT_HISUNW = PT_HIOS = 0x6fffffff
1072 PT_LOPROC = 0x70000000
1073 PT_ARM_EIDX = 0x70000001
1074 PT_MIPS_ABIFLAGS= 0x70000003
1075 PT_HIPROC = 0x7fffffff
1076 UNKNOWN_PHDR = 0xffffffff
1078 @classmethod
1079 def _missing_(cls, _:int) -> "Phdr.Type":
1080 return cls.UNKNOWN_PHDR
1082 class Flags(enum.IntFlag):
1083 PF_X = 1
1084 PF_W = 2
1085 PF_R = 4
1087 p_type: "Phdr.Type"
1088 p_flags: "Phdr.Flags"
1089 p_offset: int
1090 p_vaddr: int
1091 p_paddr: int
1092 p_filesz: int
1093 p_memsz: int
1094 p_align: int
1096 def __init__(self, elf: Elf, off: int) -> None:
1097 if not elf: 1097 ↛ 1098line 1097 didn't jump to line 1098 because the condition on line 1097 was never true
1098 return
1099 elf.seek(off)
1100 self.offset = off
1101 endian = elf.e_endianness
1102 if elf.e_class == Elf.Class.ELF_64_BITS: 1102 ↛ 1107line 1102 didn't jump to line 1107 because the condition on line 1102 was always true
1103 p_type, p_flags, self.p_offset = elf.read_and_unpack(f"{endian}IIQ")
1104 self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}QQ")
1105 self.p_filesz, self.p_memsz, self.p_align = elf.read_and_unpack(f"{endian}QQQ")
1106 else:
1107 p_type, self.p_offset = elf.read_and_unpack(f"{endian}II")
1108 self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}II")
1109 self.p_filesz, self.p_memsz, p_flags, self.p_align = elf.read_and_unpack(f"{endian}IIII")
1111 self.p_type, self.p_flags = Phdr.Type(p_type), Phdr.Flags(p_flags)
1112 return
1114 def __str__(self) -> str:
1115 return (f"Phdr(offset={self.offset}, type={self.p_type.name}, flags={self.p_flags.name}, "
1116 f"vaddr={self.p_vaddr}, paddr={self.p_paddr}, filesz={self.p_filesz}, "
1117 f"memsz={self.p_memsz}, align={self.p_align})")
1120class Shdr:
1121 class Type(enum.IntEnum):
1122 SHT_NULL = 0
1123 SHT_PROGBITS = 1
1124 SHT_SYMTAB = 2
1125 SHT_STRTAB = 3
1126 SHT_RELA = 4
1127 SHT_HASH = 5
1128 SHT_DYNAMIC = 6
1129 SHT_NOTE = 7
1130 SHT_NOBITS = 8
1131 SHT_REL = 9
1132 SHT_SHLIB = 10
1133 SHT_DYNSYM = 11
1134 SHT_NUM = 12
1135 SHT_INIT_ARRAY = 14
1136 SHT_FINI_ARRAY = 15
1137 SHT_PREINIT_ARRAY = 16
1138 SHT_GROUP = 17
1139 SHT_SYMTAB_SHNDX = 18
1140 SHT_LOOS = 0x60000000
1141 SHT_GNU_ATTRIBUTES = 0x6ffffff5
1142 SHT_GNU_HASH = 0x6ffffff6
1143 SHT_GNU_LIBLIST = 0x6ffffff7
1144 SHT_CHECKSUM = 0x6ffffff8
1145 SHT_LOSUNW = 0x6ffffffa
1146 SHT_SUNW_move = 0x6ffffffa
1147 SHT_SUNW_COMDAT = 0x6ffffffb
1148 SHT_SUNW_syminfo = 0x6ffffffc
1149 SHT_GNU_verdef = 0x6ffffffd
1150 SHT_GNU_verneed = 0x6ffffffe
1151 SHT_GNU_versym = 0x6fffffff
1152 SHT_LOPROC = 0x70000000
1153 SHT_ARM_EXIDX = 0x70000001
1154 SHT_X86_64_UNWIND = 0x70000001
1155 SHT_ARM_ATTRIBUTES = 0x70000003
1156 SHT_MIPS_OPTIONS = 0x7000000d
1157 DT_MIPS_INTERFACE = 0x7000002a
1158 SHT_HIPROC = 0x7fffffff
1159 SHT_LOUSER = 0x80000000
1160 SHT_HIUSER = 0x8fffffff
1161 UNKNOWN_SHDR = 0xffffffff
1163 @classmethod
1164 def _missing_(cls, _:int) -> "Shdr.Type":
1165 return cls.UNKNOWN_SHDR
1167 class Flags(enum.IntFlag):
1168 WRITE = 1
1169 ALLOC = 2
1170 EXECINSTR = 4
1171 MERGE = 0x10
1172 STRINGS = 0x20
1173 INFO_LINK = 0x40
1174 LINK_ORDER = 0x80
1175 OS_NONCONFORMING = 0x100
1176 GROUP = 0x200
1177 TLS = 0x400
1178 COMPRESSED = 0x800
1179 RELA_LIVEPATCH = 0x00100000
1180 RO_AFTER_INIT = 0x00200000
1181 ORDERED = 0x40000000
1182 EXCLUDE = 0x80000000
1183 UNKNOWN_FLAG = 0xffffffff
1185 @classmethod
1186 def _missing_(cls, _:int):
1187 return cls.UNKNOWN_FLAG
1189 sh_name: int
1190 sh_type: "Shdr.Type"
1191 sh_flags: "Shdr.Flags"
1192 sh_addr: int
1193 sh_offset: int
1194 sh_size: int
1195 sh_link: int
1196 sh_info: int
1197 sh_addralign: int
1198 sh_entsize: int
1199 name: str
1201 def __init__(self, elf: Elf | None, off: int) -> None:
1202 if elf is None: 1202 ↛ 1203line 1202 didn't jump to line 1203 because the condition on line 1202 was never true
1203 return
1204 elf.seek(off)
1205 endian = elf.e_endianness
1206 if elf.e_class == Elf.Class.ELF_64_BITS: 1206 ↛ 1212line 1206 didn't jump to line 1212 because the condition on line 1206 was always true
1207 self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}IIQ")
1208 self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}QQ")
1209 self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}QII")
1210 self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}QQ")
1211 else:
1212 self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}III")
1213 self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}II")
1214 self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}III")
1215 self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}II")
1217 self.sh_type = Shdr.Type(sh_type)
1218 self.sh_flags = Shdr.Flags(sh_flags)
1219 stroff = elf.e_shoff + elf.e_shentsize * elf.e_shstrndx
1221 if elf.e_class == Elf.Class.ELF_64_BITS: 1221 ↛ 1225line 1221 didn't jump to line 1225 because the condition on line 1221 was always true
1222 elf.seek(stroff + 16 + 8)
1223 offset = u64(elf.read(8))
1224 else:
1225 elf.seek(stroff + 12 + 4)
1226 offset = u32(elf.read(4))
1227 elf.seek(offset + self.sh_name)
1228 self.name = ""
1229 while True:
1230 c = u8(elf.read(1))
1231 if c == 0:
1232 break
1233 self.name += chr(c)
1234 return
1236 def __str__(self) -> str:
1237 return (f"Shdr(name={self.name}, type={self.sh_type.name}, flags={self.sh_flags.name}, "
1238 f"addr={self.sh_addr:#x}, offset={self.sh_offset}, size={self.sh_size}, link={self.sh_link}, "
1239 f"info={self.sh_info}, addralign={self.sh_addralign}, entsize={self.sh_entsize})")
1242class Instruction:
1243 """GEF representation of a CPU instruction."""
1245 def __init__(self, address: int, location: str, mnemo: str, operands: list[str], opcodes: bytes) -> None:
1246 self.address, self.location, self.mnemonic, self.operands, self.opcodes = \
1247 address, location, mnemo, operands, opcodes
1248 return
1250 # Allow formatting an instruction with {:o} to show opcodes.
1251 # The number of bytes to display can be configured, e.g. {:4o} to only show 4 bytes of the opcodes
1252 def __format__(self, format_spec: str) -> str:
1253 if len(format_spec) == 0 or format_spec[-1] != "o": 1253 ↛ 1254line 1253 didn't jump to line 1254 because the condition on line 1253 was never true
1254 return str(self)
1256 if format_spec == "o": 1256 ↛ 1257line 1256 didn't jump to line 1257 because the condition on line 1256 was never true
1257 opcodes_len = len(self.opcodes)
1258 else:
1259 opcodes_len = int(format_spec[:-1])
1261 opcodes_text = "".join(f"{b:02x}" for b in self.opcodes[:opcodes_len])
1262 if opcodes_len < len(self.opcodes):
1263 opcodes_text += "..."
1264 return (f"{self.address:#10x} {opcodes_text:{opcodes_len * 2 + 3:d}s} {self.location:16} "
1265 f"{self.mnemonic:6} {', '.join(self.operands)}")
1267 def __str__(self) -> str:
1268 return f"{self.address:#10x} {self.location:16} {self.mnemonic:6} {', '.join(self.operands)}"
1270 def is_valid(self) -> bool:
1271 return "(bad)" not in self.mnemonic
1273 def size(self) -> int:
1274 return len(self.opcodes)
1276 def next(self) -> "Instruction":
1277 address = self.address + self.size()
1278 return gef_get_instruction_at(address)
1281@deprecated("Use GefHeapManager.find_main_arena_addr()")
1282def search_for_main_arena() -> int:
1283 return GefHeapManager.find_main_arena_addr()
1285class GlibcHeapInfo:
1286 """Glibc heap_info struct"""
1288 @staticmethod
1289 def heap_info_t() -> Type[ctypes.Structure]:
1290 assert gef.libc.version
1291 class heap_info_cls(ctypes.Structure):
1292 pass
1293 pointer = ctypes.c_uint64 if gef.arch.ptrsize == 8 else ctypes.c_uint32
1294 pad_size = -5 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1)
1295 fields = [
1296 ("ar_ptr", ctypes.POINTER(GlibcArena.malloc_state_t())),
1297 ("prev", ctypes.POINTER(heap_info_cls)),
1298 ("size", pointer)
1299 ]
1300 if gef.libc.version >= (2, 5): 1300 ↛ 1305line 1300 didn't jump to line 1305 because the condition on line 1300 was always true
1301 fields += [
1302 ("mprotect_size", pointer)
1303 ]
1304 pad_size = -6 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1)
1305 if gef.libc.version >= (2, 34): 1305 ↛ 1310line 1305 didn't jump to line 1310 because the condition on line 1305 was always true
1306 fields += [
1307 ("pagesize", pointer)
1308 ]
1309 pad_size = -3 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1)
1310 fields += [
1311 ("pad", ctypes.c_uint8*pad_size)
1312 ]
1313 heap_info_cls._fields_ = fields
1314 return heap_info_cls
1316 def __init__(self, addr: str | int) -> None:
1317 self.__address : int = parse_address(f"&{addr}") if isinstance(addr, str) else addr
1318 self.reset()
1319 return
1321 def reset(self):
1322 self._sizeof = ctypes.sizeof(GlibcHeapInfo.heap_info_t())
1323 self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcHeapInfo.heap_info_t()))
1324 self._heap_info = GlibcHeapInfo.heap_info_t().from_buffer_copy(self._data)
1325 return
1327 def __getattr__(self, item: Any) -> Any:
1328 if item in dir(self._heap_info): 1328 ↛ 1330line 1328 didn't jump to line 1330 because the condition on line 1328 was always true
1329 return ctypes.cast(getattr(self._heap_info, item), ctypes.c_void_p).value
1330 return getattr(self, item)
1332 def __abs__(self) -> int:
1333 return self.__address
1335 def __int__(self) -> int:
1336 return self.__address
1338 @property
1339 def address(self) -> int:
1340 return self.__address
1342 @property
1343 def sizeof(self) -> int:
1344 return self._sizeof
1346 @property
1347 def addr(self) -> int:
1348 return int(self)
1350 @property
1351 def heap_start(self) -> int:
1352 # check special case: first heap of non-main-arena
1353 if self.ar_ptr - self.address < 0x60: 1353 ↛ 1365line 1353 didn't jump to line 1365 because the condition on line 1353 was always true
1354 # the first heap of a non-main-arena starts with a `heap_info`
1355 # struct, which should fit easily into 0x60 bytes throughout
1356 # all architectures and glibc versions. If this check succeeds
1357 # then we are currently looking at such a "first heap"
1358 arena = GlibcArena(f"*{self.ar_ptr:#x}")
1359 heap_addr = arena.heap_addr()
1360 if heap_addr: 1360 ↛ 1363line 1360 didn't jump to line 1363 because the condition on line 1360 was always true
1361 return heap_addr
1362 else:
1363 err(f"Cannot find heap address for arena {self.ar_ptr:#x}")
1364 return 0
1365 return self.address + self.sizeof
1367 @property
1368 def heap_end(self) -> int:
1369 return self.address + self.size
1372class GlibcArena:
1373 """Glibc arena class"""
1375 NFASTBINS = 10
1376 NBINS = 128
1377 NSMALLBINS = 64
1378 BINMAPSHIFT = 5
1379 BITSPERMAP = 1 << BINMAPSHIFT
1380 BINMAPSIZE = NBINS // BITSPERMAP
1382 @staticmethod
1383 def malloc_state_t() -> Type[ctypes.Structure]:
1384 pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32
1385 fields = [
1386 ("mutex", ctypes.c_uint32),
1387 ("flags", ctypes.c_uint32),
1388 ]
1389 if gef and gef.libc.version and gef.libc.version >= (2, 27): 1389 ↛ 1395line 1389 didn't jump to line 1395 because the condition on line 1389 was always true
1390 # https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684
1391 fields += [
1392 ("have_fastchunks", ctypes.c_uint32),
1393 ("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10
1394 ]
1395 fields += [
1396 ("fastbinsY", GlibcArena.NFASTBINS * pointer),
1397 ("top", pointer),
1398 ("last_remainder", pointer),
1399 ("bins", (GlibcArena.NBINS * 2 - 2) * pointer),
1400 ("binmap", GlibcArena.BINMAPSIZE * ctypes.c_uint32),
1401 ("next", pointer),
1402 ("next_free", pointer)
1403 ]
1404 if gef and gef.libc.version and gef.libc.version >= (2, 23): 1404 ↛ 1409line 1404 didn't jump to line 1409 because the condition on line 1404 was always true
1405 # https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1719
1406 fields += [
1407 ("attached_threads", pointer)
1408 ]
1409 fields += [
1410 ("system_mem", pointer),
1411 ("max_system_mem", pointer),
1412 ]
1413 class malloc_state_cls(ctypes.Structure):
1414 _fields_ = fields
1415 return malloc_state_cls
1417 def __init__(self, addr: str) -> None:
1418 try:
1419 self.__address : int = parse_address(f"&{addr}")
1420 except gdb.error:
1421 self.__address : int = GefHeapManager.find_main_arena_addr()
1422 # if `find_main_arena_addr` throws `gdb.error` on symbol lookup:
1423 # it means the session is not started, so just propagate the exception
1424 self.reset()
1425 return
1427 def reset(self):
1428 self._sizeof = ctypes.sizeof(GlibcArena.malloc_state_t())
1429 self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()))
1430 self.__arena = GlibcArena.malloc_state_t().from_buffer_copy(self._data)
1431 return
1433 def __abs__(self) -> int:
1434 return self.__address
1436 def __int__(self) -> int:
1437 return self.__address
1439 def __iter__(self) -> Generator["GlibcArena", None, None]:
1440 assert gef.heap.main_arena
1441 main_arena = int(gef.heap.main_arena)
1443 current_arena = self
1444 yield current_arena
1446 while True:
1447 if current_arena.next == 0 or current_arena.next == main_arena:
1448 break
1450 current_arena = GlibcArena(f"*{current_arena.next:#x} ")
1451 yield current_arena
1452 return
1454 def __eq__(self, other: "GlibcArena") -> bool:
1455 return self.__address == int(other)
1457 def __str__(self) -> str:
1458 properties = f"base={self.__address:#x}, top={self.top:#x}, " \
1459 f"last_remainder={self.last_remainder:#x}, next={self.next:#x}, " \
1460 f"mem={self.system_mem}, mempeak={self.max_system_mem}"
1461 return (f"{Color.colorify('Arena', 'blue bold underline')}({properties})")
1463 def __repr__(self) -> str:
1464 return f"GlibcArena(address={self.__address:#x}, size={self._sizeof})"
1466 @property
1467 def address(self) -> int:
1468 return self.__address
1470 @property
1471 def sizeof(self) -> int:
1472 return self._sizeof
1474 @property
1475 def addr(self) -> int:
1476 return int(self)
1478 @property
1479 def top(self) -> int:
1480 return self.__arena.top
1482 @property
1483 def last_remainder(self) -> int:
1484 return self.__arena.last_remainder
1486 @property
1487 def fastbinsY(self) -> ctypes.Array:
1488 return self.__arena.fastbinsY
1490 @property
1491 def bins(self) -> ctypes.Array:
1492 return self.__arena.bins
1494 @property
1495 def binmap(self) -> ctypes.Array:
1496 return self.__arena.binmap
1498 @property
1499 def next(self) -> int:
1500 return self.__arena.next
1502 @property
1503 def next_free(self) -> int:
1504 return self.__arena.next_free
1506 @property
1507 def attached_threads(self) -> int:
1508 return self.__arena.attached_threads
1510 @property
1511 def system_mem(self) -> int:
1512 return self.__arena.system_mem
1514 @property
1515 def max_system_mem(self) -> int:
1516 return self.__arena.max_system_mem
1518 def fastbin(self, i: int) -> "GlibcFastChunk | None":
1519 """Return head chunk in fastbinsY[i]."""
1520 addr = int(self.fastbinsY[i])
1521 if addr == 0:
1522 return None
1523 return GlibcFastChunk(addr + 2 * gef.arch.ptrsize)
1525 def bin(self, i: int) -> tuple[int, int]:
1526 idx = i * 2
1527 fd = int(self.bins[idx])
1528 bk = int(self.bins[idx + 1])
1529 return fd, bk
1531 def bin_at(self, i) -> int:
1532 header_sz = 2 * gef.arch.ptrsize
1533 offset = ctypes.addressof(self.__arena.bins) - ctypes.addressof(self.__arena)
1534 return self.__address + offset + (i-1) * 2 * gef.arch.ptrsize + header_sz
1536 def is_main_arena(self) -> bool:
1537 return gef.heap.main_arena is not None and int(self) == int(gef.heap.main_arena)
1539 def heap_addr(self, allow_unaligned: bool = False) -> int | None:
1540 if self.is_main_arena():
1541 heap_section = gef.heap.base_address
1542 if not heap_section: 1542 ↛ 1543line 1542 didn't jump to line 1543 because the condition on line 1542 was never true
1543 return None
1544 return heap_section
1545 _addr = int(self) + self.sizeof
1546 if allow_unaligned: 1546 ↛ 1547line 1546 didn't jump to line 1547 because the condition on line 1546 was never true
1547 return _addr
1548 return gef.heap.malloc_align_address(_addr)
1550 def get_heap_info_list(self) -> list[GlibcHeapInfo] | None:
1551 if self.is_main_arena(): 1551 ↛ 1552line 1551 didn't jump to line 1552 because the condition on line 1551 was never true
1552 return None
1553 heap_addr = self.get_heap_for_ptr(self.top)
1554 heap_infos = [GlibcHeapInfo(heap_addr)]
1555 while heap_infos[-1].prev is not None: 1555 ↛ 1556line 1555 didn't jump to line 1556 because the condition on line 1555 was never true
1556 prev = int(heap_infos[-1].prev)
1557 heap_info = GlibcHeapInfo(prev)
1558 heap_infos.append(heap_info)
1559 return heap_infos[::-1]
1561 @staticmethod
1562 def get_heap_for_ptr(ptr: int) -> int:
1563 """Find the corresponding heap for a given pointer (int).
1564 See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L129"""
1565 if is_32bit(): 1565 ↛ 1566line 1565 didn't jump to line 1566 because the condition on line 1565 was never true
1566 default_mmap_threshold_max = 512 * 1024
1567 else: # 64bit
1568 val = cached_lookup_type("long")
1569 sz = val.sizeof if val else gef.arch.ptrsize
1570 default_mmap_threshold_max = 4 * 1024 * 1024 * sz
1571 heap_max_size = 2 * default_mmap_threshold_max
1572 return ptr & ~(heap_max_size - 1)
1574 @staticmethod
1575 def verify(addr: int) -> bool:
1576 """Verify that the address matches a possible valid GlibcArena"""
1577 try:
1578 test_arena = GlibcArena(f"*{addr:#x}")
1579 cur_arena = GlibcArena(f"*{test_arena.next:#x}")
1580 while cur_arena != test_arena:
1581 if cur_arena == 0:
1582 return False
1583 cur_arena = GlibcArena(f"*{cur_arena.next:#x}")
1584 except Exception as e:
1585 dbg(f"GlibcArena.verify({addr:#x}) failed: {str(e)}")
1586 return False
1587 return True
1590class GlibcChunk:
1591 """Glibc chunk class. The default behavior (from_base=False) is to interpret the data starting at the memory
1592 address pointed to as the chunk data. Setting from_base to True instead treats that data as the chunk header.
1593 Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/."""
1595 class ChunkFlags(enum.IntFlag):
1596 PREV_INUSE = 1
1597 IS_MMAPPED = 2
1598 NON_MAIN_ARENA = 4
1600 def __str__(self) -> str:
1601 return " | ".join([
1602 Color.greenify("PREV_INUSE") if self.value & self.PREV_INUSE else Color.redify("PREV_INUSE"),
1603 Color.greenify("IS_MMAPPED") if self.value & self.IS_MMAPPED else Color.redify("IS_MMAPPED"),
1604 Color.greenify("NON_MAIN_ARENA") if self.value & self.NON_MAIN_ARENA else Color.redify("NON_MAIN_ARENA")
1605 ])
1607 @staticmethod
1608 def malloc_chunk_t() -> Type[ctypes.Structure]:
1609 pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32
1610 class malloc_chunk_cls(ctypes.Structure):
1611 pass
1613 malloc_chunk_cls._fields_ = [
1614 ("prev_size", pointer),
1615 ("size", pointer),
1616 ("fd", pointer),
1617 ("bk", pointer),
1618 ("fd_nextsize", ctypes.POINTER(malloc_chunk_cls)),
1619 ("bk_nextsize", ctypes.POINTER(malloc_chunk_cls)),
1620 ]
1621 return malloc_chunk_cls
1623 def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None:
1624 ptrsize = gef.arch.ptrsize
1625 self.data_address = addr + 2 * ptrsize if from_base else addr
1626 self.base_address = addr if from_base else addr - 2 * ptrsize
1627 if not allow_unaligned:
1628 self.data_address = gef.heap.malloc_align_address(self.data_address)
1629 self.size_addr = int(self.data_address - ptrsize)
1630 self.prev_size_addr = self.base_address
1631 self.reset()
1632 return
1634 def reset(self):
1635 self._sizeof = ctypes.sizeof(GlibcChunk.malloc_chunk_t())
1636 self._data = gef.memory.read(
1637 self.base_address, ctypes.sizeof(GlibcChunk.malloc_chunk_t()))
1638 self._chunk = GlibcChunk.malloc_chunk_t().from_buffer_copy(self._data)
1639 return
1641 @property
1642 def prev_size(self) -> int:
1643 return self._chunk.prev_size
1645 @property
1646 def size(self) -> int:
1647 return self._chunk.size & (~0x07)
1649 @property
1650 def flags(self) -> ChunkFlags:
1651 return GlibcChunk.ChunkFlags(self._chunk.size & 0x07)
1653 @property
1654 def fd(self) -> int:
1655 return self._chunk.fd
1657 @property
1658 def bk(self) -> int:
1659 return self._chunk.bk
1661 @property
1662 def fd_nextsize(self) -> int:
1663 return self._chunk.fd_nextsize
1665 @property
1666 def bk_nextsize(self) -> int:
1667 return self._chunk.bk_nextsize
1669 def get_usable_size(self) -> int:
1670 # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537
1671 ptrsz = gef.arch.ptrsize
1672 cursz = self.size
1673 if cursz == 0: return cursz 1673 ↛ exitline 1673 didn't return from function 'get_usable_size' because the return on line 1673 wasn't executed
1674 if self.has_m_bit(): return cursz - 2 * ptrsz 1674 ↛ exitline 1674 didn't return from function 'get_usable_size' because the return on line 1674 wasn't executed
1675 return cursz - ptrsz
1677 @property
1678 def usable_size(self) -> int:
1679 return self.get_usable_size()
1681 def get_prev_chunk_size(self) -> int:
1682 return gef.memory.read_integer(self.prev_size_addr)
1684 def __iter__(self) -> Generator["GlibcChunk", None, None]:
1685 assert gef.heap.main_arena
1686 current_chunk = self
1687 top = gef.heap.main_arena.top
1689 while True:
1690 yield current_chunk
1692 if current_chunk.base_address == top:
1693 break
1695 if current_chunk.size == 0: 1695 ↛ 1696line 1695 didn't jump to line 1696 because the condition on line 1695 was never true
1696 break
1698 next_chunk_addr = current_chunk.get_next_chunk_addr()
1700 if not Address(value=next_chunk_addr).valid: 1700 ↛ 1701line 1700 didn't jump to line 1701 because the condition on line 1700 was never true
1701 break
1703 next_chunk = current_chunk.get_next_chunk()
1704 if next_chunk is None: 1704 ↛ 1705line 1704 didn't jump to line 1705 because the condition on line 1704 was never true
1705 break
1707 current_chunk = next_chunk
1708 return
1710 def get_next_chunk(self, allow_unaligned: bool = False) -> "GlibcChunk":
1711 addr = self.get_next_chunk_addr()
1712 return GlibcChunk(addr, allow_unaligned=allow_unaligned)
1714 def get_next_chunk_addr(self) -> int:
1715 return self.data_address + self.size
1717 def has_p_bit(self) -> bool:
1718 return bool(self.flags & GlibcChunk.ChunkFlags.PREV_INUSE)
1720 def has_m_bit(self) -> bool:
1721 return bool(self.flags & GlibcChunk.ChunkFlags.IS_MMAPPED)
1723 def has_n_bit(self) -> bool:
1724 return bool(self.flags & GlibcChunk.ChunkFlags.NON_MAIN_ARENA)
1726 def is_used(self) -> bool:
1727 """Check if the current block is used by:
1728 - checking the M bit is true
1729 - or checking that next chunk PREV_INUSE flag is true"""
1730 if self.has_m_bit(): 1730 ↛ 1731line 1730 didn't jump to line 1731 because the condition on line 1730 was never true
1731 return True
1733 next_chunk = self.get_next_chunk()
1734 return True if next_chunk.has_p_bit() else False
1736 def __str_sizes(self) -> str:
1737 msg = []
1738 failed = False
1740 try:
1741 msg.append(f"Chunk size: {self.size:d} ({self.size:#x})")
1742 msg.append(f"Usable size: {self.usable_size:d} ({self.usable_size:#x})")
1743 failed = True
1744 except gdb.MemoryError:
1745 msg.append(f"Chunk size: Cannot read at {self.size_addr:#x} (corrupted?)")
1747 try:
1748 prev_chunk_sz = self.get_prev_chunk_size()
1749 msg.append(f"Previous chunk size: {prev_chunk_sz:d} ({prev_chunk_sz:#x})")
1750 failed = True
1751 except gdb.MemoryError:
1752 msg.append(f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)")
1754 if failed: 1754 ↛ 1757line 1754 didn't jump to line 1757 because the condition on line 1754 was always true
1755 msg.append(str(self.flags))
1757 return "\n".join(msg)
1759 def _str_pointers(self) -> str:
1760 fwd = self.data_address
1761 bkw = self.data_address + gef.arch.ptrsize
1763 msg = []
1764 try:
1765 msg.append(f"Forward pointer: {self.fd:#x}")
1766 except gdb.MemoryError:
1767 msg.append(f"Forward pointer: {fwd:#x} (corrupted?)")
1769 try:
1770 msg.append(f"Backward pointer: {self.bk:#x}")
1771 except gdb.MemoryError:
1772 msg.append(f"Backward pointer: {bkw:#x} (corrupted?)")
1774 return "\n".join(msg)
1776 def __str__(self) -> str:
1777 return (f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, "
1778 f"size={self.size:#x}, flags={self.flags!s})")
1780 def psprint(self) -> str:
1781 msg = [
1782 str(self),
1783 self.__str_sizes(),
1784 ]
1785 if not self.is_used(): 1785 ↛ 1786line 1785 didn't jump to line 1786 because the condition on line 1785 was never true
1786 msg.append(f"\n\n{self._str_pointers()}")
1787 return "\n".join(msg) + "\n"
1789 def resolve_type(self) -> str:
1790 ptr_data = gef.memory.read_integer(self.data_address)
1791 if ptr_data != 0:
1792 sym = gdb_get_location_from_symbol(ptr_data)
1793 if sym is not None and "vtable for" in sym[0]:
1794 return sym[0].replace("vtable for ", "")
1796 return ""
1799class GlibcFastChunk(GlibcChunk):
1801 @property
1802 def fd(self) -> int:
1803 assert(gef and gef.libc.version)
1804 if gef.libc.version < (2, 32): 1804 ↛ 1805line 1804 didn't jump to line 1805 because the condition on line 1804 was never true
1805 return self._chunk.fd
1806 return self.reveal_ptr(self.data_address)
1808 def protect_ptr(self, pos: int, pointer: int) -> int:
1809 """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L339"""
1810 assert(gef and gef.libc.version)
1811 if gef.libc.version < (2, 32):
1812 return pointer
1813 return (pos >> 12) ^ pointer
1815 def reveal_ptr(self, pointer: int) -> int:
1816 """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L341"""
1817 assert(gef and gef.libc.version)
1818 if gef.libc.version < (2, 32): 1818 ↛ 1819line 1818 didn't jump to line 1819 because the condition on line 1818 was never true
1819 return pointer
1820 return gef.memory.read_integer(pointer) ^ (pointer >> 12)
1822class GlibcTcacheChunk(GlibcFastChunk):
1823 pass
1825@deprecated("Use GefLibcManager.find_libc_version()")
1826def get_libc_version() -> tuple[int, ...]:
1827 return GefLibcManager.find_libc_version()
1829def titlify(text: str, color: str | None = None, msg_color: str | None = None) -> str:
1830 """Print a centered title."""
1831 _, cols = get_terminal_size()
1832 nb = (cols - len(text) - 2) // 2
1833 line_color = color or gef.config["theme.default_title_line"]
1834 text_color = msg_color or gef.config["theme.default_title_message"]
1836 msg = [Color.colorify(f"{HORIZONTAL_LINE * nb} ", line_color),
1837 Color.colorify(text, text_color),
1838 Color.colorify(f" {HORIZONTAL_LINE * nb}", line_color)]
1839 return "".join(msg)
1842def dbg(msg: str) -> None:
1843 if gef.config["gef.debug"] is True:
1844 gef_print(f"{Color.colorify('[=]', 'bold cyan')} {msg}")
1845 return
1848def err(msg: str) -> None:
1849 gef_print(f"{Color.colorify('[!]', 'bold red')} {msg}")
1850 return
1853def warn(msg: str) -> None:
1854 gef_print(f"{Color.colorify('[*]', 'bold yellow')} {msg}")
1855 return
1858def ok(msg: str) -> None:
1859 gef_print(f"{Color.colorify('[+]', 'bold green')} {msg}")
1860 return
1863def info(msg: str) -> None:
1864 gef_print(f"{Color.colorify('[+]', 'bold blue')} {msg}")
1865 return
1868def push_context_message(level: str, message: str) -> None:
1869 """Push the message to be displayed the next time the context is invoked."""
1870 if level not in ("error", "warn", "ok", "info"): 1870 ↛ 1871line 1870 didn't jump to line 1871 because the condition on line 1870 was never true
1871 err(f"Invalid level '{level}', discarding message")
1872 return
1873 gef.ui.context_messages.append((level, message))
1874 return
1877def show_last_exception() -> None:
1878 """Display the last Python exception."""
1880 def _show_code_line(fname: str, idx: int) -> str:
1881 fpath = pathlib.Path(os.path.expanduser(os.path.expandvars(fname)))
1882 _data = fpath.read_text().splitlines()
1883 return _data[idx - 1] if 0 < idx < len(_data) else ""
1885 gef_print("")
1886 exc_type, exc_value, exc_traceback = sys.exc_info()
1887 exc_name = exc_type.__name__ if exc_type else "Unknown"
1888 gef_print(" Exception raised ".center(80, HORIZONTAL_LINE))
1889 gef_print(f"{Color.colorify(exc_name, 'bold underline red')}: {exc_value}")
1890 gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE))
1892 for fs in traceback.extract_tb(exc_traceback)[::-1]:
1893 filename, lineno, method, code = fs
1895 if not code or not code.strip(): 1895 ↛ 1896line 1895 didn't jump to line 1896 because the condition on line 1895 was never true
1896 code = _show_code_line(filename, lineno)
1898 gef_print(f"""{DOWN_ARROW} File "{Color.yellowify(filename)}", line {lineno:d}, in {Color.greenify(method)}()""")
1899 gef_print(f" {RIGHT_ARROW} {code}")
1901 gef_print(" Version ".center(80, HORIZONTAL_LINE))
1902 gdb.execute("version full")
1903 gef_print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE))
1904 gdb.execute("show commands")
1905 gef_print(" Runtime environment ".center(80, HORIZONTAL_LINE))
1906 gef_print(f"* GDB: {gdb.VERSION}")
1907 gef_print(f"* Python: {sys.version_info.major:d}.{sys.version_info.minor:d}.{sys.version_info.micro:d} - {sys.version_info.releaselevel}")
1908 gef_print(f"* OS: {platform.system()} - {platform.release()} ({platform.machine()})")
1910 try:
1911 lsb_release = which("lsb_release")
1912 gdb.execute(f"!'{lsb_release}' -a")
1913 except FileNotFoundError:
1914 pass
1916 gef_print(HORIZONTAL_LINE*80)
1917 gef_print("")
1918 return
1921def gef_pystring(x: bytes) -> str:
1922 """Returns a sanitized version as string of the bytes list given in input."""
1923 res = str(x, encoding="utf-8")
1924 substs = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t"), ("\v", "\\v"), ("\b", "\\b"), ]
1925 for _x, _y in substs: res = res.replace(_x, _y)
1926 return res
1929def gef_pybytes(x: str) -> bytes:
1930 """Returns an immutable bytes list from the string given as input."""
1931 return bytes(str(x), encoding="utf-8")
1934@lru_cache()
1935def which(program: str) -> pathlib.Path:
1936 """Locate a command on the filesystem."""
1937 res = shutil.which(program)
1938 if not res:
1939 raise FileNotFoundError(f"Missing file `{program}`")
1940 return pathlib.Path(res)
1943def style_byte(b: int, color: bool = True) -> str:
1944 style = {
1945 "nonprintable": "yellow",
1946 "printable": "white",
1947 "00": "gray",
1948 "0a": "blue",
1949 "ff": "green",
1950 }
1951 sbyte = f"{b:02x}"
1952 if not color or gef.config["highlight.regex"]:
1953 return sbyte
1955 if sbyte in style:
1956 st = style[sbyte]
1957 elif chr(b) in (string.ascii_letters + string.digits + string.punctuation + " "):
1958 st = style.get("printable")
1959 else:
1960 st = style.get("nonprintable")
1961 if st: 1961 ↛ 1963line 1961 didn't jump to line 1963 because the condition on line 1961 was always true
1962 sbyte = Color.colorify(sbyte, st)
1963 return sbyte
1966def hexdump(source: ByteString, length: int = 0x10, separator: str = ".", show_raw: bool = False, show_symbol: bool = True, base: int = 0x00) -> str:
1967 """Return the hexdump of `src` argument.
1968 @param source *MUST* be of type bytes or bytearray
1969 @param length is the length of items per line
1970 @param separator is the default character to use if one byte is not printable
1971 @param show_raw if True, do not add the line nor the text translation
1972 @param base is the start address of the block being hexdump
1973 @return a string with the hexdump"""
1974 result = []
1975 align = gef.arch.ptrsize * 2 + 2 if is_alive() else 18
1977 for i in range(0, len(source), length):
1978 chunk = bytearray(source[i : i + length])
1979 hexa = " ".join([style_byte(b, color=not show_raw) for b in chunk])
1981 if show_raw:
1982 result.append(hexa)
1983 continue
1985 text = "".join([chr(b) if 0x20 <= b < 0x7F else separator for b in chunk])
1986 if show_symbol: 1986 ↛ 1990line 1986 didn't jump to line 1990 because the condition on line 1986 was always true
1987 sym = gdb_get_location_from_symbol(base + i)
1988 sym = f"<{sym[0]:s}+{sym[1]:04x}>" if sym else ""
1989 else:
1990 sym = ""
1992 result.append(f"{base + i:#0{align}x} {sym} {hexa:<{3 * length}} {text}")
1993 return "\n".join(result)
1996def is_debug() -> bool:
1997 """Check if debug mode is enabled."""
1998 return gef.config["gef.debug"] is True
2001def buffer_output() -> bool:
2002 """Check if output should be buffered until command completion."""
2003 return gef.config["gef.buffer"] is True
2006def hide_context() -> bool:
2007 """Helper function to hide the context pane."""
2008 gef.ui.context_hidden = True
2009 return True
2012def unhide_context() -> bool:
2013 """Helper function to unhide the context pane."""
2014 gef.ui.context_hidden = False
2015 return True
2018class DisableContextOutputContext:
2019 def __enter__(self) -> None:
2020 hide_context()
2021 return
2023 def __exit__(self, *exc: Any) -> None:
2024 unhide_context()
2025 return
2028class RedirectOutputContext:
2029 def __init__(self, to_file: str = "/dev/null") -> None:
2030 if " " in to_file: raise ValueError("Target filepath cannot contain spaces") 2030 ↛ exitline 2030 didn't except from function '__init__' because the raise on line 2030 wasn't executed
2031 self.redirection_target_file = to_file
2032 return
2034 def __enter__(self) -> None:
2035 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`."""
2036 gdb.execute("set logging overwrite")
2037 gdb.execute(f"set logging file {self.redirection_target_file}")
2038 gdb.execute("set logging redirect on")
2040 if GDB_VERSION >= (12, 0): 2040 ↛ 2043line 2040 didn't jump to line 2043 because the condition on line 2040 was always true
2041 gdb.execute("set logging enabled on")
2042 else:
2043 gdb.execute("set logging on")
2044 return
2046 def __exit__(self, *exc: Any) -> None:
2047 """Disable the output redirection, if any."""
2048 if GDB_VERSION >= (12, 0): 2048 ↛ 2051line 2048 didn't jump to line 2051 because the condition on line 2048 was always true
2049 gdb.execute("set logging enabled off")
2050 else:
2051 gdb.execute("set logging off")
2052 gdb.execute("set logging redirect off")
2053 return
2056def enable_redirect_output(to_file: str = "/dev/null") -> None:
2057 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`."""
2058 if " " in to_file: raise ValueError("Target filepath cannot contain spaces")
2059 gdb.execute("set logging overwrite")
2060 gdb.execute(f"set logging file {to_file}")
2061 gdb.execute("set logging redirect on")
2063 if GDB_VERSION >= (12, 0):
2064 gdb.execute("set logging enabled on")
2065 else:
2066 gdb.execute("set logging on")
2067 return
2070def disable_redirect_output() -> None:
2071 """Disable the output redirection, if any."""
2072 if GDB_VERSION >= (12, 0):
2073 gdb.execute("set logging enabled off")
2074 else:
2075 gdb.execute("set logging off")
2076 gdb.execute("set logging redirect off")
2077 return
2079@deprecated("use `pathlib.Path(...).mkdir()`")
2080def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path:
2081 """Recursive mkdir() creation. If successful, return the absolute path of the directory created."""
2082 fpath = pathlib.Path(path)
2083 if not fpath.is_dir():
2084 fpath.mkdir(mode=mode, exist_ok=True, parents=True)
2085 return fpath.absolute()
2088@lru_cache()
2089def gdb_lookup_symbol(sym: str) -> tuple[gdb.Symtab_and_line, ...] | None:
2090 """Fetch the proper symbol or None if not defined."""
2091 try:
2092 res = gdb.decode_line(sym)[1] # pylint: disable=E1136
2093 return res
2094 except gdb.error:
2095 return None
2097@lru_cache(maxsize=512)
2098def gdb_get_location_from_symbol(address: int) -> tuple[str, int] | None:
2099 """Retrieve the location of the `address` argument from the symbol table.
2100 Return a tuple with the name and offset if found, None otherwise."""
2101 # this is horrible, ugly hack and shitty perf...
2102 # find a *clean* way to get gdb.Location from an address
2103 sym = str(gdb.execute(f"info symbol {address:#x}", to_string=True))
2104 if sym.startswith("No symbol matches"):
2105 return None
2107 # gdb outputs symbols with format: "<symbol_name> + <offset> in section <section_name> of <file>",
2108 # here, we are only interested in symbol name and offset.
2109 i = sym.find(" in section ")
2110 sym = sym[:i].split(" + ")
2111 name, offset = sym[0], 0
2112 if len(sym) == 2 and sym[1].isdigit():
2113 offset = int(sym[1])
2114 return name, offset
2117def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None, None]:
2118 """Disassemble instructions from `start_pc` (Integer). Accepts the following named
2119 parameters:
2120 - `end_pc` (Integer) only instructions whose start address fall in the interval from
2121 start_pc to end_pc are returned.
2122 - `count` (Integer) list at most this many disassembled instructions
2123 If `end_pc` and `count` are not provided, the function will behave as if `count=1`.
2124 Return an iterator of Instruction objects
2125 """
2126 frame = gdb.selected_frame()
2127 arch = frame.architecture()
2129 for insn in arch.disassemble(start_pc, **kwargs):
2130 assert isinstance(insn["addr"], int)
2131 assert isinstance(insn["length"], int)
2132 assert isinstance(insn["asm"], str)
2133 address = insn["addr"]
2134 asm = insn["asm"].rstrip().split(None, 1)
2135 if len(asm) > 1:
2136 mnemo, operands = asm
2137 operands = operands.split(",")
2138 else:
2139 mnemo, operands = asm[0], []
2141 loc = gdb_get_location_from_symbol(address)
2142 location = f"<{loc[0]}+{loc[1]:04x}>" if loc else ""
2144 opcodes = gef.memory.read(insn["addr"], insn["length"])
2146 yield Instruction(address, location, mnemo, operands, opcodes)
2149def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> int | None:
2150 """Return the address (Integer) of the `n`-th instruction before `addr`."""
2151 # fixed-length ABI
2152 if gef.arch.instruction_length: 2152 ↛ 2153line 2152 didn't jump to line 2153 because the condition on line 2152 was never true
2153 return max(0, addr - n * gef.arch.instruction_length)
2155 # variable-length ABI
2156 cur_insn_addr = gef_current_instruction(addr).address
2158 # we try to find a good set of previous instructions by "guessing" disassembling backwards
2159 # the 15 comes from the longest instruction valid size
2160 for i in range(15 * n, 0, -1): 2160 ↛ 2183line 2160 didn't jump to line 2183 because the loop on line 2160 didn't complete
2161 try:
2162 insns = list(gdb_disassemble(addr - i, end_pc=cur_insn_addr))
2163 except gdb.MemoryError:
2164 # this is because we can hit an unmapped page trying to read backward
2165 break
2167 # 1. check that the disassembled instructions list size can satisfy
2168 if len(insns) < n + 1: # we expect the current instruction plus the n before it 2168 ↛ 2169line 2168 didn't jump to line 2169 because the condition on line 2168 was never true
2169 continue
2171 # If the list of instructions is longer than what we need, then we
2172 # could get lucky and already have more than what we need, so slice down
2173 insns = insns[-n - 1 :]
2175 # 2. check that the sequence ends with the current address
2176 if insns[-1].address != cur_insn_addr: 2176 ↛ 2177line 2176 didn't jump to line 2177 because the condition on line 2176 was never true
2177 continue
2179 # 3. check all instructions are valid
2180 if all(insn.is_valid() for insn in insns): 2180 ↛ 2160line 2180 didn't jump to line 2160 because the condition on line 2180 was always true
2181 return insns[0].address
2183 return None
2186@deprecated(solution="Use `gef_instruction_n().address`")
2187def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int:
2188 """Return the address of the `n`-th instruction after `addr`. """
2189 return gef_instruction_n(addr, n).address
2192def gef_instruction_n(addr: int, n: int) -> Instruction:
2193 """Return the `n`-th instruction after `addr` as an Instruction object. Note that `n` is treated as
2194 an positive index, starting from 0 (current instruction address)"""
2195 return list(gdb_disassemble(addr, count=n + 1))[n]
2198def gef_get_instruction_at(addr: int) -> Instruction:
2199 """Return the full Instruction found at the specified address."""
2200 insn = next(gef_disassemble(addr, 1))
2201 return insn
2204def gef_current_instruction(addr: int) -> Instruction:
2205 """Return the current instruction as an Instruction object."""
2206 return gef_instruction_n(addr, 0)
2209def gef_next_instruction(addr: int) -> Instruction:
2210 """Return the next instruction as an Instruction object."""
2211 return gef_instruction_n(addr, 1)
2214def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Instruction, None, None]:
2215 """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr`.
2216 Return an iterator of Instruction objects."""
2217 nb_insn = max(1, nb_insn)
2219 if nb_prev:
2220 try:
2221 start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev)
2222 if start_addr:
2223 for insn in gdb_disassemble(start_addr, count=nb_prev):
2224 if insn.address == addr: break 2224 ↛ 2230line 2224 didn't jump to line 2230 because the break on line 2224 wasn't executed
2225 yield insn
2226 except gdb.MemoryError:
2227 # If the address pointing to the previous instruction(s) is not mapped, simply skip them
2228 pass
2230 for insn in gdb_disassemble(addr, count=nb_insn):
2231 yield insn
2234def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> str | list[str]:
2235 """Execute an external command and return the result."""
2236 res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False))
2237 return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res)
2240def gef_execute_gdb_script(commands: str) -> None:
2241 """Execute the parameter `source` as GDB command. This is done by writing `commands` to
2242 a temporary file, which is then executed via GDB `source` command. The tempfile is then deleted."""
2243 fd, fname = tempfile.mkstemp(suffix=".gdb", prefix="gef_")
2244 with os.fdopen(fd, "w") as f:
2245 f.write(commands)
2246 f.flush()
2248 fname = pathlib.Path(fname)
2249 if fname.is_file() and os.access(fname, os.R_OK):
2250 gdb.execute(f"source {fname}")
2251 fname.unlink()
2252 return
2255@deprecated("Use Elf(fname).checksec()")
2256def checksec(filename: str) -> dict[str, bool]:
2257 return Elf(filename).checksec
2260@deprecated("Use `gef.arch` instead")
2261def get_arch() -> str:
2262 """Return the binary's architecture."""
2263 if is_alive():
2264 arch = gdb.selected_frame().architecture()
2265 return arch.name()
2267 arch_str = (gdb.execute("show architecture", to_string=True) or "").strip()
2268 pat = "The target architecture is set automatically (currently "
2269 if arch_str.startswith(pat):
2270 arch_str = arch_str[len(pat):].rstrip(")")
2271 return arch_str
2273 pat = "The target architecture is assumed to be "
2274 if arch_str.startswith(pat):
2275 return arch_str[len(pat):]
2277 pat = "The target architecture is set to "
2278 if arch_str.startswith(pat):
2279 # GDB version >= 10.1
2280 if '"auto"' in arch_str:
2281 return re.findall(r"currently \"(.+)\"", arch_str)[0]
2282 return re.findall(r"\"(.+)\"", arch_str)[0]
2284 # Unknown, we throw an exception to be safe
2285 raise RuntimeError(f"Unknown architecture: {arch_str}")
2288@deprecated("Use `gef.binary.entry_point` instead")
2289def get_entry_point() -> int | None:
2290 """Return the binary entry point."""
2291 return gef.binary.entry_point if gef.binary else None
2294def is_pie(fpath: str) -> bool:
2295 return Elf(fpath).checksec["PIE"]
2298@deprecated("Prefer `gef.arch.endianness == Endianness.BIG_ENDIAN`")
2299def is_big_endian() -> bool:
2300 return gef.arch.endianness == Endianness.BIG_ENDIAN
2303@deprecated("gef.arch.endianness == Endianness.LITTLE_ENDIAN")
2304def is_little_endian() -> bool:
2305 return gef.arch.endianness == Endianness.LITTLE_ENDIAN
2308def flags_to_human(reg_value: int, value_table: dict[int, str]) -> str:
2309 """Return a human readable string showing the flag states."""
2310 flags = []
2311 for bit_index, name in value_table.items():
2312 flags.append(Color.boldify(name.upper()) if reg_value & (1<<bit_index) != 0 else name.lower())
2313 return f"[{' '.join(flags)}]"
2316@lru_cache()
2317def get_section_base_address(name: str) -> int | None:
2318 section = process_lookup_path(name)
2319 return section.page_start if section else None
2322@lru_cache()
2323def get_zone_base_address(name: str) -> int | None:
2324 zone = file_lookup_name_path(name, get_filepath())
2325 return zone.zone_start if zone else None
2328#
2329# Architecture classes
2330#
2332@deprecated("Using the decorator `register_architecture` is unnecessary")
2333def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]:
2334 return cls
2336class ArchitectureBase:
2337 """Class decorator for declaring an architecture to GEF."""
2338 aliases: tuple[str | Elf.Abi, ...] = ()
2340 def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs):
2341 global __registered_architectures__
2342 super().__init_subclass__(**kwargs)
2343 for key in getattr(cls, "aliases"):
2344 if issubclass(cls, Architecture): 2344 ↛ 2343line 2344 didn't jump to line 2343 because the condition on line 2344 was always true
2345 if isinstance(key, str):
2346 __registered_architectures__[key.lower()] = cls
2347 else:
2348 __registered_architectures__[key] = cls
2349 return
2352class Architecture(ArchitectureBase):
2353 """Generic metaclass for the architecture supported by GEF."""
2355 # Mandatory defined attributes by inheriting classes
2356 arch: str
2357 mode: str
2358 all_registers: tuple[str, ...]
2359 nop_insn: bytes
2360 return_register: str
2361 flag_register: str | None
2362 instruction_length: int | None
2363 flags_table: dict[int, str]
2364 syscall_register: str | None
2365 syscall_instructions: tuple[str, ...]
2366 function_parameters: tuple[str, ...]
2368 # Optionally defined attributes
2369 _ptrsize: int | None = None
2370 _endianness: Endianness | None = None
2371 special_registers: tuple[()] | tuple[str, ...] = ()
2372 maps: GefMemoryMapProvider | None = None
2374 def __init_subclass__(cls, **kwargs):
2375 super().__init_subclass__(**kwargs)
2376 attributes = ("arch", "mode", "aliases", "all_registers", "nop_insn",
2377 "return_register", "flag_register", "instruction_length", "flags_table",
2378 "function_parameters",)
2379 if not all(map(lambda x: hasattr(cls, x), attributes)): 2379 ↛ 2380line 2379 didn't jump to line 2380 because the condition on line 2379 was never true
2380 raise NotImplementedError
2382 def __str__(self) -> str:
2383 return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})"
2385 def __repr__(self) -> str:
2386 return self.__str__()
2388 @staticmethod
2389 def supports_gdb_arch(gdb_arch: str) -> bool | None:
2390 """If implemented by a child `Architecture`, this function dictates if the current class
2391 supports the loaded ELF file (which can be accessed via `gef.binary`). This callback
2392 function will override any assumption made by GEF to determine the architecture."""
2393 return None
2395 def flag_register_to_human(self, val: int | None = None) -> str:
2396 raise NotImplementedError
2398 def is_call(self, insn: Instruction) -> bool:
2399 raise NotImplementedError
2401 def is_ret(self, insn: Instruction) -> bool:
2402 raise NotImplementedError
2404 def is_conditional_branch(self, insn: Instruction) -> bool:
2405 raise NotImplementedError
2407 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
2408 raise NotImplementedError
2410 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
2411 raise NotImplementedError
2413 def canary_address(self) -> int:
2414 raise NotImplementedError
2416 @classmethod
2417 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2418 raise NotImplementedError
2420 def reset_caches(self) -> None:
2421 self.__get_register_for_selected_frame.cache_clear()
2422 return
2424 def __get_register(self, regname: str) -> int:
2425 """Return a register's value."""
2426 curframe = gdb.selected_frame()
2427 key = curframe.pc() ^ int(curframe.read_register('sp')) # todo: check when/if gdb.Frame implements `level()`
2428 return self.__get_register_for_selected_frame(regname, int(key))
2430 @lru_cache()
2431 def __get_register_for_selected_frame(self, regname: str, hash_key: int) -> int:
2432 # 1st chance
2433 try:
2434 return parse_address(regname)
2435 except gdb.error:
2436 pass
2438 # 2nd chance - if an exception, propagate it
2439 regname = regname.lstrip("$")
2440 value = gdb.selected_frame().read_register(regname)
2441 return int(value)
2443 def register(self, name: str) -> int:
2444 if not is_alive():
2445 raise gdb.error("No debugging session active")
2446 return self.__get_register(name)
2448 @property
2449 def registers(self) -> Generator[str, None, None]:
2450 yield from self.all_registers
2452 @property
2453 def pc(self) -> int:
2454 return self.register("$pc")
2456 @property
2457 def sp(self) -> int:
2458 return self.register("$sp")
2460 @property
2461 def fp(self) -> int:
2462 return self.register("$fp")
2464 @property
2465 def ptrsize(self) -> int:
2466 if not self._ptrsize: 2466 ↛ 2467line 2466 didn't jump to line 2467 because the condition on line 2466 was never true
2467 res = cached_lookup_type("size_t")
2468 if res is not None:
2469 self._ptrsize = res.sizeof
2470 else:
2471 self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof
2472 return self._ptrsize
2474 @property
2475 def endianness(self) -> Endianness:
2476 if not self._endianness:
2477 output = (gdb.execute("show endian", to_string=True) or "").strip().lower()
2478 if "little endian" in output: 2478 ↛ 2480line 2478 didn't jump to line 2480 because the condition on line 2478 was always true
2479 self._endianness = Endianness.LITTLE_ENDIAN
2480 elif "big endian" in output:
2481 self._endianness = Endianness.BIG_ENDIAN
2482 else:
2483 raise OSError(f"No valid endianness found in '{output}'")
2484 return self._endianness
2486 def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]:
2487 """Retrieves the correct parameter used for the current function call."""
2488 reg = self.function_parameters[i]
2489 val = self.register(reg)
2490 key = reg
2491 return key, val
2494class GenericArchitecture(Architecture):
2495 arch = "Generic"
2496 mode = ""
2497 aliases = ("GenericArchitecture",)
2498 all_registers = ()
2499 instruction_length = 0
2500 return_register = ""
2501 function_parameters = ()
2502 syscall_register = ""
2503 syscall_instructions = ()
2504 nop_insn = b""
2505 flag_register = None
2506 flags_table = {}
2509class RISCV(Architecture):
2510 arch = "RISCV"
2511 mode = "RISCV"
2512 aliases = ("RISCV", Elf.Abi.RISCV)
2513 all_registers = ("$zero", "$ra", "$sp", "$gp", "$tp", "$t0", "$t1",
2514 "$t2", "$fp", "$s1", "$a0", "$a1", "$a2", "$a3",
2515 "$a4", "$a5", "$a6", "$a7", "$s2", "$s3", "$s4",
2516 "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11",
2517 "$t3", "$t4", "$t5", "$t6",)
2518 return_register = "$a0"
2519 function_parameters = ("$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7")
2520 syscall_register = "$a7"
2521 syscall_instructions = ("ecall",)
2522 nop_insn = b"\x00\x00\x00\x13"
2523 # RISC-V has no flags registers
2524 flag_register = None
2525 flags_table = {}
2527 @property
2528 def instruction_length(self) -> int:
2529 return 4
2531 def is_call(self, insn: Instruction) -> bool:
2532 return insn.mnemonic == "call"
2534 def is_ret(self, insn: Instruction) -> bool:
2535 mnemo = insn.mnemonic
2536 if mnemo == "ret":
2537 return True
2538 elif (mnemo == "jalr" and insn.operands[0] == "zero" and
2539 insn.operands[1] == "ra" and insn.operands[2] == 0):
2540 return True
2541 elif (mnemo == "c.jalr" and insn.operands[0] == "ra"):
2542 return True
2543 return False
2545 @classmethod
2546 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2547 raise OSError(f"Architecture {cls.arch} not supported yet")
2549 @property
2550 def ptrsize(self) -> int:
2551 if self._ptrsize is not None:
2552 return self._ptrsize
2553 if is_alive():
2554 self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof
2555 return self._ptrsize
2556 return 4
2558 def is_conditional_branch(self, insn: Instruction) -> bool:
2559 return insn.mnemonic.startswith("b")
2561 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
2562 def long_to_twos_complement(v: int) -> int:
2563 """Convert a python long value to its two's complement."""
2564 if is_32bit():
2565 if v & 0x80000000:
2566 return v - 0x100000000
2567 elif is_64bit():
2568 if v & 0x8000000000000000:
2569 return v - 0x10000000000000000
2570 else:
2571 raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported")
2572 return v
2574 mnemo = insn.mnemonic
2575 condition = mnemo[1:]
2577 if condition.endswith("z"):
2578 # r2 is the zero register if we are comparing to 0
2579 rs1 = gef.arch.register(insn.operands[0])
2580 rs2 = gef.arch.register("$zero")
2581 condition = condition[:-1]
2582 elif len(insn.operands) > 2:
2583 # r2 is populated with the second operand
2584 rs1 = gef.arch.register(insn.operands[0])
2585 rs2 = gef.arch.register(insn.operands[1])
2586 else:
2587 raise OSError(f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`")
2589 # If the conditional operation is not unsigned, convert the python long into
2590 # its two's complement
2591 if not condition.endswith("u"):
2592 rs2 = long_to_twos_complement(rs2)
2593 rs1 = long_to_twos_complement(rs1)
2594 else:
2595 condition = condition[:-1]
2597 if condition == "eq":
2598 if rs1 == rs2: taken, reason = True, f"{rs1}={rs2}"
2599 else: taken, reason = False, f"{rs1}!={rs2}"
2600 elif condition == "ne":
2601 if rs1 != rs2: taken, reason = True, f"{rs1}!={rs2}"
2602 else: taken, reason = False, f"{rs1}={rs2}"
2603 elif condition == "lt":
2604 if rs1 < rs2: taken, reason = True, f"{rs1}<{rs2}"
2605 else: taken, reason = False, f"{rs1}>={rs2}"
2606 elif condition == "le":
2607 if rs1 <= rs2: taken, reason = True, f"{rs1}<={rs2}"
2608 else: taken, reason = False, f"{rs1}>{rs2}"
2609 elif condition == "ge":
2610 if rs1 >= rs2: taken, reason = True, f"{rs1}>={rs2}"
2611 else: taken, reason = False, f"{rs1}<{rs2}"
2612 else:
2613 raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet")
2615 return taken, reason
2617 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
2618 ra = None
2619 if self.is_ret(insn):
2620 ra = gef.arch.register("$ra")
2621 else:
2622 older = frame.older()
2623 if older:
2624 ra = to_unsigned_long(older.pc())
2625 return ra
2627 def flag_register_to_human(self, val: int | None = None) -> str:
2628 # RISC-V has no flags registers, return an empty string to
2629 # preserve the Architecture API
2630 return ""
2632class ARM(Architecture):
2633 aliases = ("ARM", Elf.Abi.ARM)
2634 arch = "ARM"
2635 all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6",
2636 "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp",
2637 "$lr", "$pc", "$cpsr",)
2639 nop_insn = b"\x00\xf0\x20\xe3" # hint #0
2640 return_register = "$r0"
2641 flag_register: str = "$cpsr"
2642 flags_table = {
2643 31: "negative",
2644 30: "zero",
2645 29: "carry",
2646 28: "overflow",
2647 7: "interrupt",
2648 6: "fast",
2649 5: "thumb",
2650 }
2651 function_parameters = ("$r0", "$r1", "$r2", "$r3")
2652 syscall_register = "$r7"
2653 syscall_instructions = ("swi 0x0", "swi NR")
2654 _endianness = Endianness.LITTLE_ENDIAN
2656 def is_thumb(self) -> bool:
2657 """Determine if the machine is currently in THUMB mode."""
2658 return is_alive() and (self.cpsr & (1 << 5) == 1)
2660 @property
2661 def pc(self) -> int | None:
2662 pc = gef.arch.register("$pc")
2663 if self.is_thumb():
2664 pc += 1
2665 return pc
2667 @property
2668 def cpsr(self) -> int:
2669 if not is_alive():
2670 raise RuntimeError("Cannot get CPSR, program not started?")
2671 return gef.arch.register(self.flag_register)
2673 @property
2674 def mode(self) -> str:
2675 return "THUMB" if self.is_thumb() else "ARM"
2677 @property
2678 def instruction_length(self) -> int | None:
2679 # Thumb instructions have variable-length (2 or 4-byte)
2680 return None if self.is_thumb() else 4
2682 @property
2683 def ptrsize(self) -> int:
2684 return 4
2686 def is_call(self, insn: Instruction) -> bool:
2687 mnemo = insn.mnemonic
2688 call_mnemos = {"bl", "blx"}
2689 return mnemo in call_mnemos
2691 def is_ret(self, insn: Instruction) -> bool:
2692 pop_mnemos = {"pop"}
2693 branch_mnemos = {"bl", "bx"}
2694 write_mnemos = {"ldr", "add"}
2695 if insn.mnemonic in pop_mnemos:
2696 return insn.operands[-1] == " pc}"
2697 if insn.mnemonic in branch_mnemos:
2698 return insn.operands[-1] == "lr"
2699 if insn.mnemonic in write_mnemos:
2700 return insn.operands[0] == "pc"
2701 return False
2703 def flag_register_to_human(self, val: int | None = None) -> str:
2704 # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1
2705 if val is None:
2706 reg = self.flag_register
2707 val = gef.arch.register(reg)
2708 return flags_to_human(val, self.flags_table)
2710 def is_conditional_branch(self, insn: Instruction) -> bool:
2711 conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"}
2712 return insn.mnemonic[-2:] in conditions
2714 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
2715 mnemo = insn.mnemonic
2716 # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html
2717 flags = dict((self.flags_table[k], k) for k in self.flags_table)
2718 val = gef.arch.register(self.flag_register)
2719 taken, reason = False, ""
2721 if mnemo.endswith("eq"): taken, reason = bool(val&(1<<flags["zero"])), "Z"
2722 elif mnemo.endswith("ne"): taken, reason = not bool(val&(1<<flags["zero"])), "!Z"
2723 elif mnemo.endswith("lt"):
2724 taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V"
2725 elif mnemo.endswith("le"):
2726 taken, reason = bool(val&(1<<flags["zero"])) or \
2727 bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V"
2728 elif mnemo.endswith("gt"):
2729 taken, reason = bool(val&(1<<flags["zero"])) == 0 and \
2730 bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V"
2731 elif mnemo.endswith("ge"):
2732 taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V"
2733 elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V"
2734 elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V"
2735 elif mnemo.endswith("mi"):
2736 taken, reason = bool(val&(1<<flags["negative"])), "N"
2737 elif mnemo.endswith("pl"):
2738 taken, reason = not val&(1<<flags["negative"]), "N==0"
2739 elif mnemo.endswith("hi"):
2740 taken, reason = bool(val&(1<<flags["carry"])) and not bool(val&(1<<flags["zero"])), "C && !Z"
2741 elif mnemo.endswith("ls"):
2742 taken, reason = not val&(1<<flags["carry"]) or bool(val&(1<<flags["zero"])), "!C || Z"
2743 elif mnemo.endswith("cs"): taken, reason = bool(val&(1<<flags["carry"])), "C"
2744 elif mnemo.endswith("cc"): taken, reason = not val&(1<<flags["carry"]), "!C"
2745 return taken, reason
2747 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
2748 if not self.is_ret(insn):
2749 older = frame.older()
2750 if not older:
2751 return None
2752 return int(older.pc())
2754 # If it's a pop, we have to peek into the stack, otherwise use lr
2755 if insn.mnemonic == "pop":
2756 ra_addr = gef.arch.sp + (len(insn.operands)-1) * self.ptrsize
2757 if not ra_addr:
2758 return None
2759 ra = dereference(ra_addr)
2760 if ra is None:
2761 return None
2762 return to_unsigned_long(ra)
2763 elif insn.mnemonic == "ldr":
2764 ra = dereference(gef.arch.sp)
2765 if ra is None:
2766 return None
2767 return to_unsigned_long(ra)
2768 else: # 'bx lr' or 'add pc, lr, #0'
2769 return gef.arch.register("$lr")
2771 @classmethod
2772 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2773 _NR_mprotect = 125
2774 insns = [
2775 "push {r0-r2, r7}",
2776 f"mov r1, {addr & 0xffff:d}",
2777 f"mov r0, {(addr & 0xffff0000) >> 16:d}",
2778 "lsl r0, r0, 16",
2779 "add r0, r0, r1",
2780 f"mov r1, {size & 0xffff:d}",
2781 f"mov r2, {perm.value & 0xff:d}",
2782 f"mov r7, {_NR_mprotect:d}",
2783 "svc 0",
2784 "pop {r0-r2, r7}",
2785 ]
2786 return "; ".join(insns)
2789class AARCH64(ARM):
2790 aliases = ("ARM64", "AARCH64", Elf.Abi.AARCH64)
2791 arch = "ARM64"
2792 mode: str = ""
2794 all_registers = (
2795 "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",
2796 "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14","$x15",
2797 "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23",
2798 "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp",
2799 "$pc", "$cpsr", "$fpsr", "$fpcr",)
2800 return_register = "$x0"
2801 flag_register = "$cpsr"
2802 flags_table = {
2803 31: "negative",
2804 30: "zero",
2805 29: "carry",
2806 28: "overflow",
2807 7: "interrupt",
2808 9: "endian",
2809 6: "fast",
2810 5: "t32",
2811 4: "m[4]",
2812 }
2813 nop_insn = b"\x1f\x20\x03\xd5" # hint #0
2814 function_parameters = ("$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",)
2815 syscall_register = "$x8"
2816 syscall_instructions = ("svc $x0",)
2818 def is_call(self, insn: Instruction) -> bool:
2819 mnemo = insn.mnemonic
2820 call_mnemos = {"bl", "blr"}
2821 return mnemo in call_mnemos
2823 def flag_register_to_human(self, val: int | None = None) -> str:
2824 # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf
2825 reg = self.flag_register
2826 if not val:
2827 val = gef.arch.register(reg)
2828 return flags_to_human(val, self.flags_table)
2830 def is_aarch32(self) -> bool:
2831 """Determine if the CPU is currently in AARCH32 mode from runtime."""
2832 return (self.cpsr & (1 << 4) != 0) and (self.cpsr & (1 << 5) == 0)
2834 def is_thumb32(self) -> bool:
2835 """Determine if the CPU is currently in THUMB32 mode from runtime."""
2836 return (self.cpsr & (1 << 4) == 1) and (self.cpsr & (1 << 5) == 1)
2838 @property
2839 def ptrsize(self) -> int:
2840 """Determine the size of pointer from the current CPU mode"""
2841 if not is_alive():
2842 return 8
2843 if self.is_aarch32():
2844 return 4
2845 if self.is_thumb32():
2846 return 2
2847 return 8
2849 @classmethod
2850 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2851 _NR_mprotect = 226
2852 insns = [
2853 "str x8, [sp, -16]!",
2854 "str x0, [sp, -16]!",
2855 "str x1, [sp, -16]!",
2856 "str x2, [sp, -16]!",
2857 f"mov x8, {_NR_mprotect:d}",
2858 f"movz x0, {addr & 0xFFFF:#x}",
2859 f"movk x0, {(addr >> 16) & 0xFFFF:#x}, lsl 16",
2860 f"movk x0, {(addr >> 32) & 0xFFFF:#x}, lsl 32",
2861 f"movk x0, {(addr >> 48) & 0xFFFF:#x}, lsl 48",
2862 f"movz x1, {size & 0xFFFF:#x}",
2863 f"movk x1, {(size >> 16) & 0xFFFF:#x}, lsl 16",
2864 f"mov x2, {perm.value:d}",
2865 "svc 0",
2866 "ldr x2, [sp], 16",
2867 "ldr x1, [sp], 16",
2868 "ldr x0, [sp], 16",
2869 "ldr x8, [sp], 16",
2870 ]
2871 return "; ".join(insns)
2873 def is_conditional_branch(self, insn: Instruction) -> bool:
2874 # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf
2875 # sect. 5.1.1
2876 mnemo = insn.mnemonic
2877 branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"}
2878 return mnemo.startswith("b.") or mnemo in branch_mnemos
2880 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
2881 mnemo, operands = insn.mnemonic, insn.operands
2882 taken, reason = False, ""
2884 if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}:
2885 reg = f"${operands[0]}"
2886 op = gef.arch.register(reg)
2887 if mnemo == "cbnz":
2888 if op!=0: taken, reason = True, f"{reg}!=0"
2889 else: taken, reason = False, f"{reg}==0"
2890 elif mnemo == "cbz":
2891 if op == 0: taken, reason = True, f"{reg}==0"
2892 else: taken, reason = False, f"{reg}!=0"
2893 elif mnemo == "tbnz":
2894 # operands[1] has one or more white spaces in front, then a #, then the number
2895 # so we need to eliminate them
2896 i = int(operands[1].strip().lstrip("#"))
2897 if (op & 1<<i) != 0: taken, reason = True, f"{reg}&1<<{i}!=0"
2898 else: taken, reason = False, f"{reg}&1<<{i}==0"
2899 elif mnemo == "tbz":
2900 # operands[1] has one or more white spaces in front, then a #, then the number
2901 # so we need to eliminate them
2902 i = int(operands[1].strip().lstrip("#"))
2903 if (op & 1<<i) == 0: taken, reason = True, f"{reg}&1<<{i}==0"
2904 else: taken, reason = False, f"{reg}&1<<{i}!=0"
2906 if not reason:
2907 taken, reason = super().is_branch_taken(insn)
2908 return taken, reason
2911class X86(Architecture):
2912 aliases = ("X86", Elf.Abi.X86_32)
2913 arch = "X86"
2914 mode = "32"
2916 nop_insn = b"\x90"
2917 flag_register: str = "$eflags"
2918 special_registers = ("$cs", "$ss", "$ds", "$es", "$fs", "$gs", )
2919 gpr_registers = ("$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", )
2920 all_registers = gpr_registers + ( flag_register,) + special_registers
2921 instruction_length = None
2922 return_register = "$eax"
2923 function_parameters = ("$esp", )
2924 flags_table = {
2925 6: "zero",
2926 0: "carry",
2927 2: "parity",
2928 4: "adjust",
2929 7: "sign",
2930 8: "trap",
2931 9: "interrupt",
2932 10: "direction",
2933 11: "overflow",
2934 16: "resume",
2935 17: "virtualx86",
2936 21: "identification",
2937 }
2938 syscall_register = "$eax"
2939 syscall_instructions = ("sysenter", "int 0x80")
2940 _ptrsize = 4
2941 _endianness = Endianness.LITTLE_ENDIAN
2943 def flag_register_to_human(self, val: int | None = None) -> str:
2944 reg = self.flag_register
2945 if val is None:
2946 val = gef.arch.register(reg)
2947 return flags_to_human(val, self.flags_table)
2949 def is_call(self, insn: Instruction) -> bool:
2950 mnemo = insn.mnemonic
2951 call_mnemos = {"call", "callq"}
2952 return mnemo in call_mnemos
2954 def is_ret(self, insn: Instruction) -> bool:
2955 return insn.mnemonic == "ret"
2957 def is_conditional_branch(self, insn: Instruction) -> bool:
2958 mnemo = insn.mnemonic
2959 branch_mnemos = {
2960 "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna",
2961 "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl",
2962 "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns",
2963 "jo", "jp", "jpe", "js"
2964 }
2965 return mnemo in branch_mnemos
2967 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
2968 mnemo = insn.mnemonic
2969 # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654)
2970 flags = dict((self.flags_table[k], k) for k in self.flags_table)
2971 val = gef.arch.register(self.flag_register)
2973 taken, reason = False, ""
2975 if mnemo in ("ja", "jnbe"):
2976 taken, reason = not val&(1<<flags["carry"]) and not bool(val&(1<<flags["zero"])), "!C && !Z"
2977 elif mnemo in ("jae", "jnb", "jnc"):
2978 taken, reason = not val&(1<<flags["carry"]), "!C"
2979 elif mnemo in ("jb", "jc", "jnae"):
2980 taken, reason = bool(val&(1<<flags["carry"])) != 0, "C"
2981 elif mnemo in ("jbe", "jna"):
2982 taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z"
2983 elif mnemo in ("jcxz", "jecxz", "jrcxz"):
2984 cx = gef.arch.register("$rcx") if is_x86_64() else gef.arch.register("$ecx")
2985 taken, reason = cx == 0, "!$CX"
2986 elif mnemo in ("je", "jz"):
2987 taken, reason = bool(val&(1<<flags["zero"])), "Z"
2988 elif mnemo in ("jne", "jnz"):
2989 taken, reason = not bool(val&(1<<flags["zero"])), "!Z"
2990 elif mnemo in ("jg", "jnle"):
2991 taken, reason = not bool(val&(1<<flags["zero"])) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O"
2992 elif mnemo in ("jge", "jnl"):
2993 taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O"
2994 elif mnemo in ("jl", "jnge"):
2995 taken, reason = bool(val&(1<<flags["overflow"]) != val&(1<<flags["sign"])), "S!=O"
2996 elif mnemo in ("jle", "jng"):
2997 taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O"
2998 elif mnemo in ("jo",):
2999 taken, reason = bool(val&(1<<flags["overflow"])), "O"
3000 elif mnemo in ("jno",):
3001 taken, reason = not val&(1<<flags["overflow"]), "!O"
3002 elif mnemo in ("jpe", "jp"):
3003 taken, reason = bool(val&(1<<flags["parity"])), "P"
3004 elif mnemo in ("jnp", "jpo"):
3005 taken, reason = not val&(1<<flags["parity"]), "!P"
3006 elif mnemo in ("js",):
3007 taken, reason = bool(val&(1<<flags["sign"])) != 0, "S"
3008 elif mnemo in ("jns",):
3009 taken, reason = not val&(1<<flags["sign"]), "!S"
3010 return taken, reason
3012 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
3013 ra = None
3014 if self.is_ret(insn): 3014 ↛ 3017line 3014 didn't jump to line 3017 because the condition on line 3014 was always true
3015 ra = dereference(gef.arch.sp)
3016 else:
3017 older = frame.older()
3018 if older:
3019 ra = older.pc()
3020 if ra is None: 3020 ↛ 3021line 3020 didn't jump to line 3021 because the condition on line 3020 was never true
3021 return None
3022 return to_unsigned_long(ra)
3024 @classmethod
3025 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3026 _NR_mprotect = 125
3027 insns = [
3028 "pushad",
3029 "pushfd",
3030 f"mov eax, {_NR_mprotect:d}",
3031 f"mov ebx, {addr:d}",
3032 f"mov ecx, {size:d}",
3033 f"mov edx, {perm.value:d}",
3034 "int 0x80",
3035 "popfd",
3036 "popad",
3037 ]
3038 return "; ".join(insns)
3040 def get_ith_parameter(self, i: int, in_func: bool = True) -> tuple[str, int | None]:
3041 if in_func:
3042 i += 1 # Account for RA being at the top of the stack
3043 sp = gef.arch.sp
3044 sz = gef.arch.ptrsize
3045 loc = sp + (i * sz)
3046 val = gef.memory.read_integer(loc)
3047 key = f"[sp + {i * sz:#x}]"
3048 return key, val
3051class X86_64(X86):
3052 aliases = ("X86_64", Elf.Abi.X86_64, "i386:x86-64")
3053 arch = "X86"
3054 mode = "64"
3056 gpr_registers = (
3057 "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip",
3058 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", )
3059 all_registers = gpr_registers + ( X86.flag_register, ) + X86.special_registers
3060 return_register = "$rax"
3061 function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"]
3062 syscall_register = "$rax"
3063 syscall_instructions = ["syscall"]
3064 # We don't want to inherit x86's stack based param getter
3065 get_ith_parameter = Architecture.get_ith_parameter
3066 _ptrsize = 8
3068 @classmethod
3069 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3070 _NR_mprotect = 10
3071 insns = [
3072 "pushfq",
3073 "push rax",
3074 "push rdi",
3075 "push rsi",
3076 "push rdx",
3077 "push rcx",
3078 "push r11",
3079 f"mov rax, {_NR_mprotect:d}",
3080 f"mov rdi, {addr:d}",
3081 f"mov rsi, {size:d}",
3082 f"mov rdx, {perm.value:d}",
3083 "syscall",
3084 "pop r11",
3085 "pop rcx",
3086 "pop rdx",
3087 "pop rsi",
3088 "pop rdi",
3089 "pop rax",
3090 "popfq",
3091 ]
3092 return "; ".join(insns)
3094 def canary_address(self) -> int:
3095 return self.register("fs_base") + 0x28
3097class PowerPC(Architecture):
3098 aliases = ("PowerPC", Elf.Abi.POWERPC, "PPC")
3099 arch = "PPC"
3100 mode = "PPC32"
3102 all_registers = (
3103 "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
3104 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15",
3105 "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23",
3106 "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31",
3107 "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",)
3108 instruction_length = 4
3109 nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/
3110 return_register = "$r0"
3111 flag_register: str = "$cr"
3112 flags_table = {
3113 3: "negative[0]",
3114 2: "positive[0]",
3115 1: "equal[0]",
3116 0: "overflow[0]",
3117 # cr7
3118 31: "less[7]",
3119 30: "greater[7]",
3120 29: "equal[7]",
3121 28: "overflow[7]",
3122 }
3123 function_parameters = ("$i0", "$i1", "$i2", "$i3", "$i4", "$i5")
3124 syscall_register = "$r0"
3125 syscall_instructions = ("sc",)
3126 _ptrsize = 4
3129 def flag_register_to_human(self, val: int | None = None) -> str:
3130 # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3)
3131 if val is None:
3132 reg = self.flag_register
3133 val = gef.arch.register(reg)
3134 return flags_to_human(val, self.flags_table)
3136 def is_call(self, insn: Instruction) -> bool:
3137 return False
3139 def is_ret(self, insn: Instruction) -> bool:
3140 return insn.mnemonic == "blr"
3142 def is_conditional_branch(self, insn: Instruction) -> bool:
3143 mnemo = insn.mnemonic
3144 branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"}
3145 return mnemo in branch_mnemos
3147 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
3148 mnemo = insn.mnemonic
3149 flags = dict((self.flags_table[k], k) for k in self.flags_table)
3150 val = gef.arch.register(self.flag_register)
3151 taken, reason = False, ""
3152 if mnemo == "beq": taken, reason = bool(val&(1<<flags["equal[7]"])), "E"
3153 elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E"
3154 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["less[7]"])), "E || L"
3155 elif mnemo == "blt": taken, reason = bool(val&(1<<flags["less[7]"])), "L"
3156 elif mnemo == "bge": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["greater[7]"])), "E || G"
3157 elif mnemo == "bgt": taken, reason = bool(val&(1<<flags["greater[7]"])), "G"
3158 return taken, reason
3160 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
3161 ra = None
3162 if self.is_ret(insn):
3163 ra = gef.arch.register("$lr")
3164 else:
3165 older = frame.older()
3166 if older:
3167 ra = to_unsigned_long(older.pc())
3168 return ra
3170 @classmethod
3171 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3172 # Ref: https://developer.ibm.com/articles/l-ppc/
3173 _NR_mprotect = 125
3174 insns = [
3175 "addi 1, 1, -16", # 1 = r1 = sp
3176 "stw 0, 0(1)",
3177 "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args
3178 "stw 4, 8(1)",
3179 "stw 5, 12(1)",
3180 f"li 0, {_NR_mprotect:d}",
3181 f"lis 3, {addr:#x}@h",
3182 f"ori 3, 3, {addr:#x}@l",
3183 f"lis 4, {size:#x}@h",
3184 f"ori 4, 4, {size:#x}@l",
3185 f"li 5, {perm.value:d}",
3186 "sc",
3187 "lwz 0, 0(1)",
3188 "lwz 3, 4(1)",
3189 "lwz 4, 8(1)",
3190 "lwz 5, 12(1)",
3191 "addi 1, 1, 16",
3192 ]
3193 return ";".join(insns)
3196class PowerPC64(PowerPC):
3197 aliases = ("PowerPC64", Elf.Abi.POWERPC64, "PPC64")
3198 arch = "PPC"
3199 mode = "PPC64"
3200 _ptrsize = 8
3203class SPARC(Architecture):
3204 """ Refs:
3205 - https://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf
3206 """
3207 aliases = ("SPARC", Elf.Abi.SPARC)
3208 arch = "SPARC"
3209 mode = ""
3211 all_registers = (
3212 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7",
3213 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7",
3214 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7",
3215 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7",
3216 "$pc", "$npc", "$sp ", "$fp ", "$psr",)
3217 instruction_length = 4
3218 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0
3219 return_register = "$i0"
3220 flag_register: str = "$psr"
3221 flags_table = {
3222 23: "negative",
3223 22: "zero",
3224 21: "overflow",
3225 20: "carry",
3226 7: "supervisor",
3227 5: "trap",
3228 }
3229 function_parameters = ("$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",)
3230 syscall_register = "%g1"
3231 syscall_instructions = ("t 0x10",)
3233 def flag_register_to_human(self, val: int | None = None) -> str:
3234 # https://www.gaisler.com/doc/sparcv8.pdf
3235 reg = self.flag_register
3236 if val is None:
3237 val = gef.arch.register(reg)
3238 return flags_to_human(val, self.flags_table)
3240 def is_call(self, insn: Instruction) -> bool:
3241 return False
3243 def is_ret(self, insn: Instruction) -> bool:
3244 return insn.mnemonic == "ret"
3246 def is_conditional_branch(self, insn: Instruction) -> bool:
3247 mnemo = insn.mnemonic
3248 # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html
3249 branch_mnemos = {
3250 "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu",
3251 "bneg", "bpos", "bvs", "bvc", "bcs", "bcc"
3252 }
3253 return mnemo in branch_mnemos
3255 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
3256 mnemo = insn.mnemonic
3257 flags = dict((self.flags_table[k], k) for k in self.flags_table)
3258 val = gef.arch.register(self.flag_register)
3259 taken, reason = False, ""
3261 if mnemo == "be": taken, reason = bool(val&(1<<flags["zero"])), "Z"
3262 elif mnemo == "bne": taken, reason = bool(val&(1<<flags["zero"])) == 0, "!Z"
3263 elif mnemo == "bg": taken, reason = bool(val&(1<<flags["zero"])) == 0 and (val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0), "!Z && (!N || !O)"
3264 elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O"
3265 elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z"
3266 elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
3267 elif mnemo == "bl": taken, reason = bool(val&(1<<flags["negative"])) and bool(val&(1<<flags["overflow"])), "N && O"
3268 elif mnemo == "blu": taken, reason = bool(val&(1<<flags["carry"])), "C"
3269 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)"
3270 elif mnemo == "bleu": taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z"
3271 elif mnemo == "bneg": taken, reason = bool(val&(1<<flags["negative"])), "N"
3272 elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N"
3273 elif mnemo == "bvs": taken, reason = bool(val&(1<<flags["overflow"])), "O"
3274 elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O"
3275 elif mnemo == "bcs": taken, reason = bool(val&(1<<flags["carry"])), "C"
3276 elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
3277 return taken, reason
3279 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
3280 ra = None
3281 if self.is_ret(insn):
3282 ra = gef.arch.register("$o7")
3283 else:
3284 older = frame.older()
3285 if older:
3286 ra = to_unsigned_long(older.pc())
3287 return ra
3289 @classmethod
3290 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3291 hi = (addr & 0xffff0000) >> 16
3292 lo = (addr & 0x0000ffff)
3293 _NR_mprotect = 125
3294 insns = ["add %sp, -16, %sp",
3295 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]",
3296 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]",
3297 f"sethi %hi({hi}), %o0",
3298 f"or %o0, {lo}, %o0",
3299 "clr %o1",
3300 "clr %o2",
3301 f"mov {_NR_mprotect}, %g1",
3302 "t 0x10",
3303 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
3304 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
3305 "add %sp, 16, %sp",]
3306 return "; ".join(insns)
3309class SPARC64(SPARC):
3310 """Refs:
3311 - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf
3312 - https://cr.yp.to/2005-590/sparcv9.pdf
3313 """
3314 aliases = ("SPARC64", Elf.Abi.SPARC64)
3315 arch = "SPARC"
3316 mode = "V9"
3318 all_registers = [
3319 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7",
3320 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7",
3321 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7",
3322 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7",
3323 "$pc", "$npc", "$sp", "$fp", "$state", ]
3325 flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr)
3326 flags_table = {
3327 35: "negative",
3328 34: "zero",
3329 33: "overflow",
3330 32: "carry",
3331 }
3333 syscall_instructions = ["t 0x6d"]
3335 @classmethod
3336 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3337 hi = (addr & 0xffff0000) >> 16
3338 lo = (addr & 0x0000ffff)
3339 _NR_mprotect = 125
3340 insns = ["add %sp, -16, %sp",
3341 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]",
3342 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]",
3343 f"sethi %hi({hi}), %o0",
3344 f"or %o0, {lo}, %o0",
3345 "clr %o1",
3346 "clr %o2",
3347 f"mov {_NR_mprotect}, %g1",
3348 "t 0x6d",
3349 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
3350 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
3351 "add %sp, 16, %sp",]
3352 return "; ".join(insns)
3355class MIPS(Architecture):
3356 aliases = ("MIPS", Elf.Abi.MIPS)
3357 arch = "MIPS"
3358 mode = "MIPS32"
3360 # https://vhouten.home.xs4all.nl/mipsel/r3000-isa.html
3361 all_registers = (
3362 "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
3363 "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
3364 "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
3365 "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi",
3366 "$lo", "$fir", "$ra", "$gp", )
3367 instruction_length = 4
3368 _ptrsize = 4
3369 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0
3370 return_register = "$v0"
3371 flag_register = "$fcsr"
3372 flags_table = {}
3373 function_parameters = ("$a0", "$a1", "$a2", "$a3")
3374 syscall_register = "$v0"
3375 syscall_instructions = ("syscall",)
3377 def flag_register_to_human(self, val: int | None = None) -> str:
3378 return Color.colorify("No flag register", "yellow underline")
3380 def is_call(self, insn: Instruction) -> bool:
3381 return False
3383 def is_ret(self, insn: Instruction) -> bool:
3384 return insn.mnemonic == "jr" and insn.operands[0] == "ra"
3386 def is_conditional_branch(self, insn: Instruction) -> bool:
3387 mnemo = insn.mnemonic
3388 branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"}
3389 return mnemo in branch_mnemos
3391 def is_branch_taken(self, insn: Instruction) -> tuple[bool, str]:
3392 mnemo, ops = insn.mnemonic, insn.operands
3393 taken, reason = False, ""
3395 if mnemo == "beq":
3396 taken, reason = gef.arch.register(ops[0]) == gef.arch.register(ops[1]), f"{ops[0]} == {ops[1]}"
3397 elif mnemo == "bne":
3398 taken, reason = gef.arch.register(ops[0]) != gef.arch.register(ops[1]), f"{ops[0]} != {ops[1]}"
3399 elif mnemo == "beqz":
3400 taken, reason = gef.arch.register(ops[0]) == 0, f"{ops[0]} == 0"
3401 elif mnemo == "bnez":
3402 taken, reason = gef.arch.register(ops[0]) != 0, f"{ops[0]} != 0"
3403 elif mnemo == "bgtz":
3404 taken, reason = gef.arch.register(ops[0]) > 0, f"{ops[0]} > 0"
3405 elif mnemo == "bgez":
3406 taken, reason = gef.arch.register(ops[0]) >= 0, f"{ops[0]} >= 0"
3407 elif mnemo == "bltz":
3408 taken, reason = gef.arch.register(ops[0]) < 0, f"{ops[0]} < 0"
3409 elif mnemo == "blez":
3410 taken, reason = gef.arch.register(ops[0]) <= 0, f"{ops[0]} <= 0"
3411 return taken, reason
3413 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int | None:
3414 ra = None
3415 if self.is_ret(insn):
3416 ra = gef.arch.register("$ra")
3417 else:
3418 older = frame.older()
3419 if older:
3420 ra = to_unsigned_long(older.pc())
3421 return ra
3423 @classmethod
3424 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3425 _NR_mprotect = 4125
3426 insns = ["addi $sp, $sp, -16",
3427 "sw $v0, 0($sp)", "sw $a0, 4($sp)",
3428 "sw $a3, 8($sp)", "sw $a3, 12($sp)",
3429 f"li $v0, {_NR_mprotect:d}",
3430 f"li $a0, {addr:d}",
3431 f"li $a1, {size:d}",
3432 f"li $a2, {perm.value:d}",
3433 "syscall",
3434 "lw $v0, 0($sp)", "lw $a1, 4($sp)",
3435 "lw $a3, 8($sp)", "lw $a3, 12($sp)",
3436 "addi $sp, $sp, 16",]
3437 return "; ".join(insns)
3440class MIPS64(MIPS):
3441 aliases = ("MIPS64",)
3442 arch = "MIPS"
3443 mode = "MIPS64"
3444 _ptrsize = 8
3446 @staticmethod
3447 def supports_gdb_arch(gdb_arch: str) -> bool | None:
3448 if not gef.binary or not isinstance(gef.binary, Elf): 3448 ↛ 3449line 3448 didn't jump to line 3449 because the condition on line 3448 was never true
3449 return False
3450 return gdb_arch.startswith("mips") and gef.binary.e_class == Elf.Class.ELF_64_BITS
3453def copy_to_clipboard(data: bytes) -> None:
3454 """Helper function to submit data to the clipboard"""
3455 if sys.platform == "linux":
3456 xclip = which("xclip")
3457 prog = [xclip, "-selection", "clipboard", "-i"]
3458 elif sys.platform == "darwin":
3459 pbcopy = which("pbcopy")
3460 prog = [pbcopy]
3461 else:
3462 raise NotImplementedError("copy: Unsupported OS")
3464 with subprocess.Popen(prog, stdin=subprocess.PIPE) as p:
3465 assert p.stdin
3466 p.stdin.write(data)
3467 p.stdin.close()
3468 p.wait()
3469 return
3472def use_stdtype() -> str:
3473 if is_32bit(): return "uint32_t" 3473 ↛ exitline 3473 didn't return from function 'use_stdtype' because the return on line 3473 wasn't executed
3474 elif is_64bit(): return "uint64_t" 3474 ↛ 3475line 3474 didn't jump to line 3475 because the condition on line 3474 was always true
3475 return "uint16_t"
3478def use_default_type() -> str:
3479 if is_32bit(): return "unsigned int" 3479 ↛ exitline 3479 didn't return from function 'use_default_type' because the return on line 3479 wasn't executed
3480 elif is_64bit(): return "unsigned long" 3480 ↛ 3481line 3480 didn't jump to line 3481 because the condition on line 3480 was always true
3481 return "unsigned short"
3484def use_golang_type() -> str:
3485 if is_32bit(): return "uint32"
3486 elif is_64bit(): return "uint64"
3487 return "uint16"
3490def use_rust_type() -> str:
3491 if is_32bit(): return "u32"
3492 elif is_64bit(): return "u64"
3493 return "u16"
3496def to_unsigned_long(v: gdb.Value) -> int:
3497 """Cast a gdb.Value to unsigned long."""
3498 mask = (1 << (gef.arch.ptrsize*8)) - 1
3499 return int(v.cast(gdb.Value(mask).type)) & mask
3502def get_path_from_info_proc() -> str | None:
3503 for x in (gdb.execute("info proc", to_string=True) or "").splitlines():
3504 if x.startswith("exe = "):
3505 return x.split(" = ")[1].replace("'", "")
3506 return None
3509@deprecated("Use `gef.session.os`")
3510def get_os() -> str:
3511 return gef.session.os
3514@lru_cache()
3515def is_qemu() -> bool:
3516 if not is_remote_debug():
3517 return False
3518 response = gdb.execute("maintenance packet Qqemu.sstepbits", to_string=True, from_tty=False) or ""
3519 return "ENABLE=" in response
3522@lru_cache()
3523def is_qemu_usermode() -> bool:
3524 if not is_qemu():
3525 return False
3526 response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or ""
3527 return "Text=" in response
3530@lru_cache()
3531def is_qemu_system() -> bool:
3532 if not is_qemu():
3533 return False
3534 response = gdb.execute("maintenance packet qOffsets", to_string=True, from_tty=False) or ""
3535 return "received: \"\"" in response
3538def is_target_coredump() -> bool:
3539 global gef
3540 if gef.session.coredump_mode is not None:
3541 return gef.session.coredump_mode
3542 lines = (gdb.execute("maintenance info section", to_string=True) or "").splitlines()
3543 is_coredump_mode = any(map(lambda line: line.startswith("Core file: "), lines))
3544 gef.session.coredump_mode = is_coredump_mode
3545 return is_coredump_mode
3548def get_filepath() -> str | None:
3549 """Return the local absolute path of the file currently debugged."""
3550 if gef.session.remote:
3551 return str(gef.session.remote.lfile.absolute())
3552 if gef.session.file: 3552 ↛ 3554line 3552 didn't jump to line 3554 because the condition on line 3552 was always true
3553 return str(gef.session.file.absolute())
3554 return None
3557def get_function_length(sym: str) -> int:
3558 """Attempt to get the length of the raw bytes of a function."""
3559 dis = (gdb.execute(f"disassemble '{sym}'", to_string=True) or "").splitlines()
3560 start_addr = int(dis[1].split()[0], 16)
3561 end_addr = int(dis[-2].split()[0], 16)
3562 return end_addr - start_addr
3565@lru_cache()
3566def get_info_files() -> list[Zone]:
3567 """Retrieve all the files loaded by debuggee."""
3568 lines = (gdb.execute("info files", to_string=True) or "").splitlines()
3569 infos = []
3570 for line in lines:
3571 line = line.strip()
3572 if not line: 3572 ↛ 3573line 3572 didn't jump to line 3573 because the condition on line 3572 was never true
3573 break
3575 if not line.startswith("0x"):
3576 continue
3578 blobs = [x.strip() for x in line.split(" ")]
3579 addr_start = int(blobs[0], 16)
3580 addr_end = int(blobs[2], 16)
3581 section_name = blobs[4]
3583 if len(blobs) == 7:
3584 filename = blobs[6]
3585 else:
3586 filename = get_filepath()
3588 infos.append(Zone(section_name, addr_start, addr_end, filename))
3589 return infos
3592def process_lookup_address(address: int) -> Section | None:
3593 """Look up for an address in memory.
3594 Return an Address object if found, None otherwise."""
3595 if not is_alive(): 3595 ↛ 3596line 3595 didn't jump to line 3596 because the condition on line 3595 was never true
3596 err("Process is not running")
3597 return None
3599 if is_x86(): 3599 ↛ 3603line 3599 didn't jump to line 3603 because the condition on line 3599 was always true
3600 if is_in_x86_kernel(address): 3600 ↛ 3601line 3600 didn't jump to line 3601 because the condition on line 3600 was never true
3601 return None
3603 for sect in gef.memory.maps:
3604 if sect.page_start <= address < sect.page_end:
3605 return sect
3607 return None
3610@lru_cache()
3611def process_lookup_path(name: str, perm: Permission = Permission.ALL) -> Section | None:
3612 """Look up for a path in the process memory mapping.
3613 Return a Section object if found, None otherwise."""
3614 if not is_alive(): 3614 ↛ 3615line 3614 didn't jump to line 3615 because the condition on line 3614 was never true
3615 err("Process is not running")
3616 return None
3618 matches: dict[str, Section] = dict()
3619 for sect in gef.memory.maps:
3620 filename = pathlib.Path(sect.path).name
3622 if name in filename and sect.permission & perm:
3623 if sect.path not in matches.keys():
3624 matches[sect.path] = sect
3626 matches_count = len(matches)
3628 if matches_count == 0:
3629 return None
3631 if matches_count > 1: 3631 ↛ 3632line 3631 didn't jump to line 3632 because the condition on line 3631 was never true
3632 warn(f"{matches_count} matches! You should probably refine your search!")
3634 for x in matches.keys():
3635 warn(f"- '{x}'")
3637 warn("Returning the first match")
3639 return list(matches.values())[0]
3642@lru_cache()
3643def file_lookup_name_path(name: str, path: str) -> Zone | None:
3644 """Look up a file by name and path.
3645 Return a Zone object if found, None otherwise."""
3646 for xfile in get_info_files(): 3646 ↛ 3649line 3646 didn't jump to line 3649 because the loop on line 3646 didn't complete
3647 if path == xfile.filename and name == xfile.name:
3648 return xfile
3649 return None
3652@lru_cache()
3653def file_lookup_address(address: int) -> Zone | None:
3654 """Look up for a file by its address.
3655 Return a Zone object if found, None otherwise."""
3656 for info in get_info_files():
3657 if info.zone_start <= address < info.zone_end:
3658 return info
3659 return None
3662@lru_cache()
3663def lookup_address(address: int) -> Address:
3664 """Try to find the address in the process address space.
3665 Return an Address object, with validity flag set based on success."""
3666 sect = process_lookup_address(address)
3667 info = file_lookup_address(address)
3668 if sect is None and info is None:
3669 # i.e. there is no info on this address
3670 return Address(value=address, valid=False)
3671 return Address(value=address, section=sect, info=info)
3674def xor(data: ByteString, key: str) -> bytearray:
3675 """Return `data` xor-ed with `key`."""
3676 key_raw = binascii.unhexlify(key.lstrip("0x"))
3677 return bytearray(x ^ y for x, y in zip(data, itertools.cycle(key_raw)))
3680def is_hex(pattern: str) -> bool:
3681 """Return whether provided string is a hexadecimal value."""
3682 if not pattern.lower().startswith("0x"):
3683 return False
3684 return len(pattern) % 2 == 0 and all(c in string.hexdigits for c in pattern[2:])
3687def continue_handler(_: "gdb.ContinueEvent") -> None:
3688 """GDB event handler for new object continue cases."""
3689 return
3692def hook_stop_handler(_: "gdb.StopEvent") -> None:
3693 """GDB event handler for stop cases."""
3694 reset_all_caches()
3695 gdb.execute("context")
3696 return
3699def new_objfile_handler(evt: "gdb.NewObjFileEvent | None") -> None:
3700 """GDB event handler for new object file cases."""
3701 reset_all_caches()
3702 progspace = gdb.current_progspace()
3703 if evt:
3704 path = evt.new_objfile.filename or ""
3705 elif progspace: 3705 ↛ 3708line 3705 didn't jump to line 3708 because the condition on line 3705 was always true
3706 path = progspace.filename or ""
3707 else:
3708 raise RuntimeError("Cannot determine file path")
3709 try:
3710 if gef.session.root and path.startswith("target:"):
3711 # If the process is in a container, replace the "target:" prefix
3712 # with the actual root directory of the process.
3713 path = path.replace("target:", str(gef.session.root), 1)
3714 target = pathlib.Path(path)
3715 FileFormatClasses = list(filter(lambda fmtcls: fmtcls.is_valid(target), __registered_file_formats__))
3716 GuessedFileFormatClass : Type[FileFormat] = FileFormatClasses.pop() if len(FileFormatClasses) else Elf
3717 binary = GuessedFileFormatClass(target)
3718 if not gef.binary:
3719 gef.binary = binary
3720 reset_architecture()
3721 else:
3722 gef.session.modules.append(binary)
3723 except FileNotFoundError as fne:
3724 # Linux automatically maps the vDSO into our process, and GDB
3725 # will give us the string 'system-supplied DSO' as a path.
3726 # This is normal, so we shouldn't warn the user about it
3727 if "system-supplied DSO" not in path: 3727 ↛ 3728line 3727 didn't jump to line 3728 because the condition on line 3727 was never true
3728 warn(f"Failed to find objfile or not a valid file format: {str(fne)}")
3729 except RuntimeError as re:
3730 warn(f"Not a valid file format: {str(re)}")
3731 return
3734def exit_handler(_: "gdb.ExitedEvent") -> None:
3735 """GDB event handler for exit cases."""
3736 global gef
3737 # flush the caches
3738 reset_all_caches()
3740 # disconnect properly the remote session
3741 gef.session.qemu_mode = False
3742 if gef.session.remote:
3743 gef.session.remote.close()
3744 del gef.session.remote
3745 gef.session.remote = None
3746 gef.session.remote_initializing = False
3748 # if `autosave_breakpoints_file` setting is configured, save the breakpoints to disk
3749 setting = (gef.config["gef.autosave_breakpoints_file"] or "").strip()
3750 if not setting: 3750 ↛ 3753line 3750 didn't jump to line 3753 because the condition on line 3750 was always true
3751 return
3753 bkp_fpath = pathlib.Path(setting).expanduser().absolute()
3754 if bkp_fpath.exists():
3755 warn(f"{bkp_fpath} exists, content will be overwritten")
3757 with bkp_fpath.open("w") as fd:
3758 for bp in list(gdb.breakpoints()):
3759 if not bp.enabled or not bp.is_valid:
3760 continue
3761 fd.write(f"{'t' if bp.temporary else ''}break {bp.location}\n")
3762 return
3765def memchanged_handler(_: "gdb.MemoryChangedEvent") -> None:
3766 """GDB event handler for mem changes cases."""
3767 reset_all_caches()
3768 return
3771def regchanged_handler(_: "gdb.RegisterChangedEvent") -> None:
3772 """GDB event handler for reg changes cases."""
3773 reset_all_caches()
3774 return
3777def get_terminal_size() -> tuple[int, int]:
3778 """Return the current terminal size."""
3779 if is_debug(): 3779 ↛ 3782line 3779 didn't jump to line 3782 because the condition on line 3779 was always true
3780 return 600, 100
3782 if platform.system() == "Windows":
3783 from ctypes import create_string_buffer, windll # type: ignore
3784 hStdErr = -12
3785 herr = windll.kernel32.GetStdHandle(hStdErr)
3786 csbi = create_string_buffer(22)
3787 res = windll.kernel32.GetConsoleScreenBufferInfo(herr, csbi)
3788 if res:
3789 _, _, _, _, _, left, top, right, bottom, _, _ = struct.unpack("hhhhHhhhhhh", csbi.raw)
3790 tty_columns = right - left + 1
3791 tty_rows = bottom - top + 1
3792 return tty_rows, tty_columns
3793 else:
3794 return 600, 100
3795 else:
3796 import fcntl
3797 import termios
3798 try:
3799 tty_rows, tty_columns = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234")) # type: ignore
3800 return tty_rows, tty_columns
3801 except OSError:
3802 return 600, 100
3805@lru_cache()
3806def is_64bit() -> bool:
3807 """Checks if current target is 64bit."""
3808 return gef.arch.ptrsize == 8
3811@lru_cache()
3812def is_32bit() -> bool:
3813 """Checks if current target is 32bit."""
3814 return gef.arch.ptrsize == 4
3817@lru_cache()
3818def is_x86_64() -> bool:
3819 """Checks if current target is x86-64"""
3820 return Elf.Abi.X86_64 in gef.arch.aliases
3823@lru_cache()
3824def is_x86_32():
3825 """Checks if current target is an x86-32"""
3826 return Elf.Abi.X86_32 in gef.arch.aliases
3829@lru_cache()
3830def is_x86() -> bool:
3831 return is_x86_32() or is_x86_64()
3834@lru_cache()
3835def is_arch(arch: Elf.Abi) -> bool:
3836 return arch in gef.arch.aliases
3839def reset_architecture(arch: str | None = None) -> None:
3840 """Sets the current architecture.
3841 If an architecture is explicitly specified by parameter, try to use that one. If this fails, an `OSError`
3842 exception will occur.
3843 If no architecture is specified, then GEF will attempt to determine automatically based on the current
3844 ELF target. If this fails, an `OSError` exception will occur.
3845 """
3846 global gef
3847 arches = __registered_architectures__
3849 # check if the architecture is forced by parameter
3850 if arch:
3851 try:
3852 gef.arch = arches[arch.lower()]()
3853 gef.arch_reason = "The architecture has been set manually"
3854 except KeyError:
3855 raise OSError(f"Specified arch {arch.upper()} is not supported")
3856 return
3858 # check for bin running
3859 if is_alive():
3860 gdb_arch = gdb.selected_frame().architecture().name()
3861 preciser_arch = next((a for a in arches.values() if a.supports_gdb_arch(gdb_arch)), None)
3862 if preciser_arch: 3862 ↛ 3863line 3862 didn't jump to line 3863 because the condition on line 3862 was never true
3863 gef.arch = preciser_arch()
3864 gef.arch_reason = "The architecture has been detected by GDB"
3865 return
3867 # last resort, use the info from elf header to find it from the known architectures
3868 if gef.binary and isinstance(gef.binary, Elf): 3868 ↛ 3876line 3868 didn't jump to line 3876 because the condition on line 3868 was always true
3869 try:
3870 gef.arch = arches[gef.binary.e_machine]()
3871 gef.arch_reason = "The architecture has been detected via the ELF headers"
3872 except KeyError:
3873 raise OSError(f"CPU type is currently not supported: {gef.binary.e_machine}")
3874 return
3876 warn("Did not find any way to guess the correct architecture :(")
3879@lru_cache()
3880def cached_lookup_type(_type: str) -> gdb.Type | None:
3881 try:
3882 return gdb.lookup_type(_type).strip_typedefs()
3883 except RuntimeError:
3884 return None
3887@deprecated("Use `gef.arch.ptrsize` instead")
3888def get_memory_alignment(in_bits: bool = False) -> int:
3889 """Try to determine the size of a pointer on this system.
3890 First, try to parse it out of the ELF header.
3891 Next, use the size of `size_t`.
3892 Finally, try the size of $pc.
3893 If `in_bits` is set to True, the result is returned in bits, otherwise in
3894 bytes."""
3895 res = cached_lookup_type("size_t")
3896 if res is not None:
3897 return res.sizeof if not in_bits else res.sizeof * 8
3899 try:
3900 return gdb.parse_and_eval("$pc").type.sizeof
3901 except Exception:
3902 pass
3904 raise OSError("GEF is running under an unsupported mode")
3907def clear_screen(tty: str = "") -> None:
3908 """Clear the screen."""
3909 clean_sequence = "\x1b[H\x1b[J"
3910 if tty: 3910 ↛ 3911line 3910 didn't jump to line 3911 because the condition on line 3910 was never true
3911 pathlib.Path(tty).write_text(clean_sequence)
3912 else:
3913 sys.stdout.write(clean_sequence)
3915 return
3918def format_address(addr: int) -> str:
3919 """Format the address according to its size."""
3920 memalign_size = gef.arch.ptrsize
3921 addr = align_address(addr)
3922 return f"0x{addr:016x}" if memalign_size == 8 else f"0x{addr:08x}"
3925def format_address_spaces(addr: int, left: bool = True) -> str:
3926 """Format the address according to its size, but with spaces instead of zeroes."""
3927 width = gef.arch.ptrsize * 2 + 2
3928 addr = align_address(addr)
3930 if not left: 3930 ↛ 3931line 3930 didn't jump to line 3931 because the condition on line 3930 was never true
3931 return f"{addr:#x}".rjust(width)
3933 return f"{addr:#x}".ljust(width)
3936def align_address(address: int) -> int:
3937 """Align the provided address to the process's native length."""
3938 return address & 0xFFFFFFFFFFFFFFFF if gef.arch.ptrsize == 8 else address & 0xFFFFFFFF
3941def align_address_to_size(address: int, align: int) -> int:
3942 """Align the address to the given size."""
3943 return address + ((align - (address % align)) % align)
3946def align_address_to_page(address: int) -> int:
3947 """Align the address to a page."""
3948 a = align_address(address) >> DEFAULT_PAGE_ALIGN_SHIFT
3949 return a << DEFAULT_PAGE_ALIGN_SHIFT
3952def parse_address(address: str) -> int:
3953 """Parse an address and return it as an Integer."""
3954 if is_hex(address):
3955 return int(address, 16)
3956 return int(gdb.parse_and_eval(address))
3959def is_in_x86_kernel(address: int) -> bool:
3960 address = align_address(address)
3961 memalign = gef.arch.ptrsize*8 - 1
3962 return (address >> memalign) == 0xF
3965def is_remote_debug() -> bool:
3966 """"Return True is the current debugging session is running through GDB remote session."""
3967 return gef.session.remote_initializing or gef.session.remote is not None
3970def de_bruijn(alphabet: bytes, n: int) -> Generator[int, None, None]:
3971 """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib)."""
3972 k = len(alphabet)
3973 a = [0] * k * n
3975 def db(t: int, p: int) -> Generator[int, None, None]:
3976 if t > n:
3977 if n % p == 0:
3978 for j in range(1, p + 1):
3979 yield alphabet[a[j]]
3980 else:
3981 a[t] = a[t - p]
3982 yield from db(t + 1, p)
3984 for j in range(a[t - p] + 1, k):
3985 a[t] = j
3986 yield from db(t + 1, t)
3988 return db(1, 1)
3991def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray:
3992 """Create a `length` byte bytearray of a de Bruijn cyclic pattern."""
3993 charset = bytearray(b"abcdefghijklmnopqrstuvwxyz")
3994 return bytearray(itertools.islice(de_bruijn(charset, cycle), length))
3997def safe_parse_and_eval(value: str) -> "gdb.Value | None":
3998 """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising
3999 gdb.error if the eval failed."""
4000 try:
4001 return gdb.parse_and_eval(value)
4002 except gdb.error as e:
4003 dbg(f"gdb.parse_and_eval() failed, reason: {str(e)}")
4004 return None
4007@lru_cache()
4008def dereference(addr: int) -> "gdb.Value | None":
4009 """GEF wrapper for gdb dereference function."""
4010 try:
4011 ulong_t = cached_lookup_type(use_stdtype()) or \
4012 cached_lookup_type(use_default_type()) or \
4013 cached_lookup_type(use_golang_type()) or \
4014 cached_lookup_type(use_rust_type())
4015 if not ulong_t: 4015 ↛ 4016line 4015 didn't jump to line 4016 because the condition on line 4015 was never true
4016 raise gdb.MemoryError("Failed to determine unsigned long type")
4017 unsigned_long_type = ulong_t.pointer()
4018 res = gdb.Value(addr).cast(unsigned_long_type).dereference()
4019 # GDB does lazy fetch by default so we need to force access to the value
4020 res.fetch_lazy()
4021 return res
4022 except gdb.MemoryError as e:
4023 dbg(str(e))
4024 return None
4027def gef_convenience(value: str | bytes) -> str:
4028 """Defines a new convenience value."""
4029 global gef
4030 var_name = f"$_gef{gef.session.convenience_vars_index:d}"
4031 gef.session.convenience_vars_index += 1
4032 if isinstance(value, str):
4033 gdb.execute(f"""set {var_name} = "{value}" """)
4034 elif isinstance(value, bytes): 4034 ↛ 4038line 4034 didn't jump to line 4038 because the condition on line 4034 was always true
4035 value_as_array = "{" + ", ".join([f"0x{b:02x}" for b in value]) + "}"
4036 gdb.execute(f"""set {var_name} = {value_as_array} """)
4037 else:
4038 raise TypeError
4039 return var_name
4042def parse_string_range(s: str) -> Iterator[int]:
4043 """Parses an address range (e.g. 0x400000-0x401000)"""
4044 addrs = s.split("-")
4045 return map(lambda x: int(x, 16), addrs)
4048@lru_cache()
4049def is_syscall(instruction: Instruction | int) -> bool:
4050 """Checks whether an instruction or address points to a system call."""
4051 if isinstance(instruction, int):
4052 instruction = gef_current_instruction(instruction)
4053 insn_str = instruction.mnemonic
4054 if len(instruction.operands):
4055 insn_str += f" {', '.join(instruction.operands)}"
4056 return insn_str in gef.arch.syscall_instructions
4059#
4060# Deprecated API
4061#
4063@deprecated("Use `gef.session.pie_breakpoints[num]`")
4064def gef_get_pie_breakpoint(num: int) -> "PieVirtualBreakpoint":
4065 return gef.session.pie_breakpoints[num]
4068@deprecated("Use `str(gef.arch.endianness)` instead")
4069def endian_str() -> str:
4070 return str(gef.arch.endianness)
4073@deprecated("Use `gef.config[key]`")
4074def get_gef_setting(name: str) -> Any:
4075 return gef.config[name]
4078@deprecated("Use `gef.config[key] = value`")
4079def set_gef_setting(name: str, value: Any) -> None:
4080 gef.config[name] = value
4081 return
4084@deprecated("Use `gef.session.pagesize`")
4085def gef_getpagesize() -> int:
4086 return gef.session.pagesize
4089@deprecated("Use `gef.session.canary`")
4090def gef_read_canary() -> tuple[int, int] | None:
4091 return gef.session.canary
4094@deprecated("Use `gef.session.pid`")
4095def get_pid() -> int:
4096 return gef.session.pid
4099@deprecated("Use `gef.session.file.name`")
4100def get_filename() -> str:
4101 assert gef.session.file
4102 return gef.session.file.name
4105@deprecated("Use `gef.heap.main_arena`")
4106def get_glibc_arena() -> GlibcArena | None:
4107 return gef.heap.main_arena
4110@deprecated("Use `gef.arch.register(regname)`")
4111def get_register(regname) -> int | None:
4112 return gef.arch.register(regname)
4115@deprecated("Use `gef.memory.maps`")
4116def get_process_maps() -> list[Section]:
4117 return gef.memory.maps
4120@deprecated("Use `reset_architecture`")
4121def set_arch(arch: str | None = None, _: str | None = None) -> None:
4122 return reset_architecture(arch)
4124#
4125# GDB event hooking
4126#
4128@only_if_events_supported("cont")
4129def gef_on_continue_hook(func: Callable[["gdb.ContinueEvent"], None]) -> None:
4130 gdb.events.cont.connect(func)
4133@only_if_events_supported("cont")
4134def gef_on_continue_unhook(func: Callable[["gdb.ThreadEvent"], None]) -> None:
4135 gdb.events.cont.disconnect(func)
4138@only_if_events_supported("stop")
4139def gef_on_stop_hook(func: Callable[["gdb.StopEvent"], None]) -> None:
4140 gdb.events.stop.connect(func)
4143@only_if_events_supported("stop")
4144def gef_on_stop_unhook(func: Callable[["gdb.StopEvent"], None]) -> None:
4145 gdb.events.stop.disconnect(func)
4148@only_if_events_supported("exited")
4149def gef_on_exit_hook(func: Callable[["gdb.ExitedEvent"], None]) -> None:
4150 gdb.events.exited.connect(func)
4153@only_if_events_supported("exited")
4154def gef_on_exit_unhook(func: Callable[["gdb.ExitedEvent"], None]) -> None:
4155 gdb.events.exited.disconnect(func)
4158@only_if_events_supported("new_objfile")
4159def gef_on_new_hook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None:
4160 gdb.events.new_objfile.connect(func)
4163@only_if_events_supported("new_objfile")
4164def gef_on_new_unhook(func: Callable[["gdb.NewObjFileEvent"], None]) -> None:
4165 gdb.events.new_objfile.disconnect(func)
4168@only_if_events_supported("clear_objfiles")
4169def gef_on_unload_objfile_hook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None:
4170 gdb.events.clear_objfiles.connect(func)
4173@only_if_events_supported("clear_objfiles")
4174def gef_on_unload_objfile_unhook(func: Callable[["gdb.ClearObjFilesEvent"], None]) -> None:
4175 gdb.events.clear_objfiles.disconnect(func)
4178@only_if_events_supported("memory_changed")
4179def gef_on_memchanged_hook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None:
4180 gdb.events.memory_changed.connect(func)
4183@only_if_events_supported("memory_changed")
4184def gef_on_memchanged_unhook(func: Callable[["gdb.MemoryChangedEvent"], None]) -> None:
4185 gdb.events.memory_changed.disconnect(func)
4188@only_if_events_supported("register_changed")
4189def gef_on_regchanged_hook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None:
4190 gdb.events.register_changed.connect(func)
4193@only_if_events_supported("register_changed")
4194def gef_on_regchanged_unhook(func: Callable[["gdb.RegisterChangedEvent"], None]) -> None:
4195 gdb.events.register_changed.disconnect(func)
4198#
4199# Virtual breakpoints
4200#
4202class PieVirtualBreakpoint:
4203 """PIE virtual breakpoint (not real breakpoint)."""
4205 def __init__(self, set_func: Callable[[int], str], vbp_num: int, addr: int) -> None:
4206 # set_func(base): given a base address return a
4207 # "set breakpoint" gdb command string
4208 self.set_func = set_func
4209 self.vbp_num = vbp_num
4210 # breakpoint num, 0 represents not instantiated yet
4211 self.bp_num = 0
4212 self.bp_addr = 0
4213 # this address might be a symbol, just to know where to break
4214 if isinstance(addr, int): 4214 ↛ 4217line 4214 didn't jump to line 4217 because the condition on line 4214 was always true
4215 self.addr: int | str = hex(addr)
4216 else:
4217 self.addr = addr
4218 return
4220 def instantiate(self, base: int) -> None:
4221 if self.bp_num: 4221 ↛ 4222line 4221 didn't jump to line 4222 because the condition on line 4221 was never true
4222 self.destroy()
4224 try:
4225 res = gdb.execute(self.set_func(base), to_string=True) or ""
4226 if not res: return 4226 ↛ exitline 4226 didn't return from function 'instantiate' because the return on line 4226 wasn't executed
4227 except gdb.error as e:
4228 err(str(e))
4229 return
4231 if "Breakpoint" not in res: 4231 ↛ 4232line 4231 didn't jump to line 4232 because the condition on line 4231 was never true
4232 err(res)
4233 return
4235 res_list = res.split()
4236 self.bp_num = res_list[1]
4237 self.bp_addr = res_list[3]
4238 return
4240 def destroy(self) -> None:
4241 if not self.bp_num:
4242 err("Destroy PIE breakpoint not even set")
4243 return
4244 gdb.execute(f"delete {self.bp_num}")
4245 self.bp_num = 0
4246 return
4249#
4250# Breakpoints
4251#
4253class FormatStringBreakpoint(gdb.Breakpoint):
4254 """Inspect stack for format string."""
4255 def __init__(self, spec: str, num_args: int) -> None:
4256 super().__init__(spec, type=gdb.BP_BREAKPOINT, internal=False)
4257 self.num_args = num_args
4258 self.enabled = True
4259 return
4261 def stop(self) -> bool:
4262 reset_all_caches()
4263 msg = []
4264 ptr, addr = gef.arch.get_ith_parameter(self.num_args)
4265 addr = lookup_address(addr)
4267 if not addr.valid: 4267 ↛ 4268line 4267 didn't jump to line 4268 because the condition on line 4267 was never true
4268 return False
4270 if addr.section.is_writable():
4271 content = gef.memory.read_cstring(addr.value)
4272 name = addr.info.name if addr.info else addr.section.path
4273 msg.append(Color.colorify("Format string helper", "yellow bold"))
4274 msg.append(f"Possible insecure format string: {self.location}('{ptr}' {RIGHT_ARROW} {addr.value:#x}: '{content}')")
4275 msg.append(f"Reason: Call to '{self.location}()' with format string argument in position "
4276 f"#{self.num_args:d} is in page {addr.section.page_start:#x} ({name}) that has write permission")
4277 push_context_message("warn", "\n".join(msg))
4278 return True
4280 return False
4283class StubBreakpoint(gdb.Breakpoint):
4284 """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.)."""
4286 def __init__(self, func: str, retval: int | None) -> None:
4287 super().__init__(func, gdb.BP_BREAKPOINT, internal=False)
4288 self.func = func
4289 self.retval = retval
4291 m = f"All calls to '{self.func}' will be skipped"
4292 if self.retval is not None: 4292 ↛ 4294line 4292 didn't jump to line 4294 because the condition on line 4292 was always true
4293 m += f" (with return value set to {self.retval:#x})"
4294 info(m)
4295 return
4297 def stop(self) -> bool:
4298 size = "long" if gef.arch.ptrsize == 8 else "int"
4299 gdb.execute(f"return (unsigned {size}){self.retval:#x}")
4300 ok(f"Ignoring call to '{self.func}' "
4301 f"(setting return value to {self.retval:#x})")
4302 return False
4305class ChangePermissionBreakpoint(gdb.Breakpoint):
4306 """When hit, this temporary breakpoint will restore the original code, and position
4307 $pc correctly."""
4309 def __init__(self, loc: str, code: ByteString, pc: int) -> None:
4310 super().__init__(loc, gdb.BP_BREAKPOINT, internal=False)
4311 self.original_code = code
4312 self.original_pc = pc
4313 return
4315 def stop(self) -> bool:
4316 info("Restoring original context")
4317 gef.memory.write(self.original_pc, self.original_code, len(self.original_code))
4318 info("Restoring $pc")
4319 gdb.execute(f"set $pc = {self.original_pc:#x}")
4320 return True
4323class TraceMallocBreakpoint(gdb.Breakpoint):
4324 """Track allocations done with malloc() or calloc()."""
4326 def __init__(self, name: str) -> None:
4327 super().__init__(name, gdb.BP_BREAKPOINT, internal=True)
4328 self.silent = True
4329 self.name = name
4330 return
4332 def stop(self) -> bool:
4333 reset_all_caches()
4334 _, size = gef.arch.get_ith_parameter(0)
4335 assert size
4336 self.retbp = TraceMallocRetBreakpoint(size, self.name)
4337 return False
4340class TraceMallocRetBreakpoint(gdb.FinishBreakpoint):
4341 """Internal temporary breakpoint to retrieve the return value of malloc()."""
4343 def __init__(self, size: int, name: str) -> None:
4344 super().__init__(gdb.newest_frame(), internal=True)
4345 self.size = size
4346 self.name = name
4347 self.silent = True
4348 return
4350 def stop(self) -> bool:
4351 if self.return_value: 4351 ↛ 4354line 4351 didn't jump to line 4354 because the condition on line 4351 was always true
4352 loc = int(self.return_value)
4353 else:
4354 loc = parse_address(gef.arch.return_register)
4356 size = self.size
4357 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - {self.name}({size})={loc:#x}")
4358 check_heap_overlap = gef.config["heap-analysis-helper.check_heap_overlap"]
4360 # pop from free-ed list if it was in it
4361 if gef.session.heap_freed_chunks: 4361 ↛ 4362line 4361 didn't jump to line 4362 because the condition on line 4361 was never true
4362 idx = 0
4363 for item in gef.session.heap_freed_chunks:
4364 addr = item[0]
4365 if addr == loc:
4366 gef.session.heap_freed_chunks.remove(item)
4367 continue
4368 idx += 1
4370 # pop from uaf watchlist
4371 if gef.session.heap_uaf_watchpoints: 4371 ↛ 4372line 4371 didn't jump to line 4372 because the condition on line 4371 was never true
4372 idx = 0
4373 for wp in gef.session.heap_uaf_watchpoints:
4374 wp_addr = wp.address
4375 if loc <= wp_addr < loc + size:
4376 gef.session.heap_uaf_watchpoints.remove(wp)
4377 wp.enabled = False
4378 continue
4379 idx += 1
4381 item = (loc, size)
4383 if check_heap_overlap: 4383 ↛ 4405line 4383 didn't jump to line 4405 because the condition on line 4383 was always true
4384 # seek all the currently allocated chunks, read their effective size and check for overlap
4385 msg = []
4386 align = gef.arch.ptrsize
4387 for chunk_addr, _ in gef.session.heap_allocated_chunks:
4388 current_chunk = GlibcChunk(chunk_addr)
4389 current_chunk_size = current_chunk.size
4391 if chunk_addr <= loc < chunk_addr + current_chunk_size: 4391 ↛ 4392line 4391 didn't jump to line 4392 because the condition on line 4391 was never true
4392 offset = loc - chunk_addr - 2*align
4393 if offset < 0: continue # false positive, discard
4395 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
4396 msg.append("Possible heap overlap detected")
4397 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})")
4398 msg.append(f"Writing {offset:d} bytes from {chunk_addr:#x} will reach chunk {loc:#x}")
4399 msg.append(f"Payload example for chunk {chunk_addr:#x} (to overwrite {loc:#x} headers):")
4400 msg.append(f" data = 'A'*{offset:d} + 'B'*{align:d} + 'C'*{align:d}")
4401 push_context_message("warn", "\n".join(msg))
4402 return True
4404 # add it to alloc-ed list
4405 gef.session.heap_allocated_chunks.append(item)
4406 return False
4409class TraceReallocBreakpoint(gdb.Breakpoint):
4410 """Track re-allocations done with realloc()."""
4412 def __init__(self) -> None:
4413 super().__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True)
4414 self.silent = True
4415 return
4417 def stop(self) -> bool:
4418 _, ptr = gef.arch.get_ith_parameter(0)
4419 _, size = gef.arch.get_ith_parameter(1)
4420 assert ptr is not None and size is not None
4421 self.retbp = TraceReallocRetBreakpoint(ptr, size)
4422 return False
4425class TraceReallocRetBreakpoint(gdb.FinishBreakpoint):
4426 """Internal temporary breakpoint to retrieve the return value of realloc()."""
4428 def __init__(self, ptr: int, size: int) -> None:
4429 super().__init__(gdb.newest_frame(), internal=True)
4430 self.ptr = ptr
4431 self.size = size
4432 self.silent = True
4433 return
4435 def stop(self) -> bool:
4436 if self.return_value: 4436 ↛ 4439line 4436 didn't jump to line 4439 because the condition on line 4436 was always true
4437 newloc = int(self.return_value)
4438 else:
4439 newloc = parse_address(gef.arch.return_register)
4441 title = Color.colorify("Heap-Analysis", "yellow bold")
4442 if newloc != self: 4442 ↛ 4446line 4442 didn't jump to line 4446 because the condition on line 4442 was always true
4443 loc = Color.colorify(f"{newloc:#x}", "green")
4444 ok(f"{title} - realloc({self.ptr:#x}, {self.size})={loc}")
4445 else:
4446 loc = Color.colorify(f"{newloc:#x}", "red")
4447 ok(f"{title} - realloc({self.ptr:#x}, {self.size})={loc}")
4449 item = (newloc, self.size)
4451 try:
4452 # check if item was in alloc-ed list
4453 idx = [x for x, y in gef.session.heap_allocated_chunks].index(self.ptr)
4454 # if so pop it out
4455 item = gef.session.heap_allocated_chunks.pop(idx)
4456 except ValueError:
4457 if is_debug():
4458 warn(f"Chunk {self.ptr:#x} was not in tracking list")
4459 finally:
4460 # add new item to alloc-ed list
4461 gef.session.heap_allocated_chunks.append(item)
4463 return False
4466class TraceFreeBreakpoint(gdb.Breakpoint):
4467 """Track calls to free() and attempts to detect inconsistencies."""
4469 def __init__(self) -> None:
4470 super().__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True)
4471 self.silent = True
4472 return
4474 def stop(self) -> bool:
4475 reset_all_caches()
4476 _, addr = gef.arch.get_ith_parameter(0)
4477 msg = []
4478 check_free_null = gef.config["heap-analysis-helper.check_free_null"]
4479 check_double_free = gef.config["heap-analysis-helper.check_double_free"]
4480 check_weird_free = gef.config["heap-analysis-helper.check_weird_free"]
4481 check_uaf = gef.config["heap-analysis-helper.check_uaf"]
4483 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - free({addr:#x})")
4484 if not addr: 4484 ↛ 4485line 4484 didn't jump to line 4485 because the condition on line 4484 was never true
4485 if check_free_null:
4486 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
4487 msg.append(f"Attempting to free(NULL) at {gef.arch.pc:#x}")
4488 msg.append("Reason: if NULL page is allocatable, this can lead to code execution.")
4489 push_context_message("warn", "\n".join(msg))
4490 return True
4491 return False
4493 if addr in [x for (x, _) in gef.session.heap_freed_chunks]: 4493 ↛ 4494line 4493 didn't jump to line 4494 because the condition on line 4493 was never true
4494 if check_double_free:
4495 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
4496 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")
4497 msg.append("Execution will likely crash...")
4498 push_context_message("warn", "\n".join(msg))
4499 return True
4500 return False
4502 # if here, no error
4503 # 1. move alloc-ed item to free list
4504 try:
4505 # pop from alloc-ed list
4506 idx = [x for x, y in gef.session.heap_allocated_chunks].index(addr)
4507 item = gef.session.heap_allocated_chunks.pop(idx)
4509 except ValueError:
4510 if check_weird_free:
4511 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
4512 msg.append("Heap inconsistency detected:")
4513 msg.append(f"Attempting to free an unknown value: {addr:#x}")
4514 push_context_message("warn", "\n".join(msg))
4515 return True
4516 return False
4518 # 2. add it to free-ed list
4519 gef.session.heap_freed_chunks.append(item)
4521 self.retbp = None
4522 if check_uaf: 4522 ↛ 4525line 4522 didn't jump to line 4525 because the condition on line 4522 was always true
4523 # 3. (opt.) add a watchpoint on pointer
4524 self.retbp = TraceFreeRetBreakpoint(addr)
4525 return False
4528class TraceFreeRetBreakpoint(gdb.FinishBreakpoint):
4529 """Internal temporary breakpoint to track free()d values."""
4531 def __init__(self, addr: int) -> None:
4532 super().__init__(gdb.newest_frame(), internal=True)
4533 self.silent = True
4534 self.addr = addr
4535 return
4537 def stop(self) -> bool:
4538 reset_all_caches()
4539 wp = UafWatchpoint(self.addr)
4540 gef.session.heap_uaf_watchpoints.append(wp)
4541 return False
4544class UafWatchpoint(gdb.Breakpoint):
4545 """Custom watchpoints set TraceFreeBreakpoint() to monitor free()d pointers being used."""
4547 def __init__(self, addr: int) -> None:
4548 super().__init__(f"*{addr:#x}", gdb.BP_WATCHPOINT, internal=True)
4549 self.address = addr
4550 self.silent = True
4551 self.enabled = True
4552 return
4554 def stop(self) -> bool:
4555 """If this method is triggered, we likely have a UaF. Break the execution and report it."""
4556 reset_all_caches()
4557 frame = gdb.selected_frame()
4558 if frame.name() in ("_int_malloc", "malloc_consolidate", "__libc_calloc", ):
4559 return False
4561 # software watchpoints stop after the next statement (see
4562 # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html)
4563 pc = gdb_get_nth_previous_instruction_address(gef.arch.pc, 2)
4564 assert pc
4565 insn = gef_current_instruction(pc)
4566 msg = []
4567 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
4568 msg.append(f"Possible Use-after-Free in '{get_filepath()}': "
4569 f"pointer {self.address:#x} was freed, but is attempted to be used at {pc:#x}")
4570 msg.append(f"{insn.address:#x} {insn.mnemonic} {Color.yellowify(', '.join(insn.operands))}")
4571 push_context_message("warn", "\n".join(msg))
4572 return True
4575class EntryBreakBreakpoint(gdb.Breakpoint):
4576 """Breakpoint used internally to stop execution at the most convenient entry point."""
4578 def __init__(self, location: str) -> None:
4579 super().__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True)
4580 self.silent = True
4581 return
4583 def stop(self) -> bool:
4584 reset_all_caches()
4585 return True
4588class NamedBreakpoint(gdb.Breakpoint):
4589 """Breakpoint which shows a specified name, when hit."""
4591 def __init__(self, location: str, name: str) -> None:
4592 super().__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False)
4593 self.name = name
4594 self.loc = location
4595 return
4597 def stop(self) -> bool:
4598 reset_all_caches()
4599 push_context_message("info", f"Hit breakpoint {self.loc} ({Color.colorify(self.name, 'red bold')})")
4600 return True
4603class JustSilentStopBreakpoint(gdb.Breakpoint):
4604 """When hit, this temporary breakpoint stop the execution."""
4606 def __init__(self, loc: str) -> None:
4607 super().__init__(loc, gdb.BP_BREAKPOINT, temporary=True)
4608 self.silent = True
4609 return
4612#
4613# Context Panes
4614#
4616def register_external_context_pane(pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None:
4617 """
4618 Registering function for new GEF Context View.
4619 pane_name: a string that has no spaces (used in settings)
4620 display_pane_function: a function that uses gef_print() to print strings
4621 pane_title_function: a function that returns a string or None, which will be displayed as the title.
4622 If None, no title line is displayed.
4623 condition: an optional callback: if not None, the callback will be executed first. If it returns true,
4624 then only the pane title and content will displayed. Otherwise, it's simply skipped.
4626 Example usage for a simple text to show when we hit a syscall:
4627 def only_syscall(): return gef_current_instruction(gef.arch.pc).is_syscall()
4628 def display_pane():
4629 gef_print("Wow, I am a context pane!")
4630 def pane_title():
4631 return "example:pane"
4632 register_external_context_pane("example_pane", display_pane, pane_title, only_syscall)
4633 """
4634 gef.gdb.add_context_pane(pane_name, display_pane_function, pane_title_function, condition)
4635 return
4637def register_external_context_layout_mapping(current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], str | None], condition : Callable[[], bool] | None = None) -> None:
4638 gef.gdb.add_context_layout_mapping(current_pane_name, display_pane_function, pane_title_function, condition)
4639 return
4642#
4643# Commands
4644#
4645@deprecated("Use `register()`, and inherit from `GenericCommand` instead")
4646def register_external_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]:
4647 """Registering function for new GEF (sub-)command to GDB."""
4648 return cls
4650@deprecated("Use `register()`, and inherit from `GenericCommand` instead")
4651def register_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]:
4652 """Decorator for registering new GEF (sub-)command to GDB."""
4653 return cls
4655@deprecated("")
4656def register_priority_command(cls: Type["GenericCommand"]) -> Type["GenericCommand"]:
4657 """Decorator for registering new command with priority, meaning that it must
4658 loaded before the other generic commands."""
4659 return cls
4662ValidCommandType = TypeVar("ValidCommandType", bound="GenericCommand")
4663ValidFunctionType = TypeVar("ValidFunctionType", bound="GenericFunction")
4665def register(cls: Type["ValidCommandType"] | Type["ValidFunctionType"]) -> Type["ValidCommandType"] | Type["ValidFunctionType"]:
4666 global __registered_commands__, __registered_functions__
4667 if issubclass(cls, GenericCommand):
4668 assert hasattr(cls, "_cmdline_")
4669 assert hasattr(cls, "do_invoke")
4670 if any(map(lambda x: x._cmdline_ == cls._cmdline_, __registered_commands__)): 4670 ↛ 4671line 4670 didn't jump to line 4671 because the condition on line 4670 was never true
4671 raise AlreadyRegisteredException(cls._cmdline_)
4672 __registered_commands__.add(cls)
4673 return cls
4675 if issubclass(cls, GenericFunction): 4675 ↛ 4683line 4675 didn't jump to line 4683 because the condition on line 4675 was always true
4676 assert hasattr(cls, "_function_")
4677 assert hasattr(cls, "invoke")
4678 if any(map(lambda x: x._function_ == cls._function_, __registered_functions__)): 4678 ↛ 4679line 4678 didn't jump to line 4679 because the condition on line 4678 was never true
4679 raise AlreadyRegisteredException(cls._function_)
4680 __registered_functions__.add(cls)
4681 return cls
4683 raise TypeError(f"`{cls.__class__}` is an illegal class for `register`")
4686class GenericCommand(gdb.Command):
4687 """This is an abstract class for invoking commands, should not be instantiated."""
4689 _cmdline_: str
4690 _syntax_: str
4691 _example_: str | list[str] = ""
4692 _aliases_: list[str] = []
4694 def __init_subclass__(cls, **kwargs):
4695 super().__init_subclass__(**kwargs)
4696 attributes = ("_cmdline_", "_syntax_", )
4697 if not all(map(lambda x: hasattr(cls, x), attributes)): 4697 ↛ 4698line 4697 didn't jump to line 4698 because the condition on line 4697 was never true
4698 raise NotImplementedError
4700 def __init__(self, *args: Any, **kwargs: Any) -> None:
4701 self.pre_load()
4702 syntax = Color.yellowify("\nSyntax: ") + self._syntax_
4703 example = Color.yellowify("\nExamples: \n\t")
4704 if isinstance(self._example_, list):
4705 example += "\n\t".join(self._example_)
4706 elif isinstance(self._example_, str): 4706 ↛ 4708line 4706 didn't jump to line 4708 because the condition on line 4706 was always true
4707 example += self._example_
4708 self.__doc__ = (self.__doc__ or "").replace(" "*4, "") + syntax + example
4709 self.repeat = False
4710 self.repeat_count = 0
4711 self.__last_command = None
4712 command_type = kwargs.setdefault("command", gdb.COMMAND_USER)
4713 complete_type = kwargs.setdefault("complete", -1) # -1=allow user-defined `complete()`
4714 prefix = kwargs.setdefault("prefix", False)
4715 super().__init__(name=self._cmdline_, command_class=command_type,
4716 completer_class=complete_type, prefix=prefix)
4717 self.post_load()
4718 return
4720 def invoke(self, args: str, from_tty: bool) -> None:
4721 try:
4722 argv = gdb.string_to_argv(args)
4723 self.__set_repeat_count(argv, from_tty)
4724 bufferize(self.do_invoke)(argv)
4725 except Exception as e:
4726 # Note: since we are intercepting cleaning exceptions here, commands preferably should avoid
4727 # catching generic Exception, but rather specific ones. This is allows a much cleaner use.
4728 if is_debug(): 4728 ↛ 4733line 4728 didn't jump to line 4733 because the condition on line 4728 was always true
4729 show_last_exception()
4730 if gef.config["gef.propagate_debug_exception"] is True:
4731 raise
4732 else:
4733 err(f"Command '{self._cmdline_}' failed to execute properly, reason: {e}")
4734 return
4736 def usage(self) -> None:
4737 err(f"Syntax\n{self._syntax_}")
4738 return
4740 def do_invoke(self, argv: list[str]) -> None:
4741 raise NotImplementedError
4743 def pre_load(self) -> None:
4744 return
4746 def post_load(self) -> None:
4747 return
4749 def __get_setting_name(self, name: str) -> str:
4750 clsname = self.__class__._cmdline_.replace(" ", "-")
4751 return f"{clsname}.{name}"
4753 def __iter__(self) -> Generator[str, None, None]:
4754 for key in gef.config.keys():
4755 if key.startswith(self._cmdline_):
4756 yield key.replace(f"{self._cmdline_}.", "", 1)
4758 @property
4759 def settings(self) -> list[str]:
4760 """Return the list of settings for this command."""
4761 return list(iter(self))
4763 @deprecated("Use `self[setting_name]` instead")
4764 def get_setting(self, name: str) -> Any:
4765 return self.__getitem__(name)
4767 def __getitem__(self, name: str) -> Any:
4768 key = self.__get_setting_name(name)
4769 return gef.config[key]
4771 @deprecated("Use `setting_name in self` instead")
4772 def has_setting(self, name: str) -> bool:
4773 return self.__contains__(name)
4775 def __contains__(self, name: str) -> bool:
4776 return self.__get_setting_name(name) in gef.config
4778 @deprecated("Use `self[setting_name] = value` instead")
4779 def add_setting(self, name: str, value: tuple[Any, type, str], description: str = "") -> None:
4780 return self.__setitem__(name, (value, description))
4782 def __setitem__(self, name: str, value: "GefSetting | tuple[Any, str]") -> None:
4783 # make sure settings are always associated to the root command (which derives from GenericCommand)
4784 if "GenericCommand" not in [x.__name__ for x in self.__class__.__bases__]:
4785 return
4786 key = self.__get_setting_name(name)
4787 if key in gef.config:
4788 # If the setting already exists, update the entry
4789 setting = gef.config.raw_entry(key)
4790 setting.value = value
4791 return
4793 # otherwise create it
4794 if isinstance(value, GefSetting): 4794 ↛ 4795line 4794 didn't jump to line 4795 because the condition on line 4794 was never true
4795 gef.config[key] = value
4796 else:
4797 if len(value) == 1: 4797 ↛ 4798line 4797 didn't jump to line 4798 because the condition on line 4797 was never true
4798 gef.config[key] = GefSetting(value[0])
4799 elif len(value) == 2: 4799 ↛ 4801line 4799 didn't jump to line 4801 because the condition on line 4799 was always true
4800 gef.config[key] = GefSetting(value[0], description=value[1])
4801 return
4803 @deprecated("Use `del self[setting_name]` instead")
4804 def del_setting(self, name: str) -> None:
4805 return self.__delitem__(name)
4807 def __delitem__(self, name: str) -> None:
4808 del gef.config[self.__get_setting_name(name)]
4809 return
4811 def __set_repeat_count(self, argv: list[str], from_tty: bool) -> None:
4812 if not from_tty: 4812 ↛ 4817line 4812 didn't jump to line 4817 because the condition on line 4812 was always true
4813 self.repeat = False
4814 self.repeat_count = 0
4815 return
4817 command = (gdb.execute("show commands", to_string=True) or "").strip().split("\n")[-1]
4818 self.repeat = self.__last_command == command
4819 self.repeat_count = self.repeat_count + 1 if self.repeat else 0
4820 self.__last_command = command
4821 return
4824@register
4825class ArchCommand(GenericCommand):
4826 """Manage the current loaded architecture."""
4828 _cmdline_ = "arch"
4829 _syntax_ = f"{_cmdline_} (list|get|set) ..."
4830 _example_ = f"{_cmdline_} set X86"
4832 def __init__(self) -> None:
4833 super().__init__(prefix=True)
4834 return
4836 def do_invoke(self, argv: list[str]) -> None:
4837 if not argv:
4838 self.usage()
4839 return
4841@register
4842class ArchGetCommand(GenericCommand):
4843 """Get the current loaded architecture."""
4845 _cmdline_ = "arch get"
4846 _syntax_ = f"{_cmdline_}"
4847 _example_ = f"{_cmdline_}"
4849 def do_invoke(self, args: list[str]) -> None:
4850 gef_print(f"{Color.greenify('Arch')}: {gef.arch}")
4851 gef_print(f"{Color.greenify('Reason')}: {gef.arch_reason}")
4854@register
4855class ArchSetCommand(GenericCommand):
4856 """Set the current loaded architecture."""
4858 _cmdline_ = "arch set"
4859 _syntax_ = f"{_cmdline_} <arch>"
4860 _example_ = f"{_cmdline_} X86"
4862 def do_invoke(self, args: list[str]) -> None:
4863 reset_architecture(args[0] if args else None)
4865 def complete(self, text: str, word: str) -> list[str]:
4866 return sorted(x for x in __registered_architectures__.keys() if
4867 isinstance(x, str) and x.lower().startswith(text.lower().strip()))
4869@register
4870class ArchListCommand(GenericCommand):
4871 """List the available architectures."""
4873 _cmdline_ = "arch list"
4874 _syntax_ = f"{_cmdline_}"
4875 _example_ = f"{_cmdline_}"
4877 def do_invoke(self, args: list[str]) -> None:
4878 gef_print(Color.greenify("Available architectures:"))
4879 for arch in sorted(set(__registered_architectures__.values()), key=lambda x: x.arch):
4880 if arch is GenericArchitecture:
4881 continue
4883 gef_print(' ' + Color.yellowify(str(arch())))
4884 for alias in arch.aliases:
4885 if isinstance(alias, str):
4886 gef_print(f" {alias}")
4889@register
4890class VersionCommand(GenericCommand):
4891 """Display GEF version info."""
4893 _cmdline_ = "version"
4894 _syntax_ = f"{_cmdline_}"
4895 _example_ = f"{_cmdline_}"
4897 def do_invoke(self, argv: list[str]) -> None:
4898 gef_fpath = pathlib.Path(inspect.stack()[0][1]).expanduser().absolute()
4899 gef_dir = gef_fpath.parent
4900 gef_hash = hashlib.sha256(gef_fpath.read_bytes()).hexdigest()
4902 try:
4903 git = which("git")
4904 except FileNotFoundError:
4905 git = None
4907 if git: 4907 ↛ 4916line 4907 didn't jump to line 4916 because the condition on line 4907 was always true
4908 if (gef_dir / ".git").is_dir(): 4908 ↛ 4913line 4908 didn't jump to line 4913 because the condition on line 4908 was always true
4909 ver = subprocess.check_output("git log --format='%H' -n 1 HEAD", cwd=gef_dir, shell=True).decode("utf8").strip()
4910 extra = "dirty" if len(subprocess.check_output("git ls-files -m", cwd=gef_dir, shell=True).decode("utf8").strip()) else "clean"
4911 gef_print(f"GEF: rev:{ver} (Git - {extra})")
4912 else:
4913 gef_blob_hash = subprocess.check_output(f"git hash-object {gef_fpath}", shell=True).decode().strip()
4914 gef_print("GEF: (Standalone)")
4915 gef_print(f"Blob Hash({gef_fpath}): {gef_blob_hash}")
4916 gef_print(f"SHA256({gef_fpath}): {gef_hash}")
4917 gef_print(f"GDB: {gdb.VERSION}")
4918 py_ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}"
4919 gef_print(f"GDB-Python: {py_ver}")
4921 if "full" in argv:
4922 gef_print(f"Loaded commands: {', '.join(gef.gdb.loaded_command_names)}")
4923 return
4926@register
4927class PrintFormatCommand(GenericCommand):
4928 """Print bytes format in commonly used formats, such as literals in high level languages."""
4930 valid_formats = ("py", "c", "js", "asm", "hex", "bytearray")
4931 valid_bitness = (8, 16, 32, 64)
4933 _cmdline_ = "print-format"
4934 _aliases_ = ["pf",]
4935 _syntax_ = (f"{_cmdline_} [--lang LANG] [--bitlen SIZE] [(--length,-l) LENGTH] [--clip] LOCATION"
4936 f"\t--lang LANG specifies the output format for programming language (available: {valid_formats!s}, default 'py')."
4937 f"\t--bitlen SIZE specifies size of bit (possible values: {valid_bitness!s}, default is 8)."
4938 "\t--length LENGTH specifies length of array (default is 256)."
4939 "\t--clip The output data will be copied to clipboard"
4940 "\tLOCATION specifies where the address of bytes is stored.")
4941 _example_ = f"{_cmdline_} --lang py -l 16 $rsp"
4943 def __init__(self) -> None:
4944 super().__init__(complete=gdb.COMPLETE_LOCATION)
4945 self["max_size_preview"] = (10, "max size preview of bytes")
4946 return
4948 @property
4949 def format_matrix(self) -> dict[int, tuple[str, str, str]]:
4950 # `gef.arch.endianness` is a runtime property, should not be defined as a class property
4951 return {
4952 8: (f"{gef.arch.endianness}B", "char", "db"),
4953 16: (f"{gef.arch.endianness}H", "short", "dw"),
4954 32: (f"{gef.arch.endianness}I", "int", "dd"),
4955 64: (f"{gef.arch.endianness}Q", "long long", "dq"),
4956 }
4958 @only_if_gdb_running
4959 @parse_arguments({"location": "$pc", }, {("--length", "-l"): 256, "--bitlen": 0, "--lang": "py", "--clip": False,})
4960 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
4961 """Default value for print-format command."""
4962 args: argparse.Namespace = kwargs["arguments"]
4963 args.bitlen = args.bitlen or gef.arch.ptrsize * 2
4965 valid_bitlens = self.format_matrix.keys()
4966 if args.bitlen not in valid_bitlens: 4966 ↛ 4967line 4966 didn't jump to line 4967 because the condition on line 4966 was never true
4967 err(f"Size of bit must be in: {valid_bitlens!s}")
4968 return
4970 if args.lang not in self.valid_formats:
4971 err(f"Language must be in: {self.valid_formats!s}")
4972 return
4974 start_addr = parse_address(args.location)
4975 size = int(args.bitlen / 8)
4976 end_addr = start_addr + args.length * size
4977 fmt = self.format_matrix[args.bitlen][0]
4978 data = []
4980 if args.lang != "bytearray":
4981 for addr in range(start_addr, end_addr, size):
4982 value = struct.unpack(fmt, gef.memory.read(addr, size))[0]
4983 data += [value]
4984 sdata = ", ".join(map(hex, data))
4985 else:
4986 sdata = ""
4988 if args.lang == "bytearray":
4989 data = gef.memory.read(start_addr, args.length)
4990 preview = str(data[0:self["max_size_preview"]])
4991 out = f"Saved data {preview}... in '{gef_convenience(data)}'"
4992 elif args.lang == "py":
4993 out = f"buf = [{sdata}]"
4994 elif args.lang == "c": 4994 ↛ 4995line 4994 didn't jump to line 4995 because the condition on line 4994 was never true
4995 c_type = self.format_matrix[args.bitlen][1]
4996 out = f"unsigned {c_type} buf[{args.length}] = { {sdata}} ;"
4997 elif args.lang == "js":
4998 out = f"var buf = [{sdata}]"
4999 elif args.lang == "asm": 4999 ↛ 5000line 4999 didn't jump to line 5000 because the condition on line 4999 was never true
5000 asm_type = self.format_matrix[args.bitlen][2]
5001 out = f"buf {asm_type} {sdata}"
5002 elif args.lang == "hex": 5002 ↛ 5005line 5002 didn't jump to line 5005 because the condition on line 5002 was always true
5003 out = gef.memory.read(start_addr, end_addr-start_addr).hex()
5004 else:
5005 raise ValueError(f"Invalid format: {args.lang}")
5007 if args.clip: 5007 ↛ 5008line 5007 didn't jump to line 5008 because the condition on line 5007 was never true
5008 if copy_to_clipboard(gef_pybytes(out)):
5009 info("Copied to clipboard")
5010 else:
5011 warn("There's a problem while copying")
5013 gef_print(out)
5014 return
5017@register
5018class PieCommand(GenericCommand):
5019 """PIE breakpoint support."""
5021 _cmdline_ = "pie"
5022 _syntax_ = f"{_cmdline_} (breakpoint|info|delete|run|attach|remote)"
5024 def __init__(self) -> None:
5025 super().__init__(prefix=True)
5026 return
5028 def do_invoke(self, argv: list[str]) -> None:
5029 if not argv: 5029 ↛ 5031line 5029 didn't jump to line 5031 because the condition on line 5029 was always true
5030 self.usage()
5031 return
5034@register
5035class PieBreakpointCommand(GenericCommand):
5036 """Set a PIE breakpoint at an offset from the target binaries base address."""
5038 _cmdline_ = "pie breakpoint"
5039 _syntax_ = f"{_cmdline_} OFFSET"
5041 @parse_arguments({"offset": ""}, {})
5042 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
5043 args : argparse.Namespace = kwargs["arguments"]
5044 if not args.offset: 5044 ↛ 5045line 5044 didn't jump to line 5045 because the condition on line 5044 was never true
5045 self.usage()
5046 return
5048 addr = parse_address(args.offset)
5049 self.set_pie_breakpoint(lambda base: f"b *{base + addr}", addr)
5051 # When the process is already on, set real breakpoints immediately
5052 if is_alive(): 5052 ↛ 5053line 5052 didn't jump to line 5053 because the condition on line 5052 was never true
5053 vmmap = gef.memory.maps
5054 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
5055 for bp_ins in gef.session.pie_breakpoints.values():
5056 bp_ins.instantiate(base_address)
5057 return
5059 @staticmethod
5060 def set_pie_breakpoint(set_func: Callable[[int], str], addr: int) -> None:
5061 gef.session.pie_breakpoints[gef.session.pie_counter] = PieVirtualBreakpoint(set_func, gef.session.pie_counter, addr)
5062 gef.session.pie_counter += 1
5063 return
5066@register
5067class PieInfoCommand(GenericCommand):
5068 """Display breakpoint info."""
5070 _cmdline_ = "pie info"
5071 _syntax_ = f"{_cmdline_} BREAKPOINT"
5073 @parse_arguments({"breakpoints": [-1,]}, {})
5074 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
5075 args : argparse.Namespace = kwargs["arguments"]
5076 if args.breakpoints[0] == -1:
5077 # No breakpoint info needed
5078 bps = gef.session.pie_breakpoints.values()
5079 else:
5080 bps = [gef.session.pie_breakpoints[x]
5081 for x in args.breakpoints
5082 if x in gef.session.pie_breakpoints]
5084 lines = [f"{'VNum':6s} {'Num':6s} {'Addr':18s}"]
5085 lines += [
5086 f"{x.vbp_num:6d} {str(x.bp_num) if x.bp_num else 'N/A':6s} {x.addr:18s}" for x in bps
5087 ]
5088 gef_print("\n".join(lines))
5089 return
5092@register
5093class PieDeleteCommand(GenericCommand):
5094 """Delete a PIE breakpoint."""
5096 _cmdline_ = "pie delete"
5097 _syntax_ = f"{_cmdline_} [BREAKPOINT]"
5099 @parse_arguments({"breakpoints": [-1,]}, {})
5100 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
5101 global gef
5102 args : argparse.Namespace = kwargs["arguments"]
5103 if args.breakpoints[0] == -1: 5103 ↛ 5105line 5103 didn't jump to line 5105 because the condition on line 5103 was never true
5104 # no arg, delete all
5105 to_delete = list(gef.session.pie_breakpoints.values())
5106 self.delete_bp(to_delete)
5107 else:
5108 self.delete_bp([gef.session.pie_breakpoints[x]
5109 for x in args.breakpoints
5110 if x in gef.session.pie_breakpoints])
5111 return
5114 @staticmethod
5115 def delete_bp(breakpoints: list[PieVirtualBreakpoint]) -> None:
5116 global gef
5117 for bp in breakpoints:
5118 # delete current real breakpoints if exists
5119 if bp.bp_num: 5119 ↛ 5120line 5119 didn't jump to line 5120 because the condition on line 5119 was never true
5120 gdb.execute(f"delete {bp.bp_num}")
5121 # delete virtual breakpoints
5122 del gef.session.pie_breakpoints[bp.vbp_num]
5123 return
5126@register
5127class PieRunCommand(GenericCommand):
5128 """Run process with PIE breakpoint support."""
5130 _cmdline_ = "pie run"
5131 _syntax_ = _cmdline_
5133 def do_invoke(self, argv: list[str]) -> None:
5134 global gef
5135 fpath = get_filepath()
5136 if not fpath: 5136 ↛ 5137line 5136 didn't jump to line 5137 because the condition on line 5136 was never true
5137 warn("No executable to debug, use `file` to load a binary")
5138 return
5140 if not os.access(fpath, os.X_OK): 5140 ↛ 5141line 5140 didn't jump to line 5141 because the condition on line 5140 was never true
5141 warn(f"The file '{fpath}' is not executable.")
5142 return
5144 if is_alive(): 5144 ↛ 5145line 5144 didn't jump to line 5145 because the condition on line 5144 was never true
5145 warn("gdb is already running. Restart process.")
5147 # get base address
5148 gdb.execute("set stop-on-solib-events 1")
5149 hide_context()
5150 gdb.execute(f"run {' '.join(argv)}")
5151 unhide_context()
5152 gdb.execute("set stop-on-solib-events 0")
5153 vmmap = gef.memory.maps
5154 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
5155 info(f"base address {hex(base_address)}")
5157 # modify all breakpoints
5158 for bp_ins in gef.session.pie_breakpoints.values():
5159 bp_ins.instantiate(base_address)
5161 try:
5162 gdb.execute("continue")
5163 except gdb.error as e:
5164 err(str(e))
5165 gdb.execute("kill")
5166 return
5169@register
5170class PieAttachCommand(GenericCommand):
5171 """Do attach with PIE breakpoint support."""
5173 _cmdline_ = "pie attach"
5174 _syntax_ = f"{_cmdline_} PID"
5176 def do_invoke(self, argv: list[str]) -> None:
5177 try:
5178 gdb.execute(f"attach {' '.join(argv)}", to_string=True)
5179 except gdb.error as e:
5180 err(str(e))
5181 return
5182 # after attach, we are stopped so that we can
5183 # get base address to modify our breakpoint
5184 vmmap = gef.memory.maps
5185 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
5187 for bp_ins in gef.session.pie_breakpoints.values():
5188 bp_ins.instantiate(base_address)
5189 gdb.execute("context")
5190 return
5193@register
5194class PieRemoteCommand(GenericCommand):
5195 """Attach to a remote connection with PIE breakpoint support."""
5197 _cmdline_ = "pie remote"
5198 _syntax_ = f"{_cmdline_} REMOTE"
5200 def do_invoke(self, argv: list[str]) -> None:
5201 try:
5202 gdb.execute(f"gef-remote {' '.join(argv)}")
5203 except gdb.error as e:
5204 err(str(e))
5205 return
5206 # after remote attach, we are stopped so that we can
5207 # get base address to modify our breakpoint
5208 vmmap = gef.memory.maps
5209 base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0]
5211 for bp_ins in gef.session.pie_breakpoints.values():
5212 bp_ins.instantiate(base_address)
5213 gdb.execute("context")
5214 return
5217@register
5218class SmartEvalCommand(GenericCommand):
5219 """SmartEval: Smart eval (vague approach to mimic WinDBG `?`)."""
5221 _cmdline_ = "$"
5222 _syntax_ = f"{_cmdline_} EXPR\n{_cmdline_} ADDRESS1 ADDRESS2"
5223 _example_ = (f"\n{_cmdline_} $pc+1"
5224 f"\n{_cmdline_} 0x00007ffff7a10000 0x00007ffff7bce000")
5226 def do_invoke(self, argv: list[str]) -> None:
5227 argc = len(argv)
5228 if argc == 1:
5229 self.evaluate(argv)
5230 return
5232 if argc == 2: 5232 ↛ 5234line 5232 didn't jump to line 5234 because the condition on line 5232 was always true
5233 self.distance(argv)
5234 return
5236 def evaluate(self, expr: list[str]) -> None:
5237 def show_as_int(i: int) -> None:
5238 off = gef.arch.ptrsize*8
5239 def comp2_x(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):x}"
5240 def comp2_b(x: Any) -> str: return f"{(x + (1 << off)) % (1 << off):b}"
5242 try:
5243 s_i = comp2_x(res)
5244 s_i = s_i.rjust(len(s_i)+1, "0") if len(s_i)%2 else s_i
5245 gef_print(f"{i:d}")
5246 gef_print("0x" + comp2_x(res))
5247 gef_print("0b" + comp2_b(res))
5248 gef_print(f"{binascii.unhexlify(s_i)}")
5249 gef_print(f"{binascii.unhexlify(s_i)[::-1]}")
5250 except Exception:
5251 pass
5252 return
5254 parsed_expr = []
5255 for xp in expr:
5256 try:
5257 xp = gdb.parse_and_eval(xp)
5258 xp = int(xp)
5259 parsed_expr.append(f"{xp:d}")
5260 except gdb.error:
5261 parsed_expr.append(str(xp))
5263 try:
5264 res = eval(" ".join(parsed_expr))
5265 if isinstance(res, int): 5265 ↛ 5268line 5265 didn't jump to line 5268 because the condition on line 5265 was always true
5266 show_as_int(res)
5267 else:
5268 gef_print(f"{res}")
5269 except SyntaxError:
5270 gef_print(" ".join(parsed_expr))
5271 return
5273 def distance(self, args: list[str]) -> None:
5274 try:
5275 x = int(args[0], 16) if is_hex(args[0]) else int(args[0])
5276 y = int(args[1], 16) if is_hex(args[1]) else int(args[1])
5277 gef_print(f"{abs(x - y)}")
5278 except ValueError:
5279 warn(f"Distance requires 2 numbers: {self._cmdline_} 0 0xffff")
5280 return
5283@register
5284class CanaryCommand(GenericCommand):
5285 """Shows the canary value of the current process."""
5287 _cmdline_ = "canary"
5288 _syntax_ = _cmdline_
5290 @only_if_gdb_running
5291 def do_invoke(self, argv: list[str]) -> None:
5292 self.dont_repeat()
5294 fname = get_filepath()
5295 assert fname
5296 has_canary = Elf(fname).checksec["Canary"]
5297 if not has_canary: 5297 ↛ 5298line 5297 didn't jump to line 5298 because the condition on line 5297 was never true
5298 warn("This binary was not compiled with SSP.")
5299 return
5301 res = gef.session.canary
5302 if not res: 5302 ↛ 5303line 5302 didn't jump to line 5303 because the condition on line 5302 was never true
5303 err("Failed to get the canary")
5304 return
5306 canary, location = res
5307 info(f"The canary of process {gef.session.pid} is at {location:#x}, value is {canary:#x}")
5308 return
5311@register
5312class ProcessStatusCommand(GenericCommand):
5313 """Extends the info given by GDB `info proc`, by giving an exhaustive description of the
5314 process status (file descriptors, ancestor, descendants, etc.)."""
5316 _cmdline_ = "process-status"
5317 _syntax_ = _cmdline_
5318 _aliases_ = ["status", ]
5320 def __init__(self) -> None:
5321 super().__init__(complete=gdb.COMPLETE_NONE)
5322 return
5324 @only_if_gdb_running
5325 @only_if_gdb_target_local
5326 def do_invoke(self, argv: list[str]) -> None:
5327 self.show_info_proc()
5328 self.show_ancestor()
5329 self.show_descendants()
5330 self.show_fds()
5331 self.show_connections()
5332 return
5334 def get_state_of(self, pid: int) -> dict[str, str]:
5335 res = {}
5336 with open(f"/proc/{pid}/status", "r") as f:
5337 file = f.readlines()
5338 for line in file:
5339 key, value = line.split(":", 1)
5340 res[key.strip()] = value.strip()
5341 return res
5343 def get_cmdline_of(self, pid: int) -> str:
5344 with open(f"/proc/{pid}/cmdline", "r") as f:
5345 return f.read().replace("\x00", "\x20").strip()
5347 def get_process_path_of(self, pid: int) -> str:
5348 return os.readlink(f"/proc/{pid}/exe")
5350 def get_children_pids(self, pid: int) -> list[int]:
5351 cmd = [gef.session.constants["ps"], "-o", "pid", "--ppid", f"{pid}", "--noheaders"]
5352 try:
5353 return [int(x) for x in gef_execute_external(cmd, as_list=True)]
5354 except Exception:
5355 return []
5357 def show_info_proc(self) -> None:
5358 info("Process Information")
5359 pid = gef.session.pid
5360 cmdline = self.get_cmdline_of(pid)
5361 gef_print(f"\tPID {RIGHT_ARROW} {pid}",
5362 f"\tExecutable {RIGHT_ARROW} {self.get_process_path_of(pid)}",
5363 f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n")
5364 return
5366 def show_ancestor(self) -> None:
5367 info("Parent Process Information")
5368 ppid = int(self.get_state_of(gef.session.pid)["PPid"])
5369 state = self.get_state_of(ppid)
5370 cmdline = self.get_cmdline_of(ppid)
5371 gef_print(f"\tParent PID {RIGHT_ARROW} {state['Pid']}",
5372 f"\tCommand line {RIGHT_ARROW} '{cmdline}'", sep="\n")
5373 return
5375 def show_descendants(self) -> None:
5376 info("Children Process Information")
5377 children = self.get_children_pids(gef.session.pid)
5378 if not children: 5378 ↛ 5382line 5378 didn't jump to line 5382 because the condition on line 5378 was always true
5379 gef_print("\tNo child process")
5380 return
5382 for child_pid in children:
5383 state = self.get_state_of(child_pid)
5384 pid = int(state["Pid"])
5385 gef_print(f"\tPID {RIGHT_ARROW} {pid} (Name: '{self.get_process_path_of(pid)}',"
5386 f" CmdLine: '{self.get_cmdline_of(pid)}')")
5387 return
5389 def show_fds(self) -> None:
5390 pid = gef.session.pid
5391 path = f"/proc/{pid:d}/fd"
5393 info("File Descriptors:")
5394 items = os.listdir(path)
5395 if not items: 5395 ↛ 5396line 5395 didn't jump to line 5396 because the condition on line 5395 was never true
5396 gef_print("\tNo FD opened")
5397 return
5399 for fname in items:
5400 fullpath = os.path.join(path, fname)
5401 if os.path.islink(fullpath): 5401 ↛ 5399line 5401 didn't jump to line 5399 because the condition on line 5401 was always true
5402 gef_print(f"\t{fullpath} {RIGHT_ARROW} {os.readlink(fullpath)}")
5403 return
5405 def list_sockets(self, pid: int) -> list[int]:
5406 sockets = []
5407 path = f"/proc/{pid:d}/fd"
5408 items = os.listdir(path)
5409 for fname in items:
5410 fullpath = os.path.join(path, fname)
5411 if os.path.islink(fullpath) and os.readlink(fullpath).startswith("socket:"): 5411 ↛ 5412line 5411 didn't jump to line 5412 because the condition on line 5411 was never true
5412 p = os.readlink(fullpath).replace("socket:", "")[1:-1]
5413 sockets.append(int(p))
5414 return sockets
5416 def parse_ip_port(self, addr: str) -> tuple[str, int]:
5417 ip, port = addr.split(":")
5418 return socket.inet_ntoa(struct.pack("<I", int(ip, 16))), int(port, 16)
5420 def show_connections(self) -> None:
5421 # https://github.com/torvalds/linux/blob/v4.7/include/net/tcp_states.h#L16
5422 tcp_states_str = {
5423 0x01: "TCP_ESTABLISHED",
5424 0x02: "TCP_SYN_SENT",
5425 0x03: "TCP_SYN_RECV",
5426 0x04: "TCP_FIN_WAIT1",
5427 0x05: "TCP_FIN_WAIT2",
5428 0x06: "TCP_TIME_WAIT",
5429 0x07: "TCP_CLOSE",
5430 0x08: "TCP_CLOSE_WAIT",
5431 0x09: "TCP_LAST_ACK",
5432 0x0A: "TCP_LISTEN",
5433 0x0B: "TCP_CLOSING",
5434 0x0C: "TCP_NEW_SYN_RECV",
5435 }
5437 udp_states_str = {
5438 0x07: "UDP_LISTEN",
5439 }
5441 info("Network Connections")
5442 pid = gef.session.pid
5443 sockets = self.list_sockets(pid)
5444 if not sockets: 5444 ↛ 5448line 5444 didn't jump to line 5448 because the condition on line 5444 was always true
5445 gef_print("\tNo open connections")
5446 return
5448 entries = dict()
5449 with open(f"/proc/{pid:d}/net/tcp", "r") as tcp:
5450 entries["TCP"] = [x.split() for x in tcp.readlines()[1:]]
5451 with open(f"/proc/{pid:d}/net/udp", "r") as udp:
5452 entries["UDP"] = [x.split() for x in udp.readlines()[1:]]
5454 for proto in entries:
5455 for entry in entries[proto]:
5456 local, remote, state = entry[1:4]
5457 inode = int(entry[9])
5458 if inode in sockets:
5459 local = self.parse_ip_port(local)
5460 remote = self.parse_ip_port(remote)
5461 state = int(state, 16)
5462 state_str = tcp_states_str[state] if proto == "TCP" else udp_states_str[state]
5464 gef_print(f"\t{local[0]}:{local[1]} {RIGHT_ARROW} {remote[0]}:{remote[1]} ({state_str})")
5465 return
5468@register
5469class GefThemeCommand(GenericCommand):
5470 """Customize GEF appearance."""
5472 _cmdline_ = "theme"
5473 _syntax_ = f"{_cmdline_} [KEY [VALUE]]"
5474 _example_ = (f"{_cmdline_} address_stack green")
5476 def __init__(self) -> None:
5477 super().__init__(self._cmdline_)
5478 self["context_title_line"] = ("gray", "Color of the borders in context window")
5479 self["context_title_message"] = ("cyan", "Color of the title in context window")
5480 self["default_title_line"] = ("gray", "Default color of borders")
5481 self["default_title_message"] = ("cyan", "Default color of title")
5482 self["table_heading"] = ("blue", "Color of the column headings to tables (e.g. vmmap)")
5483 self["old_context"] = ("gray", "Color to use to show things such as code that is not immediately relevant")
5484 self["disassemble_current_instruction"] = ("green", "Color to use to highlight the current $pc when disassembling")
5485 self["dereference_string"] = ("yellow", "Color of dereferenced string")
5486 self["dereference_code"] = ("gray", "Color of dereferenced code")
5487 self["dereference_base_address"] = ("cyan", "Color of dereferenced address")
5488 self["dereference_register_value"] = ("bold blue", "Color of dereferenced register")
5489 self["registers_register_name"] = ("blue", "Color of the register name in the register window")
5490 self["registers_value_changed"] = ("bold red", "Color of the changed register in the register window")
5491 self["address_stack"] = ("pink", "Color to use when a stack address is found")
5492 self["address_heap"] = ("green", "Color to use when a heap address is found")
5493 self["address_code"] = ("red", "Color to use when a code address is found")
5494 self["source_current_line"] = ("green", "Color to use for the current code line in the source window")
5495 return
5497 def do_invoke(self, args: list[str]) -> None:
5498 self.dont_repeat()
5499 argc = len(args)
5501 if argc == 0:
5502 for key in self.settings:
5503 setting = self[key]
5504 value = Color.colorify(setting, setting)
5505 gef_print(f"{key:40s}: {value}")
5506 return
5508 setting_name = args[0]
5509 if setting_name not in self:
5510 err("Invalid key")
5511 return
5513 if argc == 1:
5514 value = self[setting_name]
5515 gef_print(f"{setting_name:40s}: {Color.colorify(value, value)}")
5516 return
5518 colors = (color for color in args[1:] if color in Color.colors)
5519 self[setting_name] = " ".join(colors) # type: ignore // this is valid since we overwrote __setitem__()
5522class ExternalStructureManager:
5523 class Structure:
5524 def __init__(self, manager: "ExternalStructureManager", mod_path: pathlib.Path, struct_name: str) -> None:
5525 self.manager = manager
5526 self.module_path = mod_path
5527 self.name = struct_name
5528 self.class_type = self.__get_structure_class()
5529 # if the symbol points to a class factory method and not a class
5530 if not hasattr(self.class_type, "_fields_") and callable(self.class_type): 5530 ↛ 5531line 5530 didn't jump to line 5531 because the condition on line 5530 was never true
5531 self.class_type = self.class_type(gef)
5532 return
5534 def __str__(self) -> str:
5535 return self.name
5537 def pprint(self) -> None:
5538 res: list[str] = []
5539 for _name, _type in self.class_type._fields_: # type: ignore
5540 size = ctypes.sizeof(_type)
5541 name = Color.colorify(_name, gef.config["pcustom.structure_name"])
5542 type = Color.colorify(_type.__name__, gef.config["pcustom.structure_type"])
5543 size = Color.colorify(hex(size), gef.config["pcustom.structure_size"])
5544 offset = Color.boldify(f"{getattr(self.class_type, _name).offset:04x}")
5545 res.append(f"{offset} {name:32s} {type:16s} /* size={size} */")
5546 gef_print("\n".join(res))
5547 return
5549 def __get_structure_class(self) -> Type[ctypes.Structure]:
5550 """Returns a tuple of (class, instance) if modname!classname exists"""
5551 fpath = self.module_path
5552 spec = importlib.util.spec_from_file_location(fpath.stem, fpath)
5553 assert spec and spec.loader, "Failed to determine module specification"
5554 module = importlib.util.module_from_spec(spec)
5555 sys.modules[fpath.stem] = module
5556 spec.loader.exec_module(module)
5557 _class = getattr(module, self.name)
5558 return _class
5560 def apply_at(self, address: int, max_depth: int, depth: int = 0) -> None:
5561 """Apply (recursively if possible) the structure format to the given address."""
5562 if depth >= max_depth: 5562 ↛ 5563line 5562 didn't jump to line 5563 because the condition on line 5562 was never true
5563 warn("maximum recursion level reached")
5564 return
5566 # read the data at the specified address
5567 assert isinstance(self.class_type, type)
5568 _structure = self.class_type()
5569 _sizeof_structure = ctypes.sizeof(_structure)
5571 try:
5572 data = gef.memory.read(address, _sizeof_structure)
5573 except gdb.MemoryError:
5574 err(f"{' ' * depth}Cannot read memory {address:#x}")
5575 return
5577 # deserialize the data
5578 length = min(len(data), _sizeof_structure)
5579 ctypes.memmove(ctypes.addressof(_structure), data, length)
5581 # pretty print all the fields (and call recursively if possible)
5582 ptrsize = gef.arch.ptrsize
5583 unpack = u32 if ptrsize == 4 else u64
5584 for field in _structure._fields_:
5585 assert len(field) == 2
5586 _name, _type = field
5587 _value = getattr(_structure, _name)
5588 _offset = getattr(self.class_type, _name).offset
5590 if ((ptrsize == 4 and _type is ctypes.c_uint32) 5590 ↛ 5594line 5590 didn't jump to line 5594 because the condition on line 5590 was never true
5591 or (ptrsize == 8 and _type is ctypes.c_uint64)
5592 or (ptrsize == ctypes.sizeof(ctypes.c_void_p) and _type is ctypes.c_void_p)):
5593 # try to dereference pointers
5594 _value = RIGHT_ARROW.join(dereference_from(_value))
5596 line = f"{' ' * depth}"
5597 line += f"{address:#x}+{_offset:#04x} {_name} : ".ljust(40)
5598 line += f"{_value} ({_type.__name__})"
5599 parsed_value = self.__get_ctypes_value(_structure, _name, _value)
5600 if parsed_value: 5600 ↛ 5601line 5600 didn't jump to line 5601 because the condition on line 5600 was never true
5601 line += f"{RIGHT_ARROW} {parsed_value}"
5602 gef_print(line)
5604 if issubclass(_type, ctypes.Structure): 5604 ↛ 5605line 5604 didn't jump to line 5605 because the condition on line 5604 was never true
5605 self.apply_at(address + _offset, max_depth, depth + 1)
5606 elif _type.__name__.startswith("LP_"):
5607 # Pointer to a structure of a different type
5608 __sub_type_name = _type.__name__.lstrip("LP_")
5609 result = self.manager.find(__sub_type_name)
5610 if result: 5610 ↛ 5584line 5610 didn't jump to line 5584 because the condition on line 5610 was always true
5611 _, __structure = result
5612 __address = unpack(gef.memory.read(address + _offset, ptrsize))
5613 __structure.apply_at(__address, max_depth, depth + 1)
5614 return
5616 def __get_ctypes_value(self, struct, item, value) -> str:
5617 if not hasattr(struct, "_values_"): return "" 5617 ↛ 5618line 5617 didn't jump to line 5618 because the condition on line 5617 was always true
5618 default = ""
5619 for name, values in struct._values_:
5620 if name != item: continue
5621 if callable(values):
5622 return str(values(value))
5623 try:
5624 for val, desc in values:
5625 if value == val: return desc
5626 if val is None: default = desc
5627 except Exception as e:
5628 err(f"Error parsing '{name}': {e}")
5629 return default
5631 class Module(dict):
5632 def __init__(self, manager: "ExternalStructureManager", path: pathlib.Path) -> None:
5633 self.manager = manager
5634 self.path = path
5635 self.name = path.stem
5636 self.raw = self.__load()
5638 for entry in self:
5639 structure = ExternalStructureManager.Structure(manager, self.path, entry)
5640 self[structure.name] = structure
5641 return
5643 def __load(self) -> ModuleType:
5644 """Load a custom module, and return it."""
5645 fpath = self.path
5646 spec = importlib.util.spec_from_file_location(fpath.stem, fpath)
5647 assert spec and spec.loader
5648 module = importlib.util.module_from_spec(spec)
5649 sys.modules[fpath.stem] = module
5650 spec.loader.exec_module(module)
5651 return module
5653 def __str__(self) -> str:
5654 return self.name
5656 def __iter__(self) -> Generator[str, None, None]:
5657 _invalid = {"BigEndianStructure", "LittleEndianStructure", "Structure"}
5658 for x in dir(self.raw):
5659 if x in _invalid: continue
5660 _attr = getattr(self.raw, x)
5662 # if it's a ctypes.Structure class, add it
5663 if inspect.isclass(_attr) and issubclass(_attr, ctypes.Structure):
5664 yield x
5665 continue
5667 # also accept class factory functions
5668 if callable(_attr) and _attr.__module__ == self.name and x.endswith("_t"): 5668 ↛ 5669line 5668 didn't jump to line 5669 because the condition on line 5668 was never true
5669 yield x
5670 continue
5671 return
5673 class Modules(dict):
5674 def __init__(self, manager: "ExternalStructureManager") -> None:
5675 self.manager: "ExternalStructureManager" = manager
5676 self.root: pathlib.Path = manager.path
5678 for entry in self.root.iterdir():
5679 if not entry.is_file(): continue
5680 if entry.suffix != ".py": continue 5680 ↛ 5678line 5680 didn't jump to line 5678 because the continue on line 5680 wasn't executed
5681 if entry.name == "__init__.py": continue 5681 ↛ 5678line 5681 didn't jump to line 5678 because the continue on line 5681 wasn't executed
5682 module = ExternalStructureManager.Module(manager, entry)
5683 self[module.name] = module
5684 return
5686 def __contains__(self, structure_name: str) -> bool:
5687 """Return True if the structure name is found in any of the modules"""
5688 for module in self.values():
5689 if structure_name in module:
5690 return True
5691 return False
5693 def __init__(self) -> None:
5694 self.clear_caches()
5695 return
5697 def clear_caches(self) -> None:
5698 self._path = None
5699 self._modules = None
5700 return
5702 @property
5703 def modules(self) -> "ExternalStructureManager.Modules":
5704 if not self._modules:
5705 self._modules = ExternalStructureManager.Modules(self)
5706 return self._modules
5708 @property
5709 def path(self) -> pathlib.Path:
5710 if not self._path:
5711 self._path = gef.config["pcustom.struct_path"].expanduser().absolute()
5712 return self._path
5714 @property
5715 def structures(self) -> Generator[tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"], None, None]:
5716 for module in self.modules.values():
5717 for structure in module.values():
5718 yield module, structure
5719 return
5721 @lru_cache()
5722 def find(self, structure_name: str) -> tuple["ExternalStructureManager.Module", "ExternalStructureManager.Structure"] | None:
5723 """Return the module and structure for the given structure name; `None` if the structure name was not found."""
5724 for module in self.modules.values():
5725 if structure_name in module:
5726 return module, module[structure_name]
5727 return None
5730@register
5731class PCustomCommand(GenericCommand):
5732 """Dump user defined structure.
5733 This command attempts to reproduce WinDBG awesome `dt` command for GDB and allows
5734 to apply structures (from symbols or custom) directly to an address.
5735 Custom structures can be defined in pure Python using ctypes, and should be stored
5736 in a specific directory, whose path must be stored in the `pcustom.struct_path`
5737 configuration setting."""
5739 _cmdline_ = "pcustom"
5740 _syntax_ = f"{_cmdline_} [list|edit <StructureName>|show <StructureName>]|<StructureName> 0xADDRESS]"
5742 def __init__(self) -> None:
5743 global gef
5744 super().__init__(prefix=True)
5745 self["max_depth"] = (4, "Maximum level of recursion supported")
5746 self["structure_name"] = ("bold blue", "Color of the structure name")
5747 self["structure_type"] = ("bold red", "Color of the attribute type")
5748 self["structure_size"] = ("green", "Color of the attribute size")
5749 gef.config[f"{self._cmdline_}.struct_path"] = GefSetting( gef.config["gef.tempdir"] / "structs", pathlib.Path,
5750 "Path to store/load the structure ctypes files",
5751 hooks={"on_write": [GefSetting.create_folder_tree,]})
5752 return
5754 @parse_arguments({"type": "", "address": ""}, {})
5755 def do_invoke(self, *_: Any, **kwargs: dict[str, Any]) -> None:
5756 args = cast(argparse.Namespace, kwargs["arguments"])
5757 if not args.type: 5757 ↛ 5758line 5757 didn't jump to line 5758 because the condition on line 5757 was never true
5758 gdb.execute("pcustom list")
5759 return
5761 structname = self.explode_type(args.type)[1]
5763 if not args.address:
5764 gdb.execute(f"pcustom show {structname}")
5765 return
5767 if not is_alive(): 5767 ↛ 5768line 5767 didn't jump to line 5768 because the condition on line 5767 was never true
5768 err("Session is not active")
5769 return
5771 manager = ExternalStructureManager()
5772 address = parse_address(args.address)
5773 result = manager.find(structname)
5774 if not result:
5775 err(f"No structure named '{structname}' found")
5776 return
5778 structure = result[1]
5779 structure.apply_at(address, self["max_depth"])
5780 return
5782 def explode_type(self, arg: str) -> tuple[str, str]:
5783 modname, structname = arg.split(":", 1) if ":" in arg else (arg, arg)
5784 structname = structname.split(".", 1)[0] if "." in structname else structname
5785 return modname, structname
5788@register
5789class PCustomListCommand(PCustomCommand):
5790 """PCustom: list available structures"""
5792 _cmdline_ = "pcustom list"
5793 _syntax_ = f"{_cmdline_}"
5795 def __init__(self) -> None:
5796 super().__init__()
5797 return
5799 def do_invoke(self, _: list[str]) -> None:
5800 """Dump the list of all the structures and their respective."""
5801 manager = ExternalStructureManager()
5802 info(f"Listing custom structures from '{manager.path}'")
5803 struct_color = gef.config["pcustom.structure_type"]
5804 filename_color = gef.config["pcustom.structure_name"]
5805 for module in manager.modules.values():
5806 __modules = ", ".join([Color.colorify(str(structure), struct_color) for structure in module.values()])
5807 __filename = Color.colorify(str(module.path), filename_color)
5808 gef_print(f"{RIGHT_ARROW} {__filename} ({__modules})")
5809 return
5812@register
5813class PCustomShowCommand(PCustomCommand):
5814 """PCustom: show the content of a given structure"""
5816 _cmdline_ = "pcustom show"
5817 _syntax_ = f"{_cmdline_} StructureName"
5818 _aliases_ = ["pcustom create", "pcustom update"]
5820 def __init__(self) -> None:
5821 super().__init__()
5822 return
5824 def do_invoke(self, argv: list[str]) -> None:
5825 if len(argv) == 0: 5825 ↛ 5826line 5825 didn't jump to line 5826 because the condition on line 5825 was never true
5826 self.usage()
5827 return
5829 _, structname = self.explode_type(argv[0])
5830 manager = ExternalStructureManager()
5831 result = manager.find(structname)
5832 if result:
5833 _, structure = result
5834 structure.pprint()
5835 else:
5836 err(f"No structure named '{structname}' found")
5837 return
5840@register
5841class PCustomEditCommand(PCustomCommand):
5842 """PCustom: edit the content of a given structure"""
5844 _cmdline_ = "pcustom edit"
5845 _syntax_ = f"{_cmdline_} StructureName"
5846 __aliases__ = ["pcustom create", "pcustom new", "pcustom update"]
5848 def __init__(self) -> None:
5849 super().__init__()
5850 return
5852 def do_invoke(self, argv: list[str]) -> None:
5853 if len(argv) == 0:
5854 self.usage()
5855 return
5857 modname, structname = self.explode_type(argv[0])
5858 self.__create_or_edit_structure(modname, structname)
5859 return
5861 def __create_or_edit_structure(self, mod_name: str, struct_name: str) -> int:
5862 path = gef.config["pcustom.struct_path"].expanduser() / f"{mod_name}.py"
5863 if path.is_file():
5864 info(f"Editing '{path}'")
5865 else:
5866 ok(f"Creating '{path}' from template")
5867 self.__create_template(struct_name, path)
5869 cmd = (os.getenv("EDITOR") or "nano").split()
5870 cmd.append(str(path.absolute()))
5871 return subprocess.call(cmd)
5873 def __create_template(self, structname: str, fpath: pathlib.Path) -> None:
5874 template = f"""from ctypes import *
5876class {structname}(Structure):
5877 _fields_ = []
5879 _values_ = []
5880"""
5881 with fpath.open("w") as f:
5882 f.write(template)
5883 return
5886@register
5887class ChangeFdCommand(GenericCommand):
5888 """ChangeFdCommand: redirect file descriptor during runtime."""
5890 _cmdline_ = "hijack-fd"
5891 _syntax_ = f"{_cmdline_} FD_NUM NEW_OUTPUT"
5892 _example_ = f"{_cmdline_} 2 /tmp/stderr_output.txt"
5894 @only_if_gdb_running
5895 @only_if_gdb_target_local
5896 def do_invoke(self, argv: list[str]) -> None:
5897 if len(argv) != 2:
5898 self.usage()
5899 return
5901 if not os.access(f"/proc/{gef.session.pid:d}/fd/{argv[0]}", os.R_OK):
5902 self.usage()
5903 return
5905 old_fd = int(argv[0])
5906 new_output = argv[1]
5908 if ":" in new_output:
5909 address = socket.gethostbyname(new_output.split(":")[0])
5910 port = int(new_output.split(":")[1])
5912 AF_INET = 2
5913 SOCK_STREAM = 1
5914 res = gdb.execute(f"call (int)socket({AF_INET}, {SOCK_STREAM}, 0)", to_string=True) or ""
5915 new_fd = self.get_fd_from_result(res)
5917 # fill in memory with sockaddr_in struct contents
5918 # we will do this in the stack, since connect() wants a pointer to a struct
5919 vmmap = gef.memory.maps
5920 stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0]
5921 original_contents = gef.memory.read(stack_addr, 8)
5923 gef.memory.write(stack_addr, b"\x02\x00", 2)
5924 gef.memory.write(stack_addr + 0x2, struct.pack("<H", socket.htons(port)), 2)
5925 gef.memory.write(stack_addr + 0x4, socket.inet_aton(address), 4)
5927 info(f"Trying to connect to {new_output}")
5928 res = gdb.execute(f"""call (int)connect({new_fd}, {stack_addr}, {16})""", to_string=True)
5929 if res is None:
5930 err("Call to `connect` failed")
5931 return
5933 # recover stack state
5934 gef.memory.write(stack_addr, original_contents, 8)
5936 res = self.get_fd_from_result(res)
5937 if res == -1:
5938 err(f"Failed to connect to {address}:{port}")
5939 return
5941 info(f"Connected to {new_output}")
5942 else:
5943 res = gdb.execute(f"""call (int)open("{new_output}", 66, 0666)""", to_string=True)
5944 if res is None:
5945 err("Call to `open` failed")
5946 return
5947 new_fd = self.get_fd_from_result(res)
5949 info(f"Opened '{new_output}' as fd #{new_fd:d}")
5950 gdb.execute(f"""call (int)dup2({new_fd:d}, {old_fd:d})""", to_string=True)
5951 info(f"Duplicated fd #{new_fd:d}{RIGHT_ARROW}#{old_fd:d}")
5952 gdb.execute(f"""call (int)close({new_fd:d})""", to_string=True)
5953 info(f"Closed extra fd #{new_fd:d}")
5954 ok("Success")
5955 return
5957 def get_fd_from_result(self, res: str) -> int:
5958 # Output example: $1 = 3
5959 res = gdb.execute(f"p/d {int(res.split()[2], 0)}", to_string=True) or ""
5960 return int(res.split()[2], 0)
5963@register
5964class ScanSectionCommand(GenericCommand):
5965 """Search for addresses that are located in a memory mapping (haystack) that belonging
5966 to another (needle)."""
5968 _cmdline_ = "scan"
5969 _syntax_ = f"{_cmdline_} HAYSTACK NEEDLE"
5970 _aliases_ = ["lookup",]
5971 _example_ = f"\n{_cmdline_} stack libc"
5973 @only_if_gdb_running
5974 def do_invoke(self, argv: list[str]) -> None:
5975 if len(argv) != 2: 5975 ↛ 5976line 5975 didn't jump to line 5976 because the condition on line 5975 was never true
5976 self.usage()
5977 return
5979 haystack = argv[0]
5980 needle = argv[1]
5982 info(f"Searching for addresses in '{Color.yellowify(haystack)}' "
5983 f"that point to '{Color.yellowify(needle)}'")
5985 fpath = get_filepath() or ""
5987 if haystack == "binary":
5988 haystack = fpath
5990 if needle == "binary": 5990 ↛ 5991line 5990 didn't jump to line 5991 because the condition on line 5990 was never true
5991 needle = fpath
5993 needle_sections = []
5994 haystack_sections = []
5996 if "0x" in haystack: 5996 ↛ 5997line 5996 didn't jump to line 5997 because the condition on line 5996 was never true
5997 start, end = parse_string_range(haystack)
5998 haystack_sections.append((start, end, ""))
6000 if "0x" in needle: 6000 ↛ 6001line 6000 didn't jump to line 6001 because the condition on line 6000 was never true
6001 start, end = parse_string_range(needle)
6002 needle_sections.append((start, end))
6004 for sect in gef.memory.maps:
6005 if sect.path is None: 6005 ↛ 6006line 6005 didn't jump to line 6006 because the condition on line 6005 was never true
6006 continue
6007 if haystack in sect.path:
6008 haystack_sections.append((sect.page_start, sect.page_end, os.path.basename(sect.path)))
6009 if needle in sect.path:
6010 needle_sections.append((sect.page_start, sect.page_end))
6012 step = gef.arch.ptrsize
6013 unpack = u32 if step == 4 else u64
6015 dereference_cmd = gef.gdb.commands["dereference"]
6016 assert isinstance(dereference_cmd, DereferenceCommand)
6018 for hstart, hend, hname in haystack_sections:
6019 try:
6020 mem = gef.memory.read(hstart, hend - hstart)
6021 except gdb.MemoryError:
6022 continue
6024 for i in range(0, len(mem), step):
6025 target = unpack(mem[i:i+step])
6026 for nstart, nend in needle_sections:
6027 if target >= nstart and target < nend:
6028 deref = dereference_cmd.pprint_dereferenced(hstart, int(i / step))
6029 if hname != "": 6029 ↛ 6033line 6029 didn't jump to line 6033 because the condition on line 6029 was always true
6030 name = Color.colorify(hname, "yellow")
6031 gef_print(f"{name}: {deref}")
6032 else:
6033 gef_print(f" {deref}")
6035 return
6038@register
6039class SearchPatternCommand(GenericCommand):
6040 """SearchPatternCommand: search a pattern in memory. If given an hex value (starting with 0x)
6041 the command will also try to look for upwards cross-references to this address."""
6043 _cmdline_ = "search-pattern"
6044 _syntax_ = f"{_cmdline_} PATTERN [little|big] [section]"
6045 _aliases_ = ["grep", "xref"]
6046 _example_ = [f"{_cmdline_} AAAAAAAA",
6047 f"{_cmdline_} 0x555555554000 little stack",
6048 f"{_cmdline_} AAAA 0x600000-0x601000",
6049 f"{_cmdline_} --regex 0x401000 0x401500 ([\\\\x20-\\\\x7E]{ 2,} )(?=\\\\x00) <-- It matches null-end-printable(from x20-x7e) C strings (min size 2 bytes)"]
6051 def __init__(self) -> None:
6052 super().__init__()
6053 self["max_size_preview"] = (10, "max size preview of bytes")
6054 self["nr_pages_chunk"] = (0x400, "number of pages readed for each memory read chunk")
6055 return
6057 def print_section(self, section: Section) -> None:
6058 title = "In "
6059 if section.path: 6059 ↛ 6062line 6059 didn't jump to line 6062 because the condition on line 6059 was always true
6060 title += f"'{Color.blueify(section.path)}'"
6062 title += f"({section.page_start:#x}-{section.page_end:#x})"
6063 title += f", permission={section.permission}"
6064 ok(title)
6065 return
6067 def print_loc(self, loc: tuple[int, int, str]) -> None:
6068 gef_print(f""" {loc[0]:#x} - {loc[1]:#x} {RIGHT_ARROW} "{Color.pinkify(loc[2])}" """)
6069 return
6071 def search_pattern_by_address(self, pattern: str, start_address: int, end_address: int) -> list[tuple[int, int, str]]:
6072 """Search a pattern within a range defined by arguments."""
6073 _pattern = gef_pybytes(pattern)
6074 step = self["nr_pages_chunk"] * gef.session.pagesize
6075 locations = []
6077 for chunk_addr in range(start_address, end_address, step):
6078 if chunk_addr + step > end_address: 6078 ↛ 6081line 6078 didn't jump to line 6081 because the condition on line 6078 was always true
6079 chunk_size = end_address - chunk_addr
6080 else:
6081 chunk_size = step
6083 try:
6084 mem = gef.memory.read(chunk_addr, chunk_size)
6085 except gdb.MemoryError:
6086 return []
6088 for match in re.finditer(_pattern, mem):
6089 start = chunk_addr + match.start()
6090 ustr = ""
6091 if is_ascii_string(start): 6091 ↛ 6095line 6091 didn't jump to line 6095 because the condition on line 6091 was always true
6092 ustr = gef.memory.read_ascii_string(start) or ""
6093 end = start + len(ustr)
6094 else:
6095 ustr = gef_pystring(_pattern) + "[...]"
6096 end = start + len(_pattern)
6097 locations.append((start, end, ustr))
6099 del mem
6101 return locations
6103 def search_binpattern_by_address(self, binpattern: bytes, start_address: int, end_address: int) -> list[tuple[int, int, str]]:
6104 """Search a binary pattern within a range defined by arguments."""
6106 step = self["nr_pages_chunk"] * gef.session.pagesize
6107 locations = []
6109 for chunk_addr in range(start_address, end_address, step):
6110 if chunk_addr + step > end_address: 6110 ↛ 6113line 6110 didn't jump to line 6113 because the condition on line 6110 was always true
6111 chunk_size = end_address - chunk_addr
6112 else:
6113 chunk_size = step
6115 try:
6116 mem = gef.memory.read(chunk_addr, chunk_size)
6117 except gdb.MemoryError:
6118 return []
6119 preview_size = self["max_size_preview"]
6120 preview = ""
6121 for match in re.finditer(binpattern, mem):
6122 start = chunk_addr + match.start()
6123 preview = str(mem[slice(*match.span())][0:preview_size]) + "..."
6124 size_match = match.span()[1] - match.span()[0]
6125 if size_match > 0: 6125 ↛ 6127line 6125 didn't jump to line 6127 because the condition on line 6125 was always true
6126 size_match -= 1
6127 end = start + size_match
6128 locations.append((start, end, preview))
6130 del mem
6132 return locations
6134 def search_pattern(self, pattern: str, section_name: str) -> None:
6135 """Search a pattern within the whole userland memory."""
6136 for section in gef.memory.maps:
6137 if not section.permission & Permission.READ: continue
6138 if section.path == "[vvar]": continue
6139 if section_name not in section.path: continue 6139 ↛ 6136line 6139 didn't jump to line 6136 because the continue on line 6139 wasn't executed
6141 start = section.page_start
6142 end = section.page_end - 1
6143 old_section = None
6145 for loc in self.search_pattern_by_address(pattern, start, end):
6146 addr_loc_start = lookup_address(loc[0])
6147 if addr_loc_start and addr_loc_start.section: 6147 ↛ 6152line 6147 didn't jump to line 6152 because the condition on line 6147 was always true
6148 if old_section != addr_loc_start.section: 6148 ↛ 6152line 6148 didn't jump to line 6152 because the condition on line 6148 was always true
6149 self.print_section(addr_loc_start.section)
6150 old_section = addr_loc_start.section
6152 self.print_loc(loc)
6153 return
6155 @only_if_gdb_running
6156 def do_invoke(self, argv: list[str]) -> None:
6157 argc = len(argv)
6158 if argc < 1: 6158 ↛ 6159line 6158 didn't jump to line 6159 because the condition on line 6158 was never true
6159 self.usage()
6160 return
6162 if argc > 3 and argv[0].startswith("--regex"):
6163 pattern = ' '.join(argv[3:])
6164 pattern = ast.literal_eval("b'" + pattern + "'")
6166 addr_start = parse_address(argv[1])
6167 addr_end = parse_address(argv[2])
6169 for loc in self.search_binpattern_by_address(pattern, addr_start, addr_end):
6170 self.print_loc(loc)
6172 return
6174 pattern = argv[0]
6175 endian = gef.arch.endianness
6177 if argc >= 2: 6177 ↛ 6178line 6177 didn't jump to line 6178 because the condition on line 6177 was never true
6178 if argv[1].lower() == "big": endian = Endianness.BIG_ENDIAN
6179 elif argv[1].lower() == "little": endian = Endianness.LITTLE_ENDIAN
6181 if is_hex(pattern): 6181 ↛ 6182line 6181 didn't jump to line 6182 because the condition on line 6181 was never true
6182 if endian == Endianness.BIG_ENDIAN:
6183 pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(2, len(pattern), 2)])
6184 else:
6185 pattern = "".join(["\\x" + pattern[i:i + 2] for i in range(len(pattern) - 2, 0, -2)])
6187 if argc == 3: 6187 ↛ 6188line 6187 didn't jump to line 6188 because the condition on line 6187 was never true
6188 info(f"Searching '{Color.yellowify(pattern)}' in {argv[2]}")
6190 if "0x" in argv[2]:
6191 start, end = parse_string_range(argv[2])
6193 loc = lookup_address(start)
6194 if loc.valid:
6195 self.print_section(loc.section)
6197 for loc in self.search_pattern_by_address(pattern, start, end):
6198 self.print_loc(loc)
6199 else:
6200 section_name = argv[2]
6201 if section_name == "binary":
6202 section_name = get_filepath() or ""
6204 self.search_pattern(pattern, section_name)
6205 else:
6206 info(f"Searching '{Color.yellowify(pattern)}' in memory")
6207 self.search_pattern(pattern, "")
6208 return
6211@register
6212class FlagsCommand(GenericCommand):
6213 """Edit flags in a human friendly way."""
6215 _cmdline_ = "edit-flags"
6216 _syntax_ = f"{_cmdline_} [(+|-|~)FLAGNAME ...]"
6217 _aliases_ = ["flags",]
6218 _example_ = (f"\n{_cmdline_}"
6219 f"\n{_cmdline_} +zero # sets ZERO flag")
6221 def do_invoke(self, argv: list[str]) -> None:
6222 if not gef.arch.flag_register: 6222 ↛ 6223line 6222 didn't jump to line 6223 because the condition on line 6222 was never true
6223 warn(f"The architecture {gef.arch.arch}:{gef.arch.mode} doesn't have flag register.")
6224 return
6226 for flag in argv:
6227 if len(flag) < 2: 6227 ↛ 6228line 6227 didn't jump to line 6228 because the condition on line 6227 was never true
6228 continue
6230 action = flag[0]
6231 name = flag[1:].lower()
6233 if action not in ("+", "-", "~"): 6233 ↛ 6234line 6233 didn't jump to line 6234 because the condition on line 6233 was never true
6234 err(f"Invalid action for flag '{flag}'")
6235 continue
6237 if name not in gef.arch.flags_table.values(): 6237 ↛ 6238line 6237 didn't jump to line 6238 because the condition on line 6237 was never true
6238 err(f"Invalid flag name '{flag[1:]}'")
6239 continue
6241 for off in gef.arch.flags_table:
6242 if gef.arch.flags_table[off] != name:
6243 continue
6244 old_flag = gef.arch.register(gef.arch.flag_register)
6245 if action == "+":
6246 new_flags = old_flag | (1 << off)
6247 elif action == "-":
6248 new_flags = old_flag & ~(1 << off)
6249 else:
6250 new_flags = old_flag ^ (1 << off)
6252 gdb.execute(f"set ({gef.arch.flag_register}) = {new_flags:#x}")
6254 gef_print(gef.arch.flag_register_to_human())
6255 return
6258@register
6259class RemoteCommand(GenericCommand):
6260 """GDB `target remote` command on steroids. This command will use the remote procfs to create
6261 a local copy of the execution environment, including the target binary and its libraries
6262 in the local temporary directory (the value by default is in `gef.config.tempdir`). Additionally, it
6263 will fetch all the /proc/PID/maps and loads all its information. If procfs is not available remotely, the command
6264 will likely fail. You can however still use the limited command provided by GDB `target remote`."""
6266 _cmdline_ = "gef-remote"
6267 _syntax_ = f"{_cmdline_} [OPTIONS] TARGET"
6268 _example_ = [f"{_cmdline_} localhost 1234",
6269 f"{_cmdline_} --pid 6789 localhost 1234",
6270 f"{_cmdline_} --qemu-user --qemu-binary /bin/debugme localhost 4444 "]
6272 def __init__(self) -> None:
6273 super().__init__(prefix=False)
6274 return
6276 @parse_arguments({"host": "", "port": 0}, {"--pid": -1, "--qemu-user": False, "--qemu-binary": ""})
6277 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6278 if gef.session.remote is not None: 6278 ↛ 6279line 6278 didn't jump to line 6279 because the condition on line 6278 was never true
6279 err("You already are in remote session. Close it first before opening a new one...")
6280 return
6282 # argument check
6283 args : argparse.Namespace = kwargs["arguments"]
6284 if not args.host or not args.port: 6284 ↛ 6285line 6284 didn't jump to line 6285 because the condition on line 6284 was never true
6285 err("Missing parameters")
6286 return
6288 # qemu-user support
6289 qemu_binary: pathlib.Path | None = None
6290 if args.qemu_user:
6291 try:
6292 qemu_binary = pathlib.Path(args.qemu_binary).expanduser().absolute() if args.qemu_binary else gef.session.file
6293 if not qemu_binary or not qemu_binary.exists(): 6293 ↛ 6294line 6293 didn't jump to line 6294 because the condition on line 6293 was never true
6294 raise FileNotFoundError(f"{qemu_binary} does not exist")
6295 except Exception as e:
6296 err(f"Failed to initialize qemu-user mode, reason: {str(e)}")
6297 return
6299 # Try to establish the remote session, throw on error
6300 # Set `.remote_initializing` to True here - `GefRemoteSessionManager` invokes code which
6301 # calls `is_remote_debug` which checks if `remote_initializing` is True or `.remote` is None
6302 # This prevents some spurious errors being thrown during startup
6303 gef.session.remote_initializing = True
6304 session = GefRemoteSessionManager(args.host, args.port, args.pid, qemu_binary)
6306 dbg(f"[remote] initializing remote session with {session.target} under {session.root}")
6307 if not session.connect(args.pid) or not session.setup():
6308 gef.session.remote = None
6309 gef.session.remote_initializing = False
6310 raise EnvironmentError("Failed to setup remote target")
6312 gef.session.remote_initializing = False
6313 gef.session.remote = session
6314 reset_all_caches()
6315 gdb.execute("context")
6316 return
6319@register
6320class SkipiCommand(GenericCommand):
6321 """Skip N instruction(s) execution"""
6323 _cmdline_ = "skipi"
6324 _syntax_ = (f"{_cmdline_} [LOCATION] [--n NUM_INSTRUCTIONS]"
6325 "\n\tLOCATION\taddress/symbol from where to skip"
6326 "\t--n NUM_INSTRUCTIONS\tSkip the specified number of instructions instead of the default 1.")
6328 _example_ = [f"{_cmdline_}",
6329 f"{_cmdline_} --n 3",
6330 f"{_cmdline_} 0x69696969",
6331 f"{_cmdline_} 0x69696969 --n 6",]
6333 def __init__(self) -> None:
6334 super().__init__(complete=gdb.COMPLETE_LOCATION)
6335 return
6337 @only_if_gdb_running
6338 @parse_arguments({"address": "$pc"}, {"--n": 1})
6339 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6340 args : argparse.Namespace = kwargs["arguments"]
6341 address = parse_address(args.address)
6342 num_instructions = args.n
6344 last_insn = gef_instruction_n(address, num_instructions-1)
6345 total_bytes = (last_insn.address - address) + last_insn.size()
6346 target_addr = address + total_bytes
6348 info(f"skipping {num_instructions} instructions ({total_bytes} bytes) from {address:#x} to {target_addr:#x}")
6349 gdb.execute(f"set $pc = {target_addr:#x}")
6350 return
6353@register
6354class StepoverCommand(GenericCommand):
6355 """Breaks on the instruction immediately following this one. Ex: Step over call instruction"""
6357 _cmdline_ = "stepover"
6358 _syntax_ = (f"{_cmdline_}"
6359 "\n\tBreaks on the instruction immediately following this one. Ex: Step over call instruction.")
6360 _aliases_ = ["so",]
6361 _example_ = [f"{_cmdline_}",]
6363 def __init__(self) -> None:
6364 super().__init__(complete=gdb.COMPLETE_LOCATION)
6365 return
6367 @only_if_gdb_running
6368 def do_invoke(self, _: list[str]) -> None:
6369 target_addr = gef_next_instruction(parse_address("$pc")).address
6370 JustSilentStopBreakpoint("".join(["*", str(target_addr)]))
6371 gdb.execute("continue")
6372 return
6375@register
6376class NopCommand(GenericCommand):
6377 """Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture
6378 aware."""
6380 _cmdline_ = "nop"
6381 _syntax_ = (f"{_cmdline_} [LOCATION] [--i ITEMS] [--f] [--n] [--b]"
6382 "\n\tLOCATION\taddress/symbol to patch (by default this command replaces whole instructions)"
6383 "\t--i ITEMS\tnumber of items to insert (default 1)"
6384 "\t--f\tForce patch even when the selected settings could overwrite partial instructions"
6385 "\t--n\tInstead of replacing whole instructions, insert ITEMS nop instructions, no matter how many instructions it overwrites"
6386 "\t--b\tInstead of replacing whole instructions, fill ITEMS bytes with nops")
6387 _example_ = [f"{_cmdline_}",
6388 f"{_cmdline_} $pc+3",
6389 f"{_cmdline_} --i 2 $pc+3",
6390 f"{_cmdline_} --b",
6391 f"{_cmdline_} --b $pc+3",
6392 f"{_cmdline_} --f --b --i 2 $pc+3"
6393 f"{_cmdline_} --n --i 2 $pc+3",]
6395 def __init__(self) -> None:
6396 super().__init__(complete=gdb.COMPLETE_LOCATION)
6397 return
6399 @only_if_gdb_running
6400 @parse_arguments({"address": "$pc"}, {"--i": 1, "--b": False, "--f": False, "--n": False})
6401 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6402 args : argparse.Namespace = kwargs["arguments"]
6403 address = parse_address(args.address)
6404 nop = gef.arch.nop_insn
6405 num_items = int(args.i) or 1
6406 fill_bytes = bool(args.b)
6407 fill_nops = bool(args.n)
6408 force_flag = bool(args.f) or False
6410 if fill_nops and fill_bytes:
6411 err("--b and --n cannot be specified at the same time.")
6412 return
6414 total_bytes = 0
6415 if fill_bytes:
6416 total_bytes = num_items
6417 elif fill_nops:
6418 total_bytes = num_items * len(nop)
6419 else:
6420 try:
6421 last_insn = gef_instruction_n(address, num_items-1)
6422 last_addr = last_insn.address
6423 except Exception as e:
6424 err(f"Cannot patch instruction at {address:#x} reaching unmapped area, reason: {e}")
6425 return
6426 total_bytes = (last_addr - address) + gef_get_instruction_at(last_addr).size()
6428 if len(nop) > total_bytes or total_bytes % len(nop):
6429 warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-NOP "
6430 f"(byte nr {total_bytes % len(nop):#x}) broken and may cause a crash or "
6431 "break disassembly.")
6432 if not force_flag:
6433 warn("Use --f (force) to ignore this warning.")
6434 return
6436 target_end_address = address + total_bytes
6437 curr_ins = gef_current_instruction(address)
6438 while curr_ins.address + curr_ins.size() < target_end_address:
6439 if not Address(value=curr_ins.address + 1).valid:
6440 err(f"Cannot patch instruction at {address:#x}: reaching unmapped area")
6441 return
6442 curr_ins = gef_next_instruction(curr_ins.address)
6444 final_ins_end_addr = curr_ins.address + curr_ins.size()
6446 if final_ins_end_addr != target_end_address:
6447 warn(f"Patching {total_bytes} bytes at {address:#x} will result in LAST-INSTRUCTION "
6448 f"({curr_ins.address:#x}) being partial overwritten and may cause a crash or "
6449 "break disassembly.")
6450 if not force_flag:
6451 warn("Use --f (force) to ignore this warning.")
6452 return
6454 nops = bytearray(nop * total_bytes)
6455 end_address = Address(value=address + total_bytes - 1)
6456 if not end_address.valid: 6456 ↛ 6457line 6456 didn't jump to line 6457 because the condition on line 6456 was never true
6457 err(f"Cannot patch instruction at {address:#x}: reaching unmapped "
6458 f"area: {end_address:#x}")
6459 return
6461 ok(f"Patching {total_bytes} bytes from {address:#x}")
6462 gef.memory.write(address, nops, total_bytes)
6464 return
6467@register
6468class StubCommand(GenericCommand):
6469 """Stub out the specified function. This function is useful when needing to skip one
6470 function to be called and disrupt your runtime flow (ex. fork)."""
6472 _cmdline_ = "stub"
6473 _syntax_ = (f"{_cmdline_} [--retval RETVAL] [address]"
6474 "\taddress\taddress/symbol to stub out"
6475 "\t--retval RETVAL\tSet the return value")
6476 _example_ = f"{_cmdline_} --retval 0 fork"
6478 def __init__(self) -> None:
6479 super().__init__(complete=gdb.COMPLETE_LOCATION)
6480 return
6482 @only_if_gdb_running
6483 @parse_arguments({"address": ""}, {("-r", "--retval"): 0})
6484 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6485 args : argparse.Namespace = kwargs["arguments"]
6486 loc = args.address if args.address else f"*{gef.arch.pc:#x}"
6487 StubBreakpoint(loc, args.retval)
6488 return
6491@register
6492class GlibcHeapCommand(GenericCommand):
6493 """Base command to get information about the Glibc heap structure."""
6495 _cmdline_ = "heap"
6496 _syntax_ = f"{_cmdline_} (chunk|chunks|bins|arenas|set-arena)"
6498 def __init__(self) -> None:
6499 super().__init__(prefix=True)
6500 return
6502 @only_if_gdb_running
6503 def do_invoke(self, _: list[str]) -> None:
6504 self.usage()
6505 return
6508@register
6509class GlibcHeapSetArenaCommand(GenericCommand):
6510 """Set the address of the main_arena or the currently selected arena."""
6512 _cmdline_ = "heap set-arena"
6513 _syntax_ = f"{_cmdline_} [address|&symbol]"
6514 _example_ = f"{_cmdline_} 0x001337001337"
6516 def __init__(self) -> None:
6517 super().__init__(complete=gdb.COMPLETE_LOCATION)
6518 return
6520 @only_if_gdb_running
6521 @parse_arguments({"addr": ""}, {"--reset": False})
6522 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6523 global gef
6525 args: argparse.Namespace = kwargs["arguments"]
6527 if args.reset: 6527 ↛ 6528line 6527 didn't jump to line 6528 because the condition on line 6527 was never true
6528 gef.heap.reset_caches()
6529 return
6531 if not args.addr: 6531 ↛ 6532line 6531 didn't jump to line 6532 because the condition on line 6531 was never true
6532 ok(f"Current arena set to: '{gef.heap.selected_arena}'")
6533 return
6535 try:
6536 new_arena_address = parse_address(args.addr)
6537 except gdb.error:
6538 err("Invalid symbol for arena")
6539 return
6541 new_arena = GlibcArena( f"*{new_arena_address:#x}")
6542 if new_arena in gef.heap.arenas: 6542 ↛ 6547line 6542 didn't jump to line 6547 because the condition on line 6542 was always true
6543 # if entered arena is in arena list then just select it
6544 gef.heap.selected_arena = new_arena
6545 else:
6546 # otherwise set the main arena to the entered arena
6547 gef.heap.main_arena = new_arena
6548 return
6551@register
6552class GlibcHeapArenaCommand(GenericCommand):
6553 """Display information on a heap chunk."""
6555 _cmdline_ = "heap arenas"
6556 _syntax_ = _cmdline_
6558 @only_if_gdb_running
6559 def do_invoke(self, _: list[str]) -> None:
6560 for arena in gef.heap.arenas:
6561 gef_print(str(arena))
6562 return
6565@register
6566class GlibcHeapChunkCommand(GenericCommand):
6567 """Display information on a heap chunk.
6568 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123."""
6570 _cmdline_ = "heap chunk"
6571 _syntax_ = f"{_cmdline_} [-h] [--allow-unaligned] [--number] address"
6573 def __init__(self) -> None:
6574 super().__init__(complete=gdb.COMPLETE_LOCATION)
6575 return
6577 @parse_arguments({"address": ""}, {"--allow-unaligned": False, "--number": 1})
6578 @only_if_gdb_running
6579 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6580 args : argparse.Namespace = kwargs["arguments"]
6581 if not args.address: 6581 ↛ 6582line 6581 didn't jump to line 6582 because the condition on line 6581 was never true
6582 err("Missing chunk address")
6583 self.usage()
6584 return
6586 addr = parse_address(args.address)
6587 current_chunk = GlibcChunk(addr, allow_unaligned=args.allow_unaligned)
6589 if args.number > 1:
6590 for _i in range(args.number): 6590 ↛ 6606line 6590 didn't jump to line 6606 because the loop on line 6590 didn't complete
6591 if current_chunk.size == 0: 6591 ↛ 6592line 6591 didn't jump to line 6592 because the condition on line 6591 was never true
6592 break
6594 gef_print(str(current_chunk))
6595 next_chunk_addr = current_chunk.get_next_chunk_addr()
6596 if not Address(value=next_chunk_addr).valid:
6597 break
6599 next_chunk = current_chunk.get_next_chunk()
6600 if next_chunk is None: 6600 ↛ 6601line 6600 didn't jump to line 6601 because the condition on line 6600 was never true
6601 break
6603 current_chunk = next_chunk
6604 else:
6605 gef_print(current_chunk.psprint())
6606 return
6609class GlibcHeapChunkSummary:
6610 def __init__(self, desc = ""):
6611 self.desc = desc
6612 self.count = 0
6613 self.total_bytes = 0
6615 def process_chunk(self, chunk: GlibcChunk) -> None:
6616 self.count += 1
6617 self.total_bytes += chunk.size
6620class GlibcHeapArenaSummary:
6621 def __init__(self, resolve_type = False) -> None:
6622 self.resolve_symbol = resolve_type
6623 self.size_distribution = {}
6624 self.flag_distribution = {
6625 "PREV_INUSE": GlibcHeapChunkSummary(),
6626 "IS_MMAPPED": GlibcHeapChunkSummary(),
6627 "NON_MAIN_ARENA": GlibcHeapChunkSummary()
6628 }
6630 def process_chunk(self, chunk: GlibcChunk) -> None:
6631 chunk_type = "" if not self.resolve_symbol else chunk.resolve_type()
6633 per_size_summary = self.size_distribution.get((chunk.size, chunk_type), None)
6634 if per_size_summary is None: 6634 ↛ 6637line 6634 didn't jump to line 6637 because the condition on line 6634 was always true
6635 per_size_summary = GlibcHeapChunkSummary(desc=chunk_type)
6636 self.size_distribution[(chunk.size, chunk_type)] = per_size_summary
6637 per_size_summary.process_chunk(chunk)
6639 if chunk.has_p_bit(): 6639 ↛ 6641line 6639 didn't jump to line 6641 because the condition on line 6639 was always true
6640 self.flag_distribution["PREV_INUSE"].process_chunk(chunk)
6641 if chunk.has_m_bit(): 6641 ↛ 6642line 6641 didn't jump to line 6642 because the condition on line 6641 was never true
6642 self.flag_distribution["IS_MAPPED"].process_chunk(chunk)
6643 if chunk.has_n_bit(): 6643 ↛ 6644line 6643 didn't jump to line 6644 because the condition on line 6643 was never true
6644 self.flag_distribution["NON_MAIN_ARENA"].process_chunk(chunk)
6646 def print(self) -> None:
6647 gef_print("== Chunk distribution by size ==")
6648 gef_print(f"{'ChunkBytes':<10s}\t{'Count':<10s}\t{'TotalBytes':15s}\t{'Description':s}")
6649 for chunk_info, chunk_summary in sorted(self.size_distribution.items(), key=lambda x: x[1].total_bytes, reverse=True):
6650 gef_print(f"{chunk_info[0]:<10d}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<15d}\t{chunk_summary.desc:s}")
6652 gef_print("\n== Chunk distribution by flag ==")
6653 gef_print(f"{'Flag':<15s}\t{'TotalCount':<10s}\t{'TotalBytes':s}")
6654 for chunk_flag, chunk_summary in self.flag_distribution.items():
6655 gef_print(f"{chunk_flag:<15s}\t{chunk_summary.count:<10d}\t{chunk_summary.total_bytes:<d}")
6657class GlibcHeapWalkContext:
6658 def __init__(self, print_arena: bool = False, allow_unaligned: bool = False, min_size: int = 0, max_size: int = 0, count: int = -1, resolve_type: bool = False, summary: bool = False) -> None:
6659 self.print_arena = print_arena
6660 self.allow_unaligned = allow_unaligned
6661 self.min_size = min_size
6662 self.max_size = max_size
6663 self.remaining_chunk_count = count
6664 self.summary = summary
6665 self.resolve_type = resolve_type
6667@register
6668class GlibcHeapChunksCommand(GenericCommand):
6669 """Display all heap chunks for the current arena. As an optional argument
6670 the base address of a different arena can be passed"""
6672 _cmdline_ = "heap chunks"
6673 _syntax_ = f"{_cmdline_} [-h] [--all] [--allow-unaligned] [--summary] [--min-size MIN_SIZE] [--max-size MAX_SIZE] [--count COUNT] [--resolve] [arena_address]"
6674 _example_ = (f"\n{_cmdline_}"
6675 f"\n{_cmdline_} 0x555555775000")
6677 def __init__(self) -> None:
6678 super().__init__(complete=gdb.COMPLETE_LOCATION)
6679 self["peek_nb_byte"] = (16, "Hexdump N first byte(s) inside the chunk data (0 to disable)")
6680 return
6682 @parse_arguments({"arena_address": ""}, {("--all", "-a"): False, "--allow-unaligned": False, "--min-size": 0, "--max-size": 0, ("--count", "-n"): -1, ("--summary", "-s"): False, "--resolve": False})
6683 @only_if_gdb_running
6684 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
6685 args = kwargs["arguments"]
6686 ctx = GlibcHeapWalkContext(print_arena=args.all, allow_unaligned=args.allow_unaligned, min_size=args.min_size, max_size=args.max_size, count=args.count, resolve_type=args.resolve, summary=args.summary)
6687 if args.all or not args.arena_address:
6688 for arena in gef.heap.arenas: 6688 ↛ 6692line 6688 didn't jump to line 6692 because the loop on line 6688 didn't complete
6689 self.dump_chunks_arena(arena, ctx)
6690 if not args.all: 6690 ↛ 6688line 6690 didn't jump to line 6688 because the condition on line 6690 was always true
6691 return
6692 try:
6693 if not args.arena_address: 6693 ↛ 6694line 6693 didn't jump to line 6694 because the condition on line 6693 was never true
6694 return
6695 arena_addr = parse_address(args.arena_address)
6696 arena = GlibcArena(f"*{arena_addr:#x}")
6697 self.dump_chunks_arena(arena, ctx)
6698 except gdb.error as e:
6699 err(f"Invalid arena: {e}\nArena Address: {args.arena_address}")
6700 return
6702 def dump_chunks_arena(self, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> None:
6703 heap_addr = arena.heap_addr(allow_unaligned=ctx.allow_unaligned)
6704 if heap_addr is None: 6704 ↛ 6705line 6704 didn't jump to line 6705 because the condition on line 6704 was never true
6705 err("Could not find heap for arena")
6706 return
6707 if ctx.print_arena: 6707 ↛ 6708line 6707 didn't jump to line 6708 because the condition on line 6707 was never true
6708 gef_print(str(arena))
6709 if arena.is_main_arena():
6710 heap_end = arena.top + GlibcChunk(arena.top, from_base=True).size
6711 self.dump_chunks_heap(heap_addr, heap_end, arena, ctx)
6712 else:
6713 heap_info_structs = arena.get_heap_info_list() or []
6714 for heap_info in heap_info_structs:
6715 if not self.dump_chunks_heap(heap_info.heap_start, heap_info.heap_end, arena, ctx): 6715 ↛ 6716line 6715 didn't jump to line 6716 because the condition on line 6715 was never true
6716 break
6717 return
6719 def dump_chunks_heap(self, start: int, end: int, arena: GlibcArena, ctx: GlibcHeapWalkContext) -> bool:
6720 nb = self["peek_nb_byte"]
6721 chunk_iterator = GlibcChunk(start, from_base=True, allow_unaligned=ctx.allow_unaligned)
6722 heap_summary = GlibcHeapArenaSummary(resolve_type=ctx.resolve_type)
6723 top_printed = False
6724 for chunk in chunk_iterator:
6725 heap_corrupted = chunk.base_address > end
6726 should_process = self.should_process_chunk(chunk, ctx)
6728 if not ctx.summary and chunk.base_address == arena.top:
6729 if should_process:
6730 gef_print(
6731 f"{chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}")
6732 top_printed = True
6733 break
6735 if heap_corrupted: 6735 ↛ 6736line 6735 didn't jump to line 6736 because the condition on line 6735 was never true
6736 err("Corrupted heap, cannot continue.")
6737 return False
6739 if not should_process:
6740 continue
6742 if ctx.remaining_chunk_count == 0:
6743 break
6745 if ctx.summary:
6746 heap_summary.process_chunk(chunk)
6747 else:
6748 line = str(chunk)
6749 if nb: 6749 ↛ 6751line 6749 didn't jump to line 6751 because the condition on line 6749 was always true
6750 line += f"\n [{hexdump(gef.memory.read(chunk.data_address, nb), nb, base=chunk.data_address)}]"
6751 gef_print(line)
6753 ctx.remaining_chunk_count -= 1
6755 if not top_printed and ctx.print_arena: 6755 ↛ 6756line 6755 didn't jump to line 6756 because the condition on line 6755 was never true
6756 top_chunk = GlibcChunk(arena.top, from_base=True, allow_unaligned=ctx.allow_unaligned)
6757 gef_print(f"{top_chunk!s} {LEFT_ARROW} {Color.greenify('top chunk')}")
6759 if ctx.summary:
6760 heap_summary.print()
6762 return True
6764 def should_process_chunk(self, chunk: GlibcChunk, ctx: GlibcHeapWalkContext) -> bool:
6765 if chunk.size < ctx.min_size:
6766 return False
6768 if 0 < ctx.max_size < chunk.size:
6769 return False
6771 return True
6774@register
6775class GlibcHeapBinsCommand(GenericCommand):
6776 """Display information on the bins on an arena (default: main_arena).
6777 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123."""
6779 _bin_types_ = ("tcache", "fast", "unsorted", "small", "large")
6780 _cmdline_ = "heap bins"
6781 _syntax_ = f"{_cmdline_} [{'|'.join(_bin_types_)}]"
6783 def __init__(self) -> None:
6784 super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION)
6785 return
6787 @only_if_gdb_running
6788 def do_invoke(self, argv: list[str]) -> None:
6789 if not argv:
6790 for bin_t in self._bin_types_:
6791 gdb.execute(f"heap bins {bin_t}")
6792 return
6794 bin_t = argv[0]
6795 if bin_t not in self._bin_types_:
6796 self.usage()
6797 return
6799 gdb.execute(f"heap bins {bin_t}")
6800 return
6802 def pprint_bin(self, arena_addr: str, index: int, _type: str = "") -> int:
6803 arena = GlibcArena(arena_addr)
6805 fd, bk = arena.bin(index)
6806 if (fd, bk) == (0x00, 0x00): 6806 ↛ 6807line 6806 didn't jump to line 6807 because the condition on line 6806 was never true
6807 warn("Invalid backward and forward bin pointers(fw==bk==NULL)")
6808 return -1
6810 if _type == "tcache": 6810 ↛ 6811line 6810 didn't jump to line 6811 because the condition on line 6810 was never true
6811 chunkClass = GlibcTcacheChunk
6812 elif _type == "fast": 6812 ↛ 6813line 6812 didn't jump to line 6813 because the condition on line 6812 was never true
6813 chunkClass = GlibcFastChunk
6814 else:
6815 chunkClass = GlibcChunk
6817 nb_chunk = 0
6818 head = chunkClass(bk, from_base=True).fd
6819 if fd == head:
6820 return nb_chunk
6822 ok(f"{_type}bins[{index:d}]: fw={fd:#x}, bk={bk:#x}")
6824 m = []
6825 while fd != head:
6826 chunk = chunkClass(fd, from_base=True)
6827 m.append(f"{RIGHT_ARROW} {chunk!s}")
6828 fd = chunk.fd
6829 nb_chunk += 1
6831 if m: 6831 ↛ 6833line 6831 didn't jump to line 6833 because the condition on line 6831 was always true
6832 gef_print(" ".join(m))
6833 return nb_chunk
6836@register
6837class GlibcHeapTcachebinsCommand(GenericCommand):
6838 """Display information on the Tcachebins on an arena (default: main_arena).
6839 See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc."""
6841 _cmdline_ = "heap bins tcache"
6842 _syntax_ = f"{_cmdline_} [all] [thread_ids...]"
6844 TCACHE_MAX_BINS = 0x40
6846 def __init__(self) -> None:
6847 super().__init__(complete=gdb.COMPLETE_LOCATION)
6848 return
6850 @only_if_gdb_running
6851 def do_invoke(self, argv: list[str]) -> None:
6852 # Determine if we are using libc with tcache built in (2.26+)
6853 if gef.libc.version and gef.libc.version < (2, 26): 6853 ↛ 6854line 6853 didn't jump to line 6854 because the condition on line 6853 was never true
6854 info("No Tcache in this version of libc")
6855 return
6857 current_thread = gdb.selected_thread()
6858 if current_thread is None: 6858 ↛ 6859line 6858 didn't jump to line 6859 because the condition on line 6858 was never true
6859 err("Couldn't find current thread")
6860 return
6862 # As a nicety, we want to display threads in ascending order by gdb number
6863 threads = sorted(gdb.selected_inferior().threads(), key=lambda t: t.num)
6864 if argv:
6865 if "all" in argv: 6865 ↛ 6868line 6865 didn't jump to line 6868 because the condition on line 6865 was always true
6866 tids = [t.num for t in threads]
6867 else:
6868 tids = self.check_thread_ids([int(a) for a in argv])
6869 else:
6870 tids = [current_thread.num]
6872 for thread in threads:
6873 if thread.num not in tids:
6874 continue
6876 thread.switch()
6878 tcache_addr = self.find_tcache()
6879 if tcache_addr == 0: 6879 ↛ 6880line 6879 didn't jump to line 6880 because the condition on line 6879 was never true
6880 info(f"Uninitialized tcache for thread {thread.num:d}")
6881 continue
6883 gef_print(titlify(f"Tcachebins for thread {thread.num:d}"))
6884 tcache_empty = True
6885 for i in range(self.TCACHE_MAX_BINS):
6886 chunk, count = self.tcachebin(tcache_addr, i)
6887 chunks = set()
6888 msg = []
6889 chunk_size = 0
6891 # Only print the entry if there are valid chunks. Don't trust count
6892 while True:
6893 if chunk is None:
6894 break
6896 try:
6897 msg.append(f"{LEFT_ARROW} {chunk!s} ")
6898 if not chunk_size:
6899 chunk_size = chunk.usable_size
6901 if chunk.data_address in chunks: 6901 ↛ 6902line 6901 didn't jump to line 6902 because the condition on line 6901 was never true
6902 msg.append(f"{RIGHT_ARROW} [loop detected]")
6903 break
6905 chunks.add(chunk.data_address)
6907 next_chunk = chunk.fd
6908 if next_chunk == 0:
6909 break
6911 chunk = GlibcTcacheChunk(next_chunk)
6912 except gdb.MemoryError:
6913 msg.append(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]")
6914 break
6916 if msg:
6917 tcache_empty = False
6918 tidx = gef.heap.csize2tidx(chunk_size)
6919 size = gef.heap.tidx2size(tidx)
6920 count = len(chunks)
6921 gef_print(f"Tcachebins[idx={tidx:d}, size={size:#x}, count={count}]", end="")
6922 gef_print("".join(msg))
6924 if tcache_empty:
6925 gef_print("All tcachebins are empty")
6927 current_thread.switch()
6928 return
6930 def find_tcache(self) -> int:
6931 """Return the location of the current thread's tcache."""
6932 try:
6933 # For multithreaded binaries, the tcache symbol (in thread local
6934 # storage) will give us the correct address.
6935 tcache_addr = parse_address("(void *) tcache")
6936 except gdb.error:
6937 # In binaries not linked with pthread (and therefore there is only
6938 # one thread), we can't use the tcache symbol, but we can guess the
6939 # correct address because the tcache is consistently the first
6940 # allocation in the main arena.
6941 heap_base = gef.heap.base_address
6942 if heap_base is None:
6943 err("No heap section")
6944 return 0x0
6945 tcache_addr = heap_base + 0x10
6946 return tcache_addr
6948 def check_thread_ids(self, tids: list[int]) -> list[int]:
6949 """Return the subset of tids that are currently valid."""
6950 existing_tids = set(t.num for t in gdb.selected_inferior().threads())
6951 return list(set(tids) & existing_tids)
6953 def tcachebin(self, tcache_base: int, i: int) -> tuple[GlibcTcacheChunk | None, int]:
6954 """Return the head chunk in tcache[i] and the number of chunks in the bin."""
6955 if i >= self.TCACHE_MAX_BINS: 6955 ↛ 6956line 6955 didn't jump to line 6956 because the condition on line 6955 was never true
6956 err("Incorrect index value, index value must be between 0 and "
6957 f"{self.TCACHE_MAX_BINS}-1, given {i}"
6958 )
6959 return None, 0
6961 tcache_chunk = GlibcTcacheChunk(tcache_base)
6963 # Glibc changed the size of the tcache in version 2.30; this fix has
6964 # been backported inconsistently between distributions. We detect the
6965 # difference by checking the size of the allocated chunk for the
6966 # tcache.
6967 # Minimum usable size of allocated tcache chunk = ?
6968 # For new tcache:
6969 # TCACHE_MAX_BINS * _2_ + TCACHE_MAX_BINS * ptrsize
6970 # For old tcache:
6971 # TCACHE_MAX_BINS * _1_ + TCACHE_MAX_BINS * ptrsize
6972 new_tcache_min_size = (
6973 self.TCACHE_MAX_BINS * 2 +
6974 self.TCACHE_MAX_BINS * gef.arch.ptrsize)
6976 if tcache_chunk.usable_size < new_tcache_min_size: 6976 ↛ 6977line 6976 didn't jump to line 6977 because the condition on line 6976 was never true
6977 tcache_count_size = 1
6978 count = ord(gef.memory.read(tcache_base + tcache_count_size*i, 1))
6979 else:
6980 tcache_count_size = 2
6981 count = u16(gef.memory.read(tcache_base + tcache_count_size*i, 2))
6983 chunk = dereference(tcache_base + tcache_count_size*self.TCACHE_MAX_BINS + i*gef.arch.ptrsize)
6984 chunk = GlibcTcacheChunk(int(chunk)) if chunk else None
6985 return chunk, count
6988@register
6989class GlibcHeapFastbinsYCommand(GenericCommand):
6990 """Display information on the fastbinsY on an arena (default: main_arena).
6991 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123."""
6993 _cmdline_ = "heap bins fast"
6994 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]"
6996 def __init__(self) -> None:
6997 super().__init__(complete=gdb.COMPLETE_LOCATION)
6998 return
7000 @parse_arguments({"arena_address": ""}, {})
7001 @only_if_gdb_running
7002 def do_invoke(self, *_: Any, **kwargs: Any) -> None:
7003 def fastbin_index(sz: int) -> int:
7004 return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2
7006 args : argparse.Namespace = kwargs["arguments"]
7007 if not gef.heap.main_arena: 7007 ↛ 7008line 7007 didn't jump to line 7008 because the condition on line 7007 was never true
7008 err("Heap not initialized")
7009 return
7011 SIZE_SZ = gef.arch.ptrsize
7012 MAX_FAST_SIZE = 80 * SIZE_SZ // 4
7013 NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1
7015 arena = GlibcArena(f"*{args.arena_address}") if args.arena_address else gef.heap.selected_arena
7016 if arena is None: 7016 ↛ 7017line 7016 didn't jump to line 7017 because the condition on line 7016 was never true
7017 err("Invalid Glibc arena")
7018 return
7020 gef_print(titlify(f"Fastbins for arena at {arena.addr:#x}"))
7021 for i in range(NFASTBINS):
7022 gef_print(f"Fastbins[idx={i:d}, size={(i+2)*SIZE_SZ*2:#x}] ", end="")
7023 chunk = arena.fastbin(i)
7024 chunks = set()
7026 while True:
7027 if chunk is None:
7028 gef_print("0x00", end="")
7029 break
7031 try:
7032 gef_print(f"{LEFT_ARROW} {chunk!s} ", end="")
7033 if chunk.data_address in chunks: 7033 ↛ 7034line 7033 didn't jump to line 7034 because the condition on line 7033 was never true
7034 gef_print(f"{RIGHT_ARROW} [loop detected]", end="")
7035 break
7037 if fastbin_index(chunk.size) != i: 7037 ↛ 7038line 7037 didn't jump to line 7038 because the condition on line 7037 was never true
7038 gef_print("[incorrect fastbin_index] ", end="")
7040 chunks.add(chunk.data_address)
7042 next_chunk = chunk.fd
7043 if next_chunk == 0: 7043 ↛ 7046line 7043 didn't jump to line 7046 because the condition on line 7043 was always true
7044 break
7046 chunk = GlibcFastChunk(next_chunk, from_base=True)
7047 except gdb.MemoryError:
7048 gef_print(f"{LEFT_ARROW} [Corrupted chunk at {chunk.data_address:#x}]", end="")
7049 break
7050 gef_print()
7051 return
7054@register
7055class GlibcHeapUnsortedBinsCommand(GenericCommand):
7056 """Display information on the Unsorted Bins of an arena (default: main_arena).
7057 See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689."""
7059 _cmdline_ = "heap bins unsorted"
7060 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]"
7062 def __init__(self) -> None:
7063 super().__init__(complete=gdb.COMPLETE_LOCATION)
7064 return
7066 @parse_arguments({"arena_address": ""}, {})
7067 @only_if_gdb_running
7068 def do_invoke(self, *_: Any, **kwargs: Any) -> None:
7069 args : argparse.Namespace = kwargs["arguments"]
7070 if not gef.heap.main_arena or not gef.heap.selected_arena: 7070 ↛ 7071line 7070 didn't jump to line 7071 because the condition on line 7070 was never true
7071 err("Heap not initialized")
7072 return
7074 arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}"
7075 gef_print(titlify(f"Unsorted Bin for arena at {arena_addr}"))
7076 heap_bins_cmd = gef.gdb.commands["heap bins"]
7077 assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand)
7078 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_addr}", 0, "unsorted_")
7079 if nb_chunk >= 0: 7079 ↛ 7081line 7079 didn't jump to line 7081 because the condition on line 7079 was always true
7080 info(f"Found {nb_chunk:d} chunks in unsorted bin.")
7081 return
7084@register
7085class GlibcHeapSmallBinsCommand(GenericCommand):
7086 """Convenience command for viewing small bins."""
7088 _cmdline_ = "heap bins small"
7089 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]"
7091 def __init__(self) -> None:
7092 super().__init__(complete=gdb.COMPLETE_LOCATION)
7093 return
7095 @parse_arguments({"arena_address": ""}, {})
7096 @only_if_gdb_running
7097 def do_invoke(self, *_: Any, **kwargs: Any) -> None:
7098 args : argparse.Namespace = kwargs["arguments"]
7099 if not gef.heap.main_arena or not gef.heap.selected_arena: 7099 ↛ 7100line 7099 didn't jump to line 7100 because the condition on line 7099 was never true
7100 err("Heap not initialized")
7101 return
7103 arena_address = args.arena_address or f"{gef.heap.selected_arena.address:#x}"
7104 gef_print(titlify(f"Small Bins for arena at {arena_address}"))
7105 bins: dict[int, int] = {}
7106 heap_bins_cmd = gef.gdb.commands["heap bins"]
7107 assert isinstance (heap_bins_cmd, GlibcHeapBinsCommand)
7108 for i in range(1, 63):
7109 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_address}", i, "small_")
7110 if nb_chunk < 0: 7110 ↛ 7111line 7110 didn't jump to line 7111 because the condition on line 7110 was never true
7111 break
7112 if nb_chunk > 0:
7113 bins[i] = nb_chunk
7114 info(f"Found {sum(list(bins.values())):d} chunks in {len(bins):d} small non-empty bins.")
7115 return
7118@register
7119class GlibcHeapLargeBinsCommand(GenericCommand):
7120 """Convenience command for viewing large bins."""
7122 _cmdline_ = "heap bins large"
7123 _syntax_ = f"{_cmdline_} [ARENA_ADDRESS]"
7125 def __init__(self) -> None:
7126 super().__init__(complete=gdb.COMPLETE_LOCATION)
7127 return
7129 @parse_arguments({"arena_address": ""}, {})
7130 @only_if_gdb_running
7131 def do_invoke(self, *_: Any, **kwargs: Any) -> None:
7132 args : argparse.Namespace = kwargs["arguments"]
7133 if not gef.heap.main_arena or not gef.heap.selected_arena: 7133 ↛ 7134line 7133 didn't jump to line 7134 because the condition on line 7133 was never true
7134 err("Heap not initialized")
7135 return
7137 arena_addr = args.arena_address if args.arena_address else f"{gef.heap.selected_arena.addr:#x}"
7138 gef_print(titlify(f"Large Bins for arena at {arena_addr}"))
7139 bins = {}
7140 heap_bins_cmd = gef.gdb.commands["heap bins"]
7141 assert isinstance(heap_bins_cmd, GlibcHeapBinsCommand)
7142 for i in range(63, 126):
7143 nb_chunk = heap_bins_cmd.pprint_bin(f"*{arena_addr}", i, "large_")
7144 if nb_chunk < 0: 7144 ↛ 7145line 7144 didn't jump to line 7145 because the condition on line 7144 was never true
7145 break
7146 if nb_chunk > 0:
7147 bins[i] = nb_chunk
7148 info(f"Found {sum(bins.values()):d} chunks in {len(bins):d} large non-empty bins.")
7149 return
7152@register
7153class DetailRegistersCommand(GenericCommand):
7154 """Display full details on one, many or all registers value from current architecture."""
7156 _cmdline_ = "registers"
7157 _syntax_ = f"{_cmdline_} [[Register1][Register2] ... [RegisterN]]"
7158 _example_ = (f"\n{_cmdline_}"
7159 f"\n{_cmdline_} $eax $eip $esp")
7161 @only_if_gdb_running
7162 @parse_arguments({"registers": [""]}, {})
7163 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
7164 unchanged_color = gef.config["theme.registers_register_name"]
7165 changed_color = gef.config["theme.registers_value_changed"]
7166 string_color = gef.config["theme.dereference_string"]
7167 regs = gef.arch.all_registers
7169 args : argparse.Namespace = kwargs["arguments"]
7170 if args.registers and args.registers[0]:
7171 all_regs = set(gef.arch.all_registers)
7172 regs = [reg for reg in args.registers if reg in all_regs]
7173 invalid_regs = [reg for reg in args.registers if reg not in all_regs]
7174 if invalid_regs: 7174 ↛ 7175line 7174 didn't jump to line 7175 because the condition on line 7174 was never true
7175 err(f"invalid registers for architecture: {', '.join(invalid_regs)}")
7177 memsize = gef.arch.ptrsize
7178 endian = str(gef.arch.endianness)
7179 charset = string.printable
7180 widest = max(map(len, gef.arch.all_registers))
7181 special_line = ""
7183 for regname in regs:
7184 reg = gdb.parse_and_eval(regname)
7185 if reg.type.code == gdb.TYPE_CODE_VOID: 7185 ↛ 7186line 7185 didn't jump to line 7186 because the condition on line 7185 was never true
7186 continue
7188 padreg = regname.ljust(widest, " ")
7190 if str(reg) == "<unavailable>": 7190 ↛ 7191line 7190 didn't jump to line 7191 because the condition on line 7190 was never true
7191 gef_print(f"{Color.colorify(padreg, unchanged_color)}: "
7192 f"{Color.colorify('no value', 'yellow underline')}")
7193 continue
7195 value = align_address(int(reg))
7196 ctx_cmd = gef.gdb.commands["context"]
7197 assert isinstance(ctx_cmd, ContextCommand)
7198 old_value = ctx_cmd.old_registers.get(regname, 0)
7199 if value == old_value:
7200 color = unchanged_color
7201 else:
7202 color = changed_color
7204 # Special (e.g. segment) registers go on their own line
7205 if regname in gef.arch.special_registers:
7206 special_line += f"{Color.colorify(regname, color)}: "
7207 special_line += f"{gef.arch.register(regname):#04x} "
7208 continue
7210 line = f"{Color.colorify(padreg, color)}: "
7212 if regname == gef.arch.flag_register:
7213 line += gef.arch.flag_register_to_human()
7214 gef_print(line)
7215 continue
7217 addr = lookup_address(align_address(int(value)))
7218 if addr.valid:
7219 line += str(addr)
7220 else:
7221 line += format_address_spaces(value)
7222 addrs = dereference_from(value)
7224 if len(addrs) > 1:
7225 sep = f" {RIGHT_ARROW} "
7226 line += sep
7227 line += sep.join(addrs[1:])
7229 # check to see if reg value is ascii
7230 try:
7231 fmt = f"{endian}{'I' if memsize == 4 else 'Q'}"
7232 last_addr = int(addrs[-1], 16)
7233 val = gef_pystring(struct.pack(fmt, last_addr))
7234 if all([_ in charset for _ in val]):
7235 line += f" (\"{Color.colorify(val, string_color)}\"?)"
7236 except ValueError:
7237 pass
7239 gef_print(line)
7241 if special_line: 7241 ↛ 7243line 7241 didn't jump to line 7243 because the condition on line 7241 was always true
7242 gef_print(special_line)
7243 return
7246@register
7247class ShellcodeCommand(GenericCommand):
7248 """ShellcodeCommand uses @JonathanSalwan simple-yet-awesome shellcode API to
7249 download shellcodes."""
7251 _cmdline_ = "shellcode"
7252 _syntax_ = f"{_cmdline_} (search|get)"
7254 def __init__(self) -> None:
7255 super().__init__(prefix=True)
7256 return
7258 def do_invoke(self, _: list[str]) -> None:
7259 err("Missing sub-command (search|get)")
7260 self.usage()
7261 return
7264@register
7265class ShellcodeSearchCommand(GenericCommand):
7266 """Search pattern in shell-storm's shellcode database."""
7268 _cmdline_ = "shellcode search"
7269 _syntax_ = f"{_cmdline_} PATTERN1 PATTERN2"
7270 _aliases_ = ["sc-search",]
7272 api_base = "http://shell-storm.org"
7273 search_url = f"{api_base}/api/?s="
7275 def do_invoke(self, argv: list[str]) -> None:
7276 if not argv: 7276 ↛ 7277line 7276 didn't jump to line 7277 because the condition on line 7276 was never true
7277 err("Missing pattern to search")
7278 self.usage()
7279 return
7281 # API : http://shell-storm.org/shellcode/
7282 args = "*".join(argv)
7284 res = http_get(self.search_url + args)
7285 if res is None: 7285 ↛ 7286line 7285 didn't jump to line 7286 because the condition on line 7285 was never true
7286 err("Could not query search page")
7287 return
7289 ret = gef_pystring(res)
7291 # format: [author, OS/arch, cmd, id, link]
7292 lines = ret.split("\\n")
7293 refs = [line.split("::::") for line in lines]
7295 if refs: 7295 ↛ 7306line 7295 didn't jump to line 7306 because the condition on line 7295 was always true
7296 info("Showing matching shellcodes")
7297 info("\t".join(["Id", "Platform", "Description"]))
7298 for ref in refs:
7299 try:
7300 _, arch, cmd, sid, _ = ref
7301 gef_print("\t".join([sid, arch, cmd]))
7302 except ValueError:
7303 continue
7305 info("Use `shellcode get <id>` to fetch shellcode")
7306 return
7309@register
7310class ShellcodeGetCommand(GenericCommand):
7311 """Download shellcode from shell-storm's shellcode database."""
7313 _cmdline_ = "shellcode get"
7314 _syntax_ = f"{_cmdline_} SHELLCODE_ID"
7315 _aliases_ = ["sc-get",]
7317 api_base = "http://shell-storm.org"
7318 get_url = f"{api_base}/shellcode/files/shellcode-{ :d} .html"
7320 def do_invoke(self, argv: list[str]) -> None:
7321 if len(argv) != 1: 7321 ↛ 7322line 7321 didn't jump to line 7322 because the condition on line 7321 was never true
7322 err("Missing ID to download")
7323 self.usage()
7324 return
7326 if not argv[0].isdigit(): 7326 ↛ 7327line 7326 didn't jump to line 7327 because the condition on line 7326 was never true
7327 err("ID is not a number")
7328 self.usage()
7329 return
7331 self.get_shellcode(int(argv[0]))
7332 return
7334 def get_shellcode(self, sid: int) -> None:
7335 info(f"Downloading shellcode id={sid}")
7336 res = http_get(self.get_url.format(sid))
7337 if res is None:
7338 err(f"Failed to fetch shellcode #{sid}")
7339 return
7341 ok("Downloaded, written to disk...")
7342 with tempfile.NamedTemporaryFile(prefix="sc-", suffix=".txt", mode='w+b', delete=False, dir=gef.config["gef.tempdir"]) as fd:
7343 shellcode = res.split(b"<pre>")[1].split(b"</pre>")[0]
7344 shellcode = shellcode.replace(b""", b'"')
7345 fd.write(shellcode)
7346 ok(f"Shellcode written to '{fd.name}'")
7347 return
7350@register
7351class ProcessListingCommand(GenericCommand):
7352 """List and filter process. If a PATTERN is given as argument, results shown will be grepped
7353 by this pattern."""
7355 _cmdline_ = "process-search"
7356 _syntax_ = f"{_cmdline_} [-h] [--attach] [--smart-scan] [REGEX_PATTERN]"
7357 _aliases_ = ["ps"]
7358 _example_ = f"{_cmdline_} gdb.*"
7360 def __init__(self) -> None:
7361 super().__init__(complete=gdb.COMPLETE_LOCATION)
7362 self["ps_command"] = (f"{gef.session.constants['ps']} auxww", "`ps` command to get process information")
7363 return
7365 @parse_arguments({"pattern": ""}, {"--attach": False, "--smart-scan": False})
7366 def do_invoke(self, _: list, **kwargs: Any) -> None:
7367 args : argparse.Namespace = kwargs["arguments"]
7368 do_attach = args.attach
7369 smart_scan = args.smart_scan
7370 pattern = args.pattern
7371 pattern = re.compile("^.*$") if not args else re.compile(pattern)
7373 for process in self.get_processes():
7374 pid = int(process["pid"])
7375 command = process["command"]
7377 if not re.search(pattern, command):
7378 continue
7380 if smart_scan: 7380 ↛ 7381line 7380 didn't jump to line 7381 because the condition on line 7380 was never true
7381 if command.startswith("[") and command.endswith("]"): continue
7382 if command.startswith("socat "): continue
7383 if command.startswith("grep "): continue
7384 if command.startswith("gdb "): continue
7386 if args and do_attach: 7386 ↛ 7387line 7386 didn't jump to line 7387 because the condition on line 7386 was never true
7387 ok(f"Attaching to process='{process['command']}' pid={pid:d}")
7388 gdb.execute(f"attach {pid:d}")
7389 return None
7391 line = [process[i] for i in ("pid", "user", "cpu", "mem", "tty", "command")]
7392 gef_print("\t\t".join(line))
7394 return None
7396 def get_processes(self) -> Generator[dict[str, str], None, None]:
7397 output = gef_execute_external(self["ps_command"].split(), True)
7398 names = [x.lower().replace("%", "") for x in output[0].split()]
7400 for line in output[1:]:
7401 fields = line.split()
7402 t = {}
7404 for i, name in enumerate(names):
7405 if i == len(names) - 1:
7406 t[name] = " ".join(fields[i:])
7407 else:
7408 t[name] = fields[i]
7410 yield t
7412 return
7415@register
7416class ElfInfoCommand(GenericCommand):
7417 """Display a limited subset of ELF header information. If no argument is provided, the command will
7418 show information about the current ELF being debugged."""
7420 _cmdline_ = "elf-info"
7421 _syntax_ = f"{_cmdline_} [FILE]"
7422 _example_ = f"{_cmdline_} /bin/ls"
7424 def __init__(self) -> None:
7425 super().__init__(complete=gdb.COMPLETE_LOCATION)
7426 return
7428 @parse_arguments({}, {"--filename": ""})
7429 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
7430 args : argparse.Namespace = kwargs["arguments"]
7432 if is_qemu_system(): 7432 ↛ 7433line 7432 didn't jump to line 7433 because the condition on line 7432 was never true
7433 err("Unsupported")
7434 return
7436 filename = args.filename or get_filepath()
7437 if filename is None: 7437 ↛ 7438line 7437 didn't jump to line 7438 because the condition on line 7437 was never true
7438 return
7440 try:
7441 elf = Elf(filename)
7442 except ValueError:
7443 err(f"`{filename}` is an invalid value for ELF file")
7444 return
7446 data = [
7447 ("Magic", f"{hexdump(struct.pack('>I', elf.e_magic), show_raw=True)}"),
7448 ("Class", f"{elf.e_class.value:#x} - {elf.e_class.name}"),
7449 ("Endianness", f"{elf.e_endianness.value:#x} - {Endianness(elf.e_endianness).name}"),
7450 ("Version", f"{elf.e_eiversion:#x}"),
7451 ("OS ABI", f"{elf.e_osabi.value:#x} - {elf.e_osabi.name if elf.e_osabi else ''}"),
7452 ("ABI Version", f"{elf.e_abiversion:#x}"),
7453 ("Type", f"{elf.e_type.value:#x} - {elf.e_type.name}"),
7454 ("Machine", f"{elf.e_machine.value:#x} - {elf.e_machine.name}"),
7455 ("Program Header Table", f"{format_address(elf.e_phoff)}"),
7456 ("Section Header Table", f"{format_address(elf.e_shoff)}"),
7457 ("Header Table", f"{format_address(elf.e_phoff)}"),
7458 ("ELF Version", f"{elf.e_version:#x}"),
7459 ("Header size", f"{elf.e_ehsize} ({elf.e_ehsize:#x})"),
7460 ("Entry point", f"{format_address(elf.e_entry)}"),
7461 ]
7463 for title, content in data:
7464 gef_print(f"{Color.boldify(f'{title:<22}')}: {content}")
7466 gef_print("")
7467 gef_print(titlify("Program Header"))
7469 gef_print(f" [{'#':>2s}] {'Type':12s} {'Offset':>8s} {'Virtaddr':>10s} {'Physaddr':>10s}"
7470 f" {'FileSiz':>8s} {'MemSiz':>8s} {'Flags':5s} {'Align':>8s}")
7472 for i, p in enumerate(elf.phdrs):
7473 p_type = p.p_type.name if p.p_type else ""
7474 p_flags = str(p.p_flags.name).lstrip("Flag.") if p.p_flags else "???"
7476 gef_print(f" [{i:2d}] {p_type:12s} {p.p_offset:#8x} {p.p_vaddr:#10x} {p.p_paddr:#10x}"
7477 f" {p.p_filesz:#8x} {p.p_memsz:#8x} {p_flags:5s} {p.p_align:#8x}")
7479 gef_print("")
7480 gef_print(titlify("Section Header"))
7481 gef_print(f" [{'#':>2s}] {'Name':20s} {'Type':>15s} {'Address':>10s} {'Offset':>8s}"
7482 f" {'Size':>8s} {'EntSiz':>8s} {'Flags':5s} {'Link':4s} {'Info':4s} {'Align':>8s}")
7484 for i, s in enumerate(elf.shdrs):
7485 sh_type = s.sh_type.name if s.sh_type else "UNKN"
7486 sh_flags = str(s.sh_flags).lstrip("Flags.") if s.sh_flags else "UNKN"
7488 gef_print(f" [{i:2d}] {s.name:20s} {sh_type:>15s} {s.sh_addr:#10x} {s.sh_offset:#8x} "
7489 f"{s.sh_size:#8x} {s.sh_entsize:#8x} {sh_flags:5s} {s.sh_link:#4x} {s.sh_info:#4x} {s.sh_addralign:#8x}")
7490 return
7493@register
7494class EntryPointBreakCommand(GenericCommand):
7495 """Tries to find best entry point and sets a temporary breakpoint on it. The command will test for
7496 well-known symbols for entry points, such as `main`, `_main`, `__libc_start_main`, etc. defined by
7497 the setting `entrypoint_symbols`."""
7499 _cmdline_ = "entry-break"
7500 _syntax_ = _cmdline_
7501 _aliases_ = ["start",]
7503 def __init__(self) -> None:
7504 super().__init__()
7505 self["entrypoint_symbols"] = ("main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points")
7506 return
7508 def do_invoke(self, argv: list[str]) -> None:
7509 fpath = get_filepath()
7510 if fpath is None: 7510 ↛ 7511line 7510 didn't jump to line 7511 because the condition on line 7510 was never true
7511 warn("No executable to debug, use `file` to load a binary")
7512 return
7514 if not os.access(fpath, os.X_OK): 7514 ↛ 7515line 7514 didn't jump to line 7515 because the condition on line 7514 was never true
7515 warn(f"The file '{fpath}' is not executable.")
7516 return
7518 if is_alive() and not gef.session.qemu_mode:
7519 warn("gdb is already running")
7520 return
7522 bp = None
7523 entrypoints = self["entrypoint_symbols"].split()
7525 for sym in entrypoints:
7526 try:
7527 value = parse_address(sym)
7528 info(f"Breaking at '{value:#x}'")
7529 bp = EntryBreakBreakpoint(sym)
7530 gdb.execute(f"run {' '.join(argv)}")
7531 return
7533 except gdb.error as gdb_error:
7534 if 'The "remote" target does not support "run".' in str(gdb_error): 7534 ↛ 7536line 7534 didn't jump to line 7536 because the condition on line 7534 was never true
7535 # this case can happen when doing remote debugging
7536 gdb.execute("continue")
7537 return
7538 continue
7540 # if here, clear the breakpoint if any set
7541 if bp: 7541 ↛ 7542line 7541 didn't jump to line 7542 because the condition on line 7541 was never true
7542 bp.delete()
7544 assert gef.binary
7545 # break at entry point
7546 entry = gef.binary.entry_point
7548 if is_pie(fpath): 7548 ↛ 7553line 7548 didn't jump to line 7553 because the condition on line 7548 was always true
7549 self.set_init_tbreak_pie(entry, argv)
7550 gdb.execute("continue")
7551 return
7553 self.set_init_tbreak(entry)
7554 gdb.execute(f"run {' '.join(argv)}")
7555 return
7557 def set_init_tbreak(self, addr: int) -> EntryBreakBreakpoint:
7558 info(f"Breaking at entry-point: {addr:#x}")
7559 bp = EntryBreakBreakpoint(f"*{addr:#x}")
7560 return bp
7562 def set_init_tbreak_pie(self, addr: int, argv: list[str]) -> EntryBreakBreakpoint:
7563 warn("PIC binary detected, retrieving text base address")
7564 gdb.execute("set stop-on-solib-events 1")
7565 hide_context()
7566 gdb.execute(f"run {' '.join(argv)}")
7567 unhide_context()
7568 gdb.execute("set stop-on-solib-events 0")
7569 vmmap = gef.memory.maps
7570 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
7571 return self.set_init_tbreak(base_address + addr)
7574@register
7575class NamedBreakpointCommand(GenericCommand):
7576 """Sets a breakpoint and assigns a name to it, which will be shown, when it's hit."""
7578 _cmdline_ = "name-break"
7579 _syntax_ = f"{_cmdline_} name [address]"
7580 _aliases_ = ["nb",]
7581 _example = f"{_cmdline_} main *0x4008a9"
7583 def __init__(self) -> None:
7584 super().__init__()
7585 return
7587 @parse_arguments({"name": "", "address": "*$pc"}, {})
7588 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
7589 args : argparse.Namespace = kwargs["arguments"]
7590 if not args.name: 7590 ↛ 7591line 7590 didn't jump to line 7591 because the condition on line 7590 was never true
7591 err("Missing name for breakpoint")
7592 self.usage()
7593 return
7595 NamedBreakpoint(args.address, args.name)
7596 return
7599@register
7600class ContextCommand(GenericCommand):
7601 """Displays a comprehensive and modular summary of runtime context. Unless setting `enable` is
7602 set to False, this command will be spawned automatically every time GDB hits a breakpoint, a
7603 watchpoint, or any kind of interrupt. By default, it will show panes that contain the register
7604 states, the stack, and the disassembly code around $pc."""
7606 _cmdline_ = "context"
7607 _syntax_ = f"{_cmdline_} [legend|regs|stack|code|args|memory|source|trace|threads|extra]"
7608 _aliases_ = ["ctx",]
7610 old_registers: dict[str, int | None] = {}
7612 def __init__(self) -> None:
7613 super().__init__()
7614 self["enable"] = (True, "Enable/disable printing the context when breaking")
7615 self["show_source_code_variable_values"] = (True, "Show extra PC context info in the source code")
7616 self["show_full_source_file_name_max_len"] = (30, "Show full source path name, if less than this value")
7617 self["show_basename_source_file_name_max_len"] = (20, "Show the source basename in full, if less than this value")
7618 self["show_prefix_source_path_name_len"] = (10, "When truncating source path, show this many path prefix characters")
7619 self["show_stack_raw"] = (False, "Show the stack pane as raw hexdump (no dereference)")
7620 self["show_registers_raw"] = (False, "Show the registers pane with raw values (no dereference)")
7621 self["show_opcodes_size"] = (0, "Number of bytes of opcodes to display next to the disassembly")
7622 self["peek_calls"] = (True, "Peek into calls")
7623 self["peek_ret"] = (True, "Peek at return address")
7624 self["nb_lines_stack"] = (8, "Number of line in the stack pane")
7625 self["grow_stack_down"] = (False, "Order of stack downward starts at largest down to stack pointer")
7626 self["nb_lines_backtrace"] = (10, "Number of line in the backtrace pane")
7627 self["nb_lines_backtrace_before"] = (2, "Number of line in the backtrace pane before selected frame")
7628 self["nb_lines_threads"] = (-1, "Number of line in the threads pane")
7629 self["nb_lines_code"] = (6, "Number of instruction after $pc")
7630 self["nb_lines_code_prev"] = (3, "Number of instruction before $pc")
7631 self["ignore_registers"] = ("", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')")
7632 self["clear_screen"] = (True, "Clear the screen before printing the context")
7633 self["layout"] = ("legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections")
7634 self["redirect"] = ("", "Redirect the context information to another TTY")
7635 self["libc_args"] = (False, "[DEPRECATED - Unused] Show libc function call args description")
7636 self["libc_args_path"] = ("", "[DEPRECATED - Unused] Path to libc function call args json files, provided via gef-extras")
7638 self.layout_mapping: dict[str, tuple[Callable, Callable | None, Callable | None]] = {
7639 "legend": (self.show_legend, None, None),
7640 "regs": (self.context_regs, None, None),
7641 "stack": (self.context_stack, None, None),
7642 "code": (self.context_code, None, None),
7643 "args": (self.context_args, None, None),
7644 "memory": (self.context_memory, None, None),
7645 "source": (self.context_source, None, None),
7646 "trace": (self.context_trace, None, None),
7647 "threads": (self.context_threads, None, None),
7648 "extra": (self.context_additional_information, None, None),
7649 }
7651 self.instruction_iterator = gef_disassemble
7652 return
7654 def post_load(self) -> None:
7655 gef_on_continue_hook(self.update_registers)
7656 gef_on_continue_hook(self.empty_extra_messages)
7657 return
7659 def show_legend(self) -> None:
7660 if gef.config["gef.disable_color"] is True: 7660 ↛ 7662line 7660 didn't jump to line 7662 because the condition on line 7660 was always true
7661 return
7662 changed_register_title = Color.colorify("Modified register", gef.config["theme.registers_value_changed"])
7663 code_title = Color.colorify("Code", gef.config["theme.address_code"])
7664 heap_title = Color.colorify("Heap", gef.config["theme.address_heap"])
7665 stack_title = Color.colorify("Stack", gef.config["theme.address_stack"])
7666 str_title = Color.colorify("String", gef.config["theme.dereference_string"])
7667 gef_print(f"[ Legend: {changed_register_title} | {code_title} | {heap_title} | {stack_title} | {str_title} ]")
7668 return
7670 @only_if_gdb_running
7671 def do_invoke(self, argv: list[str]) -> None:
7672 if not self["enable"] or gef.ui.context_hidden:
7673 return
7675 if not all(_ in self.layout_mapping for _ in argv): 7675 ↛ 7676line 7675 didn't jump to line 7676 because the condition on line 7675 was never true
7676 self.usage()
7677 return
7679 if len(argv) > 0: 7679 ↛ 7680line 7679 didn't jump to line 7680 because the condition on line 7679 was never true
7680 current_layout = argv
7681 else:
7682 current_layout = self["layout"].strip().split()
7684 if not current_layout: 7684 ↛ 7685line 7684 didn't jump to line 7685 because the condition on line 7684 was never true
7685 return
7687 self.tty_rows, self.tty_columns = get_terminal_size()
7689 redirect = self["redirect"]
7690 if redirect and os.access(redirect, os.W_OK): 7690 ↛ 7691line 7690 didn't jump to line 7691 because the condition on line 7690 was never true
7691 enable_redirect_output(to_file=redirect)
7693 if self["clear_screen"] and len(argv) == 0: 7693 ↛ 7696line 7693 didn't jump to line 7696 because the condition on line 7693 was always true
7694 clear_screen(redirect)
7696 for section in current_layout:
7697 if section[0] == "-": 7697 ↛ 7698line 7697 didn't jump to line 7698 because the condition on line 7697 was never true
7698 continue
7700 try:
7701 display_pane_function, pane_title_function, condition = self.layout_mapping[section]
7702 if condition: 7702 ↛ 7703line 7702 didn't jump to line 7703 because the condition on line 7702 was never true
7703 if not condition():
7704 continue
7705 if pane_title_function: 7705 ↛ 7706line 7705 didn't jump to line 7706 because the condition on line 7705 was never true
7706 self.context_title(pane_title_function())
7707 display_pane_function()
7708 except gdb.MemoryError as e:
7709 # a MemoryError will happen when $pc is corrupted (invalid address)
7710 err(str(e))
7711 except IndexError:
7712 # the `section` is not present, just skip
7713 pass
7715 self.context_title("")
7717 if redirect and os.access(redirect, os.W_OK): 7717 ↛ 7718line 7717 didn't jump to line 7718 because the condition on line 7717 was never true
7718 disable_redirect_output()
7719 return
7721 def context_title(self, m: str | None) -> None:
7722 # allow for not displaying a title line
7723 if m is None: 7723 ↛ 7724line 7723 didn't jump to line 7724 because the condition on line 7723 was never true
7724 return
7726 line_color = gef.config["theme.context_title_line"]
7727 msg_color = gef.config["theme.context_title_message"]
7729 # print an empty line in case of ""
7730 if not m:
7731 gef_print(Color.colorify(HORIZONTAL_LINE * self.tty_columns, line_color))
7732 return
7734 trail_len = len(m) + 6
7735 title = ""
7736 width = max(self.tty_columns - trail_len, 0)
7737 padd = HORIZONTAL_LINE
7738 title += Color.colorify(f"{'':{padd}<{width}} ", line_color)
7739 title += Color.colorify(m, msg_color)
7740 title += Color.colorify(f" {'':{padd}<4}", line_color)
7741 gef_print(title)
7742 return
7744 def context_regs(self) -> None:
7745 self.context_title("registers")
7746 ignored_registers = set(self["ignore_registers"].split())
7748 # Defer to DetailRegisters by default
7749 if self["show_registers_raw"] is False: 7749 ↛ 7755line 7749 didn't jump to line 7755 because the condition on line 7749 was always true
7750 regs = [reg for reg in gef.arch.all_registers if reg not in ignored_registers]
7751 printable_registers = " ".join(regs)
7752 gdb.execute(f"registers {printable_registers}")
7753 return
7755 widest = curlen = max(map(len, gef.arch.all_registers))
7756 curlen += 5
7757 curlen += gef.arch.ptrsize * 2
7758 nb = get_terminal_size()[1] // curlen
7759 i = 1
7760 line = ""
7761 changed_color = gef.config["theme.registers_value_changed"]
7762 regname_color = gef.config["theme.registers_register_name"]
7764 for reg in gef.arch.all_registers:
7765 if reg in ignored_registers:
7766 continue
7768 try:
7769 r = gdb.parse_and_eval(reg)
7770 if r.type.code == gdb.TYPE_CODE_VOID:
7771 continue
7773 new_value_type_flag = r.type.code == gdb.TYPE_CODE_FLAGS
7774 new_value = int(r)
7776 except (gdb.MemoryError, gdb.error):
7777 # If this exception is triggered, it means that the current register
7778 # is corrupted. Just use the register "raw" value (not eval-ed)
7779 new_value = gef.arch.register(reg)
7780 new_value_type_flag = False
7782 except Exception:
7783 new_value = 0
7784 new_value_type_flag = False
7786 old_value = self.old_registers.get(reg, 0)
7788 padreg = reg.ljust(widest, " ")
7789 value = align_address(new_value)
7790 old_value = align_address(old_value or 0)
7791 if value == old_value:
7792 line += f"{Color.colorify(padreg, regname_color)}: "
7793 else:
7794 line += f"{Color.colorify(padreg, changed_color)}: "
7795 if new_value_type_flag:
7796 line += f"{format_address_spaces(value)} "
7797 else:
7798 addr = lookup_address(align_address(int(value)))
7799 if addr.valid:
7800 line += f"{addr!s} "
7801 else:
7802 line += f"{format_address_spaces(value)} "
7804 if i % nb == 0:
7805 gef_print(line)
7806 line = ""
7807 i += 1
7809 if line:
7810 gef_print(line)
7812 gef_print(f"Flags: {gef.arch.flag_register_to_human()}")
7813 return
7815 def context_stack(self) -> None:
7816 self.context_title("stack")
7818 show_raw = self["show_stack_raw"]
7819 nb_lines = self["nb_lines_stack"]
7821 try:
7822 sp = gef.arch.sp
7823 if show_raw is True: 7823 ↛ 7824line 7823 didn't jump to line 7824 because the condition on line 7823 was never true
7824 mem = gef.memory.read(sp, 0x10 * nb_lines)
7825 gef_print(hexdump(mem, base=sp))
7826 else:
7827 gdb.execute(f"dereference -l {nb_lines:d} {sp:#x}")
7829 except gdb.MemoryError:
7830 err("Cannot read memory from $SP (corrupted stack pointer?)")
7832 return
7834 def addr_has_breakpoint(self, address: int, bp_locations: list[str]) -> bool:
7835 return any(hex(address) in b for b in bp_locations)
7837 def context_code(self) -> None:
7838 nb_insn = self["nb_lines_code"]
7839 nb_insn_prev = self["nb_lines_code_prev"]
7840 show_opcodes_size = "show_opcodes_size" in self and self["show_opcodes_size"]
7841 past_insns_color = gef.config["theme.old_context"]
7842 cur_insn_color = gef.config["theme.disassemble_current_instruction"]
7843 pc = gef.arch.pc
7844 breakpoints = gdb.breakpoints() or []
7845 # breakpoint.locations was introduced in gdb 13.1
7846 if len(breakpoints) and hasattr(breakpoints[-1], "locations"):
7847 bp_locations = [hex(location.address) for b in breakpoints for location in b.locations if location is not None] # type: ignore
7848 else:
7849 # location relies on the user setting the breakpoints with "b *{hex(address)}"
7850 bp_locations = [b.location for b in breakpoints if b.location and b.location.startswith("*")]
7852 frame = gdb.selected_frame()
7853 arch_name = f"{gef.arch.arch.lower()}:{gef.arch.mode}"
7855 self.context_title(f"code:{arch_name}")
7857 try:
7860 for insn in self.instruction_iterator(pc, nb_insn, nb_prev=nb_insn_prev):
7861 line = []
7862 is_taken = False
7863 target = None
7864 bp_prefix = Color.redify(BP_GLYPH) if self.addr_has_breakpoint(insn.address, bp_locations) else " "
7866 if show_opcodes_size == 0:
7867 text = str(insn)
7868 else:
7869 insn_fmt = f"{ :{show_opcodes_size}o} "
7870 text = insn_fmt.format(insn)
7872 if insn.address < pc:
7873 line += f"{bp_prefix} {Color.colorify(text, past_insns_color)}"
7875 elif insn.address == pc:
7876 line += f"{bp_prefix}{Color.colorify(f'{RIGHT_ARROW[1:]}{text}', cur_insn_color)}"
7878 if gef.arch.is_conditional_branch(insn): 7878 ↛ 7879line 7878 didn't jump to line 7879 because the condition on line 7878 was never true
7879 is_taken, reason = gef.arch.is_branch_taken(insn)
7880 if is_taken:
7881 target = insn.operands[-1].split()[0]
7882 reason = f"[Reason: {reason}]" if reason else ""
7883 line += Color.colorify(f"\tTAKEN {reason}", "bold green")
7884 else:
7885 reason = f"[Reason: !({reason})]" if reason else ""
7886 line += Color.colorify(f"\tNOT taken {reason}", "bold red")
7887 elif gef.arch.is_call(insn) and self["peek_calls"] is True: 7887 ↛ 7888line 7887 didn't jump to line 7888 because the condition on line 7887 was never true
7888 target = insn.operands[-1].split()[0]
7889 elif gef.arch.is_ret(insn) and self["peek_ret"] is True:
7890 target = gef.arch.get_ra(insn, frame)
7892 else:
7893 line += f"{bp_prefix} {text}"
7895 gef_print("".join(line))
7897 if target:
7898 try:
7899 address = int(target, 0) if isinstance(target, str) else target
7900 except ValueError:
7901 # If the operand isn't an address right now we can't parse it
7902 continue
7903 for i, tinsn in enumerate(self.instruction_iterator(address, nb_insn)): 7903 ↛ anywhereline 7903 didn't jump anywhere: it always raised an exception.
7904 text= f" {DOWN_ARROW if i == 0 else ' '} {tinsn!s}"
7905 gef_print(text)
7906 break
7908 except gdb.MemoryError:
7909 err("Cannot disassemble from $PC")
7910 return
7912 def context_args(self) -> None:
7913 insn = gef_current_instruction(gef.arch.pc)
7914 if not gef.arch.is_call(insn): 7914 ↛ 7917line 7914 didn't jump to line 7917 because the condition on line 7914 was always true
7915 return
7917 self.size2type = {
7918 1: "BYTE",
7919 2: "WORD",
7920 4: "DWORD",
7921 8: "QWORD",
7922 }
7924 if insn.operands[-1].startswith(self.size2type[gef.arch.ptrsize]+" PTR"):
7925 target = "*" + insn.operands[-1].split()[-1]
7926 elif "$"+insn.operands[0] in gef.arch.all_registers:
7927 target = f"*{gef.arch.register('$' + insn.operands[0]):#x}"
7928 else:
7929 # is there a symbol?
7930 ops = " ".join(insn.operands)
7931 if "<" in ops and ">" in ops:
7932 # extract it
7933 target = re.sub(r".*<([^\(> ]*).*", r"\1", ops)
7934 else:
7935 # it's an address, just use as is
7936 target = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops)
7938 sym = gdb.lookup_global_symbol(target)
7939 if sym is None:
7940 self.print_guessed_arguments(target)
7941 return
7943 if sym.type and sym.type.code != gdb.TYPE_CODE_FUNC:
7944 err(f"Symbol '{target}' is not a function: type={sym.type.code}")
7945 return
7947 self.print_arguments_from_symbol(target, sym)
7948 return
7950 def print_arguments_from_symbol(self, function_name: str, symbol: "gdb.Symbol") -> None:
7951 """If symbols were found, parse them and print the argument adequately."""
7952 args = []
7953 fields = symbol.type.fields() if symbol.type else []
7954 for i, f in enumerate(fields):
7955 if not f.type:
7956 continue
7957 _value = gef.arch.get_ith_parameter(i, in_func=False)[1]
7958 _value = RIGHT_ARROW.join(dereference_from(_value))
7959 _name = f.name or f"var_{i}"
7960 _type = f.type.name or self.size2type[f.type.sizeof]
7961 args.append(f"{_type} {_name} = {_value}")
7963 self.context_title("arguments")
7965 if not args:
7966 gef_print(f"{function_name} (<void>)")
7967 return
7969 gef_print(f"{function_name} (\n "+",\n ".join(args)+"\n)")
7970 return
7972 def print_guessed_arguments(self, function_name: str) -> None:
7973 """When no symbol, read the current basic block and look for "interesting" instructions."""
7975 def __get_current_block_start_address() -> int | None:
7976 pc = gef.arch.pc
7977 max_distance = 10 * 16
7978 try:
7979 block = gdb.block_for_pc(pc)
7980 block_start = block.start \
7981 if block is not None and (pc - block.start) <= max_distance \
7982 else gdb_get_nth_previous_instruction_address(pc, 5)
7983 except RuntimeError:
7984 block_start = gdb_get_nth_previous_instruction_address(pc, 5)
7985 return block_start
7987 block_start = __get_current_block_start_address()
7988 if not block_start:
7989 return
7991 parameter_set: set[str] = set()
7992 pc = gef.arch.pc
7993 function_parameters = gef.arch.function_parameters
7994 arg_key_color = gef.config["theme.registers_register_name"]
7996 for insn in self.instruction_iterator(block_start, pc - block_start):
7997 if not insn.operands:
7998 continue
8000 if is_x86_32():
8001 if insn.mnemonic == "push":
8002 parameter_set.add(insn.operands[0])
8003 else:
8004 op = "$" + insn.operands[0]
8005 if op in function_parameters:
8006 parameter_set.add(op)
8008 if is_x86_64():
8009 # also consider extended registers
8010 extended_registers = {"$rdi": ["$edi", "$di"],
8011 "$rsi": ["$esi", "$si"],
8012 "$rdx": ["$edx", "$dx"],
8013 "$rcx": ["$ecx", "$cx"],
8014 }
8015 for exreg in extended_registers:
8016 if op in extended_registers[exreg]:
8017 parameter_set.add(exreg)
8019 if is_x86_32():
8020 nb_argument = len(parameter_set)
8021 else:
8022 nb_argument = max([function_parameters.index(p)+1 for p in parameter_set], default=0)
8024 args = []
8025 for i in range(nb_argument):
8026 _key, _values = gef.arch.get_ith_parameter(i, in_func=False)
8027 _values = RIGHT_ARROW.join(dereference_from(_values))
8028 args.append(f"{Color.colorify(_key, arg_key_color)} = {_values}")
8030 self.context_title("arguments (guessed)")
8031 gef_print(f"{function_name} (")
8032 if args:
8033 gef_print(" " + ",\n ".join(args))
8034 gef_print(")")
8035 return
8037 def line_has_breakpoint(self, file_name: str, line_number: int, bp_locations: list[str]) -> bool:
8038 filename_line = f"{file_name}:{line_number}"
8039 return any(filename_line in loc for loc in bp_locations)
8041 def context_source(self) -> None:
8042 try:
8043 pc = gef.arch.pc
8044 symtabline = gdb.find_pc_line(pc)
8045 symtab = symtabline.symtab
8046 # we subtract one because the line number returned by gdb start at 1
8047 line_num = symtabline.line - 1
8048 if not symtab.is_valid(): 8048 ↛ 8049line 8048 didn't jump to line 8049 because the condition on line 8048 was never true
8049 return
8051 fpath = pathlib.Path(symtab.fullname())
8052 lines = [curline.rstrip() for curline in fpath.read_text().splitlines()]
8054 except Exception:
8055 return
8057 file_base_name = os.path.basename(symtab.filename)
8058 breakpoints = gdb.breakpoints() or []
8059 bp_locations = [b.location for b in breakpoints if b.location and file_base_name in b.location]
8060 past_lines_color = gef.config["theme.old_context"]
8062 show_full_path_max = self["show_full_source_file_name_max_len"]
8063 show_basename_path_max = self["show_basename_source_file_name_max_len"]
8065 nb_line = self["nb_lines_code"]
8066 fn = symtab.filename
8067 if len(fn) > show_full_path_max: 8067 ↛ 8068line 8067 didn't jump to line 8068 because the condition on line 8067 was never true
8068 base = os.path.basename(fn)
8069 if len(base) > show_basename_path_max:
8070 base = base[-show_basename_path_max:]
8071 fn = fn[:15] + "[...]" + base
8072 title = f"source:{fn}+{line_num + 1}"
8073 cur_line_color = gef.config["theme.source_current_line"]
8074 self.context_title(title)
8075 show_extra_info = self["show_source_code_variable_values"]
8077 for i in range(line_num - nb_line + 1, line_num + nb_line):
8078 if i < 0:
8079 continue
8081 bp_prefix = Color.redify(BP_GLYPH) if self.line_has_breakpoint(file_base_name, i + 1, bp_locations) else " "
8083 if i < line_num:
8084 gef_print("{}{}".format(bp_prefix, Color.colorify(f" {i + 1:4d}\t {lines[i]}", past_lines_color)))
8086 if i == line_num:
8087 prefix = f"{bp_prefix}{RIGHT_ARROW[1:]}{i + 1:4d}\t "
8088 leading = len(lines[i]) - len(lines[i].lstrip())
8089 if show_extra_info: 8089 ↛ 8093line 8089 didn't jump to line 8093 because the condition on line 8089 was always true
8090 extra_info = self.get_pc_context_info(pc, lines[i])
8091 if extra_info:
8092 gef_print(f"{' ' * (len(prefix) + leading)}{extra_info}")
8093 gef_print(Color.colorify(f"{prefix}{lines[i]}", cur_line_color))
8095 if i > line_num:
8096 try:
8097 gef_print(f"{bp_prefix} {i + 1:4d}\t {lines[i]}")
8098 except IndexError:
8099 break
8100 return
8102 def get_pc_context_info(self, pc: int, line: str) -> str:
8103 try:
8104 current_block = gdb.block_for_pc(pc)
8105 if not current_block or not current_block.is_valid(): return "" 8105 ↛ exitline 8105 didn't return from function 'get_pc_context_info' because the return on line 8105 wasn't executed
8106 m = collections.OrderedDict()
8107 while current_block and not current_block.is_static:
8108 for sym in list(current_block):
8109 symbol = sym.name
8110 if not sym.is_function and re.search(fr"\W{symbol}\W", line):
8111 val = gdb.parse_and_eval(symbol)
8112 if val.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY): 8112 ↛ 8113line 8112 didn't jump to line 8113 because the condition on line 8112 was never true
8113 addr = int(val.address)
8114 addrs = dereference_from(addr)
8115 if len(addrs) > 2:
8116 addrs = [addrs[0], "[...]", addrs[-1]]
8118 f = f" {RIGHT_ARROW} "
8119 val = f.join(addrs)
8120 elif val.type.code == gdb.TYPE_CODE_INT: 8120 ↛ 8123line 8120 didn't jump to line 8123 because the condition on line 8120 was always true
8121 val = hex(int(val))
8122 else:
8123 continue
8125 if symbol not in m: 8125 ↛ 8108line 8125 didn't jump to line 8108 because the condition on line 8125 was always true
8126 m[symbol] = val
8127 current_block = current_block.superblock
8129 if m:
8130 return "// " + ", ".join([f"{Color.yellowify(a)}={b}" for a, b in m.items()])
8131 except Exception:
8132 pass
8133 return ""
8135 def context_trace(self) -> None:
8136 self.context_title("trace")
8138 nb_backtrace = self["nb_lines_backtrace"]
8139 if nb_backtrace <= 0: 8139 ↛ 8140line 8139 didn't jump to line 8140 because the condition on line 8139 was never true
8140 return
8142 # backward compat for gdb (gdb < 7.10)
8143 if not hasattr(gdb, "FrameDecorator"): 8143 ↛ 8144line 8143 didn't jump to line 8144 because the condition on line 8143 was never true
8144 gdb.execute(f"backtrace {nb_backtrace:d}")
8145 return
8147 orig_frame: gdb.Frame = gdb.selected_frame()
8148 current_frame: gdb.Frame = gdb.newest_frame()
8149 frames = [current_frame,]
8150 while current_frame != orig_frame and current_frame: 8150 ↛ 8151line 8150 didn't jump to line 8151 because the condition on line 8150 was never true
8151 current_frame = current_frame.older()
8152 if not current_frame: break
8153 frames.append(current_frame)
8155 nb_backtrace_before = self["nb_lines_backtrace_before"]
8156 level = max(len(frames) - nb_backtrace_before - 1, 0)
8157 current_frame: gdb.Frame = frames[level]
8159 while current_frame: 8159 ↛ 8199line 8159 didn't jump to line 8199 because the condition on line 8159 was always true
8160 current_frame.select()
8161 if not current_frame.is_valid(): 8161 ↛ 8162line 8161 didn't jump to line 8162 because the condition on line 8161 was never true
8162 continue
8164 pc = int(current_frame.pc())
8165 name = current_frame.name()
8166 items = []
8167 items.append(f"{pc:#x}")
8168 if name:
8169 frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or [] # type: ignore
8170 symstr= ", ".join([f"{Color.yellowify(x.sym)}={x.sym.value(current_frame)!s}" for x in frame_args])
8171 m = f"{Color.greenify(name)}({symstr})"
8172 items.append(m)
8173 else:
8174 try:
8175 insn = next(gef_disassemble(int(pc), 1))
8176 except gdb.MemoryError:
8177 break
8179 # check if the gdb symbol table may know the address
8180 sym_found = gdb_get_location_from_symbol(pc)
8181 symbol = ""
8182 if sym_found: 8182 ↛ 8183line 8182 didn't jump to line 8183 because the condition on line 8182 was never true
8183 sym_name, offset = sym_found
8184 symbol = f" <{sym_name}+{offset:x}> "
8186 items.append(Color.redify(f"{symbol}{insn.mnemonic} {', '.join(insn.operands)}"))
8188 title = Color.colorify(f"#{level}", "bold green" if current_frame == orig_frame else "bold pink")
8189 gef_print(f"[{title}] {RIGHT_ARROW.join(items)}")
8190 older = current_frame.older()
8191 level += 1
8192 nb_backtrace -= 1
8193 if nb_backtrace == 0:
8194 break
8195 if not older:
8196 break
8197 current_frame = older
8199 orig_frame.select()
8200 return
8202 def context_threads(self) -> None:
8203 def reason() -> str:
8204 res = gdb.execute("info program", to_string=True)
8205 if not res: 8205 ↛ 8206line 8205 didn't jump to line 8206 because the condition on line 8205 was never true
8206 return "NOT RUNNING"
8208 for line in res.splitlines(): 8208 ↛ 8221line 8208 didn't jump to line 8221 because the loop on line 8208 didn't complete
8209 line = line.strip()
8210 if line.startswith("It stopped with signal "):
8211 return line.replace("It stopped with signal ", "").split(",", 1)[0]
8212 if line == "The program being debugged is not being run.": 8212 ↛ 8213line 8212 didn't jump to line 8213 because the condition on line 8212 was never true
8213 return "NOT RUNNING"
8214 if line == "It stopped at a breakpoint that has since been deleted.": 8214 ↛ 8215line 8214 didn't jump to line 8215 because the condition on line 8214 was never true
8215 return "TEMPORARY BREAKPOINT"
8216 if line.startswith("It stopped at breakpoint "):
8217 return "BREAKPOINT"
8218 if line == "It stopped after being stepped.": 8218 ↛ 8219line 8218 didn't jump to line 8219 because the condition on line 8218 was never true
8219 return "SINGLE STEP"
8221 return "STOPPED"
8223 self.context_title("threads")
8225 threads = gdb.selected_inferior().threads()[::-1]
8226 idx = self["nb_lines_threads"]
8227 if idx > 0: 8227 ↛ 8228line 8227 didn't jump to line 8228 because the condition on line 8227 was never true
8228 threads = threads[0:idx]
8230 if idx == 0: 8230 ↛ 8231line 8230 didn't jump to line 8231 because the condition on line 8230 was never true
8231 return
8233 if not threads: 8233 ↛ 8234line 8233 didn't jump to line 8234 because the condition on line 8233 was never true
8234 err("No thread selected")
8235 return
8237 selected_thread = gdb.selected_thread()
8238 selected_frame = gdb.selected_frame()
8240 for i, thread in enumerate(threads):
8241 line = f"[{Color.colorify(f'#{i:d}', 'bold green' if thread == selected_thread else 'bold pink')}] Id {thread.num:d}, "
8242 if thread.name:
8243 line += f"""Name: "{thread.name}", """
8244 if thread.is_running(): 8244 ↛ 8245line 8244 didn't jump to line 8245 because the condition on line 8244 was never true
8245 line += Color.colorify("running", "bold green")
8246 elif thread.is_stopped(): 8246 ↛ 8262line 8246 didn't jump to line 8262 because the condition on line 8246 was always true
8247 line += Color.colorify("stopped", "bold red")
8248 thread.switch()
8249 frame = gdb.selected_frame()
8250 frame_name = frame.name()
8252 # check if the gdb symbol table may know the address
8253 if not frame_name:
8254 sym_found = gdb_get_location_from_symbol(int(frame.pc()))
8255 if sym_found: 8255 ↛ 8256line 8255 didn't jump to line 8256 because the condition on line 8255 was never true
8256 sym_name, offset = sym_found
8257 frame_name = f"<{sym_name}+{offset:x}>"
8259 line += (f" {Color.colorify(f'{frame.pc():#x}', 'blue')} in "
8260 f"{Color.colorify(frame_name or '??', 'bold yellow')} (), "
8261 f"reason: {Color.colorify(reason(), 'bold pink')}")
8262 elif thread.is_exited():
8263 line += Color.colorify("exited", "bold yellow")
8264 gef_print(line)
8265 i += 1
8267 selected_thread.switch()
8268 selected_frame.select()
8269 return
8271 def context_additional_information(self) -> None:
8272 if not gef.ui.context_messages:
8273 return
8275 self.context_title("extra")
8276 for level, text in gef.ui.context_messages:
8277 if level == "error": err(text) 8277 ↛ 8276line 8277 didn't jump to line 8276 because
8278 elif level == "warn": warn(text) 8278 ↛ 8279line 8278 didn't jump to line 8279 because the condition on line 8278 was always true
8279 elif level == "success": ok(text)
8280 else: info(text)
8281 return
8283 def context_memory(self) -> None:
8284 for address, opt in sorted(gef.ui.watches.items()):
8285 sz, fmt = opt[0:2]
8286 self.context_title(f"memory:{address:#x}")
8287 if fmt == "pointers": 8287 ↛ 8288line 8287 didn't jump to line 8288 because the condition on line 8287 was never true
8288 gdb.execute(f"dereference -l {sz:d} {address:#x}")
8289 else:
8290 gdb.execute(f"hexdump {fmt} -s {sz:d} {address:#x}")
8292 @classmethod
8293 def update_registers(cls, _) -> None:
8294 for reg in gef.arch.all_registers:
8295 try:
8296 cls.old_registers[reg] = gef.arch.register(reg)
8297 except Exception:
8298 cls.old_registers[reg] = 0
8299 return
8301 def empty_extra_messages(self, _) -> None:
8302 gef.ui.context_messages.clear()
8303 return
8306@register
8307class MemoryCommand(GenericCommand):
8308 """Add or remove address ranges to the memory view."""
8309 _cmdline_ = "memory"
8310 _syntax_ = f"{_cmdline_} (watch|unwatch|reset|list)"
8312 def __init__(self) -> None:
8313 super().__init__(prefix=True)
8314 return
8316 @only_if_gdb_running
8317 def do_invoke(self, argv: list[str]) -> None:
8318 self.usage()
8319 return
8322@register
8323class MemoryWatchCommand(GenericCommand):
8324 """Adds address ranges to the memory view."""
8325 _cmdline_ = "memory watch"
8326 _syntax_ = f"{_cmdline_} ADDRESS [SIZE] [(qword|dword|word|byte|pointers)]"
8327 _example_ = (f"\n{_cmdline_} 0x603000 0x100 byte"
8328 f"\n{_cmdline_} $sp")
8330 def __init__(self) -> None:
8331 super().__init__(complete=gdb.COMPLETE_LOCATION)
8332 return
8334 @only_if_gdb_running
8335 def do_invoke(self, argv: list[str]) -> None:
8336 if len(argv) not in (1, 2, 3): 8336 ↛ 8337line 8336 didn't jump to line 8337 because the condition on line 8336 was never true
8337 self.usage()
8338 return
8340 address = parse_address(argv[0])
8341 size = parse_address(argv[1]) if len(argv) > 1 else 0x10
8342 group = "byte"
8344 if len(argv) == 3:
8345 group = argv[2].lower()
8346 if group not in ("qword", "dword", "word", "byte", "pointers"): 8346 ↛ 8347line 8346 didn't jump to line 8347 because the condition on line 8346 was never true
8347 warn(f"Unexpected grouping '{group}'")
8348 self.usage()
8349 return
8350 else:
8351 if gef.arch.ptrsize == 4: 8351 ↛ 8352line 8351 didn't jump to line 8352 because the condition on line 8351 was never true
8352 group = "dword"
8353 elif gef.arch.ptrsize == 8: 8353 ↛ 8356line 8353 didn't jump to line 8356 because the condition on line 8353 was always true
8354 group = "qword"
8356 gef.ui.watches[address] = (size, group)
8357 ok(f"Adding memwatch to {address:#x}")
8358 return
8361@register
8362class MemoryUnwatchCommand(GenericCommand):
8363 """Removes address ranges to the memory view."""
8364 _cmdline_ = "memory unwatch"
8365 _syntax_ = f"{_cmdline_} ADDRESS"
8366 _example_ = (f"\n{_cmdline_} 0x603000"
8367 f"\n{_cmdline_} $sp")
8369 def __init__(self) -> None:
8370 super().__init__(complete=gdb.COMPLETE_LOCATION)
8371 return
8373 @only_if_gdb_running
8374 def do_invoke(self, argv: list[str]) -> None:
8375 if not argv: 8375 ↛ 8376line 8375 didn't jump to line 8376 because the condition on line 8375 was never true
8376 self.usage()
8377 return
8379 address = parse_address(argv[0])
8380 res = gef.ui.watches.pop(address, None)
8381 if not res: 8381 ↛ 8384line 8381 didn't jump to line 8384 because the condition on line 8381 was always true
8382 warn(f"You weren't watching {address:#x}")
8383 else:
8384 ok(f"Removed memwatch of {address:#x}")
8385 return
8388@register
8389class MemoryWatchResetCommand(GenericCommand):
8390 """Removes all watchpoints."""
8391 _cmdline_ = "memory reset"
8392 _syntax_ = f"{_cmdline_}"
8394 @only_if_gdb_running
8395 def do_invoke(self, _: list[str]) -> None:
8396 gef.ui.watches.clear()
8397 ok("Memory watches cleared")
8398 return
8401@register
8402class MemoryWatchListCommand(GenericCommand):
8403 """Lists all watchpoints to display in context layout."""
8404 _cmdline_ = "memory list"
8405 _syntax_ = f"{_cmdline_}"
8407 @only_if_gdb_running
8408 def do_invoke(self, _: list[str]) -> None:
8409 if not gef.ui.watches: 8409 ↛ 8413line 8409 didn't jump to line 8413 because the condition on line 8409 was always true
8410 info("No memory watches")
8411 return
8413 info("Memory watches:")
8414 for address, opt in sorted(gef.ui.watches.items()):
8415 gef_print(f"- {address:#x} ({opt[0]}, {opt[1]})")
8416 return
8419@register
8420class HexdumpCommand(GenericCommand):
8421 """Display SIZE lines of hexdump from the memory location pointed by LOCATION."""
8423 _cmdline_ = "hexdump"
8424 _syntax_ = f"{_cmdline_} (qword|dword|word|byte) [LOCATION] [--size SIZE] [--reverse]"
8425 _example_ = f"{_cmdline_} byte $rsp --size 16 --reverse"
8427 def __init__(self) -> None:
8428 super().__init__(complete=gdb.COMPLETE_LOCATION, prefix=True)
8429 self["always_show_ascii"] = (False, "If true, hexdump will always display the ASCII dump")
8430 self.format: str | None = None
8431 self.__last_target = "$sp"
8432 return
8434 @only_if_gdb_running
8435 @parse_arguments({"address": "",}, {("--reverse", "-r"): False, ("--size", "-s"): 0})
8436 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
8437 valid_formats = ["byte", "word", "dword", "qword"]
8438 if not self.format or self.format not in valid_formats: 8438 ↛ 8439line 8438 didn't jump to line 8439 because the condition on line 8438 was never true
8439 err("Invalid command")
8440 return
8442 args : argparse.Namespace = kwargs["arguments"]
8443 target = args.address or self.__last_target
8444 start_addr = parse_address(target)
8445 read_from = align_address(start_addr)
8447 if self.format == "byte":
8448 read_len = args.size or 0x40
8449 read_from += self.repeat_count * read_len
8450 mem = gef.memory.read(read_from, read_len)
8451 lines = hexdump(mem, base=read_from).splitlines()
8452 else:
8453 read_len = args.size or 0x10
8454 lines = self._hexdump(read_from, read_len, self.format, self.repeat_count * read_len)
8456 if args.reverse:
8457 lines.reverse()
8459 self.__last_target = target
8460 gef_print("\n".join(lines))
8461 return
8463 def _hexdump(self, start_addr: int, length: int, arrange_as: str, offset: int = 0) -> list[str]:
8464 endianness = gef.arch.endianness
8466 base_address_color = gef.config["theme.dereference_base_address"]
8467 show_ascii = gef.config["hexdump.always_show_ascii"]
8469 formats = {
8470 "qword": ("Q", 8),
8471 "dword": ("I", 4),
8472 "word": ("H", 2),
8473 }
8475 formatter, width = formats[arrange_as]
8476 fmt_str = f"{ base} {VERTICAL_LINE}+{ offset:#06x} { sym} { val:#0{width*2+2}x} { text} "
8477 fmt_pack = f"{endianness!s}{formatter}"
8478 lines = []
8480 i = 0
8481 text = ""
8482 while i < length:
8483 cur_addr = start_addr + (i + offset) * width
8484 sym = gdb_get_location_from_symbol(cur_addr)
8485 sym = f"<{sym[0]:s}+{sym[1]:04x}> " if sym else ""
8486 mem = gef.memory.read(cur_addr, width)
8487 val = struct.unpack(fmt_pack, mem)[0]
8488 if show_ascii: 8488 ↛ 8489line 8488 didn't jump to line 8489 because the condition on line 8488 was never true
8489 text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem])
8490 lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color),
8491 offset=(i + offset) * width, sym=sym, val=val, text=text))
8492 i += 1
8494 return lines
8497@register
8498class HexdumpQwordCommand(HexdumpCommand):
8499 """Display SIZE lines of hexdump as QWORD from the memory location pointed by ADDRESS."""
8501 _cmdline_ = "hexdump qword"
8502 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]"
8503 _example_ = f"{_cmdline_} qword $rsp -s 16 --reverse"
8505 def __init__(self) -> None:
8506 super().__init__()
8507 self.format = "qword"
8508 return
8511@register
8512class HexdumpDwordCommand(HexdumpCommand):
8513 """Display SIZE lines of hexdump as DWORD from the memory location pointed by ADDRESS."""
8515 _cmdline_ = "hexdump dword"
8516 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]"
8517 _example_ = f"{_cmdline_} $esp -s 16 --reverse"
8519 def __init__(self) -> None:
8520 super().__init__()
8521 self.format = "dword"
8522 return
8525@register
8526class HexdumpWordCommand(HexdumpCommand):
8527 """Display SIZE lines of hexdump as WORD from the memory location pointed by ADDRESS."""
8529 _cmdline_ = "hexdump word"
8530 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]"
8531 _example_ = f"{_cmdline_} $esp -s 16 --reverse"
8533 def __init__(self) -> None:
8534 super().__init__()
8535 self.format = "word"
8536 return
8539@register
8540class HexdumpByteCommand(HexdumpCommand):
8541 """Display SIZE lines of hexdump as BYTE from the memory location pointed by ADDRESS."""
8543 _cmdline_ = "hexdump byte"
8544 _syntax_ = f"{_cmdline_} [ADDRESS] [--size SIZE] [--reverse]"
8545 _example_ = f"{_cmdline_} $rsp -s 16"
8547 def __init__(self) -> None:
8548 super().__init__()
8549 self.format = "byte"
8550 return
8553@register
8554class PatchCommand(GenericCommand):
8555 """Write specified values to the specified address."""
8557 _cmdline_ = "patch"
8558 _syntax_ = (f"{_cmdline_} (qword|dword|word|byte) LOCATION VALUES\n"
8559 f"{_cmdline_} string LOCATION \"double-escaped string\"")
8560 SUPPORTED_SIZES = {
8561 "qword": (8, "Q"),
8562 "dword": (4, "L"),
8563 "word": (2, "H"),
8564 "byte": (1, "B"),
8565 }
8567 def __init__(self) -> None:
8568 super().__init__(prefix=True, complete=gdb.COMPLETE_LOCATION)
8569 self.format: str | None = None
8570 return
8572 @only_if_gdb_running
8573 @parse_arguments({"location": "", "values": ["", ]}, {})
8574 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
8575 args : argparse.Namespace = kwargs["arguments"]
8576 if not self.format or self.format not in self.SUPPORTED_SIZES: 8576 ↛ 8577line 8576 didn't jump to line 8577 because the condition on line 8576 was never true
8577 self.usage()
8578 return
8580 if not args.location or not args.values: 8580 ↛ 8581line 8580 didn't jump to line 8581 because the condition on line 8580 was never true
8581 self.usage()
8582 return
8584 addr = align_address(parse_address(args.location))
8585 size, fcode = self.SUPPORTED_SIZES[self.format]
8586 values = args.values
8588 if size == 1:
8589 if values[0].startswith("$_gef"):
8590 var_name = values[0]
8591 try:
8592 values = str(gdb.parse_and_eval(var_name)).lstrip("{").rstrip("}").replace(",","").split(" ")
8593 except Exception:
8594 gef_print(f"Bad variable specified, check value with command: p {var_name}")
8595 return
8597 d = str(gef.arch.endianness)
8598 for value in values:
8599 value = parse_address(value) & ((1 << size * 8) - 1)
8600 vstr = struct.pack(d + fcode, value)
8601 gef.memory.write(addr, vstr, length=size)
8602 addr += size
8603 return
8606@register
8607class PatchQwordCommand(PatchCommand):
8608 """Write specified QWORD to the specified address."""
8610 _cmdline_ = "patch qword"
8611 _syntax_ = f"{_cmdline_} LOCATION QWORD1 [QWORD2 [QWORD3..]]"
8612 _example_ = f"{_cmdline_} $rip 0x4141414141414141"
8614 def __init__(self) -> None:
8615 super().__init__()
8616 self.format = "qword"
8617 return
8620@register
8621class PatchDwordCommand(PatchCommand):
8622 """Write specified DWORD to the specified address."""
8624 _cmdline_ = "patch dword"
8625 _syntax_ = f"{_cmdline_} LOCATION DWORD1 [DWORD2 [DWORD3..]]"
8626 _example_ = f"{_cmdline_} $rip 0x41414141"
8628 def __init__(self) -> None:
8629 super().__init__()
8630 self.format = "dword"
8631 return
8634@register
8635class PatchWordCommand(PatchCommand):
8636 """Write specified WORD to the specified address."""
8638 _cmdline_ = "patch word"
8639 _syntax_ = f"{_cmdline_} LOCATION WORD1 [WORD2 [WORD3..]]"
8640 _example_ = f"{_cmdline_} $rip 0x4141"
8642 def __init__(self) -> None:
8643 super().__init__()
8644 self.format = "word"
8645 return
8648@register
8649class PatchByteCommand(PatchCommand):
8650 """Write specified BYTE to the specified address."""
8652 _cmdline_ = "patch byte"
8653 _syntax_ = f"{_cmdline_} LOCATION BYTE1 [BYTE2 [BYTE3..]]"
8654 _example_ = f"{_cmdline_} $pc 0x41 0x41 0x41 0x41 0x41"
8656 def __init__(self) -> None:
8657 super().__init__()
8658 self.format = "byte"
8659 return
8662@register
8663class PatchStringCommand(GenericCommand):
8664 """Write specified string to the specified memory location pointed by ADDRESS."""
8666 _cmdline_ = "patch string"
8667 _syntax_ = f"{_cmdline_} ADDRESS \"double backslash-escaped string\""
8668 _example_ = [
8669 f"{_cmdline_} $sp \"GEFROCKS\"",
8670 f"{_cmdline_} $sp \"\\\\x41\\\\x41\\\\x41\\\\x41\""
8671 ]
8673 @only_if_gdb_running
8674 def do_invoke(self, argv: list[str]) -> None:
8675 argc = len(argv)
8676 if argc != 2: 8676 ↛ 8677line 8676 didn't jump to line 8677 because the condition on line 8676 was never true
8677 self.usage()
8678 return
8680 location, msg = argv[0:2]
8681 addr = align_address(parse_address(location))
8683 try:
8684 msg_as_bytes = codecs.escape_decode(msg, "utf-8")[0]
8685 gef.memory.write(addr, msg_as_bytes, len(msg_as_bytes)) # type: ignore
8686 except (binascii.Error, gdb.error):
8687 err(f"Could not decode '\\xXX' encoded string \"{msg}\"")
8688 return
8691@lru_cache()
8692def dereference_from(address: int) -> list[str]:
8693 if not is_alive(): 8693 ↛ 8694line 8693 didn't jump to line 8694 because the condition on line 8693 was never true
8694 return [format_address(address),]
8696 code_color = gef.config["theme.dereference_code"]
8697 string_color = gef.config["theme.dereference_string"]
8698 max_recursion = gef.config["dereference.max_recursion"] or 10
8699 addr = lookup_address(align_address(address))
8700 msg = [format_address(addr.value),]
8701 seen_addrs = set()
8703 while addr.section and max_recursion:
8704 if addr.value in seen_addrs:
8705 msg.append("[loop detected]")
8706 break
8707 seen_addrs.add(addr.value)
8709 max_recursion -= 1
8711 # Is this value a pointer or a value?
8712 # -- If it's a pointer, dereference
8713 deref = addr.dereference()
8714 if deref is None:
8715 # if here, dereferencing addr has triggered a MemoryError, no need to go further
8716 msg.append(str(addr))
8717 break
8719 new_addr = lookup_address(deref)
8720 if new_addr.valid:
8721 addr = new_addr
8722 msg.append(str(addr))
8723 continue
8725 # -- Otherwise try to parse the value
8726 if addr.section: 8726 ↛ 8747line 8726 didn't jump to line 8747 because the condition on line 8726 was always true
8727 if addr.section.is_executable() and addr.is_in_text_segment() and not is_ascii_string(addr.value):
8728 insn = gef_current_instruction(addr.value)
8729 insn_str = f"{insn.location} {insn.mnemonic} {', '.join(insn.operands)}"
8730 msg.append(Color.colorify(insn_str, code_color))
8731 break
8733 elif addr.section.permission & Permission.READ: 8733 ↛ 8747line 8733 didn't jump to line 8747 because the condition on line 8733 was always true
8734 if is_ascii_string(addr.value):
8735 s = gef.memory.read_cstring(addr.value)
8736 if len(s) < gef.arch.ptrsize:
8737 txt = f'{format_address(deref)} ("{Color.colorify(s, string_color)}"?)'
8738 elif len(s) > GEF_MAX_STRING_LENGTH:
8739 txt = Color.colorify(f'"{s[:GEF_MAX_STRING_LENGTH]}[...]"', string_color)
8740 else:
8741 txt = Color.colorify(f'"{s}"', string_color)
8743 msg.append(txt)
8744 break
8746 # if not able to parse cleanly, simply display and break
8747 msg.append(format_address(deref))
8748 break
8750 return msg
8753@register
8754class DereferenceCommand(GenericCommand):
8755 """Dereference recursively from an address and display information. This acts like WinDBG `dps`
8756 command."""
8758 _cmdline_ = "dereference"
8759 _syntax_ = f"{_cmdline_} [-h] [--length LENGTH] [--reference REFERENCE] [address]"
8760 _aliases_ = ["telescope", ]
8761 _example_ = f"{_cmdline_} --length 20 --reference $sp+0x10 $sp"
8763 def __init__(self) -> None:
8764 super().__init__(complete=gdb.COMPLETE_LOCATION)
8765 self["max_recursion"] = (7, "Maximum level of pointer recursion")
8766 return
8768 @staticmethod
8769 def pprint_dereferenced(addr: int, idx: int, base_offset: int = 0) -> str:
8770 base_address_color = gef.config["theme.dereference_base_address"]
8771 registers_color = gef.config["theme.dereference_register_value"]
8773 sep = f" {RIGHT_ARROW} "
8774 memalign = gef.arch.ptrsize
8776 offset = idx * memalign
8777 current_address = align_address(addr + offset)
8778 addrs = dereference_from(current_address)
8779 addr_l = format_address(int(addrs[0], 16))
8780 ma = (memalign*2 + 2)
8781 line = (
8782 f"{Color.colorify(addr_l, base_address_color)}{VERTICAL_LINE}"
8783 f"{base_offset+offset:+#07x}: {sep.join(addrs[1:]):{ma}s}"
8784 )
8786 register_hints = []
8788 for regname in gef.arch.all_registers:
8789 regvalue = gef.arch.register(regname)
8790 if current_address == regvalue:
8791 register_hints.append(regname)
8793 if register_hints:
8794 m = f"\t{LEFT_ARROW}{', '.join(list(register_hints))}"
8795 line += Color.colorify(m, registers_color)
8797 offset += memalign
8798 return line
8800 @only_if_gdb_running
8801 @parse_arguments({"address": "$sp"}, {("-r", "--reference"): "", ("-l", "--length"): 10})
8802 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
8803 args : argparse.Namespace = kwargs["arguments"]
8804 nb = args.length
8806 target = args.address
8807 target_addr = parse_address(target)
8809 reference = args.reference or target
8810 ref_addr = parse_address(reference)
8812 if process_lookup_address(target_addr) is None:
8813 err(f"Unmapped address: '{target}'")
8814 return
8816 if process_lookup_address(ref_addr) is None: 8816 ↛ 8817line 8816 didn't jump to line 8817 because the condition on line 8816 was never true
8817 err(f"Unmapped address: '{reference}'")
8818 return
8820 if gef.config["context.grow_stack_down"] is True: 8820 ↛ 8821line 8820 didn't jump to line 8821 because the condition on line 8820 was never true
8821 insnum_step = -1
8822 if nb > 0:
8823 from_insnum = nb * (self.repeat_count + 1) - 1
8824 to_insnum = self.repeat_count * nb - 1
8825 else:
8826 from_insnum = self.repeat_count * nb
8827 to_insnum = nb * (self.repeat_count + 1)
8828 else:
8829 insnum_step = 1
8830 if nb > 0:
8831 from_insnum = self.repeat_count * nb
8832 to_insnum = nb * (self.repeat_count + 1)
8833 else:
8834 from_insnum = nb * (self.repeat_count + 1) + 1
8835 to_insnum = (self.repeat_count * nb) + 1
8837 start_address = align_address(target_addr)
8838 base_offset = start_address - align_address(ref_addr)
8840 dereference_cmd = gef.gdb.commands["dereference"]
8841 assert isinstance(dereference_cmd, DereferenceCommand)
8842 for i in range(from_insnum, to_insnum, insnum_step):
8843 gef_print(dereference_cmd.pprint_dereferenced(start_address, i, base_offset))
8845 return
8848@register
8849class ASLRCommand(GenericCommand):
8850 """View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not
8851 attached). This command allows to change that setting."""
8853 _cmdline_ = "aslr"
8854 _syntax_ = f"{_cmdline_} [(on|off)]"
8856 def do_invoke(self, argv: list[str]) -> None:
8857 argc = len(argv)
8859 if argc == 0:
8860 ret = gdb.execute("show disable-randomization", to_string=True) or ""
8861 i = ret.find("virtual address space is ")
8862 if i < 0: 8862 ↛ 8863line 8862 didn't jump to line 8863 because the condition on line 8862 was never true
8863 return
8865 msg = "ASLR is currently "
8866 if ret[i + 25:].strip() == "on.":
8867 msg += Color.redify("disabled")
8868 else:
8869 msg += Color.greenify("enabled")
8871 gef_print(msg)
8872 return
8874 elif argc == 1: 8874 ↛ 8886line 8874 didn't jump to line 8886 because the condition on line 8874 was always true
8875 if argv[0] == "on": 8875 ↛ 8879line 8875 didn't jump to line 8879 because the condition on line 8875 was always true
8876 info("Enabling ASLR")
8877 gdb.execute("set disable-randomization off")
8878 return
8879 elif argv[0] == "off":
8880 info("Disabling ASLR")
8881 gdb.execute("set disable-randomization on")
8882 return
8884 warn("Invalid command")
8886 self.usage()
8887 return
8890@register
8891class ResetCacheCommand(GenericCommand):
8892 """Reset cache of all stored data. This command is here for debugging and test purposes, GEF
8893 handles properly the cache reset under "normal" scenario."""
8895 _cmdline_ = "reset-cache"
8896 _syntax_ = _cmdline_
8898 def do_invoke(self, _: list[str]) -> None:
8899 reset_all_caches()
8900 return
8903@register
8904class VMMapCommand(GenericCommand):
8905 """Display a comprehensive layout of the virtual memory mapping. If a filter argument, GEF will
8906 filter out the mapping whose pathname do not match that filter."""
8908 _cmdline_ = "vmmap"
8909 _syntax_ = f"{_cmdline_} [FILTER]"
8910 _example_ = f"{_cmdline_} libc"
8912 @only_if_gdb_running
8913 @parse_arguments({"unknown_types": [""]}, {("--addr", "-a"): [""], ("--name", "-n"): [""]})
8914 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
8915 args : argparse.Namespace = kwargs["arguments"]
8916 vmmap = gef.memory.maps
8917 if not vmmap: 8917 ↛ 8918line 8917 didn't jump to line 8918 because the condition on line 8917 was never true
8918 err("No address mapping information found")
8919 return
8921 addrs: dict[str, int] = {x: parse_address(x) for x in args.addr}
8922 names: list[str] = [x for x in args.name]
8924 for arg in args.unknown_types:
8925 if not arg:
8926 continue
8928 if self.is_integer(arg): 8928 ↛ 8929line 8928 didn't jump to line 8929 because the condition on line 8928 was never true
8929 addr = int(arg, 0)
8930 else:
8931 addr = safe_parse_and_eval(arg)
8933 if addr is None:
8934 names.append(arg)
8935 warn(f"`{arg}` has no type specified. We guessed it was a name filter.")
8936 else:
8937 addrs[arg] = int(addr)
8938 warn(f"`{arg}` has no type specified. We guessed it was an address filter.")
8939 warn("You can use --name or --addr before the filter value for specifying its type manually.")
8940 gef_print()
8942 if not gef.config["gef.disable_color"]: 8942 ↛ 8943line 8942 didn't jump to line 8943 because the condition on line 8942 was never true
8943 self.show_legend()
8945 color = gef.config["theme.table_heading"]
8947 headers = ["Start", "End", "Offset", "Perm", "Path"]
8948 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color))
8950 last_printed_filter = None
8952 for entry in vmmap:
8953 names_filter = [f"name = '{x}'" for x in names if x in entry.path]
8954 addrs_filter = [f"addr = {self.format_addr_filter(arg, addr)}" for arg, addr in addrs.items()
8955 if entry.page_start <= addr < entry.page_end]
8956 filter_content = f"[{' & '.join([*names_filter, *addrs_filter])}]"
8958 if not names and not addrs:
8959 self.print_entry(entry)
8961 elif names_filter or addrs_filter:
8962 if filter_content != last_printed_filter: 8962 ↛ 8966line 8962 didn't jump to line 8966 because the condition on line 8962 was always true
8963 gef_print() # skip a line between different filters
8964 gef_print(Color.greenify(filter_content))
8965 last_printed_filter = filter_content
8966 self.print_entry(entry)
8968 gef_print()
8969 return
8971 def format_addr_filter(self, arg: str, addr: int):
8972 return f"`{arg}`" if self.is_integer(arg) else f"`{arg}` ({addr:#x})"
8974 def print_entry(self, entry: Section) -> None:
8975 line_color = ""
8976 if entry.path == "[stack]":
8977 line_color = gef.config["theme.address_stack"]
8978 elif entry.path == "[heap]": 8978 ↛ 8979line 8978 didn't jump to line 8979 because the condition on line 8978 was never true
8979 line_color = gef.config["theme.address_heap"]
8980 elif entry.permission & Permission.READ and entry.permission & Permission.EXECUTE:
8981 line_color = gef.config["theme.address_code"]
8983 line_parts = [
8984 Color.colorify(format_address(entry.page_start), line_color),
8985 Color.colorify(format_address(entry.page_end), line_color),
8986 Color.colorify(format_address(entry.offset), line_color),
8987 ]
8988 if entry.permission == Permission.ALL: 8988 ↛ 8989line 8988 didn't jump to line 8989 because the condition on line 8988 was never true
8989 line_parts.append(Color.colorify(str(entry.permission), "underline " + line_color))
8990 else:
8991 line_parts.append(Color.colorify(str(entry.permission), line_color))
8993 line_parts.append(Color.colorify(entry.path, line_color))
8994 line = " ".join(line_parts)
8996 gef_print(line)
8997 return
8999 def show_legend(self) -> None:
9000 code_title = Color.colorify("Code", gef.config["theme.address_code"])
9001 stack_title = Color.colorify("Stack", gef.config["theme.address_stack"])
9002 heap_title = Color.colorify("Heap", gef.config["theme.address_heap"])
9003 gef_print(f"[ Legend: {code_title} | {stack_title} | {heap_title} ]")
9004 return
9006 def is_integer(self, n: str) -> bool:
9007 try:
9008 int(n, 0)
9009 except ValueError:
9010 return False
9011 return True
9014@register
9015class XFilesCommand(GenericCommand):
9016 """Shows all libraries (and sections) loaded by binary. This command extends the GDB command
9017 `info files`, by retrieving more information from extra sources, and providing a better
9018 display. If an argument FILE is given, the output will grep information related to only that file.
9019 If an argument name is also given, the output will grep to the name within FILE."""
9021 _cmdline_ = "xfiles"
9022 _syntax_ = f"{_cmdline_} [FILE [NAME]]"
9023 _example_ = f"\n{_cmdline_} libc\n{_cmdline_} libc IO_vtables"
9025 @only_if_gdb_running
9026 def do_invoke(self, argv: list[str]) -> None:
9027 color = gef.config["theme.table_heading"]
9028 headers = ["Start", "End", "Name", "File"]
9029 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=gef.arch.ptrsize*2+3), color))
9031 filter_by_file = argv[0] if argv and argv[0] else None
9032 filter_by_name = argv[1] if len(argv) > 1 and argv[1] else None
9034 for xfile in get_info_files():
9035 if filter_by_file: 9035 ↛ 9036line 9035 didn't jump to line 9036 because the condition on line 9035 was never true
9036 if filter_by_file not in xfile.filename:
9037 continue
9038 if filter_by_name and filter_by_name not in xfile.name:
9039 continue
9041 line_parts = [
9042 format_address(xfile.zone_start),
9043 format_address(xfile.zone_end),
9044 f"{xfile.name:<21s}",
9045 xfile.filename,
9046 ]
9047 gef_print(" ".join(line_parts))
9048 return
9051@register
9052class XAddressInfoCommand(GenericCommand):
9053 """Retrieve and display runtime information for the location(s) given as parameter."""
9055 _cmdline_ = "xinfo"
9056 _syntax_ = f"{_cmdline_} LOCATION"
9057 _example_ = f"{_cmdline_} $pc"
9059 def __init__(self) -> None:
9060 super().__init__(complete=gdb.COMPLETE_LOCATION)
9061 return
9063 @only_if_gdb_running
9064 def do_invoke(self, argv: list[str]) -> None:
9065 if not argv:
9066 err("At least one valid address must be specified")
9067 self.usage()
9068 return
9070 for sym in argv:
9071 try:
9072 addr = align_address(parse_address(sym))
9073 gef_print(titlify(f"xinfo: {addr:#x}"))
9074 self.infos(addr)
9076 except gdb.error as gdb_err:
9077 err(f"{gdb_err}")
9078 return
9080 def infos(self, address: int) -> None:
9081 addr = lookup_address(address)
9082 if not addr.valid: 9082 ↛ 9083line 9082 didn't jump to line 9083 because the condition on line 9082 was never true
9083 warn(f"Cannot reach {address:#x} in memory space")
9084 return
9086 sect = addr.section
9087 info = addr.info
9089 if sect: 9089 ↛ 9097line 9089 didn't jump to line 9097 because the condition on line 9089 was always true
9090 gef_print(f"Page: {format_address(sect.page_start)} {RIGHT_ARROW} "
9091 f"{format_address(sect.page_end)} (size={sect.page_end-sect.page_start:#x})"
9092 f"\nPermissions: {sect.permission}"
9093 f"\nPathname: {sect.path}"
9094 f"\nOffset (from page): {addr.value-sect.page_start:#x}"
9095 f"\nInode: {sect.inode}")
9097 if info:
9098 gef_print(f"Segment: {info.name} "
9099 f"({format_address(info.zone_start)}-{format_address(info.zone_end)})"
9100 f"\nOffset (from segment): {addr.value-info.zone_start:#x}")
9102 sym = gdb_get_location_from_symbol(address)
9103 if sym:
9104 name, offset = sym
9105 msg = f"Symbol: {name}"
9106 if offset: 9106 ↛ 9108line 9106 didn't jump to line 9108 because the condition on line 9106 was always true
9107 msg += f"+{offset:d}"
9108 gef_print(msg)
9110 return
9113@register
9114class XorMemoryCommand(GenericCommand):
9115 """XOR a block of memory. The command allows to simply display the result, or patch it
9116 runtime at runtime."""
9118 _cmdline_ = "xor-memory"
9119 _syntax_ = f"{_cmdline_} (display|patch) ADDRESS SIZE KEY"
9121 def __init__(self) -> None:
9122 super().__init__(prefix=True)
9123 return
9125 def do_invoke(self, _: list[str]) -> None:
9126 self.usage()
9127 return
9130@register
9131class XorMemoryDisplayCommand(GenericCommand):
9132 """Display a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be
9133 provided in hexadecimal format."""
9135 _cmdline_ = "xor-memory display"
9136 _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY"
9137 _example_ = f"{_cmdline_} $sp 16 41414141"
9139 @only_if_gdb_running
9140 def do_invoke(self, argv: list[str]) -> None:
9141 if len(argv) != 3: 9141 ↛ 9142line 9141 didn't jump to line 9142 because the condition on line 9141 was never true
9142 self.usage()
9143 return
9145 address = parse_address(argv[0])
9146 length = int(argv[1], 0)
9147 key = argv[2]
9148 block = gef.memory.read(address, length)
9149 info(f"Displaying XOR-ing {address:#x}-{address + len(block):#x} with {key!r}")
9151 gef_print(titlify("Original block"))
9152 gef_print(hexdump(block, base=address))
9154 gef_print(titlify("XOR-ed block"))
9155 gef_print(hexdump(xor(block, key), base=address))
9156 return
9159@register
9160class XorMemoryPatchCommand(GenericCommand):
9161 """Patch a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be
9162 provided in hexadecimal format."""
9164 _cmdline_ = "xor-memory patch"
9165 _syntax_ = f"{_cmdline_} ADDRESS SIZE KEY"
9166 _example_ = f"{_cmdline_} $sp 16 41414141"
9168 @only_if_gdb_running
9169 def do_invoke(self, argv: list[str]) -> None:
9170 if len(argv) != 3: 9170 ↛ 9171line 9170 didn't jump to line 9171 because the condition on line 9170 was never true
9171 self.usage()
9172 return
9174 address = parse_address(argv[0])
9175 length = int(argv[1], 0)
9176 key = argv[2]
9177 block = gef.memory.read(address, length)
9178 info(f"Patching XOR-ing {address:#x}-{address + len(block):#x} with {key!r}")
9179 xored_block = xor(block, key)
9180 gef.memory.write(address, xored_block, length)
9181 return
9184@register
9185class TraceRunCommand(GenericCommand):
9186 """Create a runtime trace of all instructions executed from $pc to LOCATION specified. The
9187 trace is stored in a text file that can be next imported in IDA Pro to visualize the runtime
9188 path."""
9190 _cmdline_ = "trace-run"
9191 _syntax_ = f"{_cmdline_} LOCATION [MAX_CALL_DEPTH]"
9192 _example_ = f"{_cmdline_} 0x555555554610"
9194 def __init__(self) -> None:
9195 super().__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION)
9196 self["max_tracing_recursion"] = (1, "Maximum depth of tracing")
9197 self["tracefile_prefix"] = ("./gef-trace-", "Specify the tracing output file prefix")
9198 return
9200 @only_if_gdb_running
9201 def do_invoke(self, argv: list[str]) -> None:
9202 if len(argv) not in (1, 2): 9202 ↛ 9203line 9202 didn't jump to line 9203 because the condition on line 9202 was never true
9203 self.usage()
9204 return
9206 if len(argv) == 2 and argv[1].isdigit(): 9206 ↛ 9207line 9206 didn't jump to line 9207 because the condition on line 9206 was never true
9207 depth = int(argv[1])
9208 else:
9209 depth = 1
9211 try:
9212 loc_start = gef.arch.pc
9213 loc_end = parse_address(argv[0])
9214 except gdb.error as e:
9215 err(f"Invalid location: {e}")
9216 return
9218 self.trace(loc_start, loc_end, depth)
9219 return
9221 def get_frames_size(self) -> int:
9222 n = 0
9223 f = gdb.newest_frame()
9224 while f:
9225 n += 1
9226 f = f.older()
9227 return n
9229 def trace(self, loc_start: int, loc_end: int, depth: int) -> None:
9230 info(f"Tracing from {loc_start:#x} to {loc_end:#x} (max depth={depth:d})")
9231 logfile = f"{self['tracefile_prefix']}{loc_start:#x}-{loc_end:#x}.txt"
9232 with RedirectOutputContext(to_file=logfile):
9233 hide_context()
9234 self.start_tracing(loc_start, loc_end, depth)
9235 unhide_context()
9236 ok(f"Done, logfile stored as '{logfile}'")
9237 info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path")
9238 return
9240 def start_tracing(self, loc_start: int, loc_end: int, depth: int) -> None:
9241 loc_cur = loc_start
9242 frame_count_init = self.get_frames_size()
9244 gef_print("#",
9245 f"# Execution tracing of {get_filepath()}",
9246 f"# Start address: {format_address(loc_start)}",
9247 f"# End address: {format_address(loc_end)}",
9248 f"# Recursion level: {depth:d}",
9249 "# automatically generated by gef.py",
9250 "#\n", sep="\n")
9252 while loc_cur != loc_end: 9252 ↛ 9271line 9252 didn't jump to line 9271 because the condition on line 9252 was always true
9253 try:
9254 delta = self.get_frames_size() - frame_count_init
9256 if delta <= depth:
9257 gdb.execute("stepi")
9258 else:
9259 gdb.execute("finish")
9261 loc_cur = gef.arch.pc
9262 gdb.flush()
9264 except gdb.error as e:
9265 gef_print("#",
9266 f"# Execution interrupted at address {format_address(loc_cur)}",
9267 f"# Exception: {e}",
9268 "#\n", sep="\n")
9269 break
9271 return
9274@register
9275class PatternCommand(GenericCommand):
9276 """Generate or Search a De Bruijn Sequence of unique substrings of length N
9277 and a total length of LENGTH. The default value of N is set to match the
9278 currently loaded architecture."""
9280 _cmdline_ = "pattern"
9281 _syntax_ = f"{_cmdline_} (create|search) ARGS"
9283 def __init__(self) -> None:
9284 super().__init__(prefix=True)
9285 self["length"] = (1024, "Default length of a cyclic buffer to generate")
9286 return
9288 def do_invoke(self, _: list[str]) -> None:
9289 self.usage()
9290 return
9293@register
9294class PatternCreateCommand(GenericCommand):
9295 """Generate a De Bruijn Sequence of unique substrings of length N and a
9296 total length of LENGTH. The default value of N is set to match the currently
9297 loaded architecture."""
9299 _cmdline_ = "pattern create"
9300 _syntax_ = f"{_cmdline_} [-h] [-n N] [length]"
9301 _example_ = [
9302 f"{_cmdline_} 4096",
9303 f"{_cmdline_} -n 4 128"
9304 ]
9306 @parse_arguments({"length": 0}, {"-n": 0,})
9307 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
9308 args : argparse.Namespace = kwargs["arguments"]
9309 length = args.length or gef.config["pattern.length"]
9310 n = args.n or gef.arch.ptrsize
9311 info(f"Generating a pattern of {length:d} bytes (n={n:d})")
9312 pattern_str = gef_pystring(generate_cyclic_pattern(length, n))
9313 gef_print(pattern_str)
9314 ok(f"Saved as '{gef_convenience(pattern_str)}'")
9315 return
9318@register
9319class PatternSearchCommand(GenericCommand):
9320 """Search a De Bruijn Sequence of unique substrings of length N and a
9321 maximum total length of MAX_LENGTH. The default value of N is set to match
9322 the currently loaded architecture. The PATTERN argument can be a GDB symbol
9323 (such as a register name), a string or a hexadecimal value"""
9325 _cmdline_ = "pattern search"
9326 _syntax_ = f"{_cmdline_} [-h] [-n N] [--max-length MAX_LENGTH] [pattern]"
9327 _example_ = [f"{_cmdline_} $pc",
9328 f"{_cmdline_} 0x61616164",
9329 f"{_cmdline_} aaab"]
9330 _aliases_ = ["pattern offset"]
9332 @only_if_gdb_running
9333 @parse_arguments({"pattern": ""}, {("--period", "-n"): 0, ("--max-length", "-l"): 0})
9334 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
9335 args = kwargs["arguments"]
9336 if not args.pattern: 9336 ↛ 9337line 9336 didn't jump to line 9337 because the condition on line 9336 was never true
9337 warn("No pattern provided")
9338 return
9340 max_length = args.max_length or gef.config["pattern.length"]
9341 n = args.period or gef.arch.ptrsize
9342 if n not in (2, 4, 8) or n > gef.arch.ptrsize: 9342 ↛ 9343line 9342 didn't jump to line 9343 because the condition on line 9342 was never true
9343 err("Incorrect value for period")
9344 return
9345 self.search(args.pattern, max_length, n)
9346 return
9348 def search(self, pattern: str, size: int, period: int) -> None:
9349 pattern_be, pattern_le = None, None
9351 # 1. check if it's a symbol (like "$sp" or "0x1337")
9352 symbol = safe_parse_and_eval(pattern)
9353 if symbol:
9354 addr = int(abs(to_unsigned_long(symbol)))
9355 dereferenced_value = dereference(addr)
9356 if dereferenced_value: 9356 ↛ 9357line 9356 didn't jump to line 9357 because the condition on line 9356 was never true
9357 addr = int(abs(to_unsigned_long(dereferenced_value)))
9358 mask = (1<<(8 * period))-1
9359 addr &= mask
9360 pattern_le = addr.to_bytes(period, 'little')
9361 pattern_be = addr.to_bytes(period, 'big')
9362 else:
9363 # 2. assume it's a plain string
9364 pattern_be = gef_pybytes(pattern)
9365 pattern_le = gef_pybytes(pattern[::-1])
9367 info(f"Searching for '{pattern_le.hex()}'/'{pattern_be.hex()}' with period={period}")
9368 cyclic_pattern = generate_cyclic_pattern(size, period)
9369 off = cyclic_pattern.find(pattern_le)
9370 if off >= 0:
9371 ok(f"Found at offset {off:d} (little-endian search) "
9372 f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}")
9373 return
9375 off = cyclic_pattern.find(pattern_be)
9376 if off >= 0: 9376 ↛ 9377line 9376 didn't jump to line 9377 because the condition on line 9376 was never true
9377 ok(f"Found at offset {off:d} (big-endian search) "
9378 f"{Color.colorify('likely', 'bold green') if gef.arch.endianness == Endianness.BIG_ENDIAN else ''}")
9379 return
9381 err(f"Pattern '{pattern}' not found")
9382 return
9385@register
9386class ChecksecCommand(GenericCommand):
9387 """Checksec the security properties of the current executable or passed as argument. The
9388 command checks for the following protections:
9389 - PIE
9390 - NX
9391 - RelRO
9392 - Glibc Stack Canaries
9393 - Fortify Source"""
9395 _cmdline_ = "checksec"
9396 _syntax_ = f"{_cmdline_} [FILENAME]"
9397 _example_ = f"{_cmdline_} /bin/ls"
9399 def __init__(self) -> None:
9400 super().__init__(complete=gdb.COMPLETE_FILENAME)
9401 return
9403 def do_invoke(self, argv: list[str]) -> None:
9404 argc = len(argv)
9406 if argc == 0: 9406 ↛ 9411line 9406 didn't jump to line 9411 because the condition on line 9406 was always true
9407 filename = get_filepath()
9408 if filename is None: 9408 ↛ 9409line 9408 didn't jump to line 9409 because the condition on line 9408 was never true
9409 warn("No executable/library specified")
9410 return
9411 elif argc == 1:
9412 filename = os.path.realpath(os.path.expanduser(argv[0]))
9413 if not os.access(filename, os.R_OK):
9414 err("Invalid filename")
9415 return
9416 else:
9417 self.usage()
9418 return
9420 info(f"{self._cmdline_} for '{filename}'")
9421 self.print_security_properties(filename)
9422 return
9424 def print_security_properties(self, filename: str) -> None:
9425 sec = Elf(filename).checksec
9426 for prop in sec:
9427 if prop in ("Partial RelRO", "Full RelRO"): continue
9428 val = sec[prop]
9429 msg = Color.greenify(Color.boldify(TICK)) if val is True else Color.redify(Color.boldify(CROSS))
9430 if val and prop == "Canary" and is_alive(): 9430 ↛ 9431line 9430 didn't jump to line 9431 because the condition on line 9430 was never true
9431 canary = gef.session.canary[0] if gef.session.canary else 0
9432 msg += f"(value: {canary:#x})"
9434 gef_print(f"{prop:<30s}: {msg}")
9436 if sec["Full RelRO"]:
9437 gef_print(f"{'RelRO':<30s}: {Color.greenify('Full')}")
9438 elif sec["Partial RelRO"]: 9438 ↛ 9441line 9438 didn't jump to line 9441 because the condition on line 9438 was always true
9439 gef_print(f"{'RelRO':<30s}: {Color.yellowify('Partial')}")
9440 else:
9441 gef_print(f"{'RelRO':<30s}: {Color.redify(Color.boldify(CROSS))}")
9442 return
9445@register
9446class GotCommand(GenericCommand):
9447 """Display current status of the got inside the process."""
9449 _cmdline_ = "got"
9450 _syntax_ = f"{_cmdline_} [FUNCTION_NAME ...] "
9451 _example_ = "got read printf exit"
9453 def __init__(self):
9454 super().__init__()
9455 self["function_resolved"] = ("green",
9456 "Line color of the got command output for resolved function")
9457 self["function_not_resolved"] = ("yellow",
9458 "Line color of the got command output for unresolved function")
9459 return
9461 def build_line(self, name: str, _path: str, color: str, address_val: int, got_address: int) -> str:
9462 line = f"[{hex(address_val)}] "
9463 line += Color.colorify(f"{name} {RIGHT_ARROW} {hex(got_address)}", color)
9464 return line
9466 @only_if_gdb_running
9467 @parse_arguments({"symbols": [""]}, {"--all": False})
9468 def do_invoke(self, _: list[str], **kwargs: Any) -> None:
9469 args : argparse.Namespace = kwargs["arguments"]
9470 vmmap = gef.memory.maps
9471 mapfiles = [mapfile for mapfile in vmmap if
9472 (args.all or mapfile.path == str(gef.session.file)) and
9473 pathlib.Path(mapfile.realpath).is_file() and
9474 mapfile.permission & Permission.EXECUTE]
9475 for mapfile in mapfiles:
9476 self.print_got_for(mapfile.path, mapfile.realpath, args.symbols)
9478 def print_got_for(self, file: str, realpath: str, argv: list[str]) -> None:
9479 readelf = gef.session.constants["readelf"]
9481 elf_file = realpath
9482 elf_virtual_path = file
9484 func_names_filter = argv if argv else []
9485 vmmap = gef.memory.maps
9486 base_address = min(x.page_start for x in vmmap if x.path == elf_virtual_path)
9487 end_address = max(x.page_end for x in vmmap if x.path == elf_virtual_path)
9489 # get the checksec output.
9490 checksec_status = Elf(elf_file).checksec
9491 relro_status = "Full RelRO"
9492 full_relro = checksec_status["Full RelRO"]
9493 pie = checksec_status["PIE"] # if pie we will have offset instead of abs address.
9495 if not full_relro: 9495 ↛ 9496line 9495 didn't jump to line 9496 because the condition on line 9495 was never true
9496 relro_status = "Partial RelRO"
9497 partial_relro = checksec_status["Partial RelRO"]
9499 if not partial_relro:
9500 relro_status = "No RelRO"
9502 # retrieve jump slots using readelf
9503 lines = gef_execute_external([readelf, "--wide", "--relocs", elf_file], as_list=True)
9504 jmpslots = [line for line in lines if "JUMP" in line]
9506 gef_print(f"{titlify(file)}\n\nGOT protection: {relro_status} | GOT functions: {len(jmpslots)}\n ")
9508 for line in jmpslots:
9509 address, _, _, _, name = line.split()[:5]
9511 # if we have a filter let's skip the entries that are not requested.
9512 if func_names_filter: 9512 ↛ 9516line 9512 didn't jump to line 9516 because the condition on line 9512 was always true
9513 if not any(map(lambda x: x in name, func_names_filter)):
9514 continue
9516 address_val = int(address, 16)
9518 # address_val is an offset from the base_address if we have PIE.
9519 if pie or is_remote_debug(): 9519 ↛ 9523line 9519 didn't jump to line 9523 because the condition on line 9519 was always true
9520 address_val = base_address + address_val
9522 # read the address of the function.
9523 got_address = gef.memory.read_integer(address_val)
9525 # for the swag: different colors if the function has been resolved or not.
9526 if base_address < got_address < end_address: 9526 ↛ 9527line 9526 didn't jump to line 9527 because the condition on line 9526 was never true
9527 color = self["function_not_resolved"]
9528 else:
9529 color = self["function_resolved"]
9531 line = self.build_line(name, elf_virtual_path, color, address_val, got_address)
9532 gef_print(line)
9533 return
9536@register
9537class HighlightCommand(GenericCommand):
9538 """Highlight user-defined text matches in GEF output universally."""
9539 _cmdline_ = "highlight"
9540 _syntax_ = f"{_cmdline_} (add|remove|list|clear)"
9541 _aliases_ = ["hl"]
9543 def __init__(self) -> None:
9544 super().__init__(prefix=True)
9545 self["regex"] = (False, "Enable regex highlighting")
9547 def do_invoke(self, _: list[str]) -> None:
9548 return self.usage()
9551@register
9552class HighlightListCommand(GenericCommand):
9553 """Show the current highlight table with matches to colors."""
9554 _cmdline_ = "highlight list"
9555 _aliases_ = ["highlight ls", "hll"]
9556 _syntax_ = _cmdline_
9558 def print_highlight_table(self) -> None:
9559 if not gef.ui.highlight_table:
9560 err("no matches found")
9561 return
9563 left_pad = max(map(len, gef.ui.highlight_table.keys()))
9564 for match, color in sorted(gef.ui.highlight_table.items()):
9565 print(f"{Color.colorify(match.ljust(left_pad), color)} {VERTICAL_LINE} "
9566 f"{Color.colorify(color, color)}")
9567 return
9569 def do_invoke(self, _: list[str]) -> None:
9570 return self.print_highlight_table()
9573@register
9574class HighlightClearCommand(GenericCommand):
9575 """Clear the highlight table, remove all matches."""
9576 _cmdline_ = "highlight clear"
9577 _aliases_ = ["hlc"]
9578 _syntax_ = _cmdline_
9580 def do_invoke(self, _: list[str]) -> None:
9581 return gef.ui.highlight_table.clear()
9584@register
9585class HighlightAddCommand(GenericCommand):
9586 """Add a match to the highlight table."""
9587 _cmdline_ = "highlight add"
9588 _syntax_ = f"{_cmdline_} MATCH COLOR"
9589 _aliases_ = ["highlight set", "hla"]
9590 _example_ = f"{_cmdline_} 41414141 yellow"
9592 def do_invoke(self, argv: list[str]) -> None:
9593 if len(argv) < 2: 9593 ↛ 9594line 9593 didn't jump to line 9594 because the condition on line 9593 was never true
9594 return self.usage()
9596 match, color = argv
9597 gef.ui.highlight_table[match] = color
9598 return
9601@register
9602class HighlightRemoveCommand(GenericCommand):
9603 """Remove a match in the highlight table."""
9604 _cmdline_ = "highlight remove"
9605 _syntax_ = f"{_cmdline_} MATCH"
9606 _aliases_ = [
9607 "highlight delete",
9608 "highlight del",
9609 "highlight unset",
9610 "highlight rm",
9611 "hlr",
9612 ]
9613 _example_ = f"{_cmdline_} remove 41414141"
9615 def do_invoke(self, argv: list[str]) -> None:
9616 if not argv:
9617 return self.usage()
9619 gef.ui.highlight_table.pop(argv[0], None)
9620 return
9623@register
9624class FormatStringSearchCommand(GenericCommand):
9625 """Exploitable format-string helper: this command will set up specific breakpoints
9626 at well-known dangerous functions (printf, snprintf, etc.), and check if the pointer
9627 holding the format string is writable, and therefore susceptible to format string
9628 attacks if an attacker can control its content."""
9629 _cmdline_ = "format-string-helper"
9630 _syntax_ = _cmdline_
9631 _aliases_ = ["fmtstr-helper",]
9633 def do_invoke(self, _: list[str]) -> None:
9634 dangerous_functions = {
9635 "printf": 0,
9636 "sprintf": 1,
9637 "fprintf": 1,
9638 "snprintf": 2,
9639 "vsnprintf": 2,
9640 }
9642 nb_installed_breaks = 0
9644 with RedirectOutputContext(to_file="/dev/null"):
9645 for function_name in dangerous_functions:
9646 argument_number = dangerous_functions[function_name]
9647 FormatStringBreakpoint(function_name, argument_number)
9648 nb_installed_breaks += 1
9650 ok(f"Enabled {nb_installed_breaks} FormatString "
9651 f"breakpoint{'s' if nb_installed_breaks > 1 else ''}")
9652 return
9655@register
9656class HeapAnalysisCommand(GenericCommand):
9657 """Heap vulnerability analysis helper: this command aims to track dynamic heap allocation
9658 done through malloc()/free() to provide some insights on possible heap vulnerabilities. The
9659 following vulnerabilities are checked:
9660 - NULL free
9661 - Use-after-Free
9662 - Double Free
9663 - Heap overlap"""
9664 _cmdline_ = "heap-analysis-helper"
9665 _syntax_ = _cmdline_
9667 def __init__(self) -> None:
9668 super().__init__(complete=gdb.COMPLETE_NONE)
9669 self["check_free_null"] = (False, "Break execution when a free(NULL) is encountered")
9670 self["check_double_free"] = (True, "Break execution when a double free is encountered")
9671 self["check_weird_free"] = (True, "Break execution when free() is called against a non-tracked pointer")
9672 self["check_uaf"] = (True, "Break execution when a possible Use-after-Free condition is found")
9673 self["check_heap_overlap"] = (True, "Break execution when a possible overlap in allocation is found")
9675 self.bp_malloc = None
9676 self.bp_calloc = None
9677 self.bp_free = None
9678 self.bp_realloc = None
9679 return
9681 @only_if_gdb_running
9682 @experimental_feature
9683 def do_invoke(self, argv: list[str]) -> None:
9684 if not argv: 9684 ↛ 9688line 9684 didn't jump to line 9688 because the condition on line 9684 was always true
9685 self.setup()
9686 return
9688 if argv[0] == "show":
9689 self.dump_tracked_allocations()
9690 return
9692 def setup(self) -> None:
9693 ok("Tracking malloc() & calloc()")
9694 self.bp_malloc = TraceMallocBreakpoint("__libc_malloc")
9695 self.bp_calloc = TraceMallocBreakpoint("__libc_calloc")
9696 ok("Tracking free()")
9697 self.bp_free = TraceFreeBreakpoint()
9698 ok("Tracking realloc()")
9699 self.bp_realloc = TraceReallocBreakpoint()
9701 ok("Disabling hardware watchpoints (this may increase the latency)")
9702 gdb.execute("set can-use-hw-watchpoints 0")
9704 info("Dynamic breakpoints correctly setup, "
9705 "GEF will break execution if a possible vulnerability is found.")
9706 warn(f"{Color.colorify('Note', 'bold underline yellow')}: "
9707 "The heap analysis slows down the execution noticeably.")
9709 # when inferior quits, we need to clean everything for a next execution
9710 gef_on_exit_hook(self.clean)
9711 return
9713 def dump_tracked_allocations(self) -> None:
9714 global gef
9716 if gef.session.heap_allocated_chunks:
9717 ok("Tracked as in-use chunks:")
9718 for addr, sz in gef.session.heap_allocated_chunks:
9719 gef_print(f"{CROSS} malloc({sz:d}) = {addr:#x}")
9720 else:
9721 ok("No malloc() chunk tracked")
9723 if gef.session.heap_freed_chunks:
9724 ok("Tracked as free-ed chunks:")
9725 for addr, sz in gef.session.heap_freed_chunks:
9726 gef_print(f"{TICK} free({sz:d}) = {addr:#x}")
9727 else:
9728 ok("No free() chunk tracked")
9729 return
9731 def clean(self, _: "gdb.ExitedEvent") -> None:
9732 global gef
9734 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Cleaning up")
9735 for bp in [self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc]:
9736 if hasattr(bp, "retbp") and bp.retbp: 9736 ↛ 9744line 9736 didn't jump to line 9744 because the condition on line 9736 was always true
9737 try:
9738 bp.retbp.delete()
9739 except RuntimeError:
9740 # in some cases, gdb was found failing to correctly remove the retbp
9741 # but they can be safely ignored since the debugging session is over
9742 pass
9744 bp.delete()
9746 for wp in gef.session.heap_uaf_watchpoints:
9747 wp.delete()
9749 gef.session.heap_allocated_chunks = []
9750 gef.session.heap_freed_chunks = []
9751 gef.session.heap_uaf_watchpoints = []
9753 ok(f"{Color.colorify('Heap-Analysis', 'yellow bold')} - Re-enabling hardware watchpoints")
9754 gdb.execute("set can-use-hw-watchpoints 1")
9756 gef_on_exit_unhook(self.clean)
9757 return
9760#
9761# GDB Function declaration
9762#
9763@deprecated("")
9764def register_function(cls: Type["GenericFunction"]) -> Type["GenericFunction"]:
9765 """Decorator for registering a new convenience function to GDB."""
9766 return cls
9769class GenericFunction(gdb.Function):
9770 """This is an abstract class for invoking convenience functions, should not be instantiated."""
9772 _function_ : str
9773 _syntax_: str = ""
9774 _example_ : str = ""
9776 def __init__(self) -> None:
9777 super().__init__(self._function_)
9779 def invoke(self, *args: Any) -> int:
9780 if not is_alive():
9781 raise gdb.GdbError("No debugging session active")
9782 return self.do_invoke(args)
9784 def arg_to_long(self, args: Any, index: int, default: int = 0) -> int:
9785 try:
9786 addr = args[index]
9787 return int(addr) if addr.address is None else int(addr.address)
9788 except IndexError:
9789 return default
9791 def do_invoke(self, args: Any) -> int:
9792 raise NotImplementedError
9795@register
9796class StackOffsetFunction(GenericFunction):
9797 """Return the current stack base address plus an optional offset."""
9798 _function_ = "_stack"
9799 _syntax_ = f"${_function_}()"
9801 def do_invoke(self, args: list) -> int:
9802 base = get_section_base_address("[stack]")
9803 if not base: 9803 ↛ 9804line 9803 didn't jump to line 9804 because the condition on line 9803 was never true
9804 raise gdb.GdbError("Stack not found")
9806 return self.arg_to_long(args, 0) + base
9809@register
9810class HeapBaseFunction(GenericFunction):
9811 """Return the current heap base address plus an optional offset."""
9812 _function_ = "_heap"
9813 _syntax_ = f"${_function_}()"
9815 def do_invoke(self, args: list[str]) -> int:
9816 base = gef.heap.base_address
9817 if not base: 9817 ↛ 9818line 9817 didn't jump to line 9818 because the condition on line 9817 was never true
9818 base = get_section_base_address("[heap]")
9819 if not base:
9820 raise gdb.GdbError("Heap not found")
9821 return self.arg_to_long(args, 0) + base
9824@register
9825class SectionBaseFunction(GenericFunction):
9826 """Return the matching file's base address plus an optional offset.
9827 Defaults to current file. Note that quotes need to be escaped"""
9828 _function_ = "_base"
9829 _syntax_ = "$_base([filepath])"
9830 _example_ = "p $_base(\\\"/usr/lib/ld-2.33.so\\\")"
9832 def do_invoke(self, args: list) -> int:
9833 addr = 0
9834 try:
9835 name = args[0].string()
9836 except IndexError:
9837 assert gef.session.file
9838 name = gef.session.file.name
9839 except gdb.error:
9840 err(f"Invalid arg: {args[0]}")
9841 return 0
9843 try:
9844 base = get_section_base_address(name)
9845 if base: 9845 ↛ 9850line 9845 didn't jump to line 9850 because the condition on line 9845 was always true
9846 addr = int(base)
9847 except TypeError:
9848 err(f"Cannot find section {name}")
9849 return 0
9850 return addr
9853@register
9854class BssBaseFunction(GenericFunction):
9855 """Return the current bss base address plus the given offset."""
9856 _function_ = "_bss"
9857 _syntax_ = f"${_function_}([OFFSET])"
9858 _example_ = "deref $_bss(0x20)"
9860 def do_invoke(self, args: list) -> int:
9861 base = get_zone_base_address(".bss")
9862 if not base: 9862 ↛ 9863line 9862 didn't jump to line 9863 because the condition on line 9862 was never true
9863 raise gdb.GdbError("BSS not found")
9864 return self.arg_to_long(args, 0) + base
9867@register
9868class GotBaseFunction(GenericFunction):
9869 """Return the current GOT base address plus the given offset."""
9870 _function_ = "_got"
9871 _syntax_ = f"${_function_}([OFFSET])"
9872 _example_ = "deref $_got(0x20)"
9874 def do_invoke(self, args: list) -> int:
9875 base = get_zone_base_address(".got")
9876 if not base: 9876 ↛ 9877line 9876 didn't jump to line 9877 because the condition on line 9876 was never true
9877 raise gdb.GdbError("GOT not found")
9878 return base + self.arg_to_long(args, 0)
9881@register
9882class GefFunctionsCommand(GenericCommand):
9883 """List the convenience functions provided by GEF."""
9884 _cmdline_ = "functions"
9885 _syntax_ = _cmdline_
9887 def __init__(self) -> None:
9888 super().__init__()
9889 self.docs = []
9890 self.should_refresh = True
9891 return
9893 def __add__(self, function: GenericFunction):
9894 """Add function to documentation."""
9895 doc = getattr(function, "__doc__", "").lstrip()
9896 if not hasattr(function, "_syntax_"): 9896 ↛ 9897line 9896 didn't jump to line 9897 because the condition on line 9896 was never true
9897 raise ValueError("Function is invalid")
9898 syntax = getattr(function, "_syntax_").lstrip()
9899 msg = f"{Color.colorify(syntax, 'bold cyan')}\n {doc}"
9900 example = getattr(function, "_example_", "").strip()
9901 if example:
9902 msg += f"\n {Color.yellowify('Example:')} {example}"
9903 self.docs.append(msg)
9904 return self
9906 def __radd__(self, function: GenericFunction):
9907 return self.__add__(function)
9909 def __str__(self) -> str:
9910 if self.should_refresh: 9910 ↛ 9912line 9910 didn't jump to line 9912 because the condition on line 9910 was always true
9911 self.__rebuild()
9912 return self.__doc__ or ""
9914 def __rebuild(self) -> None:
9915 """Rebuild the documentation for functions."""
9916 for function in gef.gdb.functions.values():
9917 self += function
9919 self.command_size = len(gef.gdb.commands)
9920 _, cols = get_terminal_size()
9921 separator = HORIZONTAL_LINE*cols
9922 self.__doc__ = f"\n{separator}\n".join(sorted(self.docs))
9923 self.should_refresh = False
9924 return
9926 def do_invoke(self, argv) -> None:
9927 self.dont_repeat()
9928 gef_print(titlify("GEF - Convenience Functions"))
9929 gef_print("These functions can be used as arguments to other "
9930 "commands to dynamically calculate values\n")
9931 gef_print(str(self))
9932 return
9935#
9936# GEF internal command classes
9937#
9938class GefCommand(gdb.Command):
9939 """GEF main command: view all new commands by typing `gef`."""
9941 _cmdline_ = "gef"
9942 _syntax_ = f"{_cmdline_} (missing|config|save|restore|set|run)"
9944 def __init__(self) -> None:
9945 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, True)
9946 gef.config["gef.follow_child"] = GefSetting(True, bool, "Automatically set GDB to follow child when forking")
9947 gef.config["gef.readline_compat"] = GefSetting(False, bool, "Workaround for readline SOH/ETX issue (SEGV)")
9948 gef.config["gef.debug"] = GefSetting(False, bool, "Enable debug mode for gef")
9949 gef.config["gef.autosave_breakpoints_file"] = GefSetting("", str, "Automatically save and restore breakpoints")
9950 gef.config["gef.disable_target_remote_overwrite"] = GefSetting(False, bool, "Disable the overwrite of `target remote`")
9951 plugins_dir = GefSetting("", str, "Autoload additional GEF commands from external directory", hooks={"on_write": [GefSetting.no_spaces, ]})
9952 plugins_dir.add_hook("on_changed", [lambda _, new_val: GefSetting.must_exist(new_val), lambda _, new_val: self.load_extra_plugins(new_val), ])
9953 gef.config["gef.extra_plugins_dir"] = plugins_dir
9954 gef.config["gef.disable_color"] = GefSetting(False, bool, "Disable all colors in GEF")
9955 gef.config["gef.tempdir"] = GefSetting(GEF_TEMP_DIR, pathlib.Path, "Directory to use for temporary/cache content", hooks={"on_write": [GefSetting.no_spaces, GefSetting.create_folder_tree]})
9956 gef.config["gef.show_deprecation_warnings"] = GefSetting(True, bool, "Toggle the display of the `deprecated` warnings")
9957 gef.config["gef.buffer"] = GefSetting(True, bool, "Internally buffer command output until completion")
9958 gef.config["gef.bruteforce_main_arena"] = GefSetting(False, bool, "Allow bruteforcing main_arena symbol if everything else fails")
9959 gef.config["gef.libc_version"] = GefSetting("", str, "Specify libc version when auto-detection fails")
9960 gef.config["gef.main_arena_offset"] = GefSetting("", str, "Offset from libc base address to main_arena symbol (int or hex). Set to empty string to disable.")
9961 gef.config["gef.propagate_debug_exception"] = GefSetting(False, bool, "If true, when debug mode is enabled, Python exceptions will be propagated all the way.")
9963 self.commands : dict[str, GenericCommand] = {}
9964 self.functions : dict[str, GenericFunction] = {}
9965 self.missing: dict[str, Exception] = {}
9966 return
9968 @property
9969 @deprecated()
9970 def loaded_commands(self) -> list[tuple[str, Type[GenericCommand], Any]]:
9971 raise ObsoleteException("Obsolete loaded_commands")
9973 @property
9974 @deprecated()
9975 def loaded_functions(self) -> list[Type[GenericFunction]]:
9976 raise ObsoleteException("Obsolete loaded_functions")
9978 @property
9979 @deprecated()
9980 def missing_commands(self) -> dict[str, Exception]:
9981 raise ObsoleteException("Obsolete missing_commands")
9983 def setup(self) -> None:
9984 self.load()
9986 GefHelpCommand()
9987 GefConfigCommand()
9988 GefSaveCommand()
9989 GefMissingCommand()
9990 GefSetCommand()
9991 GefRunCommand()
9992 GefInstallExtraScriptCommand()
9994 # At this point, commands (incl. extras) are loaded with default settings.
9995 # Load custom settings from config file if any
9996 GefRestoreCommand()
9997 return
9999 def load_extra_plugins(self, extra_plugins_dir: pathlib.Path | None = None) -> int:
10000 """Load the plugins from the gef-extras setting. Returns the number of new plugins added."""
10001 def load_plugin(fpath: pathlib.Path) -> bool:
10002 try:
10003 dbg(f"Loading '{fpath}'")
10004 gdb.execute(f"source {fpath}")
10005 except AlreadyRegisteredException:
10006 pass
10007 except Exception as e:
10008 warn(f"Exception while loading {fpath}: {str(e)}")
10009 return False
10010 return True
10012 def load_plugins_from_directory(plugin_directory: pathlib.Path):
10013 nb_added = -1
10014 nb_initial = len(__registered_commands__)
10015 start_time = time.perf_counter()
10016 for entry in plugin_directory.glob("**/*.py"):
10017 load_plugin(entry)
10019 try:
10020 nb_added = len(__registered_commands__) - nb_initial
10021 if nb_added > 0:
10022 self.load()
10023 nb_failed = len(__registered_commands__) - len(self.commands)
10024 load_time = time.perf_counter() - start_time
10025 ok(f"{Color.colorify(str(nb_added), 'bold green')} extra commands added " \
10026 f"in {load_time:.2f} seconds")
10027 if nb_failed != 0:
10028 warn(f"{Color.colorify(str(nb_failed), 'bold light_gray')} extra commands/functions failed to be added. "
10029 "Check `gef missing` to know why")
10030 except gdb.error as e:
10031 err(f"failed: {e}")
10032 return nb_added
10034 directory = extra_plugins_dir or gef.config["gef.extra_plugins_dir"]
10035 if not directory: 10035 ↛ 10037line 10035 didn't jump to line 10037 because the condition on line 10035 was always true
10036 return 0
10037 directory = pathlib.Path(directory).expanduser().absolute()
10038 if not directory.exists():
10039 return 0
10040 dbg(f"Loading extra plugins from directory={directory}")
10041 return load_plugins_from_directory(directory)
10043 @property
10044 def loaded_command_names(self) -> Iterable[str]:
10045 print("obsolete loaded_command_names")
10046 return self.commands.keys()
10048 def invoke(self, args: Any, from_tty: bool) -> None:
10049 self.dont_repeat()
10050 gdb.execute("gef help")
10051 return
10053 def add_context_layout_mapping(self, current_pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None:
10054 """Add a new context layout mapping."""
10055 context = self.commands["context"]
10056 assert isinstance(context, ContextCommand)
10058 # overload the printing of pane title
10059 context.layout_mapping[current_pane_name] = (display_pane_function, pane_title_function, condition)
10061 def add_context_pane(self, pane_name: str, display_pane_function: Callable, pane_title_function: Callable, condition: Callable | None) -> None:
10062 """Add a new context pane to ContextCommand."""
10063 # assure users can toggle the new context
10064 corrected_settings_name: str = pane_name.replace(" ", "_")
10065 if corrected_settings_name in gef.config["context.layout"]:
10066 warn(f"Duplicate name for `{pane_name}` (`{corrected_settings_name}`), skipping")
10067 return
10069 gef.config["context.layout"] += f" {corrected_settings_name}"
10070 self.add_context_layout_mapping(corrected_settings_name, display_pane_function, pane_title_function, condition)
10072 def load(self) -> None:
10073 """Load all the commands and functions defined by GEF into GDB."""
10074 current_commands = set(self.commands.keys())
10075 new_commands = set(x._cmdline_ for x in __registered_commands__) - current_commands
10076 current_functions = set(self.functions.keys())
10077 new_functions = set(x._function_ for x in __registered_functions__) - current_functions
10078 self.missing.clear()
10079 self.__load_time_ms = time.time()* 1000
10081 # load all new functions
10082 for name in sorted(new_functions):
10083 for function_cls in __registered_functions__: 10083 ↛ 10082line 10083 didn't jump to line 10082 because the loop on line 10083 didn't complete
10084 if function_cls._function_ == name:
10085 assert issubclass(function_cls, GenericFunction)
10086 self.functions[name] = function_cls()
10087 break
10089 # load all new commands
10090 for name in sorted(new_commands):
10091 try:
10092 for command_cls in __registered_commands__: 10092 ↛ 10090line 10092 didn't jump to line 10090 because the loop on line 10092 didn't complete
10093 if command_cls._cmdline_ == name:
10094 assert issubclass(command_cls, GenericCommand)
10095 command_instance = command_cls()
10097 # create the aliases if any
10098 if hasattr(command_instance, "_aliases_"): 10098 ↛ 10103line 10098 didn't jump to line 10103 because the condition on line 10098 was always true
10099 aliases = getattr(command_instance, "_aliases_")
10100 for alias in aliases:
10101 GefAlias(alias, name)
10103 self.commands[name] = command_instance
10104 break
10106 except Exception as reason:
10107 self.missing[name] = reason
10109 self.__load_time_ms = (time.time()* 1000) - self.__load_time_ms
10110 return
10113 def show_banner(self) -> None:
10114 gef_print(f"{Color.greenify('GEF')} for {gef.session.os} ready, "
10115 f"type `{Color.colorify('gef', 'underline yellow')}' to start, "
10116 f"`{Color.colorify('gef config', 'underline pink')}' to configure")
10118 ver = f"{sys.version_info.major:d}.{sys.version_info.minor:d}"
10119 gef_print(f"{Color.colorify(str(len(self.commands)), 'bold green')} commands loaded "
10120 f"and {Color.colorify(str(len(self.functions)), 'bold blue')} functions added for "
10121 f"GDB {Color.colorify(gdb.VERSION, 'bold yellow')} in {self.__load_time_ms:.2f}ms "
10122 f"using Python engine {Color.colorify(ver, 'bold red')}")
10124 nb_missing = len(self.missing)
10125 if nb_missing: 10125 ↛ 10126line 10125 didn't jump to line 10126 because the condition on line 10125 was never true
10126 warn(f"{Color.colorify(str(nb_missing), 'bold red')} "
10127 f"command{'s' if nb_missing > 1 else ''} could not be loaded, "
10128 f"run `{Color.colorify('gef missing', 'underline pink')}` to know why.")
10129 return
10132class GefHelpCommand(gdb.Command):
10133 """GEF help sub-command."""
10134 _cmdline_ = "gef help"
10135 _syntax_ = _cmdline_
10137 def __init__(self) -> None:
10138 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
10139 self.docs = []
10140 self.should_refresh = True
10141 self.command_size = 0
10142 return
10144 def invoke(self, args: Any, from_tty: bool) -> None:
10145 self.dont_repeat()
10146 gef_print(titlify("GEF - GDB Enhanced Features"))
10147 gef_print(str(self))
10148 return
10150 def __rebuild(self) -> None:
10151 """Rebuild the documentation."""
10152 for name, cmd in gef.gdb.commands.items():
10153 self += (name, cmd)
10155 self.command_size = len(gef.gdb.commands)
10156 _, cols = get_terminal_size()
10157 separator = HORIZONTAL_LINE*cols
10158 self.__doc__ = f"\n{separator}\n".join(sorted(self.docs))
10159 self.should_refresh = False
10160 return
10162 def __add__(self, command: tuple[str, GenericCommand]):
10163 """Add command to GEF documentation."""
10164 cmd, class_obj = command
10165 if " " in cmd:
10166 # do not print subcommands in gef help
10167 return self
10168 doc = getattr(class_obj, "__doc__", "").lstrip()
10169 aliases = f"Aliases: {', '.join(class_obj._aliases_)}" if hasattr(class_obj, "_aliases_") else ""
10170 msg = f"{Color.colorify(cmd, 'bold red')}\n{doc}\n{aliases}"
10171 self.docs.append(msg)
10172 return self
10174 def __radd__(self, command: tuple[str, GenericCommand]):
10175 return self.__add__(command)
10177 def __str__(self) -> str:
10178 """Lazily regenerate the `gef help` object if it was modified"""
10179 # quick check in case the docs have changed
10180 if self.should_refresh or self.command_size != len(gef.gdb.commands): 10180 ↛ 10182line 10180 didn't jump to line 10182 because the condition on line 10180 was always true
10181 self.__rebuild()
10182 return self.__doc__ or ""
10185class GefConfigCommand(gdb.Command):
10186 """GEF configuration sub-command
10187 This command will help set/view GEF settings for the current debugging session.
10188 It is possible to make those changes permanent by running `gef save` (refer
10189 to this command help), and/or restore previously saved settings by running
10190 `gef restore` (refer help).
10191 """
10192 _cmdline_ = "gef config"
10193 _syntax_ = f"{_cmdline_} [setting_name] [setting_value]"
10195 def __init__(self) -> None:
10196 super().__init__(self._cmdline_, gdb.COMMAND_NONE, prefix=False)
10197 return
10199 def invoke(self, args: str, from_tty: bool) -> None:
10200 self.dont_repeat()
10201 argv = gdb.string_to_argv(args)
10202 argc = len(argv)
10204 if not (0 <= argc <= 2): 10204 ↛ 10205line 10204 didn't jump to line 10205 because the condition on line 10204 was never true
10205 err("Invalid number of arguments")
10206 return
10208 if argc == 0:
10209 gef_print(titlify("GEF configuration settings"))
10210 self.print_settings()
10211 return
10213 if argc == 1:
10214 prefix = argv[0]
10215 names = [x for x in gef.config.keys() if x.startswith(prefix)]
10216 if names: 10216 ↛ 10223line 10216 didn't jump to line 10223 because the condition on line 10216 was always true
10217 if len(names) == 1: 10217 ↛ 10221line 10217 didn't jump to line 10221 because the condition on line 10217 was always true
10218 gef_print(titlify(f"GEF configuration setting: {names[0]}"))
10219 self.print_setting(names[0], verbose=True)
10220 else:
10221 gef_print(titlify(f"GEF configuration settings matching '{argv[0]}'"))
10222 for name in names: self.print_setting(name)
10223 return
10225 if not is_debug():
10226 try:
10227 self.set_setting(argv)
10228 except (ValueError, KeyError) as e:
10229 err(str(e))
10230 else:
10231 # Let exceptions (if any) propagate
10232 self.set_setting(argv)
10233 return
10235 def print_setting(self, plugin_name: str, verbose: bool = False) -> None:
10236 res = gef.config.raw_entry(plugin_name)
10237 string_color = gef.config["theme.dereference_string"]
10238 misc_color = gef.config["theme.dereference_base_address"]
10240 if not res: 10240 ↛ 10241line 10240 didn't jump to line 10241 because the condition on line 10240 was never true
10241 return
10243 _setting = Color.colorify(plugin_name, "green")
10244 _type = res.type.__name__
10245 if _type == "str":
10246 _value = f'"{Color.colorify(res.value, string_color)}"'
10247 else:
10248 _value = Color.colorify(res.value, misc_color)
10250 gef_print(f"{_setting} ({_type}) = {_value}")
10252 if verbose:
10253 gef_print(Color.colorify("\nDescription:", "bold underline"))
10254 gef_print(f"\t{res.description}")
10255 return
10257 def print_settings(self) -> None:
10258 for x in sorted(gef.config):
10259 self.print_setting(x)
10260 return
10262 def set_setting(self, argv: list[str]) -> bool:
10263 global gef
10264 key, new_value = argv
10266 if "." not in key: 10266 ↛ 10267line 10266 didn't jump to line 10267 because the condition on line 10266 was never true
10267 err("Invalid command format")
10268 return False
10270 loaded_commands = list( gef.gdb.commands.keys()) + ["gef"]
10271 plugin_name = key.split(".", 1)[0]
10272 if plugin_name not in loaded_commands: 10272 ↛ 10273line 10272 didn't jump to line 10273 because the condition on line 10272 was never true
10273 err(f"Unknown plugin '{plugin_name}'")
10274 return False
10276 if key not in gef.config: 10276 ↛ 10277line 10276 didn't jump to line 10277 because the condition on line 10276 was never true
10277 dbg(f"'{key}' is not a valid configuration setting")
10278 return False
10280 _type = gef.config.raw_entry(key).type
10282 # Attempt to parse specific values for known types
10283 if _type is bool:
10284 if new_value.upper() in ("TRUE", "T", "1"):
10285 _newval = True
10286 elif new_value.upper() in ("FALSE", "F", "0"):
10287 _newval = False
10288 else:
10289 raise ValueError(f"Cannot parse '{new_value}' as bool")
10290 else:
10291 _newval = _type(new_value)
10293 gef.config[key] = _newval
10295 reset_all_caches()
10296 return True
10298 def complete(self, text: str, word: str) -> list[str]:
10299 settings = sorted(gef.config)
10301 if text == "":
10302 # no prefix: example: `gef config TAB`
10303 return [s for s in settings if word in s]
10305 if "." not in text:
10306 # if looking for possible prefix
10307 return [s for s in settings if s.startswith(text.strip())]
10309 # finally, look for possible values for given prefix
10310 return [s.split(".", 1)[1] for s in settings if s.startswith(text.strip())]
10313class GefSaveCommand(gdb.Command):
10314 """GEF save sub-command.
10315 Saves the current configuration of GEF to disk (by default in file '~/.gef.rc')."""
10316 _cmdline_ = "gef save"
10317 _syntax_ = _cmdline_
10319 def __init__(self) -> None:
10320 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
10321 return
10323 def invoke(self, args: Any, from_tty: bool) -> None:
10324 self.dont_repeat()
10325 cfg = configparser.RawConfigParser()
10326 old_sect = None
10328 # save the configuration
10329 for key in sorted(gef.config):
10330 sect, optname = key.split(".", 1)
10331 value = gef.config[key]
10333 if old_sect != sect:
10334 cfg.add_section(sect)
10335 old_sect = sect
10337 cfg.set(sect, optname, value)
10339 # save the aliases
10340 cfg.add_section("aliases")
10341 for alias in gef.session.aliases:
10342 cfg.set("aliases", alias.alias, alias.command)
10344 with GEF_RC.open("w") as fd:
10345 cfg.write(fd)
10347 ok(f"Configuration saved to '{GEF_RC}'")
10348 return
10351class GefRestoreCommand(gdb.Command):
10352 """GEF restore sub-command.
10353 Loads settings from file '~/.gef.rc' and apply them to the configuration of GEF."""
10354 _cmdline_ = "gef restore"
10355 _syntax_ = _cmdline_
10357 def __init__(self) -> None:
10358 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
10359 self.reload(True)
10360 return
10362 def invoke(self, args: str, from_tty: bool) -> None:
10363 self.dont_repeat()
10364 if GEF_RC.is_file():
10365 quiet = (args.lower() == "quiet")
10366 self.reload(quiet)
10367 return
10369 def reload(self, quiet: bool):
10370 cfg = configparser.ConfigParser()
10371 cfg.read(GEF_RC)
10373 for section in cfg.sections():
10374 if section == "aliases":
10375 # load the aliases
10376 for key in cfg.options(section):
10377 try:
10378 GefAlias(key, cfg.get(section, key))
10379 except Exception as e:
10380 dbg(f"GefAlias() raised exception {e}")
10381 continue
10383 # load the other options
10384 for optname in cfg.options(section):
10385 key = f"{section}.{optname}"
10386 try:
10387 setting = gef.config.raw_entry(key)
10388 except Exception:
10389 continue
10390 new_value = cfg.get(section, optname)
10391 if setting.type is bool:
10392 new_value = True if new_value.upper() in ("TRUE", "T", "1") else False
10393 setting.value = setting.type(new_value)
10395 if not quiet: 10395 ↛ 10396line 10395 didn't jump to line 10396 because the condition on line 10395 was never true
10396 ok(f"Configuration from '{Color.colorify(str(GEF_RC), 'bold blue')}' restored")
10397 return
10400class GefMissingCommand(gdb.Command):
10401 """GEF missing sub-command
10402 Display the GEF commands that could not be loaded, along with the reason of why
10403 they could not be loaded.
10404 """
10405 _cmdline_ = "gef missing"
10406 _syntax_ = _cmdline_
10408 def __init__(self) -> None:
10409 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
10410 return
10412 def invoke(self, args: Any, from_tty: bool) -> None:
10413 self.dont_repeat()
10414 missing_commands: dict[str, Exception] = gef.gdb.missing
10415 if not missing_commands:
10416 ok("No missing command")
10417 return
10419 for cmd, exc in missing_commands.items():
10420 warn(f"Missing `{cmd}`: reason: {str(exc)})")
10421 return
10424class GefSetCommand(gdb.Command):
10425 """Override GDB set commands with the context from GEF."""
10426 _cmdline_ = "gef set"
10427 _syntax_ = f"{_cmdline_} [GDB_SET_ARGUMENTS]"
10429 def __init__(self) -> None:
10430 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_SYMBOL, False)
10431 return
10433 def invoke(self, args: Any, from_tty: bool) -> None:
10434 self.dont_repeat()
10435 args = args.split()
10436 cmd = ["set", args[0],]
10437 for p in args[1:]:
10438 if p.startswith("$_gef"): 10438 ↛ 10442line 10438 didn't jump to line 10442 because the condition on line 10438 was always true
10439 c = gdb.parse_and_eval(p)
10440 cmd.append(c.string())
10441 else:
10442 cmd.append(p)
10444 gdb.execute(" ".join(cmd))
10445 return
10448class GefRunCommand(gdb.Command):
10449 """Override GDB run commands with the context from GEF.
10450 Simple wrapper for GDB run command to use arguments set from `gef set args`."""
10451 _cmdline_ = "gef run"
10452 _syntax_ = f"{_cmdline_} [GDB_RUN_ARGUMENTS]"
10454 def __init__(self) -> None:
10455 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_FILENAME, False)
10456 return
10458 def invoke(self, args: Any, from_tty: bool) -> None:
10459 self.dont_repeat()
10460 if is_alive():
10461 gdb.execute("continue")
10462 return
10464 argv = args.split()
10465 gdb.execute(f"gef set args {' '.join(argv)}")
10466 gdb.execute("run")
10467 return
10470class GefAlias(gdb.Command):
10471 """Simple aliasing wrapper because GDB doesn't do what it should."""
10473 def __init__(self, alias: str, command: str, completer_class: int = gdb.COMPLETE_NONE, command_class: int = gdb.COMMAND_NONE) -> None:
10474 p = command.split()
10475 if not p: 10475 ↛ 10476line 10475 didn't jump to line 10476 because the condition on line 10475 was never true
10476 return
10478 if any(x for x in gef.session.aliases if x.alias == alias):
10479 return
10481 self.command = command
10482 self.alias = alias
10483 c = command.split()[0]
10484 r = self.lookup_command(c)
10485 self.__doc__ = f"Alias for '{Color.greenify(command)}'"
10486 if r is not None:
10487 _instance = r[1]
10488 self.__doc__ += f": {_instance.__doc__}"
10490 if hasattr(_instance, "complete"): 10490 ↛ 10491line 10490 didn't jump to line 10491 because the condition on line 10490 was never true
10491 self.complete = _instance.complete
10493 super().__init__(alias, command_class, completer_class=completer_class)
10494 gef.session.aliases.append(self)
10495 return
10497 def __repr__(self) -> str:
10498 return f"GefAlias(from={self.alias}, to={self.command})"
10500 def __str__(self) -> str:
10501 return f"GefAlias(from={self.alias}, to={self.command})"
10503 def invoke(self, args: Any, from_tty: bool) -> None:
10504 gdb.execute(f"{self.command} {args}", from_tty=from_tty)
10505 return
10507 def lookup_command(self, cmd: str) -> tuple[str, GenericCommand] | None:
10508 global gef
10509 for _name, _instance in gef.gdb.commands.items():
10510 if cmd == _name:
10511 return _name, _instance
10513 return None
10516@register
10517class AliasesCommand(GenericCommand):
10518 """Base command to add, remove, or list aliases."""
10520 _cmdline_ = "aliases"
10521 _syntax_ = f"{_cmdline_} (add|rm|ls)"
10523 def __init__(self) -> None:
10524 super().__init__(prefix=True)
10525 return
10527 def do_invoke(self, _: list[str]) -> None:
10528 self.usage()
10529 return
10532@register
10533class AliasesAddCommand(AliasesCommand):
10534 """Command to add aliases."""
10536 _cmdline_ = "aliases add"
10537 _syntax_ = f"{_cmdline_} [ALIAS] [COMMAND]"
10538 _example_ = f"{_cmdline_} scope telescope"
10540 def __init__(self) -> None:
10541 super().__init__()
10542 return
10544 def do_invoke(self, argv: list[str]) -> None:
10545 if len(argv) < 2: 10545 ↛ 10546line 10545 didn't jump to line 10546 because the condition on line 10545 was never true
10546 self.usage()
10547 return
10548 GefAlias(argv[0], " ".join(argv[1:]))
10549 return
10552@register
10553class AliasesRmCommand(AliasesCommand):
10554 """Command to remove aliases."""
10556 _cmdline_ = "aliases rm"
10557 _syntax_ = f"{_cmdline_} [ALIAS]"
10559 def __init__(self) -> None:
10560 super().__init__()
10561 return
10563 def do_invoke(self, argv: list[str]) -> None:
10564 global gef
10565 if len(argv) != 1: 10565 ↛ 10566line 10565 didn't jump to line 10566 because the condition on line 10565 was never true
10566 self.usage()
10567 return
10568 try:
10569 alias_to_remove = next(filter(lambda x: x.alias == argv[0], gef.session.aliases))
10570 gef.session.aliases.remove(alias_to_remove)
10571 except (ValueError, StopIteration):
10572 err(f"{argv[0]} not found in aliases.")
10573 return
10574 gef_print("You must reload GEF for alias removals to apply.")
10575 return
10578@register
10579class AliasesListCommand(AliasesCommand):
10580 """Command to list aliases."""
10582 _cmdline_ = "aliases ls"
10583 _syntax_ = _cmdline_
10585 def __init__(self) -> None:
10586 super().__init__()
10587 return
10589 def do_invoke(self, _: list[str]) -> None:
10590 ok("Aliases defined:")
10591 for a in gef.session.aliases:
10592 gef_print(f"{a.alias:30s} {RIGHT_ARROW} {a.command}")
10593 return
10596class GefTmuxSetup(gdb.Command):
10597 """Setup a comfortable tmux debugging environment."""
10599 def __init__(self) -> None:
10600 super().__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE)
10601 GefAlias("screen-setup", "tmux-setup")
10602 return
10604 def invoke(self, args: Any, from_tty: bool) -> None:
10605 self.dont_repeat()
10607 tmux = os.getenv("TMUX")
10608 if tmux:
10609 self.tmux_setup()
10610 return
10612 screen = os.getenv("TERM")
10613 if screen is not None and screen == "screen":
10614 self.screen_setup()
10615 return
10617 warn("Not in a tmux/screen session")
10618 return
10620 def tmux_setup(self) -> None:
10621 """Prepare the tmux environment by vertically splitting the current pane, and
10622 forcing the context to be redirected there."""
10623 tmux = which("tmux")
10624 ok("tmux session found, splitting window...")
10626 pane, pty = subprocess.check_output([tmux, "splitw", "-h", '-F#{session_name}:#{window_index}.#{pane_index}-#{pane_tty}', "-P"]).decode().strip().split("-")
10627 atexit.register(lambda : subprocess.run([tmux, "kill-pane", "-t", pane]))
10628 # clear the screen and let it wait for input forever
10629 gdb.execute(f"!'{tmux}' send-keys -t {pane} 'clear ; cat' C-m")
10630 gdb.execute(f"!'{tmux}' select-pane -L")
10632 ok(f"Setting `context.redirect` to '{pty}'...")
10633 gdb.execute(f"gef config context.redirect {pty}")
10634 ok("Done!")
10635 return
10637 def screen_setup(self) -> None:
10638 """Hackish equivalent of the tmux_setup() function for screen."""
10639 screen = which("screen")
10640 sty = os.getenv("STY")
10641 ok("screen session found, splitting window...")
10642 fd_script, script_path = tempfile.mkstemp()
10643 fd_tty, tty_path = tempfile.mkstemp()
10644 os.close(fd_tty)
10646 with os.fdopen(fd_script, "w") as f:
10647 f.write("startup_message off\n")
10648 f.write("split -v\n")
10649 f.write("focus right\n")
10650 f.write(f"screen bash -c 'tty > {tty_path}; clear; cat'\n")
10651 f.write("focus left\n")
10653 gdb.execute(f"!'{screen}' -r '{sty}' -m -d -X source {script_path}")
10654 # artificial delay to make sure `tty_path` is populated
10655 time.sleep(0.25)
10656 with open(tty_path, "r") as f:
10657 pty = f.read().strip()
10658 ok(f"Setting `context.redirect` to '{pty}'...")
10659 gdb.execute(f"gef config context.redirect {pty}")
10660 ok("Done!")
10661 os.unlink(script_path)
10662 os.unlink(tty_path)
10663 return
10666class GefInstallExtraScriptCommand(gdb.Command):
10667 """`gef install` command: installs one or more scripts from the `gef-extras` script repo. Note that the command
10668 doesn't check for external dependencies the script(s) might require."""
10669 _cmdline_ = "gef install"
10670 _syntax_ = f"{_cmdline_} SCRIPTNAME [SCRIPTNAME [SCRIPTNAME...]]"
10672 def __init__(self) -> None:
10673 super().__init__(self._cmdline_, gdb.COMMAND_SUPPORT, gdb.COMPLETE_NONE, False)
10674 self.branch = gef.config.get("gef.extras_default_branch", GEF_EXTRAS_DEFAULT_BRANCH)
10675 return
10677 def invoke(self, argv: str, from_tty: bool) -> None:
10678 self.dont_repeat()
10679 if not argv: 10679 ↛ 10680line 10679 didn't jump to line 10680 because the condition on line 10679 was never true
10680 err("No script name provided")
10681 return
10683 args = argv.split()
10685 if "--list" in args or "-l" in args: 10685 ↛ 10686line 10685 didn't jump to line 10686 because the condition on line 10685 was never true
10686 subprocess.run(["xdg-open", f"https://github.com/hugsy/gef-extras/{self.branch}/"])
10687 return
10689 self.dirpath = gef.config["gef.tempdir"].expanduser().absolute()
10690 if not self.dirpath.is_dir(): 10690 ↛ 10691line 10690 didn't jump to line 10691 because the condition on line 10690 was never true
10691 err("'gef.tempdir' is not a valid directory")
10692 return
10694 for script in args:
10695 script = script.lower()
10696 if not self.__install_extras_script(script): 10696 ↛ 10697line 10696 didn't jump to line 10697 because the condition on line 10696 was never true
10697 warn(f"Failed to install '{script}', skipping...")
10698 return
10701 def __install_extras_script(self, script: str) -> bool:
10702 fpath = self.dirpath / f"{script}.py"
10703 if not fpath.exists(): 10703 ↛ 10715line 10703 didn't jump to line 10715 because the condition on line 10703 was always true
10704 url = f"https://raw.githubusercontent.com/hugsy/gef-extras/{self.branch}/scripts/{script}.py"
10705 info(f"Searching for '{script}.py' in `gef-extras@{self.branch}`...")
10706 data = http_get(url)
10707 if not data: 10707 ↛ 10708line 10707 didn't jump to line 10708 because the condition on line 10707 was never true
10708 warn("Not found")
10709 return False
10711 with fpath.open("wb") as fd:
10712 fd.write(data)
10713 fd.flush()
10715 old_command_set = set(gef.gdb.commands)
10716 gdb.execute(f"source {fpath}")
10717 new_command_set = set(gef.gdb.commands)
10718 new_commands = [f"`{c[0]}`" for c in (new_command_set - old_command_set)]
10719 ok(f"Installed file '{fpath}', new command(s) available: {', '.join(new_commands)}")
10720 return True
10724# GEF internal classes
10727def __gef_prompt__(current_prompt: Callable[[Callable], str]) -> str:
10728 """GEF custom prompt function."""
10729 if gef.config["gef.readline_compat"] is True: return GEF_PROMPT
10730 if gef.config["gef.disable_color"] is True: return GEF_PROMPT
10731 prompt = gef.session.remote.mode.prompt_string() if gef.session.remote else ""
10732 prompt += "(core) " if is_target_coredump() else ""
10733 prompt += GEF_PROMPT_ON if is_alive() else GEF_PROMPT_OFF
10734 return prompt
10737class GefManager(metaclass=abc.ABCMeta):
10738 def reset_caches(self) -> None:
10739 """Reset the LRU-cached attributes"""
10740 for attr in dir(self):
10741 try:
10742 obj = getattr(self, attr)
10743 if not hasattr(obj, "cache_clear"): 10743 ↛ 10745line 10743 didn't jump to line 10745 because the condition on line 10743 was always true
10744 continue
10745 obj.cache_clear()
10746 except Exception:
10747 # we're resetting the cache here, we don't care if (or which) exception triggers
10748 continue
10749 return
10752class GefMemoryManager(GefManager):
10753 """Class that manages memory access for gef."""
10754 def __init__(self) -> None:
10755 self.reset_caches()
10756 return
10758 def reset_caches(self) -> None:
10759 super().reset_caches()
10760 self.__maps: list[Section] | None = None
10761 return
10763 def write(self, address: int, buffer: ByteString, length: int | None = None) -> None:
10764 """Write `buffer` at address `address`."""
10765 length = length or len(buffer)
10766 gdb.selected_inferior().write_memory(address, buffer, length)
10768 def read(self, addr: int, length: int = 0x10) -> bytes:
10769 """Return a `length` long byte array with the copy of the process memory at `addr`."""
10770 return gdb.selected_inferior().read_memory(addr, length).tobytes()
10772 def read_integer(self, addr: int) -> int:
10773 """Return an integer read from memory."""
10774 sz = gef.arch.ptrsize
10775 mem = self.read(addr, sz)
10776 unpack = u32 if sz == 4 else u64
10777 return unpack(mem)
10779 def read_cstring(self,
10780 address: int,
10781 max_length: int = GEF_MAX_STRING_LENGTH,
10782 encoding: str | None = None) -> str:
10783 """Return a C-string read from memory."""
10784 encoding = encoding or "unicode-escape"
10785 length = min(address | (DEFAULT_PAGE_SIZE-1), max_length+1)
10787 try:
10788 res_bytes = self.read(address, length)
10789 except gdb.error:
10790 current_address = address
10791 res_bytes = b""
10792 while len(res_bytes) < length:
10793 try:
10794 # Calculate how many bytes there are until next page
10795 next_page = current_address + DEFAULT_PAGE_SIZE
10796 page_mask = ~(DEFAULT_PAGE_SIZE - 1)
10797 size = (next_page & page_mask) - current_address
10799 # Read until the end of the current page
10800 res_bytes += self.read(current_address, size)
10802 current_address += size
10803 except gdb.error:
10804 if not res_bytes:
10805 err(f"Can't read memory at '{address:#x}'")
10806 return ""
10807 break
10808 try:
10809 with warnings.catch_warnings():
10810 # ignore DeprecationWarnings (see #735)
10811 warnings.simplefilter("ignore")
10812 res = res_bytes.decode(encoding, "strict")
10813 except UnicodeDecodeError:
10814 # latin-1 as fallback due to its single-byte to glyph mapping
10815 res = res_bytes.decode("latin-1", "replace")
10817 res = res.split("\x00", 1)[0]
10818 ustr = res.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
10819 if max_length and len(res) > max_length:
10820 return f"{ustr[:max_length]}[...]"
10821 return ustr
10823 def read_ascii_string(self, address: int) -> str | None:
10824 """Read an ASCII string from memory"""
10825 cstr = self.read_cstring(address)
10826 if isinstance(cstr, str) and cstr and all(x in string.printable for x in cstr):
10827 return cstr
10828 return None
10830 @property
10831 def maps(self) -> list[Section]:
10832 if not self.__maps:
10833 maps = self.__parse_maps()
10834 if not maps: 10834 ↛ 10835line 10834 didn't jump to line 10835 because the condition on line 10834 was never true
10835 raise RuntimeError("Failed to determine memory layout")
10836 self.__maps = maps
10837 return self.__maps
10839 def __parse_maps(self) -> list[Section] | None:
10840 """Return the mapped memory sections. If the current arch has its maps
10841 method defined, then defer to that to generated maps, otherwise, try to
10842 figure it out from procfs, then info sections, then monitor info
10843 mem."""
10844 if gef.arch.maps is not None: 10844 ↛ 10845line 10844 didn't jump to line 10845 because the condition on line 10844 was never true
10845 return list(gef.arch.maps())
10847 # Coredumps are the only case where `maintenance info sections` collected more
10848 # info than `info proc sections`.so use this unconditionally. See #1154
10850 if is_target_coredump(): 10850 ↛ 10851line 10850 didn't jump to line 10851 because the condition on line 10850 was never true
10851 return list(self.parse_gdb_maintenance_info_sections())
10853 try:
10854 return list(self.parse_gdb_info_proc_maps())
10855 except Exception:
10856 pass
10858 try:
10859 return list(self.parse_procfs_maps())
10860 except Exception:
10861 pass
10863 try:
10864 return list(self.parse_monitor_info_mem())
10865 except Exception:
10866 pass
10868 raise RuntimeError("Failed to get memory layout")
10870 @staticmethod
10871 def parse_procfs_maps() -> Generator[Section, None, None]:
10872 """Get the memory mapping from procfs."""
10873 procfs_mapfile = gef.session.maps
10874 if not procfs_mapfile:
10875 is_remote = gef.session.remote is not None
10876 raise FileNotFoundError(f"Missing {'remote ' if is_remote else ''}procfs map file")
10878 with procfs_mapfile.open("r") as fd:
10879 for line in fd:
10880 line = line.strip()
10881 addr, perm, off, _, rest = line.split(" ", 4)
10882 rest = rest.split(" ", 1)
10883 if len(rest) == 1:
10884 inode = rest[0]
10885 pathname = ""
10886 else:
10887 inode = rest[0]
10888 pathname = rest[1].lstrip()
10890 addr_start, addr_end = parse_string_range(addr)
10891 off = int(off, 16)
10892 perm = Permission.from_process_maps(perm)
10893 inode = int(inode)
10894 yield Section(page_start=addr_start,
10895 page_end=addr_end,
10896 offset=off,
10897 permission=perm,
10898 inode=inode,
10899 path=pathname)
10900 return
10902 @staticmethod
10903 def parse_gdb_info_proc_maps() -> Generator[Section, None, None]:
10904 """Get the memory mapping from GDB's command `info proc mappings`."""
10905 if GDB_VERSION < (11, 0): 10905 ↛ 10906line 10905 didn't jump to line 10906 because the condition on line 10905 was never true
10906 raise AttributeError("Disregarding old format")
10908 output = (gdb.execute("info proc mappings", to_string=True) or "")
10909 if not output: 10909 ↛ 10910line 10909 didn't jump to line 10910 because the condition on line 10909 was never true
10910 raise AttributeError
10912 start_idx = output.find("Start Addr")
10913 if start_idx == -1:
10914 raise AttributeError
10916 output = output[start_idx:]
10917 lines = output.splitlines()
10918 if len(lines) < 2: 10918 ↛ 10919line 10918 didn't jump to line 10919 because the condition on line 10918 was never true
10919 raise AttributeError
10921 # The function assumes the following output format (as of GDB 11+) for `info proc mappings`:
10922 # - live process (incl. remote)
10923 # ```
10924 # Start Addr End Addr Size Offset Perms objfile
10925 # 0x555555554000 0x555555558000 0x4000 0x0 r--p /usr/bin/ls
10926 # 0x555555558000 0x55555556c000 0x14000 0x4000 r-xp /usr/bin/ls
10927 # [...]
10928 # ```
10929 # or
10930 # - coredump & rr
10931 # ```
10932 # Start Addr End Addr Size Offset objfile
10933 # 0x555555554000 0x555555558000 0x4000 0x0 /usr/bin/ls
10934 # 0x555555558000 0x55555556c000 0x14000 0x4000 /usr/bin/ls
10935 # ```
10936 # In the latter case the 'Perms' header is missing, so mock the Permission to `rwx` so
10937 # `dereference` will still work.
10939 mock_permission = all(map(lambda x: x.strip() != "Perms", lines[0].split()))
10940 for line in lines[1:]:
10941 if not line: 10941 ↛ 10942line 10941 didn't jump to line 10942 because the condition on line 10941 was never true
10942 break
10944 parts = [x.strip() for x in line.split()]
10945 addr_start, addr_end, _, offset = [int(x, 16) for x in parts[0:4]]
10946 if mock_permission: 10946 ↛ 10947line 10946 didn't jump to line 10947 because the condition on line 10946 was never true
10947 perm = Permission(7)
10948 path = " ".join(parts[4:]) if len(parts) >= 4 else ""
10949 else:
10950 perm = Permission.from_process_maps(parts[4])
10951 path = " ".join(parts[5:]) if len(parts) >= 5 else ""
10952 yield Section(
10953 page_start=addr_start,
10954 page_end=addr_end,
10955 offset=offset,
10956 permission=perm,
10957 path=path,
10958 )
10959 return
10961 @staticmethod
10962 def parse_monitor_info_mem() -> Generator[Section, None, None]:
10963 """Get the memory mapping from GDB's command `monitor info mem`
10964 This can raise an exception, which the memory manager takes to mean
10965 that this method does not work to get a map.
10966 """
10967 stream = StringIO(gdb.execute("monitor info mem", to_string=True))
10969 for line in stream:
10970 try:
10971 ranges, off, perms = line.split()
10972 off = int(off, 16)
10973 start, end = [int(s, 16) for s in ranges.split("-")]
10974 except ValueError:
10975 continue
10977 perm = Permission.from_monitor_info_mem(perms)
10978 yield Section(page_start=start,
10979 page_end=end,
10980 offset=off,
10981 permission=perm)
10983 @staticmethod
10984 def parse_gdb_maintenance_info_sections() -> Generator[Section, None, None]:
10985 """Get the memory mapping from GDB's command `maintenance info sections` (limited info). In some cases (i.e. coredumps),
10986 the memory info collected by `info proc sections` is insufficient."""
10987 stream = StringIO(gdb.execute("maintenance info sections", to_string=True))
10989 for line in stream:
10990 if not line:
10991 break
10993 try:
10994 parts = line.split()
10995 addr_start, addr_end = [int(x, 16) for x in parts[1].split("->")]
10996 off = int(parts[3][:-1], 16)
10997 path = parts[4]
10998 perm = Permission.NONE
10999 if "DATA" in parts[5:]:
11000 perm |= Permission.READ | Permission.WRITE
11001 if "CODE" in parts[5:]:
11002 perm |= Permission.READ | Permission.EXECUTE
11003 yield Section(
11004 page_start=addr_start,
11005 page_end=addr_end,
11006 offset=off,
11007 permission=perm,
11008 path=path,
11009 )
11011 except IndexError:
11012 continue
11013 except ValueError:
11014 continue
11016 @staticmethod
11017 def parse_info_mem():
11018 """Get the memory mapping from GDB's command `info mem`. This can be
11019 provided by certain gdbserver implementations."""
11020 for line in StringIO(gdb.execute("info mem", to_string=True)):
11021 # Using memory regions provided by the target.
11022 # Num Enb Low Addr High Addr Attrs
11023 # 0 y 0x10000000 0x10200000 flash blocksize 0x1000 nocache
11024 # 1 y 0x20000000 0x20042000 rw nocache
11025 _, en, start, end, *attrs = line.split()
11026 if en != "y":
11027 continue
11029 if "flash" in attrs:
11030 perm = Permission.from_info_mem("r")
11031 else:
11032 perm = Permission.from_info_mem("rw")
11033 yield Section(page_start=int(start, 0),
11034 page_end=int(end, 0),
11035 permission=perm)
11037 def append(self, section: Section):
11038 if not self.maps:
11039 raise AttributeError("No mapping defined")
11040 if not isinstance(section, Section):
11041 raise TypeError("section has an invalid type")
11043 assert self.__maps
11044 for s in self.__maps:
11045 if section.overlaps(s):
11046 raise RuntimeError(f"{section} overlaps {s}")
11047 self.__maps.append(section)
11048 return self
11050 def __iadd__(self, section: Section):
11051 return self.append(section)
11054class GefHeapManager(GefManager):
11055 """Class managing session heap."""
11056 def __init__(self) -> None:
11057 self.reset_caches()
11058 return
11060 def reset_caches(self) -> None:
11061 self.__libc_main_arena: GlibcArena | None = None
11062 self.__libc_selected_arena: GlibcArena | None = None
11063 self.__heap_base = None
11064 return
11066 @property
11067 def main_arena(self) -> GlibcArena | None:
11068 if not self.__libc_main_arena:
11069 try:
11070 __main_arena_addr = GefHeapManager.find_main_arena_addr()
11071 self.__libc_main_arena = GlibcArena(f"*{__main_arena_addr:#x}")
11072 # the initialization of `main_arena` also defined `selected_arena`, so
11073 # by default, `main_arena` == `selected_arena`
11074 self.selected_arena = self.__libc_main_arena
11075 except Exception:
11076 # the search for arena can fail when the session is not started
11077 pass
11078 return self.__libc_main_arena
11080 @main_arena.setter
11081 def main_arena(self, value: GlibcArena) -> None:
11082 self.__libc_main_arena = value
11083 return
11085 @staticmethod
11086 @lru_cache()
11087 def find_main_arena_addr() -> int:
11088 assert gef.libc.version
11089 """A helper function to find the glibc `main_arena` address, either from
11090 symbol, from its offset from `__malloc_hook` or by brute force."""
11091 # Before anything else, use libc offset from config if available
11092 if gef.config["gef.main_arena_offset"]: 11092 ↛ 11093line 11092 didn't jump to line 11093 because the condition on line 11092 was never true
11093 try:
11094 libc_base = get_section_base_address("libc")
11095 offset: int = gef.config["gef.main_arena_offset"]
11096 if libc_base:
11097 dbg(f"Using main_arena_offset={offset:#x} from config")
11098 addr = libc_base + offset
11100 # Verify the found address before returning
11101 if GlibcArena.verify(addr):
11102 return addr
11103 except gdb.error:
11104 pass
11106 # First, try to find `main_arena` symbol directly
11107 try:
11108 return parse_address(f"&{LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME}")
11109 except gdb.error:
11110 pass
11112 # Second, try to find it by offset from `__malloc_hook`
11113 if gef.libc.version < (2, 34):
11114 try:
11115 malloc_hook_addr = parse_address("(void *)&__malloc_hook")
11117 struct_size = ctypes.sizeof(GlibcArena.malloc_state_t())
11119 if is_x86():
11120 addr = align_address_to_size(malloc_hook_addr + gef.arch.ptrsize, 0x20)
11121 elif is_arch(Elf.Abi.AARCH64):
11122 addr = malloc_hook_addr - gef.arch.ptrsize*2 - struct_size
11123 elif is_arch(Elf.Abi.ARM):
11124 addr = malloc_hook_addr - gef.arch.ptrsize - struct_size
11125 else:
11126 addr = None
11128 # Verify the found address before returning
11129 if addr and GlibcArena.verify(addr):
11130 return addr
11132 except gdb.error:
11133 pass
11135 # Last resort, try to find it via brute force if enabled in settings
11136 if gef.config["gef.bruteforce_main_arena"]:
11137 alignment = 0x8
11138 try:
11139 dbg("Trying to bruteforce main_arena address")
11140 # setup search_range for `main_arena` to `.data` of glibc
11141 def search_filter(zone: Zone) -> bool:
11142 return "libc" in pathlib.Path(zone.filename).name and zone.name == ".data"
11144 for dotdata in list(filter(search_filter, get_info_files())):
11145 search_range = range(dotdata.zone_start, dotdata.zone_end, alignment)
11146 # find first possible candidate
11147 for addr in search_range:
11148 if GlibcArena.verify(addr):
11149 dbg(f"Found candidate at {addr:#x}")
11150 return addr
11151 dbg("Bruteforce not successful")
11152 except Exception:
11153 pass
11155 # Nothing helped
11156 err_msg = f"Cannot find main_arena for {gef.arch.arch}. You might want to set a manually found libc offset "
11157 if not gef.config["gef.bruteforce_main_arena"]:
11158 err_msg += "or allow bruteforcing "
11159 err_msg += "through the GEF config."
11160 raise OSError(err_msg)
11162 @property
11163 def selected_arena(self) -> GlibcArena | None:
11164 if not self.__libc_selected_arena: 11164 ↛ 11166line 11164 didn't jump to line 11166 because the condition on line 11164 was never true
11165 # `selected_arena` must default to `main_arena`
11166 self.__libc_selected_arena = self.main_arena
11167 return self.__libc_selected_arena
11169 @selected_arena.setter
11170 def selected_arena(self, value: GlibcArena) -> None:
11171 self.__libc_selected_arena = value
11172 return
11174 @property
11175 def arenas(self) -> list | Iterator[GlibcArena]:
11176 if not self.main_arena: 11176 ↛ 11177line 11176 didn't jump to line 11177 because the condition on line 11176 was never true
11177 return []
11178 return iter(self.main_arena)
11180 @property
11181 def base_address(self) -> int | None:
11182 if not self.__heap_base:
11183 base = 0
11184 try:
11185 base = parse_address("mp_->sbrk_base")
11186 base = self.malloc_align_address(base)
11187 except gdb.error:
11188 # missing symbol, try again
11189 base = 0
11190 if not base: 11190 ↛ 11191line 11190 didn't jump to line 11191 because the condition on line 11190 was never true
11191 base = get_section_base_address("[heap]")
11192 self.__heap_base = base
11193 return self.__heap_base
11195 @property
11196 def chunks(self) -> list | Iterator:
11197 if not self.base_address:
11198 return []
11199 return iter(GlibcChunk(self.base_address, from_base=True))
11201 @property
11202 def min_chunk_size(self) -> int:
11203 return 4 * gef.arch.ptrsize
11205 @property
11206 def malloc_alignment(self) -> int:
11207 assert gef.libc.version
11208 __default_malloc_alignment = 0x10
11209 if gef.libc.version >= (2, 26) and is_x86_32(): 11209 ↛ 11212line 11209 didn't jump to line 11212 because the condition on line 11209 was never true
11210 # Special case introduced in Glibc 2.26:
11211 # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/i386/malloc-alignment.h#L22
11212 return __default_malloc_alignment
11213 # Generic case:
11214 # https://elixir.bootlin.com/glibc/glibc-2.26/source/sysdeps/generic/malloc-alignment.h#L22
11215 return 2 * gef.arch.ptrsize
11217 def csize2tidx(self, size: int) -> int:
11218 return abs((size - self.min_chunk_size + self.malloc_alignment - 1)) // self.malloc_alignment
11220 def tidx2size(self, idx: int) -> int:
11221 return idx * self.malloc_alignment + self.min_chunk_size
11223 def malloc_align_address(self, address: int) -> int:
11224 """Align addresses according to glibc's MALLOC_ALIGNMENT. See also Issue #689 on Github"""
11225 def ceil(n: float) -> int:
11226 return int(-1 * n // 1 * -1)
11227 malloc_alignment = self.malloc_alignment
11228 return malloc_alignment * ceil((address / malloc_alignment))
11231class GefSetting:
11232 """Basic class for storing gef settings as objects"""
11234 def __init__(self, value: Any, cls: type | None = None, description: str | None = None, hooks: dict[str, list[Callable]] | None = None) -> None:
11235 self.value = value
11236 self.type = cls or type(value)
11237 self.description = description or ""
11238 self.hooks: dict[str, list[Callable]] = collections.defaultdict(list)
11239 if not hooks:
11240 hooks = {"on_read": [], "on_write": [], "on_changed": []}
11242 for access, funcs in hooks.items():
11243 self.add_hook(access, funcs)
11244 return
11246 def __str__(self) -> str:
11247 return f"Setting(type={self.type.__name__}, value='{self.value}', desc='{self.description[:10]}...', " \
11248 f"read_hooks={len(self.hooks['on_read'])}, write_hooks={len(self.hooks['on_write'])}, "\
11249 f"changed_hooks={len(self.hooks['on_changed'])})"
11251 def add_hook(self, access: str, funcs: list[Callable]):
11252 if access not in ("on_read", "on_write", "on_changed"): 11252 ↛ 11253line 11252 didn't jump to line 11253 because the condition on line 11252 was never true
11253 raise ValueError("invalid access type")
11254 for func in funcs:
11255 if not callable(func): 11255 ↛ 11256line 11255 didn't jump to line 11256 because the condition on line 11255 was never true
11256 raise ValueError("hook is not callable")
11257 self.hooks[access].append(func)
11258 return self
11260 @staticmethod
11261 def no_spaces(value: pathlib.Path):
11262 if " " in str(value):
11263 raise ValidationError("setting cannot contain spaces")
11265 @staticmethod
11266 def must_exist(value: pathlib.Path):
11267 if not value or not pathlib.Path(value).expanduser().absolute().exists():
11268 raise ValidationError("specified path must exist")
11270 @staticmethod
11271 def create_folder_tree(value: pathlib.Path):
11272 value.mkdir(0o755, exist_ok=True, parents=True)
11275class GefSettingsManager(dict):
11276 """
11277 GefSettings acts as a dict where the global settings are stored and can be read, written or deleted as any other dict.
11278 For instance, to read a specific command setting: `gef.config[mycommand.mysetting]`
11279 """
11280 def __getitem__(self, name: str) -> Any:
11281 setting : GefSetting = super().__getitem__(name)
11282 self.__invoke_read_hooks(setting)
11283 return setting.value
11285 def __setitem__(self, name: str, value: Any) -> None:
11286 # check if the key exists
11287 if super().__contains__(name):
11288 # if so, update its value directly
11289 setting = super().__getitem__(name)
11290 if not isinstance(setting, GefSetting): raise TypeError 11290 ↛ exitline 11290 didn't except from function '__setitem__' because the raise on line 11290 wasn't executed
11291 new_value = setting.type(value)
11292 dbg(f"in __invoke_changed_hooks(\"{name}\"), setting.value={setting.value} -> new_value={new_value}, changing={bool(setting.value != new_value)}")
11293 self.__invoke_changed_hooks(setting, new_value)
11294 self.__invoke_write_hooks(setting, new_value)
11295 setting.value = new_value
11296 return
11298 # if not, assert `value` is a GefSetting, then insert it
11299 if not isinstance(value, GefSetting): raise TypeError("Invalid argument") 11299 ↛ exitline 11299 didn't except from function '__setitem__' because the raise on line 11299 wasn't executed
11300 if not value.type: raise TypeError("Invalid type") 11300 ↛ exitline 11300 didn't except from function '__setitem__' because the raise on line 11300 wasn't executed
11301 if not value.description: raise AttributeError("Invalid description") 11301 ↛ exitline 11301 didn't except from function '__setitem__' because the raise on line 11301 wasn't executed
11302 setting = value
11303 value = setting.value
11304 self.__invoke_write_hooks(setting, value)
11305 super().__setitem__(name, setting)
11306 return
11308 def __delitem__(self, name: str) -> None:
11309 return super().__delitem__(name)
11311 def raw_entry(self, name: str) -> GefSetting:
11312 return super().__getitem__(name)
11314 def __invoke_read_hooks(self, setting: GefSetting) -> None:
11315 for callback in setting.hooks["on_read"]: 11315 ↛ 11316line 11315 didn't jump to line 11316 because the loop on line 11315 never started
11316 callback()
11317 return
11319 def __invoke_changed_hooks(self, setting: GefSetting, new_value: Any) -> None:
11320 old_value = setting.value
11321 if old_value == new_value:
11322 return
11323 for callback in setting.hooks["on_changed"]: 11323 ↛ 11324line 11323 didn't jump to line 11324 because the loop on line 11323 never started
11324 callback(old_value, new_value)
11326 def __invoke_write_hooks(self, setting: GefSetting, new_value: Any) -> None:
11327 for callback in setting.hooks["on_write"]:
11328 callback(new_value)
11331class GefSessionManager(GefManager):
11332 """Class managing the runtime properties of GEF. """
11333 def __init__(self) -> None:
11334 self.reset_caches()
11335 self.remote: "GefRemoteSessionManager | None" = None
11336 self.remote_initializing: bool = False
11337 self.qemu_mode: bool = False
11338 self.coredump_mode: bool | None = None
11339 self.convenience_vars_index: int = 0
11340 self.heap_allocated_chunks: list[tuple[int, int]] = []
11341 self.heap_freed_chunks: list[tuple[int, int]] = []
11342 self.heap_uaf_watchpoints: list[UafWatchpoint] = []
11343 self.pie_breakpoints: dict[int, PieVirtualBreakpoint] = {}
11344 self.pie_counter: int = 1
11345 self.aliases: list[GefAlias] = []
11346 self.modules: list[FileFormat] = []
11347 self.constants = {} # a dict for runtime constants (like 3rd party file paths)
11348 for constant in ("python3", "readelf", "nm", "file", "ps"):
11349 self.constants[constant] = which(constant)
11350 return
11352 def reset_caches(self) -> None:
11353 super().reset_caches()
11354 self._auxiliary_vector = None
11355 self._pagesize = None
11356 self._os = None
11357 self._pid = None
11358 self._file = None
11359 self._maps: pathlib.Path | None = None
11360 self._root: pathlib.Path | None = None
11361 return
11363 def __str__(self) -> str:
11364 _type = "Local" if self.remote is None else f"Remote/{self.remote.mode}"
11365 return f"Session(type={_type}, pid={self.pid or 'Not running'}, os='{self.os}')"
11367 def __repr__(self) -> str:
11368 return str(self)
11370 @property
11371 def auxiliary_vector(self) -> dict[str, int] | None:
11372 if not is_alive():
11373 return None
11374 if is_qemu_system(): 11374 ↛ 11375line 11374 didn't jump to line 11375 because the condition on line 11374 was never true
11375 return None
11376 if not self._auxiliary_vector:
11377 auxiliary_vector = {}
11378 auxv_info = gdb.execute("info auxv", to_string=True) or ""
11379 if not auxv_info or "failed" in auxv_info: 11379 ↛ 11380line 11379 didn't jump to line 11380 because the condition on line 11379 was never true
11380 err("Failed to query auxiliary variables")
11381 return None
11382 for line in auxv_info.splitlines():
11383 line = line.split('"')[0].strip() # remove the ending string (if any)
11384 line = line.split() # split the string by whitespace(s)
11385 if len(line) < 4: 11385 ↛ 11386line 11385 didn't jump to line 11386 because the condition on line 11385 was never true
11386 continue
11387 __av_type = line[1]
11388 __av_value = line[-1]
11389 auxiliary_vector[__av_type] = int(__av_value, base=0)
11390 self._auxiliary_vector = auxiliary_vector
11391 return self._auxiliary_vector
11393 @property
11394 def os(self) -> str:
11395 """Return the current OS."""
11396 if not self._os:
11397 self._os = platform.system().lower()
11398 return self._os
11400 @property
11401 def pid(self) -> int:
11402 """Return the PID of the target process."""
11403 if not self._pid:
11404 pid = gdb.selected_inferior().pid if not self.qemu_mode else gdb.selected_thread().ptid[1]
11405 if not pid:
11406 raise RuntimeError("cannot retrieve PID for target process")
11407 self._pid = pid
11408 return self._pid
11410 @property
11411 def file(self) -> pathlib.Path | None:
11412 """Return a Path object of the target process."""
11413 if self.remote is not None:
11414 return self.remote.file
11415 progspace = gdb.current_progspace()
11416 assert progspace
11417 fpath: str = progspace.filename or ""
11418 if fpath and not self._file:
11419 self._file = pathlib.Path(fpath).expanduser()
11420 return self._file
11422 @property
11423 def cwd(self) -> pathlib.Path | None:
11424 if self.remote is not None:
11425 return self.remote.root
11426 return self.file.parent if self.file else None
11428 @property
11429 def pagesize(self) -> int:
11430 """Get the system page size"""
11431 auxval = self.auxiliary_vector
11432 if not auxval:
11433 return DEFAULT_PAGE_SIZE
11434 self._pagesize = auxval["AT_PAGESZ"]
11435 return self._pagesize
11437 @property
11438 def canary(self) -> tuple[int, int] | None:
11439 """Return a tuple of the canary address and value, read from the canonical
11440 location if supported by the architecture. Otherwise, read from the auxiliary
11441 vector."""
11442 try:
11443 canary_location = gef.arch.canary_address()
11444 canary = gef.memory.read_integer(canary_location)
11445 except (NotImplementedError, gdb.error):
11446 # Fall back to `AT_RANDOM`, which is the original source
11447 # of the canary value but not the canonical location
11448 return self.original_canary
11449 return canary, canary_location
11451 @property
11452 def original_canary(self) -> tuple[int, int] | None:
11453 """Return a tuple of the initial canary address and value, read from the
11454 auxiliary vector."""
11455 auxval = self.auxiliary_vector
11456 if not auxval:
11457 return None
11458 canary_location = auxval["AT_RANDOM"]
11459 canary = gef.memory.read_integer(canary_location)
11460 canary &= ~0xFF
11461 return canary, canary_location
11463 @property
11464 def maps(self) -> pathlib.Path | None:
11465 """Returns the Path to the procfs entry for the memory mapping."""
11466 if not is_alive():
11467 return None
11468 if not self._maps:
11469 if self.remote is not None:
11470 self._maps = self.remote.maps
11471 else:
11472 self._maps = pathlib.Path(f"/proc/{self.pid}/maps")
11473 return self._maps
11475 @property
11476 def root(self) -> pathlib.Path | None:
11477 """Returns the path to the process's root directory."""
11478 if not is_alive():
11479 return None
11480 if not self._root:
11481 self._root = pathlib.Path(f"/proc/{self.pid}/root")
11482 return self._root
11485class GefRemoteSessionManager(GefSessionManager):
11486 """Class for managing remote sessions with GEF. It will create a temporary environment
11487 designed to clone the remote one."""
11489 class RemoteMode(enum.IntEnum):
11490 GDBSERVER = 0
11491 QEMU = 1
11492 RR = 2
11494 def __str__(self):
11495 return self.name
11497 def __repr__(self):
11498 return f"RemoteMode = {str(self)} ({int(self)})"
11500 def prompt_string(self) -> str:
11501 match self:
11502 case GefRemoteSessionManager.RemoteMode.QEMU:
11503 return Color.boldify("(qemu) ")
11504 case GefRemoteSessionManager.RemoteMode.RR:
11505 return Color.boldify("(rr) ")
11506 case GefRemoteSessionManager.RemoteMode.GDBSERVER:
11507 return Color.boldify("(remote) ")
11508 raise AttributeError("Unknown value")
11510 def __init__(self, host: str, port: int, pid: int =-1, qemu: pathlib.Path | None = None) -> None:
11511 super().__init__()
11512 self.__host = host
11513 self.__port = port
11514 self.__local_root_fd = tempfile.TemporaryDirectory()
11515 self.__local_root_path = pathlib.Path(self.__local_root_fd.name)
11516 self.__qemu = qemu
11517 if pid > 0: 11517 ↛ 11518line 11517 didn't jump to line 11518 because the condition on line 11517 was never true
11518 self._pid = pid
11520 if self.__qemu is not None:
11521 self._mode = GefRemoteSessionManager.RemoteMode.QEMU
11522 elif os.environ.get("GDB_UNDER_RR", None) == "1": 11522 ↛ 11523line 11522 didn't jump to line 11523 because the condition on line 11522 was never true
11523 self._mode = GefRemoteSessionManager.RemoteMode.RR
11524 else:
11525 self._mode = GefRemoteSessionManager.RemoteMode.GDBSERVER
11527 def close(self) -> None:
11528 self.__local_root_fd.cleanup()
11529 try:
11530 gef_on_new_unhook(self.remote_objfile_event_handler)
11531 gef_on_new_hook(new_objfile_handler)
11532 except Exception as e:
11533 warn(f"Exception while restoring local context: {str(e)}")
11534 raise
11536 def __str__(self) -> str:
11537 return f"RemoteSession(target='{self.target}', local='{self.root}', pid={self.pid}, mode={self.mode})"
11539 def __repr__(self) -> str:
11540 return str(self)
11542 @property
11543 def target(self) -> str:
11544 return f"{self.__host}:{self.__port}"
11546 @property
11547 def root(self) -> pathlib.Path:
11548 return self.__local_root_path.absolute()
11550 @property
11551 def file(self) -> pathlib.Path:
11552 """Path to the file being debugged as seen by the remote endpoint."""
11553 if not self._file:
11554 progspace = gdb.current_progspace()
11555 if not progspace: 11555 ↛ 11556line 11555 didn't jump to line 11556 because the condition on line 11555 was never true
11556 raise RuntimeError("No session started")
11557 filename = progspace.filename
11558 if not filename: 11558 ↛ 11559line 11558 didn't jump to line 11559 because the condition on line 11558 was never true
11559 raise RuntimeError("No session started")
11560 start_idx = len("target:") if filename.startswith("target:") else 0
11561 self._file = pathlib.Path(filename[start_idx:])
11562 return self._file
11564 @property
11565 def lfile(self) -> pathlib.Path:
11566 """Local path to the file being debugged."""
11567 return self.root / str(self.file).lstrip("/")
11569 @property
11570 def maps(self) -> pathlib.Path:
11571 if not self._maps:
11572 self._maps = self.root / f"proc/{self.pid}/maps"
11573 return self._maps
11575 @property
11576 def mode(self) -> RemoteMode:
11577 return self._mode
11579 def sync(self, src: str, dst: str | None = None) -> bool:
11580 """Copy the `src` into the temporary chroot. If `dst` is provided, that path will be
11581 used instead of `src`."""
11582 if not dst:
11583 dst = src
11584 tgt = self.root / dst.lstrip("/")
11585 if tgt.exists(): 11585 ↛ 11586line 11585 didn't jump to line 11586 because the condition on line 11585 was never true
11586 return True
11587 tgt.parent.mkdir(parents=True, exist_ok=True)
11588 dbg(f"[remote] downloading '{src}' -> '{tgt}'")
11589 gdb.execute(f"remote get '{src}' '{tgt.absolute()}'")
11590 return tgt.exists()
11592 def connect(self, pid: int) -> bool:
11593 """Connect to remote target. If in extended mode, also attach to the given PID."""
11594 # before anything, register our new hook to download files from the remote target
11595 dbg("[remote] Installing new objfile handlers")
11596 try:
11597 gef_on_new_unhook(new_objfile_handler)
11598 except SystemError:
11599 # the default objfile handler might already have been removed, ignore failure
11600 pass
11602 gef_on_new_hook(self.remote_objfile_event_handler)
11604 # then attempt to connect
11605 is_extended_mode = (pid > -1)
11606 dbg(f"[remote] Enabling extended remote: {bool(is_extended_mode)}")
11607 try:
11608 with DisableContextOutputContext():
11609 cmd = f"target {'extended-' if is_extended_mode else ''}remote {self.target}"
11610 dbg(f"[remote] Executing '{cmd}'")
11611 gdb.execute(cmd)
11612 if is_extended_mode: 11612 ↛ 11613line 11612 didn't jump to line 11613 because the condition on line 11612 was never true
11613 gdb.execute(f"attach {pid:d}")
11614 return True
11615 except Exception as e:
11616 err(f"Failed to connect to {self.target}: {e}")
11618 # a failure will trigger the cleanup, deleting our hook anyway
11619 return False
11621 def setup(self) -> bool:
11622 # setup remote adequately depending on remote or qemu mode
11623 match self.mode:
11624 case GefRemoteSessionManager.RemoteMode.QEMU:
11625 dbg(f"Setting up as qemu session, target={self.__qemu}")
11626 self.__setup_qemu()
11627 case GefRemoteSessionManager.RemoteMode.RR: 11627 ↛ 11628line 11627 didn't jump to line 11628 because the pattern on line 11627 never matched
11628 dbg("Setting up as rr session")
11629 self.__setup_rr()
11630 case GefRemoteSessionManager.RemoteMode.GDBSERVER: 11630 ↛ 11633line 11630 didn't jump to line 11633 because the pattern on line 11630 always matched
11631 dbg("Setting up as remote session")
11632 self.__setup_remote()
11633 case _:
11634 raise ValueError
11636 # refresh gef to consider the binary
11637 reset_all_caches()
11638 gef.binary = Elf(self.lfile)
11639 reset_architecture()
11640 return True
11642 def __setup_qemu(self) -> bool:
11643 # setup emulated file in the chroot
11644 assert self.__qemu
11645 target = self.root / str(self.__qemu.parent).lstrip("/")
11646 target.mkdir(parents=True, exist_ok=False)
11647 shutil.copy2(self.__qemu, target)
11648 self._file = self.__qemu
11649 assert self.lfile.exists()
11651 # create a procfs
11652 procfs = self.root / f"proc/{self.pid}/"
11653 procfs.mkdir(parents=True, exist_ok=True)
11655 ## /proc/pid/cmdline
11656 cmdline = procfs / "cmdline"
11657 if not cmdline.exists(): 11657 ↛ 11662line 11657 didn't jump to line 11662 because the condition on line 11657 was always true
11658 with cmdline.open("w") as fd:
11659 fd.write("")
11661 ## /proc/pid/environ
11662 environ = procfs / "environ"
11663 if not environ.exists(): 11663 ↛ 11668line 11663 didn't jump to line 11668 because the condition on line 11663 was always true
11664 with environ.open("wb") as fd:
11665 fd.write(b"PATH=/bin\x00HOME=/tmp\x00")
11667 ## /proc/pid/maps
11668 maps = procfs / "maps"
11669 if not maps.exists(): 11669 ↛ 11674line 11669 didn't jump to line 11674 because the condition on line 11669 was always true
11670 with maps.open("w") as fd:
11671 fname = self.file.absolute()
11672 mem_range = "00000000-ffffffff" if is_32bit() else "0000000000000000-ffffffffffffffff"
11673 fd.write(f"{mem_range} rwxp 00000000 00:00 0 {fname}\n")
11674 return True
11676 def __setup_remote(self) -> bool:
11677 # get the file
11678 fpath = f"/proc/{self.pid}/exe"
11679 if not self.sync(fpath, str(self.file)): 11679 ↛ 11680line 11679 didn't jump to line 11680 because the condition on line 11679 was never true
11680 err(f"'{fpath}' could not be fetched on the remote system.")
11681 return False
11683 # pseudo procfs
11684 for _file in ("maps", "environ", "cmdline"):
11685 fpath = f"/proc/{self.pid}/{_file}"
11686 if not self.sync(fpath): 11686 ↛ 11687line 11686 didn't jump to line 11687 because the condition on line 11686 was never true
11687 err(f"'{fpath}' could not be fetched on the remote system.")
11688 return False
11690 return True
11692 def __setup_rr(self) -> bool:
11693 #
11694 # Simply override the local root path, the binary must exist
11695 # on the host.
11696 #
11697 self.__local_root_path = pathlib.Path("/")
11698 return True
11700 def remote_objfile_event_handler(self, evt: "gdb.NewObjFileEvent") -> None:
11701 dbg(f"[remote] in remote_objfile_handler({evt.new_objfile.filename if evt else 'None'}))")
11702 if not evt or not evt.new_objfile.filename: 11702 ↛ 11703line 11702 didn't jump to line 11703 because the condition on line 11702 was never true
11703 return
11704 if not evt.new_objfile.filename.startswith("target:") and not evt.new_objfile.filename.startswith("/"):
11705 warn(f"[remote] skipping '{evt.new_objfile.filename}'")
11706 return
11707 if evt.new_objfile.filename.startswith("target:"):
11708 src: str = evt.new_objfile.filename[len("target:"):]
11709 if not self.sync(src): 11709 ↛ 11710line 11709 didn't jump to line 11710 because the condition on line 11709 was never true
11710 raise FileNotFoundError(f"Failed to sync '{src}'")
11711 return
11714class GefUiManager(GefManager):
11715 """Class managing UI settings."""
11716 def __init__(self) -> None:
11717 self.redirect_fd : TextIOWrapper | None = None
11718 self.context_hidden = False
11719 self.stream_buffer : StringIO | None = None
11720 self.highlight_table: dict[str, str] = {}
11721 self.watches: dict[int, tuple[int, str]] = {}
11722 self.context_messages: list[tuple[str, str]] = []
11723 return
11726class GefLibcManager(GefManager):
11727 """Class managing everything libc-related (except heap)."""
11728 PATTERN_LIBC_VERSION_MEMORY = re.compile(rb"glibc (\d+)\.(\d+)")
11729 PATTERN_LIBC_VERSION_FILENAME = re.compile(r"libc6?[-_](\d+)\.(\d+)\.so")
11731 def __init__(self) -> None:
11732 self._version : tuple[int, int] | None = None
11733 self._patch: int | None = None
11734 self._release: str | None = None
11735 return
11737 def __str__(self) -> str:
11738 return f"Libc(version='{self.version}')"
11740 @property
11741 def version(self) -> tuple[int, int] | None:
11742 if not is_alive(): 11742 ↛ 11743line 11742 didn't jump to line 11743 because the condition on line 11742 was never true
11743 return None
11745 if not self._version:
11746 self._version = GefLibcManager.find_libc_version()
11748 # Whenever auto-detection fails, try use the user-provided version.
11749 if self._version == (0, 0): 11749 ↛ 11750line 11749 didn't jump to line 11750 because the condition on line 11749 was never true
11750 if gef.config["gef.libc_version"]:
11751 ver = [int(v) for v in gef.config["gef.libc_version"].split(".", 1)]
11752 assert len(ver) >= 2
11753 self._version = ver[0], ver[1]
11755 return self._version
11757 @staticmethod
11758 @lru_cache()
11759 def find_libc_version() -> tuple[int, int]:
11760 """Attempt to determine the libc version. This operation can be long."""
11761 libc_sections = (m for m in gef.memory.maps if "libc" in m.path and m.permission & Permission.READ)
11762 for section in libc_sections: 11762 ↛ 11777line 11762 didn't jump to line 11777 because the loop on line 11762 didn't complete
11763 # Try to determine from the filepath
11764 match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_FILENAME, section.path)
11765 if match: 11765 ↛ 11766line 11765 didn't jump to line 11766 because the condition on line 11765 was never true
11766 return int(match.group(1)), int(match.group(2))
11768 # Try to determine from memory
11769 try:
11770 mem = gef.memory.read(section.page_start, section.size)
11771 match = re.search(GefLibcManager.PATTERN_LIBC_VERSION_MEMORY, mem)
11772 if match:
11773 return int(match.group(1)), int(match.group(2))
11774 except gdb.MemoryError:
11775 continue
11777 return 0, 0
11780class Gef:
11781 """The GEF root class, which serves as a entrypoint for all the debugging session attributes (architecture,
11782 memory, settings, etc.)."""
11783 binary: FileFormat | None
11784 arch: Architecture
11785 config : GefSettingsManager
11786 ui: GefUiManager
11787 libc: GefLibcManager
11788 memory : GefMemoryManager
11789 heap : GefHeapManager
11790 session : GefSessionManager
11791 gdb: GefCommand
11793 def __init__(self) -> None:
11794 self.binary: FileFormat | None = None
11795 self.arch: Architecture = GenericArchitecture() # see PR #516, will be reset by `new_objfile_handler`
11796 self.arch_reason: str = "This is the default architecture"
11797 self.config = GefSettingsManager()
11798 self.ui = GefUiManager()
11799 self.libc = GefLibcManager()
11800 return
11802 def __str__(self) -> str:
11803 return f"Gef(binary='{self.binary or 'None'}', arch={self.arch})"
11805 def reinitialize_managers(self) -> None:
11806 """Reinitialize the managers. Avoid calling this function directly, using `pi reset()` is preferred"""
11807 self.memory = GefMemoryManager()
11808 self.heap = GefHeapManager()
11809 self.session = GefSessionManager()
11810 return
11812 def setup(self) -> None:
11813 """Setup initialize the runtime setup, which may require for the `gef` to be not None."""
11814 self.reinitialize_managers()
11815 self.gdb = GefCommand()
11816 self.gdb.setup()
11817 gdb.execute(f"save gdb-index '{self.config['gef.tempdir']}'")
11818 return
11820 def reset_caches(self) -> None:
11821 """Recursively clean the cache of all the managers. Avoid calling this function directly, using `reset-cache`
11822 is preferred"""
11823 for mgr in (self.memory, self.heap, self.session, self.arch):
11824 mgr.reset_caches()
11825 return
11828def target_remote_posthook():
11829 if gef.session.remote_initializing:
11830 return
11832 gef.session.remote = GefRemoteSessionManager("", 0)
11833 if not gef.session.remote.setup(): 11833 ↛ 11834line 11833 didn't jump to line 11834 because the condition on line 11833 was never true
11834 raise EnvironmentError(f"Failed to create a proper environment for {gef.session.remote}")
11836if __name__ == "__main__": 11836 ↛ exitline 11836 didn't exit the module because the condition on line 11836 was always true
11837 if sys.version_info[0] == 2: 11837 ↛ 11838line 11837 didn't jump to line 11838 because the condition on line 11837 was never true
11838 err("GEF has dropped Python2 support for GDB when it reached EOL on 2020/01/01.")
11839 err("If you require GEF for GDB+Python2, use https://github.com/hugsy/gef-legacy.")
11840 exit(1)
11842 if GDB_VERSION < GDB_MIN_VERSION or PYTHON_VERSION < PYTHON_MIN_VERSION: 11842 ↛ 11843line 11842 didn't jump to line 11843 because the condition on line 11842 was never true
11843 err("You're using an old version of GDB. GEF will not work correctly. "
11844 f"Consider updating to GDB {'.'.join(map(str, GDB_MIN_VERSION))} or higher "
11845 f"(with Python {'.'.join(map(str, PYTHON_MIN_VERSION))} or higher).")
11846 exit(1)
11848 # setup config
11849 gdb_initial_settings = (
11850 "set confirm off",
11851 "set verbose off",
11852 "set pagination off",
11853 "set print elements 0",
11854 "set history save on",
11855 f"set history filename {os.getenv('GDBHISTFILE', '~/.gdb_history')}",
11856 "set output-radix 0x10",
11857 "set print pretty on",
11858 "set disassembly-flavor intel",
11859 "handle SIGALRM print nopass",
11860 )
11861 for cmd in gdb_initial_settings:
11862 try:
11863 gdb.execute(cmd)
11864 except gdb.error:
11865 pass
11867 # set fallback 'debug-file-directory' for gdbs that installed outside `/usr`.
11868 try:
11869 default_dbgsym_path = "/usr/lib/debug"
11870 param_name = "debug-file-directory"
11871 dbgsym_paths = gdb.parameter(param_name)
11872 if not isinstance(dbgsym_paths, str): 11872 ↛ 11873line 11872 didn't jump to line 11873 because the condition on line 11872 was never true
11873 raise TypeError
11874 if default_dbgsym_path not in dbgsym_paths: 11874 ↛ 11875line 11874 didn't jump to line 11875 because the condition on line 11874 was never true
11875 newpath = f"{dbgsym_paths}:" if dbgsym_paths else ""
11876 newpath += default_dbgsym_path
11877 gdb.execute(f"set {param_name} {newpath}")
11878 except gdb.error as e:
11879 warn(f"Failed to set {param_name}, reason: {str(e)}")
11881 # load GEF, set up the managers and load the plugins, functions,
11882 gef = Gef()
11883 reset()
11884 assert isinstance(gef, Gef)
11885 gef.gdb.load()
11886 gef.gdb.show_banner()
11888 # load config
11889 if gef.gdb.load_extra_plugins(): 11889 ↛ 11891line 11889 didn't jump to line 11891 because the condition on line 11889 was never true
11890 # reload settings
11891 gdb.execute("gef restore")
11893 # setup gdb prompt
11894 gdb.prompt_hook = __gef_prompt__
11896 # gdb events configuration
11897 gef_on_continue_hook(continue_handler)
11898 gef_on_stop_hook(hook_stop_handler)
11899 gef_on_new_hook(new_objfile_handler)
11900 gef_on_exit_hook(exit_handler)
11901 gef_on_memchanged_hook(memchanged_handler)
11902 gef_on_regchanged_hook(regchanged_handler)
11904 progspace = gdb.current_progspace()
11905 if progspace and progspace.filename: 11905 ↛ 11909line 11905 didn't jump to line 11909 because the condition on line 11905 was always true
11906 # if here, we are sourcing gef from a gdb session already attached, force call to new_objfile (see issue #278)
11907 new_objfile_handler(None)
11909 GefTmuxSetup()
11911 if GDB_VERSION > (9, 0): 11911 ↛ 11939line 11911 didn't jump to line 11939 because the condition on line 11911 was always true
11912 disable_tr_overwrite_setting = "gef.disable_target_remote_overwrite"
11914 if not gef.config[disable_tr_overwrite_setting]: 11914 ↛ 11931line 11914 didn't jump to line 11931 because the condition on line 11914 was always true
11915 warnmsg = ("Using `target remote` with GEF should work in most cases, "
11916 "but use `gef-remote` if you can. You can disable the "
11917 "overwrite of the `target remote` command by toggling "
11918 f"`{disable_tr_overwrite_setting}` in the config.")
11919 hook = f"""
11920 define target hookpost-{ }
11921 pi target_remote_posthook()
11922 context
11923 pi if calling_function() != "connect": warn("{warnmsg}")
11924 end
11925 """
11927 # Register a post-hook for `target remote` that initialize the remote session
11928 gdb.execute(hook.format("remote"))
11929 gdb.execute(hook.format("extended-remote"))
11930 else:
11931 errmsg = ("Using `target remote` does not work, use `gef-remote` "
11932 f"instead. You can toggle `{disable_tr_overwrite_setting}` "
11933 "if this is not desired.")
11934 hook = f"""pi if calling_function() != "connect": err("{errmsg}")"""
11935 gdb.execute(f"define target hook-remote\n{hook}\nend")
11936 gdb.execute(f"define target hook-extended-remote\n{hook}\nend")
11938 # restore saved breakpoints (if any)
11939 bkp_fpath = pathlib.Path(gef.config["gef.autosave_breakpoints_file"]).expanduser().absolute()
11940 if bkp_fpath.is_file(): 11940 ↛ 11941line 11940 didn't jump to line 11941 because the condition on line 11940 was never true
11941 gdb.execute(f"source {bkp_fpath}")
11943 # Add a `source` post hook to force gef to recheck the registered plugins and
11944 # eventually load the missing one(s)
11945 gdb.execute("define hookpost-source\npi gef.gdb.load()\nend")