· 6 years ago · Aug 08, 2019, 05:04 PM
1# -*- coding: utf-8 -*-
2#
3#
4#######################################################################################
5# GEF - Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers
6#
7# by @_hugsy_
8#######################################################################################
9#
10# GEF is a kick-ass set of commands for X86, ARM, MIPS, PowerPC and SPARC to
11# make GDB cool again for exploit dev. It is aimed to be used mostly by exploit
12# devs and reversers, to provides additional features to GDB using the Python
13# API to assist during the process of dynamic analysis.
14#
15# GEF fully relies on GDB API and other Linux-specific sources of information
16# (such as /proc/<pid>). As a consequence, some of the features might not work
17# on custom or hardened systems such as GrSec.
18#
19# It has full support for both Python2 and Python3 and works on
20# * x86-32 & x86-64
21# * arm v5,v6,v7
22# * aarch64 (armv8)
23# * mips & mips64
24# * powerpc & powerpc64
25# * sparc & sparc64(v9)
26#
27# Requires GDB 7.x compiled with Python (2.x, or 3.x)
28#
29# To start: in gdb, type `source /path/to/gef.py`
30#
31#######################################################################################
32#
33# gef is distributed under the MIT License (MIT)
34# Copyright (c) 2013-2019 crazy rabbidz
35#
36# Permission is hereby granted, free of charge, to any person obtaining a copy
37# of this software and associated documentation files (the "Software"), to deal
38# in the Software without restriction, including without limitation the rights
39# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
40# copies of the Software, and to permit persons to whom the Software is
41# furnished to do so, subject to the following conditions:
42#
43# The above copyright notice and this permission notice shall be included in all
44# copies or substantial portions of the Software.
45#
46# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
52# SOFTWARE.
53#
54#
55
56
57from __future__ import print_function, division, absolute_import
58
59import abc
60import binascii
61import codecs
62import collections
63import ctypes
64import fcntl
65import functools
66import getopt
67import hashlib
68import imp
69import inspect
70import itertools
71import os
72import platform
73import re
74import shutil
75import site
76import socket
77import string
78import struct
79import subprocess
80import sys
81import tempfile
82import termios
83import time
84import traceback
85
86
87PYTHON_MAJOR = sys.version_info[0]
88
89
90if PYTHON_MAJOR == 2:
91 from HTMLParser import HTMLParser #pylint: disable=import-error
92 from cStringIO import StringIO #pylint: disable=import-error
93 from urllib import urlopen #pylint: disable=no-name-in-module
94 import ConfigParser as configparser #pylint: disable=import-error
95 import xmlrpclib #pylint: disable=import-error
96
97 # Compat Py2/3 hacks
98 def range(*args):
99 """Replace range() builtin with an iterator version."""
100 if len(args) < 1:
101 raise TypeError()
102 start, end, step = 0, args[0], 1
103 if len(args) == 2: start, end = args
104 if len(args) == 3: start, end, step = args
105 for n in itertools.count(start=start, step=step):
106 if (step>0 and n >= end) or (step<0 and n<=end): break
107 yield n
108
109 FileNotFoundError = IOError #pylint: disable=redefined-builtin
110 ConnectionRefusedError = socket.error #pylint: disable=redefined-builtin
111
112 LEFT_ARROW = "<-"
113 RIGHT_ARROW = "->"
114 DOWN_ARROW = "\\->"
115 HORIZONTAL_LINE = "-"
116 VERTICAL_LINE = "|"
117 CROSS = "x"
118 TICK = "v"
119 GEF_PROMPT = "gef> "
120 GEF_PROMPT_ON = "\001\033[1;32m\002{0:s}\001\033[0m\002".format(GEF_PROMPT)
121 GEF_PROMPT_OFF = "\001\033[1;31m\002{0:s}\001\033[0m\002".format(GEF_PROMPT)
122
123elif PYTHON_MAJOR == 3:
124 from html.parser import HTMLParser #pylint: disable=import-error
125 from io import StringIO
126 from urllib.request import urlopen #pylint: disable=import-error,no-name-in-module
127 import configparser
128 import xmlrpc.client as xmlrpclib #pylint: disable=import-error
129
130 # Compat Py2/3 hack
131 long = int
132 unicode = str
133
134 LEFT_ARROW = " \u2190 "
135 RIGHT_ARROW = " \u2192 "
136 DOWN_ARROW = "\u21b3"
137 HORIZONTAL_LINE = "\u2500"
138 VERTICAL_LINE = "\u2502"
139 CROSS = "\u2718 "
140 TICK = "\u2713 "
141 GEF_PROMPT = "gef\u27a4 "
142 GEF_PROMPT_ON = "\001\033[1;32m\002{0:s}\001\033[0m\002".format(GEF_PROMPT)
143 GEF_PROMPT_OFF = "\001\033[1;31m\002{0:s}\001\033[0m\002".format(GEF_PROMPT)
144
145else:
146 raise Exception("WTF is this Python version??")
147
148
149def http_get(url):
150 """Basic HTTP wrapper for GET request. Return the body of the page if HTTP code is OK,
151 otherwise return None."""
152 try:
153 http = urlopen(url)
154 if http.getcode() != 200:
155 return None
156 return http.read()
157 except Exception:
158 return None
159
160
161def update_gef(argv):
162 """Try to update `gef` to the latest version pushed on GitHub. Return 0 on success,
163 1 on failure. """
164 gef_local = os.path.realpath(argv[0])
165 hash_gef_local = hashlib.sha512(open(gef_local, "rb").read()).digest()
166 gef_remote = "https://raw.githubusercontent.com/hugsy/gef/master/gef.py"
167 gef_remote_data = http_get(gef_remote)
168 if gef_remote_data is None:
169 print("[-] Failed to get remote gef")
170 return 1
171
172 hash_gef_remote = hashlib.sha512(gef_remote_data).digest()
173 if hash_gef_local == hash_gef_remote:
174 print("[-] No update")
175 else:
176 with open(gef_local, "wb") as f:
177 f.write(gef_remote_data)
178 print("[+] Updated")
179 return 0
180
181
182try:
183 import gdb
184except ImportError:
185 # if out of gdb, the only action allowed is to update gef.py
186 if len(sys.argv)==2 and sys.argv[1]=="--update":
187 sys.exit(update_gef(sys.argv))
188 print("[-] gef cannot run as standalone")
189 sys.exit(0)
190
191__gef__ = None
192__commands__ = []
193__functions__ = []
194__aliases__ = []
195__config__ = {}
196__watches__ = {}
197__infos_files__ = []
198__gef_convenience_vars_index__ = 0
199__context_messages__ = []
200__heap_allocated_list__ = []
201__heap_freed_list__ = []
202__heap_uaf_watchpoints__ = []
203__pie_breakpoints__ = {}
204__pie_counter__ = 1
205__gef_remote__ = None
206__gef_qemu_mode__ = False
207__gef_default_main_arena__ = "main_arena"
208__gef_int_stream_buffer__ = None
209
210DEFAULT_PAGE_ALIGN_SHIFT = 12
211DEFAULT_PAGE_SIZE = 1 << DEFAULT_PAGE_ALIGN_SHIFT
212GEF_RC = os.path.join(os.getenv("HOME"), ".gef.rc")
213GEF_TEMP_DIR = os.path.join(tempfile.gettempdir(), "gef")
214GEF_MAX_STRING_LENGTH = 50
215
216GDB_MIN_VERSION = (7, 7)
217GDB_VERSION_MAJOR, GDB_VERSION_MINOR = [int(_) for _ in re.search(r"(\d+)[^\d]+(\d+)", gdb.VERSION).groups()]
218GDB_VERSION = (GDB_VERSION_MAJOR, GDB_VERSION_MINOR)
219
220current_elf = None
221current_arch = None
222
223highlight_table = {}
224ANSI_SPLIT_RE = "(\033\[[\d;]*m)"
225
226if PYTHON_MAJOR==3:
227 lru_cache = functools.lru_cache #pylint: disable=no-member
228else:
229 def lru_cache(maxsize = 128):
230 """Port of the Python3 LRU cache mechanism provided by itertools."""
231 class GefLruCache(object):
232 """Local LRU cache for Python2."""
233 def __init__(self, input_func, max_size):
234 self._input_func = input_func
235 self._max_size = max_size
236 self._caches_dict = {}
237 self._caches_info = {}
238 return
239
240 def cache_info(self, caller=None):
241 """Return a string with statistics of cache usage."""
242 if caller not in self._caches_dict:
243 return ""
244 hits = self._caches_info[caller]["hits"]
245 missed = self._caches_info[caller]["missed"]
246 cursz = len(self._caches_dict[caller])
247 return "CacheInfo(hits={}, misses={}, maxsize={}, currsize={})".format(hits, missed, self._max_size, cursz)
248
249 def cache_clear(self, caller=None):
250 """Clear a cache."""
251 if caller in self._caches_dict:
252 self._caches_dict[caller] = collections.OrderedDict()
253 return
254
255 def __get__(self, obj, objtype):
256 """Cache getter."""
257 return_func = functools.partial(self._cache_wrapper, obj)
258 return_func.cache_clear = functools.partial(self.cache_clear, obj)
259 return functools.wraps(self._input_func)(return_func)
260
261 def __call__(self, *args, **kwargs):
262 """Invoking the wrapped function, by attempting to get its value from cache if existing."""
263 return self._cache_wrapper(None, *args, **kwargs)
264
265 __call__.cache_clear = cache_clear
266 __call__.cache_info = cache_info
267
268 def _cache_wrapper(self, caller, *args, **kwargs):
269 """Defines the caching mechanism."""
270 kwargs_key = "".join(map(lambda x : str(x) + str(type(kwargs[x])) + str(kwargs[x]), sorted(kwargs)))
271 key = "".join(map(lambda x : str(type(x)) + str(x) , args)) + kwargs_key
272 if caller not in self._caches_dict:
273 self._caches_dict[caller] = collections.OrderedDict()
274 self._caches_info[caller] = {"hits":0, "missed":0}
275
276 cur_caller_cache_dict = self._caches_dict[caller]
277 if key in cur_caller_cache_dict:
278 self._caches_info[caller]["hits"] += 1
279 return cur_caller_cache_dict[key]
280
281 self._caches_info[caller]["missed"] += 1
282 if self._max_size is not None:
283 if len(cur_caller_cache_dict) >= self._max_size:
284 cur_caller_cache_dict.popitem(False)
285
286 cur_caller_cache_dict[key] = self._input_func(caller, *args, **kwargs) if caller != None else self._input_func(*args, **kwargs)
287 return cur_caller_cache_dict[key]
288
289 return lambda input_func: functools.wraps(input_func)(GefLruCache(input_func, maxsize))
290
291
292def reset_all_caches():
293 """Free all caches. If an object is cached, it will have a callable attribute `cache_clear`
294 which will be invoked to purge the function cache."""
295
296 for mod in dir(sys.modules["__main__"]):
297 obj = getattr(sys.modules["__main__"], mod)
298 if hasattr(obj, "cache_clear"):
299 obj.cache_clear()
300 return
301
302
303def highlight_text(text):
304 """
305 Highlight text using highlight_table { match -> color } settings.
306
307 If RegEx is enabled it will create a match group around all items in the
308 highlight_table and wrap the specified color in the highlight_table
309 around those matches.
310
311 If RegEx is disabled, split by ANSI codes and 'colorify' each match found
312 within the specified string.
313 """
314 if not highlight_table:
315 return text
316
317 if get_gef_setting("highlight.regex"):
318 for match, color in highlight_table.items():
319 text = re.sub("(" + match + ")", Color.colorify("\\1", color), text)
320 return text
321
322 ansiSplit = re.split(ANSI_SPLIT_RE, text)
323
324 for match, color in highlight_table.items():
325 for index, val in enumerate(ansiSplit):
326 found = val.find(match)
327 if found > -1:
328 ansiSplit[index] = val.replace(match, Color.colorify(match, color))
329 break
330 text = "".join(ansiSplit)
331 ansiSplit = re.split(ANSI_SPLIT_RE, text)
332
333 return "".join(ansiSplit)
334
335
336def gef_print(x="", *args, **kwargs):
337 """Wrapper around print(), using string buffering feature."""
338 x = highlight_text(x)
339 if __gef_int_stream_buffer__ and not is_debug():
340 return __gef_int_stream_buffer__.write(x + kwargs.get("end", "\n"))
341 return print(x, *args, **kwargs)
342
343
344def bufferize(f):
345 """Store the content to be printed for a function in memory, and flush it on function exit."""
346
347 @functools.wraps(f)
348 def wrapper(*args, **kwargs):
349 global __gef_int_stream_buffer__
350
351 if __gef_int_stream_buffer__:
352 return f(*args, **kwargs)
353
354 __gef_int_stream_buffer__ = StringIO()
355 try:
356 rv = f(*args, **kwargs)
357 finally:
358 sys.stdout.write(__gef_int_stream_buffer__.getvalue())
359 sys.stdout.flush()
360 __gef_int_stream_buffer__ = None
361 return rv
362
363 return wrapper
364
365
366class Color:
367 """Used to colorify terminal output."""
368 colors = {
369 "normal" : "\033[0m",
370 "gray" : "\033[1;38;5;240m",
371 "red" : "\033[31m",
372 "green" : "\033[32m",
373 "yellow" : "\033[33m",
374 "blue" : "\033[34m",
375 "pink" : "\033[35m",
376 "cyan" : "\033[36m",
377 "bold" : "\033[1m",
378 "underline" : "\033[4m",
379 "underline_off" : "\033[24m",
380 "highlight" : "\033[3m",
381 "highlight_off" : "\033[23m",
382 "blink" : "\033[5m",
383 "blink_off" : "\033[25m",
384 }
385
386 @staticmethod
387 def redify(msg): return Color.colorify(msg, "red")
388 @staticmethod
389 def greenify(msg): return Color.colorify(msg, "green")
390 @staticmethod
391 def blueify(msg): return Color.colorify(msg, "blue")
392 @staticmethod
393 def yellowify(msg): return Color.colorify(msg, "yellow")
394 @staticmethod
395 def grayify(msg): return Color.colorify(msg, "gray")
396 @staticmethod
397 def pinkify(msg): return Color.colorify(msg, "pink")
398 @staticmethod
399 def cyanify(msg): return Color.colorify(msg, "cyan")
400 @staticmethod
401 def boldify(msg): return Color.colorify(msg, "bold")
402 @staticmethod
403 def underlinify(msg): return Color.colorify(msg, "underline")
404 @staticmethod
405 def highlightify(msg): return Color.colorify(msg, "highlight")
406 @staticmethod
407 def blinkify(msg): return Color.colorify(msg, "blink")
408
409 @staticmethod
410 def colorify(text, attrs):
411 """Color text according to the given attributes."""
412 if get_gef_setting("gef.disable_color") is True: return text
413
414 colors = Color.colors
415 msg = [colors[attr] for attr in attrs.split() if attr in colors]
416 msg.append(str(text))
417 if colors["highlight"] in msg : msg.append(colors["highlight_off"])
418 if colors["underline"] in msg : msg.append(colors["underline_off"])
419 if colors["blink"] in msg : msg.append(colors["blink_off"])
420 msg.append(colors["normal"])
421 return "".join(msg)
422
423
424class Address:
425 """GEF representation of memory addresses."""
426 def __init__(self, *args, **kwargs):
427 self.value = kwargs.get("value", 0)
428 self.section = kwargs.get("section", None)
429 self.info = kwargs.get("info", None)
430 self.valid = kwargs.get("valid", True)
431 return
432
433 def __str__(self):
434 value = format_address(self.value)
435 code_color = get_gef_setting("theme.address_code")
436 stack_color = get_gef_setting("theme.address_stack")
437 heap_color = get_gef_setting("theme.address_heap")
438 if self.is_in_text_segment():
439 return Color.colorify(value, code_color)
440 if self.is_in_heap_segment():
441 return Color.colorify(value, heap_color)
442 if self.is_in_stack_segment():
443 return Color.colorify(value, stack_color)
444 return value
445
446 def is_in_text_segment(self):
447 return (hasattr(self.info, "name") and ".text" in self.info.name) or \
448 (hasattr(self.section, "path") and get_filepath() == self.section.path and self.section.is_executable())
449
450 def is_in_stack_segment(self):
451 return hasattr(self.section, "path") and "[stack]" == self.section.path
452
453 def is_in_heap_segment(self):
454 return hasattr(self.section, "path") and "[heap]" == self.section.path
455
456 def dereference(self):
457 addr = align_address(long(self.value))
458 derefed = dereference(addr)
459 return None if derefed is None else long(derefed)
460
461
462class Permission:
463 """GEF representation of Linux permission."""
464 NONE = 0
465 READ = 1
466 WRITE = 2
467 EXECUTE = 4
468 ALL = READ | WRITE | EXECUTE
469
470 def __init__(self, **kwargs):
471 self.value = kwargs.get("value", 0)
472 return
473
474 def __or__(self, value):
475 return self.value | value
476
477 def __and__(self, value):
478 return self.value & value
479
480 def __xor__(self, value):
481 return self.value ^ value
482
483 def __eq__(self, value):
484 return self.value == value
485
486 def __ne__(self, value):
487 return self.value != value
488
489 def __str__(self):
490 perm_str = ""
491 perm_str += "r" if self & Permission.READ else "-"
492 perm_str += "w" if self & Permission.WRITE else "-"
493 perm_str += "x" if self & Permission.EXECUTE else "-"
494 return perm_str
495
496 @staticmethod
497 def from_info_sections(*args):
498 perm = Permission()
499 for arg in args:
500 if "READONLY" in arg:
501 perm.value += Permission.READ
502 if "DATA" in arg:
503 perm.value += Permission.WRITE
504 if "CODE" in arg:
505 perm.value += Permission.EXECUTE
506 return perm
507
508 @staticmethod
509 def from_process_maps(perm_str):
510 perm = Permission()
511 if perm_str[0] == "r":
512 perm.value += Permission.READ
513 if perm_str[1] == "w":
514 perm.value += Permission.WRITE
515 if perm_str[2] == "x":
516 perm.value += Permission.EXECUTE
517 return perm
518
519
520class Section:
521 """GEF representation of process memory sections."""
522
523 def __init__(self, *args, **kwargs):
524 self.page_start = kwargs.get("page_start")
525 self.page_end = kwargs.get("page_end")
526 self.offset = kwargs.get("offset")
527 self.permission = kwargs.get("permission")
528 self.inode = kwargs.get("inode")
529 self.path = kwargs.get("path")
530 return
531
532 def is_readable(self):
533 return self.permission.value and self.permission.value&Permission.READ
534
535 def is_writable(self):
536 return self.permission.value and self.permission.value&Permission.WRITE
537
538 def is_executable(self):
539 return self.permission.value and self.permission.value&Permission.EXECUTE
540
541 @property
542 def size(self):
543 if self.page_end is None or self.page_start is None:
544 return -1
545 return self.page_end - self.page_start
546
547 @property
548 def realpath(self):
549 # when in a `gef-remote` session, realpath returns the path to the binary on the local disk, not remote
550 return self.path if __gef_remote__ is None else "/tmp/gef/{:d}/{:s}".format(__gef_remote__, self.path)
551
552
553Zone = collections.namedtuple("Zone", ["name", "zone_start", "zone_end", "filename"])
554
555
556class Elf:
557 """Basic ELF parsing.
558 Ref:
559 - http://www.skyfree.org/linux/references/ELF_Format.pdf
560 - http://refspecs.freestandards.org/elf/elfspec_ppc.pdf
561 - http://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi.html
562 """
563 LITTLE_ENDIAN = 1
564 BIG_ENDIAN = 2
565
566 ELF_32_BITS = 0x01
567 ELF_64_BITS = 0x02
568
569 X86_64 = 0x3e
570 X86_32 = 0x03
571 ARM = 0x28
572 MIPS = 0x08
573 POWERPC = 0x14
574 POWERPC64 = 0x15
575 SPARC = 0x02
576 SPARC64 = 0x2b
577 AARCH64 = 0xb7
578 RISCV = 0xf3
579
580 ET_EXEC = 2
581 ET_DYN = 3
582 ET_CORE = 4
583
584
585 e_magic = b"\x7fELF"
586 e_class = ELF_32_BITS
587 e_endianness = LITTLE_ENDIAN
588 e_eiversion = None
589 e_osabi = None
590 e_abiversion = None
591 e_pad = None
592 e_type = ET_EXEC
593 e_machine = X86_32
594 e_version = None
595 e_entry = 0x00
596 e_phoff = None
597 e_shoff = None
598 e_flags = None
599 e_ehsize = None
600 e_phentsize = None
601 e_phnum = None
602 e_shentsize = None
603 e_shnum = None
604 e_shstrndx = None
605
606
607
608 def __init__(self, elf="", minimalist=False):
609 """
610 Instantiate an ELF object. The default behavior is to create the object by parsing the ELF file.
611 But in some cases (QEMU-stub), we may just want a simple minimal object with default values."""
612 if minimalist:
613 return
614
615 if not os.access(elf, os.R_OK):
616 err("'{0}' not found/readable".format(elf))
617 err("Failed to get file debug information, most of gef features will not work")
618 return
619
620 with open(elf, "rb") as fd:
621 # off 0x0
622 self.e_magic, self.e_class, self.e_endianness, self.e_eiversion = struct.unpack(">IBBB", fd.read(7))
623
624 # adjust endianness in bin reading
625 endian = "<" if self.e_endianness == Elf.LITTLE_ENDIAN else ">"
626
627 # off 0x7
628 self.e_osabi, self.e_abiversion = struct.unpack("{}BB".format(endian), fd.read(2))
629 # off 0x9
630 self.e_pad = fd.read(7)
631 # off 0x10
632 self.e_type, self.e_machine, self.e_version = struct.unpack("{}HHI".format(endian), fd.read(8))
633 # off 0x18
634 if self.e_class == Elf.ELF_64_BITS:
635 # if arch 64bits
636 self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}QQQ".format(endian), fd.read(24))
637 else:
638 # else arch 32bits
639 self.e_entry, self.e_phoff, self.e_shoff = struct.unpack("{}III".format(endian), fd.read(12))
640
641 self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum = struct.unpack("{}HHHH".format(endian), fd.read(8))
642 self.e_shentsize, self.e_shnum, self.e_shstrndx = struct.unpack("{}HHH".format(endian), fd.read(6))
643 return
644
645
646class Instruction:
647 """GEF representation of a CPU instruction."""
648 def __init__(self, address, location, mnemo, operands):
649 self.address, self.location, self.mnemonic, self.operands = address, location, mnemo, operands
650 return
651
652 def __str__(self):
653 return "{:#10x} {:16} {:6} {:s}".format(self.address,
654 self.location,
655 self.mnemonic,
656 ", ".join(self.operands))
657
658 def is_valid(self):
659 return "(bad)" not in self.mnemonic
660
661@lru_cache()
662def search_for_main_arena():
663 global __gef_default_main_arena__
664 malloc_hook_addr = to_unsigned_long(gdb.parse_and_eval("(void *)&__malloc_hook"))
665
666 if is_x86():
667 addr = align_address_to_size(malloc_hook_addr + current_arch.ptrsize, 0x20)
668 elif is_arch(Elf.AARCH64) or is_arch(Elf.ARM):
669 addr = malloc_hook_addr - current_arch.ptrsize*2 - MallocStateStruct("*0").struct_size
670 else:
671 raise OSError("Cannot find main_arena for {}".format(current_arch.arch))
672
673 __gef_default_main_arena__ = "*0x{:x}".format(addr)
674 return addr
675
676class MallocStateStruct(object):
677 """GEF representation of malloc_state from https://github.com/bminor/glibc/blob/glibc-2.28/malloc/malloc.c#L1658"""
678 def __init__(self, addr):
679 try:
680 self.__addr = to_unsigned_long(gdb.parse_and_eval("&{}".format(addr)))
681 except gdb.error:
682 self.__addr = search_for_main_arena()
683
684 self.num_fastbins = 10
685 self.num_bins = 254
686
687 self.int_size = cached_lookup_type("int").sizeof
688 self.size_t = cached_lookup_type("size_t")
689 if not self.size_t:
690 ptr_type = "unsigned long" if current_arch.ptrsize == 8 else "unsigned int"
691 self.size_t = cached_lookup_type(ptr_type)
692
693 if get_libc_version() >= (2, 26):
694 self.fastbin_offset = align_address_to_size(self.int_size*3, 8)
695 else:
696 self.fastbin_offset = self.int_size*2
697 return
698
699 # struct offsets
700 @property
701 def addr(self):
702 return self.__addr
703 @property
704 def fastbins_addr(self):
705 return self.__addr + self.fastbin_offset
706 @property
707 def top_addr(self):
708 return self.fastbins_addr + self.num_fastbins*current_arch.ptrsize
709 @property
710 def last_remainder_addr(self):
711 return self.top_addr + current_arch.ptrsize
712 @property
713 def bins_addr(self):
714 return self.last_remainder_addr + current_arch.ptrsize
715 @property
716 def next_addr(self):
717 return self.bins_addr + self.num_bins*current_arch.ptrsize + self.int_size*4
718 @property
719 def next_free_addr(self):
720 return self.next_addr + current_arch.ptrsize
721 @property
722 def system_mem_addr(self):
723 return self.next_free_addr + current_arch.ptrsize*2
724 @property
725 def struct_size(self):
726 return self.system_mem_addr + current_arch.ptrsize*2 - self.__addr
727
728 # struct members
729 @property
730 def fastbinsY(self):
731 return self.get_size_t_array(self.fastbins_addr, self.num_fastbins)
732 @property
733 def top(self):
734 return self.get_size_t_pointer(self.top_addr)
735 @property
736 def last_remainder(self):
737 return self.get_size_t_pointer(self.last_remainder_addr)
738 @property
739 def bins(self):
740 return self.get_size_t_array(self.bins_addr, self.num_bins)
741 @property
742 def next(self):
743 return self.get_size_t_pointer(self.next_addr)
744 @property
745 def next_free(self):
746 return self.get_size_t_pointer(self.next_free_addr)
747 @property
748 def system_mem(self):
749 return self.get_size_t(self.system_mem_addr)
750
751 # helper methods
752 def get_size_t(self, addr):
753 return dereference(addr).cast(self.size_t)
754
755 def get_size_t_pointer(self, addr):
756 size_t_pointer = self.size_t.pointer()
757 return dereference(addr).cast(size_t_pointer)
758
759 def get_size_t_array(self, addr, length):
760 size_t_array = self.size_t.array(length)
761 return dereference(addr).cast(size_t_array)
762
763 def __getitem__(self, item):
764 return getattr(self, item)
765
766
767class GlibcArena:
768 """Glibc arena class
769 Ref: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1671 """
770 TCACHE_MAX_BINS = 0x40
771
772 def __init__(self, addr, name=None):
773 self.__name = name or __gef_default_main_arena__
774 try:
775 arena = gdb.parse_and_eval(addr)
776 malloc_state_t = cached_lookup_type("struct malloc_state")
777 self.__arena = arena.cast(malloc_state_t)
778 self.__addr = long(arena.address)
779 except:
780 self.__arena = MallocStateStruct(addr)
781 self.__addr = self.__arena.addr
782 return
783
784 def __getitem__(self, item):
785 return self.__arena[item]
786
787 def __getattr__(self, item):
788 return self.__arena[item]
789
790 def __int__(self):
791 return self.__addr
792
793 def tcachebin(self, i):
794 """Return head chunk in tcache[i]."""
795 heap_base = HeapBaseFunction.heap_base()
796 addr = dereference(heap_base + 2*current_arch.ptrsize + self.TCACHE_MAX_BINS + i*current_arch.ptrsize)
797 if not addr:
798 return None
799 return GlibcChunk(long(addr))
800
801 def fastbin(self, i):
802 """Return head chunk in fastbinsY[i]."""
803 addr = dereference_as_long(self.fastbinsY[i])
804 if addr == 0:
805 return None
806 return GlibcChunk(addr + 2 * current_arch.ptrsize)
807
808 def bin(self, i):
809 idx = i * 2
810 fd = dereference_as_long(self.bins[idx])
811 bw = dereference_as_long(self.bins[idx + 1])
812 return fd, bw
813
814 def get_next(self):
815 addr_next = dereference_as_long(self.next)
816 arena_main = GlibcArena(self.__name)
817 if addr_next == arena_main.__addr:
818 return None
819 return GlibcArena("*{:#x} ".format(addr_next))
820
821 def __str__(self):
822 top = dereference_as_long(self.top)
823 last_remainder = dereference_as_long(self.last_remainder)
824 n = dereference_as_long(self.next)
825 nfree = dereference_as_long(self.next_free)
826 sysmem = long(self.system_mem)
827 fmt = "Arena (base={:#x}, top={:#x}, last_remainder={:#x}, next={:#x}, next_free={:#x}, system_mem={:#x})"
828 return fmt.format(self.__addr, top, last_remainder, n, nfree, sysmem)
829
830
831class GlibcChunk:
832 """Glibc chunk class.
833 Ref: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/."""
834
835 def __init__(self, addr, from_base=False):
836 self.ptrsize = current_arch.ptrsize
837 if from_base:
838 self.chunk_base_address = addr
839 self.address = addr + 2 * self.ptrsize
840 else:
841 self.chunk_base_address = int(addr - 2 * self.ptrsize)
842 self.address = addr
843
844 self.size_addr = int(self.address - self.ptrsize)
845 self.prev_size_addr = self.chunk_base_address
846 return
847
848 def get_chunk_size(self):
849 return read_int_from_memory(self.size_addr) & (~0x07)
850
851 @property
852 def size(self):
853 return self.get_chunk_size()
854
855 def get_usable_size(self):
856 # https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L4537
857 cursz = self.get_chunk_size()
858 if cursz == 0: return cursz
859 if self.has_m_bit(): return cursz - 2 * self.ptrsize
860 return cursz - self.ptrsize
861
862 @property
863 def usable_size(self):
864 return self.get_usable_size()
865
866 def get_prev_chunk_size(self):
867 return read_int_from_memory(self.prev_size_addr)
868
869 def get_next_chunk(self):
870 addr = self.address + self.get_chunk_size()
871 return GlibcChunk(addr)
872
873 # if free-ed functions
874 def get_fwd_ptr(self):
875 return read_int_from_memory(self.address)
876
877 @property
878 def fwd(self):
879 return self.get_fwd_ptr()
880
881 fd = fwd # for compat
882
883 def get_bkw_ptr(self):
884 return read_int_from_memory(self.address + self.ptrsize)
885
886 @property
887 def bck(self):
888 return self.get_bkw_ptr()
889
890 bk = bck # for compat
891 # endif free-ed functions
892
893 def has_p_bit(self):
894 return read_int_from_memory(self.size_addr) & 0x01
895
896 def has_m_bit(self):
897 return read_int_from_memory(self.size_addr) & 0x02
898
899 def has_n_bit(self):
900 return read_int_from_memory(self.size_addr) & 0x04
901
902 def is_used(self):
903 """Check if the current block is used by:
904 - checking the M bit is true
905 - or checking that next chunk PREV_INUSE flag is true """
906 if self.has_m_bit():
907 return True
908
909 next_chunk = self.get_next_chunk()
910 return True if next_chunk.has_p_bit() else False
911
912 def str_chunk_size_flag(self):
913 msg = []
914 msg.append("PREV_INUSE flag: {}".format(Color.greenify("On") if self.has_p_bit() else Color.redify("Off")))
915 msg.append("IS_MMAPPED flag: {}".format(Color.greenify("On") if self.has_m_bit() else Color.redify("Off")))
916 msg.append("NON_MAIN_ARENA flag: {}".format(Color.greenify("On") if self.has_n_bit() else Color.redify("Off")))
917 return "\n".join(msg)
918
919 def _str_sizes(self):
920 msg = []
921 failed = False
922
923 try:
924 msg.append("Chunk size: {0:d} ({0:#x})".format(self.get_chunk_size()))
925 msg.append("Usable size: {0:d} ({0:#x})".format(self.get_usable_size()))
926 failed = True
927 except gdb.MemoryError:
928 msg.append("Chunk size: Cannot read at {:#x} (corrupted?)".format(self.size_addr))
929
930 try:
931 msg.append("Previous chunk size: {0:d} ({0:#x})".format(self.get_prev_chunk_size()))
932 failed = True
933 except gdb.MemoryError:
934 msg.append("Previous chunk size: Cannot read at {:#x} (corrupted?)".format(self.chunk_base_address))
935
936 if failed:
937 msg.append(self.str_chunk_size_flag())
938
939 return "\n".join(msg)
940
941 def _str_pointers(self):
942 fwd = self.address
943 bkw = self.address + self.ptrsize
944
945 msg = []
946 try:
947 msg.append("Forward pointer: {0:#x}".format(self.get_fwd_ptr()))
948 except gdb.MemoryError:
949 msg.append("Forward pointer: {0:#x} (corrupted?)".format(fwd))
950
951 try:
952 msg.append("Backward pointer: {0:#x}".format(self.get_bkw_ptr()))
953 except gdb.MemoryError:
954 msg.append("Backward pointer: {0:#x} (corrupted?)".format(bkw))
955
956 return "\n".join(msg)
957
958 def str_as_alloced(self):
959 return self._str_sizes()
960
961 def str_as_freed(self):
962 return "{}\n\n{}".format(self._str_sizes(), self._str_pointers())
963
964 def flags_as_string(self):
965 flags = []
966 if self.has_p_bit():
967 flags.append(Color.colorify("PREV_INUSE", "red bold"))
968 if self.has_m_bit():
969 flags.append(Color.colorify("IS_MMAPPED", "red bold"))
970 if self.has_n_bit():
971 flags.append(Color.colorify("NON_MAIN_ARENA", "red bold"))
972 return "|".join(flags)
973
974 def __str__(self):
975 msg = "{:s}(addr={:#x}, size={:#x}, flags={:s})".format(Color.colorify("Chunk", "yellow bold underline"),
976 long(self.address),
977 self.get_chunk_size(),
978 self.flags_as_string())
979 return msg
980
981 def psprint(self):
982 msg = []
983 msg.append(str(self))
984 if self.is_used():
985 msg.append(self.str_as_alloced())
986 else:
987 msg.append(self.str_as_freed())
988
989 return "\n".join(msg) + "\n"
990
991
992@lru_cache()
993def get_libc_version():
994 sections = get_process_maps()
995 try:
996 for section in sections:
997 if "libc-" in section.path:
998 libc_version = tuple(int(_) for _ in
999 re.search(r"libc-(\d+)\.(\d+)\.so", section.path).groups())
1000 break
1001 else:
1002 libc_version = 0, 0
1003 except AttributeError:
1004 libc_version = 0, 0
1005 return libc_version
1006
1007
1008@lru_cache()
1009def get_main_arena():
1010 try:
1011 return GlibcArena(__gef_default_main_arena__)
1012 except Exception as e:
1013 err("Failed to get the main arena, heap commands may not work properly: {}".format(e))
1014 return None
1015
1016
1017def titlify(text, color=None, msg_color=None):
1018 """Print a centered title."""
1019 cols = get_terminal_size()[1]
1020 nb = (cols - len(text) - 2)//2
1021 if color is None:
1022 color = __config__.get("theme.default_title_line")[0]
1023 if msg_color is None:
1024 msg_color = __config__.get("theme.default_title_message")[0]
1025
1026 msg = []
1027 msg.append(Color.colorify("{} ".format(HORIZONTAL_LINE * nb), color))
1028 msg.append(Color.colorify(text, msg_color))
1029 msg.append(Color.colorify(" {}".format(HORIZONTAL_LINE * nb), color))
1030 return "".join(msg)
1031
1032
1033def err(msg): return gef_print("{} {}".format(Color.colorify("[!]", "bold red"), msg))
1034def warn(msg): return gef_print("{} {}".format(Color.colorify("[*]", "bold yellow"), msg))
1035def ok(msg): return gef_print("{} {}".format(Color.colorify("[+]", "bold green"), msg))
1036def info(msg): return gef_print("{} {}".format(Color.colorify("[+]", "bold blue"), msg))
1037
1038
1039def push_context_message(level, message):
1040 """Push the message to be displayed the next time the context is invoked."""
1041 global __context_messages__
1042 if level not in ("error", "warn", "ok", "info"):
1043 err("Invalid level '{}', discarding message".format(level))
1044 return
1045 __context_messages__.append((level, message))
1046 return
1047
1048
1049def show_last_exception():
1050 """Display the last Python exception."""
1051
1052 def _show_code_line(fname, idx):
1053 fname = os.path.expanduser(os.path.expandvars(fname))
1054 __data = open(fname, "r").read().splitlines()
1055 return __data[idx-1] if idx < len(__data) else ""
1056
1057 gef_print("")
1058 exc_type, exc_value, exc_traceback = sys.exc_info()
1059
1060 gef_print(" Exception raised ".center(80, HORIZONTAL_LINE))
1061 gef_print("{}: {}".format(Color.colorify(exc_type.__name__, "bold underline red"), exc_value))
1062 gef_print(" Detailed stacktrace ".center(80, HORIZONTAL_LINE))
1063
1064 for fs in traceback.extract_tb(exc_traceback)[::-1]:
1065 filename, lineno, method, code = fs
1066
1067 if not code or not code.strip():
1068 code = _show_code_line(filename, lineno)
1069
1070 gef_print("""{} File "{}", line {:d}, in {}()""".format(DOWN_ARROW, Color.yellowify(filename),
1071 lineno, Color.greenify(method)))
1072 gef_print(" {} {}".format(RIGHT_ARROW, code))
1073
1074 gef_print(" Last 10 GDB commands ".center(80, HORIZONTAL_LINE))
1075 gdb.execute("show commands")
1076 gef_print(" Runtime environment ".center(80, HORIZONTAL_LINE))
1077 gef_print("* GDB: {}".format(gdb.VERSION))
1078 gef_print("* Python: {:d}.{:d}.{:d} - {:s}".format(sys.version_info.major, sys.version_info.minor,
1079 sys.version_info.micro, sys.version_info.releaselevel))
1080 gef_print("* OS: {:s} - {:s} ({:s}) on {:s}".format(platform.system(), platform.release(),
1081 platform.architecture()[0],
1082 " ".join(platform.dist()))) #pylint: disable=deprecated-method
1083 gef_print(HORIZONTAL_LINE*80)
1084 gef_print("")
1085 return
1086
1087
1088def gef_pystring(x):
1089 """Python 2 & 3 compatibility function for strings handling."""
1090 res = str(x, encoding="utf-8") if PYTHON_MAJOR == 3 else x
1091 substs = [("\n","\\n"), ("\r","\\r"), ("\t","\\t"), ("\v","\\v"), ("\b","\\b"), ]
1092 for x,y in substs: res = res.replace(x,y)
1093 return res
1094
1095
1096def gef_pybytes(x):
1097 """Python 2 & 3 compatibility function for bytes handling."""
1098 return bytes(str(x), encoding="utf-8") if PYTHON_MAJOR == 3 else x
1099
1100
1101@lru_cache()
1102def which(program):
1103 """Locate a command on the filesystem."""
1104 def is_exe(fpath):
1105 return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
1106
1107 fpath = os.path.split(program)[0]
1108 if fpath:
1109 if is_exe(program):
1110 return program
1111 else:
1112 for path in os.environ["PATH"].split(os.pathsep):
1113 path = path.strip('"')
1114 exe_file = os.path.join(path, program)
1115 if is_exe(exe_file):
1116 return exe_file
1117
1118 raise FileNotFoundError("Missing file `{:s}`".format(program))
1119
1120
1121def style_byte(b, color=True):
1122 style = {
1123 "nonprintable": "yellow",
1124 "printable": "white",
1125 "00": "gray",
1126 "0a": "blue",
1127 "ff": "green",
1128 }
1129 sbyte = "{:02x}".format(b)
1130 if not color or get_gef_setting("highlight.regex"):
1131 return sbyte
1132
1133 if sbyte in style:
1134 st = style[sbyte]
1135 elif chr(b) in (string.ascii_letters + string.digits + string.punctuation + " "):
1136 st = style.get("printable")
1137 else:
1138 st = style.get("nonprintable")
1139 if st:
1140 sbyte = Color.colorify(sbyte, st)
1141 return sbyte
1142
1143
1144def hexdump(source, length=0x10, separator=".", show_raw=False, base=0x00):
1145 """Return the hexdump of `src` argument.
1146 @param source *MUST* be of type bytes or bytearray
1147 @param length is the length of items per line
1148 @param separator is the default character to use if one byte is not printable
1149 @param show_raw if True, do not add the line nor the text translation
1150 @param base is the start address of the block being hexdump
1151 @return a string with the hexdump"""
1152 result = []
1153 align = get_memory_alignment()*2+2 if is_alive() else 18
1154
1155 for i in range(0, len(source), length):
1156 chunk = bytearray(source[i:i + length])
1157 hexa = " ".join([style_byte(b, color=not show_raw) for b in chunk])
1158
1159
1160 if show_raw:
1161 result.append(hexa)
1162 continue
1163
1164 text = "".join([chr(b) if 0x20 <= b < 0x7F else separator for b in chunk])
1165 sym = gdb_get_location_from_symbol(base+i)
1166 sym = "<{:s}+{:04x}>".format(*sym) if sym else ""
1167
1168 result.append("{addr:#0{aw}x} {sym} {data:<{dw}} {text}".format(aw=align,
1169 addr=base+i,
1170 sym=sym,
1171 dw=3*length,
1172 data=hexa,
1173 text=text))
1174 return "\n".join(result)
1175
1176
1177def is_debug():
1178 """Check if debug mode is enabled."""
1179 return get_gef_setting("gef.debug") is True
1180
1181context_hidden = False
1182def hide_context():
1183 global context_hidden
1184 context_hidden = True
1185def unhide_context():
1186 global context_hidden
1187 context_hidden = False
1188
1189def enable_redirect_output(to_file="/dev/null"):
1190 """Redirect all GDB output to `to_file` parameter. By default, `to_file` redirects to `/dev/null`."""
1191 gdb.execute("set logging overwrite")
1192 gdb.execute("set logging file {:s}".format(to_file))
1193 gdb.execute("set logging redirect on")
1194 gdb.execute("set logging on")
1195 return
1196
1197
1198def disable_redirect_output():
1199 """Disable the output redirection, if any."""
1200 gdb.execute("set logging off")
1201 gdb.execute("set logging redirect off")
1202 return
1203
1204
1205@lru_cache()
1206def get_gef_setting(name):
1207 """Read global gef settings.
1208 Return None if not found. A valid config setting can never return None,
1209 but False, 0 or ""."""
1210 global __config__
1211 setting = __config__.get(name, None)
1212 if not setting:
1213 return None
1214 return setting[0]
1215
1216
1217def set_gef_setting(name, value, _type=None, _desc=None):
1218 """Set global gef settings.
1219 Raise ValueError if `name` doesn't exist and `type` and `desc`
1220 are not provided."""
1221 global __config__
1222
1223 if name not in __config__:
1224 # create new setting
1225 if _type is None or _desc is None:
1226 raise ValueError("Setting '{}' is undefined, need to provide type and description".format(name))
1227 __config__[name] = [_type(value), _type, _desc]
1228 return
1229
1230 # set existing setting
1231 func = __config__[name][1]
1232 __config__[name][0] = func(value)
1233 reset_all_caches()
1234 return
1235
1236
1237def gef_makedirs(path, mode=0o755):
1238 """Recursive mkdir() creation. If successful, return the absolute path of the directory created."""
1239 abspath = os.path.realpath(path)
1240 if os.path.isdir(abspath):
1241 return abspath
1242
1243 if PYTHON_MAJOR == 3:
1244 os.makedirs(path, mode=mode, exist_ok=True) #pylint: disable=unexpected-keyword-arg
1245 else:
1246 try:
1247 os.makedirs(path, mode=mode)
1248 except os.error:
1249 pass
1250 return abspath
1251
1252
1253@lru_cache()
1254def gdb_lookup_symbol(sym):
1255 """Fetch the proper symbol or None if not defined."""
1256 try:
1257 return gdb.decode_line(sym)[1]
1258 except gdb.error:
1259 return None
1260
1261
1262@lru_cache(maxsize=512)
1263def gdb_get_location_from_symbol(address):
1264 """Retrieve the location of the `address` argument from the symbol table.
1265 Return a tuple with the name and offset if found, None otherwise."""
1266 # this is horrible, ugly hack and shitty perf...
1267 # find a *clean* way to get gdb.Location from an address
1268 name = None
1269 sym = gdb.execute("info symbol {:#x}".format(address), to_string=True)
1270 if sym.startswith("No symbol matches"):
1271 return None
1272
1273 i = sym.find(" in section ")
1274 sym = sym[:i].split()
1275 name, offset = sym[0], 0
1276 if len(sym) == 3 and sym[2].isdigit():
1277 offset = int(sym[2])
1278 return name, offset
1279
1280
1281def gdb_disassemble(start_pc, **kwargs):
1282 """Disassemble instructions from `start_pc` (Integer). Accepts the following named parameters:
1283 - `end_pc` (Integer) only instructions whose start address fall in the interval from start_pc to end_pc are returned.
1284 - `count` (Integer) list at most this many disassembled instructions
1285 If `end_pc` and `count` are not provided, the function will behave as if `count=1`.
1286 Return an iterator of Instruction objects
1287 """
1288 frame = gdb.selected_frame()
1289 arch = frame.architecture()
1290
1291 for insn in arch.disassemble(start_pc, **kwargs):
1292 address = insn["addr"]
1293 asm = insn["asm"].rstrip().split(None, 1)
1294 if len(asm) > 1:
1295 mnemo, operands = asm
1296 operands = operands.split(",")
1297 else:
1298 mnemo, operands = asm[0], []
1299
1300 loc = gdb_get_location_from_symbol(address)
1301 location = "<{}+{}>".format(*loc) if loc else ""
1302
1303 yield Instruction(address, location, mnemo, operands)
1304
1305
1306def gdb_get_nth_previous_instruction_address(addr, n):
1307 """Return the address (Integer) of the `n`-th instruction before `addr`."""
1308 # fixed-length ABI
1309 if current_arch.instruction_length:
1310 return addr - n*current_arch.instruction_length
1311
1312 # variable-length ABI
1313 cur_insn_addr = gef_current_instruction(addr).address
1314
1315 # we try to find a good set of previous instructions by "guessing" disassembling backwards
1316 # the 15 comes from the longest instruction valid size
1317 for i in range(15*n, 0, -1):
1318 try:
1319 insns = list(gdb_disassemble(addr-i, end_pc=cur_insn_addr, count=n+1))
1320 except gdb.MemoryError:
1321 # this is because we can hit an unmapped page trying to read backward
1322 break
1323
1324 # 1. check that the disassembled instructions list size is correct
1325 if len(insns)!=n+1: # we expect the current instruction plus the n before it
1326 continue
1327
1328 # 2. check all instructions are valid
1329 for insn in insns:
1330 if not insn.is_valid():
1331 continue
1332
1333 # 3. if cur_insn is at the end of the set
1334 if insns[-1].address==cur_insn_addr:
1335 return insns[0].address
1336
1337 return None
1338
1339
1340def gdb_get_nth_next_instruction_address(addr, n):
1341 """Return the address (Integer) of the `n`-th instruction after `addr`."""
1342 # fixed-length ABI
1343 if current_arch.instruction_length:
1344 return addr + n*current_arch.instruction_length
1345
1346 # variable-length ABI
1347 insn = list(gdb_disassemble(addr, count=n))[-1]
1348 return insn.address
1349
1350
1351def gef_instruction_n(addr, n):
1352 """Return the `n`-th instruction after `addr` as an Instruction object."""
1353 return list(gdb_disassemble(addr, count=n+1))[n]
1354
1355
1356def gef_get_instruction_at(addr):
1357 """Return the full Instruction found at the specified address."""
1358 insn = next(gef_disassemble(addr, 1))
1359 return insn
1360
1361
1362def gef_current_instruction(addr):
1363 """Return the current instruction as an Instruction object."""
1364 return gef_instruction_n(addr, 0)
1365
1366
1367def gef_next_instruction(addr):
1368 """Return the next instruction as an Instruction object."""
1369 return gef_instruction_n(addr, 1)
1370
1371
1372def gef_disassemble(addr, nb_insn, nb_prev=0):
1373 """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before `addr`.
1374 Return an iterator of Instruction objects."""
1375 count = nb_insn + 1 if nb_insn & 1 else nb_insn
1376
1377 if nb_prev:
1378 start_addr = gdb_get_nth_previous_instruction_address(addr, nb_prev)
1379 if start_addr:
1380 for insn in gdb_disassemble(start_addr, count=nb_prev):
1381 if insn.address == addr: break
1382 yield insn
1383
1384 for insn in gdb_disassemble(addr, count=count):
1385 yield insn
1386
1387
1388def capstone_disassemble(location, nb_insn, **kwargs):
1389 """Disassemble `nb_insn` instructions after `addr` and `nb_prev` before
1390 `addr` using the Capstone-Engine disassembler, if available.
1391 Return an iterator of Instruction objects."""
1392
1393 def cs_insn_to_gef_insn(cs_insn):
1394 sym_info = gdb_get_location_from_symbol(cs_insn.address)
1395 loc = "<{}+{}>".format(*sym_info) if sym_info else ""
1396 ops = [] + cs_insn.op_str.split(", ")
1397 return Instruction(cs_insn.address, loc, cs_insn.mnemonic, ops)
1398
1399 capstone = sys.modules["capstone"]
1400 arch, mode = get_capstone_arch(arch=kwargs.get("arch", None), mode=kwargs.get("mode", None), endian=kwargs.get("endian", None))
1401 cs = capstone.Cs(arch, mode)
1402 cs.detail = True
1403
1404 page_start = align_address_to_page(location)
1405 offset = location - page_start
1406 pc = current_arch.pc
1407
1408 skip = int(kwargs.get("skip", 0))
1409 nb_prev = int(kwargs.get("nb_prev", 0))
1410 if nb_prev > 0:
1411 location = gdb_get_nth_previous_instruction_address(pc, nb_prev)
1412 nb_insn += nb_prev
1413
1414 code = kwargs.get("code", read_memory(location, gef_getpagesize() - offset - 1))
1415 code = bytes(code)
1416
1417 for insn in cs.disasm(code, location):
1418 if skip:
1419 skip -= 1
1420 continue
1421 nb_insn -= 1
1422 yield cs_insn_to_gef_insn(insn)
1423 if nb_insn==0:
1424 break
1425 return
1426
1427
1428def gef_execute_external(command, as_list=False, *args, **kwargs):
1429 """Execute an external command and return the result."""
1430 res = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=kwargs.get("shell", False))
1431 return [gef_pystring(_) for _ in res.splitlines()] if as_list else gef_pystring(res)
1432
1433
1434def gef_execute_gdb_script(commands):
1435 """Execute the parameter `source` as GDB command. This is done by writing `commands` to
1436 a temporary file, which is then executed via GDB `source` command. The tempfile is then deleted."""
1437 fd, fname = tempfile.mkstemp(suffix=".gdb", prefix="gef_")
1438 with os.fdopen(fd, "w") as f:
1439 f.write(commands)
1440 f.flush()
1441 if os.access(fname, os.R_OK):
1442 gdb.execute("source {:s}".format(fname))
1443 os.unlink(fname)
1444 return
1445
1446
1447@lru_cache(32)
1448def checksec(filename):
1449 """Check the security property of the ELF binary. The following properties are:
1450 - Canary
1451 - NX
1452 - PIE
1453 - Fortify
1454 - Partial/Full RelRO.
1455 Return a dict() with the different keys mentioned above, and the boolean
1456 associated whether the protection was found."""
1457
1458 try:
1459 readelf = which("readelf")
1460 except IOError:
1461 err("Missing `readelf`")
1462 return
1463
1464 def __check_security_property(opt, filename, pattern):
1465 cmd = [readelf,]
1466 cmd += opt.split()
1467 cmd += [filename,]
1468 lines = gef_execute_external(cmd, as_list=True)
1469 for line in lines:
1470 if re.search(pattern, line):
1471 return True
1472 return False
1473
1474 results = collections.OrderedDict()
1475 results["Canary"] = __check_security_property("-s", filename, r"__stack_chk_fail") is True
1476 has_gnu_stack = __check_security_property("-W -l", filename, r"GNU_STACK") is True
1477 if has_gnu_stack:
1478 results["NX"] = __check_security_property("-W -l", filename, r"GNU_STACK.*RWE") is False
1479 else:
1480 results["NX"] = False
1481 results["PIE"] = __check_security_property("-h", filename, r":.*EXEC") is False
1482 results["Fortify"] = __check_security_property("-s", filename, r"_chk@GLIBC") is True
1483 results["Partial RelRO"] = __check_security_property("-l", filename, r"GNU_RELRO") is True
1484 results["Full RelRO"] = results["Partial RelRO"] and __check_security_property("-d", filename, r"BIND_NOW") is True
1485 return results
1486
1487
1488@lru_cache()
1489def get_arch():
1490 """Return the binary's architecture."""
1491 if is_alive():
1492 arch = gdb.selected_frame().architecture()
1493 return arch.name()
1494
1495 arch_str = gdb.execute("show architecture", to_string=True).strip()
1496 if "The target architecture is set automatically (currently " in arch_str:
1497 # architecture can be auto detected
1498 arch_str = arch_str.split("(currently ", 1)[1]
1499 arch_str = arch_str.split(")", 1)[0]
1500 elif "The target architecture is assumed to be " in arch_str:
1501 # architecture can be assumed
1502 arch_str = arch_str.replace("The target architecture is assumed to be ", "")
1503 else:
1504 # unknown, we throw an exception to be safe
1505 raise RuntimeError("Unknown architecture: {}".format(arch_str))
1506 return arch_str
1507
1508
1509@lru_cache()
1510def get_endian():
1511 """Return the binary endianness."""
1512 if is_alive():
1513 return get_elf_headers().e_endianness
1514 if gdb.execute("show endian", to_string=True).strip().split()[7] == "little" :
1515 return Elf.LITTLE_ENDIAN
1516 raise EnvironmentError("Invalid endianess")
1517
1518
1519def is_big_endian(): return get_endian() == Elf.BIG_ENDIAN
1520def is_little_endian(): return not is_big_endian()
1521
1522
1523def flags_to_human(reg_value, value_table):
1524 """Return a human readable string showing the flag states."""
1525 flags = []
1526 for i in value_table:
1527 flag_str = Color.boldify(value_table[i].upper()) if reg_value & (1<<i) else value_table[i].lower()
1528 flags.append(flag_str)
1529 return "[{}]".format(" ".join(flags))
1530
1531
1532class Architecture(object):
1533 """Generic metaclass for the architecture supported by GEF."""
1534 __metaclass__ = abc.ABCMeta
1535
1536 @abc.abstractproperty
1537 def all_registers(self): pass
1538 @abc.abstractproperty
1539 def instruction_length(self): pass
1540 @abc.abstractproperty
1541 def nop_insn(self): pass
1542 @abc.abstractproperty
1543 def return_register(self): pass
1544 @abc.abstractproperty
1545 def flag_register(self): pass
1546 @abc.abstractproperty
1547 def flags_table(self): pass
1548 @abc.abstractproperty
1549 def function_parameters(self): pass
1550 @abc.abstractmethod
1551 def flag_register_to_human(self, val=None): pass
1552 @abc.abstractmethod
1553 def is_call(self, insn): pass
1554 @abc.abstractmethod
1555 def is_ret(self, insn): pass
1556 @abc.abstractmethod
1557 def is_conditional_branch(self, insn): pass
1558 @abc.abstractmethod
1559 def is_branch_taken(self, insn): pass
1560 @abc.abstractmethod
1561 def get_ra(self, insn, frame): pass
1562
1563 special_registers = []
1564
1565 @property
1566 def pc(self):
1567 return get_register("$pc")
1568
1569 @property
1570 def sp(self):
1571 return get_register("$sp")
1572
1573 @property
1574 def fp(self):
1575 return get_register("$fp")
1576
1577 @property
1578 def ptrsize(self):
1579 return get_memory_alignment()
1580
1581 def get_ith_parameter(self, i, in_func=True):
1582 """Retrieves the correct parameter used for the current function call."""
1583 reg = self.function_parameters[i]
1584 val = get_register(reg)
1585 key = reg
1586 return key, val
1587
1588
1589class RISCV(Architecture):
1590 arch = "RISCV"
1591 mode = "RISCV"
1592
1593 all_registers = ["$zero", "$ra", "$sp", "$gp", "$x4", "$t0", "$t1",
1594 "$t2", "$fp", "$s1", "$a1", "$a2", "$a3", "$a4",
1595 "$a5", "$a6", "$a7", "$s2", "$s3", "$s4", "$s5",
1596 "$s6", "$s7", "$s8", "$s9", "$s10", "$s11", "$t3",
1597 "$t4", "$t5", "$t6",]
1598 return_register = "$a0"
1599 function_parameters = ["$a0", "$a1", "$a2", "$a3", "$a4", "$a5", "$a6", "$a7"]
1600 syscall_register = "$a7"
1601 syscall_register = "ecall"
1602 nop_insn = b"\x00\x00\x00\x13"
1603 # RISC-V has no flags registers
1604 flag_register = None
1605 flag_register_to_human = None
1606 flags_table = None
1607
1608 @property
1609 def instruction_length(self):
1610 return 4
1611
1612 def is_call(self, insn):
1613 return insn.mnemonic == "call"
1614
1615 def is_ret(self, insn):
1616 mnemo = insn.mnemonic
1617 if mnemo == "ret":
1618 return True
1619 elif (mnemo == "jalr" and insn.operands[0] == "zero" and
1620 insn.operands[1] == "ra" and insn.operands[2] == 0):
1621 return True
1622 elif (mnemo == "c.jalr" and insn.operands[0] == "ra"):
1623 return True
1624 return False
1625
1626 @classmethod
1627 def mprotect_asm(cls, addr, size, perm):
1628 raise OSError("Architecture {:s} not supported yet".format(cls.arch))
1629
1630 def is_conditional_branch(self, insn):
1631 return insn.mnemonic.startswith("b")
1632
1633 def is_branch_taken(self, insn):
1634 def long_to_twos_complement(v):
1635 """Convert a python long value to its two's complement."""
1636 if is_elf32():
1637 if v & 0x80000000:
1638 return v - 0x100000000
1639 elif is_elf64():
1640 if v & 0x8000000000000000:
1641 return v - 0x10000000000000000
1642 else:
1643 raise OSError("RISC-V: ELF file is not ELF32 or ELF64. This is not currently supported")
1644 return v
1645
1646 mnemo = insn.mnemonic
1647 condition = mnemo[1:]
1648
1649 if condition.endswith("z"):
1650 # r2 is the zero register if we are comparing to 0
1651 rs1 = get_register(insn.operands[0])
1652 rs2 = get_register("$zero")
1653 condition = condition[:-1]
1654 elif len(insn.operands) > 2:
1655 # r2 is populated with the second operand
1656 rs1 = get_register(insn.operands[0])
1657 rs2 = get_register(insn.operands[1])
1658 else:
1659 raise OSError("RISC-V: Failed to get rs1 and rs2 for instruction: `{}`".format(insn))
1660
1661 # If the conditional operation is not unsigned, convert the python long into
1662 # its two's complement
1663 if not condition.endswith("u"):
1664 rs2 = long_to_twos_complement(rs2)
1665 rs1 = long_to_twos_complement(rs1)
1666 else:
1667 condition = condition[:-1]
1668
1669 if condition == "eq":
1670 if rs1 == rs2: taken, reason = True, "{}={}".format(rs1, rs2)
1671 else: taken, reason = False, "{}!={}".format(rs1, rs2)
1672 elif condition == "ne":
1673 if rs1 != rs2: taken, reason = True, "{}!={}".format(rs1, rs2)
1674 else: taken, reason = False, "{}={}".format(rs1, rs2)
1675 elif condition == "lt":
1676 if rs1 < rs2: taken, reason = True, "{}<{}".format(rs1, rs2)
1677 else: taken, reason = False, "{}>={}".format(rs1, rs2)
1678 elif condition == "ge":
1679 if rs1 < rs2: taken, reason = True, "{}>={}".format(rs1, rs2)
1680 else: taken, reason = False, "{}<{}".format(rs1, rs2)
1681 else:
1682 raise OSError("RISC-V: Conditional instruction `{:s}` not supported yet".format(insn))
1683
1684 return taken, reason
1685
1686 def get_ra(self, insn, frame):
1687 ra = None
1688 if self.is_ret(insn):
1689 ra = get_register("$ra")
1690 elif frame.older():
1691 ra = frame.older().pc()
1692 return ra
1693
1694
1695class ARM(Architecture):
1696 arch = "ARM"
1697
1698 all_registers = ["$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6",
1699 "$r7", "$r8", "$r9", "$r10", "$r11", "$r12", "$sp",
1700 "$lr", "$pc", "$cpsr",]
1701
1702 # http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0041c/Caccegih.html
1703 # return b"\x00\x00\xa0\xe1" # mov r0,r0
1704 nop_insn = b"\x01\x10\xa0\xe1" # mov r1,r1
1705 return_register = "$r0"
1706 flag_register = "$cpsr"
1707 flags_table = {
1708 31: "negative",
1709 30: "zero",
1710 29: "carry",
1711 28: "overflow",
1712 7: "interrupt",
1713 6: "fast",
1714 5: "thumb"
1715 }
1716 function_parameters = ["$r0", "$r1", "$r2", "$r3"]
1717 syscall_register = "$r7"
1718 syscall_instructions = ["swi 0x0", "swi NR"]
1719
1720 @lru_cache()
1721 def is_thumb(self):
1722 """Determine if the machine is currently in THUMB mode."""
1723 return is_alive() and get_register("$cpsr") & (1<<5)
1724
1725 @property
1726 def pc(self):
1727 pc = get_register("$pc")
1728 if self.is_thumb():
1729 pc += 1
1730 return pc
1731
1732 @property
1733 def mode(self):
1734 return "THUMB" if self.is_thumb() else "ARM"
1735
1736 @property
1737 def instruction_length(self):
1738 # Thumb instructions have variable-length (2 or 4-byte)
1739 return None if self.is_thumb() else 4
1740
1741 def is_call(self, insn):
1742 mnemo = insn.mnemonic
1743 call_mnemos = {"bl", "blx"}
1744 return mnemo in call_mnemos
1745
1746 def is_ret(self, insn):
1747 pop_mnemos = {"pop"}
1748 branch_mnemos = {"bl", "bx"}
1749 write_mnemos = {"ldr", "add"}
1750 if insn.mnemonic in pop_mnemos:
1751 return insn.operands[-1] == " pc}"
1752 if insn.mnemonic in branch_mnemos:
1753 return insn.operands[-1] == "lr"
1754 if insn.mnemonic in write_mnemos:
1755 return insn.operands[0] == "pc"
1756 return
1757
1758 def flag_register_to_human(self, val=None):
1759 # http://www.botskool.com/user-pages/tutorials/electronics/arm-7-tutorial-part-1
1760 if val is None:
1761 reg = self.flag_register
1762 val = get_register(reg)
1763 return flags_to_human(val, self.flags_table)
1764
1765 def is_conditional_branch(self, insn):
1766 conditions = {"eq", "ne", "lt", "le", "gt", "ge", "vs", "vc", "mi", "pl", "hi", "ls"}
1767 return insn.mnemonic[-2:] in conditions
1768
1769 def is_branch_taken(self, insn):
1770 mnemo = insn.mnemonic
1771 # ref: http://www.davespace.co.uk/arm/introduction-to-arm/conditional.html
1772 flags = dict((self.flags_table[k], k) for k in self.flags_table)
1773 val = get_register(self.flag_register)
1774 taken, reason = False, ""
1775
1776 if mnemo.endswith("eq"): taken, reason = bool(val&(1<<flags["zero"])), "Z"
1777 elif mnemo.endswith("ne"): taken, reason = not val&(1<<flags["zero"]), "!Z"
1778 elif mnemo.endswith("lt"):
1779 taken, reason = bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "N!=V"
1780 elif mnemo.endswith("le"):
1781 taken, reason = val&(1<<flags["zero"]) or \
1782 bool(val&(1<<flags["negative"])) != bool(val&(1<<flags["overflow"])), "Z || N!=V"
1783 elif mnemo.endswith("gt"):
1784 taken, reason = val&(1<<flags["zero"]) == 0 and \
1785 bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "!Z && N==V"
1786 elif mnemo.endswith("ge"):
1787 taken, reason = bool(val&(1<<flags["negative"])) == bool(val&(1<<flags["overflow"])), "N==V"
1788 elif mnemo.endswith("vs"): taken, reason = bool(val&(1<<flags["overflow"])), "V"
1789 elif mnemo.endswith("vc"): taken, reason = not val&(1<<flags["overflow"]), "!V"
1790 elif mnemo.endswith("mi"):
1791 taken, reason = bool(val&(1<<flags["negative"])), "N"
1792 elif mnemo.endswith("pl"):
1793 taken, reason = not val&(1<<flags["negative"]), "N==0"
1794 elif mnemo.endswith("hi"):
1795 taken, reason = val&(1<<flags["carry"]) and not val&(1<<flags["zero"]), "C && !Z"
1796 elif mnemo.endswith("ls"):
1797 taken, reason = not val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "!C || Z"
1798 return taken, reason
1799
1800 def get_ra(self, insn, frame):
1801 ra = None
1802 if self.is_ret(insn):
1803 # If it's a pop, we have to peek into the stack, otherwise use lr
1804 if insn.mnemonic == "pop":
1805 ra_addr = current_arch.sp + (len(insn.operands)-1) * get_memory_alignment()
1806 ra = to_unsigned_long(dereference(ra_addr))
1807 elif insn.mnemonic == "ldr":
1808 return to_unsigned_long(dereference(current_arch.sp))
1809 else: # 'bx lr' or 'add pc, lr, #0'
1810 return get_register("$lr")
1811 elif frame.older():
1812 ra = frame.older().pc()
1813 return ra
1814
1815 @classmethod
1816 def mprotect_asm(cls, addr, size, perm):
1817 _NR_mprotect = 125
1818 insns = [
1819 "push {r0-r2, r7}",
1820 "mov r0, {:d}".format(addr),
1821 "mov r1, {:d}".format(size),
1822 "mov r2, {:d}".format(perm),
1823 "mov r7, {:d}".format(_NR_mprotect),
1824 "svc 0",
1825 "pop {r0-r2, r7}",]
1826 return "; ".join(insns)
1827
1828
1829class AARCH64(ARM):
1830 arch = "ARM64"
1831 mode = "ARM"
1832
1833 all_registers = [
1834 "$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7",
1835 "$x8", "$x9", "$x10", "$x11", "$x12", "$x13", "$x14", "$x15",
1836 "$x16", "$x17", "$x18", "$x19", "$x20", "$x21", "$x22", "$x23",
1837 "$x24", "$x25", "$x26", "$x27", "$x28", "$x29", "$x30", "$sp",
1838 "$pc", "$cpsr", "$fpsr", "$fpcr",]
1839 return_register = "$x0"
1840 flag_register = "$cpsr"
1841 flags_table = {
1842 31: "negative",
1843 30: "zero",
1844 29: "carry",
1845 28: "overflow",
1846 7: "interrupt",
1847 6: "fast"
1848 }
1849 function_parameters = ["$x0", "$x1", "$x2", "$x3", "$x4", "$x5", "$x6", "$x7"]
1850 syscall_register = "$x8"
1851 syscall_instructions = ["svc $x0"]
1852
1853 def is_call(self, insn):
1854 mnemo = insn.mnemonic
1855 call_mnemos = {"bl", "blr"}
1856 return mnemo in call_mnemos
1857
1858 def flag_register_to_human(self, val=None):
1859 # http://events.linuxfoundation.org/sites/events/files/slides/KoreaLinuxForum-2014.pdf
1860 reg = self.flag_register
1861 if not val:
1862 val = get_register(reg)
1863 return flags_to_human(val, self.flags_table)
1864
1865 @classmethod
1866 def mprotect_asm(cls, addr, size, perm):
1867 raise OSError("Architecture {:s} not supported yet".format(cls.arch))
1868
1869 def is_conditional_branch(self, insn):
1870 # https://www.element14.com/community/servlet/JiveServlet/previewBody/41836-102-1-229511/ARM.Reference_Manual.pdf
1871 # sect. 5.1.1
1872 mnemo = insn.mnemonic
1873 branch_mnemos = {"cbnz", "cbz", "tbnz", "tbz"}
1874 return mnemo.startswith("b.") or mnemo in branch_mnemos
1875
1876 def is_branch_taken(self, insn):
1877 mnemo, operands = insn.mnemonic, insn.operands
1878 flags = dict((self.flags_table[k], k) for k in self.flags_table)
1879 val = get_register(self.flag_register)
1880 taken, reason = False, ""
1881
1882 if mnemo in {"cbnz", "cbz", "tbnz", "tbz"}:
1883 reg = operands[0]
1884 op = get_register(reg)
1885 if mnemo=="cbnz":
1886 if op!=0: taken, reason = True, "{}!=0".format(reg)
1887 else: taken, reason = False, "{}==0".format(reg)
1888 elif mnemo=="cbz":
1889 if op==0: taken, reason = True, "{}==0".format(reg)
1890 else: taken, reason = False, "{}!=0".format(reg)
1891 elif mnemo=="tbnz":
1892 # operands[1] has one or more white spaces in front, then a #, then the number
1893 # so we need to eliminate them
1894 i = int(operands[1].strip().lstrip("#"))
1895 if (op & 1<<i) != 0: taken, reason = True, "{}&1<<{}!=0".format(reg,i)
1896 else: taken, reason = False, "{}&1<<{}==0".format(reg,i)
1897 elif mnemo=="tbz":
1898 # operands[1] has one or more white spaces in front, then a #, then the number
1899 # so we need to eliminate them
1900 i = int(operands[1].strip().lstrip("#"))
1901 if (op & 1<<i) == 0: taken, reason = True, "{}&1<<{}==0".format(reg,i)
1902 else: taken, reason = False, "{}&1<<{}!=0".format(reg,i)
1903
1904 if not reason:
1905 taken, reason = super(AARCH64, self).is_branch_taken(insn)
1906 return taken, reason
1907
1908
1909class X86(Architecture):
1910 arch = "X86"
1911 mode = "32"
1912
1913 nop_insn = b"\x90"
1914 flag_register = "$eflags"
1915 special_registers = ["$cs", "$ss", "$ds", "$es", "$fs", "$gs", ]
1916 gpr_registers = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", ]
1917 all_registers = gpr_registers + [ flag_register, ] + special_registers
1918 instruction_length = None
1919 return_register = "$eax"
1920 function_parameters = ["$esp", ]
1921 flags_table = {
1922 6: "zero",
1923 0: "carry",
1924 2: "parity",
1925 4: "adjust",
1926 7: "sign",
1927 8: "trap",
1928 9: "interrupt",
1929 10: "direction",
1930 11: "overflow",
1931 16: "resume",
1932 17: "virtualx86",
1933 21: "identification",
1934 }
1935 syscall_register = "$eax"
1936 syscall_instructions = ["sysenter", "int 0x80"]
1937
1938 def flag_register_to_human(self, val=None):
1939 reg = self.flag_register
1940 if not val:
1941 val = get_register(reg)
1942 return flags_to_human(val, self.flags_table)
1943
1944 def is_call(self, insn):
1945 mnemo = insn.mnemonic
1946 call_mnemos = {"call", "callq"}
1947 return mnemo in call_mnemos
1948
1949 def is_ret(self, insn):
1950 return insn.mnemonic == "ret"
1951
1952 def is_conditional_branch(self, insn):
1953 mnemo = insn.mnemonic
1954 branch_mnemos = {
1955 "ja", "jnbe", "jae", "jnb", "jnc", "jb", "jc", "jnae", "jbe", "jna",
1956 "jcxz", "jecxz", "jrcxz", "je", "jz", "jg", "jnle", "jge", "jnl",
1957 "jl", "jnge", "jle", "jng", "jne", "jnz", "jno", "jnp", "jpo", "jns",
1958 "jo", "jp", "jpe", "js"
1959 }
1960 return mnemo in branch_mnemos
1961
1962 def is_branch_taken(self, insn):
1963 mnemo = insn.mnemonic
1964 # all kudos to fG! (https://github.com/gdbinit/Gdbinit/blob/master/gdbinit#L1654)
1965 flags = dict((self.flags_table[k], k) for k in self.flags_table)
1966 val = get_register(self.flag_register)
1967
1968 taken, reason = False, ""
1969
1970 if mnemo in ("ja", "jnbe"):
1971 taken, reason = not val&(1<<flags["carry"]) and not val&(1<<flags["zero"]), "!C && !Z"
1972 elif mnemo in ("jae", "jnb", "jnc"):
1973 taken, reason = not val&(1<<flags["carry"]), "!C"
1974 elif mnemo in ("jb", "jc", "jnae"):
1975 taken, reason = val&(1<<flags["carry"]), "C"
1976 elif mnemo in ("jbe", "jna"):
1977 taken, reason = val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "C || Z"
1978 elif mnemo in ("jcxz", "jecxz", "jrcxz"):
1979 cx = get_register("$rcx") if self.mode == 64 else get_register("$ecx")
1980 taken, reason = cx == 0, "!$CX"
1981 elif mnemo in ("je", "jz"):
1982 taken, reason = val&(1<<flags["zero"]), "Z"
1983 elif mnemo in ("jne", "jnz"):
1984 taken, reason = not val&(1<<flags["zero"]), "!Z"
1985 elif mnemo in ("jg", "jnle"):
1986 taken, reason = not val&(1<<flags["zero"]) and bool(val&(1<<flags["overflow"])) == bool(val&(1<<flags["sign"])), "!Z && S==O"
1987 elif mnemo in ("jge", "jnl"):
1988 taken, reason = bool(val&(1<<flags["sign"])) == bool(val&(1<<flags["overflow"])), "S==O"
1989 elif mnemo in ("jl", "jnge"):
1990 taken, reason = val&(1<<flags["overflow"]) != val&(1<<flags["sign"]), "S!=O"
1991 elif mnemo in ("jle", "jng"):
1992 taken, reason = val&(1<<flags["zero"]) or bool(val&(1<<flags["overflow"])) != bool(val&(1<<flags["sign"])), "Z || S!=O"
1993 elif mnemo in ("jo",):
1994 taken, reason = val&(1<<flags["overflow"]), "O"
1995 elif mnemo in ("jno",):
1996 taken, reason = not val&(1<<flags["overflow"]), "!O"
1997 elif mnemo in ("jpe", "jp"):
1998 taken, reason = val&(1<<flags["parity"]), "P"
1999 elif mnemo in ("jnp", "jpo"):
2000 taken, reason = not val&(1<<flags["parity"]), "!P"
2001 elif mnemo in ("js",):
2002 taken, reason = val&(1<<flags["sign"]), "S"
2003 elif mnemo in ("jns",):
2004 taken, reason = not val&(1<<flags["sign"]), "!S"
2005 return taken, reason
2006
2007 def get_ra(self, insn, frame):
2008 ra = None
2009 if self.is_ret(insn):
2010 ra = to_unsigned_long(dereference(current_arch.sp))
2011 if frame.older():
2012 ra = frame.older().pc()
2013
2014 return ra
2015
2016 @classmethod
2017 def mprotect_asm(cls, addr, size, perm):
2018 _NR_mprotect = 125
2019 insns = [
2020 "pushad",
2021 "mov eax, {:d}".format(_NR_mprotect),
2022 "mov ebx, {:d}".format(addr),
2023 "mov ecx, {:d}".format(size),
2024 "mov edx, {:d}".format(perm),
2025 "int 0x80",
2026 "popad",]
2027 return "; ".join(insns)
2028
2029 def get_ith_parameter(self, i, in_func=True):
2030 if in_func:
2031 i += 1 # Account for RA being at the top of the stack
2032 sp = current_arch.sp
2033 sz = current_arch.ptrsize
2034 loc = sp + (i * sz)
2035 val = read_int_from_memory(loc)
2036 key = "[sp + {:#x}]".format(i * sz)
2037 return key, val
2038
2039
2040class X86_64(X86):
2041 arch = "X86"
2042 mode = "64"
2043
2044 gpr_registers = [
2045 "$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip",
2046 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", ]
2047 all_registers = gpr_registers + [ X86.flag_register, ] + X86.special_registers
2048 return_register = "$rax"
2049 function_parameters = ["$rdi", "$rsi", "$rdx", "$rcx", "$r8", "$r9"]
2050 syscall_register = "$rax"
2051 syscall_instructions = ["syscall"]
2052 # We don't want to inherit x86's stack based param getter
2053 get_ith_parameter = Architecture.get_ith_parameter
2054
2055 @classmethod
2056 def mprotect_asm(cls, addr, size, perm):
2057 _NR_mprotect = 10
2058 insns = ["push rax", "push rdi", "push rsi", "push rdx",
2059 "mov rax, {:d}".format(_NR_mprotect),
2060 "mov rdi, {:d}".format(addr),
2061 "mov rsi, {:d}".format(size),
2062 "mov rdx, {:d}".format(perm),
2063 "syscall",
2064 "pop rdx", "pop rsi", "pop rdi", "pop rax"]
2065 return "; ".join(insns)
2066
2067
2068class PowerPC(Architecture):
2069 arch = "PPC"
2070 mode = "PPC32"
2071
2072 all_registers = [
2073 "$r0", "$r1", "$r2", "$r3", "$r4", "$r5", "$r6", "$r7",
2074 "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15",
2075 "$r16", "$r17", "$r18", "$r19", "$r20", "$r21", "$r22", "$r23",
2076 "$r24", "$r25", "$r26", "$r27", "$r28", "$r29", "$r30", "$r31",
2077 "$pc", "$msr", "$cr", "$lr", "$ctr", "$xer", "$trap",]
2078 instruction_length = 4
2079 nop_insn = b"\x60\x00\x00\x00" # http://www.ibm.com/developerworks/library/l-ppc/index.html
2080 return_register = "$r0"
2081 flag_register = "$cr"
2082 flags_table = {
2083 3: "negative[0]",
2084 2: "positive[0]",
2085 1: "equal[0]",
2086 0: "overflow[0]",
2087
2088 # cr7
2089 31: "less[7]",
2090 30: "greater[7]",
2091 29: "equal[7]",
2092 28: "overflow[7]",
2093 }
2094 function_parameters = ["$i0", "$i1", "$i2", "$i3", "$i4", "$i5"]
2095 syscall_register = "$r0"
2096 syscall_instructions = ["sc"]
2097
2098 def flag_register_to_human(self, val=None):
2099 # http://www.cebix.net/downloads/bebox/pem32b.pdf (% 2.1.3)
2100 if not val:
2101 reg = self.flag_register
2102 val = get_register(reg)
2103 return flags_to_human(val, self.flags_table)
2104
2105 def is_call(self, insn):
2106 return False
2107
2108 def is_ret(self, insn):
2109 return insn.mnemonic == "blr"
2110
2111 def is_conditional_branch(self, insn):
2112 mnemo = insn.mnemonic
2113 branch_mnemos = {"beq", "bne", "ble", "blt", "bgt", "bge"}
2114 return mnemo in branch_mnemos
2115
2116 def is_branch_taken(self, insn):
2117 mnemo = insn.mnemonic
2118 flags = dict((self.flags_table[k], k) for k in self.flags_table)
2119 val = get_register(self.flag_register)
2120 taken, reason = False, ""
2121 if mnemo == "beq": taken, reason = val&(1<<flags["equal[7]"]), "E"
2122 elif mnemo == "bne": taken, reason = val&(1<<flags["equal[7]"]) == 0, "!E"
2123 elif mnemo == "ble": taken, reason = val&(1<<flags["equal[7]"]) or val&(1<<flags["less[7]"]), "E || L"
2124 elif mnemo == "blt": taken, reason = val&(1<<flags["less[7]"]), "L"
2125 elif mnemo == "bge": taken, reason = val&(1<<flags["equal[7]"]) or val&(1<<flags["greater[7]"]), "E || G"
2126 elif mnemo == "bgt": taken, reason = val&(1<<flags["greater[7]"]), "G"
2127 return taken, reason
2128
2129 def get_ra(self, insn, frame):
2130 ra = None
2131 if self.is_ret(insn):
2132 ra = get_register("$lr")
2133 elif frame.older():
2134 ra = frame.older().pc()
2135 return ra
2136
2137 @classmethod
2138 def mprotect_asm(cls, addr, size, perm):
2139 # Ref: http://www.ibm.com/developerworks/library/l-ppc/index.html
2140 _NR_mprotect = 125
2141 insns = ["addi 1, 1, -16", # 1 = r1 = sp
2142 "stw 0, 0(1)", "stw 3, 4(1)", # r0 = syscall_code | r3, r4, r5 = args
2143 "stw 4, 8(1)", "stw 5, 12(1)",
2144 "li 0, {:d}".format(_NR_mprotect),
2145 "lis 3, {:#x}@h".format(addr),
2146 "ori 3, 3, {:#x}@l".format(addr),
2147 "lis 4, {:#x}@h".format(size),
2148 "ori 4, 4, {:#x}@l".format(size),
2149 "li 5, {:d}".format(perm),
2150 "sc",
2151 "lwz 0, 0(1)", "lwz 3, 4(1)",
2152 "lwz 4, 8(1)", "lwz 5, 12(1)",
2153 "addi 1, 1, 16",]
2154 return ";".join(insns)
2155
2156
2157class PowerPC64(PowerPC):
2158 arch = "PPC"
2159 mode = "PPC64"
2160
2161
2162class SPARC(Architecture):
2163 """ Refs:
2164 - http://www.cse.scu.edu/~atkinson/teaching/sp05/259/sparc.pdf
2165 """
2166 arch = "SPARC"
2167 mode = ""
2168
2169 all_registers = [
2170 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7",
2171 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7",
2172 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7",
2173 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7",
2174 "$pc", "$npc","$sp ","$fp ","$psr",]
2175 instruction_length = 4
2176 nop_insn = b"\x00\x00\x00\x00" # sethi 0, %g0
2177 return_register = "$i0"
2178 flag_register = "$psr"
2179 flags_table = {
2180 23: "negative",
2181 22: "zero",
2182 21: "overflow",
2183 20: "carry",
2184 7: "supervisor",
2185 5: "trap",
2186 }
2187 function_parameters = ["$o0 ", "$o1 ", "$o2 ", "$o3 ", "$o4 ", "$o5 ", "$o7 ",]
2188 syscall_register = "%g1"
2189 syscall_instructions = ["t 0x10"]
2190
2191 def flag_register_to_human(self, val=None):
2192 # http://www.gaisler.com/doc/sparcv8.pdf
2193 reg = self.flag_register
2194 if not val:
2195 val = get_register(reg)
2196 return flags_to_human(val, self.flags_table)
2197
2198 def is_call(self, insn):
2199 return False
2200
2201 def is_ret(self, insn):
2202 return insn.mnemonic == "ret"
2203
2204 def is_conditional_branch(self, insn):
2205 mnemo = insn.mnemonic
2206 # http://moss.csc.ncsu.edu/~mueller/codeopt/codeopt00/notes/condbranch.html
2207 branch_mnemos = {
2208 "be", "bne", "bg", "bge", "bgeu", "bgu", "bl", "ble", "blu", "bleu",
2209 "bneg", "bpos", "bvs", "bvc", "bcs", "bcc"
2210 }
2211 return mnemo in branch_mnemos
2212
2213 def is_branch_taken(self, insn):
2214 mnemo = insn.mnemonic
2215 flags = dict((self.flags_table[k], k) for k in self.flags_table)
2216 val = get_register(self.flag_register)
2217 taken, reason = False, ""
2218
2219 if mnemo == "be": taken, reason = val&(1<<flags["zero"]), "Z"
2220 elif mnemo == "bne": taken, reason = val&(1<<flags["zero"]) == 0, "!Z"
2221 elif mnemo == "bg": taken, reason = val&(1<<flags["zero"]) == 0 and (val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0), "!Z && (!N || !O)"
2222 elif mnemo == "bge": taken, reason = val&(1<<flags["negative"]) == 0 or val&(1<<flags["overflow"]) == 0, "!N || !O"
2223 elif mnemo == "bgu": taken, reason = val&(1<<flags["carry"]) == 0 and val&(1<<flags["zero"]) == 0, "!C && !Z"
2224 elif mnemo == "bgeu": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
2225 elif mnemo == "bl": taken, reason = val&(1<<flags["negative"]) and val&(1<<flags["overflow"]), "N && O"
2226 elif mnemo == "blu": taken, reason = val&(1<<flags["carry"]), "C"
2227 elif mnemo == "ble": taken, reason = val&(1<<flags["zero"]) or (val&(1<<flags["negative"]) or val&(1<<flags["overflow"])), "Z || (N || O)"
2228 elif mnemo == "bleu": taken, reason = val&(1<<flags["carry"]) or val&(1<<flags["zero"]), "C || Z"
2229 elif mnemo == "bneg": taken, reason = val&(1<<flags["negative"]), "N"
2230 elif mnemo == "bpos": taken, reason = val&(1<<flags["negative"]) == 0, "!N"
2231 elif mnemo == "bvs": taken, reason = val&(1<<flags["overflow"]), "O"
2232 elif mnemo == "bvc": taken, reason = val&(1<<flags["overflow"]) == 0, "!O"
2233 elif mnemo == "bcs": taken, reason = val&(1<<flags["carry"]), "C"
2234 elif mnemo == "bcc": taken, reason = val&(1<<flags["carry"]) == 0, "!C"
2235 return taken, reason
2236
2237 def get_ra(self, insn, frame):
2238 ra = None
2239 if self.is_ret(insn):
2240 ra = get_register("$o7")
2241 elif frame.older():
2242 ra = frame.older().pc()
2243 return ra
2244
2245 @classmethod
2246 def mprotect_asm(cls, addr, size, perm):
2247 hi = (addr & 0xffff0000) >> 16
2248 lo = (addr & 0x0000ffff)
2249 _NR_mprotect = 125
2250 insns = ["add %sp, -16, %sp",
2251 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]",
2252 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]",
2253 "sethi %hi({}), %o0".format(hi),
2254 "or %o0, {}, %o0".format(lo),
2255 "clr %o1",
2256 "clr %o2",
2257 "mov {}, %g1".format(_NR_mprotect),
2258 "t 0x10",
2259 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
2260 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
2261 "add %sp, 16, %sp",]
2262 return "; ".join(insns)
2263
2264
2265class SPARC64(SPARC):
2266 """ Refs:
2267 - http://math-atlas.sourceforge.net/devel/assembly/abi_sysV_sparc.pdf
2268 - https://cr.yp.to/2005-590/sparcv9.pdf
2269 """
2270 arch = "SPARC"
2271 mode = "V9"
2272
2273 all_registers = [
2274 "$g0", "$g1", "$g2", "$g3", "$g4", "$g5", "$g6", "$g7",
2275 "$o0", "$o1", "$o2", "$o3", "$o4", "$o5", "$o7",
2276 "$l0", "$l1", "$l2", "$l3", "$l4", "$l5", "$l6", "$l7",
2277 "$i0", "$i1", "$i2", "$i3", "$i4", "$i5", "$i7",
2278 "$pc", "$npc", "$sp", "$fp", "$state", ]
2279
2280 flag_register = "$state" # sparcv9.pdf, 5.1.5.1 (ccr)
2281 flags_table = {
2282 35: "negative",
2283 34: "zero",
2284 33: "overflow",
2285 32: "carry",
2286 }
2287
2288 syscall_instructions = ["t 0x6d"]
2289
2290 @classmethod
2291 def mprotect_asm(cls, addr, size, perm):
2292 hi = (addr & 0xffff0000) >> 16
2293 lo = (addr & 0x0000ffff)
2294 _NR_mprotect = 125
2295 insns = ["add %sp, -16, %sp",
2296 "st %g1, [ %sp ]", "st %o0, [ %sp + 4 ]",
2297 "st %o1, [ %sp + 8 ]", "st %o2, [ %sp + 12 ]",
2298 "sethi %hi({}), %o0".format(hi),
2299 "or %o0, {}, %o0".format(lo),
2300 "clr %o1",
2301 "clr %o2",
2302 "mov {}, %g1".format(_NR_mprotect),
2303 "t 0x6d",
2304 "ld [ %sp ], %g1", "ld [ %sp + 4 ], %o0",
2305 "ld [ %sp + 8 ], %o1", "ld [ %sp + 12 ], %o2",
2306 "add %sp, 16, %sp",]
2307 return "; ".join(insns)
2308
2309
2310class MIPS(Architecture):
2311 arch = "MIPS"
2312 mode = "MIPS32"
2313
2314 # http://vhouten.home.xs4all.nl/mipsel/r3000-isa.html
2315 all_registers = [
2316 "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
2317 "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
2318 "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
2319 "$t8", "$t9", "$k0", "$k1", "$s8", "$pc", "$sp", "$hi",
2320 "$lo", "$fir", "$ra", "$gp", ]
2321 instruction_length = 4
2322 nop_insn = b"\x00\x00\x00\x00" # sll $0,$0,0
2323 return_register = "$v0"
2324 flag_register = "$fcsr"
2325 flags_table = {}
2326 function_parameters = ["$a0", "$a1", "$a2", "$a3"]
2327 syscall_register = "$v0"
2328 syscall_instructions = ["syscall"]
2329
2330 def flag_register_to_human(self, val=None):
2331 return Color.colorify("No flag register", "yellow underline")
2332
2333 def is_call(self, insn):
2334 return False
2335
2336 def is_ret(self, insn):
2337 return insn.mnemonic == "jr" and insn.operands[0] == "ra"
2338
2339 def is_conditional_branch(self, insn):
2340 mnemo = insn.mnemonic
2341 branch_mnemos = {"beq", "bne", "beqz", "bnez", "bgtz", "bgez", "bltz", "blez"}
2342 return mnemo in branch_mnemos
2343
2344 def is_branch_taken(self, insn):
2345 mnemo, ops = insn.mnemonic, insn.operands
2346 taken, reason = False, ""
2347
2348 if mnemo == "beq":
2349 taken, reason = get_register(ops[0]) == get_register(ops[1]), "{0[0]} == {0[1]}".format(ops)
2350 elif mnemo == "bne":
2351 taken, reason = get_register(ops[0]) != get_register(ops[1]), "{0[0]} != {0[1]}".format(ops)
2352 elif mnemo == "beqz":
2353 taken, reason = get_register(ops[0]) == 0, "{0[0]} == 0".format(ops)
2354 elif mnemo == "bnez":
2355 taken, reason = get_register(ops[0]) != 0, "{0[0]} != 0".format(ops)
2356 elif mnemo == "bgtz":
2357 taken, reason = get_register(ops[0]) > 0, "{0[0]} > 0".format(ops)
2358 elif mnemo == "bgez":
2359 taken, reason = get_register(ops[0]) >= 0, "{0[0]} >= 0".format(ops)
2360 elif mnemo == "bltz":
2361 taken, reason = get_register(ops[0]) < 0, "{0[0]} < 0".format(ops)
2362 elif mnemo == "blez":
2363 taken, reason = get_register(ops[0]) <= 0, "{0[0]} <= 0".format(ops)
2364 return taken, reason
2365
2366 def get_ra(self, insn, frame):
2367 ra = None
2368 if self.is_ret(insn):
2369 ra = get_register("$ra")
2370 elif frame.older():
2371 ra = frame.older().pc()
2372 return ra
2373
2374 @classmethod
2375 def mprotect_asm(cls, addr, size, perm):
2376 _NR_mprotect = 4125
2377 insns = ["addi $sp, $sp, -16",
2378 "sw $v0, 0($sp)", "sw $a0, 4($sp)",
2379 "sw $a3, 8($sp)", "sw $a3, 12($sp)",
2380 "li $v0, {:d}".format(_NR_mprotect),
2381 "li $a0, {:d}".format(addr),
2382 "li $a1, {:d}".format(size),
2383 "li $a2, {:d}".format(perm),
2384 "syscall",
2385 "lw $v0, 0($sp)", "lw $a1, 4($sp)",
2386 "lw $a3, 8($sp)", "lw $a3, 12($sp)",
2387 "addi $sp, $sp, 16",]
2388 return "; ".join(insns)
2389
2390
2391def write_memory(address, buffer, length=0x10):
2392 """Write `buffer` at address `address`."""
2393 if PYTHON_MAJOR == 2: buffer = str(buffer)
2394 return gdb.selected_inferior().write_memory(address, buffer, length)
2395
2396
2397def read_memory(addr, length=0x10):
2398 """Return a `length` long byte array with the copy of the process memory at `addr`."""
2399 if PYTHON_MAJOR == 2:
2400 return gdb.selected_inferior().read_memory(addr, length)
2401
2402 return gdb.selected_inferior().read_memory(addr, length).tobytes()
2403
2404
2405def read_int_from_memory(addr):
2406 """Return an integer read from memory."""
2407 sz = current_arch.ptrsize
2408 mem = read_memory(addr, sz)
2409 fmt = "{}{}".format(endian_str(), "I" if sz==4 else "Q")
2410 return struct.unpack(fmt, mem)[0]
2411
2412
2413def read_cstring_from_memory(address, max_length=GEF_MAX_STRING_LENGTH, encoding=None):
2414 """Return a C-string read from memory."""
2415
2416 if not encoding:
2417 encoding = "unicode_escape" if PYTHON_MAJOR==3 else "ascii"
2418
2419 char_ptr = cached_lookup_type("char").pointer()
2420
2421 length = min(address|(DEFAULT_PAGE_SIZE-1), max_length+1)
2422 try:
2423 res = gdb.Value(address).cast(char_ptr).string(encoding=encoding, length=length).strip()
2424 except gdb.error:
2425 res = bytes(read_memory(address, length)).decode("utf-8")
2426
2427 res = res.split("\x00", 1)[0]
2428 ustr = res.replace("\n","\\n").replace("\r","\\r").replace("\t","\\t")
2429 if max_length and len(res) > max_length:
2430 return "{}[...]".format(ustr[:max_length])
2431
2432 return ustr
2433
2434
2435def read_ascii_string(address):
2436 """Read an ASCII string from memory"""
2437 cstr = read_cstring_from_memory(address)
2438 if isinstance(cstr, unicode) and cstr and all([x in string.printable for x in cstr]):
2439 return cstr
2440 return None
2441
2442
2443def is_ascii_string(address):
2444 """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)"""
2445 try:
2446 return read_ascii_string(address) is not None
2447 except Exception:
2448 return False
2449
2450
2451def is_alive():
2452 """Check if GDB is running."""
2453 try:
2454 return gdb.selected_inferior().pid > 0
2455 except Exception:
2456 return False
2457 return False
2458
2459
2460def only_if_gdb_running(f):
2461 """Decorator wrapper to check if GDB is running."""
2462 @functools.wraps(f)
2463 def wrapper(*args, **kwargs):
2464 if is_alive():
2465 return f(*args, **kwargs)
2466 else:
2467 warn("No debugging session active")
2468 return wrapper
2469
2470
2471def only_if_gdb_target_local(f):
2472 """Decorator wrapper to check if GDB is running locally (target not remote)."""
2473 @functools.wraps(f)
2474 def wrapper(*args, **kwargs):
2475 if not is_remote_debug():
2476 return f(*args, **kwargs)
2477 else:
2478 warn("This command cannot work for remote sessions.")
2479 return wrapper
2480
2481
2482def experimental_feature(f):
2483 """Decorator to add a warning when a feature is experimental."""
2484 @functools.wraps(f)
2485 def wrapper(*args, **kwargs):
2486 warn("This feature is under development, expect bugs and unstability...")
2487 return f(*args, **kwargs)
2488 return wrapper
2489
2490
2491def only_if_gdb_version_higher_than(required_gdb_version):
2492 """Decorator to check whether current GDB version requirements."""
2493 def wrapper(f):
2494 def inner_f(*args, **kwargs):
2495 if GDB_VERSION >= required_gdb_version:
2496 f(*args, **kwargs)
2497 else:
2498 reason = "GDB >= {} for this command".format(required_gdb_version)
2499 raise EnvironmentError(reason)
2500 return inner_f
2501 return wrapper
2502
2503
2504def use_stdtype():
2505 if is_elf32(): return "uint32_t"
2506 elif is_elf64(): return "uint64_t"
2507 return "uint16_t"
2508
2509
2510def use_default_type():
2511 if is_elf32(): return "unsigned int"
2512 elif is_elf64(): return "unsigned long"
2513 return "unsigned short"
2514
2515
2516def use_golang_type():
2517 if is_elf32(): return "uint32"
2518 elif is_elf64(): return "uint64"
2519 return "uint16"
2520
2521
2522def to_unsigned_long(v):
2523 """Cast a gdb.Value to unsigned long."""
2524 mask = (1 << 64) - 1
2525 return int(v.cast(gdb.Value(mask).type)) & mask
2526
2527
2528def get_register(regname):
2529 """Return a register's value."""
2530 try:
2531 value = gdb.parse_and_eval(regname)
2532 return to_unsigned_long(value) if value.type.code == gdb.TYPE_CODE_INT else long(value)
2533 except gdb.error:
2534 assert(regname[0] == '$')
2535 regname = regname[1:]
2536 try:
2537 value = gdb.selected_frame().read_register(regname)
2538 except ValueError:
2539 return None
2540
2541 return long(value)
2542
2543
2544def get_path_from_info_proc():
2545 for x in gdb.execute("info proc", to_string=True).splitlines():
2546 if x.startswith("exe = "):
2547 return x.split(" = ")[1].replace("'", "")
2548 return None
2549
2550
2551@lru_cache()
2552def get_os():
2553 """Return the current OS."""
2554 return platform.system().lower()
2555
2556
2557@lru_cache()
2558def get_pid():
2559 """Return the PID of the debuggee process."""
2560 return gdb.selected_inferior().pid
2561
2562
2563@lru_cache()
2564def get_filepath():
2565 """Return the local absolute path of the file currently debugged."""
2566 filename = gdb.current_progspace().filename
2567
2568 if is_remote_debug():
2569 # if no filename specified, try downloading target from /proc
2570 if filename is None:
2571 pid = get_pid()
2572 if pid > 0:
2573 return download_file("/proc/{:d}/exe".format(pid), use_cache=True)
2574 return None
2575
2576 # if target is remote file, download
2577 elif filename.startswith("target:"):
2578 fname = filename[len("target:"):]
2579 return download_file(fname, use_cache=True, local_name=fname)
2580
2581 elif __gef_remote__ is not None:
2582 return "/tmp/gef/{:d}/{:s}".format(__gef_remote__, get_path_from_info_proc())
2583 return filename
2584 else:
2585 if filename is not None:
2586 return filename
2587 # inferior probably did not have name, extract cmdline from info proc
2588 return get_path_from_info_proc()
2589
2590
2591@lru_cache()
2592def get_filename():
2593 """Return the full filename of the file currently debugged."""
2594 return os.path.basename(get_filepath())
2595
2596
2597def download_file(target, use_cache=False, local_name=None):
2598 """Download filename `target` inside the mirror tree inside the GEF_TEMP_DIR.
2599 The tree architecture must be GEF_TEMP_DIR/gef/<local_pid>/<remote_filepath>.
2600 This allow a "chroot-like" tree format."""
2601
2602 try:
2603 local_root = os.path.sep.join([GEF_TEMP_DIR, str(get_pid())])
2604 if local_name is None:
2605 local_path = os.path.sep.join([local_root, os.path.dirname(target)])
2606 local_name = os.path.sep.join([local_path, os.path.basename(target)])
2607 else:
2608 local_path = os.path.sep.join([local_root, os.path.dirname(local_name)])
2609 local_name = os.path.sep.join([local_path, os.path.basename(local_name)])
2610
2611 if use_cache and os.access(local_name, os.R_OK):
2612 return local_name
2613
2614 gef_makedirs(local_path)
2615 gdb.execute("remote get {0:s} {1:s}".format(target, local_name))
2616
2617 except gdb.error:
2618 # gdb-stub compat
2619 with open(local_name, "w") as f:
2620 if is_elf32():
2621 f.write("00000000-ffffffff rwxp 00000000 00:00 0 {}\n".format(get_filepath()))
2622 else:
2623 f.write("0000000000000000-ffffffffffffffff rwxp 00000000 00:00 0 {}\n".format(get_filepath()))
2624
2625 except Exception as e:
2626 err("download_file() failed: {}".format(str(e)))
2627 local_name = None
2628 return local_name
2629
2630
2631def open_file(path, use_cache=False):
2632 """Attempt to open the given file, if remote debugging is active, download
2633 it first to the mirror in /tmp/."""
2634 if is_remote_debug():
2635 lpath = download_file(path, use_cache)
2636 if not lpath:
2637 raise IOError("cannot open remote path {:s}".format(path))
2638 path = lpath
2639
2640 return open(path, "r")
2641
2642
2643def get_function_length(sym):
2644 """Attempt to get the length of the raw bytes of a function."""
2645 dis = gdb.execute("disassemble {:s}".format(sym), to_string=True).splitlines()
2646 start_addr = int(dis[1].split()[0], 16)
2647 end_addr = int(dis[-2].split()[0], 16)
2648 return end_addr - start_addr
2649
2650
2651def get_process_maps_linux(proc_map_file):
2652 """Parse the Linux process `/proc/pid/maps` file."""
2653 for line in open_file(proc_map_file, use_cache=False):
2654 line = line.strip()
2655 addr, perm, off, _, rest = line.split(" ", 4)
2656 rest = rest.split(" ", 1)
2657 if len(rest) == 1:
2658 inode = rest[0]
2659 pathname = ""
2660 else:
2661 inode = rest[0]
2662 pathname = rest[1].lstrip()
2663
2664 addr_start, addr_end = list(map(lambda x: long(x, 16), addr.split("-")))
2665 off = long(off, 16)
2666 perm = Permission.from_process_maps(perm)
2667
2668 yield Section(page_start=addr_start,
2669 page_end=addr_end,
2670 offset=off,
2671 permission=perm,
2672 inode=inode,
2673 path=pathname)
2674 return
2675
2676
2677@lru_cache()
2678def get_process_maps():
2679 """Parse the `/proc/pid/maps` file."""
2680
2681 sections = []
2682 try:
2683 pid = get_pid()
2684 fpath = "/proc/{:d}/maps".format(pid)
2685 sections = get_process_maps_linux(fpath)
2686 return list(sections)
2687
2688 except FileNotFoundError as e:
2689 warn("Failed to read /proc/<PID>/maps, using GDB sections info: {}".format(e))
2690 return list(get_info_sections())
2691
2692
2693@lru_cache()
2694def get_info_sections():
2695 """Retrieve the debuggee sections."""
2696 stream = StringIO(gdb.execute("maintenance info sections", to_string=True))
2697
2698 for line in stream:
2699 if not line:
2700 break
2701
2702 try:
2703 parts = [x.strip() for x in line.split()]
2704 addr_start, addr_end = [long(x, 16) for x in parts[1].split("->")]
2705 off = long(parts[3][:-1], 16)
2706 path = parts[4]
2707 inode = ""
2708 perm = Permission.from_info_sections(parts[5:])
2709
2710 yield Section(page_start=addr_start,
2711 page_end=addr_end,
2712 offset=off,
2713 permission=perm,
2714 inode=inode,
2715 path=path)
2716
2717 except IndexError:
2718 continue
2719 except ValueError:
2720 continue
2721
2722 return
2723
2724
2725@lru_cache()
2726def get_info_files():
2727 """Retrieve all the files loaded by debuggee."""
2728 lines = gdb.execute("info files", to_string=True).splitlines()
2729
2730 if len(lines) < len(__infos_files__):
2731 return __infos_files__
2732
2733 for line in lines:
2734 line = line.strip()
2735
2736 if not line:
2737 break
2738
2739 if not line.startswith("0x"):
2740 continue
2741
2742 blobs = [x.strip() for x in line.split(" ")]
2743 addr_start = long(blobs[0], 16)
2744 addr_end = long(blobs[2], 16)
2745 section_name = blobs[4]
2746
2747 if len(blobs) == 7:
2748 filename = blobs[6]
2749 else:
2750 filename = get_filepath()
2751
2752 info = Zone(section_name, addr_start, addr_end, filename)
2753
2754 __infos_files__.append(info)
2755
2756 return __infos_files__
2757
2758
2759def process_lookup_address(address):
2760 """Look up for an address in memory.
2761 Return an Address object if found, None otherwise."""
2762 if not is_alive():
2763 err("Process is not running")
2764 return None
2765
2766 if is_x86() :
2767 if is_in_x86_kernel(address):
2768 return None
2769
2770 for sect in get_process_maps():
2771 if sect.page_start <= address < sect.page_end:
2772 return sect
2773
2774 return None
2775
2776
2777def process_lookup_path(name, perm=Permission.ALL):
2778 """Look up for a path in the process memory mapping.
2779 Return a Section object if found, None otherwise."""
2780 if not is_alive():
2781 err("Process is not running")
2782 return None
2783
2784 for sect in get_process_maps():
2785 if name in sect.path and sect.permission.value & perm:
2786 return sect
2787
2788 return None
2789
2790def file_lookup_name_path(name, path):
2791 """Look up a file by name and path.
2792 Return a Zone object if found, None otherwise."""
2793 for xfile in get_info_files():
2794 if path == xfile.filename and name == xfile.name:
2795 return xfile
2796 return None
2797
2798def file_lookup_address(address):
2799 """Look up for a file by its address.
2800 Return a Zone object if found, None otherwise."""
2801 for info in get_info_files():
2802 if info.zone_start <= address < info.zone_end:
2803 return info
2804 return None
2805
2806
2807def lookup_address(address):
2808 """Try to find the address in the process address space.
2809 Return an Address object, with validity flag set based on success."""
2810 sect = process_lookup_address(address)
2811 info = file_lookup_address(address)
2812 if sect is None and info is None:
2813 # i.e. there is no info on this address
2814 return Address(value=address, valid=False)
2815 return Address(value=address, section=sect, info=info)
2816
2817
2818def xor(data, key):
2819 """Return `data` xor-ed with `key`."""
2820 key = key.lstrip("0x")
2821 key = binascii.unhexlify(key)
2822 if PYTHON_MAJOR == 2:
2823 return b"".join([chr(ord(x) ^ ord(y)) for x, y in zip(data, itertools.cycle(key))])
2824
2825 return bytearray([x ^ y for x, y in zip(data, itertools.cycle(key))])
2826
2827
2828def is_hex(pattern):
2829 """Return whether provided string is a hexadecimal value."""
2830 if not pattern.startswith("0x") and not pattern.startswith("0X"):
2831 return False
2832 return len(pattern)%2==0 and all(c in string.hexdigits for c in pattern[2:])
2833
2834
2835def ida_synchronize_handler(event):
2836 gdb.execute("ida-interact sync", from_tty=True)
2837 return
2838
2839
2840def continue_handler(event):
2841 """GDB event handler for new object continue cases."""
2842 return
2843
2844
2845def hook_stop_handler(event):
2846 """GDB event handler for stop cases."""
2847 reset_all_caches()
2848 gdb.execute("context")
2849 return
2850
2851
2852def new_objfile_handler(event):
2853 """GDB event handler for new object file cases."""
2854 reset_all_caches()
2855 set_arch()
2856 return
2857
2858
2859def exit_handler(event):
2860 """GDB event handler for exit cases."""
2861 global __gef_remote__, __gef_qemu_mode__
2862
2863 reset_all_caches()
2864 __gef_qemu_mode__ = False
2865 if __gef_remote__ and get_gef_setting("gef-remote.clean_on_exit") is True:
2866 shutil.rmtree("/tmp/gef/{:d}".format(__gef_remote__))
2867 __gef_remote__ = None
2868 return
2869
2870
2871def get_terminal_size():
2872 """Return the current terminal size."""
2873 if is_debug():
2874 return 600, 100
2875
2876 try:
2877 cmd = struct.unpack("hh", fcntl.ioctl(1, termios.TIOCGWINSZ, "1234"))
2878 tty_rows, tty_columns = int(cmd[0]), int(cmd[1])
2879 return tty_rows, tty_columns
2880
2881 except OSError:
2882 return 600, 100
2883
2884
2885def get_generic_arch(module, prefix, arch, mode, big_endian, to_string=False):
2886 """
2887 Retrieves architecture and mode from the arguments for use for the holy
2888 {cap,key}stone/unicorn trinity.
2889 """
2890 if to_string:
2891 arch = "{:s}.{:s}_ARCH_{:s}".format(module.__name__, prefix, arch)
2892 if mode:
2893 mode = "{:s}.{:s}_MODE_{:s}".format(module.__name__, prefix, str(mode))
2894 else:
2895 mode = ""
2896 if is_big_endian():
2897 mode += " + {:s}.{:s}_MODE_BIG_ENDIAN".format(module.__name__, prefix)
2898 else:
2899 mode += " + {:s}.{:s}_MODE_LITTLE_ENDIAN".format(module.__name__, prefix)
2900
2901 else:
2902 arch = getattr(module, "{:s}_ARCH_{:s}".format(prefix, arch))
2903 if mode:
2904 mode = getattr(module, "{:s}_MODE_{:s}".format(prefix, mode))
2905 else:
2906 mode = 0
2907 if big_endian:
2908 mode |= getattr(module, "{:s}_MODE_BIG_ENDIAN".format(prefix))
2909 else:
2910 mode |= getattr(module, "{:s}_MODE_LITTLE_ENDIAN".format(prefix))
2911
2912 return arch, mode
2913
2914
2915def get_generic_running_arch(module, prefix, to_string=False):
2916 """
2917 Retrieves architecture and mode from the current context.
2918 """
2919
2920 if not is_alive():
2921 return None, None
2922
2923 if current_arch is not None:
2924 arch, mode = current_arch.arch, current_arch.mode
2925 else:
2926 raise OSError("Emulation not supported for your OS")
2927
2928 return get_generic_arch(module, prefix, arch, mode, is_big_endian(), to_string)
2929
2930
2931def get_unicorn_arch(arch=None, mode=None, endian=None, to_string=False):
2932 unicorn = sys.modules["unicorn"]
2933 if (arch, mode, endian) == (None,None,None):
2934 return get_generic_running_arch(unicorn, "UC", to_string)
2935 return get_generic_arch(unicorn, "UC", arch, mode, endian, to_string)
2936
2937
2938def get_capstone_arch(arch=None, mode=None, endian=None, to_string=False):
2939 capstone = sys.modules["capstone"]
2940
2941 # hacky patch to unify capstone/ppc syntax with keystone & unicorn:
2942 # CS_MODE_PPC32 does not exist (but UC_MODE_32 & KS_MODE_32 do)
2943 if is_arch(Elf.POWERPC64):
2944 raise OSError("Capstone not supported for PPC64 yet.")
2945
2946 if is_alive() and is_arch(Elf.POWERPC):
2947
2948 arch = "PPC"
2949 mode = "32"
2950 endian = is_big_endian()
2951 return get_generic_arch(capstone, "CS",
2952 arch or current_arch.arch,
2953 mode or current_arch.mode,
2954 endian or is_big_endian(),
2955 to_string)
2956
2957 if (arch, mode, endian) == (None,None,None):
2958 return get_generic_running_arch(capstone, "CS", to_string)
2959 return get_generic_arch(capstone, "CS",
2960 arch or current_arch.arch,
2961 mode or current_arch.mode,
2962 endian or is_big_endian(),
2963 to_string)
2964
2965
2966def get_keystone_arch(arch=None, mode=None, endian=None, to_string=False):
2967 keystone = sys.modules["keystone"]
2968 if (arch, mode, endian) == (None,None,None):
2969 return get_generic_running_arch(keystone, "KS", to_string)
2970 return get_generic_arch(keystone, "KS", arch, mode, endian, to_string)
2971
2972
2973def get_unicorn_registers(to_string=False):
2974 "Return a dict matching the Unicorn identifier for a specific register."
2975 unicorn = sys.modules["unicorn"]
2976 regs = {}
2977
2978 if current_arch is not None:
2979 arch = current_arch.arch.lower()
2980 else:
2981 raise OSError("Oops")
2982
2983 const = getattr(unicorn, "{}_const".format(arch))
2984 for reg in current_arch.all_registers:
2985 regname = "UC_{:s}_REG_{:s}".format(arch.upper(), reg[1:].upper())
2986 if to_string:
2987 regs[reg] = "{:s}.{:s}".format(const.__name__, regname)
2988 else:
2989 regs[reg] = getattr(const, regname)
2990 return regs
2991
2992
2993def keystone_assemble(code, arch, mode, *args, **kwargs):
2994 """Assembly encoding function based on keystone."""
2995 keystone = sys.modules["keystone"]
2996 code = gef_pybytes(code)
2997 addr = kwargs.get("addr", 0x1000)
2998
2999 try:
3000 ks = keystone.Ks(arch, mode)
3001 enc, cnt = ks.asm(code, addr)
3002 except keystone.KsError as e:
3003 err("Keystone assembler error: {:s}".format(str(e)))
3004 return None
3005
3006 if cnt==0:
3007 return ""
3008
3009 enc = bytearray(enc)
3010 if "raw" not in kwargs:
3011 s = binascii.hexlify(enc)
3012 enc = b"\\x" + b"\\x".join([s[i:i + 2] for i in range(0, len(s), 2)])
3013 enc = enc.decode("utf-8")
3014
3015 return enc
3016
3017
3018@lru_cache()
3019def get_elf_headers(filename=None):
3020 """Return an Elf object with info from `filename`. If not provided, will return
3021 the currently debugged file."""
3022 if filename is None:
3023 filename = get_filepath()
3024
3025 if filename.startswith("target:"):
3026 warn("Your file is remote, you should try using `gef-remote` instead")
3027 return
3028
3029 return Elf(filename)
3030
3031
3032@lru_cache()
3033def is_elf64(filename=None):
3034 """Checks if `filename` is an ELF64."""
3035 elf = current_elf or get_elf_headers(filename)
3036 return elf.e_class == Elf.ELF_64_BITS
3037
3038
3039@lru_cache()
3040def is_elf32(filename=None):
3041 """Checks if `filename` is an ELF32."""
3042 elf = current_elf or get_elf_headers(filename)
3043 return elf.e_class == Elf.ELF_32_BITS
3044
3045
3046@lru_cache()
3047def is_x86_64(filename=None):
3048 """Checks if `filename` is an x86-64 ELF."""
3049 elf = current_elf or get_elf_headers(filename)
3050 return elf.e_machine == Elf.X86_64
3051
3052
3053@lru_cache()
3054def is_x86_32(filename=None):
3055 """Checks if `filename` is an x86-32 ELF."""
3056 elf = current_elf or get_elf_headers(filename)
3057 return elf.e_machine == Elf.X86_32
3058
3059@lru_cache()
3060def is_x86(filename=None):
3061 return is_x86_32(filename) or is_x86_64(filename)
3062
3063
3064@lru_cache()
3065def is_arch(arch):
3066 elf = current_elf or get_elf_headers()
3067 return elf.e_machine == arch
3068
3069
3070def set_arch(arch=None, default=None):
3071 """Sets the current architecture.
3072 If an arch is explicitly specified, use that one, otherwise try to parse it
3073 out of the ELF header. If that fails, and default is specified, select and
3074 set that arch.
3075 Return the selected arch, or raise an OSError.
3076 """
3077 arches = {
3078 "ARM": ARM, Elf.ARM: ARM,
3079 "AARCH64": AARCH64, "ARM64": AARCH64, Elf.AARCH64: AARCH64,
3080 "X86": X86, Elf.X86_32: X86,
3081 "X86_64": X86_64, Elf.X86_64: X86_64,
3082 "PowerPC": PowerPC, "PPC": PowerPC, Elf.POWERPC: PowerPC,
3083 "PowerPC64": PowerPC64, "PPC64": PowerPC64, Elf.POWERPC64: PowerPC64,
3084 "RISCV": RISCV, Elf.RISCV: RISCV,
3085 "SPARC": SPARC, Elf.SPARC: SPARC,
3086 "SPARC64": SPARC64, Elf.SPARC64: SPARC64,
3087 "MIPS": MIPS, Elf.MIPS: MIPS,
3088 }
3089 global current_arch, current_elf
3090
3091 if arch:
3092 try:
3093 current_arch = arches[arch.upper()]()
3094 return current_arch
3095 except KeyError:
3096 raise OSError("Specified arch {:s} is not supported".format(arch.upper()))
3097
3098 current_elf = current_elf or get_elf_headers()
3099 try:
3100 current_arch = arches[current_elf.e_machine]()
3101 except KeyError:
3102 if default:
3103 try:
3104 current_arch = arches[default.upper()]()
3105 except KeyError:
3106 raise OSError("CPU not supported, neither is default {:s}".format(default.upper()))
3107 else:
3108 raise OSError("CPU type is currently not supported: {:s}".format(get_arch()))
3109 return current_arch
3110
3111
3112@lru_cache()
3113def cached_lookup_type(_type):
3114 try:
3115 return gdb.lookup_type(_type).strip_typedefs()
3116 except RuntimeError:
3117 return None
3118
3119
3120@lru_cache()
3121def get_memory_alignment(in_bits=False):
3122 """Try to determine the size of a pointer on this system.
3123 First, try to parse it out of the ELF header.
3124 Next, use the size of `size_t`.
3125 Finally, try the size of $pc.
3126 If `in_bits` is set to True, the result is returned in bits, otherwise in
3127 bytes."""
3128 if is_elf32():
3129 return 4 if not in_bits else 32
3130 elif is_elf64():
3131 return 8 if not in_bits else 64
3132
3133 res = cached_lookup_type("size_t")
3134 if res is not None:
3135 return res.sizeof if not in_bits else res.sizeof * 8
3136
3137 try:
3138 return gdb.parse_and_eval("$pc").type.sizeof
3139 except:
3140 pass
3141 raise EnvironmentError("GEF is running under an unsupported mode")
3142
3143
3144def clear_screen(tty=""):
3145 """Clear the screen."""
3146 if not tty:
3147 gdb.execute("shell clear")
3148 return
3149
3150 with open(tty, "w") as f:
3151 f.write("\x1b[H\x1b[J")
3152 return
3153
3154
3155def format_address(addr):
3156 """Format the address according to its size."""
3157 memalign_size = get_memory_alignment()
3158 addr = align_address(addr)
3159
3160 if memalign_size == 4:
3161 return "0x{:08x}".format(addr)
3162
3163 return "0x{:016x}".format(addr)
3164
3165
3166def format_address_spaces(addr, left=True):
3167 """Format the address according to its size, but with spaces instead of zeroes."""
3168 width = get_memory_alignment() * 2 + 2
3169 addr = align_address(addr)
3170
3171 if not left:
3172 return "0x{:x}".format(addr).rjust(width)
3173
3174 return "0x{:x}".format(addr).ljust(width)
3175
3176
3177def align_address(address):
3178 """Align the provided address to the process's native length."""
3179 if get_memory_alignment() == 4:
3180 return address & 0xFFFFFFFF
3181
3182 return address & 0xFFFFFFFFFFFFFFFF
3183
3184def align_address_to_size(address, align):
3185 """Align the address to the given size."""
3186 return address + ((align - (address % align)) % align)
3187
3188def align_address_to_page(address):
3189 """Align the address to a page."""
3190 a = align_address(address) >> DEFAULT_PAGE_ALIGN_SHIFT
3191 return a << DEFAULT_PAGE_ALIGN_SHIFT
3192
3193
3194def parse_address(address):
3195 """Parse an address and return it as an Integer."""
3196 if is_hex(address):
3197 return long(address, 16)
3198 return to_unsigned_long(gdb.parse_and_eval(address))
3199
3200
3201def is_in_x86_kernel(address):
3202 address = align_address(address)
3203 memalign = get_memory_alignment(in_bits=True) - 1
3204 return (address >> memalign) == 0xF
3205
3206
3207@lru_cache()
3208def endian_str():
3209 elf = current_elf or get_elf_headers()
3210 return "<" if elf.e_endianness == Elf.LITTLE_ENDIAN else ">"
3211
3212
3213@lru_cache()
3214def is_remote_debug():
3215 """"Return True is the current debugging session is running through GDB remote session."""
3216 return __gef_remote__ is not None or "remote" in gdb.execute("maintenance print target-stack", to_string=True)
3217
3218
3219def de_bruijn(alphabet, n):
3220 """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib)."""
3221 k = len(alphabet)
3222 a = [0] * k * n
3223 def db(t, p):
3224 if t > n:
3225 if n % p == 0:
3226 for j in range(1, p + 1):
3227 yield alphabet[a[j]]
3228 else:
3229 a[t] = a[t - p]
3230 for c in db(t + 1, p):
3231 yield c
3232
3233 for j in range(a[t - p] + 1, k):
3234 a[t] = j
3235 for c in db(t + 1, t):
3236 yield c
3237 return db(1,1)
3238
3239
3240def generate_cyclic_pattern(length):
3241 """Create a `length` byte bytearray of a de Bruijn cyclic pattern."""
3242 charset = bytearray(b"abcdefghijklmnopqrstuvwxyz")
3243 cycle = get_memory_alignment()
3244 res = bytearray()
3245
3246 for i, c in enumerate(de_bruijn(charset, cycle)):
3247 if i == length:
3248 break
3249 res.append(c)
3250
3251 return res
3252
3253
3254def safe_parse_and_eval(value):
3255 """GEF wrapper for gdb.parse_and_eval(): this function returns None instead of raising
3256 gdb.error if the eval failed."""
3257 try:
3258 return gdb.parse_and_eval(value)
3259 except gdb.error:
3260 return None
3261
3262
3263def dereference(addr):
3264 """GEF wrapper for gdb dereference function."""
3265 try:
3266 ulong_t = cached_lookup_type(use_stdtype()) or \
3267 cached_lookup_type(use_default_type()) or \
3268 cached_lookup_type(use_golang_type())
3269 unsigned_long_type = ulong_t.pointer()
3270 res = gdb.Value(addr).cast(unsigned_long_type).dereference()
3271 # GDB does lazy fetch by default so we need to force access to the value
3272 res.fetch_lazy()
3273 return res
3274 except gdb.MemoryError:
3275 pass
3276 return None
3277
3278
3279def dereference_as_long(addr):
3280 derefed = dereference(addr)
3281 return long(derefed.address) if derefed is not None else 0
3282
3283
3284def gef_convenience(value):
3285 """Defines a new convenience value."""
3286 global __gef_convenience_vars_index__
3287 var_name = "$_gef{:d}".format(__gef_convenience_vars_index__)
3288 __gef_convenience_vars_index__ += 1
3289 gdb.execute("""set {:s} = "{:s}" """.format(var_name, value))
3290 return var_name
3291
3292
3293def parse_string_range(s):
3294 """Parses an address range (e.g. 0x400000-0x401000)"""
3295 addrs = s.split("-")
3296 return map(lambda x: int(x, 16), addrs)
3297
3298
3299@lru_cache()
3300def gef_get_auxiliary_values():
3301 """Retrieves the auxiliary values of the current execution. Returns None if not running, or a dict()
3302 of values."""
3303 if not is_alive():
3304 return None
3305
3306 res = {}
3307 for line in gdb.execute("info auxv", to_string=True).splitlines():
3308 tmp = line.split()
3309 _type = tmp[1]
3310 if _type in ("AT_PLATFORM", "AT_EXECFN"):
3311 idx = line[:-1].rfind('"') - 1
3312 tmp = line[:idx].split()
3313
3314 res[_type] = int(tmp[-1], base=0)
3315 return res
3316
3317
3318def gef_read_canary():
3319 """Read the canary of a running process using Auxiliary Vector. Return a tuple of (canary, location)
3320 if found, None otherwise."""
3321 auxval = gef_get_auxiliary_values()
3322 if not auxval:
3323 return None
3324
3325 canary_location = auxval["AT_RANDOM"]
3326 canary = read_int_from_memory(canary_location)
3327 canary &= ~0xff
3328 return canary, canary_location
3329
3330
3331def gef_get_pie_breakpoint(num):
3332 global __pie_breakpoints__
3333 return __pie_breakpoints__[num]
3334
3335
3336@lru_cache()
3337def gef_getpagesize():
3338 """Get the page size from auxiliary values."""
3339 auxval = gef_get_auxiliary_values()
3340 if not auxval:
3341 return DEFAULT_PAGE_SIZE
3342 return auxval["AT_PAGESZ"]
3343
3344
3345def only_if_events_supported(event_type):
3346 """Checks if GDB supports events without crashing."""
3347 def wrap(f):
3348 def wrapped_f(*args, **kwargs):
3349 if getattr(gdb, "events") and getattr(gdb.events, event_type):
3350 return f(*args, **kwargs)
3351 warn("GDB events cannot be set")
3352 return wrapped_f
3353 return wrap
3354
3355
3356#
3357# Event hooking
3358#
3359
3360@only_if_events_supported("cont")
3361def gef_on_continue_hook(func): return gdb.events.cont.connect(func)
3362@only_if_events_supported("cont")
3363def gef_on_continue_unhook(func): return gdb.events.cont.disconnect(func)
3364
3365@only_if_events_supported("stop")
3366def gef_on_stop_hook(func): return gdb.events.stop.connect(func)
3367@only_if_events_supported("stop")
3368def gef_on_stop_unhook(func): return gdb.events.stop.disconnect(func)
3369
3370@only_if_events_supported("exited")
3371def gef_on_exit_hook(func): return gdb.events.exited.connect(func)
3372@only_if_events_supported("exited")
3373def gef_on_exit_unhook(func): return gdb.events.exited.disconnect(func)
3374
3375@only_if_events_supported("new_objfile")
3376def gef_on_new_hook(func): return gdb.events.new_objfile.connect(func)
3377@only_if_events_supported("new_objfile")
3378def gef_on_new_unhook(func): return gdb.events.new_objfile.disconnect(func)
3379
3380
3381#
3382# Virtual breakpoints
3383#
3384
3385class PieVirtualBreakpoint(object):
3386 """PIE virtual breakpoint (not real breakpoint)."""
3387 def __init__(self, set_func, vbp_num, addr):
3388 # set_func(base): given a base address return a
3389 # set breakpoint gdb command string
3390 self.set_func = set_func
3391 self.vbp_num = vbp_num
3392 # breakpoint num, 0 represents not instantiated yet
3393 self.bp_num = 0
3394 self.bp_addr = 0
3395 # this address might be a symbol, just to know where to break
3396 if isinstance(addr, int):
3397 self.addr = hex(addr)
3398 else:
3399 self.addr = addr
3400
3401 def instantiate(self, base):
3402 if self.bp_num:
3403 self.destroy()
3404
3405 try:
3406 res = gdb.execute(self.set_func(base), to_string=True)
3407 except gdb.error as e:
3408 err(e)
3409 return
3410
3411 if "Breakpoint" not in res:
3412 err(res)
3413 return
3414 res_list = res.split()
3415 # Breakpoint (no) at (addr)
3416 self.bp_num = res_list[1]
3417 self.bp_addr = res_list[3]
3418
3419 def destroy(self):
3420 if not self.bp_num:
3421 err("Destroy PIE breakpoint not even set")
3422 return
3423 gdb.execute("delete {}".format(self.bp_num))
3424 self.bp_num = 0
3425
3426#
3427# Breakpoints
3428#
3429
3430class FormatStringBreakpoint(gdb.Breakpoint):
3431 """Inspect stack for format string."""
3432 def __init__(self, spec, num_args):
3433 super(FormatStringBreakpoint, self).__init__(spec, type=gdb.BP_BREAKPOINT, internal=False)
3434 self.num_args = num_args
3435 self.enabled = True
3436 return
3437
3438 def stop(self):
3439 msg = []
3440 ptr, addr = current_arch.get_ith_parameter(self.num_args)
3441 addr = lookup_address(addr)
3442
3443 if not addr.valid:
3444 return False
3445
3446 if addr.section.permission.value & Permission.WRITE:
3447 content = read_cstring_from_memory(addr.value)
3448 name = addr.info.name if addr.info else addr.section.path
3449 msg.append(Color.colorify("Format string helper", "yellow bold"))
3450 msg.append("Possible insecure format string: {:s}('{:s}' {:s} {:#x}: '{:s}')".format(self.location, ptr, RIGHT_ARROW, addr.value, content))
3451 msg.append("Reason: Call to '{:s}()' with format string argument in position "
3452 "#{:d} is in page {:#x} ({:s}) that has write permission".format(self.location, self.num_args, addr.section.page_start, name))
3453 push_context_message("warn", "\n".join(msg))
3454 return True
3455
3456 return False
3457
3458
3459class StubBreakpoint(gdb.Breakpoint):
3460 """Create a breakpoint to permanently disable a call (fork/alarm/signal/etc.)."""
3461
3462 def __init__(self, func, retval):
3463 super(StubBreakpoint, self).__init__(func, gdb.BP_BREAKPOINT, internal=False)
3464 self.func = func
3465 self.retval = retval
3466
3467 m = "All calls to '{:s}' will be skipped".format(self.func)
3468 if self.retval is not None:
3469 m += " (with return value set to {:#x})".format(self.retval)
3470 info(m)
3471 return
3472
3473 def stop(self):
3474 m = "Ignoring call to '{:s}' ".format(self.func)
3475 m+= "(setting return value to {:#x})".format(self.retval)
3476 gdb.execute("return (unsigned int){:#x}".format(self.retval))
3477 ok(m)
3478 return False
3479
3480
3481class ChangePermissionBreakpoint(gdb.Breakpoint):
3482 """When hit, this temporary breakpoint will restore the original code, and position
3483 $pc correctly."""
3484
3485 def __init__(self, loc, code, pc):
3486 super(ChangePermissionBreakpoint, self).__init__(loc, gdb.BP_BREAKPOINT, internal=False)
3487 self.original_code = code
3488 self.original_pc = pc
3489 return
3490
3491 def stop(self):
3492 info("Restoring original context")
3493 write_memory(self.original_pc, self.original_code, len(self.original_code))
3494 info("Restoring $pc")
3495 gdb.execute("set $pc = {:#x}".format(self.original_pc))
3496 return True
3497
3498
3499class TraceMallocBreakpoint(gdb.Breakpoint):
3500 """Track allocations done with malloc() or calloc()."""
3501
3502 def __init__(self, name):
3503 super(TraceMallocBreakpoint, self).__init__(name, gdb.BP_BREAKPOINT, internal=True)
3504 self.silent = True
3505 self.name = name
3506 return
3507
3508 def stop(self):
3509 _, size = current_arch.get_ith_parameter(0)
3510 self.retbp = TraceMallocRetBreakpoint(size, self.name)
3511 return False
3512
3513
3514
3515class TraceMallocRetBreakpoint(gdb.FinishBreakpoint):
3516 """Internal temporary breakpoint to retrieve the return value of malloc()."""
3517
3518 def __init__(self, size, name):
3519 super(TraceMallocRetBreakpoint, self).__init__(gdb.newest_frame(), internal=True)
3520 self.size = size
3521 self.name = name
3522 self.silent = True
3523 return
3524
3525
3526 def stop(self):
3527 global __heap_uaf_watchpoints__, __heap_freed_list__, __heap_allocated_list__
3528
3529 if self.return_value:
3530 loc = long(self.return_value)
3531 else:
3532 loc = to_unsigned_long(gdb.parse_and_eval(current_arch.return_register))
3533
3534 size = self.size
3535 ok("{} - {}({})={:#x}".format(Color.colorify("Heap-Analysis", "yellow bold"), self.name, size, loc))
3536 check_heap_overlap = get_gef_setting("heap-analysis-helper.check_heap_overlap")
3537
3538 # pop from free-ed list if it was in it
3539 if __heap_freed_list__:
3540 idx = 0
3541 for item in __heap_freed_list__:
3542 addr = item[0]
3543 if addr==loc:
3544 __heap_freed_list__.remove(item)
3545 continue
3546 idx+=1
3547
3548 # pop from uaf watchlist
3549 if __heap_uaf_watchpoints__:
3550 idx = 0
3551 for wp in __heap_uaf_watchpoints__:
3552 wp_addr = wp.address
3553 if loc <= wp_addr < loc+size:
3554 __heap_uaf_watchpoints__.remove(wp)
3555 wp.enabled = False
3556 continue
3557 idx+=1
3558
3559 item = (loc, size)
3560
3561 if check_heap_overlap:
3562 # seek all the currently allocated chunks, read their effective size and check for overlap
3563 msg = []
3564 align = get_memory_alignment()
3565 for chunk_addr, _ in __heap_allocated_list__:
3566 current_chunk = GlibcChunk(chunk_addr)
3567 current_chunk_size = current_chunk.get_chunk_size()
3568
3569 if chunk_addr <= loc < chunk_addr + current_chunk_size:
3570 offset = loc - chunk_addr - 2*align
3571 if offset < 0: continue # false positive, discard
3572
3573 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
3574 msg.append("Possible heap overlap detected")
3575 msg.append("Reason {} new allocated chunk {:#x} (of size {:d}) overlaps in-used chunk {:#x} (of size {:#x})".format(RIGHT_ARROW, loc, size, chunk_addr, current_chunk_size))
3576 msg.append("Writing {0:d} bytes from {1:#x} will reach chunk {2:#x}".format(offset, chunk_addr, loc))
3577 msg.append("Payload example for chunk {1:#x} (to overwrite {0:#x} headers):".format(loc, chunk_addr))
3578 msg.append(" data = 'A'*{0:d} + 'B'*{1:d} + 'C'*{1:d}".format(offset, align))
3579 push_context_message("warn", "\n".join(msg))
3580 return True
3581
3582 # add it to alloc-ed list
3583 __heap_allocated_list__.append(item)
3584 return False
3585
3586
3587class TraceReallocBreakpoint(gdb.Breakpoint):
3588 """Track re-allocations done with realloc()."""
3589
3590 def __init__(self):
3591 super(TraceReallocBreakpoint, self).__init__("__libc_realloc", gdb.BP_BREAKPOINT, internal=True)
3592 self.silent = True
3593 return
3594
3595 def stop(self):
3596 _, ptr = current_arch.get_ith_parameter(0)
3597 _, size = current_arch.get_ith_parameter(1)
3598 self.retbp = TraceReallocRetBreakpoint(ptr, size)
3599 return False
3600
3601
3602class TraceReallocRetBreakpoint(gdb.FinishBreakpoint):
3603 """Internal temporary breakpoint to retrieve the return value of realloc()."""
3604
3605 def __init__(self, ptr, size):
3606 super(TraceReallocRetBreakpoint, self).__init__(gdb.newest_frame(), internal=True)
3607 self.ptr = ptr
3608 self.size = size
3609 self.silent = True
3610 return
3611
3612 def stop(self):
3613 global __heap_uaf_watchpoints__, __heap_freed_list__, __heap_allocated_list__
3614
3615 if self.return_value:
3616 newloc = long(self.return_value)
3617 else:
3618 newloc = to_unsigned_long(gdb.parse_and_eval(current_arch.return_register))
3619
3620 if newloc != self:
3621 ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"),
3622 self.ptr, self.size,
3623 Color.colorify("{:#x}".format(newloc), "green"),))
3624 else:
3625 ok("{} - realloc({:#x}, {})={}".format(Color.colorify("Heap-Analysis", "yellow bold"),
3626 self.ptr, self.size,
3627 Color.colorify("{:#x}".format(newloc), "red"),))
3628
3629 item = (newloc, self.size)
3630
3631 try:
3632 # check if item was in alloc-ed list
3633 idx = [x for x,y in __heap_allocated_list__].index(self.ptr)
3634 # if so pop it out
3635 item = __heap_allocated_list__.pop(idx)
3636 except ValueError:
3637 if is_debug():
3638 warn("Chunk {:#x} was not in tracking list".format(self.ptr))
3639 finally:
3640 # add new item to alloc-ed list
3641 __heap_allocated_list__.append(item)
3642
3643 return False
3644
3645
3646class TraceFreeBreakpoint(gdb.Breakpoint):
3647 """Track calls to free() and attempts to detect inconsistencies."""
3648
3649 def __init__(self):
3650 super(TraceFreeBreakpoint, self).__init__("__libc_free", gdb.BP_BREAKPOINT, internal=True)
3651 self.silent = True
3652 return
3653
3654 def stop(self):
3655 _, addr = current_arch.get_ith_parameter(0)
3656 msg = []
3657 check_free_null = get_gef_setting("heap-analysis-helper.check_free_null")
3658 check_double_free = get_gef_setting("heap-analysis-helper.check_double_free")
3659 check_weird_free = get_gef_setting("heap-analysis-helper.check_weird_free")
3660 check_uaf = get_gef_setting("heap-analysis-helper.check_uaf")
3661
3662 ok("{} - free({:#x})".format(Color.colorify("Heap-Analysis", "yellow bold"), addr))
3663 if addr==0:
3664 if check_free_null:
3665 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
3666 msg.append("Attempting to free(NULL) at {:#x}".format(current_arch.pc))
3667 msg.append("Reason: if NULL page is allocatable, this can lead to code execution.")
3668 push_context_message("warn", "\n".join(msg))
3669 return True
3670 return False
3671
3672
3673 if addr in [x for (x,y) in __heap_freed_list__]:
3674 if check_double_free:
3675 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
3676 msg.append("Double-free detected {} free({:#x}) is called at {:#x} but is already in the free-ed list".format(RIGHT_ARROW, addr, current_arch.pc))
3677 msg.append("Execution will likely crash...")
3678 push_context_message("warn", "\n".join(msg))
3679 return True
3680 return False
3681
3682 # if here, no error
3683 # 1. move alloc-ed item to free list
3684 try:
3685 # pop from alloc-ed list
3686 idx = [x for x,y in __heap_allocated_list__].index(addr)
3687 item = __heap_allocated_list__.pop(idx)
3688
3689 except ValueError:
3690 if check_weird_free:
3691 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
3692 msg.append("Heap inconsistency detected:")
3693 msg.append("Attempting to free an unknown value: {:#x}".format(addr))
3694 push_context_message("warn", "\n".join(msg))
3695 return True
3696 return False
3697
3698 # 2. add it to free-ed list
3699 __heap_freed_list__.append(item)
3700
3701 self.retbp = None
3702 if check_uaf:
3703 # 3. (opt.) add a watchpoint on pointer
3704 self.retbp = TraceFreeRetBreakpoint(addr)
3705 return False
3706
3707
3708class TraceFreeRetBreakpoint(gdb.FinishBreakpoint):
3709 """Internal temporary breakpoint to track free()d values."""
3710
3711 def __init__(self, addr):
3712 super(TraceFreeRetBreakpoint, self).__init__(gdb.newest_frame(), internal=True)
3713 self.silent = True
3714 self.addr = addr
3715 return
3716
3717 def stop(self):
3718 wp = UafWatchpoint(self.addr)
3719 __heap_uaf_watchpoints__.append(wp)
3720 ok("{} - watching {:#x}".format(Color.colorify("Heap-Analysis", "yellow bold"), self.addr))
3721 return False
3722
3723
3724class UafWatchpoint(gdb.Breakpoint):
3725 """Custom watchpoints set TraceFreeBreakpoint() to monitor free()d pointers being used."""
3726
3727 def __init__(self, addr):
3728 super(UafWatchpoint, self).__init__("*{:#x}".format(addr), gdb.BP_WATCHPOINT, internal=True)
3729 self.address = addr
3730 self.silent = True
3731 self.enabled = True
3732 return
3733
3734 def stop(self):
3735 """If this method is triggered, we likely have a UaF. Break the execution and report it."""
3736 frame = gdb.selected_frame()
3737 if frame.name() in ("_int_malloc", "malloc_consolidate", "__libc_calloc"):
3738 # ignore when the watchpoint is raised by malloc() - due to reuse
3739 return False
3740
3741 # software watchpoints stop after the next statement (see
3742 # https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html)
3743 pc = gdb_get_nth_previous_instruction_address(current_arch.pc, 2)
3744 insn = gef_current_instruction(pc)
3745 msg = []
3746 msg.append(Color.colorify("Heap-Analysis", "yellow bold"))
3747 msg.append("Possible Use-after-Free in '{:s}': pointer {:#x} was freed, but is attempted to be used at {:#x}"
3748 .format(get_filepath(), self.address, pc))
3749 msg.append("{:#x} {:s} {:s}".format(insn.address, insn.mnemonic, Color.yellowify(", ".join(insn.operands))))
3750 push_context_message("warn", "\n".join(msg))
3751 return True
3752
3753
3754class EntryBreakBreakpoint(gdb.Breakpoint):
3755 """Breakpoint used internally to stop execution at the most convenient entry point."""
3756
3757 def __init__(self, location):
3758 super(EntryBreakBreakpoint, self).__init__(location, gdb.BP_BREAKPOINT, internal=True, temporary=True)
3759 self.silent = True
3760 return
3761
3762 def stop(self):
3763 return True
3764
3765
3766class NamedBreakpoint(gdb.Breakpoint):
3767 """Breakpoint which shows a specified name, when hit."""
3768
3769 def __init__(self, location, name):
3770 super(NamedBreakpoint, self).__init__(spec=location, type=gdb.BP_BREAKPOINT, internal=False, temporary=False)
3771 self.name = name
3772 self.loc = location
3773
3774 return
3775
3776 def stop(self):
3777 push_context_message("info", "Hit breakpoint {} ({})".format(self.loc, Color.colorify(self.name, "red bold")))
3778 return True
3779
3780
3781#
3782# Commands
3783#
3784
3785def register_external_command(obj):
3786 """Registering function for new GEF (sub-)command to GDB."""
3787 global __commands__, __gef__
3788 cls = obj.__class__
3789 __commands__.append(cls)
3790 __gef__.load(initial=False)
3791 __gef__.doc.add_command_to_doc((cls._cmdline_, cls, None))
3792 __gef__.doc.refresh()
3793 return cls
3794
3795
3796def register_command(cls):
3797 """Decorator for registering new GEF (sub-)command to GDB."""
3798 global __commands__
3799 __commands__.append(cls)
3800 return cls
3801
3802
3803def register_priority_command(cls):
3804 """Decorator for registering new command with priority, meaning that it must
3805 loaded before the other generic commands."""
3806 global __commands__
3807 __commands__.insert(0, cls)
3808 return cls
3809
3810def register_function(cls):
3811 """Decorator for registering a new convenience function to GDB."""
3812 global __functions__
3813 __functions__.append(cls)
3814 return cls
3815
3816class GenericCommand(gdb.Command):
3817 """This is an abstract class for invoking commands, should not be instantiated."""
3818 __metaclass__ = abc.ABCMeta
3819
3820 def __init__(self, *args, **kwargs):
3821 self.pre_load()
3822 syntax = Color.yellowify("\nSyntax: ") + self._syntax_
3823 example = Color.yellowify("\nExample: ") + self._example_ if self._example_ else ""
3824 self.__doc__ = self.__doc__.replace(" "*4, "") + syntax + example
3825 self.repeat = False
3826 self.repeat_count = 0
3827 self.__last_command = None
3828 command_type = kwargs.setdefault("command", gdb.COMMAND_OBSCURE)
3829 complete_type = kwargs.setdefault("complete", gdb.COMPLETE_NONE)
3830 prefix = kwargs.setdefault("prefix", False)
3831 super(GenericCommand, self).__init__(self._cmdline_, command_type, complete_type, prefix)
3832 self.post_load()
3833 return
3834
3835 def invoke(self, args, from_tty):
3836 try:
3837 argv = gdb.string_to_argv(args)
3838 self.__set_repeat_count(argv, from_tty)
3839 bufferize(self.do_invoke)(argv)
3840 except Exception as e:
3841 # Note: since we are intercepting cleaning exceptions here, commands preferably should avoid
3842 # catching generic Exception, but rather specific ones. This is allows a much cleaner use.
3843 if is_debug():
3844 show_last_exception()
3845 else:
3846 err("Command '{:s}' failed to execute properly, reason: {:s}".format(self._cmdline_, str(e)))
3847 return
3848
3849 def usage(self):
3850 err("Syntax\n{}".format(self._syntax_))
3851 return
3852
3853 @abc.abstractproperty
3854 def _cmdline_(self): pass
3855
3856 @abc.abstractproperty
3857 def _syntax_(self): pass
3858
3859 @abc.abstractproperty
3860 def _example_(self): return ""
3861
3862 @abc.abstractmethod
3863 def do_invoke(self, argv): pass
3864
3865 def pre_load(self): pass
3866
3867 def post_load(self): pass
3868
3869 def __get_setting_name(self, name):
3870 def __sanitize_class_name(clsname):
3871 if " " not in clsname:
3872 return clsname
3873 return "-".join(clsname.split())
3874
3875 class_name = __sanitize_class_name(self.__class__._cmdline_)
3876 return "{:s}.{:s}".format(class_name, name)
3877
3878 @property
3879 def settings(self):
3880 """Return the list of settings for this command."""
3881 return [ x.split(".", 1)[1] for x in __config__
3882 if x.startswith("{:s}.".format(self._cmdline_)) ]
3883
3884 def get_setting(self, name):
3885 key = self.__get_setting_name(name)
3886 setting = __config__[key]
3887 return setting[1](setting[0])
3888
3889 def has_setting(self, name):
3890 key = self.__get_setting_name(name)
3891 return key in __config__
3892
3893 def add_setting(self, name, value, description=""):
3894 key = self.__get_setting_name(name)
3895 __config__[key] = [value, type(value), description]
3896 return
3897
3898 def del_setting(self, name):
3899 key = self.__get_setting_name(name)
3900 del __config__[key]
3901 return
3902
3903 def __set_repeat_count(self, argv, from_tty):
3904 if not from_tty:
3905 self.repeat = False
3906 self.repeat_count = 0
3907 return
3908
3909 command = gdb.execute("show commands", to_string=True).strip().split("\n")[-1]
3910 self.repeat = self.__last_command == command
3911 self.repeat_count = self.repeat_count + 1 if self.repeat else 0
3912 self.__last_command = command
3913 return
3914
3915
3916# Copy/paste this template for new command
3917# @register_command
3918# class TemplateCommand(GenericCommand):
3919# """TemplateCommand: description here will be seen in the help menu for the command."""
3920# _cmdline_ = "template-fake"
3921# _syntax_ = "{:s}".format(_cmdline_)
3922# _aliases_ = ["tpl-fk",]
3923# def __init__(self):
3924# super(TemplateCommand, self).__init__(complete=gdb.COMPLETE_FILENAME)
3925# return
3926# def do_invoke(self, argv):
3927# return
3928
3929@register_command
3930class PrintFormatCommand(GenericCommand):
3931 """Print bytes format in high level languages."""
3932
3933 _cmdline_ = "print-format"
3934 _syntax_ = "{:s} [-f FORMAT] [-b BITSIZE] [-l LENGTH] [-c] [-h] LOCATION".format(_cmdline_)
3935 _aliases_ = ["pf",]
3936 _example_ = "{0:s} -f py -b 8 -l 256 $rsp".format(_cmdline_)
3937
3938 bitformat = {8: "<B", 16: "<H", 32: "<I", 64: "<Q"}
3939 c_type = {8: "char", 16: "short", 32: "int", 64: "long long"}
3940 asm_type = {8: "db", 16: "dw", 32: "dd", 64: "dq"}
3941
3942 def __init__(self):
3943 super(PrintFormatCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
3944 return
3945
3946 def usage(self):
3947 h = self._syntax_
3948 h += "\n\t-f FORMAT specifies the output format for programming language, avaliable value is py, c, js, asm (default py).\n"
3949 h += "\t-b BITSIZE sepecifies size of bit, avaliable values is 8, 16, 32, 64 (default is 8).\n"
3950 h += "\t-l LENGTH specifies length of array (default is 256).\n"
3951 h += "\t-c The result of data will copied to clipboard\n"
3952 h += "\tLOCATION specifies where the address of bytes is stored."
3953 info(h)
3954 return
3955
3956 def clip(self, data):
3957 if sys.platform == "linux":
3958 xclip = which("xclip")
3959 prog = [xclip, "-selection", "clipboard", "-i"] # For linux
3960 elif sys.platform == "darwin":
3961 pbcopy = which("pbcopy")
3962 prog = [pbcopy] # For OSX
3963 else:
3964 warn("Can't copy to clipboard, platform not supported")
3965 return False
3966
3967 try:
3968 p = subprocess.Popen(prog, stdin=subprocess.PIPE)
3969 except Exception:
3970 warn("Can't copy to clipboard, Something went wrong while copying")
3971 return False
3972
3973 p.stdin.write(data)
3974 p.stdin.close()
3975 p.wait()
3976 return True
3977
3978 @only_if_gdb_running
3979 def do_invoke(self, argv):
3980 """Default value for print-format command."""
3981 lang = "py"
3982 length = 256
3983 bitlen = 8
3984 copy_to_clipboard = False
3985 supported_formats = ["py", "c", "js", "asm"]
3986
3987 opts, args = getopt.getopt(argv, "f:l:b:ch")
3988 for o,a in opts:
3989 if o == "-f": lang = a
3990 elif o == "-l": length = long(gdb.parse_and_eval(a))
3991 elif o == "-b": bitlen = long(a)
3992 elif o == "-c": copy_to_clipboard = True
3993 elif o == "-h":
3994 self.usage()
3995 return
3996
3997 if not args:
3998 err("No address specified")
3999 return
4000
4001 start_addr = long(gdb.parse_and_eval(args[0]))
4002
4003 if bitlen not in [8, 16, 32, 64]:
4004 err("Size of bit must be in 8, 16, 32, or 64")
4005 return
4006
4007 if lang not in supported_formats:
4008 err("Language must be : {}".format(str(supported_formats)))
4009 return
4010
4011 size = long(bitlen / 8)
4012 end_addr = start_addr+length*size
4013 bf = self.bitformat[bitlen]
4014 data = []
4015 out = ""
4016
4017 for address in range(start_addr, end_addr, size):
4018 value = struct.unpack(bf, read_memory(address, size))[0]
4019 data += [value]
4020 sdata = ", ".join(map(hex, data))
4021
4022 if lang == "py":
4023 out = "buf = [{}]".format(sdata)
4024 elif lang == "c":
4025 out = "unsigned {0} buf[{1}] = {{{2}}};".format(self.c_type[bitlen], length, sdata)
4026 elif lang == "js":
4027 out = "var buf = [{}]".format(sdata)
4028 elif lang == "asm":
4029 out += "buf {0} {1}".format(self.asm_type[bitlen], sdata)
4030
4031 if copy_to_clipboard:
4032 if self.clip(bytes(out, "utf-8")):
4033 info("Copied to clipboard")
4034 else:
4035 warn("There's a problem while copying")
4036
4037 print(out)
4038 return
4039
4040
4041@register_command
4042class PieCommand(GenericCommand):
4043 """PIE breakpoint support."""
4044
4045 _cmdline_ = "pie"
4046 _syntax_ = "{:s} (breakpoint|info|delete|run|attach|remote)".format(_cmdline_)
4047
4048 def __init__(self):
4049 super(PieCommand, self).__init__(prefix=True)
4050 return
4051
4052 def do_invoke(self, argv):
4053 if not argv:
4054 self.usage()
4055 return
4056
4057
4058@register_command
4059class PieBreakpointCommand(GenericCommand):
4060 """Set a PIE breakpoint."""
4061
4062 _cmdline_ = "pie breakpoint"
4063 _syntax_ = "{:s} BREAKPOINT".format(_cmdline_)
4064
4065 def do_invoke(self, argv):
4066 global __pie_counter__, __pie_breakpoints__
4067 if len(argv) < 1:
4068 self.usage()
4069 return
4070 bp_expr = " ".join(argv)
4071 tmp_bp_expr = bp_expr
4072
4073 if bp_expr[0] == "*":
4074 addr = long(gdb.parse_and_eval(bp_expr[1:]))
4075 else:
4076 addr = long(gdb.parse_and_eval("&{}".format(bp_expr))) # get address of symbol or function name
4077
4078 self.set_pie_breakpoint(lambda base: "b *{}".format(base + addr), addr)
4079
4080 # When the process is already on, set real breakpoints immediately
4081 if is_alive():
4082 vmmap = get_process_maps()
4083 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
4084 for bp_ins in __pie_breakpoints__.values():
4085 bp_ins.instantiate(base_address)
4086
4087
4088 @staticmethod
4089 def set_pie_breakpoint(set_func, addr):
4090 global __pie_counter__, __pie_breakpoints__
4091 __pie_breakpoints__[__pie_counter__] = PieVirtualBreakpoint(set_func, __pie_counter__, addr)
4092 __pie_counter__ += 1
4093
4094
4095@register_command
4096class PieInfoCommand(GenericCommand):
4097 """Display breakpoint info."""
4098
4099 _cmdline_ = "pie info"
4100 _syntax_ = "{:s} BREAKPOINT".format(_cmdline_)
4101
4102 def do_invoke(self, argv):
4103 global __pie_breakpoints__
4104 if len(argv) < 1:
4105 # No breakpoint info needed
4106 bps = [__pie_breakpoints__[x] for x in __pie_breakpoints__]
4107 else:
4108 try:
4109 bps = [__pie_breakpoints__[int(x)] for x in argv]
4110 except ValueError:
4111 err("Please give me breakpoint number")
4112 return
4113 lines = []
4114 lines.append("VNum\tNum\tAddr")
4115 lines += [
4116 "{}\t{}\t{}".format(x.vbp_num, x.bp_num if x.bp_num else "N/A", x.addr) for x in bps
4117 ]
4118 gef_print("\n".join(lines))
4119
4120
4121@register_command
4122class PieDeleteCommand(GenericCommand):
4123 """Delete a PIE breakpoint."""
4124
4125 _cmdline_ = "pie delete"
4126 _syntax_ = "{:s} [BREAKPOINT]".format(_cmdline_)
4127
4128 def do_invoke(self, argv):
4129 global __pie_breakpoints__
4130 if len(argv) < 1:
4131 # no arg, delete all
4132 to_delete = [__pie_breakpoints__[x] for x in __pie_breakpoints__]
4133 self.delete_bp(to_delete)
4134 try:
4135 self.delete_bp([__pie_breakpoints__[int(x)] for x in argv])
4136 except ValueError:
4137 err("Please input PIE virtual breakpoint number to delete")
4138
4139 @staticmethod
4140 def delete_bp(breakpoints):
4141 global __pie_breakpoints__
4142 for bp in breakpoints:
4143 # delete current real breakpoints if exists
4144 if bp.bp_num:
4145 gdb.execute("delete {}".format(bp.bp_num))
4146 # delete virtual breakpoints
4147 del __pie_breakpoints__[bp.vbp_num]
4148
4149
4150@register_command
4151class PieRunCommand(GenericCommand):
4152 """Run process with PIE breakpoint support."""
4153
4154 _cmdline_ = "pie run"
4155 _syntax_ = _cmdline_
4156
4157 def do_invoke(self, argv):
4158 global __pie_breakpoints__
4159 fpath = get_filepath()
4160 if fpath is None:
4161 warn("No executable to debug, use `file` to load a binary")
4162 return
4163
4164 if not os.access(fpath, os.X_OK):
4165 warn("The file '{}' is not executable.".format(fpath))
4166 return
4167
4168 if is_alive():
4169 warn("gdb is already running. Restart process.")
4170
4171 # get base address
4172 gdb.execute("set stop-on-solib-events 1")
4173 hide_context()
4174 gdb.execute("run {}".format(" ".join(argv)))
4175 unhide_context()
4176 gdb.execute("set stop-on-solib-events 0")
4177 vmmap = get_process_maps()
4178 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
4179 info("base address {}".format(hex(base_address)))
4180
4181 # modify all breakpoints
4182 for bp_ins in __pie_breakpoints__.values():
4183 bp_ins.instantiate(base_address)
4184
4185 try:
4186 gdb.execute("continue")
4187 except gdb.error as e:
4188 err(e)
4189 gdb.execute("kill")
4190
4191
4192@register_command
4193class PieAttachCommand(GenericCommand):
4194 """Do attach with PIE breakpoint support."""
4195
4196 _cmdline_ = "pie attach"
4197 _syntax_ = "{:s} PID".format(_cmdline_)
4198
4199 def do_invoke(self, argv):
4200 try:
4201 gdb.execute("attach {}".format(" ".join(argv)), to_string=True)
4202 except gdb.error as e:
4203 err(e)
4204 return
4205 # after attach, we are stopped so that we can
4206 # get base address to modify our breakpoint
4207 vmmap = get_process_maps()
4208 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
4209
4210 for bp_ins in __pie_breakpoints__.values():
4211 bp_ins.instantiate(base_address)
4212 gdb.execute("context")
4213
4214
4215@register_command
4216class PieRemoteCommand(GenericCommand):
4217 """Attach to a remote connection with PIE breakpoint support."""
4218
4219 _cmdline_ = "pie remote"
4220 _syntax_ = "{:s} REMOTE".format(_cmdline_)
4221
4222 def do_invoke(self, argv):
4223 try:
4224 gdb.execute("gef-remote {}".format(" ".join(argv)))
4225 except gdb.error as e:
4226 err(e)
4227 return
4228 # after remote attach, we are stopped so that we can
4229 # get base address to modify our breakpoint
4230 vmmap = get_process_maps()
4231 base_address = [x.page_start for x in vmmap if x.realpath == get_filepath()][0]
4232
4233 for bp_ins in __pie_breakpoints__.values():
4234 bp_ins.instantiate(base_address)
4235 gdb.execute("context")
4236
4237
4238@register_command
4239class SmartEvalCommand(GenericCommand):
4240 """SmartEval: Smart eval (vague approach to mimic WinDBG `?`)."""
4241 _cmdline_ = "$"
4242 _syntax_ = "{0:s} EXPR\n{0:s} ADDRESS1 ADDRESS2".format(_cmdline_)
4243 _example_ = "\n{0:s} $pc+1\n{0:s} 0x00007ffff7a10000 0x00007ffff7bce000".format(_cmdline_)
4244
4245 def do_invoke(self, argv):
4246 argc = len(argv)
4247 if argc==1:
4248 self.evaluate(argv)
4249 return
4250
4251 if argc==2:
4252 self.distance(argv)
4253 return
4254
4255 def evaluate(self, expr):
4256 def show_as_int(i):
4257 off = current_arch.ptrsize*8
4258 def comp2_x(x): return "{:x}".format((x + (1 << off)) % (1 << off))
4259 def comp2_b(x): return "{:b}".format((x + (1 << off)) % (1 << off))
4260
4261 try:
4262 s_i = comp2_x(res)
4263 s_i = s_i.rjust(len(s_i)+1, "0") if len(s_i)%2 else s_i
4264 gef_print("{:d}".format(i))
4265 gef_print("0x" + comp2_x(res))
4266 gef_print("0b" + comp2_b(res))
4267 gef_print("{}".format(binascii.unhexlify(s_i)))
4268 gef_print("{}".format(binascii.unhexlify(s_i)[::-1]))
4269 except:
4270 pass
4271 return
4272
4273 parsed_expr = []
4274 for xp in expr:
4275 try:
4276 xp = gdb.parse_and_eval(xp)
4277 xp = int(xp)
4278 parsed_expr.append("{:d}".format(xp))
4279 except gdb.error:
4280 parsed_expr.append(str(xp))
4281
4282 try:
4283 res = eval(" ".join(parsed_expr))
4284 if type(res) is int:
4285 show_as_int(res)
4286 else:
4287 gef_print("{}".format(res))
4288 except SyntaxError:
4289 gef_print(" ".join(parsed_expr))
4290 return
4291
4292 def distance(self, args):
4293 try:
4294 x = int(args[0], 16) if is_hex(args[0]) else int(args[0])
4295 y = int(args[1], 16) if is_hex(args[1]) else int(args[1])
4296 gef_print("{}".format(abs(x-y)))
4297 except ValueError:
4298 warn("Distance requires 2 numbers: {} 0 0xffff".format(self._cmdline_))
4299 return
4300
4301
4302@register_command
4303class CanaryCommand(GenericCommand):
4304 """Shows the canary value of the current process. Apply the techique detailed in
4305 https://www.elttam.com.au/blog/playing-with-canaries/ to show the canary."""
4306
4307 _cmdline_ = "canary"
4308 _syntax_ = _cmdline_
4309
4310 @only_if_gdb_running
4311 def do_invoke(self, argv):
4312 self.dont_repeat()
4313
4314 has_canary = checksec(get_filepath())["Canary"]
4315 if not has_canary:
4316 warn("This binary was not compiled with SSP.")
4317 return
4318
4319 res = gef_read_canary()
4320 if not res:
4321 err("Failed to get the canary")
4322 return
4323
4324 canary, location = res
4325 info("Found AT_RANDOM at {:#x}, reading {} bytes".format(location, current_arch.ptrsize))
4326 info("The canary of process {} is {:#x}".format(get_pid(), canary))
4327 return
4328
4329
4330@register_command
4331class ProcessStatusCommand(GenericCommand):
4332 """Extends the info given by GDB `info proc`, by giving an exhaustive description of the
4333 process status (file descriptors, ancestor, descendants, etc.). """
4334
4335 _cmdline_ = "process-status"
4336 _syntax_ = _cmdline_
4337 _aliases_ = ["status", ]
4338
4339 def __init__(self):
4340 super(ProcessStatusCommand, self).__init__(complete=gdb.COMPLETE_NONE)
4341 return
4342
4343 @only_if_gdb_running
4344 @only_if_gdb_target_local
4345 def do_invoke(self, argv):
4346 self.show_info_proc()
4347 self.show_ancestor()
4348 self.show_descendants()
4349 self.show_fds()
4350 self.show_connections()
4351 return
4352
4353 def get_state_of(self, pid):
4354 res = {}
4355 for line in open("/proc/{}/status".format(pid), "r"):
4356 key, value = line.split(":", 1)
4357 res[key.strip()] = value.strip()
4358 return res
4359
4360 def get_cmdline_of(self, pid):
4361 return open("/proc/{}/cmdline".format(pid), "r").read().replace("\x00", "\x20").strip()
4362
4363 def get_process_path_of(self, pid):
4364 return os.readlink("/proc/{}/exe".format(pid))
4365
4366 def get_children_pids(self, pid):
4367 ps = which("ps")
4368 cmd = [ps, "-o", "pid", "--ppid","{}".format(pid), "--noheaders"]
4369 try:
4370 return [int(x) for x in gef_execute_external(cmd, as_list=True)]
4371 except Exception:
4372 return []
4373
4374 def show_info_proc(self):
4375 info("Process Information")
4376 pid = get_pid()
4377 cmdline = self.get_cmdline_of(pid)
4378 gef_print("\tPID {} {}".format(RIGHT_ARROW, pid))
4379 gef_print("\tExecutable {} {}".format(RIGHT_ARROW, self.get_process_path_of(pid)))
4380 gef_print("\tCommand line {} '{}'".format(RIGHT_ARROW, cmdline))
4381 return
4382
4383 def show_ancestor(self):
4384 info("Parent Process Information")
4385 ppid = int(self.get_state_of(get_pid())["PPid"])
4386 state = self.get_state_of(ppid)
4387 cmdline = self.get_cmdline_of(ppid)
4388 gef_print("\tParent PID {} {}".format(RIGHT_ARROW, state["Pid"]))
4389 gef_print("\tCommand line {} '{}'".format(RIGHT_ARROW, cmdline))
4390 return
4391
4392 def show_descendants(self):
4393 info("Children Process Information")
4394 children = self.get_children_pids(get_pid())
4395 if not children:
4396 gef_print("\tNo child process")
4397 return
4398
4399 for child_pid in children:
4400 state = self.get_state_of(child_pid)
4401 pid = state["Pid"]
4402 gef_print("\tPID {} {} (Name: '{}', CmdLine: '{}')".format(RIGHT_ARROW,
4403 pid,
4404 self.get_process_path_of(pid),
4405 self.get_cmdline_of(pid)))
4406 return
4407
4408 def show_fds(self):
4409 pid = get_pid()
4410 path = "/proc/{:d}/fd".format(pid)
4411
4412 info("File Descriptors:")
4413 items = os.listdir(path)
4414 if not items:
4415 gef_print("\tNo FD opened")
4416 return
4417
4418 for fname in items:
4419 fullpath = os.path.join(path, fname)
4420 if os.path.islink(fullpath):
4421 gef_print("\t{:s} {:s} {:s}".format (fullpath, RIGHT_ARROW, os.readlink(fullpath)))
4422 return
4423
4424 def list_sockets(self, pid):
4425 sockets = []
4426 path = "/proc/{:d}/fd".format(pid)
4427 items = os.listdir(path)
4428 for fname in items:
4429 fullpath = os.path.join(path, fname)
4430 if os.path.islink(fullpath) and os.readlink(fullpath).startswith("socket:"):
4431 p = os.readlink(fullpath).replace("socket:", "")[1:-1]
4432 sockets.append(int(p))
4433 return sockets
4434
4435 def parse_ip_port(self, addr):
4436 ip, port = addr.split(":")
4437 return socket.inet_ntoa(struct.pack("<I", int(ip, 16))), int(port, 16)
4438
4439 def show_connections(self):
4440 # https://github.com/torvalds/linux/blob/v4.7/include/net/tcp_states.h#L16
4441 tcp_states_str = {
4442 0x01: "TCP_ESTABLISHED",
4443 0x02: "TCP_SYN_SENT",
4444 0x03: "TCP_SYN_RECV",
4445 0x04: "TCP_FIN_WAIT1",
4446 0x05: "TCP_FIN_WAIT2",
4447 0x06: "TCP_TIME_WAIT",
4448 0x07: "TCP_CLOSE",
4449 0x08: "TCP_CLOSE_WAIT",
4450 0x09: "TCP_LAST_ACK",
4451 0x0a: "TCP_LISTEN",
4452 0x0b: "TCP_CLOSING",
4453 0x0c: "TCP_NEW_SYN_RECV",
4454 }
4455
4456 udp_states_str = {
4457 0x07: "UDP_LISTEN",
4458 }
4459
4460 info("Network Connections")
4461 pid = get_pid()
4462 sockets = self.list_sockets(pid)
4463 if not sockets:
4464 gef_print("\tNo open connections")
4465 return
4466
4467 entries = {}
4468 entries["TCP"] = [x.split() for x in open("/proc/{:d}/net/tcp".format(pid), "r").readlines()[1:]]
4469 entries["UDP"]= [x.split() for x in open("/proc/{:d}/net/udp".format(pid), "r").readlines()[1:]]
4470
4471 for proto in entries:
4472 for entry in entries[proto]:
4473 local, remote, state = entry[1:4]
4474 inode = int(entry[9])
4475 if inode in sockets:
4476 local = self.parse_ip_port(local)
4477 remote = self.parse_ip_port(remote)
4478 state = int(state, 16)
4479 state_str = tcp_states_str[state] if proto=="TCP" else udp_states_str[state]
4480
4481 gef_print("\t{}:{} {} {}:{} ({})".format(local[0], local[1],
4482 RIGHT_ARROW,
4483 remote[0], remote[1],
4484 state_str))
4485 return
4486
4487
4488@register_priority_command
4489class GefThemeCommand(GenericCommand):
4490 """Customize GEF appearance."""
4491 _cmdline_ = "theme"
4492 _syntax_ = "{:s} [KEY [VALUE]]".format(_cmdline_)
4493
4494 def __init__(self, *args, **kwargs):
4495 super(GefThemeCommand, self).__init__(GefThemeCommand._cmdline_)
4496 self.add_setting("context_title_line", "gray", "Color of the borders in context window")
4497 self.add_setting("context_title_message", "cyan", "Color of the title in context window")
4498 self.add_setting("default_title_line", "gray", "Default color of borders")
4499 self.add_setting("default_title_message", "cyan", "Default color of title")
4500 self.add_setting("table_heading", "blue", "Color of the column headings to tables (e.g. vmmap)")
4501 self.add_setting("disassemble_current_instruction", "green", "Color to use to highlight the current $pc when disassembling")
4502 self.add_setting("dereference_string", "yellow", "Color of dereferenced string")
4503 self.add_setting("dereference_code", "gray", "Color of dereferenced code")
4504 self.add_setting("dereference_base_address", "cyan", "Color of dereferenced address")
4505 self.add_setting("dereference_register_value", "bold blue" , "Color of dereferenced register")
4506 self.add_setting("registers_register_name", "blue", "Color of the register name in the register window")
4507 self.add_setting("registers_value_changed", "bold red", "Color of the changed register in the register window")
4508 self.add_setting("address_stack", "pink", "Color to use when a stack address is found")
4509 self.add_setting("address_heap", "green", "Color to use when a heap address is found")
4510 self.add_setting("address_code", "red", "Color to use when a code address is found")
4511 self.add_setting("source_current_line", "green", "Color to use for the current code line in the source window")
4512 return
4513
4514 def do_invoke(self, args):
4515 self.dont_repeat()
4516 argc = len(args)
4517
4518 if argc==0:
4519 for setting in sorted(self.settings):
4520 value = self.get_setting(setting)
4521 value = Color.colorify(value, value)
4522 gef_print("{:40s}: {:s}".format(setting, value))
4523 return
4524
4525 setting = args[0]
4526 if not self.has_setting(setting):
4527 err("Invalid key")
4528 return
4529
4530 if argc==1:
4531 value = self.get_setting(setting)
4532 value = Color.colorify(value, value)
4533 gef_print("{:40s}: {:s}".format(setting, value))
4534 return
4535
4536 val = [x for x in args[1:] if x in Color.colors]
4537 self.add_setting(setting, " ".join(val))
4538 return
4539
4540
4541@register_command
4542class PCustomCommand(GenericCommand):
4543 """Dump user defined structure.
4544 This command attempts to reproduce WinDBG awesome `dt` command for GDB and allows
4545 to apply structures (from symbols or custom) directly to an address.
4546 Custom structures can be defined in pure Python using ctypes, and should be stored
4547 in a specific directory, whose path must be stored in the `pcustom.struct_path`
4548 configuration setting."""
4549
4550 _cmdline_ = "pcustom"
4551 _syntax_ = "{:s} [-l] [StructA [0xADDRESS] [-e]]".format(_cmdline_)
4552
4553 def __init__(self):
4554 super(PCustomCommand, self).__init__(complete=gdb.COMPLETE_SYMBOL)
4555 self.add_setting("struct_path", os.path.join(GEF_TEMP_DIR, "structs"),
4556 "Path to store/load the structure ctypes files")
4557 return
4558
4559 def do_invoke(self, argv):
4560 argc = len(argv)
4561 if argc == 0:
4562 self.usage()
4563 return
4564
4565 if argv[0] == "-l":
4566 self.list_custom_structures()
4567 return
4568
4569 modname, structname = argv[0].split(":", 1) if ":" in argv[0] else (argv[0], argv[0])
4570 structname = structname.split(".", 1)[0] if "." in structname else structname
4571
4572 if argc == 1:
4573 self.dump_structure(modname, structname)
4574 return
4575
4576 if argv[1] == "-e":
4577 self.create_or_edit_structure(modname, structname)
4578 return
4579
4580 if not is_alive():
4581 return
4582
4583 try:
4584 address = long(gdb.parse_and_eval(argv[1]))
4585 except gdb.error:
4586 err("Failed to parse '{:s}'".format(argv[1]))
4587 return
4588
4589 self.apply_structure_to_address(modname, structname, address)
4590 return
4591
4592
4593 def get_struct_path(self):
4594 path = os.path.expanduser(self.get_setting("struct_path"))
4595 path = os.path.realpath(path)
4596 return path if os.path.isdir(path) else None
4597
4598
4599 def pcustom_filepath(self, x):
4600 p = self.get_struct_path()
4601 if not p: return None
4602 return os.path.join(p, "{}.py".format(x))
4603
4604
4605 def is_valid_struct(self, x):
4606 p = self.pcustom_filepath(x)
4607 return os.access(p, os.R_OK) if p else None
4608
4609
4610 def dump_structure(self, mod_name, struct_name):
4611 # If it's a builtin or defined in the ELF use gdb's `ptype`
4612 try:
4613 gdb.execute("ptype struct {:s}".format(struct_name))
4614 return
4615 except gdb.error:
4616 pass
4617
4618 self.dump_custom_structure(mod_name, struct_name)
4619 return
4620
4621
4622 def dump_custom_structure(self, mod_name, struct_name):
4623 if not self.is_valid_struct(mod_name):
4624 err("Invalid structure name '{:s}'".format(struct_name))
4625 return
4626
4627 _class, _struct = self.get_structure_class(mod_name, struct_name)
4628
4629 for _name, _type in _struct._fields_:
4630 _size = ctypes.sizeof(_type)
4631 gef_print("+{:04x} {:s} {:s} ({:#x})".format(getattr(_class, _name).offset, _name, _type.__name__, _size))
4632 return
4633
4634
4635 def deserialize(self, struct, data):
4636 length = min(len(data), ctypes.sizeof(struct))
4637 ctypes.memmove(ctypes.addressof(struct), data, length)
4638 return
4639
4640
4641 def get_module(self, modname):
4642 _fullname = self.pcustom_filepath(modname)
4643 return imp.load_source(modname, _fullname)
4644
4645
4646 def get_structure_class(self, modname, classname):
4647 _mod = self.get_module(modname)
4648 _class = getattr(_mod, classname)
4649 return _class, _class()
4650
4651 def list_all_structs(self, modname):
4652 _mod = self.get_module(modname)
4653 _invalid = set(["BigEndianStructure", "LittleEndianStructure", "Structure"])
4654 _structs = set([x for x in dir(_mod) \
4655 if inspect.isclass(getattr(_mod, x)) \
4656 and issubclass(getattr(_mod, x), ctypes.Structure)])
4657 return _structs - _invalid
4658
4659
4660 def apply_structure_to_address(self, mod_name, struct_name, addr, depth=0):
4661 if not self.is_valid_struct(mod_name):
4662 err("Invalid structure name '{:s}'".format(struct_name))
4663 return
4664
4665 try:
4666 _class, _struct = self.get_structure_class(mod_name, struct_name)
4667 data = read_memory(addr, ctypes.sizeof(_struct))
4668 except gdb.MemoryError:
4669 err("{}Cannot reach memory {:#x}".format(" "*depth, addr))
4670 return
4671
4672 self.deserialize(_struct, data)
4673
4674 _regsize = get_memory_alignment()
4675
4676 for field in _struct._fields_:
4677 _name, _type = field
4678 _value = getattr(_struct, _name)
4679 _offset = getattr(_class, _name).offset
4680
4681 if (_regsize == 4 and _type is ctypes.c_uint32) \
4682 or (_regsize == 8 and _type is ctypes.c_uint64) \
4683 or (_regsize == ctypes.sizeof(ctypes.c_void_p) and _type is ctypes.c_void_p):
4684 # try to dereference pointers
4685 _value = RIGHT_ARROW.join(DereferenceCommand.dereference_from(_value))
4686
4687 line = []
4688 line += " "*depth
4689 line += ("{:#x}+0x{:04x} {} : ".format(addr, _offset, _name)).ljust(40)
4690 line += "{} ({})".format(_value, _type.__name__)
4691 parsed_value = self.get_ctypes_value(_struct, _name, _value)
4692 if parsed_value:
4693 line += " {} {}".format(RIGHT_ARROW, parsed_value)
4694 gef_print("".join(line))
4695
4696 if issubclass(_type, ctypes.Structure):
4697 self.apply_structure_to_address(mod_name, _type.__name__, addr + _offset, depth + 1)
4698 return
4699
4700
4701 def get_ctypes_value(self, struct, item, value):
4702 if not hasattr(struct, "_values_"): return ""
4703 values_list = getattr(struct, "_values_")
4704 default = ""
4705 for name, values in values_list:
4706 if name != item: continue
4707 if callable(values):
4708 return values(value)
4709 try:
4710 for val, desc in values:
4711 if value == val: return desc
4712 if val is None: default = desc
4713 except:
4714 err("Error while trying to obtain values from _values_[\"{}\"]".format(name))
4715
4716 return default
4717
4718
4719 def create_or_edit_structure(self, mod_name, struct_name):
4720 path = self.get_struct_path()
4721 if path is None:
4722 err("Invalid struct path")
4723 return
4724
4725 fullname = self.pcustom_filepath(mod_name)
4726 if not self.is_valid_struct(mod_name):
4727 info("Creating '{:s}' from template".format(fullname))
4728 with open(fullname, "w") as f:
4729 f.write(self.get_template(struct_name))
4730 f.flush()
4731 else:
4732 info("Editing '{:s}'".format(fullname))
4733
4734 cmd = os.getenv("EDITOR").split() if os.getenv("EDITOR") else ["nano",]
4735 cmd.append(fullname)
4736 retcode = subprocess.call(cmd)
4737 return retcode
4738
4739
4740 def get_template(self, structname):
4741 d = [
4742 "from ctypes import *\n\n",
4743 "class ", structname, "(Structure):\n",
4744 " _fields_ = []\n"
4745 ]
4746 return "".join(d)
4747
4748
4749 def list_custom_structures(self):
4750 path = self.get_struct_path()
4751 if path is None:
4752 err("Cannot open '{0}': check directory and/or `gef config {0}` "
4753 "setting, currently: '{1}'".format("pcustom.struct_path", self.get_setting("struct_path")))
4754 return
4755
4756 info("Listing custom structures from '{:s}'".format(path))
4757 for filen in os.listdir(path):
4758 name, ext = os.path.splitext(filen)
4759 if ext != ".py": continue
4760 _modz = self.list_all_structs(name)
4761 ok("{:s} {:s} ({:s})".format(RIGHT_ARROW, name, ", ".join(_modz)))
4762 return
4763
4764
4765@register_command
4766class ChangeFdCommand(GenericCommand):
4767 """ChangeFdCommand: redirect file descriptor during runtime."""
4768
4769 _cmdline_ = "hijack-fd"
4770 _syntax_ = "{:s} FD_NUM NEW_OUTPUT".format(_cmdline_)
4771 _example_ = "{:s} 2 /tmp/stderr_output.txt".format(_cmdline_)
4772
4773 @only_if_gdb_running
4774 @only_if_gdb_target_local
4775 def do_invoke(self, argv):
4776 if len(argv)!=2:
4777 self.usage()
4778 return
4779
4780 if not os.access("/proc/{:d}/fd/{:s}".format(get_pid(), argv[0]), os.R_OK):
4781 self.usage()
4782 return
4783
4784 old_fd = int(argv[0])
4785 new_output = argv[1]
4786
4787 if ":" in new_output:
4788 address = socket.gethostbyname(new_output.split(":")[0])
4789 port = int(new_output.split(":")[1])
4790
4791 AF_INET = 2
4792 SOCK_STREAM = 1
4793 res = gdb.execute("""call (int)socket({}, {}, 0)""".format(AF_INET, SOCK_STREAM), to_string=True)
4794 new_fd = self.get_fd_from_result(res)
4795
4796 # fill in memory with sockaddr_in struct contents
4797 # we will do this in the stack, since connect() wants a pointer to a struct
4798 vmmap = get_process_maps()
4799 stack_addr = [entry.page_start for entry in vmmap if entry.path == "[stack]"][0]
4800 original_contents = read_memory(stack_addr, 8)
4801
4802 write_memory(stack_addr, "\x02\x00", 2)
4803 write_memory(stack_addr + 0x2, struct.pack("<H", socket.htons(port)), 2)
4804 write_memory(stack_addr + 0x4, socket.inet_aton(address), 4)
4805
4806 info("Trying to connect to {}".format(new_output))
4807 res = gdb.execute("""call (int)connect({}, {}, {})""".format(new_fd, stack_addr, 16), to_string=True)
4808
4809 # recover stack state
4810 write_memory(stack_addr, original_contents, 8)
4811
4812 res = self.get_fd_from_result(res)
4813 if res == -1:
4814 err("Failed to connect to {}:{}".format(address, port))
4815 return
4816
4817 info("Connected to {}".format(new_output))
4818 else:
4819 res = gdb.execute("""call (int)open("{:s}", 66, 0666)""".format(new_output), to_string=True)
4820 new_fd = self.get_fd_from_result(res)
4821
4822 info("Opened '{:s}' as fd #{:d}".format(new_output, new_fd))
4823 gdb.execute("""call (int)dup2({:d}, {:d})""".format(new_fd, old_fd), to_string=True)
4824 info("Duplicated fd #{:d}{:s}#{:d}".format(new_fd, RIGHT_ARROW, old_fd))
4825 gdb.execute("""call (int)close({:d})""".format(new_fd), to_string=True)
4826 info("Closed extra fd #{:d}".format(new_fd))
4827 ok("Success")
4828 return
4829
4830 def get_fd_from_result(self, res):
4831 # Output example: $1 = 3
4832 res = int(res.split()[2], 0)
4833 res = gdb.execute("""p/d {}""".format(res), to_string=True)
4834 res = int(res.split()[2], 0)
4835 return res
4836
4837
4838@register_command
4839class IdaInteractCommand(GenericCommand):
4840 """IDA Interact: set of commands to interact with IDA via a XML RPC service
4841 deployed via the IDA script `ida_gef.py`. It should be noted that this command
4842 can also be used to interact with Binary Ninja (using the script `binja_gef.py`)
4843 using the same interface."""
4844
4845 _cmdline_ = "ida-interact"
4846 _syntax_ = "{:s} METHOD [ARGS]".format(_cmdline_)
4847 _aliases_ = ["binaryninja-interact", "bn", "binja"]
4848 _example_ = "\n{0:s} Jump $pc\n{0:s} SetColor $pc ff00ff".format(_cmdline_)
4849
4850 def __init__(self):
4851 super(IdaInteractCommand, self).__init__(prefix=False)
4852 host, port = "127.0.0.1", 1337
4853 self.add_setting("host", host, "IP address to use connect to IDA/Binary Ninja script")
4854 self.add_setting("port", port, "Port to use connect to IDA/Binary Ninja script")
4855 self.add_setting("sync_cursor", False, "Enable real-time $pc synchronisation")
4856
4857 self.sock = None
4858 self.version = ("", "")
4859 self.old_bps = set()
4860 return
4861
4862 def is_target_alive(self, host, port):
4863 try:
4864 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4865 s.settimeout(1)
4866 s.connect((host, port))
4867 s.close()
4868 except socket.error:
4869 return False
4870 return True
4871
4872 def connect(self, host=None, port=None):
4873 """Connect to the XML-RPC service."""
4874 host = host or self.get_setting("host")
4875 port = port or self.get_setting("port")
4876
4877 try:
4878 sock = xmlrpclib.ServerProxy("http://{:s}:{:d}".format(host, port))
4879 gef_on_stop_hook(ida_synchronize_handler)
4880 gef_on_continue_hook(ida_synchronize_handler)
4881 self.version = sock.version()
4882 except ConnectionRefusedError:
4883 err("Failed to connect to '{:s}:{:d}'".format(host, port))
4884 sock = None
4885 self.sock = sock
4886 return
4887
4888 def disconnect(self):
4889 gef_on_stop_unhook(ida_synchronize_handler)
4890 gef_on_continue_unhook(ida_synchronize_handler)
4891 self.sock = None
4892 return
4893
4894 def do_invoke(self, argv):
4895 def parsed_arglist(arglist):
4896 args = []
4897 for arg in arglist:
4898 try:
4899 # try to solve the argument using gdb
4900 argval = gdb.parse_and_eval(arg)
4901 argval.fetch_lazy()
4902 # check if value is addressable
4903 argval = long(argval) if argval.address is None else long(argval.address)
4904 # if the bin is PIE, we need to substract the base address
4905 is_pie = checksec(get_filepath())["PIE"]
4906 if is_pie and main_base_address <= argval < main_end_address:
4907 argval -= main_base_address
4908 args.append("{:#x}".format(argval,))
4909 except Exception:
4910 # if gdb can't parse the value, let ida deal with it
4911 args.append(arg)
4912 return args
4913
4914 if self.sock is None:
4915 # trying to reconnect
4916 self.connect()
4917 if self.sock is None:
4918 self.disconnect()
4919 return
4920
4921 if len(argv) == 0 or argv[0] in ("-h", "--help"):
4922 method_name = argv[1] if len(argv)>1 else None
4923 self.usage(method_name)
4924 return
4925
4926 method_name = argv[0].lower()
4927 if method_name == "version":
4928 self.version = self.sock.version()
4929 info("Enhancing {:s} with {:s} (v.{:s})".format(Color.greenify("gef"),
4930 Color.redify(self.version[0]),
4931 Color.yellowify(self.version[1])))
4932 return
4933
4934 if not is_alive():
4935 main_base_address = main_end_address = 0
4936 else:
4937 vmmap = get_process_maps()
4938 main_base_address = min([x.page_start for x in vmmap if x.realpath == get_filepath()])
4939 main_end_address = max([x.page_end for x in vmmap if x.realpath == get_filepath()])
4940
4941 try:
4942 if method_name == "sync":
4943 self.synchronize()
4944 else:
4945 method = getattr(self.sock, method_name)
4946 if len(argv) > 1:
4947 args = parsed_arglist(argv[1:])
4948 res = method(*args)
4949 else:
4950 res = method()
4951
4952 if method_name == "importstruct":
4953 self.import_structures(res)
4954 else:
4955 gef_print(str(res))
4956
4957 if self.get_setting("sync_cursor") is True:
4958 jump = getattr(self.sock, "Jump")
4959 jump(hex(current_arch.pc-main_base_address),)
4960
4961 except socket.error:
4962 self.disconnect()
4963 return
4964
4965
4966 def synchronize(self):
4967 """Submit all active breakpoint addresses to IDA/BN."""
4968 pc = current_arch.pc
4969 vmmap = get_process_maps()
4970 base_address = min([x.page_start for x in vmmap if x.path == get_filepath()])
4971 end_address = max([x.page_end for x in vmmap if x.path == get_filepath()])
4972 if not (base_address <= pc < end_address):
4973 # do not sync in library
4974 return
4975
4976 breakpoints = gdb.breakpoints() or []
4977 gdb_bps = set()
4978 for bp in breakpoints:
4979 if bp.enabled and not bp.temporary:
4980 if bp.location[0]=="*": # if it's an address i.e. location starts with "*"
4981 addr = long(gdb.parse_and_eval(bp.location[1:]))
4982 else: # it is a symbol
4983 addr = long(gdb.parse_and_eval(bp.location).address)
4984 if not (base_address <= addr < end_address):
4985 continue
4986 gdb_bps.add(addr-base_address)
4987
4988 added = gdb_bps - self.old_bps
4989 removed = self.old_bps - gdb_bps
4990 self.old_bps = gdb_bps
4991
4992 try:
4993 # it is possible that the server was stopped between now and the last sync
4994 rc = self.sock.Sync("{:#x}".format(pc-base_address), list(added), list(removed))
4995 except ConnectionRefusedError:
4996 self.disconnect()
4997 return
4998
4999 ida_added, ida_removed = rc
5000
5001 # add new bp from IDA
5002 for new_bp in ida_added:
5003 location = base_address+new_bp
5004 gdb.Breakpoint("*{:#x}".format(location), type=gdb.BP_BREAKPOINT)
5005 self.old_bps.add(location)
5006
5007 # and remove the old ones
5008 breakpoints = gdb.breakpoints() or []
5009 for bp in breakpoints:
5010 if bp.enabled and not bp.temporary:
5011 if bp.location[0]=="*": # if it's an address i.e. location starts with "*"
5012 addr = long(gdb.parse_and_eval(bp.location[1:]))
5013 else: # it is a symbol
5014 addr = long(gdb.parse_and_eval(bp.location).address)
5015
5016 if not (base_address <= addr < end_address):
5017 continue
5018
5019 if (addr-base_address) in ida_removed:
5020 if (addr-base_address) in self.old_bps:
5021 self.old_bps.remove((addr-base_address))
5022 bp.delete()
5023 return
5024
5025
5026 def usage(self, meth=None):
5027 if self.sock is None:
5028 return
5029
5030 if meth is not None:
5031 gef_print(titlify(meth))
5032 gef_print(self.sock.system.methodHelp(meth))
5033 return
5034
5035 info("Listing available methods and syntax examples: ")
5036 for m in self.sock.system.listMethods():
5037 if m.startswith("system."): continue
5038 gef_print(titlify(m))
5039 gef_print(self.sock.system.methodHelp(m))
5040 return
5041
5042
5043 def import_structures(self, structs):
5044 if self.version[0] != "IDA Pro":
5045 return
5046
5047 path = get_gef_setting("pcustom.struct_path")
5048 if path is None:
5049 return
5050
5051 if not os.path.isdir(path):
5052 gef_makedirs(path)
5053
5054 for struct_name in structs:
5055 fullpath = os.path.join(path, "{}.py".format(struct_name))
5056 with open(fullpath, "w") as f:
5057 f.write("from ctypes import *\n\n")
5058 f.write("class ")
5059 f.write(struct_name)
5060 f.write("(Structure):\n")
5061 f.write(" _fields_ = [\n")
5062 for _, name, size in structs[struct_name]:
5063 name = bytes(name, encoding="utf-8")
5064 if size == 1: csize = "c_uint8"
5065 elif size == 2: csize = "c_uint16"
5066 elif size == 4: csize = "c_uint32"
5067 elif size == 8: csize = "c_uint64"
5068 else: csize = "c_byte * {}".format(size)
5069 m = ' (\"{}\", {}),\n'.format(name, csize)
5070 f.write(m)
5071 f.write("]\n")
5072 ok("Success, {:d} structure{:s} imported".format(len(structs),
5073 "s" if len(structs)>1 else ""))
5074 return
5075
5076
5077@register_command
5078class ScanSectionCommand(GenericCommand):
5079 """Search for addresses that are located in a memory mapping (haystack) that belonging
5080 to another (needle)."""
5081
5082 _cmdline_ = "scan"
5083 _syntax_ = "{:s} HAYSTACK NEEDLE".format(_cmdline_)
5084 _aliases_ = ["lookup",]
5085 _example_ = "\n{0:s} stack libc".format(_cmdline_)
5086
5087 @only_if_gdb_running
5088 def do_invoke(self, argv):
5089 if len(argv) != 2:
5090 self.usage()
5091 return
5092
5093 haystack = argv[0]
5094 needle = argv[1]
5095
5096 info("Searching for addresses in '{:s}' that point to '{:s}'"
5097 .format(Color.yellowify(haystack), Color.yellowify(needle)))
5098
5099 if haystack == "binary":
5100 haystack = get_filepath()
5101
5102 if needle == "binary":
5103 needle = get_filepath()
5104
5105 needle_sections = []
5106 haystack_sections = []
5107
5108 if "0x" in haystack:
5109 start, end = parse_string_range(haystack)
5110 haystack_sections.append((start, end, ""))
5111
5112 if "0x" in needle:
5113 start, end = parse_string_range(needle)
5114 needle_sections.append((start, end))
5115
5116 for sect in get_process_maps():
5117 if haystack in sect.path:
5118 haystack_sections.append((sect.page_start, sect.page_end, os.path.basename(sect.path)))
5119 if needle in sect.path:
5120 needle_sections.append((sect.page_start, sect.page_end))
5121
5122 step = current_arch.ptrsize
5123 fmt = "{}{}".format(endian_str(), "I" if step==4 else "Q")
5124
5125 for hstart, hend, hname in haystack_sections:
5126 try:
5127 mem = read_memory(hstart, hend - hstart)
5128 except gdb.MemoryError:
5129 continue
5130
5131 for i in range(0, len(mem), step):
5132 target = struct.unpack(fmt, mem[i:i+step])[0]
5133 for nstart, nend in needle_sections:
5134 if target >= nstart and target < nend:
5135 deref = DereferenceCommand.pprint_dereferenced(hstart, long(i / step))
5136 if hname != "":
5137 name = Color.colorify(hname, "yellow")
5138 gef_print("{:s}: {:s}".format(name, deref))
5139 else:
5140 gef_print(" {:s}".format(deref))
5141
5142 return
5143
5144@register_command
5145class SearchPatternCommand(GenericCommand):
5146 """SearchPatternCommand: search a pattern in memory. If given an hex value (starting with 0x)
5147 the command will also try to look for upwards cross-references to this address."""
5148
5149 _cmdline_ = "search-pattern"
5150 _syntax_ = "{:s} PATTERN [small|big] [section]".format(_cmdline_)
5151 _aliases_ = ["grep", "xref"]
5152 _example_ = "\n{0:s} AAAAAAAA\n{0:s} 0x555555554000 little stack\n{0:s}AAAA 0x600000-0x601000".format(_cmdline_)
5153
5154 def print_section(self, section):
5155 title = "In "
5156 if section.path:
5157 title += "'{}'".format(Color.blueify(section.path) )
5158
5159 title += "({:#x}-{:#x})".format(section.page_start, section.page_end)
5160 title += ", permission={}".format(section.permission)
5161 ok(title)
5162 return
5163
5164 def print_loc(self, loc):
5165 gef_print(""" {:#x} - {:#x} {} "{}" """.format(loc[0], loc[1], RIGHT_ARROW, Color.pinkify(loc[2]),))
5166 return
5167
5168 def search_pattern_by_address(self, pattern, start_address, end_address):
5169 """Search a pattern within a range defined by arguments."""
5170 pattern = gef_pybytes(pattern)
5171 step = 0x400 * 0x1000
5172 locations = []
5173
5174 for chunk_addr in range(start_address, end_address, step):
5175 if chunk_addr + step > end_address:
5176 chunk_size = end_address - chunk_addr
5177 else:
5178 chunk_size = step
5179
5180 mem = read_memory(chunk_addr, chunk_size)
5181
5182 for match in re.finditer(pattern, mem):
5183 start = chunk_addr + match.start()
5184 if is_ascii_string(start):
5185 ustr = read_ascii_string(start)
5186 end = start + len(ustr)
5187 else :
5188 ustr = gef_pystring(pattern)+"[...]"
5189 end = start + len(pattern)
5190 locations.append((start, end, ustr))
5191
5192 del mem
5193
5194 return locations
5195
5196 def search_pattern(self, pattern, section_name):
5197 """Search a pattern within the whole userland memory."""
5198 for section in get_process_maps():
5199 if not section.permission & Permission.READ: continue
5200 if section.path == "[vvar]": continue
5201 if not section_name in section.path: continue
5202
5203 start = section.page_start
5204 end = section.page_end - 1
5205 old_section = None
5206
5207 for loc in self.search_pattern_by_address(pattern, start, end):
5208 addr_loc_start = lookup_address(loc[0])
5209 if addr_loc_start and addr_loc_start.section:
5210 if old_section != addr_loc_start.section:
5211 self.print_section(addr_loc_start.section)
5212 old_section = addr_loc_start.section
5213
5214 self.print_loc(loc)
5215 return
5216
5217 @only_if_gdb_running
5218 def do_invoke(self, argv):
5219 argc = len(argv)
5220 if argc < 1:
5221 self.usage()
5222 return
5223
5224 pattern = argv[0]
5225 endian = get_endian()
5226
5227 if argc >= 2:
5228 if argv[1].lower() == "big": endian = Elf.BIG_ENDIAN
5229 elif argv[1].lower() == "small": endian = Elf.LITTLE_ENDIAN
5230
5231 if is_hex(pattern):
5232 if endian == Elf.BIG_ENDIAN:
5233 pattern = "".join(["\\x"+pattern[i:i+2] for i in range(2, len(pattern), 2)])
5234 else:
5235 pattern = "".join(["\\x"+pattern[i:i+2] for i in range(len(pattern) - 2, 0, -2)])
5236
5237 if argc == 3:
5238 info("Searching '{:s}' in {:s}".format(Color.yellowify(pattern), argv[2]))
5239
5240 if "0x" in argv[2]:
5241 start, end = parse_string_range(argv[2])
5242
5243 loc = lookup_address(start)
5244 if loc.valid:
5245 self.print_section(loc.section)
5246
5247 for loc in self.search_pattern_by_address(pattern, start, end):
5248 self.print_loc(loc)
5249 else:
5250 section_name = argv[2]
5251 if section_name == "binary":
5252 section_name = get_filepath()
5253
5254 self.search_pattern(pattern, section_name)
5255 else:
5256 info("Searching '{:s}' in memory".format(Color.yellowify(pattern)))
5257 self.search_pattern(pattern, "")
5258 return
5259
5260
5261@register_command
5262class FlagsCommand(GenericCommand):
5263 """Edit flags in a human friendly way."""
5264
5265 _cmdline_ = "edit-flags"
5266 _syntax_ = "{:s} [(+|-|~)FLAGNAME ...]".format(_cmdline_)
5267 _aliases_ = ["flags",]
5268 _example_ = "\n{0:s}\n{0:s} +zero # sets ZERO flag".format(_cmdline_)
5269
5270 def do_invoke(self, argv):
5271 for flag in argv:
5272 if len(flag)<2:
5273 continue
5274
5275 action = flag[0]
5276 name = flag[1:].lower()
5277
5278 if action not in ("+", "-", "~"):
5279 err("Invalid action for flag '{:s}'".format(flag))
5280 continue
5281
5282 if name not in current_arch.flags_table.values():
5283 err("Invalid flag name '{:s}'".format(flag[1:]))
5284 continue
5285
5286 for k in current_arch.flags_table:
5287 if current_arch.flags_table[k] == name:
5288 off = k
5289 break
5290
5291 old_flag = get_register(current_arch.flag_register)
5292 if action == "+":
5293 new_flags = old_flag | (1 << off)
5294 elif action == "-":
5295 new_flags = old_flag & ~(1 << off)
5296 else:
5297 new_flags = old_flag ^ (1<<off)
5298
5299 gdb.execute("set ({:s}) = {:#x}".format(current_arch.flag_register, new_flags))
5300
5301 gef_print(current_arch.flag_register_to_human())
5302 return
5303
5304
5305@register_command
5306class ChangePermissionCommand(GenericCommand):
5307 """Change a page permission. By default, it will change it to RWX."""
5308
5309 _cmdline_ = "set-permission"
5310 _syntax_ = "{:s} LOCATION [PERMISSION]".format(_cmdline_)
5311 _aliases_ = ["mprotect",]
5312 _example_ = "{:s} $sp 7"
5313
5314 def __init__(self):
5315 super(ChangePermissionCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
5316 return
5317
5318 def pre_load(self):
5319 try:
5320 __import__("keystone")
5321 except ImportError:
5322 msg = "Missing `keystone-engine` package for Python{0}, install with: `pip{0} install keystone-engine`.".format(PYTHON_MAJOR)
5323 raise ImportWarning(msg)
5324 return
5325
5326 @only_if_gdb_running
5327 def do_invoke(self, argv):
5328 if len(argv) not in (1, 2):
5329 err("Incorrect syntax")
5330 self.usage()
5331 return
5332
5333 if len(argv) == 2:
5334 perm = int(argv[1])
5335 else:
5336 perm = Permission.READ | Permission.WRITE | Permission.EXECUTE
5337
5338 loc = safe_parse_and_eval(argv[0])
5339 if loc is None:
5340 err("Invalid address")
5341 return
5342
5343 loc = long(loc)
5344 sect = process_lookup_address(loc)
5345 if sect is None:
5346 err("Unmapped address")
5347 return
5348
5349 size = sect.page_end - sect.page_start
5350 original_pc = current_arch.pc
5351
5352 info("Generating sys_mprotect({:#x}, {:#x}, '{:s}') stub for arch {:s}"
5353 .format(sect.page_start, size, str(Permission(value=perm)), get_arch()))
5354 stub = self.get_stub_by_arch(sect.page_start, size, perm)
5355 if stub is None:
5356 err("Failed to generate mprotect opcodes")
5357 return
5358
5359 info("Saving original code")
5360 original_code = read_memory(original_pc, len(stub))
5361
5362 bp_loc = "*{:#x}".format(original_pc + len(stub))
5363 info("Setting a restore breakpoint at {:s}".format(bp_loc))
5364 ChangePermissionBreakpoint(bp_loc, original_code, original_pc)
5365
5366 info("Overwriting current memory at {:#x} ({:d} bytes)".format(loc, len(stub)))
5367 write_memory(original_pc, stub, len(stub))
5368
5369 info("Resuming execution")
5370 gdb.execute("continue")
5371 return
5372
5373 def get_stub_by_arch(self, addr, size, perm):
5374 code = current_arch.mprotect_asm(addr, size, perm)
5375 arch, mode = get_keystone_arch()
5376 raw_insns = keystone_assemble(code, arch, mode, raw=True)
5377 return raw_insns
5378
5379
5380@register_command
5381class UnicornEmulateCommand(GenericCommand):
5382 """Use Unicorn-Engine to emulate the behavior of the binary, without affecting the GDB runtime.
5383 By default the command will emulate only the next instruction, but location and number of
5384 instruction can be changed via arguments to the command line. By default, it will emulate
5385 the next instruction from current PC."""
5386
5387 _cmdline_ = "unicorn-emulate"
5388 _syntax_ = "{:s} [-f LOCATION] [-t LOCATION] [-n NB_INSTRUCTION] [-s] [-o PATH] [-h]".format(_cmdline_)
5389 _aliases_ = ["emulate",]
5390 _example_ = "{0:s} -f $pc -n 10 -o /tmp/my-gef-emulation.py".format(_cmdline_)
5391
5392 def __init__(self):
5393 super(UnicornEmulateCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
5394 self.add_setting("verbose", False, "Set unicorn-engine in verbose mode")
5395 self.add_setting("show_disassembly", False, "Show every instruction executed")
5396 return
5397
5398 def help(self):
5399 h = self._syntax_
5400 h += "\n\t-f LOCATION specifies the start address of the emulated run (default $pc).\n"
5401 h += "\t-t LOCATION specifies the end address of the emulated run.\n"
5402 h += "\t-s Script-Only: do not execute the script once generated.\n"
5403 h += "\t-o /PATH/TO/SCRIPT.py writes the persistent Unicorn script into this file.\n"
5404 h += "\t-n NB_INSTRUCTION indicates the number of instructions to execute (mutually exclusive with `-t` and `-g`).\n"
5405 h += "\t-g NB_GADGET indicates the number of gadgets to execute (mutually exclusive with `-t` and `-n`).\n"
5406 h += "\nAdditional options can be setup via `gef config unicorn-emulate`\n"
5407 info(h)
5408 return
5409
5410 def pre_load(self):
5411 try:
5412 __import__("unicorn")
5413 except ImportError:
5414 msg = "Missing `unicorn` package for Python{0}. Install with `pip{0} install unicorn`.".format(PYTHON_MAJOR)
5415 raise ImportWarning(msg)
5416
5417 try:
5418 __import__("capstone")
5419 except ImportError:
5420 msg = "Missing `capstone` package for Python{0}. Install with `pip{0} install capstone`.".format(PYTHON_MAJOR)
5421 raise ImportWarning(msg)
5422 return
5423
5424 @only_if_gdb_running
5425 def do_invoke(self, argv):
5426 start_insn = None
5427 end_insn = -1
5428 nb_insn = -1
5429 to_file = None
5430 to_script_only = None
5431 opts = getopt.getopt(argv, "f:t:n:so:h")[0]
5432 for o,a in opts:
5433 if o == "-f": start_insn = int(a, 16)
5434 elif o == "-t":
5435 end_insn = int(a, 16)
5436 self.nb_insn = -1
5437
5438 elif o == "-n":
5439 nb_insn = int(a)
5440 end_insn = -1
5441
5442 elif o == "-s":
5443 to_script_only = True
5444
5445 elif o == "-o":
5446 to_file = a
5447
5448 elif o == "-h":
5449 self.help()
5450 return
5451
5452 if start_insn is None:
5453 start_insn = current_arch.pc
5454
5455 if end_insn < 0 and nb_insn < 0:
5456 err("No stop condition (-t|-n) defined.")
5457 return
5458
5459 if end_insn > 0:
5460 self.run_unicorn(start_insn, end_insn, to_script_only=to_script_only, to_file=to_file)
5461
5462 elif nb_insn > 0:
5463 end_insn = self.get_unicorn_end_addr(start_insn, nb_insn)
5464 self.run_unicorn(start_insn, end_insn, to_script_only=to_script_only, to_file=to_file)
5465
5466 else:
5467 raise Exception("Should never be here")
5468 return
5469
5470 def get_unicorn_end_addr(self, start_addr, nb):
5471 dis = list(gef_disassemble(start_addr, nb+1, True))
5472 last_insn = dis[-1]
5473 return last_insn.address
5474
5475 def run_unicorn(self, start_insn_addr, end_insn_addr, *args, **kwargs):
5476 verbose = self.get_setting("verbose") or False
5477 to_script_only = kwargs.get("to_script_only", False)
5478 arch, mode = get_unicorn_arch(to_string=True)
5479 unicorn_registers = get_unicorn_registers(to_string=True)
5480 cs_arch, cs_mode = get_capstone_arch(to_string=True)
5481 fname = get_filename()
5482 to_file = kwargs.get("to_file", None)
5483
5484 if to_file:
5485 tmp_filename = to_file
5486 to_file = open(to_file, "w")
5487 tmp_fd = to_file.fileno()
5488 else:
5489 tmp_fd, tmp_filename = tempfile.mkstemp(suffix=".py", prefix="gef-uc-")
5490
5491 if is_x86():
5492 # need to handle segmentation (and pagination) via MSR
5493 emulate_segmentation_block = """
5494# from https://github.com/unicorn-engine/unicorn/blob/master/tests/regress/x86_64_msr.py
5495SCRATCH_ADDR = 0xf000
5496SEGMENT_FS_ADDR = 0x5000
5497SEGMENT_GS_ADDR = 0x6000
5498FSMSR = 0xC0000100
5499GSMSR = 0xC0000101
5500
5501def set_msr(uc, msr, value, scratch=SCRATCH_ADDR):
5502 buf = b"\\x0f\\x30" # x86: wrmsr
5503 uc.mem_map(scratch, 0x1000)
5504 uc.mem_write(scratch, buf)
5505 uc.reg_write(unicorn.x86_const.UC_X86_REG_RAX, value & 0xFFFFFFFF)
5506 uc.reg_write(unicorn.x86_const.UC_X86_REG_RDX, (value >> 32) & 0xFFFFFFFF)
5507 uc.reg_write(unicorn.x86_const.UC_X86_REG_RCX, msr & 0xFFFFFFFF)
5508 uc.emu_start(scratch, scratch+len(buf), count=1)
5509 uc.mem_unmap(scratch, 0x1000)
5510 return
5511
5512def set_gs(uc, addr): return set_msr(uc, GSMSR, addr)
5513def set_fs(uc, addr): return set_msr(uc, FSMSR, addr)
5514
5515"""
5516
5517 context_segmentation_block = """
5518 emu.mem_map(SEGMENT_FS_ADDR-0x1000, 0x3000)
5519 set_fs(emu, SEGMENT_FS_ADDR)
5520 set_gs(emu, SEGMENT_GS_ADDR)
5521"""
5522
5523
5524 content = """#!/usr/bin/python -i
5525#
5526# Emulation script for "{fname}" from {start:#x} to {end:#x}
5527#
5528# Powered by gef, unicorn-engine, and capstone-engine
5529#
5530# @_hugsy_
5531#
5532from __future__ import print_function
5533import collections
5534import capstone, unicorn
5535
5536registers = collections.OrderedDict(sorted({{{regs}}}.items(), key=lambda t: t[0]))
5537uc = None
5538verbose = {verbose}
5539syscall_register = "{syscall_reg}"
5540
5541def disassemble(code, addr):
5542 cs = capstone.Cs({cs_arch}, {cs_mode})
5543 for i in cs.disasm(code, addr):
5544 return i
5545
5546def hook_code(emu, address, size, user_data):
5547 code = emu.mem_read(address, size)
5548 insn = disassemble(code, address)
5549 print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str))
5550 return
5551
5552def code_hook(emu, address, size, user_data):
5553 code = emu.mem_read(address, size)
5554 insn = disassemble(code, address)
5555 print(">>> {{:#x}}: {{:s}} {{:s}}".format(insn.address, insn.mnemonic, insn.op_str))
5556 return
5557
5558def intr_hook(emu, intno, data):
5559 print(" \\-> interrupt={{:d}}".format(intno))
5560 return
5561
5562def syscall_hook(emu, user_data):
5563 sysno = emu.reg_read(registers[syscall_register])
5564 print(" \\-> syscall={{:d}}".format(sysno))
5565 return
5566
5567def print_regs(emu, regs):
5568 for i, r in enumerate(regs):
5569 print("{{:7s}} = {{:#0{ptrsize}x}} ".format(r, emu.reg_read(regs[r])), end="")
5570 if (i % 4 == 3) or (i == len(regs)-1): print("")
5571 return
5572
5573{emu_block}
5574
5575def reset():
5576 emu = unicorn.Uc({arch}, {mode})
5577
5578{context_block}
5579""".format(fname=fname, start=start_insn_addr, end=end_insn_addr,
5580 regs=",".join(["'%s': %s" % (k.strip(), unicorn_registers[k]) for k in unicorn_registers]),
5581 verbose="True" if verbose else "False",
5582 syscall_reg=current_arch.syscall_register,
5583 cs_arch=cs_arch, cs_mode=cs_mode,
5584 ptrsize=current_arch.ptrsize,
5585 emu_block=emulate_segmentation_block if is_x86() else "",
5586 arch=arch, mode=mode,
5587 context_block=context_segmentation_block if is_x86() else "")
5588
5589 if verbose:
5590 info("Duplicating registers")
5591
5592 for r in current_arch.all_registers:
5593 gregval = get_register(r)
5594 content += " emu.reg_write({}, {:#x})\n".format(unicorn_registers[r], gregval)
5595
5596
5597 vmmap = get_process_maps()
5598 if not vmmap:
5599 warn("An error occured when reading memory map.")
5600 return
5601
5602 if verbose:
5603 info("Duplicating memory map")
5604
5605 for sect in vmmap:
5606 if sect.path == "[vvar]":
5607 # this section is for GDB only, skip it
5608 continue
5609
5610 page_start = sect.page_start
5611 page_end = sect.page_end
5612 size = sect.size
5613 perm = sect.permission
5614
5615 content += " # Mapping {}: {:#x}-{:#x}\n".format(sect.path, page_start, page_end)
5616 content += " emu.mem_map({:#x}, {:#x}, {})\n".format(page_start, size, oct(perm.value))
5617
5618 if perm & Permission.READ:
5619 code = read_memory(page_start, size)
5620 loc = "/tmp/gef-{}-{:#x}.raw".format(fname, page_start)
5621 with open(loc, "wb") as f:
5622 f.write(bytes(code))
5623
5624 content += " emu.mem_write({:#x}, open('{}', 'rb').read())\n".format(page_start, loc)
5625 content += "\n"
5626
5627
5628 content += " emu.hook_add(unicorn.UC_HOOK_CODE, code_hook)\n"
5629 content += " emu.hook_add(unicorn.UC_HOOK_INTR, intr_hook)\n"
5630 if is_x86_64():
5631 content += " emu.hook_add(unicorn.UC_HOOK_INSN, syscall_hook, None, 1, 0, unicorn.x86_const.UC_X86_INS_SYSCALL)\n"
5632 content += " return emu\n"
5633
5634 content += """
5635def emulate(emu, start_addr, end_addr):
5636 print("========================= Initial registers =========================")
5637 print_regs(emu, registers)
5638
5639 try:
5640 print("========================= Starting emulation =========================")
5641 emu.emu_start(start_addr, end_addr)
5642 except Exception as e:
5643 emu.emu_stop()
5644 print("========================= Emulation failed =========================")
5645 print("[!] Error: {{}}".format(e))
5646
5647 print("========================= Final registers =========================")
5648 print_regs(emu, registers)
5649 return
5650
5651
5652uc = reset()
5653emulate(uc, {start:#x}, {end:#x})
5654
5655# unicorn-engine script generated by gef
5656""".format(start=start_insn_addr, end=end_insn_addr)
5657
5658 os.write(tmp_fd, gef_pybytes(content))
5659 os.close(tmp_fd)
5660
5661 if kwargs.get("to_file", None):
5662 info("Unicorn script generated as '{}'".format(tmp_filename))
5663 os.chmod(tmp_filename, 0o700)
5664
5665 if to_script_only:
5666 return
5667
5668 ok("Starting emulation: {:#x} {} {:#x}".format(start_insn_addr, RIGHT_ARROW, end_insn_addr))
5669
5670 pythonbin = "python{}".format(PYTHON_MAJOR)
5671 res = gef_execute_external([pythonbin, tmp_filename], as_list=True)
5672 gef_print("\n".join(res))
5673
5674 if not kwargs.get("to_file", None):
5675 os.unlink(tmp_filename)
5676 return
5677
5678
5679@register_command
5680class RemoteCommand(GenericCommand):
5681 """gef wrapper for the `target remote` command. This command will automatically
5682 download the target binary in the local temporary directory (defaut /tmp) and then
5683 source it. Additionally, it will fetch all the /proc/PID/maps and loads all its
5684 information."""
5685
5686 _cmdline_ = "gef-remote"
5687 _syntax_ = "{:s} [OPTIONS] TARGET".format(_cmdline_)
5688 _example_ = "\n{0:s} -p 6789 localhost:1234\n{0:s} -q localhost:4444 # when using qemu-user".format(_cmdline_)
5689
5690 def __init__(self):
5691 super(RemoteCommand, self).__init__(prefix=False)
5692 self.handler_connected = False
5693 self.add_setting("clean_on_exit", False, "Clean the temporary data downloaded when the session exits.")
5694 return
5695
5696 def do_invoke(self, argv):
5697 global __gef_remote__
5698
5699 if __gef_remote__ is not None:
5700 err("You already are in remote session. Close it first before opening a new one...")
5701 return
5702
5703 target = None
5704 rpid = -1
5705 update_solib = False
5706 self.download_all_libs = False
5707 download_lib = None
5708 is_extended_remote = False
5709 qemu_gdb_mode = False
5710 opts, args = getopt.getopt(argv, "p:UD:qAEh")
5711 for o,a in opts:
5712 if o == "-U": update_solib = True
5713 elif o == "-D": download_lib = a
5714 elif o == "-A": self.download_all_libs = True
5715 elif o == "-E": is_extended_remote = True
5716 elif o == "-p": rpid = int(a)
5717 elif o == "-q": qemu_gdb_mode = True
5718 elif o == "-h":
5719 self.help()
5720 return
5721
5722 if not args or ":" not in args[0]:
5723 err("A target (HOST:PORT) must always be provided.")
5724 return
5725
5726 if qemu_gdb_mode:
5727 # compat layer for qemu-user
5728 self.prepare_qemu_stub(args[0])
5729 return
5730
5731 # lazily install handler on first use
5732 if not self.handler_connected:
5733 gef_on_new_hook(self.new_objfile_handler)
5734 self.handler_connected = True
5735
5736 target = args[0]
5737
5738 if self.connect_target(target, is_extended_remote) is False:
5739 return
5740
5741 # if extended-remote, need to attach
5742 if is_extended_remote:
5743 ok("Attaching to {:d}".format(rpid))
5744 hide_context()
5745 gdb.execute("attach {:d}".format(rpid))
5746 unhide_context()
5747 else:
5748 rpid = get_pid()
5749 ok("Targeting PID={:d}".format(rpid))
5750
5751 self.add_setting("target", target, "Remote target to connect to")
5752 self.setup_remote_environment(rpid, update_solib)
5753
5754 if not is_remote_debug():
5755 err("Failed to establish remote target environment.")
5756 return
5757
5758 if self.download_all_libs is True:
5759 vmmap = get_process_maps()
5760 success = 0
5761 for sect in vmmap:
5762 if sect.path.startswith("/"):
5763 _file = download_file(sect.path)
5764 if _file is None:
5765 err("Failed to download {:s}".format(sect.path))
5766 else:
5767 success += 1
5768
5769 ok("Downloaded {:d} files".format(success))
5770
5771 elif download_lib is not None:
5772 _file = download_file(download_lib)
5773 if _file is None:
5774 err("Failed to download remote file")
5775 return
5776
5777 ok("Download success: {:s} {:s} {:s}".format(download_lib, RIGHT_ARROW, _file))
5778
5779 if update_solib:
5780 self.refresh_shared_library_path()
5781
5782 set_arch()
5783 __gef_remote__ = rpid
5784 return
5785
5786 def new_objfile_handler(self, event):
5787 """Hook that handles new_objfile events, will update remote environment accordingly."""
5788 if not is_remote_debug():
5789 return
5790
5791 if self.download_all_libs and event.new_objfile.filename.startswith("target:"):
5792 lib = event.new_objfile.filename[len("target:"):]
5793 llib = download_file(lib, use_cache=True)
5794 if llib:
5795 ok("Download success: {:s} {:s} {:s}".format(lib, RIGHT_ARROW, llib))
5796 return
5797
5798
5799 def setup_remote_environment(self, pid, update_solib=False):
5800 """Clone the remote environment locally in the temporary directory.
5801 The command will duplicate the entries in the /proc/<pid> locally and then
5802 source those information into the current gdb context to allow gef to use
5803 all the extra commands as it was local debugging."""
5804 gdb.execute("reset-cache")
5805
5806 infos = {}
5807 for i in ("maps", "environ", "cmdline",):
5808 infos[i] = self.load_from_remote_proc(pid, i)
5809 if infos[i] is None:
5810 err("Failed to load memory map of '{:s}'".format(i))
5811 return
5812
5813 exepath = get_path_from_info_proc()
5814 infos["exe"] = download_file("/proc/{:d}/exe".format(pid), use_cache=False, local_name=exepath)
5815 if not os.access(infos["exe"], os.R_OK):
5816 err("Source binary is not readable")
5817 return
5818
5819 directory = os.path.sep.join([GEF_TEMP_DIR, str(get_pid())])
5820 # gdb.execute("file {:s}".format(infos["exe"]))
5821 self.add_setting("root", directory, "Path to store the remote data")
5822 ok("Remote information loaded to temporary path '{:s}'".format(directory))
5823 return
5824
5825
5826 def connect_target(self, target, is_extended_remote):
5827 """Connect to remote target and get symbols. To prevent `gef` from requesting information
5828 not fetched just yet, we disable the context disable when connection was successful."""
5829 hide_context()
5830 try:
5831 cmd = "target {} {}".format("extended-remote" if is_extended_remote else "remote", target)
5832 gdb.execute(cmd)
5833 ok("Connected to '{}'".format(target))
5834 ret = True
5835 except Exception as e:
5836 err("Failed to connect to {:s}: {:s}".format(target, str(e)))
5837 ret = False
5838 unhide_context()
5839 return ret
5840
5841
5842 def load_from_remote_proc(self, pid, info):
5843 """Download one item from /proc/pid."""
5844 remote_name = "/proc/{:d}/{:s}".format(pid, info)
5845 return download_file(remote_name, use_cache=False)
5846
5847
5848 def refresh_shared_library_path(self):
5849 dirs = [r for r, d, f in os.walk(self.get_setting("root"))]
5850 path = ":".join(dirs)
5851 gdb.execute("set solib-search-path {:s}".format(path,))
5852 return
5853
5854
5855 def help(self):
5856 h = self._syntax_
5857 h += "\n\t TARGET (mandatory) specifies the host:port, serial port or tty to connect to.\n"
5858 h += "\t-U will update gdb `solib-search-path` attribute to include the files downloaded from server (default: False).\n"
5859 h += "\t-A will download *ALL* the remote shared libraries and store them in the new environment. " \
5860 "This command can take a few minutes to complete (default: False).\n"
5861 h += "\t-D LIB will download the remote library called LIB.\n"
5862 h += "\t-E Use 'extended-remote' to connect to the target.\n"
5863 h += "\t-p PID (mandatory if -E is used) specifies PID of the debugged process on gdbserver's end.\n"
5864 h += "\t-q Uses this option when connecting to a Qemu GDBserver.\n"
5865 info(h)
5866 return
5867
5868
5869 def prepare_qemu_stub(self, target):
5870 global current_arch, current_elf, __gef_qemu_mode__
5871
5872 reset_all_caches()
5873 arch = get_arch()
5874 current_elf = Elf(minimalist=True)
5875 if arch.startswith("arm"):
5876 current_elf.e_machine = Elf.ARM
5877 current_arch = ARM()
5878 elif arch.startswith("aarch64"):
5879 current_elf.e_machine = Elf.AARCH64
5880 current_arch = AARCH64()
5881 elif arch.startswith("i386:intel"):
5882 current_elf.e_machine = Elf.X86_32
5883 current_arch = X86()
5884 elif arch.startswith("i386:x86-64"):
5885 current_elf.e_machine = Elf.X86_64
5886 current_elf.e_class = Elf.ELF_64_BITS
5887 current_arch = X86_64()
5888 elif arch.startswith("mips"):
5889 current_elf.e_machine = Elf.MIPS
5890 current_arch = MIPS()
5891 elif arch.startswith("powerpc"):
5892 current_elf.e_machine = Elf.POWERPC
5893 current_arch = PowerPC()
5894 elif arch.startswith("sparc"):
5895 current_elf.e_machine = Elf.SPARC
5896 current_arch = SPARC()
5897 else:
5898 raise RuntimeError("unsupported architecture: {}".format(arch))
5899
5900 ok("Setting QEMU-stub for '{}' (memory mapping may be wrong)".format(current_arch.arch))
5901 gdb.execute("target remote {}".format(target))
5902 __gef_qemu_mode__ = True
5903 return
5904
5905
5906@register_command
5907class NopCommand(GenericCommand):
5908 """Patch the instruction(s) pointed by parameters with NOP. Note: this command is architecture
5909 aware."""
5910
5911 _cmdline_ = "nop"
5912 _syntax_ = "{:s} [-b NUM_BYTES] [-h] [LOCATION]".format(_cmdline_)
5913 _example_ = "{:s} $pc".format(_cmdline_)
5914
5915 def __init__(self):
5916 super(NopCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
5917 return
5918
5919
5920 def get_insn_size(self, addr):
5921 cur_insn = gef_current_instruction(addr)
5922 next_insn = gef_instruction_n(addr, 2)
5923 return next_insn.address - cur_insn.address
5924
5925
5926 def do_invoke(self, argv):
5927 opts, args = getopt.getopt(argv, "b:h")
5928 num_bytes = 0
5929 for o, a in opts:
5930 if o == "-b":
5931 num_bytes = long(a, 0)
5932 elif o == "-h":
5933 self.help()
5934 return
5935
5936 if args:
5937 loc = parse_address(args[0])
5938 else:
5939 loc = current_arch.pc
5940
5941 self.nop_bytes(loc, num_bytes)
5942 return
5943
5944
5945 def help(self):
5946 m = self._syntax_
5947 m += "\n LOCATION\taddress/symbol to patch\n"
5948 m += " -b NUM_BYTES\tInstead of writing one instruction, patch the specified number of bytes\n"
5949 m += " -h \t\tprint this help\n"
5950 info(m)
5951 return
5952
5953 @only_if_gdb_running
5954 def nop_bytes(self, loc, num_bytes):
5955 if num_bytes == 0:
5956 size = self.get_insn_size(loc)
5957 else:
5958 size = num_bytes
5959 nops = current_arch.nop_insn
5960
5961 if len(nops) > size:
5962 m = "Cannot patch instruction at {:#x} (nop_size is:{:d},insn_size is:{:d})".format(loc, len(nops), size)
5963 err(m)
5964 return
5965
5966 while len(nops) < size:
5967 nops += current_arch.nop_insn
5968
5969 if len(nops) != size:
5970 err("Cannot patch instruction at {:#x} (nop instruction does not evenly fit in requested size)"
5971 .format(loc))
5972 return
5973
5974 ok("Patching {:d} bytes from {:s}".format(size, format_address(loc)))
5975 write_memory(loc, nops, size)
5976
5977 return
5978
5979
5980@register_command
5981class StubCommand(GenericCommand):
5982 """Stub out the specified function. This function is useful when needing to skip one
5983 function to be called and disrupt your runtime flow (ex. fork)."""
5984
5985 _cmdline_ = "stub"
5986 _syntax_ = """{:s} [-r RETVAL] [-h] [LOCATION]
5987\tLOCATION\taddress/symbol to stub out
5988\t-r RETVAL\tSet the return value""".format(_cmdline_)
5989 _example_ = "{:s} -r 0 fork"
5990
5991 def __init__(self):
5992 super(StubCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
5993 return
5994
5995 @only_if_gdb_running
5996 def do_invoke(self, argv):
5997 try:
5998 opts, args = getopt.getopt(argv, "r:")
5999 retval = 0
6000 for o, a in opts:
6001 if o == "-r":
6002 retval = long(a, 0)
6003 except getopt.GetoptError:
6004 self.usage()
6005 return
6006
6007 loc = args[0] if args else "*{:#x}".format(current_arch.pc)
6008 StubBreakpoint(loc, retval)
6009 return
6010
6011
6012@register_command
6013class CapstoneDisassembleCommand(GenericCommand):
6014 """Use capstone disassembly framework to disassemble code."""
6015
6016 _cmdline_ = "capstone-disassemble"
6017 _syntax_ = "{:s} [LOCATION] [[length=LENGTH] [option=VALUE]] ".format(_cmdline_)
6018 _aliases_ = ["cs-dis",]
6019 _example_ = "{:s} $pc length=50".format(_cmdline_)
6020
6021 def pre_load(self):
6022 try:
6023 __import__("capstone")
6024 except ImportError:
6025 msg = "Missing `capstone` package for Python{0}. Install with `pip{0} install capstone`.".format(PYTHON_MAJOR)
6026 raise ImportWarning(msg)
6027 return
6028
6029 def __init__(self):
6030 super(CapstoneDisassembleCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6031 return
6032
6033 @only_if_gdb_running
6034 def do_invoke(self, argv):
6035 location = None
6036
6037 kwargs = {}
6038 for arg in argv:
6039 if "=" in arg:
6040 key, value = arg.split("=", 1)
6041 kwargs[key] = value
6042
6043 elif location is None:
6044 location = parse_address(arg)
6045
6046 location = location or current_arch.pc
6047 length = int(kwargs.get("length", get_gef_setting("context.nb_lines_code")))
6048
6049 for insn in capstone_disassemble(location, length, skip=length*self.repeat_count, **kwargs):
6050 text_insn = str(insn)
6051 msg = ""
6052
6053 if insn.address == current_arch.pc:
6054 msg = Color.colorify("{} {}".format(RIGHT_ARROW, text_insn), "bold red")
6055 reason = self.capstone_analyze_pc(insn, length)[0]
6056 if reason:
6057 gef_print(msg)
6058 gef_print(reason)
6059 break
6060 else:
6061 msg = "{} {}".format(" "*5, text_insn)
6062
6063 gef_print(msg)
6064 return
6065
6066 def capstone_analyze_pc(self, insn, nb_insn):
6067 if current_arch.is_conditional_branch(insn):
6068 is_taken, reason = current_arch.is_branch_taken(insn)
6069 if is_taken:
6070 reason = "[Reason: {:s}]".format(reason) if reason else ""
6071 msg = Color.colorify("\tTAKEN {:s}".format(reason), "bold green")
6072 else:
6073 reason = "[Reason: !({:s})]".format(reason) if reason else ""
6074 msg = Color.colorify("\tNOT taken {:s}".format(reason), "bold red")
6075 return (is_taken, msg)
6076
6077 if current_arch.is_call(insn):
6078 target_address = int(insn.operands[-1].split()[0], 16)
6079 msg = []
6080 for i, new_insn in enumerate(capstone_disassemble(target_address, nb_insn)):
6081 msg.append(" {} {}".format (DOWN_ARROW if i==0 else " ", str(new_insn)))
6082 return (True, "\n".join(msg))
6083
6084 return (False, "")
6085
6086
6087@register_command
6088class GlibcHeapCommand(GenericCommand):
6089 """Base command to get information about the Glibc heap structure."""
6090
6091 _cmdline_ = "heap"
6092 _syntax_ = "{:s} (chunk|chunks|bins|arenas)".format(_cmdline_)
6093
6094 def __init__(self):
6095 super(GlibcHeapCommand, self).__init__(prefix=True)
6096 return
6097
6098 @only_if_gdb_running
6099 def do_invoke(self, argv):
6100 self.usage()
6101 return
6102
6103
6104@register_command
6105class GlibcHeapSetArenaCommand(GenericCommand):
6106 """Display information on a heap chunk."""
6107
6108 _cmdline_ = "heap set-arena"
6109 _syntax_ = "{:s} LOCATION".format(_cmdline_)
6110 _example_ = "{:s} 0x001337001337".format(_cmdline_)
6111
6112 def __init__(self):
6113 super(GlibcHeapSetArenaCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6114 return
6115
6116 @only_if_gdb_running
6117 def do_invoke(self, argv):
6118 global __gef_default_main_arena__
6119
6120 if not argv:
6121 ok("Current main_arena set to: '{}'".format(__gef_default_main_arena__))
6122 return
6123
6124 new_arena = safe_parse_and_eval(argv[0])
6125 if new_arena is None:
6126 err("Invalid location")
6127 return
6128
6129 if argv[0].startswith("0x"):
6130 new_arena = Address(value=to_unsigned_long(new_arena))
6131 if new_arena is None or not new_arena.valid:
6132 err("Invalid location")
6133 return
6134
6135 __gef_default_main_arena__ = "*{:s}".format(format_address(new_arena.value))
6136 else:
6137 __gef_default_main_arena__ = argv[0]
6138 return
6139
6140@register_command
6141class GlibcHeapArenaCommand(GenericCommand):
6142 """Display information on a heap chunk."""
6143
6144 _cmdline_ = "heap arenas"
6145 _syntax_ = _cmdline_
6146
6147 @only_if_gdb_running
6148 def do_invoke(self, argv):
6149 try:
6150 arena = GlibcArena(__gef_default_main_arena__)
6151 except gdb.error:
6152 err("Could not find Glibc main arena")
6153 return
6154
6155 while True:
6156 gef_print("{}".format(arena))
6157 arena = arena.get_next()
6158 if arena is None:
6159 break
6160 return
6161
6162@register_command
6163class GlibcHeapChunkCommand(GenericCommand):
6164 """Display information on a heap chunk.
6165 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123."""
6166
6167 _cmdline_ = "heap chunk"
6168 _syntax_ = "{:s} LOCATION".format(_cmdline_)
6169
6170 def __init__(self):
6171 super(GlibcHeapChunkCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6172 return
6173
6174 @only_if_gdb_running
6175 def do_invoke(self, argv):
6176 if not argv:
6177 err("Missing chunk address")
6178 self.usage()
6179 return
6180
6181 if get_main_arena() is None:
6182 return
6183
6184 addr = to_unsigned_long(gdb.parse_and_eval(argv[0]))
6185 chunk = GlibcChunk(addr)
6186 gef_print(chunk.psprint())
6187 return
6188
6189@register_command
6190class GlibcHeapChunksCommand(GenericCommand):
6191 """Display information all chunks from main_arena heap. If a location is passed,
6192 it must correspond to the base address of the first chunk."""
6193
6194 _cmdline_ = "heap chunks"
6195 _syntax_ = "{0} [LOCATION]".format(_cmdline_)
6196 _example_ = "\n{0}\n{0} 0x555555775000".format(_cmdline_)
6197
6198 def __init__(self):
6199 super(GlibcHeapChunksCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6200 self.add_setting("peek_nb_byte", 16, "Hexdump N first byte(s) inside the chunk data (0 to disable)")
6201 return
6202
6203 @only_if_gdb_running
6204 def do_invoke(self, argv):
6205
6206 if not argv:
6207 heap_section = [x for x in get_process_maps() if x.path == "[heap]"]
6208 if not heap_section:
6209 err("No heap section")
6210 return
6211
6212 heap_section = heap_section[0].page_start
6213 else:
6214 heap_section = int(argv[0], 0)
6215
6216
6217 arena = get_main_arena()
6218 if arena is None:
6219 err("No valid arena")
6220 return
6221
6222 nb = self.get_setting("peek_nb_byte")
6223 current_chunk = GlibcChunk(heap_section, from_base=True)
6224 while True:
6225
6226 if current_chunk.chunk_base_address == arena.top:
6227 gef_print("{} {} {}".format(str(current_chunk), LEFT_ARROW, Color.greenify("top chunk")))
6228 break
6229
6230 if current_chunk.chunk_base_address > arena.top:
6231 break
6232
6233 if current_chunk.size == 0:
6234 # EOF
6235 break
6236
6237 line = str(current_chunk)
6238 if nb:
6239 line += "\n [" + hexdump(read_memory(current_chunk.address, nb), nb, base=current_chunk.address) + "]"
6240 gef_print(line)
6241
6242 next_chunk = current_chunk.get_next_chunk()
6243 if next_chunk is None:
6244 break
6245
6246 next_chunk_addr = Address(value=next_chunk.address)
6247 if not next_chunk_addr.valid:
6248 # corrupted
6249 break
6250
6251
6252 current_chunk = next_chunk
6253 return
6254
6255@register_command
6256class GlibcHeapBinsCommand(GenericCommand):
6257 """Display information on the bins on an arena (default: main_arena).
6258 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123."""
6259
6260 _bin_types_ = ["tcache", "fast", "unsorted", "small", "large"]
6261 _cmdline_ = "heap bins"
6262 _syntax_ = "{:s} [{:s}]".format(_cmdline_, "|".join(_bin_types_))
6263
6264 def __init__(self):
6265 super(GlibcHeapBinsCommand, self).__init__(prefix=True, complete=gdb.COMPLETE_LOCATION)
6266 return
6267
6268 @only_if_gdb_running
6269 def do_invoke(self, argv):
6270 if not argv:
6271 for bin_t in GlibcHeapBinsCommand._bin_types_:
6272 gdb.execute("heap bins {:s}".format(bin_t))
6273 return
6274
6275 bin_t = argv[0]
6276 if bin_t not in GlibcHeapBinsCommand._bin_types_:
6277 self.usage()
6278 return
6279
6280 gdb.execute("heap bins {}".format(bin_t))
6281 return
6282
6283 @staticmethod
6284 def pprint_bin(arena_addr, index, _type=""):
6285 arena = GlibcArena(arena_addr)
6286 fw, bk = arena.bin(index)
6287
6288 if bk==0x00 and fw==0x00:
6289 warn("Invalid backward and forward bin pointers(fw==bk==NULL)")
6290 return -1
6291
6292 nb_chunk = 0
6293 head = GlibcChunk(bk, from_base=True).fwd
6294 if fw == head:
6295 return nb_chunk
6296
6297 ok("{}bins[{:d}]: fw={:#x}, bk={:#x}".format(_type, index, fw, bk))
6298
6299 m = []
6300 while fw != head:
6301 chunk = GlibcChunk(fw, from_base=True)
6302 m.append("{:s} {:s}".format(RIGHT_ARROW, str(chunk)))
6303 fw = chunk.fwd
6304 nb_chunk += 1
6305
6306 if m:
6307 gef_print(" ".join(m))
6308 return nb_chunk
6309
6310
6311@register_command
6312class GlibcHeapTcachebinsCommand(GenericCommand):
6313 """Display information on the Tcachebins on an arena (default: main_arena).
6314 See https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc."""
6315
6316 _cmdline_ = "heap bins tcache"
6317 _syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
6318
6319 def __init__(self):
6320 super(GlibcHeapTcachebinsCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6321 return
6322
6323 @only_if_gdb_running
6324 def do_invoke(self, argv):
6325 # Determine if we are using libc with tcache built in (2.26+)
6326 if get_libc_version() < (2, 26):
6327 info("No Tcache in this version of libc")
6328 return
6329
6330 arena = GlibcArena("*{:s}".format(argv[0])) if len(argv) == 1 else get_main_arena()
6331
6332 if arena is None:
6333 err("Invalid Glibc arena")
6334 return
6335
6336 # Get tcache_perthread_struct for this arena
6337 addr = HeapBaseFunction.heap_base() + 0x10
6338
6339 gef_print(titlify("Tcachebins for arena {:#x}".format(int(arena))))
6340 for i in range(GlibcArena.TCACHE_MAX_BINS):
6341 count = ord(read_memory(addr + i, 1))
6342 chunk = arena.tcachebin(i)
6343 chunks = set()
6344 m = []
6345
6346 # Only print the entry if there are valid chunks. Don't trust count
6347 while True:
6348 if chunk is None:
6349 break
6350
6351 try:
6352 m.append("{:s} {:s} ".format(LEFT_ARROW, str(chunk)))
6353 if chunk.address in chunks:
6354 m.append("{:s} [loop detected]".format(RIGHT_ARROW))
6355 break
6356
6357 chunks.add(chunk.address)
6358
6359 next_chunk = chunk.get_fwd_ptr()
6360 if next_chunk == 0:
6361 break
6362
6363 chunk = GlibcChunk(next_chunk)
6364 except gdb.MemoryError:
6365 m.append("{:s} [Corrupted chunk at {:#x}]".format(LEFT_ARROW, chunk.address))
6366 break
6367 if m:
6368 gef_print("Tcachebins[idx={:d}, size={:#x}] count={:d} ".format(i, (i+1)*(current_arch.ptrsize)*2, count), end="")
6369 gef_print("".join(m))
6370 return
6371
6372
6373
6374@register_command
6375class GlibcHeapFastbinsYCommand(GenericCommand):
6376 """Display information on the fastbinsY on an arena (default: main_arena).
6377 See https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1123."""
6378
6379 _cmdline_ = "heap bins fast"
6380 _syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
6381
6382 def __init__(self):
6383 super(GlibcHeapFastbinsYCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6384 return
6385
6386 @only_if_gdb_running
6387 def do_invoke(self, argv):
6388 def fastbin_index(sz):
6389 return (sz >> 4) - 2 if SIZE_SZ == 8 else (sz >> 3) - 2
6390
6391 SIZE_SZ = current_arch.ptrsize
6392 MAX_FAST_SIZE = (80 * SIZE_SZ // 4)
6393 NFASTBINS = fastbin_index(MAX_FAST_SIZE) - 1
6394
6395 arena = GlibcArena("*{:s}".format(argv[0])) if len(argv) == 1 else get_main_arena()
6396
6397 if arena is None:
6398 err("Invalid Glibc arena")
6399 return
6400
6401 gef_print(titlify("Fastbins for arena {:#x}".format(int(arena))))
6402 for i in range(NFASTBINS):
6403 gef_print("Fastbins[idx={:d}, size={:#x}] ".format(i, (i+1)*SIZE_SZ*2), end="")
6404 chunk = arena.fastbin(i)
6405 chunks = set()
6406
6407 while True:
6408 if chunk is None:
6409 gef_print("0x00", end="")
6410 break
6411
6412 try:
6413 gef_print("{:s} {:s} ".format(LEFT_ARROW, str(chunk)), end="")
6414 if chunk.address in chunks:
6415 gef_print("{:s} [loop detected]".format(RIGHT_ARROW), end="")
6416 break
6417
6418 if fastbin_index(chunk.get_chunk_size()) != i:
6419 gef_print("[incorrect fastbin_index] ", end="")
6420
6421 chunks.add(chunk.address)
6422
6423 next_chunk = chunk.get_fwd_ptr()
6424 if next_chunk == 0:
6425 break
6426
6427 chunk = GlibcChunk(next_chunk, from_base=True)
6428 except gdb.MemoryError:
6429 gef_print("{:s} [Corrupted chunk at {:#x}]".format(LEFT_ARROW, chunk.address), end="")
6430 break
6431 gef_print()
6432 return
6433
6434@register_command
6435class GlibcHeapUnsortedBinsCommand(GenericCommand):
6436 """Display information on the Unsorted Bins of an arena (default: main_arena).
6437 See: https://github.com/sploitfun/lsploits/blob/master/glibc/malloc/malloc.c#L1689."""
6438
6439 _cmdline_ = "heap bins unsorted"
6440 _syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
6441
6442 def __init__(self):
6443 super(GlibcHeapUnsortedBinsCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6444 return
6445
6446 @only_if_gdb_running
6447 def do_invoke(self, argv):
6448 if get_main_arena() is None:
6449 err("Invalid Glibc arena")
6450 return
6451
6452 arena_addr = "*{:s}".format(argv[0]) if len(argv) == 1 else __gef_default_main_arena__
6453 gef_print(titlify("Unsorted Bin for arena '{:s}'".format(arena_addr)))
6454 nb_chunk = GlibcHeapBinsCommand.pprint_bin(arena_addr, 0, "unsorted_")
6455 if nb_chunk >= 0:
6456 info("Found {:d} chunks in unsorted bin.".format(nb_chunk))
6457 return
6458
6459@register_command
6460class GlibcHeapSmallBinsCommand(GenericCommand):
6461 """Convenience command for viewing small bins."""
6462
6463 _cmdline_ = "heap bins small"
6464 _syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
6465
6466 def __init__(self):
6467 super(GlibcHeapSmallBinsCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6468 return
6469
6470 @only_if_gdb_running
6471 def do_invoke(self, argv):
6472 if get_main_arena() is None:
6473 err("Invalid Glibc arena")
6474 return
6475
6476 arena_addr = "*{:s}".format(argv[0]) if len(argv) == 1 else __gef_default_main_arena__
6477 gef_print(titlify("Small Bins for arena '{:s}'".format(arena_addr)))
6478 bins = {}
6479 for i in range(1, 63):
6480 nb_chunk = GlibcHeapBinsCommand.pprint_bin(arena_addr, i, "small_")
6481 if nb_chunk < 0:
6482 break
6483 if nb_chunk > 0:
6484 bins[i] = nb_chunk
6485 info("Found {:d} chunks in {:d} small non-empty bins.".format(sum(bins.values()), len(bins)))
6486 return
6487
6488@register_command
6489class GlibcHeapLargeBinsCommand(GenericCommand):
6490 """Convenience command for viewing large bins."""
6491
6492 _cmdline_ = "heap bins large"
6493 _syntax_ = "{:s} [ARENA_ADDRESS]".format(_cmdline_)
6494
6495 def __init__(self):
6496 super(GlibcHeapLargeBinsCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6497 return
6498
6499 @only_if_gdb_running
6500 def do_invoke(self, argv):
6501 if get_main_arena() is None:
6502 err("Invalid Glibc arena")
6503 return
6504
6505 arena_addr = "*{:s}".format(argv[0]) if len(argv) == 1 else __gef_default_main_arena__
6506 gef_print(titlify("Large Bins for arena '{:s}'".format(arena_addr)))
6507 bins = {}
6508 for i in range(63, 126):
6509 nb_chunk = GlibcHeapBinsCommand.pprint_bin(arena_addr, i, "large_")
6510 if nb_chunk <= 0:
6511 break
6512 if nb_chunk > 0:
6513 bins[i] = nb_chunk
6514 info("Found {:d} chunks in {:d} large non-empty bins.".format(sum(bins.values()), len(bins)))
6515 return
6516
6517
6518@register_command
6519class SolveKernelSymbolCommand(GenericCommand):
6520 """Solve kernel symbols from kallsyms table."""
6521
6522 _cmdline_ = "ksymaddr"
6523 _syntax_ = "{:s} SymbolToSearch".format(_cmdline_)
6524 _example_ = "{:s} prepare_creds".format(_cmdline_)
6525
6526 def do_invoke(self, argv):
6527 if len(argv) != 1:
6528 self.usage()
6529 return
6530
6531 found = False
6532 sym = argv[0]
6533 with open("/proc/kallsyms", "r") as f:
6534 for line in f:
6535 try:
6536 symaddr, symtype, symname = line.strip().split(" ", 3)
6537 symaddr = long(symaddr, 16)
6538 if symname == sym:
6539 ok("Found matching symbol for '{:s}' at {:#x} (type={:s})".format(sym, symaddr, symtype))
6540 found = True
6541 if sym in symname:
6542 warn("Found partial match for '{:s}' at {:#x} (type={:s}): {:s}".format(sym, symaddr, symtype, symname))
6543 found = True
6544 except ValueError:
6545 pass
6546
6547 if not found:
6548 err("No match for '{:s}'".format(sym))
6549 return
6550
6551
6552@register_command
6553class DetailRegistersCommand(GenericCommand):
6554 """Display full details on one, many or all registers value from current architecture."""
6555
6556 _cmdline_ = "registers"
6557 _syntax_ = "{:s} [[Register1][Register2] ... [RegisterN]]".format(_cmdline_)
6558 _example_ = "\n{0:s}\n{0:s} $eax $eip $esp".format(_cmdline_)
6559
6560 @only_if_gdb_running
6561 def do_invoke(self, argv):
6562 unchanged_color = get_gef_setting("theme.registers_register_name")
6563 changed_color = get_gef_setting("theme.registers_value_changed")
6564 string_color = get_gef_setting("theme.dereference_string")
6565
6566 if argv:
6567 regs = [reg for reg in current_arch.all_registers if reg in argv]
6568 if not regs:
6569 warn("No matching registers found")
6570 else:
6571 regs = current_arch.all_registers
6572
6573
6574 memsize = current_arch.ptrsize
6575 endian = endian_str()
6576 charset = string.printable
6577 widest = max(map(len, current_arch.all_registers))
6578 special_line = ""
6579
6580 for regname in regs:
6581 reg = gdb.parse_and_eval(regname)
6582 if reg.type.code == gdb.TYPE_CODE_VOID:
6583 continue
6584
6585 padreg = regname.ljust(widest, " ")
6586
6587 if str(reg) == "<unavailable>":
6588 line = "{}: ".format(Color.colorify(padreg, unchanged_color))
6589 line += Color.colorify("no value", "yellow underline")
6590 gef_print(line)
6591 continue
6592
6593 value = align_address(long(reg))
6594 old_value = ContextCommand.old_registers.get(regname, 0)
6595 if value == old_value:
6596 color = unchanged_color
6597 else:
6598 color = changed_color
6599
6600 # Special (e.g. segment) registers go on their own line
6601 if regname in current_arch.special_registers:
6602 special_line += "{}: ".format(Color.colorify(regname, color))
6603 special_line += "0x{:04x} ".format(get_register(regname))
6604 continue
6605
6606 line = "{}: ".format(Color.colorify(padreg, color))
6607
6608 if regname == current_arch.flag_register:
6609 line += current_arch.flag_register_to_human()
6610 gef_print(line)
6611 continue
6612
6613 addr = lookup_address(align_address(long(value)))
6614 if addr.valid:
6615 line += str(addr)
6616 else:
6617 line += format_address_spaces(value)
6618 addrs = DereferenceCommand.dereference_from(value)
6619
6620 if len(addrs) > 1:
6621 sep = " {:s} ".format(RIGHT_ARROW)
6622 line += sep
6623 line += sep.join(addrs[1:])
6624
6625 # check to see if reg value is ascii
6626 try:
6627 fmt = "{}{}".format(endian, "I" if memsize==4 else "Q")
6628 last_addr = int(addrs[-1],16)
6629 val = gef_pystring(struct.pack(fmt, last_addr))
6630 if all([_ in charset for _ in val]):
6631 line += ' ("{:s}"?)'.format(Color.colorify(val, string_color))
6632 except ValueError:
6633 pass
6634
6635 gef_print(line)
6636
6637 if special_line:
6638 gef_print(special_line)
6639 return
6640
6641
6642@register_command
6643class ShellcodeCommand(GenericCommand):
6644 """ShellcodeCommand uses @JonathanSalwan simple-yet-awesome shellcode API to
6645 download shellcodes."""
6646
6647 _cmdline_ = "shellcode"
6648 _syntax_ = "{:s} (search|get)".format(_cmdline_)
6649
6650 def __init__(self):
6651 super(ShellcodeCommand, self).__init__(prefix=True)
6652 return
6653
6654 def do_invoke(self, argv):
6655 err("Missing sub-command (search|get)")
6656 self.usage()
6657 return
6658
6659
6660@register_command
6661class ShellcodeSearchCommand(GenericCommand):
6662 """Search pattern in shell-storm's shellcode database."""
6663
6664 _cmdline_ = "shellcode search"
6665 _syntax_ = "{:s} PATTERN1 PATTERN2".format(_cmdline_)
6666 _aliases_ = ["sc-search",]
6667
6668 api_base = "http://shell-storm.org"
6669 search_url = "{}/api/?s=".format(api_base)
6670
6671
6672 def do_invoke(self, argv):
6673 if not argv:
6674 err("Missing pattern to search")
6675 self.usage()
6676 return
6677
6678 self.search_shellcode(argv)
6679 return
6680
6681
6682 def search_shellcode(self, search_options):
6683 # API : http://shell-storm.org/shellcode/
6684 args = "*".join(search_options)
6685
6686 res = http_get(self.search_url + args)
6687 if res is None:
6688 err("Could not query search page")
6689 return
6690
6691 ret = gef_pystring(res)
6692
6693 # format: [author, OS/arch, cmd, id, link]
6694 lines = ret.split("\\n")
6695 refs = [line.split("::::") for line in lines]
6696
6697 if refs:
6698 info("Showing matching shellcodes")
6699 info("\t".join(["Id", "Platform", "Description"]))
6700 for ref in refs:
6701 try:
6702 _, arch, cmd, sid, _ = ref
6703 gef_print("\t".join([sid, arch, cmd]))
6704 except ValueError:
6705 continue
6706
6707 info("Use `shellcode get <id>` to fetch shellcode")
6708 return
6709
6710@register_command
6711class ShellcodeGetCommand(GenericCommand):
6712 """Download shellcode from shell-storm's shellcode database."""
6713
6714 _cmdline_ = "shellcode get"
6715 _syntax_ = "{:s} SHELLCODE_ID".format(_cmdline_)
6716 _aliases_ = ["sc-get",]
6717
6718 api_base = "http://shell-storm.org"
6719 get_url = "{}/shellcode/files/shellcode-{{:d}}.php".format(api_base)
6720
6721 def do_invoke(self, argv):
6722 if len(argv) != 1:
6723 err("Missing ID to download")
6724 self.usage()
6725 return
6726
6727 if not argv[0].isdigit():
6728 err("ID is not a number")
6729 self.usage()
6730 return
6731
6732 self.get_shellcode(long(argv[0]))
6733 return
6734
6735 def get_shellcode(self, sid):
6736 res = http_get(self.get_url.format(sid))
6737 if res is None:
6738 err("Failed to fetch shellcode #{:d}".format(sid))
6739 return
6740
6741 ret = gef_pystring(res)
6742
6743 info("Downloading shellcode id={:d}".format(sid))
6744 fd, fname = tempfile.mkstemp(suffix=".txt", prefix="sc-", text=True, dir="/tmp")
6745 data = ret.split("\\n")[7:-11]
6746 buf = "\n".join(data)
6747 buf = HTMLParser().unescape(buf)
6748 os.write(fd, gef_pybytes(buf))
6749 os.close(fd)
6750 info("Shellcode written to '{:s}'".format(fname))
6751 return
6752
6753
6754@register_command
6755class RopperCommand(GenericCommand):
6756 """Ropper (http://scoding.de/ropper) plugin."""
6757
6758 _cmdline_ = "ropper"
6759 _syntax_ = "{:s} [ROPPER_OPTIONS]".format(_cmdline_)
6760
6761 def __init__(self):
6762 super(RopperCommand, self).__init__(complete=gdb.COMPLETE_NONE)
6763 return
6764
6765 def pre_load(self):
6766 try:
6767 __import__("ropper")
6768 except ImportError:
6769 msg = "Missing `ropper` package for Python{0}, install with: `pip{0} install ropper`.".format(PYTHON_MAJOR)
6770 raise ImportWarning(msg)
6771 return
6772
6773
6774 @only_if_gdb_running
6775 def do_invoke(self, argv):
6776 ropper = sys.modules["ropper"]
6777 if "--file" not in argv:
6778 path = get_filepath()
6779 sect = next(filter(lambda x: x.path == path, get_process_maps()))
6780 argv.append("--file")
6781 argv.append(path)
6782 argv.append("-I")
6783 argv.append("{:#x}".format(sect.page_start))
6784
6785 import readline
6786 # ropper set up own autocompleter after which gdb/gef autocomplete don't work
6787 old_completer_delims = readline.get_completer_delims()
6788 old_completer = readline.get_completer()
6789 ropper.start(argv)
6790 readline.set_completer(old_completer)
6791 readline.set_completer_delims(old_completer_delims)
6792 return
6793
6794
6795@register_command
6796class AssembleCommand(GenericCommand):
6797 """Inline code assemble. Architecture can be set in GEF runtime config (default x86-32). """
6798
6799 _cmdline_ = "assemble"
6800 _syntax_ = "{:s} [-a ARCH] [-m MODE] [-e] [-s] [-l LOCATION] instruction;[instruction;...instruction;])".format(_cmdline_)
6801 _aliases_ = ["asm",]
6802 _example_ = "\n{0:s} -a x86 -m 32 nop ; nop ; inc eax ; int3\n{0:s} -a arm -m arm add r0, r0, 1".format(_cmdline_)
6803
6804 def __init__(self, *args, **kwargs):
6805 super(AssembleCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6806 self.valid_arch_modes = {
6807 "ARM" : ["ARM", "THUMB"],
6808 "ARM64" : ["ARM", "THUMB", "V5", "V8", ],
6809 "MIPS" : ["MICRO", "MIPS3", "MIPS32", "MIPS32R6", "MIPS64",],
6810 "PPC" : ["PPC32", "PPC64", "QPX",],
6811 "SPARC" : ["SPARC32", "SPARC64", "V9",],
6812 "SYSTEMZ" : ["32",],
6813 "X86" : ["16", "32", "64"],
6814 }
6815 return
6816
6817 def pre_load(self):
6818 try:
6819 __import__("keystone")
6820 except ImportError:
6821 msg = "Missing `keystone-engine` package for Python{0}, install with: `pip{0} install keystone-engine`.".format(PYTHON_MAJOR)
6822 raise ImportWarning(msg)
6823 return
6824
6825 def usage(self):
6826 super(AssembleCommand, self).usage()
6827 gef_print("\nAvailable architectures/modes:")
6828 # for updates, see https://github.com/keystone-engine/keystone/blob/master/include/keystone/keystone.h
6829 for arch in self.valid_arch_modes:
6830 gef_print(" - {} ".format(arch))
6831 gef_print(" * {}".format(" / ".join(self.valid_arch_modes[arch])))
6832 return
6833
6834 def do_invoke(self, argv):
6835 arch_s, mode_s, big_endian, as_shellcode, write_to_location = None, None, False, False, None
6836 opts, args = getopt.getopt(argv, "a:m:l:esh")
6837 for o,a in opts:
6838 if o == "-a": arch_s = a.upper()
6839 if o == "-m": mode_s = a.upper()
6840 if o == "-e": big_endian = True
6841 if o == "-s": as_shellcode = True
6842 if o == "-l": write_to_location = long(gdb.parse_and_eval(a))
6843 if o == "-h":
6844 self.usage()
6845 return
6846
6847 if not args:
6848 return
6849
6850 if (arch_s, mode_s) == (None, None):
6851 if is_alive():
6852 arch_s, mode_s = current_arch.arch, current_arch.mode
6853 endian_s = "big" if is_big_endian() else "little"
6854 arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=is_big_endian())
6855 else:
6856 # if not alive, defaults to x86-32
6857 arch_s = "X86"
6858 mode_s = "32"
6859 endian_s = "little"
6860 arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=False)
6861 elif not arch_s:
6862 err("An architecture (-a) must be provided")
6863 return
6864 elif not mode_s:
6865 err("A mode (-m) must be provided")
6866 return
6867 else:
6868 arch, mode = get_keystone_arch(arch=arch_s, mode=mode_s, endian=big_endian)
6869 endian_s = "big" if big_endian else "little"
6870
6871 insns = " ".join(args)
6872 insns = [x.strip() for x in insns.split(";") if x is not None]
6873
6874 info("Assembling {} instruction{} for {} ({} endian)".format(len(insns),
6875 "s" if len(insns)>1 else "",
6876 ":".join([arch_s, mode_s]),
6877 endian_s))
6878
6879 if as_shellcode:
6880 gef_print("""sc="" """)
6881
6882 raw = b""
6883 for insn in insns:
6884 res = keystone_assemble(insn, arch, mode, raw=True)
6885 if res is None:
6886 gef_print("(Invalid)")
6887 continue
6888
6889 if write_to_location:
6890 raw += res
6891 continue
6892
6893 s = binascii.hexlify(res)
6894 res = b"\\x" + b"\\x".join([s[i:i + 2] for i in range(0, len(s), 2)])
6895 res = res.decode("utf-8")
6896
6897 if as_shellcode:
6898 res = """sc+="{0:s}" """.format(res)
6899
6900 gef_print("{0:60s} # {1}".format(res, insn))
6901
6902 if write_to_location:
6903 l = len(raw)
6904 info("Overwriting {:d} bytes at {:s}".format(l, format_address(write_to_location)))
6905 write_memory(write_to_location, raw, l)
6906 return
6907
6908
6909@register_command
6910class ProcessListingCommand(GenericCommand):
6911 """List and filter process. If a PATTERN is given as argument, results shown will be grepped
6912 by this pattern."""
6913
6914 _cmdline_ = "process-search"
6915 _syntax_ = "{:s} [PATTERN]".format(_cmdline_)
6916 _aliases_ = ["ps",]
6917 _example_ = "{:s} gdb".format(_cmdline_)
6918
6919 def __init__(self):
6920 super(ProcessListingCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6921 self.add_setting("ps_command", "/bin/ps auxww", "`ps` command to get process information")
6922 return
6923
6924 def do_invoke(self, argv):
6925 do_attach = False
6926 smart_scan = False
6927
6928 opts, args = getopt.getopt(argv, "as")
6929 for o, _ in opts:
6930 if o == "-a": do_attach = True
6931 if o == "-s": smart_scan = True
6932
6933 pattern = re.compile("^.*$") if not args else re.compile(args[0])
6934
6935 for process in self.get_processes():
6936 pid = int(process["pid"])
6937 command = process["command"]
6938
6939 if not re.search(pattern, command):
6940 continue
6941
6942 if smart_scan:
6943 if command.startswith("[") and command.endswith("]"): continue
6944 if command.startswith("socat "): continue
6945 if command.startswith("grep "): continue
6946 if command.startswith("gdb "): continue
6947
6948 if args and do_attach:
6949 ok("Attaching to process='{:s}' pid={:d}".format(process["command"], pid))
6950 gdb.execute("attach {:d}".format(pid))
6951 return None
6952
6953 line = [process[i] for i in ("pid", "user", "cpu", "mem", "tty", "command")]
6954 gef_print("\t\t".join(line))
6955
6956 return None
6957
6958
6959 def get_processes(self):
6960 output = gef_execute_external(self.get_setting("ps_command").split(), True)
6961 names = [x.lower().replace("%", "") for x in output[0].split()]
6962
6963 for line in output[1:]:
6964 fields = line.split()
6965 t = {}
6966
6967 for i, name in enumerate(names):
6968 if i == len(names) - 1:
6969 t[name] = " ".join(fields[i:])
6970 else:
6971 t[name] = fields[i]
6972
6973 yield t
6974
6975 return
6976
6977
6978@register_command
6979class ElfInfoCommand(GenericCommand):
6980 """Display a limited subset of ELF header information. If no argument is provided, the command will
6981 show information about the current ELF being debugged."""
6982
6983 _cmdline_ = "elf-info"
6984 _syntax_ = "{:s} [FILE]".format(_cmdline_)
6985 _example_ = "{:s} /bin/ls".format(_cmdline_)
6986
6987 def __init__(self, *args, **kwargs):
6988 super(ElfInfoCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
6989 return
6990
6991
6992 def do_invoke(self, argv):
6993 # http://www.sco.com/developers/gabi/latest/ch4.eheader.html
6994 classes = {0x01: "32-bit",
6995 0x02: "64-bit",}
6996 endianness = {0x01: "Little-Endian",
6997 0x02: "Big-Endian",}
6998 osabi = {
6999 0x00: "System V",
7000 0x01: "HP-UX",
7001 0x02: "NetBSD",
7002 0x03: "Linux",
7003 0x06: "Solaris",
7004 0x07: "AIX",
7005 0x08: "IRIX",
7006 0x09: "FreeBSD",
7007 0x0C: "OpenBSD",
7008 }
7009
7010 types = {
7011 0x01: "Relocatable",
7012 0x02: "Executable",
7013 0x03: "Shared",
7014 0x04: "Core"
7015 }
7016
7017 machines = {
7018 0x02: "SPARC",
7019 0x03: "x86",
7020 0x08: "MIPS",
7021 0x12: "SPARC64",
7022 0x14: "PowerPC",
7023 0x15: "PowerPC64",
7024 0x28: "ARM",
7025 0x32: "IA-64",
7026 0x3E: "x86-64",
7027 0xB7: "AArch64",
7028 }
7029
7030 filename = argv[0] if argv else get_filepath()
7031 if filename is None:
7032 return
7033
7034 elf = get_elf_headers(filename)
7035 if elf is None:
7036 return
7037
7038 data = [
7039 ("Magic", "{0!s}".format(hexdump(struct.pack(">I",elf.e_magic), show_raw=True))),
7040 ("Class", "{0:#x} - {1}".format(elf.e_class, classes[elf.e_class])),
7041 ("Endianness", "{0:#x} - {1}".format(elf.e_endianness, endianness[elf.e_endianness])),
7042 ("Version", "{:#x}".format(elf.e_eiversion)),
7043 ("OS ABI", "{0:#x} - {1}".format(elf.e_osabi, osabi[elf.e_osabi])),
7044 ("ABI Version", "{:#x}".format(elf.e_abiversion)),
7045 ("Type", "{0:#x} - {1}".format(elf.e_type, types[elf.e_type])),
7046 ("Machine", "{0:#x} - {1}".format(elf.e_machine, machines[elf.e_machine])),
7047 ("Program Header Table" , "{}".format(format_address(elf.e_phoff))),
7048 ("Section Header Table" , "{}".format(format_address(elf.e_shoff))),
7049 ("Header Table" , "{}".format(format_address(elf.e_phoff))),
7050 ("ELF Version", "{:#x}".format(elf.e_version)),
7051 ("Header size" , "{0} ({0:#x})".format(elf.e_ehsize)),
7052 ("Entry point", "{}".format(format_address(elf.e_entry))),
7053 ]
7054
7055 for title, content in data:
7056 gef_print("{}: {}".format(Color.boldify("{:<22}".format(title)), content))
7057 return
7058
7059
7060@register_command
7061class EntryPointBreakCommand(GenericCommand):
7062 """Tries to find best entry point and sets a temporary breakpoint on it. The command will test for
7063 well-known symbols for entry points, such as `main`, `_main`, `__libc_start_main`, etc. defined by
7064 the setting `entrypoint_symbols`."""
7065
7066 _cmdline_ = "entry-break"
7067 _syntax_ = _cmdline_
7068 _aliases_ = ["start",]
7069
7070 def __init__(self, *args, **kwargs):
7071 super(EntryPointBreakCommand, self).__init__()
7072 self.add_setting("entrypoint_symbols", "main _main __libc_start_main __uClibc_main start _start", "Possible symbols for entry points")
7073 return
7074
7075 def do_invoke(self, argv):
7076 fpath = get_filepath()
7077 if fpath is None:
7078 warn("No executable to debug, use `file` to load a binary")
7079 return
7080
7081 if not os.access(fpath, os.X_OK):
7082 warn("The file '{}' is not executable.".format(fpath))
7083 return
7084
7085 if is_alive() and not __gef_qemu_mode__:
7086 warn("gdb is already running")
7087 return
7088
7089 bp = None
7090 entrypoints = self.get_setting("entrypoint_symbols").split()
7091
7092 for sym in entrypoints:
7093 try:
7094 value = gdb.parse_and_eval(sym)
7095 info("Breaking at '{:s}'".format(str(value)))
7096 bp = EntryBreakBreakpoint(sym)
7097 gdb.execute("run {}".format(" ".join(argv)))
7098 return
7099
7100 except gdb.error as gdb_error:
7101 if 'The "remote" target does not support "run".' in str(gdb_error):
7102 # this case can happen when doing remote debugging
7103 gdb.execute("continue")
7104 return
7105 continue
7106
7107 # if here, clear the breakpoint if any set
7108 if bp:
7109 bp.delete()
7110
7111 # break at entry point
7112 elf = get_elf_headers()
7113 if elf is None:
7114 return
7115
7116 if self.is_pie(fpath):
7117 self.set_init_tbreak_pie(elf.e_entry, argv)
7118 gdb.execute("continue")
7119 return
7120
7121 self.set_init_tbreak(elf.e_entry)
7122 gdb.execute("run {}".format(" ".join(argv)))
7123 return
7124
7125 def set_init_tbreak(self, addr):
7126 info("Breaking at entry-point: {:#x}".format(addr))
7127 bp = EntryBreakBreakpoint("*{:#x}".format(addr))
7128 return bp
7129
7130 def set_init_tbreak_pie(self, addr, argv):
7131 warn("PIC binary detected, retrieving text base address")
7132 gdb.execute("set stop-on-solib-events 1")
7133 hide_context()
7134 gdb.execute("run {}".format(" ".join(argv)))
7135 unhide_context()
7136 gdb.execute("set stop-on-solib-events 0")
7137 vmmap = get_process_maps()
7138 base_address = [x.page_start for x in vmmap if x.path == get_filepath()][0]
7139 return self.set_init_tbreak(base_address + addr)
7140
7141 def is_pie(self, fpath):
7142 return checksec(fpath)["PIE"]
7143
7144
7145@register_command
7146class NamedBreakpointCommand(GenericCommand):
7147 """Sets a breakpoint and assigns a name to it, which will be shown, when it's hit."""
7148
7149 _cmdline_ = "name-break"
7150 _syntax_ = "{:s} NAME [LOCATION]".format(_cmdline_)
7151 _aliases_ = ["nb",]
7152 _example = "{:s} main *0x4008a9"
7153
7154 def __init__(self, *args, **kwargs):
7155 super(NamedBreakpointCommand, self).__init__()
7156 return
7157
7158 def do_invoke(self, argv):
7159 if not argv:
7160 err("Missing name for breakpoint")
7161 self.usage()
7162 return
7163
7164 name = argv[0]
7165 location = argv[1] if len(argv) > 1 else "*{}".format(hex(current_arch.pc))
7166
7167 NamedBreakpoint(location, name)
7168 return
7169
7170
7171@register_command
7172class ContextCommand(GenericCommand):
7173 """Displays a comprehensive and modular summary of runtime context. Unless setting `enable` is
7174 set to False, this command will be spawned automatically every time GDB hits a breakpoint, a
7175 watchpoint, or any kind of interrupt. By default, it will show panes that contain the register
7176 states, the stack, and the disassembly code around $pc."""
7177
7178 _cmdline_ = "context"
7179 _syntax_ = "{:s} [legend|regs|stack|code|args|memory|source|trace|threads|extra]".format(_cmdline_)
7180 _aliases_ = ["ctx",]
7181
7182 old_registers = {}
7183
7184 def __init__(self):
7185 super(ContextCommand, self).__init__()
7186 self.add_setting("enable", True, "Enable/disable printing the context when breaking")
7187 self.add_setting("show_stack_raw", False, "Show the stack pane as raw hexdump (no dereference)")
7188 self.add_setting("show_registers_raw", False, "Show the registers pane with raw values (no dereference)")
7189 self.add_setting("peek_calls", True, "Peek into calls")
7190 self.add_setting("peek_ret", True, "Peek at return address")
7191 self.add_setting("nb_lines_stack", 8, "Number of line in the stack pane")
7192 self.add_setting("grow_stack_down", False, "Order of stack downward starts at largest down to stack pointer")
7193 self.add_setting("nb_lines_backtrace", 10, "Number of line in the backtrace pane")
7194 self.add_setting("nb_lines_threads", -1, "Number of line in the threads pane")
7195 self.add_setting("nb_lines_code", 6, "Number of instruction after $pc")
7196 self.add_setting("nb_lines_code_prev", 3, "Number of instruction before $pc")
7197 self.add_setting("ignore_registers", "", "Space-separated list of registers not to display (e.g. '$cs $ds $gs')")
7198 self.add_setting("clear_screen", False, "Clear the screen before printing the context")
7199 self.add_setting("layout", "legend regs stack code args source memory threads trace extra", "Change the order/presence of the context sections")
7200 self.add_setting("redirect", "", "Redirect the context information to another TTY")
7201
7202 if "capstone" in list(sys.modules.keys()):
7203 self.add_setting("use_capstone", False, "Use capstone as disassembler in the code pane (instead of GDB)")
7204
7205 self.layout_mapping = {
7206 "legend": self.show_legend,
7207 "regs": self.context_regs,
7208 "stack": self.context_stack,
7209 "code": self.context_code,
7210 "args": self.context_args,
7211 "memory": self.context_memory,
7212 "source": self.context_source,
7213 "trace": self.context_trace,
7214 "threads": self.context_threads,
7215 "extra": self.context_additional_information,
7216 }
7217 return
7218
7219 def post_load(self):
7220 gef_on_continue_hook(self.update_registers)
7221 gef_on_continue_hook(self.empty_extra_messages)
7222 return
7223
7224 def show_legend(self):
7225 if get_gef_setting("gef.disable_color")!=True:
7226 str_color = get_gef_setting("theme.dereference_string")
7227 code_addr_color = get_gef_setting("theme.address_code")
7228 stack_addr_color = get_gef_setting("theme.address_stack")
7229 heap_addr_color = get_gef_setting("theme.address_heap")
7230 changed_register_color = get_gef_setting("theme.registers_value_changed")
7231
7232 gef_print("[ Legend: {} | {} | {} | {} | {} ]".format(Color.colorify("Modified register", changed_register_color),
7233 Color.colorify("Code", code_addr_color),
7234 Color.colorify("Heap", heap_addr_color),
7235 Color.colorify("Stack", stack_addr_color),
7236 Color.colorify("String", str_color)
7237 ))
7238 return
7239
7240 @only_if_gdb_running
7241 def do_invoke(self, argv):
7242 if not self.get_setting("enable") or context_hidden:
7243 return
7244
7245 if not all(_ in self.layout_mapping for _ in argv):
7246 self.usage()
7247 return
7248
7249 if len(argv) > 0:
7250 current_layout = argv
7251 else:
7252 current_layout = self.get_setting("layout").strip().split()
7253
7254 if not current_layout:
7255 return
7256
7257 self.tty_rows, self.tty_columns = get_terminal_size()
7258
7259 redirect = self.get_setting("redirect")
7260 if redirect and os.access(redirect, os.W_OK):
7261 enable_redirect_output(to_file=redirect)
7262
7263 if self.get_setting("clear_screen") and len(argv) == 0:
7264 clear_screen(redirect)
7265
7266 for section in current_layout:
7267 if section[0] == "-":
7268 continue
7269
7270 try:
7271 self.layout_mapping[section]()
7272 except gdb.MemoryError as e:
7273 # a MemoryError will happen when $pc is corrupted (invalid address)
7274 err(str(e))
7275
7276
7277 self.context_title("")
7278
7279 if redirect and os.access(redirect, os.W_OK):
7280 disable_redirect_output()
7281 return
7282
7283 def context_title(self, m):
7284 line_color= get_gef_setting("theme.context_title_line")
7285 msg_color = get_gef_setting("theme.context_title_message")
7286
7287 if not m:
7288 gef_print(Color.colorify(HORIZONTAL_LINE * self.tty_columns, line_color))
7289 return
7290
7291 trail_len = len(m) + 6
7292 title = ""
7293 title += Color.colorify("{:{padd}<{width}} ".format("",
7294 width=max(self.tty_columns - trail_len, 0),
7295 padd=HORIZONTAL_LINE),
7296 line_color)
7297 title += Color.colorify(m, msg_color)
7298 title += Color.colorify(" {:{padd}<4}".format("", padd=HORIZONTAL_LINE),
7299 line_color)
7300 gef_print(title)
7301 return
7302
7303 def context_regs(self):
7304 self.context_title("registers")
7305 ignored_registers = set(self.get_setting("ignore_registers").split())
7306
7307 if self.get_setting("show_registers_raw") is False:
7308 regs = set(current_arch.all_registers)
7309 printable_registers = " ".join(list(regs - ignored_registers))
7310 gdb.execute("registers {}".format(printable_registers))
7311 return
7312
7313 widest = l = max(map(len, current_arch.all_registers))
7314 l += 5
7315 l += current_arch.ptrsize * 2
7316 nb = get_terminal_size()[1]//l
7317 i = 1
7318 line = ""
7319 changed_color = get_gef_setting("theme.registers_value_changed")
7320 regname_color = get_gef_setting("theme.registers_register_name")
7321
7322 for reg in current_arch.all_registers:
7323 if reg in ignored_registers:
7324 continue
7325
7326 try:
7327 r = gdb.parse_and_eval(reg)
7328 if r.type.code == gdb.TYPE_CODE_VOID:
7329 continue
7330
7331 new_value_type_flag = (r.type.code == gdb.TYPE_CODE_FLAGS)
7332 new_value = long(r)
7333
7334 except (gdb.MemoryError, gdb.error):
7335 # If this exception is triggered, it means that the current register
7336 # is corrupted. Just use the register "raw" value (not eval-ed)
7337 new_value = get_register(reg)
7338 new_value_type_flag = False
7339
7340 except Exception:
7341 new_value = 0
7342
7343 old_value = self.old_registers.get(reg, 0)
7344
7345 padreg = reg.ljust(widest, " ")
7346 value = align_address(new_value)
7347 old_value = align_address(old_value)
7348 if value == old_value:
7349 line += "{}: ".format(Color.colorify(padreg, regname_color))
7350 else:
7351 line += "{}: ".format(Color.colorify(padreg, changed_color))
7352 if new_value_type_flag:
7353 line += "{:s} ".format(format_address_spaces(value))
7354 else:
7355 addr = lookup_address(align_address(long(value)))
7356 if addr.valid:
7357 line += "{:s} ".format(str(addr))
7358 else:
7359 line += "{:s} ".format(format_address_spaces(value))
7360
7361 if i % nb == 0 :
7362 gef_print(line)
7363 line = ""
7364 i += 1
7365
7366 if line:
7367 gef_print(line)
7368
7369 gef_print("Flags: {:s}".format(current_arch.flag_register_to_human()))
7370 return
7371
7372 def context_stack(self):
7373 self.context_title("stack")
7374
7375 show_raw = self.get_setting("show_stack_raw")
7376 nb_lines = self.get_setting("nb_lines_stack")
7377
7378 try:
7379 sp = current_arch.sp
7380 if show_raw is True:
7381 mem = read_memory(sp, 0x10 * nb_lines)
7382 gef_print(hexdump(mem, base=sp))
7383 else:
7384 gdb.execute("dereference {:#x} l{:d}".format(sp, nb_lines))
7385
7386 except gdb.MemoryError:
7387 err("Cannot read memory from $SP (corrupted stack pointer?)")
7388
7389 return
7390
7391 def context_code(self):
7392 nb_insn = self.get_setting("nb_lines_code")
7393 nb_insn_prev = self.get_setting("nb_lines_code_prev")
7394 use_capstone = self.has_setting("use_capstone") and self.get_setting("use_capstone")
7395 cur_insn_color = get_gef_setting("theme.disassemble_current_instruction")
7396 pc = current_arch.pc
7397
7398 frame = gdb.selected_frame()
7399 arch = frame.architecture()
7400 arch_name = "{}:{}".format(current_arch.arch.lower(), current_arch.mode)
7401
7402 self.context_title("code:{}".format(arch_name))
7403
7404 try:
7405 instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble
7406
7407 for insn in instruction_iterator(pc, nb_insn, nb_prev=nb_insn_prev):
7408 line = []
7409 is_taken = False
7410 target = None
7411 text = str(insn)
7412
7413 if insn.address < pc:
7414 line += Color.grayify(" {}".format(text))
7415
7416 elif insn.address == pc:
7417 line += Color.colorify("{:s}{:s}".format(RIGHT_ARROW, text), cur_insn_color)
7418
7419 if current_arch.is_conditional_branch(insn):
7420 is_taken, reason = current_arch.is_branch_taken(insn)
7421 if is_taken:
7422 target = insn.operands[-1].split()[0]
7423 reason = "[Reason: {:s}]".format(reason) if reason else ""
7424 line += Color.colorify("\tTAKEN {:s}".format(reason), "bold green")
7425 else:
7426 reason = "[Reason: !({:s})]".format(reason) if reason else ""
7427 line += Color.colorify("\tNOT taken {:s}".format(reason), "bold red")
7428 elif current_arch.is_call(insn) and self.get_setting("peek_calls") is True:
7429 target = insn.operands[-1].split()[0]
7430 elif current_arch.is_ret(insn) and self.get_setting("peek_ret") is True:
7431 target = current_arch.get_ra(insn, frame)
7432
7433 else:
7434 line += " {}".format(text)
7435
7436 gef_print("".join(line))
7437
7438 if target:
7439 try:
7440 target = int(target, 0)
7441 except TypeError: # Already an int
7442 pass
7443 except ValueError:
7444 # If the operand isn't an address right now we can't parse it
7445 continue
7446 for i, tinsn in enumerate(instruction_iterator(target, nb_insn)):
7447 text= " {} {}".format (DOWN_ARROW if i==0 else " ", str(tinsn))
7448 gef_print(text)
7449 break
7450
7451 except gdb.MemoryError:
7452 err("Cannot disassemble from $PC")
7453 return
7454
7455 def context_args(self):
7456 insn = gef_current_instruction(current_arch.pc)
7457 if not current_arch.is_call(insn):
7458 return
7459
7460 self.size2type = {
7461 1: "BYTE",
7462 2: "WORD",
7463 4: "DWORD",
7464 8: "QWORD",
7465 }
7466
7467 if insn.operands[-1].startswith(self.size2type[current_arch.ptrsize]+" PTR"):
7468 target = "*" + insn.operands[-1].split()[-1]
7469 elif "$"+insn.operands[0] in current_arch.all_registers:
7470 target = "*{:#x}".format(get_register("$"+insn.operands[0]))
7471 else:
7472 # is there a symbol?
7473 ops = " ".join(insn.operands)
7474 if "<" in ops and ">" in ops:
7475 # extract it
7476 target = re.sub(r".*<([^\(> ]*).*", r"\1", ops)
7477 else:
7478 # it's an address, just use as is
7479 target = re.sub(r".*(0x[a-fA-F0-9]*).*", r"\1", ops)
7480
7481 sym = gdb.lookup_global_symbol(target)
7482 if sym is None:
7483 self.print_guessed_arguments(target)
7484 return
7485
7486 if sym.type.code != gdb.TYPE_CODE_FUNC:
7487 err("Symbol '{}' is not a function: type={}".format(target, sym.type.code))
7488 return
7489
7490 self.print_arguments_from_symbol(target, sym)
7491 return
7492
7493 def print_arguments_from_symbol(self, function_name, symbol):
7494 """If symbols were found, parse them and print the argument adequately."""
7495 args = []
7496
7497 for i, f in enumerate(symbol.type.fields()):
7498 _value = current_arch.get_ith_parameter(i, in_func=False)[1]
7499 _value = RIGHT_ARROW.join(DereferenceCommand.dereference_from(_value))
7500 _name = f.name or "var_{}".format(i)
7501 _type = f.type.name or self.size2type[f.type.sizeof]
7502 args.append("{} {} = {}".format(_type, _name, _value))
7503
7504 self.context_title("arguments")
7505
7506 if not args:
7507 gef_print("{} (<void>)".format(function_name))
7508 return
7509
7510 gef_print("{} (".format(function_name))
7511 gef_print(" " + ",\n ".join(args))
7512 gef_print(")")
7513 return
7514
7515 def print_guessed_arguments(self, function_name):
7516 """When no symbol, read the current basic block and look for "interesting" instructions."""
7517
7518 def __get_current_block_start_address():
7519 pc = current_arch.pc
7520 try:
7521 block_start = gdb.block_for_pc(pc).start
7522 except RuntimeError:
7523 # if stripped, let's roll back 5 instructions
7524 block_start = gdb_get_nth_previous_instruction_address(pc, 5)
7525 return block_start
7526
7527
7528 parameter_set = set()
7529 pc = current_arch.pc
7530 block_start = __get_current_block_start_address()
7531 use_capstone = self.has_setting("use_capstone") and self.get_setting("use_capstone")
7532 instruction_iterator = capstone_disassemble if use_capstone else gef_disassemble
7533 function_parameters = current_arch.function_parameters
7534 arg_key_color = get_gef_setting("theme.registers_register_name")
7535
7536 for insn in instruction_iterator(block_start, pc-block_start):
7537 if not insn.operands:
7538 continue
7539
7540 if is_x86_32():
7541 if insn.mnemonic == "push":
7542 parameter_set.add(insn.operands[0])
7543 else:
7544 op = "$"+insn.operands[0]
7545 if op in function_parameters:
7546 parameter_set.add(op)
7547
7548 if is_x86_64():
7549 # also consider extended registers
7550 extended_registers = {"$rdi": ["$edi", "$di"],
7551 "$rsi": ["$esi", "$si"],
7552 "$rdx": ["$edx", "$dx"],
7553 "$rcx": ["$ecx", "$cx"],
7554 }
7555 for exreg in extended_registers:
7556 if op in extended_registers[exreg]:
7557 parameter_set.add(exreg)
7558
7559 if is_x86_32():
7560 nb_argument = len(parameter_set)
7561 else:
7562 nb_argument = 0
7563 for p in parameter_set:
7564 nb_argument = max(nb_argument, function_parameters.index(p)+1)
7565
7566 args = []
7567 for i in range(nb_argument):
7568 _key, _value = current_arch.get_ith_parameter(i, in_func=False)
7569 _value = RIGHT_ARROW.join(DereferenceCommand.dereference_from(_value))
7570 args.append("{} = {}".format(Color.colorify(_key, arg_key_color), _value))
7571
7572 self.context_title("arguments (guessed)")
7573 gef_print("{} (".format(function_name))
7574 if args:
7575 gef_print(" "+",\n ".join(args))
7576 gef_print(")")
7577 return
7578
7579
7580 def context_source(self):
7581 try:
7582 pc = current_arch.pc
7583 symtabline = gdb.find_pc_line(pc)
7584 symtab = symtabline.symtab
7585 line_num = symtabline.line - 1 # we substract one because line number returned by gdb start at 1
7586 if not symtab.is_valid():
7587 return
7588
7589 fpath = symtab.fullname()
7590 with open(fpath, "r") as f:
7591 lines = [l.rstrip() for l in f.readlines()]
7592
7593 except Exception:
7594 return
7595
7596 nb_line = self.get_setting("nb_lines_code")
7597 fn = symtab.filename
7598 if len(fn) > 20:
7599 fn = "{}[...]{}".format(fn[:15], os.path.splitext(fn)[1])
7600 title = "source:{}+{}".format(fn, line_num + 1)
7601 cur_line_color = get_gef_setting("theme.source_current_line")
7602 self.context_title(title)
7603
7604 for i in range(line_num - nb_line + 1, line_num + nb_line):
7605 if i < 0:
7606 continue
7607
7608 if i < line_num:
7609 gef_print(Color.grayify(" {:4d}\t {:s}".format(i + 1, lines[i],)))
7610
7611 if i == line_num:
7612 extra_info = self.get_pc_context_info(pc, lines[i])
7613 prefix = "{}{:4d}\t ".format(RIGHT_ARROW, i + 1)
7614 leading = len(lines[i]) - len(lines[i].lstrip())
7615 if extra_info:
7616 gef_print("{}{}".format(" "*(len(prefix) + leading), extra_info))
7617 gef_print(Color.colorify("{}{:s}".format(prefix, lines[i]), cur_line_color))
7618
7619 if i > line_num:
7620 try:
7621 gef_print(" {:4d}\t {:s}".format(i + 1, lines[i],))
7622 except IndexError:
7623 break
7624 return
7625
7626 def get_pc_context_info(self, pc, line):
7627 try:
7628 current_block = gdb.block_for_pc(pc)
7629 if not current_block.is_valid(): return ""
7630 m = collections.OrderedDict()
7631 while current_block and not current_block.is_static:
7632 for sym in current_block:
7633 symbol = sym.name
7634 if not sym.is_function and re.search(r"\W{}\W".format(symbol), line):
7635 val = gdb.parse_and_eval(symbol)
7636 if val.type.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY):
7637 addr = long(val.address)
7638 addrs = DereferenceCommand.dereference_from(addr)
7639 if len(addrs) > 2:
7640 addrs = [addrs[0], "[...]", addrs[-1]]
7641
7642 f = " {:s} ".format(RIGHT_ARROW)
7643 val = f.join(addrs)
7644 elif val.type.code == gdb.TYPE_CODE_INT:
7645 val = hex(long(val))
7646 else:
7647 continue
7648
7649 if symbol not in m:
7650 m[symbol] = val
7651 current_block = current_block.superblock
7652
7653 if m:
7654 return "// " + ", ".join(["{}={}".format(Color.yellowify(a), b) for a, b in m.items()])
7655 except Exception:
7656 pass
7657 return ""
7658
7659 def context_trace(self):
7660 self.context_title("trace")
7661
7662 nb_backtrace = self.get_setting("nb_lines_backtrace")
7663 if nb_backtrace <= 0:
7664 return
7665 orig_frame = current_frame = gdb.selected_frame()
7666 i = 0
7667
7668 # backward compat for gdb (gdb < 7.10)
7669 if not hasattr(gdb, "FrameDecorator"):
7670 gdb.execute("backtrace {:d}".format(nb_backtrace))
7671 return
7672
7673 while current_frame:
7674 current_frame.select()
7675 if not current_frame.is_valid():
7676 continue
7677
7678 pc = current_frame.pc()
7679 name = current_frame.name()
7680 items = []
7681 items.append("{:#x}".format(pc))
7682 if name:
7683 frame_args = gdb.FrameDecorator.FrameDecorator(current_frame).frame_args() or []
7684 m = "{}({})".format(Color.greenify(name),
7685 ", ".join(["{}={!s}".format(Color.yellowify(x.sym),
7686 x.sym.value(current_frame)) for x in frame_args]))
7687 items.append(m)
7688 else:
7689 try:
7690 insn = next(gef_disassemble(pc, 1))
7691 except gdb.MemoryError:
7692 break
7693 items.append(Color.redify("{} {}".format(insn.mnemonic, ", ".join(insn.operands))))
7694
7695 gef_print("[{}] {}".format(Color.colorify("#{}".format(i), "bold pink"),
7696 RIGHT_ARROW.join(items)))
7697 current_frame = current_frame.older()
7698 i += 1
7699 nb_backtrace -= 1
7700 if nb_backtrace == 0:
7701 break
7702
7703 orig_frame.select()
7704 return
7705
7706 def context_threads(self):
7707 def reason():
7708 res = gdb.execute("info program", to_string=True).splitlines()
7709 if not res:
7710 return "NOT RUNNING"
7711
7712 for line in res:
7713 line = line.strip()
7714 if line.startswith("It stopped with signal "):
7715 return line.replace("It stopped with signal ", "").split(",", 1)[0]
7716 if line == "The program being debugged is not being run.":
7717 return "NOT RUNNING"
7718 if line == "It stopped at a breakpoint that has since been deleted.":
7719 return "TEMPORARY BREAKPOINT"
7720 if line.startswith("It stopped at breakpoint "):
7721 return "BREAKPOINT"
7722 if line == "It stopped after being stepped.":
7723 return "SINGLE STEP"
7724
7725 return "STOPPED"
7726
7727 self.context_title("threads")
7728
7729 threads = gdb.selected_inferior().threads()[::-1]
7730 idx = self.get_setting("nb_lines_threads")
7731 if idx > 0:
7732 threads = threads[0:idx]
7733
7734 if idx==0:
7735 return
7736
7737 if not threads:
7738 err("No thread selected")
7739 return
7740
7741 selected_thread = gdb.selected_thread()
7742
7743 for i, thread in enumerate(threads):
7744 line = """[{:s}] Id {:d}, """.format(Color.colorify("#{:d}".format(i), "bold green" if thread==selected_thread else "bold pink"), thread.num)
7745 if thread.name:
7746 line += """Name: "{:s}", """.format(thread.name)
7747 if thread.is_running():
7748 line += Color.colorify("running", "bold green")
7749 elif thread.is_stopped():
7750 line += Color.colorify("stopped", "bold red")
7751 thread.switch()
7752 frame = gdb.selected_frame()
7753 line += " {:s} in {:s} ()".format(Color.colorify("{:#x}".format(frame.pc()), "blue"),Color.colorify(frame.name() or "??" ,"bold yellow"))
7754 line += ", reason: {}".format(Color.colorify(reason(), "bold pink"))
7755 elif thread.is_exited():
7756 line += Color.colorify("exited", "bold yellow")
7757 gef_print(line)
7758 i += 1
7759
7760 selected_thread.switch()
7761 return
7762
7763
7764 def context_additional_information(self):
7765 if not __context_messages__:
7766 return
7767
7768 self.context_title("extra")
7769 for level, text in __context_messages__:
7770 if level=="error": err(text)
7771 elif level=="warn": warn(text)
7772 elif level=="success": ok(text)
7773 else: info(text)
7774 return
7775
7776 def context_memory(self):
7777 global __watches__
7778 for address, opt in sorted(__watches__.items()):
7779 self.context_title("memory:{:#x}".format(address))
7780 gdb.execute("hexdump {fmt:s} {address:d} {size:d}".format(
7781 address=address,
7782 size=opt[0],
7783 fmt=opt[1]
7784 ))
7785
7786 @classmethod
7787 def update_registers(cls, event):
7788 for reg in current_arch.all_registers:
7789 try:
7790 cls.old_registers[reg] = get_register(reg)
7791 except Exception:
7792 cls.old_registers[reg] = 0
7793 return
7794
7795
7796 def empty_extra_messages(self, event):
7797 global __context_messages__
7798 __context_messages__ = []
7799 return
7800
7801
7802@register_command
7803class MemoryCommand(GenericCommand):
7804 """Add or remove address ranges to the memory view."""
7805 _cmdline_ = "memory"
7806 _syntax_ = "{:s} (watch|unwatch|reset|list)".format(_cmdline_)
7807
7808 def __init__(self):
7809 super(MemoryCommand, self).__init__(prefix=True)
7810 return
7811
7812 @only_if_gdb_running
7813 def do_invoke(self, argv):
7814 self.usage()
7815 return
7816
7817@register_command
7818class MemoryWatchCommand(GenericCommand):
7819 """Adds address ranges to the memory view."""
7820 _cmdline_ = "memory watch"
7821 _syntax_ = "{:s} ADDRESS [SIZE] [(qword|dword|word|byte)]".format(_cmdline_)
7822 _example_ = "\n\t{0:s} 0x603000 0x100 byte\n\t{0:s} $sp".format(_cmdline_)
7823
7824 @only_if_gdb_running
7825 def do_invoke(self, argv):
7826 global __watches__
7827
7828 if len(argv) not in (1, 2, 3):
7829 self.usage()
7830 return
7831
7832 address = to_unsigned_long(gdb.parse_and_eval(argv[0]))
7833 size = to_unsigned_long(gdb.parse_and_eval(argv[1])) if len(argv) > 1 else 0x10
7834 group = "byte"
7835
7836 if len(argv) == 3:
7837 group = argv[2].lower()
7838 if group not in ("qword", "dword", "word", "byte"):
7839 warn("Unexpected grouping '{}'".format(group))
7840 self.usage()
7841 return
7842 else:
7843 if current_arch.ptrsize == 4:
7844 group = "dword"
7845 elif current_arch.ptrsize == 8:
7846 group = "qword"
7847
7848 __watches__[address] = (size, group)
7849 ok("Adding memwatch to {:#x}".format(address))
7850 return
7851
7852@register_command
7853class MemoryUnwatchCommand(GenericCommand):
7854 """Removes address ranges to the memory view."""
7855 _cmdline_ = "memory unwatch"
7856 _syntax_ = "{:s} ADDRESS".format(_cmdline_)
7857 _example_ = "\n\t{0:s} 0x603000\n\t{0:s} $sp".format(_cmdline_)
7858
7859 @only_if_gdb_running
7860 def do_invoke(self, argv):
7861 global __watches__
7862 if not argv:
7863 self.usage()
7864 return
7865
7866 address = to_unsigned_long(gdb.parse_and_eval(argv[0]))
7867 res = __watches__.pop(address, None)
7868 if not res:
7869 warn("You weren't watching {:#x}".format(address))
7870 else:
7871 ok("Removed memwatch of {:#x}".format(address))
7872 return
7873
7874@register_command
7875class MemoryWatchResetCommand(GenericCommand):
7876 """Removes all watchpoints."""
7877 _cmdline_ = "memory reset"
7878 _syntax_ = "{:s}".format(_cmdline_)
7879
7880 @only_if_gdb_running
7881 def do_invoke(self, argv):
7882 global __watches__
7883 __watches__.clear()
7884 ok("Memory watches cleared")
7885 return
7886
7887@register_command
7888class MemoryWatchListCommand(GenericCommand):
7889 """Lists all watchpoints to display in context layout."""
7890 _cmdline_ = "memory list"
7891 _syntax_ = "{:s}".format(_cmdline_)
7892
7893 @only_if_gdb_running
7894 def do_invoke(self, argv):
7895 global __watches__
7896
7897 if not __watches__:
7898 info("No memory watches")
7899 return
7900
7901 info("Memory watches:")
7902 for address, opt in sorted(__watches__.items()):
7903 gef_print("- {:#x} ({}, {})".format(address, opt[0], opt[1]))
7904 return
7905
7906
7907@register_command
7908class HexdumpCommand(GenericCommand):
7909 """Display SIZE lines of hexdump from the memory location pointed by ADDRESS. """
7910
7911 _cmdline_ = "hexdump"
7912 _syntax_ = "{:s} [qword|dword|word|byte] [ADDRESS] [[L][SIZE]] [REVERSE]".format(_cmdline_)
7913 _example_ = "{:s} byte $rsp L16 REVERSE".format(_cmdline_)
7914
7915 def __init__(self):
7916 super(HexdumpCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
7917 self.add_setting("always_show_ascii", False, "If true, hexdump will always display the ASCII dump")
7918 return
7919
7920 @only_if_gdb_running
7921 def do_invoke(self, argv):
7922 fmt = "byte"
7923 target = "$sp"
7924 valid_formats = ["byte", "word", "dword", "qword"]
7925 read_len = None
7926 reverse = False
7927
7928 for arg in argv:
7929 arg = arg.lower()
7930 is_format_given = False
7931 for valid_format in valid_formats:
7932 if valid_format.startswith(arg):
7933 fmt = valid_format
7934 is_format_given = True
7935 break
7936 if is_format_given:
7937 continue
7938 if arg.startswith("l"):
7939 arg = arg[1:]
7940 try:
7941 read_len = long(arg, 0)
7942 continue
7943 except ValueError:
7944 pass
7945
7946 if "reverse".startswith(arg):
7947 reverse = True
7948 continue
7949 target = arg
7950
7951 start_addr = to_unsigned_long(gdb.parse_and_eval(target))
7952 read_from = align_address(start_addr)
7953 if not read_len:
7954 read_len = 0x40 if fmt=="byte" else 0x10
7955
7956 if fmt == "byte":
7957 read_from += self.repeat_count * read_len
7958 mem = read_memory(read_from, read_len)
7959 lines = hexdump(mem, base=read_from).splitlines()
7960 else:
7961 lines = self._hexdump(read_from, read_len, fmt, self.repeat_count * read_len)
7962
7963 if reverse:
7964 lines.reverse()
7965
7966 gef_print("\n".join(lines))
7967 return
7968
7969
7970 def _hexdump(self, start_addr, length, arrange_as, offset=0):
7971 elf = get_elf_headers()
7972 if elf is None:
7973 return
7974 endianness = endian_str()
7975
7976 base_address_color = get_gef_setting("theme.dereference_base_address")
7977 show_ascii = self.get_setting("always_show_ascii")
7978
7979 formats = {
7980 "qword": ("Q", 8),
7981 "dword": ("I", 4),
7982 "word": ("H", 2),
7983 }
7984
7985 r, l = formats[arrange_as]
7986 fmt_str = "{{base}}{v}+{{offset:#06x}} {{sym}}{{val:#0{prec}x}} {{text}}".format(v=VERTICAL_LINE, prec=l*2+2)
7987 fmt_pack = endianness + r
7988 lines = []
7989
7990 i = 0
7991 text = ""
7992 while i < length:
7993 cur_addr = start_addr + (i + offset) * l
7994 sym = gdb_get_location_from_symbol(cur_addr)
7995 sym = "<{:s}+{:04x}> ".format(*sym) if sym else ""
7996 mem = read_memory(cur_addr, l)
7997 val = struct.unpack(fmt_pack, mem)[0]
7998 if show_ascii:
7999 text = "".join([chr(b) if 0x20 <= b < 0x7F else "." for b in mem])
8000 lines.append(fmt_str.format(base=Color.colorify(format_address(cur_addr), base_address_color),
8001 offset=(i + offset) * l, sym=sym, val=val, text=text))
8002 i += 1
8003
8004 return lines
8005
8006
8007@register_command
8008class PatchCommand(GenericCommand):
8009 """Write specified values to the specified address."""
8010
8011 _cmdline_ = "patch"
8012 _syntax_ = ("{0:s} (qword|dword|word|byte) LOCATION VALUES\n"
8013 "{0:s} string LOCATION \"double-escaped string\"".format(_cmdline_))
8014 SUPPORTED_SIZES = {
8015 "qword": (8, "Q"),
8016 "dword": (4, "L"),
8017 "word": (2, "H"),
8018 "byte": (1, "B"),
8019 }
8020
8021 def __init__(self):
8022 super(PatchCommand, self).__init__(complete=gdb.COMPLETE_LOCATION, prefix=True)
8023 return
8024
8025 @only_if_gdb_running
8026 def do_invoke(self, argv):
8027 argc = len(argv)
8028 if argc < 3:
8029 self.usage()
8030 return
8031
8032 fmt, location, values = argv[0].lower(), argv[1], argv[2:]
8033 if fmt not in self.SUPPORTED_SIZES:
8034 self.usage()
8035 return
8036
8037 addr = align_address(long(gdb.parse_and_eval(location)))
8038 size, fcode = self.SUPPORTED_SIZES[fmt]
8039
8040 d = "<" if is_little_endian() else ">"
8041 for value in values:
8042 value = parse_address(value) & ((1 << size * 8) - 1)
8043 vstr = struct.pack(d + fcode, value)
8044 write_memory(addr, vstr, length=size)
8045 addr += size
8046
8047 return
8048
8049@register_command
8050class PatchStringCommand(GenericCommand):
8051 """Write specified string to the specified memory location pointed by ADDRESS."""
8052
8053 _cmdline_ = "patch string"
8054 _syntax_ = "{:s} ADDRESS \"double backslash-escaped string\"".format(_cmdline_)
8055 _example_ = "{:s} $sp \"GEFROCKS\"".format(_cmdline_)
8056
8057 @only_if_gdb_running
8058 def do_invoke(self, argv):
8059 argc = len(argv)
8060 if argc != 2:
8061 self.usage()
8062 return
8063
8064 location, s = argv[0:2]
8065 addr = align_address(long(gdb.parse_and_eval(location)))
8066
8067 try:
8068 s = codecs.escape_decode(s)[0]
8069 except binascii.Error:
8070 gef_print("Could not decode '\\xXX' encoded string \"{}\"".format(s))
8071 return
8072
8073 write_memory(addr, s, len(s))
8074 return
8075
8076
8077@register_command
8078class DereferenceCommand(GenericCommand):
8079 """Dereference recursively from an address and display information. This acts like WinDBG `dps`
8080 command."""
8081
8082 _cmdline_ = "dereference"
8083 _syntax_ = "{:s} [LOCATION] [l[NB]]".format(_cmdline_)
8084 _aliases_ = ["telescope", ]
8085 _example_ = "{:s} $sp l20".format(_cmdline_)
8086
8087 def __init__(self):
8088 super(DereferenceCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
8089 self.add_setting("max_recursion", 7, "Maximum level of pointer recursion")
8090 return
8091
8092 @staticmethod
8093 def pprint_dereferenced(addr, off):
8094 base_address_color = get_gef_setting("theme.dereference_base_address")
8095 registers_color = get_gef_setting("theme.dereference_register_value")
8096
8097 regs = [(k, get_register(k)) for k in current_arch.all_registers]
8098
8099 sep = " {:s} ".format(RIGHT_ARROW)
8100 memalign = current_arch.ptrsize
8101
8102 offset = off * memalign
8103 current_address = align_address(addr + offset)
8104 addrs = DereferenceCommand.dereference_from(current_address)
8105 l = ""
8106 addr_l = format_address(long(addrs[0], 16))
8107 l += "{:s}{:s}+{:#06x}: {:{ma}s}".format(Color.colorify(addr_l, base_address_color),
8108 VERTICAL_LINE, offset,
8109 sep.join(addrs[1:]), ma=(memalign*2 + 2))
8110
8111 register_hints = []
8112
8113 for regname, regvalue in regs:
8114 if current_address == regvalue:
8115 register_hints.append(regname)
8116
8117 if register_hints:
8118 m = "\t{:s}{:s}".format(LEFT_ARROW, ", ".join(list(register_hints)))
8119 l += Color.colorify(m, registers_color)
8120
8121 offset += memalign
8122 return l
8123
8124
8125 @only_if_gdb_running
8126 def do_invoke(self, argv):
8127 target = "$sp"
8128 nb = 10
8129
8130 for arg in argv:
8131 if arg.isdigit():
8132 nb = int(arg)
8133 elif arg[0] in ("l", "L") and arg[1:].isdigit():
8134 nb = int(arg[1:])
8135 else:
8136 target = arg
8137
8138 addr = safe_parse_and_eval(target)
8139 if addr is None:
8140 err("Invalid address")
8141 return
8142
8143 addr = long(addr)
8144 if process_lookup_address(addr) is None:
8145 err("Unmapped address")
8146 return
8147
8148 if get_gef_setting("context.grow_stack_down") is True:
8149 from_insnum = nb * (self.repeat_count + 1) - 1
8150 to_insnum = self.repeat_count * nb - 1
8151 insnum_step = -1
8152 else:
8153 from_insnum = 0 + self.repeat_count * nb
8154 to_insnum = nb * (self.repeat_count + 1)
8155 insnum_step = 1
8156
8157 start_address = align_address(addr)
8158
8159 for i in range(from_insnum, to_insnum, insnum_step):
8160 gef_print(DereferenceCommand.pprint_dereferenced(start_address, i))
8161
8162 return
8163
8164
8165 @staticmethod
8166 def dereference_from(addr):
8167 if not is_alive():
8168 return [format_address(addr),]
8169
8170 code_color = get_gef_setting("theme.dereference_code")
8171 string_color = get_gef_setting("theme.dereference_string")
8172 max_recursion = get_gef_setting("dereference.max_recursion") or 10
8173 addr = lookup_address(align_address(long(addr)))
8174 msg = [format_address(addr.value),]
8175 seen_addrs = set()
8176
8177 while addr.section and max_recursion:
8178 if addr.value in seen_addrs:
8179 msg.append("[loop detected]")
8180 break
8181 seen_addrs.add(addr.value)
8182
8183 max_recursion -= 1
8184
8185 # Is this value a pointer or a value?
8186 # -- If it's a pointer, dereference
8187 deref = addr.dereference()
8188 if deref is None:
8189 # if here, dereferencing addr has triggered a MemoryError, no need to go further
8190 msg.append(str(addr))
8191 break
8192
8193 new_addr = lookup_address(deref)
8194 if new_addr.valid:
8195 addr = new_addr
8196 msg.append(str(addr))
8197 continue
8198
8199 # -- Otherwise try to parse the value
8200 if addr.section:
8201 if addr.section.is_executable() and addr.is_in_text_segment() and not is_ascii_string(addr.value):
8202 insn = gef_current_instruction(addr.value)
8203 insn_str = "{} {} {}".format(insn.location, insn.mnemonic, ", ".join(insn.operands))
8204 msg.append(Color.colorify(insn_str, code_color))
8205 break
8206
8207 elif addr.section.permission.value & Permission.READ:
8208 if is_ascii_string(addr.value):
8209 s = read_cstring_from_memory(addr.value)
8210 if len(s) < get_memory_alignment():
8211 txt = '{:s} ("{:s}"?)'.format(format_address(deref), Color.colorify(s, string_color))
8212 elif len(s) > 50:
8213 txt = Color.colorify('"{:s}[...]"'.format(s[:50]), string_color)
8214 else:
8215 txt = Color.colorify('"{:s}"'.format(s), string_color)
8216
8217 msg.append(txt)
8218 break
8219
8220 # if not able to parse cleanly, simply display and break
8221 val = "{:#0{ma}x}".format(long(deref & 0xFFFFFFFFFFFFFFFF), ma=(current_arch.ptrsize * 2 + 2))
8222 msg.append(val)
8223 break
8224
8225 return msg
8226
8227
8228@register_command
8229class ASLRCommand(GenericCommand):
8230 """View/modify the ASLR setting of GDB. By default, GDB will disable ASLR when it starts the process. (i.e. not
8231 attached). This command allows to change that setting."""
8232
8233 _cmdline_ = "aslr"
8234 _syntax_ = "{:s} (on|off)".format(_cmdline_)
8235
8236 def do_invoke(self, argv):
8237 argc = len(argv)
8238
8239 if argc == 0:
8240 ret = gdb.execute("show disable-randomization", to_string=True)
8241 i = ret.find("virtual address space is ")
8242 if i < 0:
8243 return
8244
8245 msg = "ASLR is currently "
8246 if ret[i + 25:].strip() == "on.":
8247 msg += Color.redify("disabled")
8248 else:
8249 msg += Color.greenify("enabled")
8250
8251 gef_print(msg)
8252 return
8253
8254 elif argc == 1:
8255 if argv[0] == "on":
8256 info("Enabling ASLR")
8257 gdb.execute("set disable-randomization off")
8258 return
8259 elif argv[0] == "off":
8260 info("Disabling ASLR")
8261 gdb.execute("set disable-randomization on")
8262 return
8263
8264 warn("Invalid command")
8265
8266 self.usage()
8267 return
8268
8269
8270@register_command
8271class ResetCacheCommand(GenericCommand):
8272 """Reset cache of all stored data. This command is here for debugging and test purposes, GEF
8273 handles properly the cache reset under "normal" scenario."""
8274
8275 _cmdline_ = "reset-cache"
8276 _syntax_ = _cmdline_
8277
8278 def do_invoke(self, argv):
8279 reset_all_caches()
8280 return
8281
8282
8283@register_command
8284class VMMapCommand(GenericCommand):
8285 """Display a comprehensive layout of the virtual memory mapping. If a filter argument, GEF will
8286 filter out the mapping whose pathname do not match that filter."""
8287
8288 _cmdline_ = "vmmap"
8289 _syntax_ = "{:s} [FILTER]".format(_cmdline_)
8290 _example_ = "{:s} libc".format(_cmdline_)
8291
8292 @only_if_gdb_running
8293 def do_invoke(self, argv):
8294 vmmap = get_process_maps()
8295 if not vmmap:
8296 err("No address mapping information found")
8297 return
8298
8299 if not get_gef_setting("gef.disable_color"):
8300 self.show_legend()
8301
8302 color = get_gef_setting("theme.table_heading")
8303
8304 headers = ["Start", "End", "Offset", "Perm", "Path"]
8305 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<{w}s}{:<4s} {:s}".format(*headers, w=get_memory_alignment()*2+3), color))
8306
8307 for entry in vmmap:
8308 if argv and not argv[0] in entry.path:
8309 continue
8310
8311 self.print_entry(entry)
8312 return
8313
8314 def print_entry(self, entry):
8315 line_color = ""
8316 if entry.path == "[stack]":
8317 line_color = get_gef_setting("theme.address_stack")
8318 elif entry.path == "[heap]":
8319 line_color = get_gef_setting("theme.address_heap")
8320 elif entry.permission.value & Permission.READ and entry.permission.value & Permission.EXECUTE:
8321 line_color = get_gef_setting("theme.address_code")
8322
8323 l = []
8324 l.append(Color.colorify(format_address(entry.page_start), line_color))
8325 l.append(Color.colorify(format_address(entry.page_end), line_color))
8326 l.append(Color.colorify(format_address(entry.offset), line_color))
8327
8328 if entry.permission.value == (Permission.READ|Permission.WRITE|Permission.EXECUTE):
8329 l.append(Color.colorify(str(entry.permission), "underline " + line_color))
8330 else:
8331 l.append(Color.colorify(str(entry.permission), line_color))
8332
8333 l.append(Color.colorify(entry.path, line_color))
8334 line = " ".join(l)
8335
8336 gef_print(line)
8337 return
8338
8339 def show_legend(self):
8340 code_addr_color = get_gef_setting("theme.address_code")
8341 stack_addr_color = get_gef_setting("theme.address_stack")
8342 heap_addr_color = get_gef_setting("theme.address_heap")
8343
8344 gef_print("[ Legend: {} | {} | {} ]".format(Color.colorify("Code", code_addr_color),
8345 Color.colorify("Heap", heap_addr_color),
8346 Color.colorify("Stack", stack_addr_color)
8347 ))
8348 return
8349
8350
8351@register_command
8352class XFilesCommand(GenericCommand):
8353 """Shows all libraries (and sections) loaded by binary. This command extends the GDB command
8354 `info files`, by retrieving more information from extra sources, and providing a better
8355 display. If an argument FILE is given, the output will grep information related to only that file.
8356 If an argument name is also given, the output will grep to the name within FILE."""
8357
8358 _cmdline_ = "xfiles"
8359 _syntax_ = "{:s} [FILE [NAME]]".format(_cmdline_)
8360 _example_ = "\n{0:s} libc\n{0:s} libc IO_vtables".format(_cmdline_)
8361
8362 @only_if_gdb_running
8363 def do_invoke(self, argv):
8364 color = get_gef_setting("theme.table_heading")
8365 headers = ["Start", "End", "Name", "File"]
8366 gef_print(Color.colorify("{:<{w}s}{:<{w}s}{:<21s} {:s}".format(*headers, w=get_memory_alignment()*2+3), color))
8367
8368 filter_by_file = argv[0] if argv and argv[0] else None
8369 filter_by_name = argv[1] if len(argv) > 1 and argv[1] else None
8370
8371 for xfile in get_info_files():
8372 if filter_by_file:
8373 if filter_by_file not in xfile.filename:
8374 continue
8375 if filter_by_name and filter_by_name not in xfile.name:
8376 continue
8377
8378 l = []
8379 l.append(format_address(xfile.zone_start))
8380 l.append(format_address(xfile.zone_end))
8381 l.append("{:<21s}".format(xfile.name))
8382 l.append(xfile.filename)
8383 gef_print(" ".join(l))
8384 return
8385
8386
8387@register_command
8388class XAddressInfoCommand(GenericCommand):
8389 """Retrieve and display runtime information for the location(s) given as parameter."""
8390
8391 _cmdline_ = "xinfo"
8392 _syntax_ = "{:s} LOCATION".format(_cmdline_)
8393 _example_ = "{:s} $pc".format(_cmdline_)
8394
8395 def __init__(self):
8396 super(XAddressInfoCommand, self).__init__(complete=gdb.COMPLETE_LOCATION)
8397 return
8398
8399 @only_if_gdb_running
8400 def do_invoke (self, argv):
8401 if not argv:
8402 err ("At least one valid address must be specified")
8403 self.usage()
8404 return
8405
8406 for sym in argv:
8407 try:
8408 addr = align_address(parse_address(sym))
8409 gef_print(titlify("xinfo: {:#x}".format(addr)))
8410 self.infos(addr)
8411
8412 except gdb.error as gdb_err:
8413 err("{:s}".format(str(gdb_err)))
8414 return
8415
8416 def infos(self, address):
8417 addr = lookup_address(address)
8418 if not addr.valid:
8419 warn("Cannot reach {:#x} in memory space".format(address))
8420 return
8421
8422 sect = addr.section
8423 info = addr.info
8424
8425 if sect:
8426 gef_print("Page: {:s} {:s} {:s} (size={:#x})".format(format_address(sect.page_start),
8427 RIGHT_ARROW,
8428 format_address(sect.page_end),
8429 sect.page_end-sect.page_start))
8430 gef_print("Permissions: {}".format(sect.permission))
8431 gef_print("Pathname: {:s}".format(sect.path))
8432 gef_print("Offset (from page): {:#x}".format(addr.value-sect.page_start))
8433 gef_print("Inode: {:s}".format(sect.inode))
8434
8435 if info:
8436 gef_print("Segment: {:s} ({:s}-{:s})".format(info.name,
8437 format_address(info.zone_start),
8438 format_address(info.zone_end)))
8439 gef_print("Offset (from segment): {:#x}".format(addr.value-info.zone_start))
8440
8441 sym = gdb_get_location_from_symbol(address)
8442 if sym:
8443 name, offset = sym
8444 msg = "Symbol: {:s}".format(name)
8445 if offset:
8446 msg+= "+{:d}".format(offset)
8447 gef_print(msg)
8448
8449 return
8450
8451
8452@register_command
8453class XorMemoryCommand(GenericCommand):
8454 """XOR a block of memory. The command allows to simply display the result, or patch it
8455 runtime at runtime."""
8456
8457 _cmdline_ = "xor-memory"
8458 _syntax_ = "{:s} (display|patch) ADDRESS SIZE KEY".format(_cmdline_)
8459
8460 def __init__(self):
8461 super(XorMemoryCommand, self).__init__(prefix=True)
8462 return
8463
8464 def do_invoke(self, argv):
8465 self.usage()
8466 return
8467
8468@register_command
8469class XorMemoryDisplayCommand(GenericCommand):
8470 """Display a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be
8471 provided in hexadecimal format."""
8472
8473 _cmdline_ = "xor-memory display"
8474 _syntax_ = "{:s} ADDRESS SIZE KEY".format(_cmdline_)
8475 _example_ = "{:s} $sp 16 41414141".format(_cmdline_)
8476
8477 @only_if_gdb_running
8478 def do_invoke(self, argv):
8479 if len(argv) != 3:
8480 self.usage()
8481 return
8482
8483 address = long(gdb.parse_and_eval(argv[0]))
8484 length = long(argv[1], 0)
8485 key = argv[2]
8486 block = read_memory(address, length)
8487 info("Displaying XOR-ing {:#x}-{:#x} with {:s}".format(address, address + len(block), repr(key)))
8488
8489 gef_print(titlify("Original block"))
8490 gef_print(hexdump(block, base=address))
8491
8492 gef_print(titlify("XOR-ed block"))
8493 gef_print(hexdump(xor(block, key), base=address))
8494 return
8495
8496@register_command
8497class XorMemoryPatchCommand(GenericCommand):
8498 """Patch a block of memory pointed by ADDRESS by xor-ing each byte with KEY. The key must be
8499 provided in hexadecimal format."""
8500
8501 _cmdline_ = "xor-memory patch"
8502 _syntax_ = "{:s} ADDRESS SIZE KEY".format(_cmdline_)
8503 _example_ = "{:s} $sp 16 41414141".format(_cmdline_)
8504
8505 @only_if_gdb_running
8506 def do_invoke(self, argv):
8507 if len(argv) != 3:
8508 self.usage()
8509 return
8510
8511 address = parse_address(argv[0])
8512 length = long(argv[1], 0)
8513 key = argv[2]
8514 block = read_memory(address, length)
8515 info("Patching XOR-ing {:#x}-{:#x} with '{:s}'".format(address, address + len(block), key))
8516 xored_block = xor(block, key)
8517 write_memory(address, xored_block, length)
8518 return
8519
8520
8521@register_command
8522class TraceRunCommand(GenericCommand):
8523 """Create a runtime trace of all instructions executed from $pc to LOCATION specified. The
8524 trace is stored in a text file that can be next imported in IDA Pro to visualize the runtime
8525 path."""
8526
8527 _cmdline_ = "trace-run"
8528 _syntax_ = "{:s} LOCATION [MAX_CALL_DEPTH]".format(_cmdline_)
8529 _example_ = "{:s} 0x555555554610".format(_cmdline_)
8530
8531 def __init__(self):
8532 super(TraceRunCommand, self).__init__(self._cmdline_, complete=gdb.COMPLETE_LOCATION)
8533 self.add_setting("max_tracing_recursion", 1, "Maximum depth of tracing")
8534 self.add_setting("tracefile_prefix", "./gef-trace-", "Specify the tracing output file prefix")
8535 return
8536
8537 @only_if_gdb_running
8538 def do_invoke(self, argv):
8539 if len(argv) not in (1, 2):
8540 self.usage()
8541 return
8542
8543 if len(argv) == 2 and argv[1].isdigit():
8544 depth = long(argv[1])
8545 else:
8546 depth = 1
8547
8548 try:
8549 loc_start = current_arch.pc
8550 loc_end = long(gdb.parse_and_eval(argv[0]))
8551 except gdb.error as e:
8552 err("Invalid location: {:s}".format(e))
8553 return
8554
8555 self.trace(loc_start, loc_end, depth)
8556 return
8557
8558
8559 def get_frames_size(self):
8560 n = 0
8561 f = gdb.newest_frame()
8562 while f:
8563 n += 1
8564 f = f.older()
8565 return n
8566
8567
8568 def trace(self, loc_start, loc_end, depth):
8569 info("Tracing from {:#x} to {:#x} (max depth={:d})".format(loc_start, loc_end,depth))
8570 logfile = "{:s}{:#x}-{:#x}.txt".format(self.get_setting("tracefile_prefix"), loc_start, loc_end)
8571 enable_redirect_output(to_file=logfile)
8572 hide_context()
8573 self.start_tracing(loc_start, loc_end, depth)
8574 unhide_context()
8575 disable_redirect_output()
8576 ok("Done, logfile stored as '{:s}'".format(logfile))
8577 info("Hint: import logfile with `ida_color_gdb_trace.py` script in IDA to visualize path")
8578 return
8579
8580
8581 def start_tracing(self, loc_start, loc_end, depth):
8582 loc_cur = loc_start
8583 frame_count_init = self.get_frames_size()
8584
8585 gef_print("#")
8586 gef_print("# Execution tracing of {:s}".format(get_filepath()))
8587 gef_print("# Start address: {:s}".format(format_address(loc_start)))
8588 gef_print("# End address: {:s}".format(format_address(loc_end)))
8589 gef_print("# Recursion level: {:d}".format(depth))
8590 gef_print("# automatically generated by gef.py")
8591 gef_print("#\n")
8592
8593 while loc_cur != loc_end:
8594 try:
8595 delta = self.get_frames_size() - frame_count_init
8596
8597 if delta <= depth :
8598 gdb.execute("stepi")
8599 else:
8600 gdb.execute("finish")
8601
8602 loc_cur = current_arch.pc
8603 gdb.flush()
8604
8605 except gdb.error as e:
8606 gef_print("#")
8607 gef_print("# Execution interrupted at address {:s}".format(format_address(loc_cur)))
8608 gef_print("# Exception: {:s}".format(e))
8609 gef_print("#\n")
8610 break
8611
8612 return
8613
8614
8615@register_command
8616class PatternCommand(GenericCommand):
8617 """This command will create or search a De Bruijn cyclic pattern to facilitate
8618 determining the offset in memory. The algorithm used is the same as the one
8619 used by pwntools, and can therefore be used in conjunction."""
8620
8621 _cmdline_ = "pattern"
8622 _syntax_ = "{:s} (create|search) ARGS".format(_cmdline_)
8623
8624 def __init__(self, *args, **kwargs):
8625 super(PatternCommand, self).__init__(prefix=True)
8626 self.add_setting("length", 1024, "Initial length of a cyclic buffer to generate")
8627 return
8628
8629 def do_invoke(self, argv):
8630 self.usage()
8631 return
8632
8633@register_command
8634class PatternCreateCommand(GenericCommand):
8635 """Generate a de Bruijn cyclic pattern. It will generate a pattern long of SIZE,
8636 incrementally varying of one byte at each generation. The length of each block is
8637 equal to sizeof(void*).
8638 Note: This algorithm is the same than the one used by pwntools library."""
8639
8640 _cmdline_ = "pattern create"
8641 _syntax_ = "{:s} [SIZE]".format(_cmdline_)
8642
8643 def do_invoke(self, argv):
8644 if len(argv) == 1:
8645 if not argv[0].isdigit():
8646 err("Invalid size")
8647 return
8648 set_gef_setting("pattern.length", long(argv[0]))
8649 elif len(argv) > 1:
8650 err("Invalid syntax")
8651 return
8652
8653 size = get_gef_setting("pattern.length")
8654 info("Generating a pattern of {:d} bytes".format(size))
8655 pattern_str = gef_pystring(generate_cyclic_pattern(size))
8656 gef_print(pattern_str)
8657 ok("Saved as '{:s}'".format(gef_convenience(pattern_str)))
8658 return
8659
8660@register_command
8661class PatternSearchCommand(GenericCommand):
8662 """Search for the cyclic de Bruijn pattern generated by the `pattern create` command. The
8663 PATTERN argument can be a GDB symbol (such as a register name) or an hexadecimal value."""
8664
8665 _cmdline_ = "pattern search"
8666 _syntax_ = "{:s} PATTERN [SIZE]".format(_cmdline_)
8667 _example_ = "\n{0:s} $pc\n{0:s} 0x61616164\n{0:s} aaab".format(_cmdline_)
8668 _aliases_ = ["pattern offset",]
8669
8670 @only_if_gdb_running
8671 def do_invoke(self, argv):
8672 argc = len(argv)
8673 if argc not in (1, 2):
8674 self.usage()
8675 return
8676
8677 if argc==2:
8678 if not argv[1].isdigit():
8679 err("Invalid size")
8680 return
8681 size = long(argv[1])
8682 else:
8683 size = get_gef_setting("pattern.length")
8684
8685 pattern = argv[0]
8686 info("Searching '{:s}'".format(pattern))
8687 self.search(pattern, size)
8688 return
8689
8690 def search(self, pattern, size):
8691 pattern_be, pattern_le = None, None
8692
8693 # 1. check if it's a symbol (like "$sp" or "0x1337")
8694 symbol = safe_parse_and_eval(pattern)
8695 if symbol:
8696 addr = long(symbol)
8697 dereferenced_value = dereference(addr)
8698 # 1-bis. try to dereference
8699 if dereferenced_value:
8700 addr = long(dereferenced_value)
8701
8702 if current_arch.ptrsize == 4:
8703 pattern_be = struct.pack(">I", addr)
8704 pattern_le = struct.pack("<I", addr)
8705 else:
8706 pattern_be = struct.pack(">Q", addr)
8707 pattern_le = struct.pack("<Q", addr)
8708
8709 else:
8710 # 2. assume it's a plain string
8711 pattern_be = gef_pybytes(pattern)
8712 pattern_le = gef_pybytes(pattern[::-1])
8713
8714
8715 cyclic_pattern = generate_cyclic_pattern(size)
8716 found = False
8717 off = cyclic_pattern.find(pattern_le)
8718 if off >= 0:
8719 ok("Found at offset {:d} (little-endian search) {:s}".format(off, Color.colorify("likely", "bold red") if is_little_endian() else ""))
8720 found = True
8721
8722 off = cyclic_pattern.find(pattern_be)
8723 if off >= 0:
8724 ok("Found at offset {:d} (big-endian search) {:s}".format(off, Color.colorify("likely", "bold green") if is_big_endian() else ""))
8725 found = True
8726
8727 if not found:
8728 err("Pattern '{}' not found".format(pattern))
8729 return
8730
8731
8732@register_command
8733class ChecksecCommand(GenericCommand):
8734 """Checksec the security properties of the current executable or passed as argument. The
8735 command checks for the following protections:
8736 - PIE
8737 - NX
8738 - RelRO
8739 - Glibc Stack Canaries
8740 - Fortify Source"""
8741
8742 _cmdline_ = "checksec"
8743 _syntax_ = "{:s} [FILENAME]".format(_cmdline_)
8744 _example_ = "{} /bin/ls".format(_cmdline_)
8745
8746 def __init__(self):
8747 super(ChecksecCommand, self).__init__(complete=gdb.COMPLETE_FILENAME)
8748 return
8749
8750 def pre_load(self):
8751 which("readelf")
8752 return
8753
8754 def do_invoke(self, argv):
8755 argc = len(argv)
8756
8757 if argc == 0:
8758 filename = get_filepath()
8759 if filename is None:
8760 warn("No executable/library specified")
8761 return
8762 elif argc == 1:
8763 filename = os.path.realpath(os.path.expanduser(argv[0]))
8764 if not os.access(filename, os.R_OK):
8765 err("Invalid filename")
8766 return
8767 else:
8768 self.usage()
8769 return
8770
8771 info("{:s} for '{:s}'".format(self._cmdline_, filename))
8772 self.print_security_properties(filename)
8773 return
8774
8775 def print_security_properties(self, filename):
8776 sec = checksec(filename)
8777 for prop in sec:
8778 if prop in ("Partial RelRO", "Full RelRO"): continue
8779 val = sec[prop]
8780 msg = Color.greenify(Color.boldify(TICK)) if val is True else Color.redify(Color.boldify(CROSS))
8781 if val and prop=="Canary" and is_alive():
8782 canary = gef_read_canary()[0]
8783 msg+= "(value: {:#x})".format(canary)
8784
8785 gef_print("{:<30s}: {:s}".format(prop, msg))
8786
8787 if sec["Full RelRO"]:
8788 gef_print("{:<30s}: {:s}".format("RelRO", Color.greenify("Full")))
8789 elif sec["Partial RelRO"]:
8790 gef_print("{:<30s}: {:s}".format("RelRO", Color.yellowify("Partial")))
8791 else:
8792 gef_print("{:<30s}: {:s}".format("RelRO", Color.redify(Color.boldify(CROSS))))
8793 return
8794
8795
8796@register_command
8797class GotCommand(GenericCommand):
8798 """Display current status of the got inside the process."""
8799
8800 _cmdline_ = "got"
8801 _syntax_ = "{:s} [FUNCTION_NAME ...] ".format(_cmdline_)
8802 _example_ = "got read printf exit"
8803
8804 def __init__(self, *args, **kwargs):
8805 super(GotCommand, self).__init__()
8806 self.add_setting("function_resolved", "green", "Line color of the got command output if the function has "
8807 "been resolved")
8808 self.add_setting("function_not_resolved", "yellow", "Line color of the got command output if the function has "
8809 "not been resolved")
8810 return
8811
8812 def get_jmp_slots(self, readelf, filename):
8813 output = []
8814 cmd = [readelf, "--relocs", filename]
8815 lines = gef_execute_external(cmd, as_list=True)
8816 for line in lines:
8817 if "JUMP" in line:
8818 output.append(line)
8819 return output
8820
8821 @only_if_gdb_running
8822 def do_invoke(self, argv):
8823
8824 try:
8825 readelf = which("readelf")
8826 except IOError:
8827 err("Missing `readelf`")
8828 return
8829
8830 # get the filtering parameter.
8831 func_names_filter = []
8832 if argv:
8833 func_names_filter = argv
8834
8835 # getting vmmap to understand the boundaries of the main binary
8836 # we will use this info to understand if a function has been resolved or not.
8837 vmmap = get_process_maps()
8838 base_address = min([x.page_start for x in vmmap if x.path == get_filepath()])
8839 end_address = max([x.page_end for x in vmmap if x.path == get_filepath()])
8840
8841 # get the checksec output.
8842 checksec_status = checksec(get_filepath())
8843 relro_status = "Full RelRO"
8844 full_relro = checksec_status["Full RelRO"]
8845 pie = checksec_status["PIE"] # if pie we will have offset instead of abs address.
8846
8847 if not full_relro:
8848 relro_status = "Partial RelRO"
8849 partial_relro = checksec_status["Partial RelRO"]
8850
8851 if not partial_relro:
8852 relro_status = "No RelRO"
8853
8854 # retrieve jump slots using readelf
8855 jmpslots = self.get_jmp_slots(readelf, get_filepath())
8856
8857 gef_print("\nGOT protection: {} | GOT functions: {}\n ".format(relro_status, len(jmpslots)))
8858
8859 for line in jmpslots:
8860 address, _, _, _, name = line.split()[:5]
8861
8862 # if we have a filter let's skip the entries that are not requested.
8863 if func_names_filter:
8864 if not any(map(lambda x: x in name, func_names_filter)):
8865 continue
8866
8867 address_val = int(address, 16)
8868
8869 # address_val is an offset from the base_address if we have PIE.
8870 if pie:
8871 address_val = base_address + address_val
8872
8873 # read the address of the function.
8874 got_address = read_int_from_memory(address_val)
8875
8876 # for the swag: different colors if the function has been resolved or not.
8877 if base_address < got_address < end_address:
8878 color = self.get_setting("function_not_resolved") # function hasn't already been resolved
8879 else:
8880 color = self.get_setting("function_resolved") # function has already been resolved
8881
8882 line = "[{}] ".format(hex(address_val))
8883 line += Color.colorify("{} {} {}".format(name, RIGHT_ARROW, hex(got_address)), color)
8884 gef_print(line)
8885
8886 return
8887
8888
8889@register_command
8890class HighlightCommand(GenericCommand):
8891 """
8892 This command highlights user defined text matches which modifies GEF output universally.
8893 """
8894 _cmdline_ = "highlight"
8895 _syntax_ = "{} (add|remove|list|clear)".format(_cmdline_)
8896 _aliases_ = ["hl"]
8897
8898 def __init__(self):
8899 super(HighlightCommand, self).__init__(prefix=True)
8900 self.add_setting("regex", False, "Enable regex highlighting")
8901
8902 def do_invoke(self, argv):
8903 return self.usage()
8904
8905
8906@register_command
8907class HighlightListCommand(GenericCommand):
8908 """Show the current highlight table with matches to colors."""
8909 _cmdline_ = "highlight list"
8910 _aliases_ = ["highlight ls", "hll"]
8911 _syntax_ = _cmdline_
8912
8913 def print_highlight_table(self):
8914 if not highlight_table:
8915 return err("no matches found")
8916
8917 left_pad = max(map(len, highlight_table.keys()))
8918 for match, color in sorted(highlight_table.items()):
8919 print("{} | {}".format(Color.colorify(match.ljust(left_pad), color),
8920 Color.colorify(color, color)))
8921 return
8922
8923 def do_invoke(self, argv):
8924 return self.print_highlight_table()
8925
8926
8927@register_command
8928class HighlightClearCommand(GenericCommand):
8929 """Clear the highlight table, remove all matches."""
8930 _cmdline_ = "highlight clear"
8931 _aliases_ = ["hlc"]
8932 _syntax_ = _cmdline_
8933
8934 def do_invoke(self, argv):
8935 return highlight_table.clear()
8936
8937
8938@register_command
8939class HighlightAddCommand(GenericCommand):
8940 """Add a match to the highlight table."""
8941 _cmdline_ = "highlight add"
8942 _syntax_ = "{} MATCH COLOR".format(_cmdline_)
8943 _aliases_ = ["highlight set", "hla"]
8944 _example_ = "{} 41414141 yellow".format(_cmdline_)
8945
8946 def do_invoke(self, argv):
8947 if len(argv) < 2:
8948 return self.usage()
8949
8950 match, color = argv
8951 highlight_table[match] = color
8952 return
8953
8954
8955@register_command
8956class HighlightRemoveCommand(GenericCommand):
8957 """Remove a match in the highlight table."""
8958 _cmdline_ = "highlight remove"
8959 _syntax_ = "{} MATCH".format(_cmdline_)
8960 _aliases_ = [
8961 "highlight delete",
8962 "highlight del",
8963 "highlight unset",
8964 "highlight rm",
8965 "hlr"
8966 ]
8967 _example_ = "{} remove 41414141".format(_cmdline_)
8968
8969 def do_invoke(self, argv):
8970 if not argv:
8971 return self.usage()
8972
8973 highlight_table.pop(argv[0], None)
8974 return
8975
8976
8977@register_command
8978class FormatStringSearchCommand(GenericCommand):
8979 """Exploitable format-string helper: this command will set up specific breakpoints
8980 at well-known dangerous functions (printf, snprintf, etc.), and check if the pointer
8981 holding the format string is writable, and therefore susceptible to format string
8982 attacks if an attacker can control its content."""
8983 _cmdline_ = "format-string-helper"
8984 _syntax_ = _cmdline_
8985 _aliases_ = ["fmtstr-helper",]
8986
8987 def do_invoke(self, argv):
8988 dangerous_functions = {
8989 "printf": 0,
8990 "sprintf": 1,
8991 "fprintf": 1,
8992 "snprintf": 2,
8993 "vsnprintf": 2,
8994 }
8995
8996 enable_redirect_output("/dev/null")
8997
8998 for func_name, num_arg in dangerous_functions.items():
8999 FormatStringBreakpoint(func_name, num_arg)
9000
9001 disable_redirect_output()
9002 ok("Enabled {:d} FormatStringBreakpoint".format(len(dangerous_functions)))
9003 return
9004
9005
9006
9007@register_command
9008class HeapAnalysisCommand(GenericCommand):
9009 """Heap vulnerability analysis helper: this command aims to track dynamic heap allocation
9010 done through malloc()/free() to provide some insights on possible heap vulnerabilities. The
9011 following vulnerabilities are checked:
9012 - NULL free
9013 - Use-after-Free
9014 - Double Free
9015 - Heap overlap"""
9016 _cmdline_ = "heap-analysis-helper"
9017 _syntax_ = _cmdline_
9018
9019 def __init__(self, *args, **kwargs):
9020 super(HeapAnalysisCommand, self).__init__(complete=gdb.COMPLETE_NONE)
9021 self.add_setting("check_free_null", False, "Break execution when a free(NULL) is encountered")
9022 self.add_setting("check_double_free", True, "Break execution when a double free is encountered")
9023 self.add_setting("check_weird_free", True, "Break execution when free() is called against a non-tracked pointer")
9024 self.add_setting("check_uaf", True, "Break execution when a possible Use-after-Free condition is found")
9025 self.add_setting("check_heap_overlap", True, "Break execution when a possible overlap in allocation is found")
9026
9027 self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc = None, None, None, None
9028 return
9029
9030 @only_if_gdb_running
9031 @experimental_feature
9032 def do_invoke(self, argv):
9033 if not argv:
9034 self.setup()
9035 return
9036
9037 if argv[0]=="show":
9038 self.dump_tracked_allocations()
9039 return
9040
9041 def setup(self):
9042 ok("Tracking malloc() & calloc()")
9043 self.bp_malloc = TraceMallocBreakpoint("__libc_malloc")
9044 self.bp_calloc = TraceMallocBreakpoint("__libc_calloc")
9045 ok("Tracking free()")
9046 self.bp_free = TraceFreeBreakpoint()
9047 ok("Tracking realloc()")
9048 self.bp_realloc = TraceReallocBreakpoint()
9049
9050 ok("Disabling hardware watchpoints (this may increase the latency)")
9051 gdb.execute("set can-use-hw-watchpoints 0")
9052
9053 info("Dynamic breakpoints correctly setup, GEF will break execution if a possible vulnerabity is found.")
9054 warn("{}: The heap analysis slows down the execution noticeably.".format(
9055 Color.colorify("Note", "bold underline yellow")))
9056
9057
9058 # when inferior quits, we need to clean everything for a next execution
9059 gef_on_exit_hook(self.clean)
9060 return
9061
9062 def dump_tracked_allocations(self):
9063 global __heap_allocated_list__, __heap_freed_list__, __heap_uaf_watchpoints__
9064
9065 if __heap_allocated_list__:
9066 ok("Tracked as in-use chunks:")
9067 for addr, sz in __heap_allocated_list__: gef_print("{} malloc({:d}) = {:#x}".format(CROSS, sz, addr))
9068 else:
9069 ok("No malloc() chunk tracked")
9070
9071 if __heap_freed_list__:
9072 ok("Tracked as free-ed chunks:")
9073 for addr, sz in __heap_freed_list__: gef_print("{} free({:d}) = {:#x}".format(TICK, sz, addr))
9074 else:
9075 ok("No free() chunk tracked")
9076 return
9077
9078 def clean(self, event):
9079 global __heap_allocated_list__, __heap_freed_list__, __heap_uaf_watchpoints__
9080
9081 ok("{} - Cleaning up".format(Color.colorify("Heap-Analysis", "yellow bold"),))
9082 for bp in [self.bp_malloc, self.bp_calloc, self.bp_free, self.bp_realloc]:
9083 if hasattr(bp, "retbp") and bp.retbp:
9084 bp.retbp.delete()
9085 bp.delete()
9086
9087 for wp in __heap_uaf_watchpoints__:
9088 wp.delete()
9089
9090 __heap_allocated_list__ = []
9091 __heap_freed_list__ = []
9092 __heap_uaf_watchpoints__ = []
9093
9094 ok("{} - Re-enabling hardware watchpoints".format(Color.colorify("Heap-Analysis", "yellow bold"),))
9095 gdb.execute("set can-use-hw-watchpoints 1")
9096
9097 gef_on_exit_unhook(self.clean)
9098 return
9099
9100
9101@register_command
9102class IsSyscallCommand(GenericCommand):
9103 """
9104 Tells whether the next instruction is a system call."""
9105 _cmdline_ = "is-syscall"
9106 _syntax_ = _cmdline_
9107
9108 def do_invoke(self, argv):
9109 insn = gef_current_instruction(current_arch.pc)
9110 ok("Current instruction is{}a syscall".format(" " if self.is_syscall(current_arch, insn) else " not "))
9111
9112 return
9113
9114 def is_syscall(self, arch, instruction):
9115 insn_str = instruction.mnemonic + " " + ", ".join(instruction.operands)
9116 return insn_str.strip() in arch.syscall_instructions
9117
9118
9119@register_command
9120class SyscallArgsCommand(GenericCommand):
9121 """Gets the syscall name and arguments based on the register values in the current state."""
9122 _cmdline_ = "syscall-args"
9123 _syntax_ = _cmdline_
9124
9125 def __init__(self):
9126 super(SyscallArgsCommand, self).__init__()
9127 self.add_setting("path", os.path.join(GEF_TEMP_DIR, "syscall-tables"),
9128 "Path to store/load the syscall tables files")
9129 return
9130
9131 def do_invoke(self, argv):
9132 color = get_gef_setting("theme.table_heading")
9133
9134 path = self.get_settings_path()
9135 if path is None:
9136 err("Cannot open '{0}': check directory and/or `gef config {0}` setting, "
9137 "currently: '{1}'".format("syscall-args.path", self.get_setting("path")))
9138 return
9139
9140 arch = current_arch.__class__.__name__
9141 syscall_table = self.get_syscall_table(arch)
9142
9143 reg_value = get_register(current_arch.syscall_register)
9144 if reg_value not in syscall_table:
9145 warn("There is no system call for {:#x}".format(reg_value))
9146 return
9147 syscall_entry = syscall_table[reg_value]
9148
9149 values = []
9150 for param in syscall_entry.params:
9151 values.append(get_register(param.reg))
9152
9153 parameters = [s.param for s in syscall_entry.params]
9154 registers = [s.reg for s in syscall_entry.params]
9155
9156 info("Detected syscall {}".format(Color.colorify(syscall_entry.name, color)))
9157 gef_print(" {}({})".format(syscall_entry.name, ", ".join(parameters)))
9158
9159 headers = ["Parameter", "Register", "Value"]
9160 param_names = [re.split(r" |\*", p)[-1] for p in parameters]
9161 info(Color.colorify("{:<28} {:<28} {}".format(*headers), color))
9162 for name, register, value in zip(param_names, registers, values):
9163 line = " {:<15} {:<15} 0x{:x}".format(name, register, value)
9164
9165 addrs = DereferenceCommand.dereference_from(value)
9166
9167 if len(addrs) > 1:
9168 sep = " {:s} ".format(RIGHT_ARROW)
9169 line += sep
9170 line += sep.join(addrs[1:])
9171
9172 gef_print(line)
9173
9174 return
9175
9176 def get_filepath(self, x):
9177 p = self.get_settings_path()
9178 if not p: return None
9179 return os.path.join(p, "{}.py".format(x))
9180
9181 def get_module(self, modname):
9182 _fullname = self.get_filepath(modname)
9183 return imp.load_source(modname, _fullname)
9184
9185 def get_syscall_table(self, modname):
9186 _mod = self.get_module(modname)
9187 return getattr(_mod, "syscall_table")
9188
9189 def get_settings_path(self):
9190 path = os.path.expanduser(self.get_setting("path"))
9191 path = os.path.realpath(path)
9192 return path if os.path.isdir(path) else None
9193
9194
9195@lru_cache()
9196def get_section_base_address(name):
9197 section = process_lookup_path(name)
9198 if section:
9199 return section.page_start
9200
9201 return None
9202
9203@lru_cache()
9204def get_zone_base_address(name):
9205 zone = file_lookup_name_path(name, get_filepath())
9206 if zone:
9207 return zone.zone_start
9208
9209 return None
9210
9211class GenericFunction(gdb.Function):
9212 """This is an abstract class for invoking convenience functions, should not be instantiated."""
9213 __metaclass__ = abc.ABCMeta
9214
9215 @abc.abstractproperty
9216 def _function_(self): pass
9217 @property
9218 def _syntax_(self):
9219 return "${}([offset])".format(self._function_)
9220
9221 def __init__ (self):
9222 super(GenericFunction, self).__init__(self._function_)
9223
9224 def invoke(self, *args):
9225 if not is_alive():
9226 raise gdb.GdbError("No debugging session active")
9227 return long(self.do_invoke(args))
9228
9229 def arg_to_long(self, args, index, default=0):
9230 try:
9231 addr = args[index]
9232 return long(addr) if addr.address is None else long(addr.address)
9233 except IndexError:
9234 return default
9235
9236 @abc.abstractmethod
9237 def do_invoke(self, args): pass
9238
9239
9240@register_function
9241class StackOffsetFunction(GenericFunction):
9242 """Return the current stack base address plus an optional offset."""
9243 _function_ = "_stack"
9244
9245 def do_invoke(self, args):
9246 return self.arg_to_long(args, 0) + get_section_base_address("[stack]")
9247
9248@register_function
9249class HeapBaseFunction(GenericFunction):
9250 """Return the current heap base address plus an optional offset."""
9251 _function_ = "_heap"
9252
9253 def do_invoke(self, args):
9254 base = HeapBaseFunction.heap_base()
9255 if not base:
9256 raise gdb.GdbError("Heap not found")
9257
9258 return self.arg_to_long(args, 0) + base
9259
9260 @staticmethod
9261 def heap_base():
9262 try:
9263 base = long(gdb.parse_and_eval("mp_->sbrk_base"))
9264 if base != 0:
9265 return base
9266 except gdb.error:
9267 pass
9268 return get_section_base_address("[heap]")
9269
9270@register_function
9271class PieBaseFunction(GenericFunction):
9272 """Return the current pie base address plus an optional offset."""
9273 _function_ = "_pie"
9274
9275 def do_invoke(self, args):
9276 return self.arg_to_long(args, 0) + get_section_base_address(get_filepath())
9277
9278@register_function
9279class BssBaseFunction(GenericFunction):
9280 """Return the current bss base address plus the given offset."""
9281 _function_ = "_bss"
9282
9283 def do_invoke(self, args):
9284 return self.arg_to_long(args, 0) + get_zone_base_address(".bss")
9285
9286@register_function
9287class GotBaseFunction(GenericFunction):
9288 """Return the current bss base address plus the given offset."""
9289 _function_ = "_got"
9290
9291 def do_invoke(self, args):
9292 return self.arg_to_long(args, 0) + get_zone_base_address(".got")
9293
9294@register_command
9295class GefFunctionsCommand(GenericCommand):
9296 """List the convenience functions provided by GEF."""
9297 _cmdline_ = "functions"
9298 _syntax_ = _cmdline_
9299
9300 def __init__(self):
9301 super(GefFunctionsCommand, self).__init__()
9302 self.docs = []
9303 self.setup()
9304 return
9305
9306 def setup(self):
9307 global __gef__
9308 for function in __gef__.loaded_functions:
9309 self.add_function_to_doc(function)
9310 self.__doc__ = "\n".join(sorted(self.docs))
9311 return
9312
9313 def add_function_to_doc(self, function):
9314 """Add function to documentation."""
9315 doc = getattr(function, "__doc__", "").lstrip()
9316 doc = "\n ".join(doc.split("\n"))
9317 syntax = getattr(function, "_syntax_", "").lstrip()
9318 msg = "{syntax:<25s} -- {help:s}".format(syntax=syntax, help=Color.greenify(doc))
9319 self.docs.append(msg)
9320 return
9321
9322 def do_invoke(self, argv):
9323 self.dont_repeat()
9324 gef_print(titlify("GEF - Convenience Functions"))
9325 gef_print("These functions can be used as arguments to other "
9326 "commands to dynamically calculate values, eg: {:s}\n"
9327 .format(Color.colorify("deref $_heap(0x20)", "yellow")))
9328 gef_print(self.__doc__)
9329 return
9330
9331class GefCommand(gdb.Command):
9332 """GEF main command: view all new commands by typing `gef`."""
9333
9334 _cmdline_ = "gef"
9335 _syntax_ = "{:s} (missing|config|save|restore|set|run)".format(_cmdline_)
9336
9337 def __init__(self):
9338 super(GefCommand, self).__init__(GefCommand._cmdline_,
9339 gdb.COMMAND_SUPPORT,
9340 gdb.COMPLETE_NONE,
9341 True)
9342 set_gef_setting("gef.follow_child", True, bool, "Automatically set GDB to follow child when forking")
9343 set_gef_setting("gef.readline_compat", False, bool, "Workaround for readline SOH/ETX issue (SEGV)")
9344 set_gef_setting("gef.debug", False, bool, "Enable debug mode for gef")
9345 set_gef_setting("gef.autosave_breakpoints_file", "", str, "Automatically save and restore breakpoints")
9346 set_gef_setting("gef.extra_plugins_dir", "", str, "Autoload additional GEF commands from external directory")
9347 set_gef_setting("gef.disable_color", False, bool, "Disable all colors in GEF")
9348 self.loaded_commands = []
9349 self.loaded_functions = []
9350 self.missing_commands = {}
9351 return
9352
9353 def setup(self):
9354 self.load(initial=True)
9355 # loading GEF sub-commands
9356 self.doc = GefHelpCommand(self.loaded_commands)
9357 self.cfg = GefConfigCommand(self.loaded_command_names)
9358 GefSaveCommand()
9359 GefRestoreCommand()
9360 GefMissingCommand()
9361 GefSetCommand()
9362 GefRunCommand()
9363
9364 # load the saved settings
9365 gdb.execute("gef restore")
9366
9367 # restore the autosave/autoreload breakpoints policy (if any)
9368 self.__reload_auto_breakpoints()
9369
9370 # load plugins from `extra_plugins_dir`
9371 if self.__load_extra_plugins() > 0:
9372 # if here, at least one extra plugin was loaded, so we need to restore
9373 # the settings once more
9374 gdb.execute("gef restore quiet")
9375
9376 return
9377
9378
9379 def __reload_auto_breakpoints(self):
9380 bkp_fname = __config__.get("gef.autosave_breakpoints_file", None)
9381 bkp_fname = bkp_fname[0] if bkp_fname else None
9382 if bkp_fname:
9383 # restore if existing
9384 if os.access(bkp_fname, os.R_OK):
9385 gdb.execute("source {:s}".format(bkp_fname))
9386
9387 # add hook for autosave breakpoints on quit command
9388 source = [
9389 "define hook-quit",
9390 " save breakpoints {:s}".format(bkp_fname),
9391 "end"
9392 ]
9393 gef_execute_gdb_script("\n".join(source) + "\n")
9394 return
9395
9396
9397 def __load_extra_plugins(self):
9398 nb_added = -1
9399 try:
9400 nb_inital = len(self.loaded_commands)
9401 directories = get_gef_setting("gef.extra_plugins_dir")
9402 if directories:
9403 for directory in directories.split(";"):
9404 directory = os.path.realpath(os.path.expanduser(directory))
9405 if os.path.isdir(directory):
9406 sys.path.append(directory)
9407 for fname in os.listdir(directory):
9408 if not fname.endswith(".py"): continue
9409 fpath = "{:s}/{:s}".format(directory, fname)
9410 if os.path.isfile(fpath):
9411 gdb.execute("source {:s}".format(fpath))
9412 nb_added = len(self.loaded_commands) - nb_inital
9413 if nb_added > 0:
9414 ok("{:s} extra commands added from '{:s}'".format(Color.colorify(nb_added, "bold green"),
9415 Color.colorify(directory, "bold blue")))
9416 except gdb.error as e:
9417 err("failed: {}".format(str(e)))
9418 return nb_added
9419
9420
9421 @property
9422 def loaded_command_names(self):
9423 return [x[0] for x in self.loaded_commands]
9424
9425
9426 def invoke(self, args, from_tty):
9427 self.dont_repeat()
9428 gdb.execute("gef help")
9429 return
9430
9431
9432 def load(self, initial=False):
9433 """Load all the commands and functions defined by GEF into GDB."""
9434 nb_missing = 0
9435 self.commands = [(x._cmdline_, x) for x in __commands__]
9436
9437 # load all of the functions
9438 for function_class_name in __functions__:
9439 self.loaded_functions.append(function_class_name())
9440
9441 def is_loaded(x):
9442 return any(filter(lambda u: x == u[0], self.loaded_commands))
9443
9444 for cmd, class_name in self.commands:
9445 if is_loaded(cmd):
9446 continue
9447
9448 try:
9449 self.loaded_commands.append((cmd, class_name, class_name()))
9450
9451 if hasattr(class_name, "_aliases_"):
9452 aliases = getattr(class_name, "_aliases_")
9453 for alias in aliases:
9454 GefAlias(alias, cmd)
9455
9456 except Exception as reason:
9457 self.missing_commands[cmd] = reason
9458 nb_missing += 1
9459
9460 # sort by command name
9461 self.loaded_commands = sorted(self.loaded_commands, key=lambda x: x[1]._cmdline_)
9462
9463 if initial:
9464 gef_print("{:s} for {:s} ready, type `{:s}' to start, `{:s}' to configure"
9465 .format(Color.greenify("GEF"), get_os(),
9466 Color.colorify("gef","underline yellow"),
9467 Color.colorify("gef config", "underline pink")))
9468
9469 ver = "{:d}.{:d}".format(sys.version_info.major, sys.version_info.minor)
9470 nb_cmds = len(self.loaded_commands)
9471 gef_print("{:s} commands loaded for GDB {:s} using Python engine {:s}"
9472 .format(Color.colorify(nb_cmds, "bold green"),
9473 Color.colorify(gdb.VERSION, "bold yellow"),
9474 Color.colorify(ver, "bold red")))
9475
9476 if nb_missing:
9477 warn("{:s} command{} could not be loaded, run `{:s}` to know why."
9478 .format(Color.colorify(nb_missing, "bold red"),
9479 "s" if nb_missing > 1 else "",
9480 Color.colorify("gef missing", "underline pink")))
9481 return
9482
9483
9484class GefHelpCommand(gdb.Command):
9485 """GEF help sub-command."""
9486 _cmdline_ = "gef help"
9487 _syntax_ = _cmdline_
9488
9489 def __init__(self, commands, *args, **kwargs):
9490 super(GefHelpCommand, self).__init__(GefHelpCommand._cmdline_,
9491 gdb.COMMAND_SUPPORT,
9492 gdb.COMPLETE_NONE,
9493 False)
9494 self.docs = []
9495 self.generate_help(commands)
9496 self.refresh()
9497 return
9498
9499 def invoke(self, args, from_tty):
9500 self.dont_repeat()
9501 gef_print(titlify("GEF - GDB Enhanced Features"))
9502 gef_print(self.__doc__)
9503 return
9504
9505 def generate_help(self, commands):
9506 """Generate builtin commands documentation."""
9507 for command in commands:
9508 self.add_command_to_doc(command)
9509 return
9510
9511 def add_command_to_doc(self, command):
9512 """Add command to GEF documentation."""
9513 cmd, class_name, _ = command
9514 if " " in cmd:
9515 # do not print subcommands in gef help
9516 return
9517 doc = getattr(class_name, "__doc__", "").lstrip()
9518 doc = "\n ".join(doc.split("\n"))
9519 aliases = " (alias: {:s})".format(", ".join(class_name._aliases_)) if hasattr(class_name, "_aliases_") else ""
9520 msg = "{cmd:<25s} -- {help:s}{aliases:s}".format(cmd=cmd, help=Color.greenify(doc), aliases=aliases)
9521 self.docs.append(msg)
9522 return
9523
9524 def refresh(self):
9525 """Refresh the documentation."""
9526 self.__doc__ = "\n".join(sorted(self.docs))
9527 return
9528
9529
9530class GefConfigCommand(gdb.Command):
9531 """GEF configuration sub-command
9532 This command will help set/view GEF settingsfor the current debugging session.
9533 It is possible to make those changes permanent by running `gef save` (refer
9534 to this command help), and/or restore previously saved settings by running
9535 `gef restore` (refer help).
9536 """
9537 _cmdline_ = "gef config"
9538 _syntax_ = "{:s} [setting_name] [setting_value]".format(_cmdline_)
9539
9540 def __init__(self, loaded_commands, *args, **kwargs):
9541 super(GefConfigCommand, self).__init__(GefConfigCommand._cmdline_, gdb.COMMAND_NONE, prefix=False)
9542 self.loaded_commands = loaded_commands
9543 return
9544
9545 def invoke(self, args, from_tty):
9546 self.dont_repeat()
9547 argv = gdb.string_to_argv(args)
9548 argc = len(argv)
9549
9550 if not (0 <= argc <= 2):
9551 err("Invalid number of arguments")
9552 return
9553
9554 if argc == 0:
9555 gef_print(titlify("GEF configuration settings"))
9556 self.print_settings()
9557 return
9558
9559 if argc == 1:
9560 prefix = argv[0]
9561 names = list(filter(lambda x: x.startswith(prefix), __config__.keys()))
9562 if names:
9563 if len(names)==1:
9564 gef_print(titlify("GEF configuration setting: {:s}".format(names[0])))
9565 self.print_setting(names[0], verbose=True)
9566 else:
9567 gef_print(titlify("GEF configuration settings matching '{:s}'".format(argv[0])))
9568 for name in names: self.print_setting(name)
9569 return
9570
9571 self.set_setting(argc, argv)
9572 return
9573
9574 def print_setting(self, plugin_name, verbose=False):
9575 res = __config__.get(plugin_name)
9576 string_color = get_gef_setting("theme.dereference_string")
9577 misc_color = get_gef_setting("theme.dereference_base_address")
9578
9579 if not res:
9580 return
9581
9582 _value, _type, _desc = res
9583 _setting = Color.colorify(plugin_name, "green")
9584 _type = _type.__name__
9585 if _type == "str":
9586 _value = '"{:s}"'.format(Color.colorify(_value, string_color))
9587 else:
9588 _value = Color.colorify(_value, misc_color)
9589
9590 gef_print("{:s} ({:s}) = {:s}".format(_setting, _type, _value))
9591
9592 if verbose:
9593 gef_print(Color.colorify("\nDescription:", "bold underline"))
9594 gef_print("\t{:s}".format(_desc))
9595 return
9596
9597 def print_settings(self):
9598 for x in sorted(__config__):
9599 self.print_setting(x)
9600 return
9601
9602 def set_setting(self, argc, argv):
9603 global __gef__
9604 if "." not in argv[0]:
9605 err("Invalid command format")
9606 return
9607
9608 loaded_commands = [ x[0] for x in __gef__.loaded_commands ] + ["gef"]
9609 plugin_name = argv[0].split(".", 1)[0]
9610 if plugin_name not in loaded_commands:
9611 err("Unknown plugin '{:s}'".format(plugin_name))
9612 return
9613
9614 _type = __config__.get(argv[0], [None, None, None])[1]
9615 if _type is None:
9616 err("Failed to get '{:s}' config setting".format(argv[0],))
9617 return
9618
9619 try:
9620 if _type == bool:
9621 _newval = True if argv[1].upper() in ("TRUE", "T", "1") else False
9622 else:
9623 _newval = _type(argv[1])
9624
9625 except Exception:
9626 err("{} expects type '{}'".format(argv[0], _type.__name__))
9627 return
9628
9629 reset_all_caches()
9630 __config__[argv[0]][0] = _newval
9631 return
9632
9633 def complete(self, text, word):
9634 settings = sorted(__config__)
9635
9636 if text=="":
9637 # no prefix: example: `gef config TAB`
9638 return [s for s in settings if word in s]
9639
9640 if "." not in text:
9641 # if looking for possible prefix
9642 return [s for s in settings if s.startswith(text.strip())]
9643
9644 # finally, look for possible values for given prefix
9645 return [s.split(".", 1)[1] for s in settings if s.startswith(text.strip())]
9646
9647
9648class GefSaveCommand(gdb.Command):
9649 """GEF save sub-command.
9650 Saves the current configuration of GEF to disk (by default in file '~/.gef.rc')."""
9651 _cmdline_ = "gef save"
9652 _syntax_ = _cmdline_
9653
9654 def __init__(self, *args, **kwargs):
9655 super(GefSaveCommand, self).__init__(GefSaveCommand._cmdline_, gdb.COMMAND_SUPPORT,
9656 gdb.COMPLETE_NONE, False)
9657 return
9658
9659 def invoke(self, args, from_tty):
9660 self.dont_repeat()
9661 cfg = configparser.RawConfigParser()
9662 old_sect = None
9663
9664 # save the configuration
9665 for key in sorted(__config__):
9666 sect, optname = key.split(".", 1)
9667 value = __config__.get(key, None)
9668 value = value[0] if value else None
9669
9670 if old_sect != sect:
9671 cfg.add_section(sect)
9672 old_sect = sect
9673
9674 cfg.set(sect, optname, value)
9675
9676 # save the aliases
9677 cfg.add_section("aliases")
9678 for alias in __aliases__:
9679 cfg.set("aliases", alias._alias, alias._command)
9680
9681 with open(GEF_RC, "w") as fd:
9682 cfg.write(fd)
9683
9684 ok("Configuration saved to '{:s}'".format(GEF_RC))
9685 return
9686
9687
9688class GefRestoreCommand(gdb.Command):
9689 """GEF restore sub-command.
9690 Loads settings from file '~/.gef.rc' and apply them to the configuration of GEF."""
9691 _cmdline_ = "gef restore"
9692 _syntax_ = _cmdline_
9693
9694 def __init__(self, *args, **kwargs):
9695 super(GefRestoreCommand, self).__init__(GefRestoreCommand._cmdline_,
9696 gdb.COMMAND_SUPPORT,
9697 gdb.COMPLETE_NONE,
9698 False)
9699 return
9700
9701 def invoke(self, args, from_tty):
9702 self.dont_repeat()
9703 if not os.access(GEF_RC, os.R_OK):
9704 return
9705
9706 quiet = args.lower() == "quiet"
9707 cfg = configparser.ConfigParser()
9708 cfg.read(GEF_RC)
9709
9710 for section in cfg.sections():
9711 if section == "aliases":
9712 # load the aliases
9713 for key in cfg.options(section):
9714 GefAlias(key, cfg.get(section, key))
9715 continue
9716
9717 # load the other options
9718 for optname in cfg.options(section):
9719 try:
9720 key = "{:s}.{:s}".format(section, optname)
9721 _type = __config__.get(key)[1]
9722 new_value = cfg.get(section, optname)
9723 if _type == bool:
9724 new_value = True if new_value == "True" else False
9725 else:
9726 new_value = _type(new_value)
9727 __config__[key][0] = new_value
9728 except Exception:
9729 pass
9730
9731 if not quiet:
9732 ok("Configuration from '{:s}' restored".format(Color.colorify(GEF_RC, "bold blue")))
9733 return
9734
9735
9736class GefMissingCommand(gdb.Command):
9737 """GEF missing sub-command
9738 Display the GEF commands that could not be loaded, along with the reason of why
9739 they could not be loaded.
9740 """
9741 _cmdline_ = "gef missing"
9742 _syntax_ = _cmdline_
9743
9744 def __init__(self, *args, **kwargs):
9745 super(GefMissingCommand, self).__init__(GefMissingCommand._cmdline_,
9746 gdb.COMMAND_SUPPORT,
9747 gdb.COMPLETE_NONE,
9748 False)
9749 return
9750
9751 def invoke(self, args, from_tty):
9752 self.dont_repeat()
9753 missing_commands = __gef__.missing_commands.keys()
9754 if not missing_commands:
9755 ok("No missing command")
9756 return
9757
9758 for missing_command in missing_commands:
9759 reason = __gef__.missing_commands[missing_command]
9760 warn("Command `{}` is missing, reason {} {}".format(missing_command, RIGHT_ARROW, reason))
9761 return
9762
9763
9764class GefSetCommand(gdb.Command):
9765 """Override GDB set commands with the context from GEF.
9766 """
9767 _cmdline_ = "gef set"
9768 _syntax_ = "{:s} [GDB_SET_ARGUMENTS]".format(_cmdline_)
9769
9770 def __init__(self, *args, **kwargs):
9771 super(GefSetCommand, self).__init__(GefSetCommand._cmdline_,
9772 gdb.COMMAND_SUPPORT,
9773 gdb.COMPLETE_SYMBOL,
9774 False)
9775 return
9776
9777 def invoke(self, args, from_tty):
9778 self.dont_repeat()
9779 args = args.split()
9780 cmd = ["set", args[0],]
9781 for p in args[1:]:
9782 if p.startswith("$_gef"):
9783 c = gdb.parse_and_eval(p)
9784 cmd.append(c.string())
9785 else:
9786 cmd.append(p)
9787
9788 gdb.execute(" ".join(cmd))
9789 return
9790
9791
9792class GefRunCommand(gdb.Command):
9793 """Override GDB run commands with the context from GEF.
9794 Simple wrapper for GDB run command to use arguments set from `gef set args`. """
9795 _cmdline_ = "gef run"
9796 _syntax_ = "{:s} [GDB_RUN_ARGUMENTS]".format(_cmdline_)
9797
9798 def __init__(self, *args, **kwargs):
9799 super(GefRunCommand, self).__init__(GefRunCommand._cmdline_,
9800 gdb.COMMAND_SUPPORT,
9801 gdb.COMPLETE_FILENAME,
9802 False)
9803 return
9804
9805 def invoke(self, args, from_tty):
9806 self.dont_repeat()
9807 if is_alive():
9808 gdb.execute("continue")
9809 return
9810
9811 argv = args.split()
9812 gdb.execute("gef set args {:s}".format(" ".join(argv)))
9813 gdb.execute("run")
9814 return
9815
9816
9817class GefAlias(gdb.Command):
9818 """Simple aliasing wrapper because GDB doesn't do what it should.
9819 """
9820 def __init__(self, alias, command, completer_class=gdb.COMPLETE_NONE, command_class=gdb.COMMAND_NONE):
9821 p = command.split()
9822 if not p:
9823 return
9824
9825 if list(filter(lambda x: x._alias == alias, __aliases__)):
9826 return
9827
9828 self._command = command
9829 self._alias = alias
9830 c = command.split()[0]
9831 r = self.lookup_command(c)
9832 self.__doc__ = "Alias for '{}'".format(Color.greenify(command))
9833 if r is not None:
9834 _instance = r[2]
9835 self.__doc__ += ": {}".format(_instance.__doc__)
9836
9837 if hasattr(_instance, "complete"):
9838 self.complete = _instance.complete
9839
9840 super(GefAlias, self).__init__(alias, command_class, completer_class=completer_class)
9841 __aliases__.append(self)
9842 return
9843
9844 def invoke(self, args, from_tty):
9845 gdb.execute("{} {}".format(self._command, args), from_tty=from_tty)
9846 return
9847
9848 def lookup_command(self, cmd):
9849 global __gef__
9850 for _name, _class, _instance in __gef__.loaded_commands:
9851 if cmd == _name:
9852 return _name, _class, _instance
9853
9854 return None
9855
9856
9857class GefAliases(gdb.Command):
9858 """List all custom aliases."""
9859 def __init__(self):
9860 super(GefAliases, self).__init__("aliases", gdb.COMMAND_OBSCURE, gdb.COMPLETE_NONE)
9861 return
9862
9863 def invoke(self, args, from_tty):
9864 self.dont_repeat()
9865 ok("Aliases defined:")
9866 for _alias in __aliases__:
9867 gef_print("{:30s} {} {}".format(_alias._alias, RIGHT_ARROW, _alias._command))
9868 return
9869
9870
9871class GefTmuxSetup(gdb.Command):
9872 """Setup a confortable tmux debugging environment."""
9873 def __init__(self):
9874 super(GefTmuxSetup, self).__init__("tmux-setup", gdb.COMMAND_NONE, gdb.COMPLETE_NONE)
9875 GefAlias("screen-setup", "tmux-setup")
9876 return
9877
9878 def invoke(self, args, from_tty):
9879 self.dont_repeat()
9880
9881 tmux = os.getenv("TMUX")
9882 if tmux:
9883 self.tmux_setup()
9884 return
9885
9886 screen = os.getenv("TERM")
9887 if screen is not None and screen == "screen":
9888 self.screen_setup()
9889 return
9890
9891 warn("Not in a tmux/screen session")
9892 return
9893
9894
9895 def tmux_setup(self):
9896 """Prepare the tmux environment by vertically splitting the current pane, and
9897 forcing the context to be redirected there."""
9898 tmux = which("tmux")
9899 ok("tmux session found, splitting window...")
9900 old_ptses = set(os.listdir("/dev/pts"))
9901 gdb.execute("! {} split-window -h 'clear ; cat'".format(tmux))
9902 gdb.execute("! {} select-pane -L".format(tmux))
9903 new_ptses = set(os.listdir("/dev/pts"))
9904 pty = list(new_ptses - old_ptses)[0]
9905 pty = "/dev/pts/{}".format(pty)
9906 ok("Setting `context.redirect` to '{}'...".format(pty))
9907 gdb.execute("gef config context.redirect {}".format(pty))
9908 ok("Done!")
9909 return
9910
9911
9912 def screen_setup(self):
9913 """Hackish equivalent of the tmux_setup() function for screen."""
9914 screen = which("screen")
9915 sty = os.getenv("STY")
9916 ok("screen session found, splitting window...")
9917 fd_script, script_path = tempfile.mkstemp()
9918 fd_tty, tty_path = tempfile.mkstemp()
9919 os.close(fd_tty)
9920
9921 with os.fdopen(fd_script, "w") as f:
9922 f.write("startup_message off\n")
9923 f.write("split -v\n")
9924 f.write("focus right\n")
9925 f.write("screen /bin/bash -c 'tty > {}; clear; cat'\n".format(tty_path))
9926 f.write("focus left\n")
9927
9928 gdb.execute("""! {} -r {} -m -d -X source {}""".format(screen, sty, script_path))
9929 # artificial delay to make sure `tty_path` is populated
9930 time.sleep(0.25)
9931 with open(tty_path, "r") as f:
9932 pty = f.read().strip()
9933 ok("Setting `context.redirect` to '{}'...".format(pty))
9934 gdb.execute("gef config context.redirect {}".format(pty))
9935 ok("Done!")
9936 os.unlink(script_path)
9937 os.unlink(tty_path)
9938 return
9939
9940
9941def __gef_prompt__(current_prompt):
9942 """GEF custom prompt function."""
9943 if get_gef_setting("gef.readline_compat") is True: return GEF_PROMPT
9944 if get_gef_setting("gef.disable_color") is True: return GEF_PROMPT
9945 if is_alive(): return GEF_PROMPT_ON
9946 return GEF_PROMPT_OFF
9947
9948
9949if __name__ == "__main__":
9950
9951 if PYTHON_MAJOR == 2:
9952 warn("GEF will stop support for GDB+Python2 when it reaches EOL on 2020/01/01.")
9953 warn("See https://github.com/hugsy/gef/projects/4 for updates.")
9954
9955 if GDB_VERSION < GDB_MIN_VERSION:
9956 err("You're using an old version of GDB. GEF will not work correctly. "
9957 "Consider updating to GDB {} or higher.".format(".".join(map(str, GDB_MIN_VERSION))))
9958
9959 else:
9960 try:
9961 pyenv = which("pyenv")
9962 PYENV_ROOT = gef_pystring(subprocess.check_output([pyenv, "root"]).strip())
9963 PYENV_VERSION = gef_pystring(subprocess.check_output([pyenv, "version-name"]).strip())
9964 site_packages_dir = os.path.join(PYENV_ROOT, "versions", PYENV_VERSION, "lib",
9965 "python{}".format(PYENV_VERSION[:3]), "site-packages")
9966 site.addsitedir(site_packages_dir)
9967 except FileNotFoundError:
9968 pass
9969
9970 # setup prompt
9971 gdb.prompt_hook = __gef_prompt__
9972
9973 # setup config
9974 gdb.execute("set confirm off")
9975 gdb.execute("set verbose off")
9976 gdb.execute("set pagination off")
9977 gdb.execute("set step-mode on")
9978 gdb.execute("set print elements 0")
9979
9980 # gdb history
9981 gdb.execute("set history save on")
9982 gdb.execute("set history filename ~/.gdb_history")
9983
9984 # gdb input and output bases
9985 gdb.execute("set output-radix 0x10")
9986
9987 # pretty print
9988 gdb.execute("set print pretty on")
9989
9990 try:
9991 # this will raise a gdb.error unless we're on x86
9992 gdb.execute("set disassembly-flavor intel")
9993 except gdb.error:
9994 # we can safely ignore this
9995 pass
9996
9997 # SIGALRM will simply display a message, but gdb won't forward the signal to the process
9998 gdb.execute("handle SIGALRM print nopass")
9999
10000 # saving GDB indexes in GEF tempdir
10001 gef_makedirs(GEF_TEMP_DIR)
10002 gdb.execute("save gdb-index {}".format(GEF_TEMP_DIR))
10003
10004 # load GEF
10005 __gef__ = GefCommand()
10006 __gef__.setup()
10007
10008 # gdb events configuration
10009 gef_on_continue_hook(continue_handler)
10010 gef_on_stop_hook(hook_stop_handler)
10011 gef_on_new_hook(new_objfile_handler)
10012 gef_on_exit_hook(exit_handler)
10013
10014 if gdb.current_progspace().filename is not None:
10015 # if here, we are sourcing gef from a gdb session already attached
10016 # we must force a call to the new_objfile handler (see issue #278)
10017 new_objfile_handler(None)
10018
10019 GefAliases()
10020 GefTmuxSetup()