Coverage for gef.py: 71.6387%
7578 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-12 03:36 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-12 03:36 +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-2023 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 site
74import socket
75import string
76import struct
77import subprocess
78import sys
79import tempfile
80import time
81import traceback
82import warnings
83from functools import lru_cache
84from io import StringIO, TextIOWrapper
85from types import ModuleType
86from typing import (Any, ByteString, Callable, Dict, Generator, Iterable,
87 Iterator, List, NoReturn, Optional, Sequence, Set, Tuple, Type,
88 Union)
89from urllib.request import urlopen
91GEF_DEFAULT_BRANCH = "main"
92GEF_EXTRAS_DEFAULT_BRANCH = "main"
94def http_get(url: str) -> Optional[bytes]:
95 """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK,
96 otherwise return None."""
97 try:
98 http = urlopen(url)
99 return http.read() if http.getcode() == 200 else None
100 except Exception:
101 return None
104def update_gef(argv: List[str]) -> int:
105 """Try to update `gef` to the latest version pushed on GitHub main branch.
106 Return 0 on success, 1 on failure. """
107 ver = GEF_DEFAULT_BRANCH
108 latest_gef_data = http_get(f"https://raw.githubusercontent.com/hugsy/gef/{ver}/scripts/gef.sh")
109 if not latest_gef_data:
110 print("[-] Failed to get remote gef")
111 return 1
112 with tempfile.NamedTemporaryFile(suffix=".sh") as fd:
113 fd.write(latest_gef_data)
114 fd.flush()
115 fpath = pathlib.Path(fd.name)
116 return subprocess.run(["bash", fpath, ver], stdout=subprocess.DEVNULL,
117 stderr=subprocess.DEVNULL).returncode
120try:
121 import gdb # type:ignore
122except ImportError:
123 # if out of gdb, the only action allowed is to update gef.py
124 if len(sys.argv) >= 2 and sys.argv[1].lower() in ("--update", "--upgrade"):
125 sys.exit(update_gef(sys.argv[2:]))
126 print("[-] gef cannot run as standalone")
127 sys.exit(0)
130GDB_MIN_VERSION = (8, 0)
131GDB_VERSION = tuple(map(int, re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups()))
132PYTHON_MIN_VERSION = (3, 6)
133PYTHON_VERSION = sys.version_info[0:2]
135DEFAULT_PAGE_ALIGN_SHIFT = 12
136DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT
138GEF_RC = (pathlib.Path(os.getenv("GEF_RC", "")).absolute()
139 if os.getenv("GEF_RC")
140 else pathlib.Path().home() / ".gef.rc")
141GEF_TEMP_DIR = os.path.join(tempfile.gettempdir(), "gef")
142GEF_MAX_STRING_LENGTH = 50
144LIBC_HEAP_MAIN_ARENA_DEFAULT_NAME = "main_arena"
145ANSI_SPLIT_RE = r"(\033\[[\d;]*m)"
147LEFT_ARROW = " ← "
148RIGHT_ARROW = " → "
149DOWN_ARROW = "↳"
150HORIZONTAL_LINE = "─"
151VERTICAL_LINE = "│"
152CROSS = "✘ "
153TICK = "✓ "
154BP_GLYPH = "●"
155GEF_PROMPT = "gef➤ "
156GEF_PROMPT_ON = f"\001\033[1;32m\002{GEF_PROMPT}\001\033[0m\002"
157GEF_PROMPT_OFF = f"\001\033[1;31m\002{GEF_PROMPT}\001\033[0m\002"
159PATTERN_LIBC_VERSION = re.compile(rb"glibc (\d+)\.(\d+)")
161gef : "Gef"
162__registered_commands__ : Set[Type["GenericCommand"]] = set()
163__registered_functions__ : Set[Type["GenericFunction"]] = set()
164__registered_architectures__ : Dict[Union["Elf.Abi", str], Type["Architecture"]] = {}
165__registered_file_formats__ : Set[ Type["FileFormat"] ] = set()
168def reset_all_caches() -> None:
169 """Free all caches. If an object is cached, it will have a callable attribute `cache_clear`
170 which will be invoked to purge the function cache."""
172 for mod in dir(sys.modules["__main__"]):
173 obj = getattr(sys.modules["__main__"], mod)
174 if hasattr(obj, "cache_clear"):
175 obj.cache_clear()
177 gef.reset_caches()
178 return
181def reset() -> None:
182 global gef
184 arch = None
185 if "gef" in locals().keys(): 185 ↛ 186line 185 didn't jump to line 186, because the condition on line 185 was never true
186 reset_all_caches()
187 arch = gef.arch
188 del gef
190 gef = Gef()
191 gef.setup()
193 if arch: 193 ↛ 194line 193 didn't jump to line 194, because the condition on line 193 was never true
194 gef.arch = arch
195 return
198def highlight_text(text: str) -> str:
199 """
200 Highlight text using `gef.ui.highlight_table` { match -> color } settings.
202 If RegEx is enabled it will create a match group around all items in the
203 `gef.ui.highlight_table` and wrap the specified color in the `gef.ui.highlight_table`
204 around those matches.
206 If RegEx is disabled, split by ANSI codes and 'colorify' each match found
207 within the specified string.
208 """
209 global gef
211 if not gef.ui.highlight_table:
212 return text
214 if gef.config["highlight.regex"]: 214 ↛ 215line 214 didn't jump to line 215, because the condition on line 214 was never true
215 for match, color in gef.ui.highlight_table.items():
216 text = re.sub("(" + match + ")", Color.colorify("\\1", color), text)
217 return text
219 ansiSplit = re.split(ANSI_SPLIT_RE, text)
221 for match, color in gef.ui.highlight_table.items():
222 for index, val in enumerate(ansiSplit): 222 ↛ 227line 222 didn't jump to line 227, because the loop on line 222 didn't complete
223 found = val.find(match)
224 if found > -1:
225 ansiSplit[index] = val.replace(match, Color.colorify(match, color))
226 break
227 text = "".join(ansiSplit)
228 ansiSplit = re.split(ANSI_SPLIT_RE, text)
230 return "".join(ansiSplit)
233def gef_print(*args: str, end="\n", sep=" ", **kwargs: Any) -> None:
234 """Wrapper around print(), using string buffering feature."""
235 parts = [highlight_text(a) for a in args]
236 if buffer_output() and gef.ui.stream_buffer and not is_debug(): 236 ↛ 237line 236 didn't jump to line 237, because the condition on line 236 was never true
237 gef.ui.stream_buffer.write(sep.join(parts) + end)
238 return
240 print(*parts, sep=sep, end=end, **kwargs)
241 return
244def bufferize(f: Callable) -> Callable:
245 """Store the content to be printed for a function in memory, and flush it on function exit."""
247 @functools.wraps(f)
248 def wrapper(*args: Any, **kwargs: Any) -> Any:
249 global gef
251 if gef.ui.stream_buffer:
252 return f(*args, **kwargs)
254 gef.ui.stream_buffer = StringIO()
255 try:
256 rv = f(*args, **kwargs)
257 finally:
258 redirect = gef.config["context.redirect"]
259 if redirect.startswith("/dev/pts/"): 259 ↛ 260line 259 didn't jump to line 260, because the condition on line 259 was never true
260 if not gef.ui.redirect_fd:
261 # if the FD has never been open, open it
262 fd = open(redirect, "wt")
263 gef.ui.redirect_fd = fd
264 elif redirect != gef.ui.redirect_fd.name:
265 # if the user has changed the redirect setting during runtime, update the state
266 gef.ui.redirect_fd.close()
267 fd = open(redirect, "wt")
268 gef.ui.redirect_fd = fd
269 else:
270 # otherwise, keep using it
271 fd = gef.ui.redirect_fd
272 else:
273 fd = sys.stdout
274 gef.ui.redirect_fd = None
276 if gef.ui.redirect_fd and fd.closed: 276 ↛ 278line 276 didn't jump to line 278, because the condition on line 276 was never true
277 # if the tty was closed, revert back to stdout
278 fd = sys.stdout
279 gef.ui.redirect_fd = None
280 gef.config["context.redirect"] = ""
282 fd.write(gef.ui.stream_buffer.getvalue())
283 fd.flush()
284 gef.ui.stream_buffer = None
285 return rv
287 return wrapper
290#
291# Helpers
292#
294def p8(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
295 """Pack one byte respecting the current architecture endianness."""
296 endian = e or gef.arch.endianness
297 return struct.pack(f"{endian}B", x) if not s else struct.pack(f"{endian:s}b", x)
300def p16(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
301 """Pack one word respecting the current architecture endianness."""
302 endian = e or gef.arch.endianness
303 return struct.pack(f"{endian}H", x) if not s else struct.pack(f"{endian:s}h", x)
306def p32(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
307 """Pack one dword respecting the current architecture endianness."""
308 endian = e or gef.arch.endianness
309 return struct.pack(f"{endian}I", x) if not s else struct.pack(f"{endian:s}i", x)
312def p64(x: int, s: bool = False, e: Optional["Endianness"] = None) -> bytes:
313 """Pack one qword respecting the current architecture endianness."""
314 endian = e or gef.arch.endianness
315 return struct.pack(f"{endian}Q", x) if not s else struct.pack(f"{endian:s}q", x)
318def u8(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
319 """Unpack one byte respecting the current architecture endianness."""
320 endian = e or gef.arch.endianness
321 return struct.unpack(f"{endian}B", x)[0] if not s else struct.unpack(f"{endian:s}b", x)[0]
324def u16(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
325 """Unpack one word respecting the current architecture endianness."""
326 endian = e or gef.arch.endianness
327 return struct.unpack(f"{endian}H", x)[0] if not s else struct.unpack(f"{endian:s}h", x)[0]
330def u32(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
331 """Unpack one dword respecting the current architecture endianness."""
332 endian = e or gef.arch.endianness
333 return struct.unpack(f"{endian}I", x)[0] if not s else struct.unpack(f"{endian:s}i", x)[0]
336def u64(x: bytes, s: bool = False, e: Optional["Endianness"] = None) -> int:
337 """Unpack one qword respecting the current architecture endianness."""
338 endian = e or gef.arch.endianness
339 return struct.unpack(f"{endian}Q", x)[0] if not s else struct.unpack(f"{endian:s}q", x)[0]
342def is_ascii_string(address: int) -> bool:
343 """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)"""
344 try:
345 return gef.memory.read_ascii_string(address) is not None
346 except Exception:
347 return False
350def is_alive() -> bool:
351 """Check if GDB is running."""
352 try:
353 return gdb.selected_inferior().pid > 0
354 except Exception:
355 return False
358def calling_function() -> Optional[str]:
359 """Return the name of the calling function"""
360 try:
361 stack_info = traceback.extract_stack()[-3]
362 return stack_info.name
363 except:
364 return None
367#
368# Decorators
369#
370def only_if_gdb_running(f: Callable) -> Callable:
371 """Decorator wrapper to check if GDB is running."""
373 @functools.wraps(f)
374 def wrapper(*args: Any, **kwargs: Any) -> Any:
375 if is_alive():
376 return f(*args, **kwargs)
377 else:
378 warn("No debugging session active")
380 return wrapper
383def only_if_gdb_target_local(f: Callable) -> Callable:
384 """Decorator wrapper to check if GDB is running locally (target not remote)."""
386 @functools.wraps(f)
387 def wrapper(*args: Any, **kwargs: Any) -> Any:
388 if not is_remote_debug(): 388 ↛ 391line 388 didn't jump to line 391, because the condition on line 388 was never false
389 return f(*args, **kwargs)
390 else:
391 warn("This command cannot work for remote sessions.")
393 return wrapper
396def deprecated(solution: str = "") -> Callable:
397 """Decorator to add a warning when a command is obsolete and will be removed."""
398 def decorator(f: Callable) -> Callable:
399 @functools.wraps(f)
400 def wrapper(*args: Any, **kwargs: Any) -> Any:
401 caller = inspect.stack()[1]
402 caller_file = pathlib.Path(caller.filename)
403 caller_loc = caller.lineno
404 msg = f"{caller_file.name}:L{caller_loc} '{f.__name__}' is deprecated and will be removed in a feature release. "
405 if not gef: 405 ↛ 406line 405 didn't jump to line 406, because the condition on line 405 was never true
406 print(msg)
407 elif gef.config["gef.show_deprecation_warnings"] is True: 407 ↛ 411line 407 didn't jump to line 411, because the condition on line 407 was never false
408 if solution: 408 ↛ 410line 408 didn't jump to line 410, because the condition on line 408 was never false
409 msg += solution
410 warn(msg)
411 return f(*args, **kwargs)
413 if not wrapper.__doc__:
414 wrapper.__doc__ = ""
415 wrapper.__doc__ += f"\r\n`{f.__name__}` is **DEPRECATED** and will be removed in the future.\r\n{solution}"
416 return wrapper
417 return decorator
420def experimental_feature(f: Callable) -> Callable:
421 """Decorator to add a warning when a feature is experimental."""
423 @functools.wraps(f)
424 def wrapper(*args: Any, **kwargs: Any) -> Any:
425 warn("This feature is under development, expect bugs and unstability...")
426 return f(*args, **kwargs)
428 return wrapper
431def only_if_events_supported(event_type: str) -> Callable:
432 """Checks if GDB supports events without crashing."""
433 def wrap(f: Callable) -> Callable:
434 def wrapped_f(*args: Any, **kwargs: Any) -> Any:
435 if getattr(gdb, "events") and getattr(gdb.events, event_type): 435 ↛ 437line 435 didn't jump to line 437, because the condition on line 435 was never false
436 return f(*args, **kwargs)
437 warn("GDB events cannot be set")
438 return wrapped_f
439 return wrap
442class classproperty(property):
443 """Make the attribute a `classproperty`."""
444 def __get__(self, cls, owner):
445 return classmethod(self.fget).__get__(None, owner)()
448def FakeExit(*args: Any, **kwargs: Any) -> NoReturn:
449 raise RuntimeWarning
452sys.exit = FakeExit
455def parse_arguments(required_arguments: Dict[Union[str, Tuple[str, str]], Any],
456 optional_arguments: Dict[Union[str, Tuple[str, str]], Any]) -> Callable:
457 """Argument parsing decorator."""
459 def int_wrapper(x: str) -> int: return int(x, 0)
461 def decorator(f: Callable) -> Optional[Callable]:
462 def wrapper(*args: Any, **kwargs: Any) -> Callable:
463 parser = argparse.ArgumentParser(prog=args[0]._cmdline_, add_help=True)
464 for argname in required_arguments:
465 argvalue = required_arguments[argname]
466 argtype = type(argvalue)
467 if argtype is int:
468 argtype = int_wrapper
470 argname_is_list = not isinstance(argname, str)
471 assert not argname_is_list and isinstance(argname, str)
472 if not argname_is_list and argname.startswith("-"): 472 ↛ 474line 472 didn't jump to line 474, because the condition on line 472 was never true
473 # optional args
474 if argtype is bool:
475 parser.add_argument(argname, action="store_true" if argvalue else "store_false")
476 else:
477 parser.add_argument(argname, type=argtype, required=True, default=argvalue)
478 else:
479 if argtype in (list, tuple):
480 nargs = "*"
481 argtype = type(argvalue[0])
482 else:
483 nargs = "?"
484 # positional args
485 parser.add_argument(argname, type=argtype, default=argvalue, nargs=nargs)
487 for argname in optional_arguments:
488 if isinstance(argname, str) and not argname.startswith("-"): 488 ↛ 490line 488 didn't jump to line 490, because the condition on line 488 was never true
489 # refuse positional arguments
490 continue
491 argvalue = optional_arguments[argname]
492 argtype = type(argvalue)
493 if isinstance(argname, str):
494 argname = [argname,]
495 if argtype is int:
496 argtype = int_wrapper
497 if argtype is bool:
498 parser.add_argument(*argname, action="store_true" if argvalue else "store_false")
499 else:
500 parser.add_argument(*argname, type=argtype, default=argvalue)
502 parsed_args = parser.parse_args(*(args[1:]))
503 kwargs["arguments"] = parsed_args
504 return f(*args, **kwargs)
505 return wrapper
506 return decorator
509class Color:
510 """Used to colorify terminal output."""
511 colors = {
512 "normal" : "\033[0m",
513 "gray" : "\033[1;38;5;240m",
514 "light_gray" : "\033[0;37m",
515 "red" : "\033[31m",
516 "green" : "\033[32m",
517 "yellow" : "\033[33m",
518 "blue" : "\033[34m",
519 "pink" : "\033[35m",
520 "cyan" : "\033[36m",
521 "bold" : "\033[1m",
522 "underline" : "\033[4m",
523 "underline_off" : "\033[24m",
524 "highlight" : "\033[3m",
525 "highlight_off" : "\033[23m",
526 "blink" : "\033[5m",
527 "blink_off" : "\033[25m",
528 }
530 @staticmethod
531 def redify(msg: str) -> str: return Color.colorify(msg, "red")
532 @staticmethod
533 def greenify(msg: str) -> str: return Color.colorify(msg, "green")
534 @staticmethod
535 def blueify(msg: str) -> str: return Color.colorify(msg, "blue")
536 @staticmethod
537 def yellowify(msg: str) -> str: return Color.colorify(msg, "yellow")
538 @staticmethod
539 def grayify(msg: str) -> str: return Color.colorify(msg, "gray") 539 ↛ exitline 539 didn't return from function 'grayify', because the return on line 539 wasn't executed
540 @staticmethod
541 def light_grayify(msg: str) -> str: return Color.colorify(msg, "light_gray") 541 ↛ exitline 541 didn't return from function 'light_grayify', because the return on line 541 wasn't executed
542 @staticmethod
543 def pinkify(msg: str) -> str: return Color.colorify(msg, "pink")
544 @staticmethod
545 def cyanify(msg: str) -> str: return Color.colorify(msg, "cyan") 545 ↛ exitline 545 didn't return from function 'cyanify', because the return on line 545 wasn't executed
546 @staticmethod
547 def boldify(msg: str) -> str: return Color.colorify(msg, "bold")
548 @staticmethod
549 def underlinify(msg: str) -> str: return Color.colorify(msg, "underline") 549 ↛ exitline 549 didn't return from function 'underlinify', because the return on line 549 wasn't executed
550 @staticmethod
551 def highlightify(msg: str) -> str: return Color.colorify(msg, "highlight") 551 ↛ exitline 551 didn't return from function 'highlightify', because the return on line 551 wasn't executed
552 @staticmethod
553 def blinkify(msg: str) -> str: return Color.colorify(msg, "blink") 553 ↛ exitline 553 didn't return from function 'blinkify', because the return on line 553 wasn't executed
555 @staticmethod
556 def colorify(text: str, attrs: str) -> str:
557 """Color text according to the given attributes."""
558 if gef.config["gef.disable_color"] is True: return text 558 ↛ exitline 558 didn't return from function 'colorify', because the return on line 558 wasn't executed
560 colors = Color.colors
561 msg = [colors[attr] for attr in attrs.split() if attr in colors]
562 msg.append(str(text))
563 if colors["highlight"] in msg: msg.append(colors["highlight_off"])
564 if colors["underline"] in msg: msg.append(colors["underline_off"])
565 if colors["blink"] in msg: msg.append(colors["blink_off"])
566 msg.append(colors["normal"])
567 return "".join(msg)
570class Address:
571 """GEF representation of memory addresses."""
572 def __init__(self, **kwargs: Any) -> None:
573 self.value: int = kwargs.get("value", 0)
574 self.section: "Section" = kwargs.get("section", None)
575 self.info: "Zone" = kwargs.get("info", None)
576 return
578 def __str__(self) -> str:
579 value = format_address(self.value)
580 code_color = gef.config["theme.address_code"]
581 stack_color = gef.config["theme.address_stack"]
582 heap_color = gef.config["theme.address_heap"]
583 if self.is_in_text_segment():
584 return Color.colorify(value, code_color)
585 if self.is_in_heap_segment(): 585 ↛ 586line 585 didn't jump to line 586, because the condition on line 585 was never true
586 return Color.colorify(value, heap_color)
587 if self.is_in_stack_segment():
588 return Color.colorify(value, stack_color)
589 return value
591 def __int__(self) -> int:
592 return self.value
594 def is_in_text_segment(self) -> bool:
595 return (hasattr(self.info, "name") and ".text" in self.info.name) or \
596 (hasattr(self.section, "path") and get_filepath() == self.section.path and self.section.is_executable())
598 def is_in_stack_segment(self) -> bool:
599 return hasattr(self.section, "path") and "[stack]" == self.section.path
601 def is_in_heap_segment(self) -> bool:
602 return hasattr(self.section, "path") and "[heap]" == self.section.path
604 def dereference(self) -> Optional[int]:
605 addr = align_address(int(self.value))
606 derefed = dereference(addr)
607 return None if derefed is None else int(derefed)
609 @property
610 def valid(self) -> bool:
611 return any(map(lambda x: x.page_start <= self.value < x.page_end, gef.memory.maps))
614class Permission(enum.Flag):
615 """GEF representation of Linux permission."""
616 NONE = 0
617 EXECUTE = 1
618 WRITE = 2
619 READ = 4
620 ALL = 7
622 def __str__(self) -> str:
623 perm_str = ""
624 perm_str += "r" if self & Permission.READ else "-"
625 perm_str += "w" if self & Permission.WRITE else "-"
626 perm_str += "x" if self & Permission.EXECUTE else "-"
627 return perm_str
629 @classmethod
630 def from_info_sections(cls, *args: str) -> "Permission":
631 perm = cls(0)
632 for arg in args:
633 if "READONLY" in arg: perm |= Permission.READ
634 if "DATA" in arg: perm |= Permission.WRITE
635 if "CODE" in arg: perm |= Permission.EXECUTE
636 return perm
638 @classmethod
639 def from_process_maps(cls, perm_str: str) -> "Permission":
640 perm = cls(0)
641 if perm_str[0] == "r": perm |= Permission.READ
642 if perm_str[1] == "w": perm |= Permission.WRITE
643 if perm_str[2] == "x": perm |= Permission.EXECUTE
644 return perm
646 @classmethod
647 def from_info_mem(cls, perm_str: str) -> "Permission":
648 perm = cls(0)
649 # perm_str[0] shows if this is a user page, which
650 # we don't track
651 if perm_str[1] == "r": perm |= Permission.READ
652 if perm_str[2] == "w": perm |= Permission.WRITE
653 return perm
656class Section:
657 """GEF representation of process memory sections."""
659 def __init__(self, **kwargs: Any) -> None:
660 self.page_start: int = kwargs.get("page_start", 0)
661 self.page_end: int = kwargs.get("page_end", 0)
662 self.offset: int = kwargs.get("offset", 0)
663 self.permission: Permission = kwargs.get("permission", Permission(0))
664 self.inode: int = kwargs.get("inode", 0)
665 self.path: str = kwargs.get("path", "")
666 return
668 def is_readable(self) -> bool:
669 return (self.permission & Permission.READ) != 0
671 def is_writable(self) -> bool:
672 return (self.permission & Permission.WRITE) != 0
674 def is_executable(self) -> bool:
675 return (self.permission & Permission.EXECUTE) != 0
677 @property
678 def size(self) -> int:
679 if self.page_end is None or self.page_start is None:
680 return -1
681 return self.page_end - self.page_start
683 @property
684 def realpath(self) -> str:
685 # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote
686 return self.path if gef.session.remote is None else f"/tmp/gef/{gef.session.remote:d}/{self.path}"
688 def __str__(self) -> str:
689 return (f"Section(page_start={self.page_start:#x}, page_end={self.page_end:#x}, "
690 f"permissions={self.permission!s})")
693Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"])
696class Endianness(enum.Enum):
697 LITTLE_ENDIAN = 1
698 BIG_ENDIAN = 2
700 def __str__(self) -> str:
701 return "<" if self == Endianness.LITTLE_ENDIAN else ">"
703 def __repr__(self) -> str:
704 return self.name
706 def __int__(self) -> int:
707 return self.value
710class FileFormatSection:
711 misc: Any
714class FileFormat:
715 name: str
716 path: pathlib.Path
717 entry_point: int
718 checksec: Dict[str, bool]
719 sections: List[FileFormatSection]
721 def __init__(self, path: Union[str, pathlib.Path]) -> None:
722 raise NotImplemented
724 def __init_subclass__(cls: Type["FileFormat"], **kwargs):
725 global __registered_file_formats__
726 super().__init_subclass__(**kwargs)
727 required_attributes = ("name", "entry_point", "is_valid", "checksec",)
728 for attr in required_attributes:
729 if not hasattr(cls, attr): 729 ↛ 730line 729 didn't jump to line 730, because the condition on line 729 was never true
730 raise NotImplementedError(f"File format '{cls.__name__}' is invalid: missing attribute '{attr}'")
731 __registered_file_formats__.add(cls)
732 return
734 @classmethod
735 def is_valid(cls, path: pathlib.Path) -> bool:
736 raise NotImplemented
738 def __str__(self) -> str:
739 return f"{self.name}('{self.path.absolute()}', entry @ {self.entry_point:#x})"
742class Elf(FileFormat):
743 """Basic ELF parsing.
744 Ref:
745 - http://www.skyfree.org/linux/references/ELF_Format.pdf
746 - https://refspecs.linuxfoundation.org/elf/elfspec_ppc.pdf
747 - https://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
748 """
749 class Class(enum.Enum):
750 ELF_32_BITS = 0x01
751 ELF_64_BITS = 0x02
753 ELF_MAGIC = 0x7f454c46
755 class Abi(enum.Enum):
756 X86_64 = 0x3e
757 X86_32 = 0x03
758 ARM = 0x28
759 MIPS = 0x08
760 POWERPC = 0x14
761 POWERPC64 = 0x15
762 SPARC = 0x02
763 SPARC64 = 0x2b
764 AARCH64 = 0xb7
765 RISCV = 0xf3
766 IA64 = 0x32
767 M68K = 0x04
769 class Type(enum.Enum):
770 ET_RELOC = 1
771 ET_EXEC = 2
772 ET_DYN = 3
773 ET_CORE = 4
775 class OsAbi(enum.Enum):
776 SYSTEMV = 0x00
777 HPUX = 0x01
778 NETBSD = 0x02
779 LINUX = 0x03
780 SOLARIS = 0x06
781 AIX = 0x07
782 IRIX = 0x08
783 FREEBSD = 0x09
784 OPENBSD = 0x0C
786 e_magic: int = ELF_MAGIC
787 e_class: "Elf.Class" = Class.ELF_32_BITS
788 e_endianness: Endianness = Endianness.LITTLE_ENDIAN
789 e_eiversion: int
790 e_osabi: "Elf.OsAbi"
791 e_abiversion: int
792 e_pad: bytes
793 e_type: "Elf.Type" = Type.ET_EXEC
794 e_machine: Abi = Abi.X86_32
795 e_version: int
796 e_entry: int
797 e_phoff: int
798 e_shoff: int
799 e_flags: int
800 e_ehsize: int
801 e_phentsize: int
802 e_phnum: int
803 e_shentsize: int
804 e_shnum: int
805 e_shstrndx: int
807 path: pathlib.Path
808 phdrs : List["Phdr"]
809 shdrs : List["Shdr"]
810 name: str = "ELF"
812 __checksec : Dict[str, bool]
814 def __init__(self, path: Union[str, pathlib.Path]) -> None:
815 """Instantiate an ELF object. A valid ELF must be provided, or an exception will be thrown."""
817 if isinstance(path, str):
818 self.path = pathlib.Path(path).expanduser()
819 elif isinstance(path, pathlib.Path): 819 ↛ 822line 819 didn't jump to line 822, because the condition on line 819 was never false
820 self.path = path
821 else:
822 raise TypeError
824 if not self.path.exists(): 824 ↛ 825line 824 didn't jump to line 825, because the condition on line 824 was never true
825 raise FileNotFoundError(f"'{self.path}' not found/readable, most gef features will not work")
827 self.__checksec = {}
829 with self.path.open("rb") as self.fd:
830 # off 0x0
831 self.e_magic, e_class, e_endianness, self.e_eiversion = self.read_and_unpack(">IBBB")
832 if self.e_magic != Elf.ELF_MAGIC: 832 ↛ 834line 832 didn't jump to line 834, because the condition on line 832 was never true
833 # The ELF is corrupted, GDB won't handle it, no point going further
834 raise RuntimeError("Not a valid ELF file (magic)")
836 self.e_class, self.e_endianness = Elf.Class(e_class), Endianness(e_endianness)
838 if self.e_endianness != gef.arch.endianness: 838 ↛ 839line 838 didn't jump to line 839, because the condition on line 838 was never true
839 warn("Unexpected endianness for architecture")
841 endian = self.e_endianness
843 # off 0x7
844 e_osabi, self.e_abiversion = self.read_and_unpack(f"{endian}BB")
845 self.e_osabi = Elf.OsAbi(e_osabi)
847 # off 0x9
848 self.e_pad = self.read(7)
850 # off 0x10
851 e_type, e_machine, self.e_version = self.read_and_unpack(f"{endian}HHI")
852 self.e_type, self.e_machine = Elf.Type(e_type), Elf.Abi(e_machine)
854 # off 0x18
855 if self.e_class == Elf.Class.ELF_64_BITS: 855 ↛ 858line 855 didn't jump to line 858, because the condition on line 855 was never false
856 self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}QQQ")
857 else:
858 self.e_entry, self.e_phoff, self.e_shoff = self.read_and_unpack(f"{endian}III")
860 self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = self.read_and_unpack(f"{endian}IHHH")
861 self.e_shentsize, self.e_shnum, self.e_shstrndx = self.read_and_unpack(f"{endian}HHH")
863 self.phdrs = []
864 for i in range(self.e_phnum):
865 self.phdrs.append(Phdr(self, self.e_phoff + self.e_phentsize * i))
867 self.shdrs = []
868 for i in range(self.e_shnum):
869 self.shdrs.append(Shdr(self, self.e_shoff + self.e_shentsize * i))
870 return
872 def read(self, size: int) -> bytes:
873 return self.fd.read(size)
875 def read_and_unpack(self, fmt: str) -> Tuple[Any, ...]:
876 size = struct.calcsize(fmt)
877 data = self.fd.read(size)
878 return struct.unpack(fmt, data)
880 def seek(self, off: int) -> None:
881 self.fd.seek(off, 0)
883 def __str__(self) -> str:
884 return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})"
886 def __repr__(self) -> str:
887 return f"ELF('{self.path.absolute()}', {self.e_class.name}, {self.e_machine.name})"
889 @property
890 def entry_point(self) -> int:
891 return self.e_entry
893 @classmethod
894 def is_valid(cls, path: pathlib.Path) -> bool:
895 return u32(path.open("rb").read(4), e = Endianness.BIG_ENDIAN) == Elf.ELF_MAGIC
897 @property
898 def checksec(self) -> Dict[str, bool]:
899 """Check the security property of the ELF binary. The following properties are:
900 - Canary
901 - NX
902 - PIE
903 - Fortify
904 - Partial/Full RelRO.
905 Return a dict() with the different keys mentioned above, and the boolean
906 associated whether the protection was found."""
907 if not self.__checksec: 907 ↛ 930line 907 didn't jump to line 930, because the condition on line 907 was never false
908 def __check_security_property(opt: str, filename: str, pattern: str) -> bool:
909 cmd = [readelf,]
910 cmd += opt.split()
911 cmd += [filename,]
912 lines = gef_execute_external(cmd, as_list=True)
913 for line in lines:
914 if re.search(pattern, line):
915 return True
916 return False
918 abspath = str(self.path.absolute())
919 readelf = gef.session.constants["readelf"]
920 self.__checksec["Canary"] = __check_security_property("-rs", abspath, r"__stack_chk_fail") is True
921 has_gnu_stack = __check_security_property("-W -l", abspath, r"GNU_STACK") is True
922 if has_gnu_stack: 922 ↛ 925line 922 didn't jump to line 925, because the condition on line 922 was never false
923 self.__checksec["NX"] = __check_security_property("-W -l", abspath, r"GNU_STACK.*RWE") is False
924 else:
925 self.__checksec["NX"] = False
926 self.__checksec["PIE"] = __check_security_property("-h", abspath, r":.*EXEC") is False
927 self.__checksec["Fortify"] = __check_security_property("-s", abspath, r"_chk@GLIBC") is True
928 self.__checksec["Partial RelRO"] = __check_security_property("-l", abspath, r"GNU_RELRO") is True
929 self.__checksec["Full RelRO"] = self.__checksec["Partial RelRO"] and __check_security_property("-d", abspath, r"BIND_NOW") is True
930 return self.__checksec
932 @classproperty
933 @deprecated("use `Elf.Abi.X86_64`")
934 def X86_64(cls) -> int: return Elf.Abi.X86_64.value # pylint: disable=no-self-argument
936 @classproperty
937 @deprecated("use `Elf.Abi.X86_32`")
938 def X86_32(cls) -> int : return Elf.Abi.X86_32.value # pylint: disable=no-self-argument
940 @classproperty
941 @deprecated("use `Elf.Abi.ARM`")
942 def ARM(cls) -> int : return Elf.Abi.ARM.value # pylint: disable=no-self-argument
944 @classproperty
945 @deprecated("use `Elf.Abi.MIPS`")
946 def MIPS(cls) -> int : return Elf.Abi.MIPS.value # pylint: disable=no-self-argument
948 @classproperty
949 @deprecated("use `Elf.Abi.POWERPC`")
950 def POWERPC(cls) -> int : return Elf.Abi.POWERPC.value # pylint: disable=no-self-argument
952 @classproperty
953 @deprecated("use `Elf.Abi.POWERPC64`")
954 def POWERPC64(cls) -> int : return Elf.Abi.POWERPC64.value # pylint: disable=no-self-argument
956 @classproperty
957 @deprecated("use `Elf.Abi.SPARC`")
958 def SPARC(cls) -> int : return Elf.Abi.SPARC.value # pylint: disable=no-self-argument
960 @classproperty
961 @deprecated("use `Elf.Abi.SPARC64`")
962 def SPARC64(cls) -> int : return Elf.Abi.SPARC64.value # pylint: disable=no-self-argument
964 @classproperty
965 @deprecated("use `Elf.Abi.AARCH64`")
966 def AARCH64(cls) -> int : return Elf.Abi.AARCH64.value # pylint: disable=no-self-argument
968 @classproperty
969 @deprecated("use `Elf.Abi.RISCV`")
970 def RISCV(cls) -> int : return Elf.Abi.RISCV.value # pylint: disable=no-self-argument
973class Phdr:
974 class Type(enum.IntEnum):
975 PT_NULL = 0
976 PT_LOAD = 1
977 PT_DYNAMIC = 2
978 PT_INTERP = 3
979 PT_NOTE = 4
980 PT_SHLIB = 5
981 PT_PHDR = 6
982 PT_TLS = 7
983 PT_LOOS = 0x60000000
984 PT_GNU_EH_FRAME = 0x6474e550
985 PT_GNU_STACK = 0x6474e551
986 PT_GNU_RELRO = 0x6474e552
987 PT_GNU_PROPERTY = 0x6474e553
988 PT_LOSUNW = 0x6ffffffa
989 PT_SUNWBSS = 0x6ffffffa
990 PT_SUNWSTACK = 0x6ffffffb
991 PT_HISUNW = PT_HIOS = 0x6fffffff
992 PT_LOPROC = 0x70000000
993 PT_ARM_EIDX = 0x70000001
994 PT_MIPS_ABIFLAGS= 0x70000003
995 PT_HIPROC = 0x7fffffff
996 UNKNOWN_PHDR = 0xffffffff
998 @classmethod
999 def _missing_(cls, _:int) -> Type:
1000 return cls.UNKNOWN_PHDR
1002 class Flags(enum.IntFlag):
1003 PF_X = 1
1004 PF_W = 2
1005 PF_R = 4
1007 p_type: "Phdr.Type"
1008 p_flags: "Phdr.Flags"
1009 p_offset: int
1010 p_vaddr: int
1011 p_paddr: int
1012 p_filesz: int
1013 p_memsz: int
1014 p_align: int
1016 def __init__(self, elf: Elf, off: int) -> None:
1017 if not elf: 1017 ↛ 1018line 1017 didn't jump to line 1018, because the condition on line 1017 was never true
1018 return
1019 elf.seek(off)
1020 self.offset = off
1021 endian = elf.e_endianness
1022 if elf.e_class == Elf.Class.ELF_64_BITS: 1022 ↛ 1027line 1022 didn't jump to line 1027, because the condition on line 1022 was never false
1023 p_type, p_flags, self.p_offset = elf.read_and_unpack(f"{endian}IIQ")
1024 self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}QQ")
1025 self.p_filesz, self.p_memsz, self.p_align = elf.read_and_unpack(f"{endian}QQQ")
1026 else:
1027 p_type, self.p_offset = elf.read_and_unpack(f"{endian}II")
1028 self.p_vaddr, self.p_paddr = elf.read_and_unpack(f"{endian}II")
1029 self.p_filesz, self.p_memsz, p_flags, self.p_align = elf.read_and_unpack(f"{endian}IIII")
1031 self.p_type, self.p_flags = Phdr.Type(p_type), Phdr.Flags(p_flags)
1032 return
1034 def __str__(self) -> str:
1035 return (f"Phdr(offset={self.offset}, type={self.p_type.name}, flags={self.p_flags.name}, "
1036 f"vaddr={self.p_vaddr}, paddr={self.p_paddr}, filesz={self.p_filesz}, "
1037 f"memsz={self.p_memsz}, align={self.p_align})")
1040class Shdr:
1041 class Type(enum.IntEnum):
1042 SHT_NULL = 0
1043 SHT_PROGBITS = 1
1044 SHT_SYMTAB = 2
1045 SHT_STRTAB = 3
1046 SHT_RELA = 4
1047 SHT_HASH = 5
1048 SHT_DYNAMIC = 6
1049 SHT_NOTE = 7
1050 SHT_NOBITS = 8
1051 SHT_REL = 9
1052 SHT_SHLIB = 10
1053 SHT_DYNSYM = 11
1054 SHT_NUM = 12
1055 SHT_INIT_ARRAY = 14
1056 SHT_FINI_ARRAY = 15
1057 SHT_PREINIT_ARRAY = 16
1058 SHT_GROUP = 17
1059 SHT_SYMTAB_SHNDX = 18
1060 SHT_LOOS = 0x60000000
1061 SHT_GNU_ATTRIBUTES = 0x6ffffff5
1062 SHT_GNU_HASH = 0x6ffffff6
1063 SHT_GNU_LIBLIST = 0x6ffffff7
1064 SHT_CHECKSUM = 0x6ffffff8
1065 SHT_LOSUNW = 0x6ffffffa
1066 SHT_SUNW_move = 0x6ffffffa
1067 SHT_SUNW_COMDAT = 0x6ffffffb
1068 SHT_SUNW_syminfo = 0x6ffffffc
1069 SHT_GNU_verdef = 0x6ffffffd
1070 SHT_GNU_verneed = 0x6ffffffe
1071 SHT_GNU_versym = 0x6fffffff
1072 SHT_LOPROC = 0x70000000
1073 SHT_ARM_EXIDX = 0x70000001
1074 SHT_X86_64_UNWIND = 0x70000001
1075 SHT_ARM_ATTRIBUTES = 0x70000003
1076 SHT_MIPS_OPTIONS = 0x7000000d
1077 DT_MIPS_INTERFACE = 0x7000002a
1078 SHT_HIPROC = 0x7fffffff
1079 SHT_LOUSER = 0x80000000
1080 SHT_HIUSER = 0x8fffffff
1081 UNKNOWN_SHDR = 0xffffffff
1083 @classmethod
1084 def _missing_(cls, _:int) -> Type:
1085 return cls.UNKNOWN_SHDR
1087 class Flags(enum.IntFlag):
1088 WRITE = 1
1089 ALLOC = 2
1090 EXECINSTR = 4
1091 MERGE = 0x10
1092 STRINGS = 0x20
1093 INFO_LINK = 0x40
1094 LINK_ORDER = 0x80
1095 OS_NONCONFORMING = 0x100
1096 GROUP = 0x200
1097 TLS = 0x400
1098 COMPRESSED = 0x800
1099 RELA_LIVEPATCH = 0x00100000
1100 RO_AFTER_INIT = 0x00200000
1101 ORDERED = 0x40000000
1102 EXCLUDE = 0x80000000
1103 UNKNOWN_FLAG = 0xffffffff
1105 @classmethod
1106 def _missing_(cls, _:int):
1107 return cls.UNKNOWN_FLAG
1109 sh_name: int
1110 sh_type: "Shdr.Type"
1111 sh_flags: "Shdr.Flags"
1112 sh_addr: int
1113 sh_offset: int
1114 sh_size: int
1115 sh_link: int
1116 sh_info: int
1117 sh_addralign: int
1118 sh_entsize: int
1119 name: str
1121 def __init__(self, elf: Optional[Elf], off: int) -> None:
1122 if elf is None: 1122 ↛ 1123line 1122 didn't jump to line 1123, because the condition on line 1122 was never true
1123 return
1124 elf.seek(off)
1125 endian = elf.e_endianness
1126 if elf.e_class == Elf.Class.ELF_64_BITS: 1126 ↛ 1132line 1126 didn't jump to line 1132, because the condition on line 1126 was never false
1127 self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}IIQ")
1128 self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}QQ")
1129 self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}QII")
1130 self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}QQ")
1131 else:
1132 self.sh_name, sh_type, sh_flags = elf.read_and_unpack(f"{endian}III")
1133 self.sh_addr, self.sh_offset = elf.read_and_unpack(f"{endian}II")
1134 self.sh_size, self.sh_link, self.sh_info = elf.read_and_unpack(f"{endian}III")
1135 self.sh_addralign, self.sh_entsize = elf.read_and_unpack(f"{endian}II")
1137 self.sh_type = Shdr.Type(sh_type)
1138 self.sh_flags = Shdr.Flags(sh_flags)
1139 stroff = elf.e_shoff + elf.e_shentsize * elf.e_shstrndx
1141 if elf.e_class == Elf.Class.ELF_64_BITS: 1141 ↛ 1145line 1141 didn't jump to line 1145, because the condition on line 1141 was never false
1142 elf.seek(stroff + 16 + 8)
1143 offset = u64(elf.read(8))
1144 else:
1145 elf.seek(stroff + 12 + 4)
1146 offset = u32(elf.read(4))
1147 elf.seek(offset + self.sh_name)
1148 self.name = ""
1149 while True:
1150 c = u8(elf.read(1))
1151 if c == 0:
1152 break
1153 self.name += chr(c)
1154 return
1156 def __str__(self) -> str:
1157 return (f"Shdr(name={self.name}, type={self.sh_type.name}, flags={self.sh_flags.name}, "
1158 f"addr={self.sh_addr:#x}, offset={self.sh_offset}, size={self.sh_size}, link={self.sh_link}, "
1159 f"info={self.sh_info}, addralign={self.sh_addralign}, entsize={self.sh_entsize})")
1162class Instruction:
1163 """GEF representation of a CPU instruction."""
1165 def __init__(self, address: int, location: str, mnemo: str, operands: List[str], opcodes: bytes) -> None:
1166 self.address, self.location, self.mnemonic, self.operands, self.opcodes = \
1167 address, location, mnemo, operands, opcodes
1168 return
1170 # Allow formatting an instruction with {:o} to show opcodes.
1171 # The number of bytes to display can be configured, e.g. {:4o} to only show 4 bytes of the opcodes
1172 def __format__(self, format_spec: str) -> str:
1173 if len(format_spec) == 0 or format_spec[-1] != "o": 1173 ↛ 1174line 1173 didn't jump to line 1174, because the condition on line 1173 was never true
1174 return str(self)
1176 if format_spec == "o": 1176 ↛ 1177line 1176 didn't jump to line 1177, because the condition on line 1176 was never true
1177 opcodes_len = len(self.opcodes)
1178 else:
1179 opcodes_len = int(format_spec[:-1])
1181 opcodes_text = "".join(f"{b:02x}" for b in self.opcodes[:opcodes_len])
1182 if opcodes_len < len(self.opcodes):
1183 opcodes_text += "..."
1184 return (f"{self.address:#10x} {opcodes_text:{opcodes_len * 2 + 3:d}s} {self.location:16} "
1185 f"{self.mnemonic:6} {', '.join(self.operands)}")
1187 def __str__(self) -> str:
1188 return f"{self.address:#10x} {self.location:16} {self.mnemonic:6} {', '.join(self.operands)}"
1190 def is_valid(self) -> bool:
1191 return "(bad)" not in self.mnemonic
1193 def size(self) -> int:
1194 return len(self.opcodes)
1196 def next(self) -> "Instruction":
1197 address = self.address + self.size()
1198 return gef_get_instruction_at(address)
1201@deprecated("Use GefHeapManager.find_main_arena_addr()")
1202def search_for_main_arena() -> int:
1203 return GefHeapManager.find_main_arena_addr()
1205class GlibcHeapInfo:
1206 """Glibc heap_info struct"""
1208 @staticmethod
1209 def heap_info_t() -> Type[ctypes.Structure]:
1210 assert gef.libc.version
1211 class heap_info_cls(ctypes.Structure):
1212 pass
1213 pointer = ctypes.c_uint64 if gef.arch.ptrsize == 8 else ctypes.c_uint32
1214 pad_size = -5 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1)
1215 fields = [
1216 ("ar_ptr", ctypes.POINTER(GlibcArena.malloc_state_t())),
1217 ("prev", ctypes.POINTER(heap_info_cls)),
1218 ("size", pointer)
1219 ]
1220 if gef.libc.version >= (2, 5): 1220 ↛ 1225line 1220 didn't jump to line 1225, because the condition on line 1220 was never false
1221 fields += [
1222 ("mprotect_size", pointer)
1223 ]
1224 pad_size = -6 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1)
1225 if gef.libc.version >= (2, 34): 1225 ↛ 1230line 1225 didn't jump to line 1230, because the condition on line 1225 was never false
1226 fields += [
1227 ("pagesize", pointer)
1228 ]
1229 pad_size = -3 * gef.arch.ptrsize & (gef.heap.malloc_alignment - 1)
1230 fields += [
1231 ("pad", ctypes.c_uint8*pad_size)
1232 ]
1233 heap_info_cls._fields_ = fields
1234 return heap_info_cls
1236 def __init__(self, addr: Union[str, int]) -> None:
1237 self.__address : int = parse_address(f"&{addr}") if isinstance(addr, str) else addr
1238 self.reset()
1239 return
1241 def reset(self):
1242 self._sizeof = ctypes.sizeof(GlibcHeapInfo.heap_info_t())
1243 self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcHeapInfo.heap_info_t()))
1244 self._heap_info = GlibcHeapInfo.heap_info_t().from_buffer_copy(self._data)
1245 return
1247 def __getattr__(self, item: Any) -> Any:
1248 if item in dir(self._heap_info): 1248 ↛ 1250line 1248 didn't jump to line 1250, because the condition on line 1248 was never false
1249 return ctypes.cast(getattr(self._heap_info, item), ctypes.c_void_p).value
1250 return getattr(self, item)
1252 def __abs__(self) -> int:
1253 return self.__address
1255 def __int__(self) -> int:
1256 return self.__address
1258 @property
1259 def address(self) -> int:
1260 return self.__address
1262 @property
1263 def sizeof(self) -> int:
1264 return self._sizeof
1266 @property
1267 def addr(self) -> int:
1268 return int(self)
1270 @property
1271 def heap_start(self) -> int:
1272 # check special case: first heap of non-main-arena
1273 if self.ar_ptr - self.address < 0x60: 1273 ↛ 1285line 1273 didn't jump to line 1285, because the condition on line 1273 was never false
1274 # the first heap of a non-main-arena starts with a `heap_info`
1275 # struct, which should fit easily into 0x60 bytes throughout
1276 # all architectures and glibc versions. If this check succeeds
1277 # then we are currently looking at such a "first heap"
1278 arena = GlibcArena(f"*{self.ar_ptr:#x}")
1279 heap_addr = arena.heap_addr()
1280 if heap_addr: 1280 ↛ 1283line 1280 didn't jump to line 1283, because the condition on line 1280 was never false
1281 return heap_addr
1282 else:
1283 err(f"Cannot find heap address for arena {self.ar_ptr:#x}")
1284 return 0
1285 return self.address + self.sizeof
1287 @property
1288 def heap_end(self) -> int:
1289 return self.address + self.size
1292class GlibcArena:
1293 """Glibc arena class"""
1295 NFASTBINS = 10
1296 NBINS = 128
1297 NSMALLBINS = 64
1298 BINMAPSHIFT = 5
1299 BITSPERMAP = 1 << BINMAPSHIFT
1300 BINMAPSIZE = NBINS // BITSPERMAP
1302 @staticmethod
1303 def malloc_state_t() -> Type[ctypes.Structure]:
1304 pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32
1305 fields = [
1306 ("mutex", ctypes.c_uint32),
1307 ("flags", ctypes.c_uint32),
1308 ]
1309 if gef and gef.libc.version and gef.libc.version >= (2, 27): 1309 ↛ 1315line 1309 didn't jump to line 1315, because the condition on line 1309 was never false
1310 # https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L1684
1311 fields += [
1312 ("have_fastchunks", ctypes.c_uint32),
1313 ("UNUSED_c", ctypes.c_uint32), # padding to align to 0x10
1314 ]
1315 fields += [
1316 ("fastbinsY", GlibcArena.NFASTBINS * pointer),
1317 ("top", pointer),
1318 ("last_remainder", pointer),
1319 ("bins", (GlibcArena.NBINS * 2 - 2) * pointer),
1320 ("binmap", GlibcArena.BINMAPSIZE * ctypes.c_uint32),
1321 ("next", pointer),
1322 ("next_free", pointer)
1323 ]
1324 if gef and gef.libc.version and gef.libc.version >= (2, 23): 1324 ↛ 1329line 1324 didn't jump to line 1329, because the condition on line 1324 was never false
1325 # https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1719
1326 fields += [
1327 ("attached_threads", pointer)
1328 ]
1329 fields += [
1330 ("system_mem", pointer),
1331 ("max_system_mem", pointer),
1332 ]
1333 class malloc_state_cls(ctypes.Structure):
1334 _fields_ = fields
1335 return malloc_state_cls
1337 def __init__(self, addr: str) -> None:
1338 try:
1339 self.__address : int = parse_address(f"&{addr}")
1340 except gdb.error:
1341 self.__address : int = GefHeapManager.find_main_arena_addr()
1342 # if `find_main_arena_addr` throws `gdb.error` on symbol lookup:
1343 # it means the session is not started, so just propagate the exception
1344 self.reset()
1345 return
1347 def reset(self):
1348 self._sizeof = ctypes.sizeof(GlibcArena.malloc_state_t())
1349 self._data = gef.memory.read(self.__address, ctypes.sizeof(GlibcArena.malloc_state_t()))
1350 self.__arena = GlibcArena.malloc_state_t().from_buffer_copy(self._data)
1351 return
1353 def __abs__(self) -> int:
1354 return self.__address
1356 def __int__(self) -> int:
1357 return self.__address
1359 def __iter__(self) -> Generator["GlibcArena", None, None]:
1360 main_arena = int(gef.heap.main_arena)
1362 current_arena = self
1363 yield current_arena
1365 while True:
1366 if current_arena.next == 0 or current_arena.next == main_arena:
1367 break
1369 current_arena = GlibcArena(f"*{current_arena.next:#x} ")
1370 yield current_arena
1371 return
1373 def __eq__(self, other: "GlibcArena") -> bool:
1374 return self.__address == int(other)
1376 def __str__(self) -> str:
1377 properties = f"base={self.__address:#x}, top={self.top:#x}, " \
1378 f"last_remainder={self.last_remainder:#x}, next={self.next:#x}"
1379 return (f"{Color.colorify('Arena', 'blue bold underline')}({properties})")
1381 def __repr__(self) -> str:
1382 return f"GlibcArena(address={self.__address:#x}, size={self._sizeof})"
1384 @property
1385 def address(self) -> int:
1386 return self.__address
1388 @property
1389 def sizeof(self) -> int:
1390 return self._sizeof
1392 @property
1393 def addr(self) -> int:
1394 return int(self)
1396 @property
1397 def top(self) -> int:
1398 return self.__arena.top
1400 @property
1401 def last_remainder(self) -> int:
1402 return self.__arena.last_remainder
1404 @property
1405 def fastbinsY(self) -> ctypes.Array:
1406 return self.__arena.fastbinsY
1408 @property
1409 def bins(self) -> ctypes.Array:
1410 return self.__arena.bins
1412 @property
1413 def binmap(self) -> ctypes.Array:
1414 return self.__arena.binmap
1416 @property
1417 def next(self) -> int:
1418 return self.__arena.next
1420 @property
1421 def next_free(self) -> int:
1422 return self.__arena.next_free
1424 @property
1425 def attached_threads(self) -> int:
1426 return self.__arena.attached_threads
1428 @property
1429 def system_mem(self) -> int:
1430 return self.__arena.system_mem
1432 @property
1433 def max_system_mem(self) -> int:
1434 return self.__arena.max_system_mem
1436 def fastbin(self, i: int) -> Optional["GlibcFastChunk"]:
1437 """Return head chunk in fastbinsY[i]."""
1438 addr = int(self.fastbinsY[i])
1439 if addr == 0:
1440 return None
1441 return GlibcFastChunk(addr + 2 * gef.arch.ptrsize)
1443 def bin(self, i: int) -> Tuple[int, int]:
1444 idx = i * 2
1445 fd = int(self.bins[idx])
1446 bk = int(self.bins[idx + 1])
1447 return fd, bk
1449 def bin_at(self, i) -> int:
1450 header_sz = 2 * gef.arch.ptrsize
1451 offset = ctypes.addressof(self.__arena.bins) - ctypes.addressof(self.__arena)
1452 return self.__address + offset + (i-1) * 2 * gef.arch.ptrsize + header_sz
1454 def is_main_arena(self) -> bool:
1455 return gef.heap.main_arena is not None and int(self) == int(gef.heap.main_arena)
1457 def heap_addr(self, allow_unaligned: bool = False) -> Optional[int]:
1458 if self.is_main_arena():
1459 heap_section = gef.heap.base_address
1460 if not heap_section: 1460 ↛ 1461line 1460 didn't jump to line 1461, because the condition on line 1460 was never true
1461 return None
1462 return heap_section
1463 _addr = int(self) + self.sizeof
1464 if allow_unaligned: 1464 ↛ 1465line 1464 didn't jump to line 1465, because the condition on line 1464 was never true
1465 return _addr
1466 return gef.heap.malloc_align_address(_addr)
1468 def get_heap_info_list(self) -> Optional[List[GlibcHeapInfo]]:
1469 if self.is_main_arena(): 1469 ↛ 1470line 1469 didn't jump to line 1470, because the condition on line 1469 was never true
1470 return None
1471 heap_addr = self.get_heap_for_ptr(self.top)
1472 heap_infos = [GlibcHeapInfo(heap_addr)]
1473 while heap_infos[-1].prev is not None: 1473 ↛ 1474line 1473 didn't jump to line 1474, because the condition on line 1473 was never true
1474 prev = int(heap_infos[-1].prev)
1475 heap_info = GlibcHeapInfo(prev)
1476 heap_infos.append(heap_info)
1477 return heap_infos[::-1]
1479 @staticmethod
1480 def get_heap_for_ptr(ptr: int) -> int:
1481 """Find the corresponding heap for a given pointer (int).
1482 See https://github.com/bminor/glibc/blob/glibc-2.34/malloc/arena.c#L129"""
1483 if is_32bit(): 1483 ↛ 1484line 1483 didn't jump to line 1484, because the condition on line 1483 was never true
1484 default_mmap_threshold_max = 512 * 1024
1485 else: # 64bit
1486 default_mmap_threshold_max = 4 * 1024 * 1024 * cached_lookup_type("long").sizeof
1487 heap_max_size = 2 * default_mmap_threshold_max
1488 return ptr & ~(heap_max_size - 1)
1490 @staticmethod
1491 def verify(addr: int) -> bool:
1492 """Verify that the address matches a possible valid GlibcArena"""
1493 try:
1494 test_arena = GlibcArena(f"*{addr:#x}")
1495 cur_arena = GlibcArena(f"*{test_arena.next:#x}")
1496 while cur_arena != test_arena:
1497 if cur_arena == 0:
1498 return False
1499 cur_arena = GlibcArena(f"*{cur_arena.next:#x}")
1500 except Exception as e:
1501 return False
1502 return True
1505class GlibcChunk:
1506 """Glibc chunk class. The default behavior (from_base=False) is to interpret the data starting at the memory
1507 address pointed to as the chunk data. Setting from_base to True instead treats that data as the chunk header.
1508 Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/."""
1510 class ChunkFlags(enum.IntFlag):
1511 PREV_INUSE = 1
1512 IS_MMAPPED = 2
1513 NON_MAIN_ARENA = 4
1515 def __str__(self) -> str:
1516 return f" | ".join([
1517 Color.greenify("PREV_INUSE") if self.value & self.PREV_INUSE else Color.redify("PREV_INUSE"),
1518 Color.greenify("IS_MMAPPED") if self.value & self.IS_MMAPPED else Color.redify("IS_MMAPPED"),
1519 Color.greenify("NON_MAIN_ARENA") if self.value & self.NON_MAIN_ARENA else Color.redify("NON_MAIN_ARENA")
1520 ])
1522 @staticmethod
1523 def malloc_chunk_t() -> Type[ctypes.Structure]:
1524 pointer = ctypes.c_uint64 if gef and gef.arch.ptrsize == 8 else ctypes.c_uint32
1525 class malloc_chunk_cls(ctypes.Structure):
1526 pass
1528 malloc_chunk_cls._fields_ = [
1529 ("prev_size", pointer),
1530 ("size", pointer),
1531 ("fd", pointer),
1532 ("bk", pointer),
1533 ("fd_nextsize", ctypes.POINTER(malloc_chunk_cls)),
1534 ("bk_nextsize", ctypes.POINTER(malloc_chunk_cls)),
1535 ]
1536 return malloc_chunk_cls
1538 def __init__(self, addr: int, from_base: bool = False, allow_unaligned: bool = True) -> None:
1539 ptrsize = gef.arch.ptrsize
1540 self.data_address = addr + 2 * ptrsize if from_base else addr
1541 self.base_address = addr if from_base else addr - 2 * ptrsize
1542 if not allow_unaligned:
1543 self.data_address = gef.heap.malloc_align_address(self.data_address)
1544 self.size_addr = int(self.data_address - ptrsize)
1545 self.prev_size_addr = self.base_address
1546 self.reset()
1547 return
1549 def reset(self):
1550 self._sizeof = ctypes.sizeof(GlibcChunk.malloc_chunk_t())
1551 self._data = gef.memory.read(
1552 self.base_address, ctypes.sizeof(GlibcChunk.malloc_chunk_t()))
1553 self._chunk = GlibcChunk.malloc_chunk_t().from_buffer_copy(self._data)
1554 return
1556 @property
1557 def prev_size(self) -> int:
1558 return self._chunk.prev_size
1560 @property
1561 def size(self) -> int:
1562 return self._chunk.size & (~0x07)
1564 @property
1565 def flags(self) -> ChunkFlags:
1566 return GlibcChunk.ChunkFlags(self._chunk.size & 0x07)
1568 @property
1569 def fd(self) -> int:
1570 return self._chunk.fd
1572 @property
1573 def bk(self) -> int:
1574 return self._chunk.bk
1576 @property
1577 def fd_nextsize(self) -> int:
1578 return self._chunk.fd_nextsize
1580 @property
1581 def bk_nextsize(self) -> int:
1582 return self._chunk.bk_nextsize
1584 def get_usable_size(self) -> int:
1585 # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537
1586 ptrsz = gef.arch.ptrsize
1587 cursz = self.size
1588 if cursz == 0: return cursz 1588 ↛ exitline 1588 didn't return from function 'get_usable_size', because the return on line 1588 wasn't executed
1589 if self.has_m_bit(): return cursz - 2 * ptrsz 1589 ↛ exitline 1589 didn't return from function 'get_usable_size', because the return on line 1589 wasn't executed
1590 return cursz - ptrsz
1592 @property
1593 def usable_size(self) -> int:
1594 return self.get_usable_size()
1596 def get_prev_chunk_size(self) -> int:
1597 return gef.memory.read_integer(self.prev_size_addr)
1599 def __iter__(self) -> Generator["GlibcChunk", None, None]:
1600 current_chunk = self
1601 top = gef.heap.main_arena.top
1603 while True:
1604 yield current_chunk
1606 if current_chunk.base_address == top: 1606 ↛ 1607line 1606 didn't jump to line 1607, because the condition on line 1606 was never true
1607 break
1609 if current_chunk.size == 0: 1609 ↛ 1610line 1609 didn't jump to line 1610, because the condition on line 1609 was never true
1610 break
1612 next_chunk_addr = current_chunk.get_next_chunk_addr()
1614 if not Address(value=next_chunk_addr).valid: 1614 ↛ 1615line 1614 didn't jump to line 1615, because the condition on line 1614 was never true
1615 break
1617 next_chunk = current_chunk.get_next_chunk()
1618 if next_chunk is None: 1618 ↛ 1619line 1618 didn't jump to line 1619, because the condition on line 1618 was never true
1619 break
1621 current_chunk = next_chunk
1622 return
1624 def get_next_chunk(self, allow_unaligned: bool = False) -> "GlibcChunk":
1625 addr = self.get_next_chunk_addr()
1626 return GlibcChunk(addr, allow_unaligned=allow_unaligned)
1628 def get_next_chunk_addr(self) -> int:
1629 return self.data_address + self.size
1631 def has_p_bit(self) -> bool:
1632 return bool(self.flags & GlibcChunk.ChunkFlags.PREV_INUSE)
1634 def has_m_bit(self) -> bool:
1635 return bool(self.flags & GlibcChunk.ChunkFlags.IS_MMAPPED)
1637 def has_n_bit(self) -> bool:
1638 return bool(self.flags & GlibcChunk.ChunkFlags.NON_MAIN_ARENA)
1640 def is_used(self) -> bool:
1641 """Check if the current block is used by:
1642 - checking the M bit is true
1643 - or checking that next chunk PREV_INUSE flag is true"""
1644 if self.has_m_bit(): 1644 ↛ 1645line 1644 didn't jump to line 1645, because the condition on line 1644 was never true
1645 return True
1647 next_chunk = self.get_next_chunk()
1648 return True if next_chunk.has_p_bit() else False
1650 def __str_sizes(self) -> str:
1651 msg = []
1652 failed = False
1654 try:
1655 msg.append("Chunk size: {0:d} ({0:#x})".format(self.size))
1656 msg.append("Usable size: {0:d} ({0:#x})".format(self.usable_size))
1657 failed = True
1658 except gdb.MemoryError:
1659 msg.append(f"Chunk size: Cannot read at {self.size_addr:#x} (corrupted?)")
1661 try:
1662 msg.append("Previous chunk size: {0:d} ({0:#x})".format(self.get_prev_chunk_size()))
1663 failed = True
1664 except gdb.MemoryError:
1665 msg.append(f"Previous chunk size: Cannot read at {self.base_address:#x} (corrupted?)")
1667 if failed: 1667 ↛ 1670line 1667 didn't jump to line 1670, because the condition on line 1667 was never false
1668 msg.append(str(self.flags))
1670 return "\n".join(msg)
1672 def _str_pointers(self) -> str:
1673 fwd = self.data_address
1674 bkw = self.data_address + gef.arch.ptrsize
1676 msg = []
1677 try:
1678 msg.append(f"Forward pointer: {self.fd:#x}")
1679 except gdb.MemoryError:
1680 msg.append(f"Forward pointer: {fwd:#x} (corrupted?)")
1682 try:
1683 msg.append(f"Backward pointer: {self.bk:#x}")
1684 except gdb.MemoryError:
1685 msg.append(f"Backward pointer: {bkw:#x} (corrupted?)")
1687 return "\n".join(msg)
1689 def __str__(self) -> str:
1690 return (f"{Color.colorify('Chunk', 'yellow bold underline')}(addr={self.data_address:#x}, "
1691 f"size={self.size:#x}, flags={self.flags!s})")
1693 def psprint(self) -> str:
1694 msg = [
1695 str(self),
1696 self.__str_sizes(),
1697 ]
1698 if not self.is_used(): 1698 ↛ 1699line 1698 didn't jump to line 1699, because the condition on line 1698 was never true
1699 msg.append(f"\n\n{self._str_pointers()}")
1700 return "\n".join(msg) + "\n"
1703class GlibcFastChunk(GlibcChunk):
1705 @property
1706 def fd(self) -> int:
1707 assert(gef and gef.libc.version)
1708 if gef.libc.version < (2, 32): 1708 ↛ 1709line 1708 didn't jump to line 1709, because the condition on line 1708 was never true
1709 return self._chunk.fd
1710 return self.reveal_ptr(self.data_address)
1712 def protect_ptr(self, pos: int, pointer: int) -> int:
1713 """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L339"""
1714 assert(gef and gef.libc.version)
1715 if gef.libc.version < (2, 32):
1716 return pointer
1717 return (pos >> 12) ^ pointer
1719 def reveal_ptr(self, pointer: int) -> int:
1720 """https://elixir.bootlin.com/glibc/glibc-2.32/source/malloc/malloc.c#L341"""
1721 assert(gef and gef.libc.version)
1722 if gef.libc.version < (2, 32): 1722 ↛ 1723line 1722 didn't jump to line 1723, because the condition on line 1722 was never true
1723 return pointer
1724 return gef.memory.read_integer(pointer) ^ (pointer >> 12)
1726class GlibcTcacheChunk(GlibcFastChunk):
1728 pass
1730@deprecated("Use GefLibcManager.find_libc_version()")
1731def get_libc_version() -> Tuple[int, ...]:
1732 return GefLibcManager.find_libc_version()
1734def titlify(text: str, color: Optional[str] = None, msg_color: Optional[str] = None) -> str:
1735 """Print a centered title."""
1736 _, cols = get_terminal_size()
1737 nb = (cols - len(text) - 2) // 2
1738 line_color = color or gef.config["theme.default_title_line"]
1739 text_color = msg_color or gef.config["theme.default_title_message"]
1741 msg = [Color.colorify(f"{HORIZONTAL_LINE * nb} ", line_color),
1742 Color.colorify(text, text_color),
1743 Color.colorify(f" {HORIZONTAL_LINE * nb}", line_color)]
1744 return "".join(msg)
1747def dbg(msg: str) -> None:
1748 if gef.config["gef.debug"] is True: 1748 ↛ 1750line 1748 didn't jump to line 1750, because the condition on line 1748 was never false
1749 gef_print(f"{Color.colorify('[=]', 'bold cyan')} {msg}")
1750 return
1753def err(msg: str) -> None:
1754 gef_print(f"{Color.colorify('[!]', 'bold red')} {msg}")
1755 return
1758def warn(msg: str) -> None:
1759 gef_print(f"{Color.colorify('[*]', 'bold yellow')} {msg}")
1760 return
1763def ok(msg: str) -> None:
1764 gef_print(f"{Color.colorify('[+]', 'bold green')} {msg}")
1765 return
1768def info(msg: str) -> None:
1769 gef_print(f"{Color.colorify('[+]', 'bold blue')} {msg}")
1770 return
1773def push_context_message(level: str, message: str) -> None:
1774 """Push the message to be displayed the next time the context is invoked."""
1775 if level not in ("error", "warn", "ok", "info"): 1775 ↛ 1776line 1775 didn't jump to line 1776, because the condition on line 1775 was never true
1776 err(f"Invalid level '{level}', discarding message")
1777 return
1778 gef.ui.context_messages.append((level, message))
1779 return
1782def show_last_exception() -> None:
1783 """Display the last Python exception."""
1785 def _show_code_line(fname: str, idx: int) -> str:
1786 fname = os.path.expanduser(os.path.expandvars(fname))
1787 with open(fname, "r") as f:
1788 _data = f.readlines()
1789 return _data[idx - 1] if 0 < idx < len(_data) else ""
1791 gef_print("")
1792 exc_type, exc_value, exc_traceback = sys.exc_info()
1794 gef_print(" Exception raised ".center(80, HORIZONTAL_LINE))
1795 gef_print(f"{Color.colorify(exc_type.__name__, 'bold underline red')}: {exc_value}")
1796 gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE))
1798 for fs in traceback.extract_tb(exc_traceback)[::-1]:
1799 filename, lineno, method, code = fs
1801 if not code or not code.strip(): 1801 ↛ 1802line 1801 didn't jump to line 1802, because the condition on line 1801 was never true
1802 code = _show_code_line(filename, lineno)
1804 gef_print(f"""{DOWN_ARROW} File "{Color.yellowify(filename)}", line {lineno:d}, in {Color.greenify(method)}()""")
1805 gef_print(f" {RIGHT_ARROW} {code}")
1807 gef_print(" Version ".center(80, HORIZONTAL_LINE))
1808 gdb.execute("version full")
1809 gef_print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE))
1810 gdb.execute("show commands")
1811 gef_print(" Runtime environment ".center(80, HORIZONTAL_LINE))
1812 gef_print(f"* GDB: {gdb.VERSION}")
1813 gef_print(f"* Python: {sys.version_info.major:d}.{sys.version_info.minor:d}.{sys.version_info.micro:d} - {sys.version_info.releaselevel}")
1814 gef_print(f"* OS: {platform.system()} - {platform.release()} ({platform.machine()})")
1816 try:
1817 lsb_release = which("lsb_release")
1818 gdb.execute(f"!'{lsb_release}' -a")
1819 except FileNotFoundError:
1820 gef_print("lsb_release is missing, cannot collect additional debug information")
1822 gef_print(HORIZONTAL_LINE*80)
1823 gef_print("")
1824 return
1827def gef_pystring(x: bytes) -> str:
1828 """Returns a sanitized version as string of the bytes list given in input."""
1829 res = str(x, encoding="utf-8")
1830 substs = [("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t"), ("\v", "\\v"), ("\b", "\\b"), ]
1831 for x, y in substs: res = res.replace(x, y)
1832 return res
1835def gef_pybytes(x: str) -> bytes:
1836 """Returns an immutable bytes list from the string given as input."""
1837 return bytes(str(x), encoding="utf-8")
1840@lru_cache()
1841def which(program: str) -> pathlib.Path:
1842 """Locate a command on the filesystem."""
1843 for path in os.environ["PATH"].split(os.pathsep):
1844 dirname = pathlib.Path(path)
1845 fpath = dirname / program
1846 if os.access(fpath, os.X_OK):
1847 return fpath
1849 raise FileNotFoundError(f"Missing file `{program}`")
1852def style_byte(b: int, color: bool = True) -> str:
1853 style = {
1854 "nonprintable": "yellow",
1855 "printable": "white",
1856 "00": "gray",
1857 "0a": "blue",
1858 "ff": "green",
1859 }
1860 sbyte = f"{b:02x}"
1861 if not color or gef.config["highlight.regex"]:
1862 return sbyte
1864 if sbyte in style:
1865 st = style[sbyte]
1866 elif chr(b) in (string.ascii_letters + string.digits + string.punctuation + " "):
1867 st = style.get("printable")
1868 else:
1869 st = style.get("nonprintable")
1870 if st: 1870 ↛ 1872line 1870 didn't jump to line 1872, because the condition on line 1870 was never false
1871 sbyte = Color.colorify(sbyte, st)
1872 return sbyte
1875def hexdump(source: ByteString, length: int = 0x10, separator: str = ".", show_raw: bool = False, show_symbol: bool = True, base: int = 0x00) -> str:
1876 """Return the hexdump of `src` argument.
1877 @param source *MUST* be of type bytes or bytearray
1878 @param length is the length of items per line
1879 @param separator is the default character to use if one byte is not printable
1880 @param show_raw if True, do not add the line nor the text translation
1881 @param base is the start address of the block being hexdump
1882 @return a string with the hexdump"""
1883 result = []
1884 align = gef.arch.ptrsize * 2 + 2 if is_alive() else 18
1886 for i in range(0, len(source), length):
1887 chunk = bytearray(source[i : i + length])
1888 hexa = " ".join([style_byte(b, color=not show_raw) for b in chunk])
1890 if show_raw:
1891 result.append(hexa)
1892 continue
1894 text = "".join([chr(b) if 0x20 <= b < 0x7F else separator for b in chunk])
1895 if show_symbol: 1895 ↛ 1899line 1895 didn't jump to line 1899, because the condition on line 1895 was never false
1896 sym = gdb_get_location_from_symbol(base + i)
1897 sym = "<{:s}+{:04x}>".format(*sym) if sym else ""
1898 else:
1899 sym = ""
1901 result.append(f"{base + i:#0{align}x} {sym} {hexa:<{3 * length}} {text}")
1902 return "\n".join(result)
1905def is_debug() -> bool:
1906 """Check if debug mode is enabled."""
1907 return gef.config["gef.debug"] is True
1910def buffer_output() -> bool:
1911 """Check if output should be buffered until command completion."""
1912 return gef.config["gef.buffer"] is True
1915def hide_context() -> bool:
1916 """Helper function to hide the context pane."""
1917 gef.ui.context_hidden = True
1918 return True
1921def unhide_context() -> bool:
1922 """Helper function to unhide the context pane."""
1923 gef.ui.context_hidden = False
1924 return True
1927class DisableContextOutputContext:
1928 def __enter__(self) -> None:
1929 hide_context()
1930 return
1932 def __exit__(self, *exc: Any) -> None:
1933 unhide_context()
1934 return
1937class RedirectOutputContext:
1938 def __init__(self, to_file: str = "/dev/null") -> None:
1939 if " " in to_file: raise ValueEror("Target filepath cannot contain spaces") 1939 ↛ exitline 1939 didn't except from function '__init__', because the raise on line 1939 wasn't executed
1940 self.redirection_target_file = to_file
1941 return
1943 def __enter__(self) -> None:
1944 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`."""
1945 gdb.execute("set logging overwrite")
1946 gdb.execute(f"set logging file {self.redirection_target_file}")
1947 gdb.execute("set logging redirect on")
1948 gdb.execute("set logging on")
1949 return
1951 def __exit__(self, *exc: Any) -> None:
1952 """Disable the output redirection, if any."""
1953 gdb.execute("set logging off")
1954 gdb.execute("set logging redirect off")
1955 return
1958def enable_redirect_output(to_file: str = "/dev/null") -> None:
1959 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`."""
1960 if " " in to_file: raise ValueEror("Target filepath cannot contain spaces")
1961 gdb.execute("set logging overwrite")
1962 gdb.execute(f"set logging file {to_file}")
1963 gdb.execute("set logging redirect on")
1964 gdb.execute("set logging on")
1965 return
1968def disable_redirect_output() -> None:
1969 """Disable the output redirection, if any."""
1970 gdb.execute("set logging off")
1971 gdb.execute("set logging redirect off")
1972 return
1975def gef_makedirs(path: str, mode: int = 0o755) -> pathlib.Path:
1976 """Recursive mkdir() creation. If successful, return the absolute path of the directory created."""
1977 fpath = pathlib.Path(path)
1978 if not fpath.is_dir():
1979 fpath.mkdir(mode=mode, exist_ok=True, parents=True)
1980 return fpath.absolute()
1983@lru_cache()
1984def gdb_lookup_symbol(sym: str) -> Optional[Tuple[Optional[str], Optional[Tuple[gdb.Symtab_and_line, ...]]]]:
1985 """Fetch the proper symbol or None if not defined."""
1986 try:
1987 return gdb.decode_line(sym)[1]
1988 except gdb.error:
1989 return None
1992@lru_cache(maxsize=512)
1993def gdb_get_location_from_symbol(address: int) -> Optional[Tuple[str, int]]:
1994 """Retrieve the location of the `address` argument from the symbol table.
1995 Return a tuple with the name and offset if found, None otherwise."""
1996 # this is horrible, ugly hack and shitty perf...
1997 # find a *clean* way to get gdb.Location from an address
1998 sym = str(gdb.execute(f"info symbol {address:#x}", to_string=True))
1999 if sym.startswith("No symbol matches"):
2000 return None
2002 i = sym.find(" in section ")
2003 sym = sym[:i].split()
2004 name, offset = sym[0], 0
2005 if len(sym) == 3 and sym[2].isdigit():
2006 offset = int(sym[2])
2007 return name, offset
2010def gdb_disassemble(start_pc: int, **kwargs: int) -> Generator[Instruction, None, None]:
2011 """Disassemble instructions from `start_pc` (Integer). Accepts the following named
2012 parameters:
2013 - `end_pc` (Integer) only instructions whose start address fall in the interval from
2014 start_pc to end_pc are returned.
2015 - `count` (Integer) list at most this many disassembled instructions
2016 If `end_pc` and `count` are not provided, the function will behave as if `count=1`.
2017 Return an iterator of Instruction objects
2018 """
2019 frame = gdb.selected_frame()
2020 arch = frame.architecture()
2022 for insn in arch.disassemble(start_pc, **kwargs):
2023 assert isinstance(insn["addr"], int)
2024 assert isinstance(insn["length"], int)
2025 assert isinstance(insn["asm"], str)
2026 address = insn["addr"]
2027 asm = insn["asm"].rstrip().split(None, 1)
2028 if len(asm) > 1:
2029 mnemo, operands = asm
2030 operands = operands.split(",")
2031 else:
2032 mnemo, operands = asm[0], []
2034 loc = gdb_get_location_from_symbol(address)
2035 location = "<{}+{}>".format(*loc) if loc else ""
2037 opcodes = gef.memory.read(insn["addr"], insn["length"])
2039 yield Instruction(address, location, mnemo, operands, opcodes)
2042def gdb_get_nth_previous_instruction_address(addr: int, n: int) -> Optional[int]:
2043 """Return the address (Integer) of the `n`-th instruction before `addr`."""
2044 # fixed-length ABI
2045 if gef.arch.instruction_length: 2045 ↛ 2046line 2045 didn't jump to line 2046, because the condition on line 2045 was never true
2046 return max(0, addr - n * gef.arch.instruction_length)
2048 # variable-length ABI
2049 cur_insn_addr = gef_current_instruction(addr).address
2051 # we try to find a good set of previous instructions by "guessing" disassembling backwards
2052 # the 15 comes from the longest instruction valid size
2053 for i in range(15 * n, 0, -1): 2053 ↛ 2076line 2053 didn't jump to line 2076, because the loop on line 2053 didn't complete
2054 try:
2055 insns = list(gdb_disassemble(addr - i, end_pc=cur_insn_addr))
2056 except gdb.MemoryError:
2057 # this is because we can hit an unmapped page trying to read backward
2058 break
2060 # 1. check that the disassembled instructions list size can satisfy
2061 if len(insns) < n + 1: # we expect the current instruction plus the n before it 2061 ↛ 2062line 2061 didn't jump to line 2062, because the condition on line 2061 was never true
2062 continue
2064 # If the list of instructions is longer than what we need, then we
2065 # could get lucky and already have more than what we need, so slice down
2066 insns = insns[-n - 1 :]
2068 # 2. check that the sequence ends with the current address
2069 if insns[-1].address != cur_insn_addr: 2069 ↛ 2070line 2069 didn't jump to line 2070, because the condition on line 2069 was never true
2070 continue
2072 # 3. check all instructions are valid
2073 if all(insn.is_valid() for insn in insns): 2073 ↛ 2053line 2073 didn't jump to line 2053, because the condition on line 2073 was never false
2074 return insns[0].address
2076 return None
2079@deprecated(solution="Use `gef_instruction_n().address`")
2080def gdb_get_nth_next_instruction_address(addr: int, n: int) -> int:
2081 """Return the address of the `n`-th instruction after `addr`. """
2082 return gef_instruction_n(addr, n).address
2085def gef_instruction_n(addr: int, n: int) -> Instruction:
2086 """Return the `n`-th instruction after `addr` as an Instruction object. Note that `n` is treated as
2087 an positive index, starting from 0 (current instruction address)"""
2088 return list(gdb_disassemble(addr, count=n + 1))[n]
2091def gef_get_instruction_at(addr: int) -> Instruction:
2092 """Return the full Instruction found at the specified address."""
2093 insn = next(gef_disassemble(addr, 1))
2094 return insn
2097def gef_current_instruction(addr: int) -> Instruction:
2098 """Return the current instruction as an Instruction object."""
2099 return gef_instruction_n(addr, 0)
2102def gef_next_instruction(addr: int) -> Instruction:
2103 """Return the next instruction as an Instruction object."""
2104 return gef_instruction_n(addr, 1)
2107def gef_disassemble(addr: int, nb_insn: int, nb_prev: int = 0) -> Generator[Instruction, None, None]:
2108 """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr`.
2109 Return an iterator of Instruction objects."""
2110 nb_insn = max(1, nb_insn)
2112 if nb_prev:
2113 try:
2114 start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev)
2115 if start_addr:
2116 for insn in gdb_disassemble(start_addr, count=nb_prev):
2117 if insn.address == addr: break 2117 ↛ 2123line 2117 didn't jump to line 2123, because the break on line 2117 wasn't executed
2118 yield insn
2119 except gdb.MemoryError:
2120 # If the address pointing to the previous instruction(s) is not mapped, simply skip them
2121 pass
2123 for insn in gdb_disassemble(addr, count=nb_insn):
2124 yield insn
2127def gef_execute_external(command: Sequence[str], as_list: bool = False, **kwargs: Any) -> Union[str, List[str]]:
2128 """Execute an external command and return the result."""
2129 res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False))
2130 return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res)
2133def gef_execute_gdb_script(commands: str) -> None:
2134 """Execute the parameter `source` as GDB command. This is done by writing `commands` to
2135 a temporary file, which is then executed via GDB `source` command. The tempfile is then deleted."""
2136 fd, fname = tempfile.mkstemp(suffix=".gdb", prefix="gef_")
2137 with os.fdopen(fd, "w") as f:
2138 f.write(commands)
2139 f.flush()
2141 fname = pathlib.Path(fname)
2142 if fname.is_file() and os.access(fname, os.R_OK):
2143 gdb.execute(f"source {fname}")
2144 fname.unlink()
2145 return
2148@deprecated("Use Elf(fname).checksec()")
2149def checksec(filename: str) -> Dict[str, bool]:
2150 return Elf(filename).checksec
2153@lru_cache()
2154def get_arch() -> str:
2155 """Return the binary's architecture."""
2156 if is_alive():
2157 arch = gdb.selected_frame().architecture()
2158 return arch.name()
2160 arch_str = gdb.execute("show architecture", to_string=True).strip()
2161 pat = "The target architecture is set automatically (currently "
2162 if arch_str.startswith(pat): 2162 ↛ 2163line 2162 didn't jump to line 2163, because the condition on line 2162 was never true
2163 arch_str = arch_str[len(pat):].rstrip(")")
2164 return arch_str
2166 pat = "The target architecture is assumed to be "
2167 if arch_str.startswith(pat): 2167 ↛ 2168line 2167 didn't jump to line 2168, because the condition on line 2167 was never true
2168 return arch_str[len(pat):]
2170 pat = "The target architecture is set to "
2171 if arch_str.startswith(pat): 2171 ↛ 2178line 2171 didn't jump to line 2178, because the condition on line 2171 was never false
2172 # GDB version >= 10.1
2173 if '"auto"' in arch_str: 2173 ↛ 2175line 2173 didn't jump to line 2175, because the condition on line 2173 was never false
2174 return re.findall(r"currently \"(.+)\"", arch_str)[0]
2175 return re.findall(r"\"(.+)\"", arch_str)[0]
2177 # Unknown, we throw an exception to be safe
2178 raise RuntimeError(f"Unknown architecture: {arch_str}")
2181@deprecated("Use `gef.binary.entry_point` instead")
2182def get_entry_point() -> Optional[int]:
2183 """Return the binary entry point."""
2184 return gef.binary.entry_point if gef.binary else None
2187def is_pie(fpath: str) -> bool:
2188 return Elf(fpath).checksec["PIE"]
2191@deprecated("Prefer `gef.arch.endianness == Endianness.BIG_ENDIAN`")
2192def is_big_endian() -> bool:
2193 return gef.arch.endianness == Endianness.BIG_ENDIAN
2196@deprecated("gef.arch.endianness == Endianness.LITTLE_ENDIAN")
2197def is_little_endian() -> bool:
2198 return gef.arch.endianness == Endianness.LITTLE_ENDIAN
2201def flags_to_human(reg_value: int, value_table: Dict[int, str]) -> str:
2202 """Return a human readable string showing the flag states."""
2203 flags = []
2204 for bit_index, name in value_table.items():
2205 flags.append(Color.boldify(name.upper()) if reg_value & (1<<bit_index) != 0 else name.lower())
2206 return f"[{' '.join(flags)}]"
2209@lru_cache()
2210def get_section_base_address(name: str) -> Optional[int]:
2211 section = process_lookup_path(name)
2212 return section.page_start if section else None
2215@lru_cache()
2216def get_zone_base_address(name: str) -> Optional[int]:
2217 zone = file_lookup_name_path(name, get_filepath())
2218 return zone.zone_start if zone else None
2221#
2222# Architecture classes
2223#
2224@deprecated("Using the decorator `register_architecture` is unecessary")
2225def register_architecture(cls: Type["Architecture"]) -> Type["Architecture"]:
2226 return cls
2228class ArchitectureBase:
2229 """Class decorator for declaring an architecture to GEF."""
2230 aliases: Union[Tuple[()], Tuple[Union[str, Elf.Abi], ...]] = ()
2232 def __init_subclass__(cls: Type["ArchitectureBase"], **kwargs):
2233 global __registered_architectures__
2234 super().__init_subclass__(**kwargs)
2235 for key in getattr(cls, "aliases"):
2236 if issubclass(cls, Architecture): 2236 ↛ 2235line 2236 didn't jump to line 2235, because the condition on line 2236 was never false
2237 __registered_architectures__[key] = cls
2238 return
2241class Architecture(ArchitectureBase):
2242 """Generic metaclass for the architecture supported by GEF."""
2244 # Mandatory defined attributes by inheriting classes
2245 arch: str
2246 mode: str
2247 all_registers: Union[Tuple[()], Tuple[str, ...]]
2248 nop_insn: bytes
2249 return_register: str
2250 flag_register: Optional[str]
2251 instruction_length: Optional[int]
2252 flags_table: Dict[int, str]
2253 syscall_register: Optional[str]
2254 syscall_instructions: Union[Tuple[()], Tuple[str, ...]]
2255 function_parameters: Union[Tuple[()], Tuple[str, ...]]
2257 # Optionally defined attributes
2258 _ptrsize: Optional[int] = None
2259 _endianness: Optional[Endianness] = None
2260 special_registers: Union[Tuple[()], Tuple[str, ...]] = ()
2262 def __init_subclass__(cls, **kwargs):
2263 super().__init_subclass__(**kwargs)
2264 attributes = ("arch", "mode", "aliases", "all_registers", "nop_insn",
2265 "return_register", "flag_register", "instruction_length", "flags_table",
2266 "function_parameters",)
2267 if not all(map(lambda x: hasattr(cls, x), attributes)): 2267 ↛ 2268line 2267 didn't jump to line 2268, because the condition on line 2267 was never true
2268 raise NotImplementedError
2270 def __str__(self) -> str:
2271 return f"Architecture({self.arch}, {self.mode or 'None'}, {repr(self.endianness)})"
2273 @staticmethod
2274 def supports_gdb_arch(gdb_arch: str) -> Optional[bool]:
2275 """If implemented by a child `Architecture`, this function dictates if the current class
2276 supports the loaded ELF file (which can be accessed via `gef.binary`). This callback
2277 function will override any assumption made by GEF to determine the architecture."""
2278 return None
2280 def flag_register_to_human(self, val: Optional[int] = None) -> str:
2281 raise NotImplementedError
2283 def is_call(self, insn: Instruction) -> bool:
2284 raise NotImplementedError
2286 def is_ret(self, insn: Instruction) -> bool:
2287 raise NotImplementedError
2289 def is_conditional_branch(self, insn: Instruction) -> bool:
2290 raise NotImplementedError
2292 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
2293 raise NotImplementedError
2295 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]:
2296 raise NotImplementedError
2298 def canary_address(self) -> int:
2299 raise NotImplementedError
2301 @classmethod
2302 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2303 raise NotImplementedError
2305 def reset_caches(self) -> None:
2306 self.__get_register_for_selected_frame.cache_clear()
2307 return
2309 def __get_register(self, regname: str) -> int:
2310 """Return a register's value."""
2311 curframe = gdb.selected_frame()
2312 key = curframe.pc() ^ int(curframe.read_register('sp')) # todo: check when/if gdb.Frame implements `level()`
2313 return self.__get_register_for_selected_frame(regname, key)
2315 @lru_cache()
2316 def __get_register_for_selected_frame(self, regname: str, hash_key: int) -> int:
2317 # 1st chance
2318 try:
2319 return parse_address(regname)
2320 except gdb.error:
2321 pass
2323 # 2nd chance - if an exception, propagate it
2324 regname = regname.lstrip("$")
2325 value = gdb.selected_frame().read_register(regname)
2326 return int(value)
2328 def register(self, name: str) -> int:
2329 if not is_alive():
2330 raise gdb.error("No debugging session active")
2331 return self.__get_register(name)
2333 @property
2334 def registers(self) -> Generator[str, None, None]:
2335 yield from self.all_registers
2337 @property
2338 def pc(self) -> int:
2339 return self.register("$pc")
2341 @property
2342 def sp(self) -> int:
2343 return self.register("$sp")
2345 @property
2346 def fp(self) -> int:
2347 return self.register("$fp")
2349 @property
2350 def ptrsize(self) -> int:
2351 if not self._ptrsize: 2351 ↛ 2352line 2351 didn't jump to line 2352, because the condition on line 2351 was never true
2352 res = cached_lookup_type("size_t")
2353 if res is not None:
2354 self._ptrsize = res.sizeof
2355 else:
2356 self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof
2357 return self._ptrsize
2359 @property
2360 def endianness(self) -> Endianness:
2361 if not self._endianness:
2362 output = gdb.execute("show endian", to_string=True).strip().lower()
2363 if "little endian" in output: 2363 ↛ 2365line 2363 didn't jump to line 2365, because the condition on line 2363 was never false
2364 self._endianness = Endianness.LITTLE_ENDIAN
2365 elif "big endian" in output:
2366 self._endianness = Endianness.BIG_ENDIAN
2367 else:
2368 raise OSError(f"No valid endianess found in '{output}'")
2369 return self._endianness
2371 def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]:
2372 """Retrieves the correct parameter used for the current function call."""
2373 reg = self.function_parameters[i]
2374 val = self.register(reg)
2375 key = reg
2376 return key, val
2379class GenericArchitecture(Architecture):
2380 arch = "Generic"
2381 mode = ""
2382 aliases = ("GenericArchitecture",)
2383 all_registers = ()
2384 instruction_length = 0
2385 return_register = ""
2386 function_parameters = ()
2387 syscall_register = ""
2388 syscall_instructions = ()
2389 nop_insn = b""
2390 flag_register = None
2391 flags_table = {}
2394class RISCV(Architecture):
2395 arch = "RISCV"
2396 mode = "RISCV"
2397 aliases = ("RISCV", Elf.Abi.RISCV)
2398 all_registers = ("$zero", "$ra", "$sp", "$gp", "$tp", "$t0", "$t1",
2399 "$t2", "$fp", "$s1", "$a0", "$a1", "$a2", "$a3",
2400 "$a4", "$a5", "$a6", "$a7", "$s2", "$s3", "$s4",
2401 "$s5", "$s6", "$s7", "$s8", "$s9", "$s10", "$s11",
2402 "$t3", "$t4", "$t5", "$t6",)
2403 return_register = "$a0"
2404 function_parameters = ("$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7")
2405 syscall_register = "$a7"
2406 syscall_instructions = ("ecall",)
2407 nop_insn = b"\x00\x00\x00\x13"
2408 # RISC-V has no flags registers
2409 flag_register = None
2410 flags_table = {}
2412 @property
2413 def instruction_length(self) -> int:
2414 return 4
2416 def is_call(self, insn: Instruction) -> bool:
2417 return insn.mnemonic == "call"
2419 def is_ret(self, insn: Instruction) -> bool:
2420 mnemo = insn.mnemonic
2421 if mnemo == "ret":
2422 return True
2423 elif (mnemo == "jalr" and insn.operands[0] == "zero" and
2424 insn.operands[1] == "ra" and insn.operands[2] == 0):
2425 return True
2426 elif (mnemo == "c.jalr" and insn.operands[0] == "ra"):
2427 return True
2428 return False
2430 @classmethod
2431 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2432 raise OSError(f"Architecture {cls.arch} not supported yet")
2434 @property
2435 def ptrsize(self) -> int:
2436 if self._ptrsize is not None:
2437 return self._ptrsize
2438 if is_alive():
2439 self._ptrsize = gdb.parse_and_eval("$pc").type.sizeof
2440 return self._ptrsize
2441 return 4
2443 def is_conditional_branch(self, insn: Instruction) -> bool:
2444 return insn.mnemonic.startswith("b")
2446 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
2447 def long_to_twos_complement(v: int) -> int:
2448 """Convert a python long value to its two's complement."""
2449 if is_32bit():
2450 if v & 0x80000000:
2451 return v - 0x100000000
2452 elif is_64bit():
2453 if v & 0x8000000000000000:
2454 return v - 0x10000000000000000
2455 else:
2456 raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported")
2457 return v
2459 mnemo = insn.mnemonic
2460 condition = mnemo[1:]
2462 if condition.endswith("z"):
2463 # r2 is the zero register if we are comparing to 0
2464 rs1 = gef.arch.register(insn.operands[0])
2465 rs2 = gef.arch.register("$zero")
2466 condition = condition[:-1]
2467 elif len(insn.operands) > 2:
2468 # r2 is populated with the second operand
2469 rs1 = gef.arch.register(insn.operands[0])
2470 rs2 = gef.arch.register(insn.operands[1])
2471 else:
2472 raise OSError(f"RISC-V: Failed to get rs1 and rs2 for instruction: `{insn}`")
2474 # If the conditional operation is not unsigned, convert the python long into
2475 # its two's complement
2476 if not condition.endswith("u"):
2477 rs2 = long_to_twos_complement(rs2)
2478 rs1 = long_to_twos_complement(rs1)
2479 else:
2480 condition = condition[:-1]
2482 if condition == "eq":
2483 if rs1 == rs2: taken, reason = True, f"{rs1}={rs2}"
2484 else: taken, reason = False, f"{rs1}!={rs2}"
2485 elif condition == "ne":
2486 if rs1 != rs2: taken, reason = True, f"{rs1}!={rs2}"
2487 else: taken, reason = False, f"{rs1}={rs2}"
2488 elif condition == "lt":
2489 if rs1 < rs2: taken, reason = True, f"{rs1}<{rs2}"
2490 else: taken, reason = False, f"{rs1}>={rs2}"
2491 elif condition == "le":
2492 if rs1 <= rs2: taken, reason = True, f"{rs1}<={rs2}"
2493 else: taken, reason = False, f"{rs1}>{rs2}"
2494 elif condition == "ge":
2495 if rs1 < rs2: taken, reason = True, f"{rs1}>={rs2}"
2496 else: taken, reason = False, f"{rs1}<{rs2}"
2497 else:
2498 raise OSError(f"RISC-V: Conditional instruction `{insn}` not supported yet")
2500 return taken, reason
2502 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]:
2503 ra = None
2504 if self.is_ret(insn):
2505 ra = gef.arch.register("$ra")
2506 elif frame.older():
2507 ra = frame.older().pc()
2508 return ra
2511class ARM(Architecture):
2512 aliases = ("ARM", Elf.Abi.ARM)
2513 arch = "ARM"
2514 all_registers = ("$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6",
2515 "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp",
2516 "$lr", "$pc", "$cpsr",)
2518 nop_insn = b"\x00\xf0\x20\xe3" # hint #0
2519 return_register = "$r0"
2520 flag_register: str = "$cpsr"
2521 flags_table = {
2522 31: "negative",
2523 30: "zero",
2524 29: "carry",
2525 28: "overflow",
2526 7: "interrupt",
2527 6: "fast",
2528 5: "thumb",
2529 }
2530 function_parameters = ("$r0", "$r1", "$r2", "$r3")
2531 syscall_register = "$r7"
2532 syscall_instructions = ("swi 0x0", "swi NR")
2533 _endianness = Endianness.LITTLE_ENDIAN
2535 def is_thumb(self) -> bool:
2536 """Determine if the machine is currently in THUMB mode."""
2537 return is_alive() and (self.cpsr & (1 << 5) == 1)
2539 @property
2540 def pc(self) -> Optional[int]:
2541 pc = gef.arch.register("$pc")
2542 if self.is_thumb():
2543 pc += 1
2544 return pc
2546 @property
2547 def cpsr(self) -> int:
2548 if not is_alive():
2549 raise RuntimeError("Cannot get CPSR, program not started?")
2550 return gef.arch.register(self.flag_register)
2552 @property
2553 def mode(self) -> str:
2554 return "THUMB" if self.is_thumb() else "ARM"
2556 @property
2557 def instruction_length(self) -> Optional[int]:
2558 # Thumb instructions have variable-length (2 or 4-byte)
2559 return None if self.is_thumb() else 4
2561 @property
2562 def ptrsize(self) -> int:
2563 return 4
2565 def is_call(self, insn: Instruction) -> bool:
2566 mnemo = insn.mnemonic
2567 call_mnemos = {"bl", "blx"}
2568 return mnemo in call_mnemos
2570 def is_ret(self, insn: Instruction) -> bool:
2571 pop_mnemos = {"pop"}
2572 branch_mnemos = {"bl", "bx"}
2573 write_mnemos = {"ldr", "add"}
2574 if insn.mnemonic in pop_mnemos:
2575 return insn.operands[-1] == " pc}"
2576 if insn.mnemonic in branch_mnemos:
2577 return insn.operands[-1] == "lr"
2578 if insn.mnemonic in write_mnemos:
2579 return insn.operands[0] == "pc"
2580 return False
2582 def flag_register_to_human(self, val: Optional[int] = None) -> str:
2583 # https://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1
2584 if val is None:
2585 reg = self.flag_register
2586 val = gef.arch.register(reg)
2587 return flags_to_human(val, self.flags_table)
2589 def is_conditional_branch(self, insn: Instruction) -> bool:
2590 conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls", "cc", "cs"}
2591 return insn.mnemonic[-2:] in conditions
2593 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
2594 mnemo = insn.mnemonic
2595 # ref: https://www.davespace.co.uk/arm/introduction-to-arm/conditional.html
2596 flags = dict((self.flags_table[k], k) for k in self.flags_table)
2597 val = gef.arch.register(self.flag_register)
2598 taken, reason = False, ""
2600 if mnemo.endswith("eq"): taken, reason = bool(val&(1<<flags["zero"])), "Z"
2601 elif mnemo.endswith("ne"): taken, reason = not bool(val&(1<<flags["zero"])), "!Z"
2602 elif mnemo.endswith("lt"):
2603 taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V"
2604 elif mnemo.endswith("le"):
2605 taken, reason = bool(val&(1<<flags["zero"])) or \
2606 bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V"
2607 elif mnemo.endswith("gt"):
2608 taken, reason = bool(val&(1<<flags["zero"])) == 0 and \
2609 bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V"
2610 elif mnemo.endswith("ge"):
2611 taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V"
2612 elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V"
2613 elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V"
2614 elif mnemo.endswith("mi"):
2615 taken, reason = bool(val&(1<<flags["negative"])), "N"
2616 elif mnemo.endswith("pl"):
2617 taken, reason = not val&(1<<flags["negative"]), "N==0"
2618 elif mnemo.endswith("hi"):
2619 taken, reason = bool(val&(1<<flags["carry"])) and not bool(val&(1<<flags["zero"])), "C && !Z"
2620 elif mnemo.endswith("ls"):
2621 taken, reason = not val&(1<<flags["carry"]) or bool(val&(1<<flags["zero"])), "!C || Z"
2622 elif mnemo.endswith("cs"): taken, reason = bool(val&(1<<flags["carry"])), "C"
2623 elif mnemo.endswith("cc"): taken, reason = not val&(1<<flags["carry"]), "!C"
2624 return taken, reason
2626 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> int:
2627 ra = None
2628 if self.is_ret(insn):
2629 # If it's a pop, we have to peek into the stack, otherwise use lr
2630 if insn.mnemonic == "pop":
2631 ra_addr = gef.arch.sp + (len(insn.operands)-1) * self.ptrsize
2632 ra = to_unsigned_long(dereference(ra_addr))
2633 elif insn.mnemonic == "ldr":
2634 return to_unsigned_long(dereference(gef.arch.sp))
2635 else: # 'bx lr' or 'add pc, lr, #0'
2636 return gef.arch.register("$lr")
2637 elif frame.older():
2638 ra = frame.older().pc()
2639 return ra
2641 @classmethod
2642 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2643 _NR_mprotect = 125
2644 insns = [
2645 "push {r0-r2, r7}",
2646 f"mov r1, {addr & 0xffff:d}",
2647 f"mov r0, {(addr & 0xffff0000) >> 16:d}",
2648 "lsl r0, r0, 16",
2649 "add r0, r0, r1",
2650 f"mov r1, {size & 0xffff:d}",
2651 f"mov r2, {perm.value & 0xff:d}",
2652 f"mov r7, {_NR_mprotect:d}",
2653 "svc 0",
2654 "pop {r0-r2, r7}",
2655 ]
2656 return "; ".join(insns)
2659class AARCH64(ARM):
2660 aliases = ("ARM64", "AARCH64", Elf.Abi.AARCH64)
2661 arch = "ARM64"
2662 mode: str = ""
2664 all_registers = (
2665 "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",
2666 "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14","$x15",
2667 "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23",
2668 "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp",
2669 "$pc", "$cpsr", "$fpsr", "$fpcr",)
2670 return_register = "$x0"
2671 flag_register = "$cpsr"
2672 flags_table = {
2673 31: "negative",
2674 30: "zero",
2675 29: "carry",
2676 28: "overflow",
2677 7: "interrupt",
2678 9: "endian",
2679 6: "fast",
2680 5: "t32",
2681 4: "m[4]",
2682 }
2683 nop_insn = b"\x1f\x20\x03\xd5" # hint #0
2684 function_parameters = ("$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",)
2685 syscall_register = "$x8"
2686 syscall_instructions = ("svc $x0",)
2688 def is_call(self, insn: Instruction) -> bool:
2689 mnemo = insn.mnemonic
2690 call_mnemos = {"bl", "blr"}
2691 return mnemo in call_mnemos
2693 def flag_register_to_human(self, val: Optional[int] = None) -> str:
2694 # https://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf
2695 reg = self.flag_register
2696 if not val:
2697 val = gef.arch.register(reg)
2698 return flags_to_human(val, self.flags_table)
2700 def is_aarch32(self) -> bool:
2701 """Determine if the CPU is currently in AARCH32 mode from runtime."""
2702 return (self.cpsr & (1 << 4) != 0) and (self.cpsr & (1 << 5) == 0)
2704 def is_thumb32(self) -> bool:
2705 """Determine if the CPU is currently in THUMB32 mode from runtime."""
2706 return (self.cpsr & (1 << 4) == 1) and (self.cpsr & (1 << 5) == 1)
2708 @property
2709 def ptrsize(self) -> int:
2710 """Determine the size of pointer from the current CPU mode"""
2711 if not is_alive():
2712 return 8
2713 if self.is_aarch32():
2714 return 4
2715 if self.is_thumb32():
2716 return 2
2717 return 8
2719 @classmethod
2720 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2721 _NR_mprotect = 226
2722 insns = [
2723 "str x8, [sp, -16]!",
2724 "str x0, [sp, -16]!",
2725 "str x1, [sp, -16]!",
2726 "str x2, [sp, -16]!",
2727 f"mov x8, {_NR_mprotect:d}",
2728 f"movz x0, {addr & 0xFFFF:#x}",
2729 f"movk x0, {(addr >> 16) & 0xFFFF:#x}, lsl 16",
2730 f"movk x0, {(addr >> 32) & 0xFFFF:#x}, lsl 32",
2731 f"movk x0, {(addr >> 48) & 0xFFFF:#x}, lsl 48",
2732 f"movz x1, {size & 0xFFFF:#x}",
2733 f"movk x1, {(size >> 16) & 0xFFFF:#x}, lsl 16",
2734 f"mov x2, {perm.value:d}",
2735 "svc 0",
2736 "ldr x2, [sp], 16",
2737 "ldr x1, [sp], 16",
2738 "ldr x0, [sp], 16",
2739 "ldr x8, [sp], 16",
2740 ]
2741 return "; ".join(insns)
2743 def is_conditional_branch(self, insn: Instruction) -> bool:
2744 # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf
2745 # sect. 5.1.1
2746 mnemo = insn.mnemonic
2747 branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"}
2748 return mnemo.startswith("b.") or mnemo in branch_mnemos
2750 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
2751 mnemo, operands = insn.mnemonic, insn.operands
2752 taken, reason = False, ""
2754 if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}:
2755 reg = f"${operands[0]}"
2756 op = gef.arch.register(reg)
2757 if mnemo == "cbnz":
2758 if op!=0: taken, reason = True, f"{reg}!=0"
2759 else: taken, reason = False, f"{reg}==0"
2760 elif mnemo == "cbz":
2761 if op == 0: taken, reason = True, f"{reg}==0"
2762 else: taken, reason = False, f"{reg}!=0"
2763 elif mnemo == "tbnz":
2764 # operands[1] has one or more white spaces in front, then a #, then the number
2765 # so we need to eliminate them
2766 i = int(operands[1].strip().lstrip("#"))
2767 if (op & 1<<i) != 0: taken, reason = True, f"{reg}&1<<{i}!=0"
2768 else: taken, reason = False, f"{reg}&1<<{i}==0"
2769 elif mnemo == "tbz":
2770 # operands[1] has one or more white spaces in front, then a #, then the number
2771 # so we need to eliminate them
2772 i = int(operands[1].strip().lstrip("#"))
2773 if (op & 1<<i) == 0: taken, reason = True, f"{reg}&1<<{i}==0"
2774 else: taken, reason = False, f"{reg}&1<<{i}!=0"
2776 if not reason:
2777 taken, reason = super().is_branch_taken(insn)
2778 return taken, reason
2781class X86(Architecture):
2782 aliases: Tuple[Union[str, Elf.Abi], ...] = ("X86", Elf.Abi.X86_32)
2783 arch = "X86"
2784 mode = "32"
2786 nop_insn = b"\x90"
2787 flag_register: str = "$eflags"
2788 special_registers = ("$cs", "$ss", "$ds", "$es", "$fs", "$gs", )
2789 gpr_registers = ("$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", )
2790 all_registers = gpr_registers + ( flag_register,) + special_registers
2791 instruction_length = None
2792 return_register = "$eax"
2793 function_parameters = ("$esp", )
2794 flags_table = {
2795 6: "zero",
2796 0: "carry",
2797 2: "parity",
2798 4: "adjust",
2799 7: "sign",
2800 8: "trap",
2801 9: "interrupt",
2802 10: "direction",
2803 11: "overflow",
2804 16: "resume",
2805 17: "virtualx86",
2806 21: "identification",
2807 }
2808 syscall_register = "$eax"
2809 syscall_instructions = ("sysenter", "int 0x80")
2810 _ptrsize = 4
2811 _endianness = Endianness.LITTLE_ENDIAN
2813 def flag_register_to_human(self, val: Optional[int] = None) -> str:
2814 reg = self.flag_register
2815 if not val: 2815 ↛ 2817line 2815 didn't jump to line 2817, because the condition on line 2815 was never false
2816 val = gef.arch.register(reg)
2817 return flags_to_human(val, self.flags_table)
2819 def is_call(self, insn: Instruction) -> bool:
2820 mnemo = insn.mnemonic
2821 call_mnemos = {"call", "callq"}
2822 return mnemo in call_mnemos
2824 def is_ret(self, insn: Instruction) -> bool:
2825 return insn.mnemonic == "ret"
2827 def is_conditional_branch(self, insn: Instruction) -> bool:
2828 mnemo = insn.mnemonic
2829 branch_mnemos = {
2830 "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna",
2831 "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl",
2832 "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns",
2833 "jo", "jp", "jpe", "js"
2834 }
2835 return mnemo in branch_mnemos
2837 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
2838 mnemo = insn.mnemonic
2839 # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654)
2840 flags = dict((self.flags_table[k], k) for k in self.flags_table)
2841 val = gef.arch.register(self.flag_register)
2843 taken, reason = False, ""
2845 if mnemo in ("ja", "jnbe"):
2846 taken, reason = not val&(1<<flags["carry"]) and not bool(val&(1<<flags["zero"])), "!C && !Z"
2847 elif mnemo in ("jae", "jnb", "jnc"):
2848 taken, reason = not val&(1<<flags["carry"]), "!C"
2849 elif mnemo in ("jb", "jc", "jnae"):
2850 taken, reason = bool(val&(1<<flags["carry"])) != 0, "C"
2851 elif mnemo in ("jbe", "jna"):
2852 taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z"
2853 elif mnemo in ("jcxz", "jecxz", "jrcxz"):
2854 cx = gef.arch.register("$rcx") if is_x86_64() else gef.arch.register("$ecx")
2855 taken, reason = cx == 0, "!$CX"
2856 elif mnemo in ("je", "jz"):
2857 taken, reason = bool(val&(1<<flags["zero"])), "Z"
2858 elif mnemo in ("jne", "jnz"):
2859 taken, reason = not bool(val&(1<<flags["zero"])), "!Z"
2860 elif mnemo in ("jg", "jnle"):
2861 taken, reason = not bool(val&(1<<flags["zero"])) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O"
2862 elif mnemo in ("jge", "jnl"):
2863 taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O"
2864 elif mnemo in ("jl", "jnge"):
2865 taken, reason = bool(val&(1<<flags["overflow"]) != val&(1<<flags["sign"])), "S!=O"
2866 elif mnemo in ("jle", "jng"):
2867 taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O"
2868 elif mnemo in ("jo",):
2869 taken, reason = bool(val&(1<<flags["overflow"])), "O"
2870 elif mnemo in ("jno",):
2871 taken, reason = not val&(1<<flags["overflow"]), "!O"
2872 elif mnemo in ("jpe", "jp"):
2873 taken, reason = bool(val&(1<<flags["parity"])), "P"
2874 elif mnemo in ("jnp", "jpo"):
2875 taken, reason = not val&(1<<flags["parity"]), "!P"
2876 elif mnemo in ("js",):
2877 taken, reason = bool(val&(1<<flags["sign"])) != 0, "S"
2878 elif mnemo in ("jns",):
2879 taken, reason = not val&(1<<flags["sign"]), "!S"
2880 return taken, reason
2882 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]:
2883 ra = None
2884 if self.is_ret(insn): 2884 ↛ 2886line 2884 didn't jump to line 2886, because the condition on line 2884 was never false
2885 ra = to_unsigned_long(dereference(gef.arch.sp))
2886 if frame.older(): 2886 ↛ 2889line 2886 didn't jump to line 2889, because the condition on line 2886 was never false
2887 ra = frame.older().pc()
2889 return ra
2891 @classmethod
2892 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2893 _NR_mprotect = 125
2894 insns = [
2895 "pushad",
2896 "pushfd",
2897 f"mov eax, {_NR_mprotect:d}",
2898 f"mov ebx, {addr:d}",
2899 f"mov ecx, {size:d}",
2900 f"mov edx, {perm.value:d}",
2901 "int 0x80",
2902 "popfd",
2903 "popad",
2904 ]
2905 return "; ".join(insns)
2907 def get_ith_parameter(self, i: int, in_func: bool = True) -> Tuple[str, Optional[int]]:
2908 if in_func:
2909 i += 1 # Account for RA being at the top of the stack
2910 sp = gef.arch.sp
2911 sz = gef.arch.ptrsize
2912 loc = sp + (i * sz)
2913 val = gef.memory.read_integer(loc)
2914 key = f"[sp + {i * sz:#x}]"
2915 return key, val
2918class X86_64(X86):
2919 aliases = ("X86_64", Elf.Abi.X86_64, "i386:x86-64")
2920 arch = "X86"
2921 mode = "64"
2923 gpr_registers = (
2924 "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip",
2925 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", )
2926 all_registers = gpr_registers + ( X86.flag_register, ) + X86.special_registers
2927 return_register = "$rax"
2928 function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"]
2929 syscall_register = "$rax"
2930 syscall_instructions = ["syscall"]
2931 # We don't want to inherit x86's stack based param getter
2932 get_ith_parameter = Architecture.get_ith_parameter
2933 _ptrsize = 8
2935 @classmethod
2936 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
2937 _NR_mprotect = 10
2938 insns = [
2939 "pushfq",
2940 "push rax",
2941 "push rdi",
2942 "push rsi",
2943 "push rdx",
2944 "push rcx",
2945 "push r11",
2946 f"mov rax, {_NR_mprotect:d}",
2947 f"mov rdi, {addr:d}",
2948 f"mov rsi, {size:d}",
2949 f"mov rdx, {perm.value:d}",
2950 "syscall",
2951 "pop r11",
2952 "pop rcx",
2953 "pop rdx",
2954 "pop rsi",
2955 "pop rdi",
2956 "pop rax",
2957 "popfq",
2958 ]
2959 return "; ".join(insns)
2961 def canary_address(self) -> int:
2962 return self.register("fs_base") + 0x28
2964class PowerPC(Architecture):
2965 aliases = ("PowerPC", Elf.Abi.POWERPC, "PPC")
2966 arch = "PPC"
2967 mode = "PPC32"
2969 all_registers = (
2970 "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
2971 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15",
2972 "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23",
2973 "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31",
2974 "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",)
2975 instruction_length = 4
2976 nop_insn = b"\x60\x00\x00\x00" # https://developer.ibm.com/articles/l-ppc/
2977 return_register = "$r0"
2978 flag_register: str = "$cr"
2979 flags_table = {
2980 3: "negative[0]",
2981 2: "positive[0]",
2982 1: "equal[0]",
2983 0: "overflow[0]",
2984 # cr7
2985 31: "less[7]",
2986 30: "greater[7]",
2987 29: "equal[7]",
2988 28: "overflow[7]",
2989 }
2990 function_parameters = ("$i0", "$i1", "$i2", "$i3", "$i4", "$i5")
2991 syscall_register = "$r0"
2992 syscall_instructions = ("sc",)
2993 ptrsize = 4
2996 def flag_register_to_human(self, val: Optional[int] = None) -> str:
2997 # https://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3)
2998 if not val:
2999 reg = self.flag_register
3000 val = gef.arch.register(reg)
3001 return flags_to_human(val, self.flags_table)
3003 def is_call(self, insn: Instruction) -> bool:
3004 return False
3006 def is_ret(self, insn: Instruction) -> bool:
3007 return insn.mnemonic == "blr"
3009 def is_conditional_branch(self, insn: Instruction) -> bool:
3010 mnemo = insn.mnemonic
3011 branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"}
3012 return mnemo in branch_mnemos
3014 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
3015 mnemo = insn.mnemonic
3016 flags = dict((self.flags_table[k], k) for k in self.flags_table)
3017 val = gef.arch.register(self.flag_register)
3018 taken, reason = False, ""
3019 if mnemo == "beq": taken, reason = bool(val&(1<<flags["equal[7]"])), "E"
3020 elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E"
3021 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["less[7]"])), "E || L"
3022 elif mnemo == "blt": taken, reason = bool(val&(1<<flags["less[7]"])), "L"
3023 elif mnemo == "bge": taken, reason = bool(val&(1<<flags["equal[7]"])) or bool(val&(1<<flags["greater[7]"])), "E || G"
3024 elif mnemo == "bgt": taken, reason = bool(val&(1<<flags["greater[7]"])), "G"
3025 return taken, reason
3027 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]:
3028 ra = None
3029 if self.is_ret(insn):
3030 ra = gef.arch.register("$lr")
3031 elif frame.older():
3032 ra = frame.older().pc()
3033 return ra
3035 @classmethod
3036 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3037 # Ref: https://developer.ibm.com/articles/l-ppc/
3038 _NR_mprotect = 125
3039 insns = [
3040 "addi 1, 1, -16", # 1 = r1 = sp
3041 "stw 0, 0(1)",
3042 "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args
3043 "stw 4, 8(1)",
3044 "stw 5, 12(1)",
3045 f"li 0, {_NR_mprotect:d}",
3046 f"lis 3, {addr:#x}@h",
3047 f"ori 3, 3, {addr:#x}@l",
3048 f"lis 4, {size:#x}@h",
3049 f"ori 4, 4, {size:#x}@l",
3050 f"li 5, {perm.value:d}",
3051 "sc",
3052 "lwz 0, 0(1)",
3053 "lwz 3, 4(1)",
3054 "lwz 4, 8(1)",
3055 "lwz 5, 12(1)",
3056 "addi 1, 1, 16",
3057 ]
3058 return ";".join(insns)
3061class PowerPC64(PowerPC):
3062 aliases = ("PowerPC64", Elf.Abi.POWERPC64, "PPC64")
3063 arch = "PPC"
3064 mode = "PPC64"
3065 ptrsize = 8
3068class SPARC(Architecture):
3069 """ Refs:
3070 - https://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf
3071 """
3072 aliases = ("SPARC", Elf.Abi.SPARC)
3073 arch = "SPARC"
3074 mode = ""
3076 all_registers = (
3077 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7",
3078 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7",
3079 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7",
3080 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7",
3081 "$pc", "$npc", "$sp ", "$fp ", "$psr",)
3082 instruction_length = 4
3083 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0
3084 return_register = "$i0"
3085 flag_register: str = "$psr"
3086 flags_table = {
3087 23: "negative",
3088 22: "zero",
3089 21: "overflow",
3090 20: "carry",
3091 7: "supervisor",
3092 5: "trap",
3093 }
3094 function_parameters = ("$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",)
3095 syscall_register = "%g1"
3096 syscall_instructions = ("t 0x10",)
3098 def flag_register_to_human(self, val: Optional[int] = None) -> str:
3099 # https://www.gaisler.com/doc/sparcv8.pdf
3100 reg = self.flag_register
3101 if not val:
3102 val = gef.arch.register(reg)
3103 return flags_to_human(val, self.flags_table)
3105 def is_call(self, insn: Instruction) -> bool:
3106 return False
3108 def is_ret(self, insn: Instruction) -> bool:
3109 return insn.mnemonic == "ret"
3111 def is_conditional_branch(self, insn: Instruction) -> bool:
3112 mnemo = insn.mnemonic
3113 # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html
3114 branch_mnemos = {
3115 "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu",
3116 "bneg", "bpos", "bvs", "bvc", "bcs", "bcc"
3117 }
3118 return mnemo in branch_mnemos
3120 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
3121 mnemo = insn.mnemonic
3122 flags = dict((self.flags_table[k], k) for k in self.flags_table)
3123 val = gef.arch.register(self.flag_register)
3124 taken, reason = False, ""
3126 if mnemo == "be": taken, reason = bool(val&(1<<flags["zero"])), "Z"
3127 elif mnemo == "bne": taken, reason = bool(val&(1<<flags["zero"])) == 0, "!Z"
3128 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)"
3129 elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O"
3130 elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z"
3131 elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
3132 elif mnemo == "bl": taken, reason = bool(val&(1<<flags["negative"])) and bool(val&(1<<flags["overflow"])), "N && O"
3133 elif mnemo == "blu": taken, reason = bool(val&(1<<flags["carry"])), "C"
3134 elif mnemo == "ble": taken, reason = bool(val&(1<<flags["zero"])) or bool(val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)"
3135 elif mnemo == "bleu": taken, reason = bool(val&(1<<flags["carry"])) or bool(val&(1<<flags["zero"])), "C || Z"
3136 elif mnemo == "bneg": taken, reason = bool(val&(1<<flags["negative"])), "N"
3137 elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N"
3138 elif mnemo == "bvs": taken, reason = bool(val&(1<<flags["overflow"])), "O"
3139 elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O"
3140 elif mnemo == "bcs": taken, reason = bool(val&(1<<flags["carry"])), "C"
3141 elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
3142 return taken, reason
3144 def get_ra(self, insn: Instruction, frame: "gdb.Frame") -> Optional[int]:
3145 ra = None
3146 if self.is_ret(insn):
3147 ra = gef.arch.register("$o7")
3148 elif frame.older():
3149 ra = frame.older().pc()
3150 return ra
3152 @classmethod
3153 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3154 hi = (addr & 0xffff0000) >> 16
3155 lo = (addr & 0x0000ffff)
3156 _NR_mprotect = 125
3157 insns = ["add %sp, -16, %sp",
3158 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]",
3159 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]",
3160 f"sethi %hi({hi}), %o0",
3161 f"or %o0, {lo}, %o0",
3162 "clr %o1",
3163 "clr %o2",
3164 f"mov {_NR_mprotect}, %g1",
3165 "t 0x10",
3166 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
3167 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
3168 "add %sp, 16, %sp",]
3169 return "; ".join(insns)
3172class SPARC64(SPARC):
3173 """Refs:
3174 - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf
3175 - https://cr.yp.to/2005-590/sparcv9.pdf
3176 """
3177 aliases = ("SPARC64", Elf.Abi.SPARC64)
3178 arch = "SPARC"
3179 mode = "V9"
3181 all_registers = [
3182 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7",
3183 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7",
3184 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7",
3185 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7",
3186 "$pc", "$npc", "$sp", "$fp", "$state", ]
3188 flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr)
3189 flags_table = {
3190 35: "negative",
3191 34: "zero",
3192 33: "overflow",
3193 32: "carry",
3194 }
3196 syscall_instructions = ["t 0x6d"]
3198 @classmethod
3199 def mprotect_asm(cls, addr: int, size: int, perm: Permission) -> str:
3200 hi = (addr & 0xffff0000) >> 16
3201 lo = (addr & 0x0000ffff)
3202 _NR_mprotect = 125
3203 insns = ["add %sp, -16, %sp",
3204 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]",
3205 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]",
3206 f"sethi %hi({hi}), %o0",
3207 f"or %o0, {lo}, %o0",
3208 "clr %o1",
3209 "clr %o2",
3210 f"mov {_NR_mprotect}, %g1",
3211 "t 0x6d",
3212 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
3213 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
3214 "add %sp, 16, %sp",]
3215 return "; ".join(insns)
3218class MIPS(Architecture):
3219 aliases: Tuple[Union[str, Elf.Abi], ...] = ("MIPS", Elf.Abi.MIPS)
3220 arch = "MIPS"
3221 mode = "MIPS32"
3223 # https://vhouten.home.xs4all.nl/mipsel/r3000-isa.html
3224 all_registers = (
3225 "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
3226 "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
3227 "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
3228 "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi",
3229 "$lo", "$fir", "$ra", "$gp", )
3230 instruction_length = 4
3231 _ptrsize = 4
3232 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0
3233 return_register = "$v0"
3234 flag_register = "$fcsr"
3235 flags_table = {}
3236 function_parameters = ("$a0", "$a1", "$a2", "$a3")
3237 syscall_register = "$v0"
3238 syscall_instructions = ("syscall",)
3240 def flag_register_to_human(self, val: Optional[int] = None) -> str:
3241 return Color.colorify("No flag register", "yellow underline")
3243 def is_call(self, insn: Instruction) -> bool:
3244 return False
3246 def is_ret(self, insn: Instruction) -> bool:
3247 return insn.mnemonic == "jr" and insn.operands[0] == "ra"
3249 def is_conditional_branch(self, insn: Instruction) -> bool:
3250 mnemo = insn.mnemonic
3251 branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"}
3252 return mnemo in branch_mnemos
3254 def is_branch_taken(self, insn: Instruction) -> Tuple[bool, str]:
3255 mnemo, ops = insn.mnemonic, insn.operands
3256 taken, reason = False, ""
3258 if mnemo == "beq":
3259 taken, reason = gef.arch.register(ops[0])