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