diff --git a/pwndbg/__init__.py b/pwndbg/__init__.py index 7b9cbd0be..3f1fe2102 100644 --- a/pwndbg/__init__.py +++ b/pwndbg/__init__.py @@ -8,6 +8,7 @@ import pwndbg.elf import pwndbg.proc import pwndbg.regs import pwndbg.stack +import pwndbg.stdio import pwndbg.color import pwndbg.function import pwndbg.typeinfo @@ -26,6 +27,7 @@ import pwndbg.commands.ida import pwndbg.commands.reload import pwndbg.commands.rop import pwndbg.commands.shell +import pwndbg.commands.aslr __all__ = [ 'arch', @@ -99,4 +101,4 @@ def prompt_hook(*a): gdb.prompt_hook = prompt_hook msg = "Loaded %i commands. Type pwndbg for a list." % len(pwndbg.commands.Command.commands) -print(pwndbg.color.red(msg)) \ No newline at end of file +print(pwndbg.color.red(msg)) diff --git a/pwndbg/auxv.py b/pwndbg/auxv.py index b9601f08f..08becf36d 100644 --- a/pwndbg/auxv.py +++ b/pwndbg/auxv.py @@ -6,6 +6,7 @@ import pwndbg.events import pwndbg.info import pwndbg.memory import pwndbg.regs +import pwndbg.stack import pwndbg.typeinfo example_info_auxv_linux = """ @@ -138,7 +139,10 @@ def walk_stack(): if not auxv: # For whatever reason, sometimes the ARM AUXV under qemu-user is # not aligned properly. - return walk_stack2(1) + auxv = walk_stack2(1) + + if not auxv['AT_EXECFN']: + auxv['AT_EXECFN'] = get_execfn() return auxv @@ -215,3 +219,24 @@ def walk_stack2(offset=0): p += 2 return auxv + +def get_execfn(): + # QEMU does not put AT_EXECFN in the Auxiliary Vector + # on the stack. + # + # However, it does put it at the very top of the stack. + # + # 32c:1960| 0x7fffffffefe0 <-- '/home/user/pwndbg/ld....' + # 32d:1968| 0x7fffffffefe8 <-- 'er/pwndbg/ld.so' + # 32e:1970| 0x7fffffffeff0 <-- 0x6f732e646c2f67 /* 'g/ld.so' */ + # 32f:1978| 0x7fffffffeff8 <-- 0 + # 330:1980| 0x7ffffffff000 + addr = pwndbg.stack.find_upper_stack_boundary(pwndbg.regs.sp) + + while pwndbg.memory.byte(addr-1) == 0: + addr -= 1 + + while pwndbg.memory.byte(addr-1) != 0: + addr -= 1 + + return pwndbg.strings.get(addr, 1024) diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py index eb7604c2b..0a676a0f8 100644 --- a/pwndbg/commands/__init__.py +++ b/pwndbg/commands/__init__.py @@ -1,3 +1,4 @@ +import functools import traceback import gdb @@ -37,11 +38,11 @@ class Command(gdb.Command): commands = [] def __init__(self, function): - self.__doc__ = function.__doc__ super(Command, self).__init__(function.__name__, gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION) self.function = function Command.commands.append(self) + functools.update_wrapper(self, function) def split_args(self, argument): return gdb.string_to_argv(argument) diff --git a/pwndbg/commands/aslr.py b/pwndbg/commands/aslr.py new file mode 100644 index 000000000..deaf4e23e --- /dev/null +++ b/pwndbg/commands/aslr.py @@ -0,0 +1,13 @@ +import pwndbg.vmmap +import pwndbg.commands +import pwndbg.color + +@pwndbg.commands.OnlyWhenRunning +@pwndbg.commands.Command +def aslr(): + status = pwndbg.color.red('OFF') + if pwndbg.vmmap.aslr: + status = pwndbg.color.green('ON') + + print("ASLR is %s" % status) + diff --git a/pwndbg/elf.py b/pwndbg/elf.py index dfba2a31d..ba9e2b45b 100644 --- a/pwndbg/elf.py +++ b/pwndbg/elf.py @@ -130,11 +130,12 @@ def get_ehdr(pointer): # Do not search more than 4MB of memory for i in range(1024): + if data == b'\x7FELF': + break + base -= pwndbg.memory.PAGE_SIZE data = pwndbg.memory.read(base, 4) - if data == b'\x7FELF': - break else: print("ERROR: Could not find ELF base!") return None, None @@ -288,8 +289,3 @@ def map_inner(ei_class, ehdr, objfile): page.objfile = objfile return tuple(sorted(pages)) - -@pwndbg.events.stop -def update_main_exe(): - addr = int(exe().address) - map(addr) diff --git a/pwndbg/events.py b/pwndbg/events.py index 939b2686a..dae004b18 100644 --- a/pwndbg/events.py +++ b/pwndbg/events.py @@ -5,6 +5,7 @@ Enables callbacks into functions to be automatically invoked when various events occur to the debuggee (e.g. STOP on SIGINT) by using a decorator. """ +import functools import traceback import gdb import sys @@ -31,16 +32,16 @@ class StartEvent(object): def disconnect(self, function): if function in self.registered: self.registered.remove(function) - def on_new_objfile(self, o): + def on_new_objfile(self): if self.running or not gdb.selected_thread(): return self.running = True for function in self.registered: - function(o) + function() - def on_stop(self, e): + def on_stop(self): self.running = False gdb.events.start = StartEvent() @@ -65,19 +66,17 @@ def connect(func, event_handler, name=''): if debug: print("Connecting", func.__name__, event_handler) + @functools.wraps(func) def caller(*a): - func.__doc__ if debug: sys.stdout.write('%r %s.%s %r\n' % (name, func.__module__, func.__name__, a)) if pause: return try: func() except Exception as e: - if debug: print(traceback.format_exc()) + print(traceback.format_exc()) raise e registered[event_handler].append(caller) - caller.name = func.__name__ - caller.__name__ = func.__name__ event_handler.connect(caller) return func @@ -102,9 +101,9 @@ def on_reload(): registered[event] = [] @new_objfile -def _start_newobjfile(o=None): - gdb.events.start.on_new_objfile(o) +def _start_newobjfile(): + gdb.events.start.on_new_objfile() @stop -def _start_stop(o=None): - gdb.events.start.on_stop(o) +def _start_stop(): + gdb.events.start.on_stop() diff --git a/pwndbg/ida.py b/pwndbg/ida.py index 99948ce68..abbf9da84 100644 --- a/pwndbg/ida.py +++ b/pwndbg/ida.py @@ -5,6 +5,7 @@ Talks to an XMLRPC server running inside of an active IDA Pro instance, in order to query it about the database. Allows symbol resolution and interactive debugging. """ +import functools import socket from contextlib import closing @@ -46,7 +47,7 @@ setPort(8888) class withIDA(object): def __init__(self, fn): self.fn = fn - self.__name__ = fn.__name__ + functools.update_wrapper(self, fn) def __call__(self, *args, **kwargs): if _ida is not None: return self.fn(*args, **kwargs) @@ -55,14 +56,14 @@ class withIDA(object): class takes_address(object): def __init__(self, fn): self.fn = fn - self.__name__ = fn.__name__ + functools.update_wrapper(self, fn) def __call__(self, address, *args): return self.fn(l2r(address), *args) class returns_address(object): def __init__(self, fn): self.fn = fn - self.__name__ = fn.__name__ + functools.update_wrapper(self, fn) def __call__(self, *a, **kw): return r2l(self.fn(*a, **kw)) diff --git a/pwndbg/memoize.py b/pwndbg/memoize.py index 24e98259e..0011c7abe 100644 --- a/pwndbg/memoize.py +++ b/pwndbg/memoize.py @@ -18,6 +18,12 @@ import pwndbg.events debug = False class memoize(object): + def __init__(self, func): + self.func = func + self.cache = {} + self.caches.append(self) + functools.update_wrapper(self, func) + def __call__(self, *args): how = None @@ -61,11 +67,6 @@ class reset_on_stop(memoize): caches = [] kind = 'stop' - def __init__(self, func): - self.func = func - self.cache = {} - self.caches.append(self) - @staticmethod @pwndbg.events.stop def __reset(): @@ -76,13 +77,6 @@ class reset_on_exit(memoize): caches = [] kind = 'exit' - def __init__(self, func): - self.func = func - self.cache = {} - self.caches.append(self) - self.__name__ = func.__name__ - self.__module__ = func.__module__ - @staticmethod @pwndbg.events.exit def __reset(): @@ -93,13 +87,6 @@ class reset_on_objfile(memoize): caches = [] kind = 'objfile' - def __init__(self, func): - self.func = func - self.cache = {} - self.caches.append(self) - self.__name__ = func.__name__ - self.__module__ = func.__module__ - @staticmethod @pwndbg.events.new_objfile def __reset(): @@ -110,13 +97,6 @@ class reset_on_start(memoize): caches = [] kind = 'start' - def __init__(self, func): - self.func = func - self.cache = {} - self.caches.append(self) - self.__name__ = func.__name__ - self.__module__ = func.__module__ - @staticmethod @pwndbg.events.stop @pwndbg.events.start diff --git a/pwndbg/memory.py b/pwndbg/memory.py index 8c2d01b1f..3d0c0d5d3 100644 --- a/pwndbg/memory.py +++ b/pwndbg/memory.py @@ -11,9 +11,6 @@ PAGE_SIZE = 0x1000 MMAP_MIN_ADDR = 0x10000 def read(addr, count): - if count < 0: - import pdb - pdb.set_trace() result = gdb.selected_inferior().read_memory(addr, count) if pwndbg.compat.python3: @@ -77,6 +74,8 @@ def find_upper_boundary(addr, max_pages=1024): try: for i in range(max_pages): pwndbg.memory.read(addr, 1) + import sys + sys.stdout.write(hex(addr) + '\n') addr += pwndbg.memory.PAGE_SIZE except gdb.MemoryError: pass diff --git a/pwndbg/proc.py b/pwndbg/proc.py index d848d287f..07c49b4f6 100644 --- a/pwndbg/proc.py +++ b/pwndbg/proc.py @@ -5,6 +5,7 @@ Provides values which would be available from /proc which are not fulfilled by other modules. """ import sys +import functools from types import ModuleType import gdb @@ -28,13 +29,10 @@ class module(ModuleType): auxv = pwndbg.auxv.get() def OnlyWhenRunning(self, func): + @functools.wraps(func) def wrapper(*a, **kw): - func.__doc__ if self.alive: return func(*a, **kw) - wrapper.__name__ = func.__name__ - wrapper.__module__ = func.__module__ - wrapper.__doc__ = func.__doc__ return wrapper # To prevent garbage collection diff --git a/pwndbg/stack.py b/pwndbg/stack.py index a0db0d6cc..57787e185 100644 --- a/pwndbg/stack.py +++ b/pwndbg/stack.py @@ -27,11 +27,27 @@ def find(address): Returns a pwndbg.memory.Page object which corresponds to the currently-loaded stack. """ + if not stacks: + update() + for stack in stacks: if address in stack: return stack +def find_upper_stack_boundary(addr, max_pages=1024): + addr = pwndbg.memory.page_align(int(addr)) + try: + for i in range(max_pages): + data = pwndbg.memory.read(addr, 4) + if b'\x7fELF' == pwndbg.memory.read(addr, 4): + break + addr += pwndbg.memory.PAGE_SIZE + except gdb.MemoryError: + pass + return addr + @pwndbg.events.stop +@pwndbg.memoize.reset_on_stop def update(): """ For each running thread, updates the known address range @@ -49,7 +65,7 @@ def update(): page = stacks.get(thread.ptid, None) if page is None: start = pwndbg.memory.find_lower_boundary(sp) - stop = pwndbg.memory.find_upper_boundary(sp) + stop = find_upper_stack_boundary(sp) page = pwndbg.memory.Page(start, stop-start, 6 if not is_executable() else 7, 0, '[stack]') stacks[thread.ptid] = page continue @@ -57,7 +73,7 @@ def update(): page.objfile = '[stack]' # If we *DO* already know about this thread, just - # udpate the lower boundary. + # update the lower boundary. low = pwndbg.memory.find_lower_boundary(page.vaddr) if low != page.vaddr: page.memsz += (page.vaddr - low) diff --git a/pwndbg/stdio.py b/pwndbg/stdio.py new file mode 100644 index 000000000..26065c7ff --- /dev/null +++ b/pwndbg/stdio.py @@ -0,0 +1,18 @@ +""" +Provides functionality to circumvent GDB's hooks on sys.stdin and sys.stdout +which prevent output from appearing on-screen inside of certain event handlers. +""" +import gdb +import io +import sys + +debug = True + +def get(fd, mode): + file = io.open(1, mode=mode, buffering=0, closefd=False) + return io.TextIOWrapper(file, write_through=True) + +if debug: + sys.stdin = get(0, 'rb') + sys.stdout = get(1, 'wb') + sys.stderr = get(2, 'wb') diff --git a/pwndbg/strings.py b/pwndbg/strings.py index c57879d3b..7938c941d 100644 --- a/pwndbg/strings.py +++ b/pwndbg/strings.py @@ -27,7 +27,10 @@ def update_length(): message = message.strip('.') length = int(message) -def get(address): +def get(address, maxlen = None): + if maxlen is None: + maxlen = length + try: sz = gdb.Value(address) sz = sz.cast(pwndbg.typeinfo.pchar) @@ -39,7 +42,7 @@ def get(address): if not all(s in string.printable for s in sz.rstrip('\x00')): return None - if len(sz) < length: + if len(sz) < maxlen: return sz - return sz[:length] + '...' + return sz[:maxlen] + '...' diff --git a/pwndbg/symbol.py b/pwndbg/symbol.py index d74aa0b19..7b9fa9358 100644 --- a/pwndbg/symbol.py +++ b/pwndbg/symbol.py @@ -12,8 +12,9 @@ import pwndbg.elf import pwndbg.ida import pwndbg.memoize import pwndbg.memory +import pwndbg.remote import pwndbg.stack - +import pwndbg.vmmap @pwndbg.memoize.reset_on_objfile def get(address): @@ -67,3 +68,15 @@ def address(symbol): return int(address, 0) except gdb.error: return None + +@pwndbg.events.stop +@pwndbg.memoize.reset_on_start +def add_main_exe_to_symbols(): + if not pwndbg.remote.is_remote(): + return + + exe = pwndbg.elf.exe() + addr = exe.address + path = pwndbg.vmmap.find(addr).objfile + if addr and path: + gdb.execute('add-symbol-file %s %#x' % (path, addr), from_tty=False, to_string=True) diff --git a/pwndbg/vmmap.py b/pwndbg/vmmap.py index b1d7c3898..86d530c84 100644 --- a/pwndbg/vmmap.py +++ b/pwndbg/vmmap.py @@ -322,8 +322,17 @@ def check_aslr(): vmmap = sys.modules[__name__] vmmap.aslr = False + system_aslr = True + data = '' + try: data = pwndbg.file.get('/proc/sys/kernel/randomize_va_space') + except OSError: pass + + + output = gdb.execute('show disable-randomization', to_string=True) if "is off." in output: vmmap.aslr = True + + return vmmap.aslr