diff --git a/gdbinit.py b/gdbinit.py index 1ccb5081fd8..cbe1368f13a 100644 --- a/gdbinit.py +++ b/gdbinit.py @@ -9,4 +9,3 @@ import gef -gef.main() \ No newline at end of file diff --git a/gef/__init__.py b/gef/__init__.py index ac8b48d2a7e..fea5aa5d3d6 100644 --- a/gef/__init__.py +++ b/gef/__init__.py @@ -1,3 +1,4 @@ +import gdb import gef.arch import gef.vmmap import gef.dt @@ -6,6 +7,34 @@ import gef.proc import gef.regs import gef.stack +import gef.commands +import gef.commands.hexdump +import gef.commands.context +import gef.commands.telescope +import gef.commands.vmmap +import gef.commands.dt -def main(): - pass \ No newline at end of file + +pre_commands = """ +set confirm off +set verbose off +set output-radix 0x10 +set prompt geef> +set height 0 +set history expansion on +set history save on +set disassembly-flavor intel +set follow-fork-mode child +set backtrace past-main on +set step-mode on +set print pretty on +set width 0 +set print elements 15 +set input-radix 16 +handle SIGALRM print nopass +handle SIGSEGV stop print nopass +""".strip() + +for line in pre_commands.splitlines(): + if line: + gdb.execute(line) diff --git a/gef/arch.py b/gef/arch.py index b6db3b80cb2..6c9526cb5d5 100644 --- a/gef/arch.py +++ b/gef/arch.py @@ -1,3 +1,5 @@ +import struct +import sys import gdb import gef.memoize import gef.events @@ -5,10 +7,33 @@ current = None ptrmask = 0xfffffffff +endian = 'little' +ptrsize = gef.types.ptrsize +fmt = '=i' @gef.events.stop def update(): - global current - global ptrmask - current = gdb.selected_frame().architecture().name() - ptrmask = (1 << 8*gef.types.ptrsize)-1 \ No newline at end of file + m = sys.modules[__name__] + + m.current = gdb.selected_frame().architecture().name() + m.ptrsize = gef.types.ptrsize + m.ptrmask = (1 << 8*gef.types.ptrsize)-1 + + if 'little' in gdb.execute('show endian', to_string=True): + m.endian = 'little' + else: + m.endian = 'big' + + m.fmt = { + (4, 'little'): 'I', + (8, 'little'): 'Q', + }.get((m.ptrsize, m.endian)) + + +def pack(integer): + return struct.pack(fmt, integer & ptrmask) + +def unpack(data): + return struct.unpack(fmt, data)[0] \ No newline at end of file diff --git a/gef/auxv.py b/gef/auxv.py index 33aafbd2d27..b8eae33af46 100644 --- a/gef/auxv.py +++ b/gef/auxv.py @@ -128,6 +128,8 @@ def use_info_auxv(): def walk_stack(): sp = gef.regs.sp + print("BAD SP") + if not sp: return None @@ -167,6 +169,8 @@ def walk_stack(): # Scan them into our structure auxv = AUXV() + print("STARTING AT %s" % p) + print("STOPPING AT %s" % AT_NULL) while p < AT_NULL: const, value = p.dereference(), (p+1).dereference() const = int(const) diff --git a/gef/chain.py b/gef/chain.py index 27ef760cbf0..c3e799176fa 100644 --- a/gef/chain.py +++ b/gef/chain.py @@ -1,6 +1,9 @@ import gdb -import gef.types +import gef.color +import gef.enhance import gef.memory +import gef.types +import gef.vmmap def get(address, limit=5): @@ -10,21 +13,24 @@ def get(address, limit=5): Returns: A list containing ``address``, followed by up to ``limit`` valid pointers. """ - result = [int(address)] + result = [] for i in range(limit): + result.append(address) try: - # Convert the current address to a void** - address = gef.memory.poi(gef.types.ppvoid, address) - - # Ensure that it's a valid pointer by dereferencing it - # *AND* attempting to get the resulting value. - # - # GDB will let you .dereference() anything, the int() throws - # the gdb.MemoryError. - int(address.dereference()) - - # Save it off - result.append(int(address)) + address = int(gef.memory.poi(gef.types.ppvoid, address)) except gdb.MemoryError: break + return result + + +def format(value): + chain = get(value) + + # Enhance the last entry + end = [gef.enhance.enhance(chain[-1])] + + # Colorize the rest + rest = list(map(gef.color.get, chain[:-1])) + + return ' --> '.join(rest + end) \ No newline at end of file diff --git a/gef/color.py b/gef/color.py index c553fd0ec4a..1b5a472f9c9 100644 --- a/gef/color.py +++ b/gef/color.py @@ -9,15 +9,23 @@ BLUE = "\x1b[34m" PURPLE = "\x1b[35m" CYAN = "\x1b[36m" -GREY = GRAY = "\x1b[37m" +GREY = GRAY = "\x1b[90m" BOLD = "\x1b[1m" UNDERLINE = "\x1b[4m" -STACK = BLUE -HEAP = BLUE + BOLD +STACK = YELLOW +HEAP = BLUE CODE = RED -RWX = RED + BOLD -DATA = YELLOW +RWX = RED + BOLD + UNDERLINE +DATA = PURPLE + +def normal(x): return NORMAL + x +def bold(x): return BOLD + x + NORMAL +def red(x): return RED + x + NORMAL +def blue(x): return BLUE + x + NORMAL +def gray(x): return GRAY + x + NORMAL +def green(x): return GREEN + x + NORMAL +def yellow(x): return YELLOW + x + NORMAL def get(address, text = None): """ @@ -33,14 +41,26 @@ def get(address, text = None): if page is None: color = NORMAL elif '[stack' in page.objfile: color = STACK elif '[heap' in page.objfile: color = HEAP - elif page.rwx: color = RWX elif page.execute: color = CODE elif page.rw: color = DATA else: color = NORMAL + if page.rwx: + color = color + UNDERLINE + if text is None and isinstance(address, int) and address > 255: text = hex(address) if text is None: text = address return "%s%s%s" % (color, text, NORMAL) + +def legend(): + return 'LEGEND: ' + ' | '.join(( + STACK + 'STACK' + NORMAL, + HEAP + 'HEAP' + NORMAL, + CODE + 'CODE' + NORMAL, + DATA + 'DATA' + NORMAL, + UNDERLINE + 'RWX' + NORMAL, + 'RODATA' + )) diff --git a/gef/commands/__init__.py b/gef/commands/__init__.py index da5e0fc847e..07e2074cc8a 100644 --- a/gef/commands/__init__.py +++ b/gef/commands/__init__.py @@ -1,2 +1,67 @@ +import traceback +import gdb -class Command(gdb.Command): \ No newline at end of file +import gef.regs +import gef.memory +import gef.hexdump +import gef.color +import gef.chain +import gef.enhance +import gef.symbol +import gef.ui +import gef.proc + +debug = True + +class ParsedCommand(gdb.Command): + def __init__(self, function): + super(ParsedCommand, self).__init__(function.__name__, gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION) + self.function = function + + def invoke(self, argument, from_tty): + argv = gdb.string_to_argv(argument) + + for i,arg in enumerate(argv): + try: + argv[i] = gdb.parse_and_eval(arg) + continue + except Exception: + pass + + try: + arg = gef.regs.fix(arg) + argv[i] = gdb.parse_and_eval(arg) + except Exception: + pass + + try: + self.function(*argv) + except TypeError: + if debug: print(traceback.format_exc()) + pass + + def __call__(self, *args): + self.function(*args) + +def OnlyWhenRunning(func): + def wrapper(*a): + func.__doc__ + if not gef.proc.alive: + pass + else: + func(*a) + wrapper.__name__ = func.__name__ + wrapper.__module__ = func.__module__ + return wrapper + + +@ParsedCommand +@OnlyWhenRunning +def searchmem(searchfor): + + if isinstance(searchfor, gdb.Value): + try: + searchfor = gef.memory.read(searchfor.address, searchfor.sizeof) + except: + searchfor = 0 + print(searchfor) \ No newline at end of file diff --git a/gef/commands/context.py b/gef/commands/context.py new file mode 100644 index 00000000000..1565c858703 --- /dev/null +++ b/gef/commands/context.py @@ -0,0 +1,86 @@ +import gdb +import gef.commands +import gef.color +import gef.vmmap +import gef.symbol +import gef.regs +import gef.ui +import gef.disasm +import gef.chain +import gef.commands.telescope +import gef.events + + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning +@gef.events.stop +def context(*args): + if len(args) == 0: + args = ['reg','code','stack','backtrace'] + + args = [a[0] for a in args] + + print(gef.color.legend()) + if 'r' in args: context_regs() + if 'c' in args: context_code() + if 's' in args: context_stack() + if 'b' in args: context_backtrace() + +def context_regs(): + print(gef.color.blue(gef.ui.banner("registers"))) + for reg in gef.regs.gpr + (gef.regs.frame, gef.regs.stack, '$pc'): + if reg is None: + continue + + value = gef.regs[reg] + + # Make the register stand out + regname = gef.color.bold(reg.ljust(4).upper()) + + print("%s %s" % (regname, gef.chain.format(value))) + +def context_code(): + print(gef.color.blue(gef.ui.banner("code"))) + pc = gef.regs.pc + instructions = gef.disasm.near(gef.regs.pc, 5) + + # In case $pc is in a new map we don't know about, + # this will trigger an exploratory search. + gef.vmmap.find(pc) + + # Ensure screen data is always at the same spot + for i in range(11 - len(instructions)): + print() + + # Find all of the symbols for the addresses + symbols = [] + for i in instructions: + symbol = gef.symbol.get(i.address) + if symbol: + symbol = '<%s> ' % symbol + symbols.append(symbol) + + # Find the longest symbol name so we can adjust + longest_sym = max(map(len, symbols)) + + # Pad them all out + for i,s in enumerate(symbols): + symbols[i] = s.ljust(longest_sym) + + # Print out each instruction + for i,s in zip(instructions, symbols): + asm = gef.disasm.color(i) + prefix = ' =>' if i.address == pc else ' ' + print(prefix, s + hex(i.address), asm) + +def context_stack(): + print(gef.color.blue(gef.ui.banner("stack"))) + gef.commands.telescope.telescope(gef.regs.sp) + +def context_backtrace(): + print(gef.color.blue(gef.ui.banner("backtrace"))) + frame = gdb.selected_frame() + for i in range(0,10): + if frame: + print(gef.ui.addrsz(frame.pc()), frame.name() or '???') + frame = frame.older() \ No newline at end of file diff --git a/gef/commands/dt.py b/gef/commands/dt.py new file mode 100644 index 00000000000..04d53ab85df --- /dev/null +++ b/gef/commands/dt.py @@ -0,0 +1,10 @@ +import gdb +import gef.vmmap +import gef.commands +import gef.color +import gef.dt + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning +def dt(typename, address=None): + print(gef.dt.dt(typename, addr=address)) diff --git a/gef/commands/hexdump.py b/gef/commands/hexdump.py new file mode 100644 index 00000000000..9b934e87a52 --- /dev/null +++ b/gef/commands/hexdump.py @@ -0,0 +1,19 @@ +import gef.regs +import gef.commands +import gef.memory +import gef.hexdump + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning +def hexdump(address=None, count=64): + """Hexdumps some data""" + if address is None: + address = gef.regs.sp + + int(address) + + data = gef.memory.read(address, count) + + for line in gef.hexdump.hexdump(data, address=address): + print(line) + diff --git a/gef/commands/packing.py b/gef/commands/packing.py new file mode 100644 index 00000000000..aee68f58418 --- /dev/null +++ b/gef/commands/packing.py @@ -0,0 +1,4 @@ +import struct +import gdb + +def pack(data, size=None): diff --git a/gef/commands/start.py b/gef/commands/start.py new file mode 100644 index 00000000000..08fec537315 --- /dev/null +++ b/gef/commands/start.py @@ -0,0 +1,29 @@ +import gdb +import gef.commands + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning +def start(): + + entries = ["main"] + main_addr = peda.main_entry() + if main_addr: + entries += ["*0x%x" % main_addr] + entries += ["__libc_start_main@plt"] + entries += ["_start"] + entries += ["_init"] + + started = 0 + for e in entries: + out = peda.execute_redirect("tbreak %s" % e) + if out and "breakpoint" in out: + peda.execute("run %s" % ' '.join(arg)) + started = 1 + break + + if not started: # try ELF entry point or just "run" as the last resort + elf_entry = peda.elfentry() + if elf_entry: + out = peda.execute_redirect("tbreak *%s" % elf_entry) + + peda.execute("run") diff --git a/gef/commands/telescope.py b/gef/commands/telescope.py new file mode 100644 index 00000000000..e064b630cf2 --- /dev/null +++ b/gef/commands/telescope.py @@ -0,0 +1,44 @@ +import gef.memory +import gef.regs +import gef.types +import gef.commands +import gef.chain + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning +def telescope(address=None, count=8): + if address is None: + address = gef.regs.sp + + if address < 100: + count = address + address = gef.regs.sp + + address = int(address) + count = int(count) + + reg_values = {r:v for (r,v) in gef.regs.items()} + # address = gef.memory.poi(gef.types.ppvoid, address) + ptrsize = gef.types.ptrsize + + start = address + stop = address + (count*ptrsize) + step = ptrsize + + # Find all registers which show up in the trace + regs = {} + for i in range(start, stop, step): + regs[i] = [] + for reg, regval in reg_values.items(): + if i <= regval < i+ptrsize: + regs[i].append(reg) + regs[i] = ' '.join(regs[i]) + + # Find the longest set of register information + longest_regs = max(map(len, regs.values())) + 1 + + # Print everything out + for i,addr in enumerate(range(start, stop, step)): + print("%02i:%04i|" % (i, addr-start), + regs[addr].ljust(longest_regs), + gef.chain.format(addr)) diff --git a/gef/commands/vmmap.py b/gef/commands/vmmap.py new file mode 100644 index 00000000000..300d2ce36bd --- /dev/null +++ b/gef/commands/vmmap.py @@ -0,0 +1,24 @@ +import gdb +import gef.vmmap +import gef.commands +import gef.color + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning +def vmmap(map=None): + int_map = None + str_map = None + if isinstance(map, str): + str_map = map + elif isinstance(map, (int, gdb.Value)): + int_map = int(map) + + + for page in gef.vmmap.get(): + if str_map and str_map not in page.objfile: + continue + if int_map and int_map not in page: + continue + + print(gef.color.get(page.vaddr, text=str(page))) + print(gef.color.legend()) \ No newline at end of file diff --git a/gef/commands/windbg.py b/gef/commands/windbg.py new file mode 100644 index 00000000000..f97b00f89d9 --- /dev/null +++ b/gef/commands/windbg.py @@ -0,0 +1,5 @@ +import gdb +import gef.commands + +@gef.commands.ParsedCommand +@gef.commands.OnlyWhenRunning diff --git a/gef/disasm.py b/gef/disasm.py index d129dadf38d..50260a6a387 100644 --- a/gef/disasm.py +++ b/gef/disasm.py @@ -1,5 +1,6 @@ import gdb import collections +import gef.color Instruction = collections.namedtuple('Instruction', ['address', 'length', 'asm']) @@ -10,3 +11,67 @@ def get(address, instructions=1): for insn in raw: retval.append(Instruction(insn['addr'],insn['length'], insn['asm'])) return retval + +def near(address, instructions=1): + # Find out how far back we can go without having a page fault + distance = instructions * 8 + for start in range(address-distance, address): + if gef.memory.peek(start): + break + + # Disassemble more than we expect to need, move forward until we have + # enough instructions and we start on the correct spot + insns = [] + while start < address: + insns = get(start, instructions) + last = insns[-1] + + if last.address + last.length == address: + break + + start += 1 + + return insns[-instructions:] + get(address, instructions + 1) + + + +branches = set([ +# Unconditional x86 branches +'call', 'callq', +'jmp', +'ret', +# Conditional x86 branches +'ja', 'jna', +'jae', 'jnae', +'jb', 'jnb', +'jbe', 'jnbe', +'jc', 'jnc', +'je', 'jne', +'jg', 'jng', +'jge', 'jnge', +'jl', 'jnl', +'jle', 'jnle', +'jo', 'jno', +'jp', 'jnp', +'jpe', 'jpo', +'js', 'jns', +'jz', 'jnz', +# ARM branches +'b', 'bl', 'bx', 'blx', 'bxj', 'b.w', +'beq', 'beq.w', 'bne', 'bmi', 'bpl', 'blt', +'ble', 'bgt', 'bge', 'bxne', +# MIPS branches +'j', 'jal', 'jr' +# PowerPC has too many, don't care +# http://llvm.org/klaus/llvm/raw/e48e8c7a6069374daee4c9be1e17b8ed31527f5e/test/MC/PowerPC/ppc64-encoding-ext.s +# SPARC +'ba', 'bne', 'be', 'bg', 'ble', 'bge', 'bl', 'bgu', 'bleu', +'jmpl' +]) + +def color(ins): + asm = ins.asm + if asm.split()[0] in branches: + asm = gef.color.yellow(asm) + asm += '\n' + return asm \ No newline at end of file diff --git a/gef/dt.py b/gef/dt.py index d5d9caf27ce..940821e2bfa 100644 --- a/gef/dt.py +++ b/gef/dt.py @@ -1,11 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import gdb +import glob import os import re import subprocess import tempfile +import gef.memory +import gef.types + def get_type(v): t = v.type while not t.name: @@ -75,7 +79,7 @@ def dt(name='', addr=None, obj = None): # Lookup the type name specified by the user else: - t = gdb.lookup_type(name) + t = gef.types.load(name) # If it's not a struct (e.g. int or char*), bail if t.code not in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_TYPEDEF): @@ -91,17 +95,37 @@ def dt(name='', addr=None, obj = None): if obj: header = "%s @ %s" % (header, hex(int(obj.address))) rv.append(header) + if t.strip_typedefs().code != gdb.TYPE_CODE_STRUCT: + t = {name: obj or gdb.Value(0).cast(t)} + for name, field in t.items(): # Offset into the parent structure - o = field.bitpos/8 + o = getattr(field, 'bitpos', 0)/8 extra = str(field.type) - if obj: + ftype = field.type.strip_typedefs() + + if obj and obj.type.strip_typedefs().code == gdb.TYPE_CODE_STRUCT: v = obj[name] - if field.type.strip_typedefs().code == gdb.TYPE_CODE_INT: + + if ftype.code == gdb.TYPE_CODE_INT: v = hex(int(v)) + if ftype.code in (gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY) \ + and ftype.target() == gef.types.uchar: + data = gef.memory.read(v.address, ftype.sizeof) + v = ' '.join('%02x' % b for b in data) + extra = v + # Adjust trailing lines in 'extra' to line up + # This is necessary when there are nested structures. + # Ideally we'd expand recursively if the type is complex. + extra_lines = [] + for i, line in enumerate(str(extra).splitlines()): + if i == 0: extra_lines.append(line) + else: extra_lines.append(35*' ' + line) + extra = '\n'.join(extra_lines) + line = " +0x%04x %-20s : %s" % (o, name, extra) rv.append(line) - return ('\n'.join(rv)) \ No newline at end of file + return ('\n'.join(rv)) diff --git a/gef/elf.py b/gef/elf.py index 1f87e71f520..f2b1af06ebc 100644 --- a/gef/elf.py +++ b/gef/elf.py @@ -258,8 +258,7 @@ def map(pointer, objfile=''): for page in pages: page.objfile = objfile - pages.sort() - return pages + return tuple(sorted(pages)) @gef.events.stop def update_main_exe(): diff --git a/gef/enhance.py b/gef/enhance.py index 61887c070d6..46001a8cb63 100644 --- a/gef/enhance.py +++ b/gef/enhance.py @@ -1,11 +1,14 @@ import gdb +import string import gef.symbol import gef.memory import gef.color import gef.types -import gef.string +import gef.strings import gef.disasm import gef.memoize +import gef.arch +import string @gef.memoize.reset_on_stop def enhance(value): @@ -17,31 +20,51 @@ def enhance(value): # If it's not in a page we know about, try to dereference # it anyway just to test. can_read = True - if not page and None == gef.memory.poke(value): + if not page and None == gef.memory.peek(value): can_read = False if not can_read: - return hex(int(value)) + retval = hex(int(value)) - # It's mapped memory, or we can at least read it. - # Try to find out if it's a string. - data = None - if page and page.execute: - data = gef.disasm.get(value, 1)[0].asm + # Try to unpack the value as a string + packed = gef.arch.pack(int(value)) + if all(c in string.printable.encode('utf-8') for c in packed): + retval = '%s (%r)' % (retval, packed.decode()) - if data is None: - data = gef.string.get(value) - if data: - data = repr(data) + return retval - if data is None and isinstance(data, int): - data = hex(data) + else: + # It's mapped memory, or we can at least read it. + # Try to find out if it's a string. + data = None + if page and page.execute: + data = gef.disasm.get(value, 1)[0].asm + + # However, if it contains bad instructions, bail + if '.byte' in data or '.long' in data: + data = None + + if data is None: + data = gef.strings.get(value) or None + if data: + data = repr(data) + + if data is None: + data = gef.memory.poi(gef.types.pvoid, value) + + # Try to unpack the value as a string + try: + packed = gef.arch.pack(int(data)) + if all(c in string.printable.encode('utf-8') for c in packed): + data = repr(packed.decode()) + except: + data = str(data) colored = gef.color.get(value) - if data and name: return "%s <%s: %s>" % (colored, name, data) - elif name: return "%s <%s>" % (colored, name) - elif data: return "%s <%s>" % (colored, data) + if data and name: return "%s (%s: %s)" % (colored, name, data) + elif name: return "%s (%s)" % (colored, name) + elif data: return "%s (%s)" % (colored, data) return colored diff --git a/gef/events.py b/gef/events.py index 8aa90ef4167..9be966d4741 100644 --- a/gef/events.py +++ b/gef/events.py @@ -1,6 +1,7 @@ import gdb +import traceback -debug = True +debug = False def connect(func, event_handler): def caller(*a): @@ -9,7 +10,7 @@ def caller(*a): try: func() except Exception as e: - print("Exception occurred", e) + if debug: print(traceback.format_exc()) raise e caller.name = func.__name__ event_handler.connect(caller) diff --git a/gef/hexdump.py b/gef/hexdump.py new file mode 100644 index 00000000000..9b7404ab329 --- /dev/null +++ b/gef/hexdump.py @@ -0,0 +1,80 @@ +import copy +import string +import gef.color + + +def groupby(array, count, fill=None): + array = copy.copy(array) + while fill and len(array) % count: + array.append(fill) + for i in range(0, len(array), count): + yield array[i:i+count] + +# +# We want to colorize the hex characters +# +color_scheme = {i:gef.color.normal("%02x" % i) for i in range(256)} + +for c in (string.ascii_letters + string.digits + string.punctuation).encode('utf-8'): + color_scheme[c] = gef.color.bold("%02x" % c) + +for c in bytearray(b'\x00\xff'): + color_scheme[c] = gef.color.red("%02x" % c) + +color_scheme[-1] = ' ' + +# +# Only print out printable values on the righ hand side +# +printable = {i:'.' for i in range(256)} +for c in (string.ascii_letters + string.digits + string.punctuation).encode('utf-8'): + printable[c] = chr(c) + +printable[-1] = ' ' + +def hexdump(data, address = 0, width = 16, skip = True): + data = list(bytearray(data)) + base = address + last_line = None + skipping = False + for i, line in enumerate(groupby(data, width, -1)): + if skip and line == last_line: + if not skipping: + skipping = True + yield '*' + continue + else: + skipping = False + last_line = line + + hexline = [] + + if address: + hexline.append("+%04x " % (i*width)) + + hexline.append("%#08x " % (base + (i*width))) + + for group in groupby(line, 4): + for char in group: + hexline.append(color_scheme[char]) + hexline.append(' ') + hexline.append(' ') + + hexline.append('|') + for group in groupby(line, 4): + for char in group: + hexline.append(printable[char]) + hexline.append('|') + + + yield(''.join(hexline)) + + hexline = [] + + if address: + hexline.append("+%04x " % len(data)) + + hexline.append("%#08x " % (base + len(data))) + + yield ''.join(hexline) + diff --git a/gef/memoize.py b/gef/memoize.py index 74f564d6ee9..5782cb154f1 100644 --- a/gef/memoize.py +++ b/gef/memoize.py @@ -1,3 +1,4 @@ +import copy import collections import gdb import functools @@ -16,6 +17,10 @@ def __call__(self, *args): value = self.func(*args) self.cache[args] = value + + if isinstance(value, list): + print("Shouldnt cache mutable types! %r" % self.func.__name__) + return value def __repr__(self): diff --git a/gef/memory.py b/gef/memory.py index 2dced54c8b3..957955c1e89 100644 --- a/gef/memory.py +++ b/gef/memory.py @@ -5,6 +5,7 @@ import gef.types PAGE_SIZE = 0x1000 +MMAP_MIN_ADDR = 0x10000 def read(addr, count): result = gdb.selected_inferior().read_memory(addr, count) @@ -17,11 +18,21 @@ def read(addr, count): def readtype(gdb_type, addr): return int(gdb.Value(addr).cast(gdb_type.pointer()).dereference()) +def write(addr, data): + gdb.selected_inferior().write_memory(addr, data) + def peek(address): - try: return read(address, 1) + try: return read(address, 1) except: pass return None +def poke(address): + c = peek(address) + if c is None: return False + try: write(address, c) + except: return False + return True + def byte(addr): return readtype(gef.types.uchar, addr) def uchar(addr): return readtype(gef.types.uchar, addr) def ushort(addr): return readtype(gef.types.ushort, addr) @@ -84,8 +95,8 @@ class Page(object): memsz = 0 #: Size of the address space, in bytes flags = 0 #: Flags set by the ELF file, see PF_X, PF_R, PF_W offset = 0 #: Offset into the original ELF file that the data is loaded from - objfile = None #: Path to the ELF on disk - def __init__(self, start, size, flags, offset, objfile=None): + objfile = '' #: Path to the ELF on disk + def __init__(self, start, size, flags, offset, objfile=''): self.vaddr = start self.memsz = size self.flags = flags @@ -114,9 +125,9 @@ def permstr(self): 'x' if flags & 1 else '-', 'p']) def __str__(self): - width = 2*gef.types.ptrsize - fmt_string = "%0x-%0x %s %8x %-6x %s" - # fmt_string = fmt_string.format(width, width) + width = 2 + 2*gef.types.ptrsize + fmt_string = "%#{}x %#{}x %s %8x %-6x %s" + fmt_string = fmt_string.format(width, width) return fmt_string % (self.vaddr, self.vaddr+self.memsz, self.permstr, @@ -126,7 +137,7 @@ def __str__(self): def __repr__(self): return "%s(%r)" % (self.__class__.__name__, self.__str__()) def __contains__(self, a): - return self.vaddr-1 < a < (self.vaddr + self.memsz) + return self.vaddr <= a < (self.vaddr + self.memsz) def __eq__(self, other): return self.vaddr == getattr(other, 'vaddr', other) def __lt__(self, other): diff --git a/gef/proc.py b/gef/proc.py index 20b618e92d1..6f2e4c7f226 100644 --- a/gef/proc.py +++ b/gef/proc.py @@ -6,7 +6,6 @@ class module(ModuleType): @property - @gef.memoize.reset_on_exit def pid(self): i = gdb.selected_inferior() if i is not None: diff --git a/gef/regs.py b/gef/regs.py index d9efa52a59a..6867deeb9b7 100644 --- a/gef/regs.py +++ b/gef/regs.py @@ -1,11 +1,12 @@ import gdb import sys +import re from types import ModuleType import gef.memoize import gef.arch class RegisterSet(object): - def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): + def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc, args): self.pc = pc self.stack = stack self.frame = frame @@ -13,6 +14,7 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): self.flags = flags self.gpr = gpr self.misc = misc + self.args = args arm = RegisterSet('pc', 'sp', @@ -20,7 +22,8 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): ('lr',), ('cpsr',), ('r0','r1','r2','r3','r4','r5','r6','r7','r8','r9','r10','r11','r12'), - None) + None, + ('r0','r1','r2','r3')) amd64 = RegisterSet('rip', 'rsp', @@ -30,7 +33,8 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): ('rax','rbx','rcx','rdx','rdi','rsi', 'r8', 'r9', 'r10','r11','r12', 'r13','r14','r15'), - ('cs','ss','ds','es','fs','gs')) + ('cs','ss','ds','es','fs','gs'), + ('rdi','rsi','rdx','rcx','r8','r9')) i386 = RegisterSet('eip', 'esp', @@ -38,7 +42,14 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): None, ('eflags',), ('eax','ebx','ecx','edx','edi','esi'), - ('cs','ss','ds','es','fs','gs')) + ('cs','ss','ds','es','fs','gs'), + ('*((void**)$sp+0)', + '*((void**)$sp+1)', + '*((void**)$sp+2)', + '*((void**)$sp+3)', + '*((void**)$sp+4)', + '*((void**)$sp+5)', + '*((void**)$sp+6)',)) # http://math-atlas.sourceforge.net/devel/assembly/elfspec_ppc.pdf @@ -57,7 +68,8 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): ('lr','r0'), ('msr','xer'), tuple('r%i' % i for i in range(3,32)), - ('cr','lr','trap','r2')) + ('cr','lr','trap','r2'), + tuple()) # http://people.cs.clemson.edu/~mark/sparc/sparc_arch_desc.txt # http://people.cs.clemson.edu/~mark/subroutines/sparc.html @@ -95,7 +107,8 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): ('o7',), ('psr',), sparc_gp, - None) + None, + ('i0','i1','i2','i3','i4','i5')) # http://logos.cs.uic.edu/366/notes/mips%20quick%20tutorial.htm @@ -112,12 +125,13 @@ def __init__(self, pc, stack, frame, retaddr, flags, gpr, misc): # r30 => frame pointer # r31 => return address mips = RegisterSet('pc', - 'r29', - 'r30', - ('r31',), + 'sp', + 'fp', + ('ra',), + None, + tuple('r%i' % i for i in range(1,26)), None, - ('r%i' for i in range(1,26)), - None) + ('a0','a1','a2','a3')) arch_to_regs = { 'i386': i386, @@ -137,7 +151,7 @@ def __getattr__(self, attr): value = int(gdb.parse_and_eval('$' + attr.lstrip('$'))) return value & gef.arch.ptrmask except gdb.error: - return 0 + return None def __getitem__(self, item): return getattr(self, item) @@ -152,7 +166,49 @@ def frame(self): @property def retaddr(self): - return arch_to_regs[gef.arch.current].retaddr + return arch_to_regs[gef.arch.current].retaddr + + @property + def stack(self): + return arch_to_regs[gef.arch.current].stack + + @property + def all(self): + regs = arch_to_regs[gef.arch.current] + retval = [] + for regset in (regs.pc, regs.stack, regs.frame, regs.retaddr, regs.flags, regs.gpr, regs.misc): + if regset is None: + continue + elif isinstance(regset, (list, tuple)): + retval.extend(regset) + else: + retval.append(regset) + return retval + + def fix(self, expression): + for regname in set(self.all + ['sp','pc']): + expression = re.sub(r'\$?\b%s\b' % regname, r'$'+regname, expression) + return expression + + + def items(self): + for regname in self.all: + yield regname, self[regname] + + @property + def arguments(self): + argnames = arch_to_regs[gef.arch.current].args + retval = [] + for arg in argnames: + val = self[arg] + if val is None: + try: val = gdb.parse_and_eval(arg) + except: val = '???' + retval.append(val) + return retval + + arch_to_regs = arch_to_regs + # To prevent garbage collection diff --git a/gef/strings.py b/gef/strings.py index f5caafd3bee..f2e6b362277 100644 --- a/gef/strings.py +++ b/gef/strings.py @@ -1,9 +1,17 @@ import gdb +import string import gef.types def get(address): try: - return gdb.Value(value).cast(gef.types.pchar).string() + sz = gdb.Value(address).cast(gef.types.pchar).string() except Exception as e: - print(e) - return None \ No newline at end of file + return None + + if not all(s in string.printable for s in sz): + sz + + if len(sz) < 15: + return sz + + return sz[:15] + '...' \ No newline at end of file diff --git a/gef/symbol.py b/gef/symbol.py index 2957414a92c..f4801399f52 100644 --- a/gef/symbol.py +++ b/gef/symbol.py @@ -1,11 +1,15 @@ import gdb import gef.memoize - +import gef.memory @gef.memoize.reset_on_objfile def get(address): """ Retrieve the textual name for a symbol """ + # Fast path + if address < gef.memory.MMAP_MIN_ADDR: + return '' + # This sucks, but there's not a GDB API for this. result = gdb.execute('info symbol %#x' % int(address), to_string=True, from_tty=False) diff --git a/gef/types.py b/gef/types.py index 3ac0275be7a..24f857893b8 100644 --- a/gef/types.py +++ b/gef/types.py @@ -1,11 +1,16 @@ +import os import sys import gdb +import glob +import tempfile +import subprocess import gef.events import gef.memoize module = sys.modules[__name__] + @gef.events.new_objfile @gef.memoize.reset_on_exit def update(): @@ -31,8 +36,78 @@ def update(): module.ptrsize = pvoid.sizeof + if pvoid.sizeof == 4: module.ptrdiff = uint32 + if pvoid.sizeof == 8: module.ptrdiff = uint64 + + # Call it once so we load all of the types update() # Reset the cache so that the first load isn't cached. -update.clear() \ No newline at end of file +update.clear() + + +tempdir = tempfile.gettempdir() + '/gef' +if not os.path.exists(tempdir): + os.mkdir(tempdir) + +# Trial and error until things work +blacklist = ['regexp.h', 'xf86drm.h', 'libxl_json.h', 'xf86drmMode.h', +'caca0.h', 'xenguest.h', '_libxl_types_json.h', 'term_entry.h', 'slcurses.h', +'pcreposix.h', 'sudo_plugin.h', 'tic.h', 'sys/elf.h', 'sys/vm86.h', +'xenctrlosdep.h', 'xenctrl.h', 'cursesf.h', 'cursesm.h', 'gdbm.h', 'dbm.h', +'gcrypt-module.h', 'term.h'] + +def load(name): + try: + return gdb.lookup_type(name) + except gdb.error: + pass + + s, _ = gdb.lookup_symbol + + # Try to find an architecture-specific include path + arch = gef.arch.current.split(':')[0] + + include_dir = glob.glob('/usr/%s*/include' % arch) + + if include_dir: + include_dir = include_dir[0] + else: + include_dir = '/usr/include' + + source = '#include \n' + + for subdir in ['', 'sys', 'netinet']: + dirname = os.path.join(include_dir, subdir) + for path in glob.glob(os.path.join(dirname, '*.h')): + if any(b in path for b in blacklist): + continue + + source += '#include "%s"\n' % path + + + source += ''' +#ifdef %(name)s +#pragma push("%(name)s") +#undef %(name)s +long long %(name)s = +#pragma pop("%(name)s") +%(name)s; +#else +%(name)s foo; +#endif +'''.format(**locals()) + + filename = '%s/%s_%s' % (tempdir, arch, name) + + if not os.path.exists(filename + '.o'): + with open(filename + '.cc', 'w+') as f: + f.write(source) + f.flush() + + subprocess.check_output('g++ -w -c -g %s.cc -o %s.o' % (filename, filename), shell=True) + + gdb.execute('add-symbol-file %s.o 0' % filename, from_tty=False, to_string=True) + + return gdb.lookup_type(name) \ No newline at end of file diff --git a/gef/ui.py b/gef/ui.py new file mode 100644 index 00000000000..96dd923da73 --- /dev/null +++ b/gef/ui.py @@ -0,0 +1,15 @@ +import struct, termios, fcntl, sys +import gef.arch + +def banner(title): + title = title.upper() + try: + _height, width = struct.unpack('hh', fcntl.ioctl(sys.stdin.fileno(), termios.TIOCGWINSZ, '1234')) + except: + width = 80 + width -= 2 + return ("[{:-^%ss}]" % width).format(title) + +def addrsz(address): + address = int(address) & gef.arch.ptrmask + return "%{}x".format(2*gef.arch.ptrsize) % address diff --git a/gef/vmmap.py b/gef/vmmap.py index c88608cdb0b..82503ae273b 100644 --- a/gef/vmmap.py +++ b/gef/vmmap.py @@ -18,27 +18,84 @@ import gef.compat import gef.memoize import gef.stack +import gef.events +import gef.regs + +# List of manually-explored pages which were discovered +# by analyzing the stack or register context. +explored_pages = [] -@gef.memoize.reset_on_stop def get(): - pages = proc_pid_maps() + pages = [] + pages.extend(proc_pid_maps()) if not pages: - pages = info_auxv() + pages.extend(info_auxv()) - if pages: pages += info_sharedlibrary() - else: pages = info_files() + if pages: pages.extend(info_sharedlibrary()) + else: pages.extend(info_files()) pages.extend(gef.stack.stacks.values()) + pages.extend(explored_pages) + pages.sort() return pages +@gef.memoize.reset_on_stop def find(address): + if address < gef.memory.MMAP_MIN_ADDR: + return None + for page in get(): if address in page: return page - return None + return explore(address) + +def explore(address_maybe): + """ + Given a potential address, check to see what permissions it has. + + Returns: + Page object + + Note: + Adds the Page object to a persistent list of pages which are + only reset when the process dies. This means pages which are + added this way will not be removed when unmapped. + + Also assumes the entire contiguous section has the same permission. + """ + address_maybe = gef.memory.page_align(address_maybe) + + flags = 4 if gef.memory.peek(address_maybe) else 0 + + if not flags: + return None + + flags |= 2 if gef.memory.poke(address_maybe) else 0 + flags |= 1 if not gef.stack.nx else 0 + + page = find_boundaries(address_maybe) + page.flags = flags + + explored_pages.append(page) + + return page + +# Automatically ensure that all registers are explored on each stop +@gef.events.stop +def explore_registers(): + for regname in gef.regs.all: + find(gef.regs[regname]) + + +@gef.events.exit +def clear_explored_pages(): + while explored_pages: + explored_pages.pop() + +@gef.memoize.reset_on_stop def proc_pid_maps(): """ Parse the contents of /proc/$PID/maps on the server. @@ -92,7 +149,7 @@ def proc_pid_maps(): maps, perm, offset, dev, inode_objfile = line.split(None, 4) try: inode, objfile = inode_objfile.split() - except: objfile = None + except: objfile = '' start, stop = maps.split('-') @@ -109,9 +166,10 @@ def proc_pid_maps(): page = gef.memory.Page(start, size, flags, offset, objfile) pages.append(page) - return sorted(pages) + return tuple(pages) +@gef.memoize.reset_on_objfile def info_sharedlibrary(): """ Parses the output of `info sharedlibrary`. @@ -156,6 +214,7 @@ def info_sharedlibrary(): return sorted(pages) +@gef.memoize.reset_on_objfile def info_files(): example_info_files_linues = """ @@ -177,7 +236,7 @@ def info_files(): """ seen_files = set() - pages = [] + pages = list() main_exe = '' for line in gdb.execute('info files', to_string=True).splitlines(): @@ -210,11 +269,11 @@ def info_files(): pages.extend(gef.elf.map(vaddr, objfile)) - return sorted(pages) - + return tuple(pages) +@gef.memoize.reset_on_exit def info_auxv(skip_exe=False): """ Extracts the name of the executable from the output of the command @@ -251,8 +310,8 @@ def find_boundaries(addr, name=''): Given a single address, find all contiguous pages which are mapped. """ - start = gef.memory.find_upper_boundary(addr) - end = gef.memory.find_lower_boundary(addr) + start = gef.memory.find_lower_boundary(addr) + end = gef.memory.find_upper_boundary(addr) return gef.memory.Page(start, end-start, 4, 0, name) aslr = False