def unpack_memory(self): if self.mem is None: print_error("No memory found") raise Exception('sys.exit()') self.mutex = self.unpack_variable("<I", 0) self.flags = self.unpack_variable("<I", 4) offset = 8 if self.version >= 2.23: if self.size_sz == 4: fmt = "<I" elif self.size_sz == 8: fmt = "<Q" # this is padded on 64-bit despite being int self.have_fastchunks = self.unpack_variable(fmt, offset) offset = offset + self.size_sz if self.size_sz == 4: fmt = "<10I" elif self.size_sz == 8: fmt = "<10Q" self.fastbinsY = struct.unpack_from(fmt, self.mem, offset) offset = offset + 10 * self.size_sz if self.size_sz == 4: fmt = "<I" elif self.size_sz == 8: fmt = "<Q" self.top = self.unpack_variable(fmt, offset) offset += self.size_sz self.last_remainder = self.unpack_variable(fmt, offset) offset = offset + self.size_sz if self.size_sz == 4: fmt = "<254I" elif self.size_sz == 8: fmt = "<254Q" self.bins = struct.unpack_from(fmt, self.mem, offset) offset = offset + (254 * self.size_sz) self.binmap = struct.unpack_from("<IIII", self.mem, offset) if self.size_sz == 4: fmt = "<I" elif self.size_sz == 8: fmt = "<Q" offset = offset + 16 self.next = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.next_free = self.unpack_variable(fmt, offset) if self.version >= 2.23: offset = offset + self.size_sz self.attached_threads = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.system_mem = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.max_system_mem = self.unpack_variable(fmt, offset)
def get_heap_address(self, mp=None): """Read heap address from glibc's mp_ structure if available, otherwise fall back to /proc/self/maps which is unreliable. """ start, end = None, None if mp is not None: from libheap.ptmalloc.malloc_par import malloc_par if isinstance(mp, malloc_par): start = mp.sbrk_base else: print_error("Please specify a valid malloc_par variable") # XXX: add end from arena(s).system_mem ? else: pid, task_id, thread_id = gdb.selected_thread().ptid maps_file = "/proc/%d/task/%d/maps" maps_data = open(maps_file % (pid, task_id)).readlines() for line in maps_data: if any(x.strip() == '[heap]' for x in line.split(' ')): heap_range = line.split(' ')[0] start, end = [int(h, 16) for h in heap_range.split('-')] break return start, end
def unpack_memory(self): if self.mem is None: print_error("No memory found") sys.exit() if self.sz == 4: fmt = "<I" elif self.sz == 8: fmt = "<Q" self.trim_threshold = self.unpack_variable(fmt, 0) self.top_pad = self.unpack_variable(fmt, self.sz) self.mmap_threshold = self.unpack_variable(fmt, self.sz * 2) self.arena_text = self.unpack_variable(fmt, self.sz * 3) self.arena_max = self.unpack_variable(fmt, self.sz * 4) # size shared on both 32bit and 64bit Intel fmt = "<I" offset = self.sz * 5 self.n_mmaps = self.unpack_variable(fmt, offset) offset = offset + 4 self.n_mmaps_max = self.unpack_variable(fmt, offset) offset = offset + 4 self.max_n_mmaps = self.unpack_variable(fmt, offset) offset = offset + 4 self.no_dyn_threshold = self.unpack_variable(fmt, offset) if self.sz == 4: fmt = "<I" elif self.sz == 8: fmt = "<Q" offset = offset + 4 self.mmapped_mem = self.unpack_variable(fmt, offset) offset = offset + self.sz self.max_mmapped_mem = self.unpack_variable(fmt, offset) # max_total_mem removed in 2.24 if self.version <= 2.23: offset = offset + self.sz self.max_total_mem = self.unpack_variable(fmt, offset) offset = offset + self.sz self.sbrk_base = self.unpack_variable(fmt, offset) # could not read sbrk_base from mp_, fall back to maps file if (self.sbrk_base == 0) or (self.sbrk_base is None): self.sbrk_base, end = self.dbg.get_heap_address() # we can't read heap address from mp_ or from maps file, exit libheap if (self.sbrk_base == 0) or (self.sbrk_base is None): print_error("Could not find sbrk_base, this setup is unsupported.") exit()
def bin_at(self, m, i): "addressing -- note that bin_at(0) does not exist" if i == 0: print_error("bin_at(0) does not exist") sys.exit() index = (i-1) * 2 return m.bins[index]
def write_memory(self, address, buf, length=None): if self.inferior is None: self.inferior = self.get_inferior() try: if length is None: self.inferior.write_memory(address, buf) else: self.inferior.write_memory(address, buf, length) except MemoryError: print_error("GDB inferior write_memory error")
def read_variable(self, variable=None): if variable is None: print_error("Please specify a variable to read") return None try: return gdb.selected_frame().read_var(variable) except RuntimeError: # No idea why this works but sometimes the frame is not selected print_error("No gdb frame is currently selected.\n") return gdb.selected_frame().read_var(variable)
def __init__(self, debugger=None, version=None): super(smallbins, self).__init__("smallbins", gdb.COMMAND_OBSCURE, gdb.COMPLETE_NONE) if debugger is not None: self.dbg = debugger else: print_error("Please specify a debugger") sys.exit() self.version = version
def __init__(self, debugger=None, version=None): super(heaplsc, self).__init__("heaplsc", gdb.COMMAND_OBSCURE, gdb.COMPLETE_NONE) if debugger is not None: self.dbg = debugger else: print_error("Please specify a debugger") sys.exit() self.version = version
def get_inferior(self): try: if self.inferior is None: if len(gdb.inferiors()) == 0: print_error("No gdb inferior could be found.") return -1 else: self.inferior = gdb.inferiors()[0] return self.inferior else: return self.inferior except AttributeError: print_error("This gdb's python support is too old.") sys.exit()
def unpack_memory(self): if self.mem is None: print_error("No memory found") sys.exit() self.mutex = self.unpack_variable("<I", 0) self.flags = self.unpack_variable("<I", 4) if self.size_sz == 4: fmt = "<10I" elif self.size_sz == 8: fmt = "<10Q" self.fastbinsY = struct.unpack_from(fmt, self.mem, 8) if self.size_sz == 4: fmt = "<I" elif self.size_sz == 8: fmt = "<Q" offset = 8 + (10 * self.size_sz) self.top = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.last_remainder = self.unpack_variable(fmt, offset) if self.size_sz == 4: fmt = "<254I" elif self.size_sz == 8: fmt = "<254Q" offset = offset + self.size_sz self.bins = struct.unpack_from(fmt, self.mem, offset) offset = offset + (254 * self.size_sz) self.binmap = struct.unpack_from("<IIII", self.mem, offset) if self.size_sz == 4: fmt = "<I" elif self.size_sz == 8: fmt = "<Q" offset = offset + 16 self.next = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.next_free = self.unpack_variable(fmt, offset) if self.version >= 2.23: offset = offset + self.size_sz self.attached_threads = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.system_mem = self.unpack_variable(fmt, offset) offset = offset + self.size_sz self.max_system_mem = self.unpack_variable(fmt, offset)
def read_variable(self, variable=None): if variable is None: print_error("Please specify a variable to read") return None try: return gdb.selected_frame().read_var(variable) except RuntimeError: # No idea why this works but sometimes the frame is not selected # print_error("No gdb frame is currently selected.\n") try: return gdb.selected_frame().read_var(variable) except RuntimeError: # variable was not found # print_error("wrong here!") return None except ValueError: # variable was not found return None
def set_globals(self, SIZE_SZ=None): if SIZE_SZ is None: if self.dbg is None: print_error("Please specify a debugger.") sys.exit() self.SIZE_SZ = self.dbg.get_size_sz() else: self.SIZE_SZ = SIZE_SZ self.MIN_CHUNK_SIZE = 4 * self.SIZE_SZ self.MALLOC_ALIGNMENT = 2 * self.SIZE_SZ self.MALLOC_ALIGN_MASK = self.MALLOC_ALIGNMENT - 1 self.MINSIZE = ((self.MIN_CHUNK_SIZE + self.MALLOC_ALIGN_MASK) & ~self.MALLOC_ALIGN_MASK) self.SMALLBIN_WIDTH = self.MALLOC_ALIGNMENT self.MIN_LARGE_SIZE = self.NSMALLBINS * self.SMALLBIN_WIDTH self.MAX_FAST_SIZE = (80 * self.SIZE_SZ / 4) size = self.request2size(self.MAX_FAST_SIZE) self.NFASTBINS = self.fastbin_index(size) + 1
def get_size_sz(self): try: _machine = self.get_arch()[0] except IndexError: _machine = "" SIZE_SZ = 0 print_error("Retrieving SIZE_SZ failed.") except TypeError: # gdb is not running _machine = "" SIZE_SZ = 0 print_error("Retrieving SIZE_SZ failed.") if "elf64" in _machine: SIZE_SZ = 8 elif "elf32" in _machine: SIZE_SZ = 4 else: SIZE_SZ = 0 print_error("Retrieving SIZE_SZ failed.") return SIZE_SZ
def write(self, inferior=None): # XXX: fixme print_error("malloc_par write() not yet implemented.")
def __init__(self, addr=None, mem=None, debugger=None, version=None): self.trim_threshold = 0 self.top_pad = 0 self.mmap_threshold = 0 self.arena_test = 0 self.arena_max = 0 self.n_mmaps = 0 self.n_mmaps_max = 0 self.max_n_mmaps = 0 self.no_dyn_threshold = 0 self.mmapped_mem = 0 self.max_mmapped_mem = 0 self.max_total_mem = 0 self.sbrk_base = 0 if addr is None: if mem is None: print_error("Please specify an address or raw memory.") return None self.address = None else: self.address = addr if debugger is not None: self.dbg = debugger else: print_error("Please specify a debugger") sys.exit() # get architecture SIZE_SZ self.sz = self.dbg.get_size_sz() if version is None: print_error("Please specify a malloc_par version.") sys.exit() else: self.version = version if mem is None: # a string of raw memory was not provided if self.version >= 2.15 and self.version <= 2.23: if self.sz == 4: # sizeof(malloc_par) = 20 + 16 + 16 struct_malloc_par_size = 0x34 elif self.sz == 8: # sizeof(malloc_par) = 40 + 16 + 32 struct_malloc_par_size = 0x58 elif self.version == 2.24 or self.version == 2.25: # max_total_mem removed in 2.24 if self.sz == 4: struct_malloc_par_size = 0x30 elif self.sz == 8: struct_malloc_par_size = 0x50 try: self.mem = self.dbg.read_memory(addr, struct_malloc_par_size) except TypeError: print_error("Invalid address specified.") return None except RuntimeError: print_error("Could not read address {0:#x}".format(addr)) return None else: self.mem = mem self.unpack_memory()
def invoke(self, arg, from_tty): """Inspired by jp's phrack print and arena.c""" ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") main_arena_address = self.dbg.format_address(main_arena.address) thread_arena = self.dbg.read_variable("thread_arena") if thread_arena is not None: thread_arena_address = self.dbg.format_address(thread_arena) else: thread_arena_address = main_arena_address argv = self.dbg.string_to_argv(arg) if len(argv) == 1: arena_address = int(argv[0], 16) elif len(argv): print_error('Too many arguments') return else: arena_address = thread_arena_address ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # XXX: add mp_ address guessing via offset without symbols mp_ = self.dbg.read_variable("mp_") mp_address = mp_.address mp = malloc_par(mp_address, debugger=self.dbg, version=self.version) if arena_address == main_arena_address: start, _ = self.dbg.get_heap_address(mp) else: # XXX: start offset start = arena_address + ar_ptr.size sbrk_base = start # print("{:>19}".format("arena @ "), end="") # print_value("{:#x}".format(arena_address), end="\n\n") # print_title("{:>15}".format("flat heap listing"), end="\n") print_title("{:>15}{:>17}{:>18}".format("ADDR", "SIZE", "STATUS"), end="\n") print("{:11}".format("sbrk_base"), end="") print_value("{:#x}".format(int(sbrk_base)), end="\n") p = malloc_chunk(sbrk_base, inuse=True, read_data=False, debugger=self.dbg) while (1): print("{:11}".format("chunk"), end="") print_value("{: <#17x}".format(int(p.address)), end="") print("{: <#16x}".format(int(ptm.chunksize(p))), end="") if p.address == ptm.top(ar_ptr): print("(top)") break elif p.size == (0 | ptm.PREV_INUSE): print("(fence)") break if ptm.inuse(p): print("(inuse)") else: p = malloc_chunk(p.address, inuse=False, debugger=self.dbg) print("(F) FD ", end="") print_value("{:#x} ".format(int(p.fd))) print("BK ", end="") print_value("{:#x} ".format(int(p.bk))) if ((p.fd == ar_ptr.last_remainder) and (p.bk == ar_ptr.last_remainder) and (ar_ptr.last_remainder != 0)): print("(LR)") elif ((p.fd == p.bk) & ~ptm.inuse(p)): print("(LC)") else: print("") p = malloc_chunk(ptm.next_chunk(p), inuse=True, read_data=False, debugger=self.dbg) sbrk_end = int(sbrk_base + ar_ptr.max_system_mem) print("{:11}".format("sbrk_end"), end="") print_value("{:#x}".format(sbrk_end), end="") print("")
def invoke(self, arg, from_tty): """Inspired by jp's phrack print and arena.c""" ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") main_arena_address = self.dbg.format_address(main_arena.address) thread_arena = self.dbg.read_variable("thread_arena") if thread_arena is not None: thread_arena_address = self.dbg.format_address(thread_arena) else: thread_arena_address = main_arena_address argv = self.dbg.string_to_argv(arg) if len(argv) == 1: arena_address = int(argv[0], 16) elif len(argv): print_error('Too many arguments') return else: arena_address = thread_arena_address ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # XXX: add mp_ address guessing via offset without symbols mp_ = self.dbg.read_variable("mp_") mp_address = mp_.address mp = malloc_par(mp_address, debugger=self.dbg, version=self.version) if arena_address == main_arena_address: start, _ = self.dbg.get_heap_address(mp) else: # XXX: start offset start = arena_address + ar_ptr.size sbrk_base = start # print("{:>19}".format("arena @ "), end="") # print_value("{:#x}".format(arena_address), end="\n\n") # print_title("{:>15}".format("flat heap listing"), end="\n") print_title("{:>15}{:>17}{:>18}".format("ADDR", "SIZE", "STATUS"), end="\n") print("{:11}".format("sbrk_base"), end="") print_value("{:#x}".format(int(sbrk_base)), end="\n") p = malloc_chunk(sbrk_base, inuse=True, read_data=False, debugger=self.dbg) while(1): print("{:11}".format("chunk"), end="") print_value("{: <#17x}".format(int(p.address)), end="") print("{: <#16x}".format(int(ptm.chunksize(p))), end="") if p.address == ptm.top(ar_ptr): print("(top)") break elif p.size == (0 | ptm.PREV_INUSE): print("(fence)") break if ptm.inuse(p): print("(inuse)") else: p = malloc_chunk(p.address, inuse=False, debugger=self.dbg) print("(F) FD ", end="") print_value("{:#x} ".format(int(p.fd))) print("BK ", end="") print_value("{:#x} ".format(int(p.bk))) if ((p.fd == ar_ptr.last_remainder) and (p.bk == ar_ptr.last_remainder) and (ar_ptr.last_remainder != 0)): print("(LR)") elif ((p.fd == p.bk) & ~ptm.inuse(p)): print("(LC)") else: print("") p = malloc_chunk(ptm.next_chunk(p), inuse=True, read_data=False, debugger=self.dbg) sbrk_end = int(sbrk_base + ar_ptr.max_system_mem) print("{:11}".format("sbrk_end"), end="") print_value("{:#x}".format(sbrk_end), end="") print("")
def invoke(self, arg, from_tty): # XXX: self.dbg.string_to_argv if arg.find("-h") != -1: # print_header("heap ", end="") # print("Options:", end="\n\n") # print_header("{:<15}".format("-a 0x1234")) # print("Specify an arena address") print_header("{:<15}".format("heapls")) print("Print a flat listing of all chunks in an arena") print_header("{:<15}".format("fastbins [#]")) print("Print all fast bins, or only a single fast bin") print_header("{:<15}".format("smallbins [#]")) print("Print all small bins, or only a single small bin") print_header("{:<15}".format("freebins")) print("Print compact bin listing (only free chunks)") print_header("{:<15}".format("heaplsc")) print("Print compact arena listing (all chunks)") print_header("{:<15}".format("mstats"), end="") print("Print memory alloc statistics similar to malloc_stats(3)") # print_header("{:<22}".format("print_bin_layout [#]"), end="") # print("Print the layout of a particular free bin") return ptm = ptmalloc(self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") # XXX: add arena address guessing via offset without symbols arena_address = self.dbg.format_address(main_arena.address) ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # XXX: add arena address passing via arg (-a) if (len(arg) == 0) and (ar_ptr.next == 0): # struct malloc_state may be invalid size (wrong glibc version) print_error("No arenas could be found at {:#x}".format( ar_ptr.address)) return print("Arena(s) found:", end="\n") print(" arena @ ", end="") print_header("{:#x}".format(int(ar_ptr.address)), end="\n") if ar_ptr.address != ar_ptr.next: # we have more than one arena curr_arena = malloc_state(ar_ptr.next, debugger=self.dbg, version=self.version) while (ar_ptr.address != curr_arena.address): print(" arena @ ", end="") print_header("{:#x}".format(int(curr_arena.address)), end="\n") curr_arena = malloc_state(curr_arena.next, debugger=self.dbg, version=self.version) if curr_arena.address == 0: print_error("No arenas could be correctly found.") break # breaking infinite loop
def invoke(self, arg, from_tty): ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() if ptm.SIZE_SZ == 4: pad_width = 27 elif ptm.SIZE_SZ == 8: pad_width = 31 # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") arena_address = self.dbg.format_address(main_arena.address) ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # mchunkptr bins in struct malloc_state if ptm.SIZE_SZ == 4: bins_offset = 4 + 4 + 40 + 4 + 4 # 56 sb_base = int(ar_ptr.address) + bins_offset elif ptm.SIZE_SZ == 8: bins_offset = 4 + 4 + 80 + 8 + 8 # 104 sb_base = int(ar_ptr.address) + bins_offset if len(arg) == 0: sb_num = None else: sb_num = int(arg.split(" ")[0]) if (sb_num * 2) > ptm.NBINS: print_error("Invalid smallbin number") return print_title("smallbins", end="") for sb in range(2, ptm.NBINS + 2, 2): if sb_num is not None and sb_num != 0: sb = sb_num*2 offset = sb_base + (sb - 2) * ptm.SIZE_SZ try: mem = self.dbg.read_memory(offset, 2 * ptm.SIZE_SZ) if ptm.SIZE_SZ == 4: fd, bk = struct.unpack("<II", mem) elif ptm.SIZE_SZ == 8: fd, bk = struct.unpack("<QQ", mem) except RuntimeError: print_error("Invalid smallbin addr {0:#x}".format(offset)) return print("") print("[ sb {:02} ] ".format(int(sb / 2)), end="") print("{:#x}{:>{width}}".format(int(offset), "-> ", width=5), end="") if fd == (offset - 2 * ptm.SIZE_SZ): print("[ {:#x} | {:#x} ] ".format(int(fd), int(bk)), end="") else: print_value("[ {:#x} | {:#x} ] ".format(int(fd), int(bk))) while (1): if fd == (offset - 2 * ptm.SIZE_SZ): break chunk = malloc_chunk(fd, inuse=False, debugger=self.dbg) print("") print_value("{:>{width}}{:#x} | {:#x} ] ".format("[ ", int(chunk.fd), int(chunk.bk), width=pad_width)) print("({})".format(int(ptm.chunksize(chunk))), end="") fd = chunk.fd if sb_num is not None: # only print one smallbin break print("")
def _gdb_is_running(*args, **kwargs): if (gdb.selected_thread() is not None): return f(*args, **kwargs) else: print_error("GDB is not running.")
def invoke(self, arg, from_tty): ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() if ptm.SIZE_SZ == 4: pad_width = 25 elif ptm.SIZE_SZ == 8: pad_width = 29 # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") arena_address = self.dbg.format_address(main_arena.address) thread_arena = self.dbg.read_variable("thread_arena") if thread_arena is not None: thread_arena_address = self.dbg.format_address(thread_arena) else: thread_arena_address = arena_address argv = self.dbg.string_to_argv(arg) if len(argv) == 1: arena_address = int(argv[0], 16) elif len(argv): print_error('Too many arguments') return else: arena_address = thread_arena_address ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # 8 bytes into struct malloc_state on both 32/64bit # XXX: fixme for glibc <= 2.19 with THREAD_STATS fastbinsY = int(ar_ptr.address) + 8 fb_base = fastbinsY print_title("fastbins", end="") for fb in range(0, ptm.NFASTBINS): offset = int(fb_base + fb * ptm.SIZE_SZ) try: mem = self.dbg.read_memory(offset, ptm.SIZE_SZ) if ptm.SIZE_SZ == 4: fd = struct.unpack("<I", mem)[0] elif ptm.SIZE_SZ == 8: fd = struct.unpack("<Q", mem)[0] except RuntimeError: print_error("Invalid fastbin addr {0:#x}".format(offset)) return print("") print("[ fb {} ] ".format(fb), end="") print("{:#x}{:>{width}}".format(offset, "-> ", width=5), end="") if fd == 0: print("[ {:#x} ] ".format(fd), end="") else: print_value("[ {:#x} ] ".format(fd)) if fd != 0: # fastbin is not empty fb_size = ((ptm.MIN_CHUNK_SIZE) + (ptm.MALLOC_ALIGNMENT) * fb) print("({})".format(int(fb_size)), end="") chunk = malloc_chunk(fd, inuse=False, debugger=self.dbg) while chunk.fd != 0: if chunk.fd is None: # could not read memory section break print_value("\n{:>{width}} {:#x} {} ".format( "[", chunk.fd, "]", width=pad_width)) print("({})".format(fb_size), end="") chunk = malloc_chunk(chunk.fd, inuse=False, debugger=self.dbg) print("")
def invoke(self, arg, from_tty): "Specify an optional arena addr: print_bin_layout main_arena=0x12345" ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() if len(arg) == 0: print_error("Please specify the free bin to dump") return try: if arg.find("main_arena") == -1: main_arena = self.dbg.read_variable("main_arena") main_arena_address = self.dbg.format_address( main_arena.address) else: # XXX: fixme arg = arg.split() for item in arg: if item.find("main_arena") != -1: if len(item) < 12: print_error("Malformed main_arena parameter") return else: main_arena_address = int(item[11:], 16) except RuntimeError: print_error("No frame is currently selected.") return except ValueError: print_error("Debug glibc was not found.") return if main_arena_address == 0: print_error("Invalid main_arena address (0)") return ar_ptr = malloc_state(main_arena_address, debugger=self.dbg, version=self.version) ptm.mutex_lock(ar_ptr) # print_title("Bin Layout") if int(arg) == 0: print_error("bin_at(0) does not exist") return b = ptm.bin_at(ar_ptr, int(arg)) first = ptm.first(malloc_chunk(b, inuse=False, debugger=self.dbg)) p = malloc_chunk(first, inuse=False, debugger=self.dbg) print_once = True print_str = "" count = 0 while p.address != int(b): if print_once: print_once = False print_str += "--> " print_str += color_value("[bin {}]".format(int(arg))) count += 1 print_str += " <--> " print_str += color_value("{:#x}".format(int(p.address))) count += 1 p = malloc_chunk(ptm.first(p), inuse=False, debugger=self.dbg) if len(print_str) != 0: print_str += " <--" print(print_str) print("|{}|".format(" " * (len(print_str) - 2 - count*12))) print("{}".format("-" * (len(print_str) - count*12))) else: print_value("Bin {} ".format(int(arg)), end="") print("empty") ptm.mutex_unlock(ar_ptr)
def invoke(self, arg, from_tty): if arg.find("-h") != -1: # print_header("heap ", end="") # print("Options:", end="\n\n") # print_header("{:<15}".format("-a 0x1234")) # print("Specify an arena address") print_header("{:<15}".format("heapls")) print("Print a flat listing of all chunks in an arena") print_header("{:<15}".format("fastbins [#]")) print("Print all fast bins, or only a single fast bin") print_header("{:<15}".format("smallbins [#]")) print("Print all small bins, or only a single small bin") print_header("{:<15}".format("freebins")) print("Print compact bin listing (only free chunks)") print_header("{:<15}".format("heaplsc")) print("Print compact arena listing (all chunks)") print_header("{:<15}".format("mstats"), end="") print("Print memory alloc statistics similar to malloc_stats(3)") # print_header("{:<22}".format("print_bin_layout [#]"), end="") # print("Print the layout of a particular free bin") return ptm = ptmalloc(self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") # XXX: add arena address guessing via offset without symbols arena_address = self.dbg.format_address(main_arena.address) ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # XXX: add arena address passing via arg (-a) if (len(arg) == 0) and (ar_ptr.next == 0): # struct malloc_state may be invalid size (wrong glibc version) print_error("No arenas could be found at {:#x}".format( ar_ptr.address)) return print("Arena(s) found:", end="\n") print(" arena @ ", end="") print_header("{:#x}".format(int(ar_ptr.address)), end="\n") if ar_ptr.address != ar_ptr.next: # we have more than one arena curr_arena = malloc_state(ar_ptr.next, debugger=self.dbg, version=self.version) while (ar_ptr.address != curr_arena.address): print(" arena @ ", end="") print_header("{:#x}".format(int(curr_arena.address)), end="\n") curr_arena = malloc_state(curr_arena.next, debugger=self.dbg, version=self.version) if curr_arena.address == 0: print_error("No arenas could be correctly found.") break # breaking infinite loop
def __init__(self, addr=None, mem=None, size=None, inuse=False, read_data=True, debugger=None): self.prev_size = 0 self.size = 0 self.data = None self.fd = None self.bk = None self.fd_nextsize = None self.bk_nextsize = None if addr is None or addr == 0: if mem is None: print_error("Please specify a valid struct malloc_chunk addr.") return None self.address = None else: self.address = addr if debugger is not None: self.dbg = debugger else: print_error("Please specify a debugger") sys.exit() self.SIZE_SZ = self.dbg.get_size_sz() if mem is None: # a string of raw memory was not provided try: if self.SIZE_SZ == 4: mem = self.dbg.read_memory(addr, 0x8) elif self.SIZE_SZ == 8: mem = self.dbg.read_memory(addr, 0x10) except TypeError: print_error("Invalid address specified.") return None except RuntimeError: print_error("Could not read address {0:#x}".format(addr)) return None else: # a string of raw memory was provided if inuse: if (len(mem) != 0x8) and (len(mem) < 0x10): print_error("Insufficient mem provided for malloc_chunk.") return None if len(mem) == 0x8 or len(mem) == 0x10: # header only provided read_data = False else: if (len(mem) != 0x18) and (len(mem) < 0x30): print_error("Insufficient mem provided for a free chunk.") return None if self.SIZE_SZ == 4: (self.prev_size, self.size) = struct.unpack_from("<II", mem, 0x0) elif self.SIZE_SZ == 8: (self.prev_size, self.size) = struct.unpack_from("<QQ", mem, 0x0) ptm = ptmalloc.ptmalloc.ptmalloc(debugger=self.dbg) if size is None: real_size = (self.size & ~ptm.SIZE_BITS) else: # a size was provided (for a malformed chunk with an invalid size) real_size = size & ~ptm.SIZE_BITS if inuse: if read_data: if self.address is not None: # a string of raw memory was not provided try: mem = self.dbg.read_memory(addr, real_size + self.SIZE_SZ) except TypeError: print_error("Invalid address specified.") return None except RuntimeError: print_error("Could not read address {0:#x}".format( addr)) return None real_size = (real_size - self.SIZE_SZ) / self.SIZE_SZ if self.SIZE_SZ == 4: self.data = struct.unpack_from("<%dI" % real_size, mem, 0x8) elif self.SIZE_SZ == 8: self.data = struct.unpack_from("<%dQ" % real_size, mem, 0x10) if not inuse: if self.address is not None: # a string of raw memory was not provided if self.SIZE_SZ == 4: mem = self.dbg.read_memory(addr, 0x18) elif self.SIZE_SZ == 8: mem = self.dbg.read_memory(addr, 0x30) if self.SIZE_SZ == 4: (self.fd, self.bk, self.fd_nextsize, self.bk_nextsize) = struct.unpack_from("<IIII", mem, 0x8) elif self.SIZE_SZ == 8: (self.fd, self.bk, self.fd_nextsize, self.bk_nextsize) = struct.unpack_from("<QQQQ", mem, 0x10)
def __init__(self, addr=None, mem=None, size=None, inuse=False, read_data=True, debugger=None): self.prev_size = 0 self.size = 0 self.data = None self.fd = None self.bk = None self.fd_nextsize = None self.bk_nextsize = None if addr is None or addr == 0: if mem is None: print_error("Please specify a valid struct malloc_chunk addr.") return None self.address = None else: self.address = addr if debugger is not None: self.dbg = debugger else: print_error("Please specify a debugger") sys.exit() self.SIZE_SZ = self.dbg.get_size_sz() if mem is None: # a string of raw memory was not provided try: if self.SIZE_SZ == 4: mem = self.dbg.read_memory(addr, 0x8) elif self.SIZE_SZ == 8: mem = self.dbg.read_memory(addr, 0x10) except TypeError: print_error("Invalid address specified.") return None except RuntimeError: print_error("Could not read address {0:#x}".format(addr)) return None else: # a string of raw memory was provided if inuse: if (len(mem) != 0x8) and (len(mem) < 0x10): print_error("Insufficient mem provided for malloc_chunk.") return None if len(mem) == 0x8 or len(mem) == 0x10: # header only provided read_data = False else: if (len(mem) != 0x18) and (len(mem) < 0x30): print_error("Insufficient mem provided for a free chunk.") return None if self.SIZE_SZ == 4: (self.prev_size, self.size) = struct.unpack_from("<II", mem, 0x0) elif self.SIZE_SZ == 8: (self.prev_size, self.size) = struct.unpack_from("<QQ", mem, 0x0) ptm = ptmalloc.ptmalloc.ptmalloc(debugger=self.dbg) if size is None: real_size = (self.size & ~ptm.SIZE_BITS) else: # a size was provided (for a malformed chunk with an invalid size) real_size = size & ~ptm.SIZE_BITS if inuse: if read_data: if self.address is not None: # a string of raw memory was not provided try: mem = self.dbg.read_memory(addr, real_size + self.SIZE_SZ) except TypeError: print_error("Invalid address specified.") return None except RuntimeError: print_error( "Could not read address {0:#x}".format(addr)) return None real_size = (real_size - self.SIZE_SZ) / self.SIZE_SZ if self.SIZE_SZ == 4: self.data = struct.unpack_from("<%dI" % real_size, mem, 0x8) elif self.SIZE_SZ == 8: self.data = struct.unpack_from("<%dQ" % real_size, mem, 0x10) if not inuse: if self.address is not None: # a string of raw memory was not provided if self.SIZE_SZ == 4: mem = self.dbg.read_memory(addr, 0x18) elif self.SIZE_SZ == 8: mem = self.dbg.read_memory(addr, 0x30) if self.SIZE_SZ == 4: (self.fd, self.bk, self.fd_nextsize, self.bk_nextsize) = struct.unpack_from("<IIII", mem, 0x8) elif self.SIZE_SZ == 8: (self.fd, self.bk, self.fd_nextsize, self.bk_nextsize) = struct.unpack_from("<QQQQ", mem, 0x10)
def invoke(self, arg, from_tty): "Specify an optional arena addr: print_bin_layout main_arena=0x12345" ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() if len(arg) == 0: print_error("Please specify the free bin to dump") return try: if arg.find("main_arena") == -1: main_arena = self.dbg.read_variable("main_arena") main_arena_address = self.dbg.format_address( main_arena.address) else: # XXX: fixme arg = arg.split() for item in arg: if item.find("main_arena") != -1: if len(item) < 12: print_error("Malformed main_arena parameter") return else: main_arena_address = int(item[11:], 16) except RuntimeError: print_error("No frame is currently selected.") return except ValueError: print_error("Debug glibc was not found.") return if main_arena_address == 0: print_error("Invalid main_arena address (0)") return ar_ptr = malloc_state(main_arena_address, debugger=self.dbg, version=self.version) ptm.mutex_lock(ar_ptr) # print_title("Bin Layout") if int(arg) == 0: print_error("bin_at(0) does not exist") return b = ptm.bin_at(ar_ptr, int(arg)) first = ptm.first(malloc_chunk(b, inuse=False, debugger=self.dbg)) p = malloc_chunk(first, inuse=False, debugger=self.dbg) print_once = True print_str = "" count = 0 while p.address != int(b): if print_once: print_once = False print_str += "--> " print_str += color_value("[bin {}]".format(int(arg))) count += 1 print_str += " <--> " print_str += color_value("{:#x}".format(int(p.address))) count += 1 p = malloc_chunk(ptm.first(p), inuse=False, debugger=self.dbg) if len(print_str) != 0: print_str += " <--" print(print_str) print("|{}|".format(" " * (len(print_str) - 2 - count * 12))) print("{}".format("-" * (len(print_str) - count * 12))) else: print_value("Bin {} ".format(int(arg)), end="") print("empty") ptm.mutex_unlock(ar_ptr)
def invoke(self, arg, from_tty): ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() if ptm.SIZE_SZ == 4: pad_width = 27 elif ptm.SIZE_SZ == 8: pad_width = 31 # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") arena_address = self.dbg.format_address(main_arena.address) ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # mchunkptr bins in struct malloc_state sb_base = int(ar_ptr.address) + ar_ptr.bins_offset if len(arg) == 0: sb_num = None else: sb_num = int(arg.split(" ")[0]) if (sb_num * 2) > ptm.NBINS: print_error("Invalid smallbin number") return print_title("smallbins", end="") for sb in range(2, ptm.NBINS + 2, 2): if sb_num is not None and sb_num != 0: sb = sb_num*2 offset = sb_base + (sb - 2) * ptm.SIZE_SZ try: mem = self.dbg.read_memory(offset, 2 * ptm.SIZE_SZ) if ptm.SIZE_SZ == 4: fd, bk = struct.unpack("<II", mem) elif ptm.SIZE_SZ == 8: fd, bk = struct.unpack("<QQ", mem) except RuntimeError: print_error("Invalid smallbin addr {0:#x}".format(offset)) return print("") print("[ sb {:02} ] ".format(int(sb / 2)), end="") print("{:#x}{:>{width}}".format(int(offset), "-> ", width=5), end="") if fd == (offset - 2 * ptm.SIZE_SZ): print("[ {:#x} | {:#x} ] ".format(int(fd), int(bk)), end="") else: print_value("[ {:#x} | {:#x} ] ".format(int(fd), int(bk))) while (1): if fd == (offset - 2 * ptm.SIZE_SZ): break chunk = malloc_chunk(fd, inuse=False, debugger=self.dbg) print("") print_value("{:>{width}}{:#x} | {:#x} ] ".format("[ ", int(chunk.fd), int(chunk.bk), width=pad_width)) print("({})".format(int(ptm.chunksize(chunk))), end="") fd = chunk.fd if sb_num is not None: # only print one smallbin break print("")
def __init__(self, addr=None, mem=None, debugger=None, version=None): self.size = 0 self.mutex = 0 self.flags = 0 self.fastbinsY = 0 self.top = 0 self.last_remainder = 0 self.bins = 0 self.binmap = 0 self.next = 0 self.next_free = 0 # self.attached_threads = 0 self.system_mem = 0 self.max_system_mem = 0 if addr is None: if mem is None: print_error("Please specify a struct malloc_state address.") return None self.address = None else: self.address = addr if debugger is not None: self.dbg = debugger else: print_error("Please specify a debugger") sys.exit() self.size_sz = self.dbg.get_size_sz() if version is None: print_error("Please specify a malloc_state version.") sys.exit() else: self.version = version if mem is None: # a string of raw memory was not provided if self.version >= 2.15 and self.version < 2.23: if self.size_sz == 4: # sizeof(malloc_state) = 4+4+40+4+4+(254*4)+16+4+4+4+4 self.size = 0x450 elif self.size_sz == 8: # sizeof(malloc_state) = 4+4+80+8+8+(254*8)+16+8+8+8+8 self.size = 0x888 elif self.version >= 2.23 and self.version <= 2.25: # attached_threads added in 2.23 if self.size_sz == 4: self.size = 0x454 elif self.size_sz == 8: self.size = 0x890 try: self.mem = self.dbg.read_memory(addr, self.size) except TypeError: print_error("Invalid address specified.") return None except RuntimeError: print_error("Could not read address {0:#x}".format(addr)) return None else: # XXX: fix class size # self.size = len(mem) self.mem = mem self.unpack_memory()
def invoke(self, arg, from_tty): "Specify an optional arena addr: print_mstats main_arena=0x12345" ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() try: # XXX: add mp_ address guessing via offset without symbols mp = self.dbg.read_variable("mp_") if arg.find("main_arena") == -1: main_arena = self.dbg.read_variable("main_arena") main_arena_address = self.dbg.format_address( main_arena.address) else: arg = arg.split() for item in arg: if item.find("main_arena") != -1: if len(item) < 12: print_error("Malformed main_arena parameter") return else: main_arena_address = int(item[11:], 16) except RuntimeError: print_error("No frame is currently selected.") return except ValueError: print_error("Debug glibc was not found.") return if main_arena_address == 0: print_error("Invalid main_arena address (0)") return in_use_b = mp['mmapped_mem'] system_b = in_use_b print("Malloc Stats", end="\n\n") arena = 0 ar_ptr = malloc_state(main_arena_address, debugger=self.dbg, version=self.version) while(1): ptm.mutex_lock(ar_ptr) # account for top avail = ptm.chunksize(malloc_chunk(ptm.top(ar_ptr), inuse=True, read_data=False, debugger=self.dbg)) nblocks = 1 nfastblocks = 0 fastavail = 0 # traverse fastbins for i in range(ptm.NFASTBINS): p = ptm.fastbin(ar_ptr, i) while p != 0: p = malloc_chunk(p, inuse=False, debugger=self.dbg) nfastblocks += 1 fastavail += ptm.chunksize(p) p = p.fd avail += fastavail # traverse regular bins for i in range(1, ptm.NBINS): b = ptm.bin_at(ar_ptr, i) first = malloc_chunk(b, inuse=False, debugger=self.dbg) first = ptm.first(first) p = malloc_chunk(first, inuse=False, debugger=self.dbg) while p.address != int(b): nblocks += 1 avail += ptm.chunksize(p) p = malloc_chunk(ptm.first(p), inuse=False, debugger=self.dbg) print_header("Arena {}:".format(arena), end="\n") print("{:16} = ".format("system bytes"), end='') print_value("{}".format(ar_ptr.max_system_mem), end='\n') print("{:16} = ".format("in use bytes"), end='') print_value("{}".format(ar_ptr.max_system_mem - avail), end='\n') system_b += ar_ptr.max_system_mem in_use_b += (ar_ptr.max_system_mem - avail) ptm.mutex_unlock(ar_ptr) if ar_ptr.next == main_arena_address: break else: next_addr = self.dbg.format_address(ar_ptr.next) ar_ptr = malloc_state(next_addr, debugger=self.dbg, version=self.version) arena += 1 print_header("\nTotal (including mmap):", end="\n") print("{:16} = ".format("system bytes"), end='') print_value("{}".format(system_b), end='\n') print("{:16} = ".format("in use bytes"), end='') print_value("{}".format(in_use_b), end='\n') # XXX: max_total_mem removed in 2.24 try: # catch the error before we print anything val = mp['max_total_mem'] print("{:16} = ".format("max system bytes"), end='') print_value("{}".format(val), end='\n') except gdb.error: pass print("{:16} = ".format("max mmap regions"), end='') print_value("{}".format(mp['max_n_mmaps']), end='\n') print("{:16} = ".format("max mmap bytes"), end='') print_value("{}".format(mp['max_mmapped_mem']), end='\n')
def invoke(self, arg, from_tty): ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() if ptm.SIZE_SZ == 4: pad_width = 25 elif ptm.SIZE_SZ == 8: pad_width = 29 # XXX: from old heap command, replace main_arena = self.dbg.read_variable("main_arena") arena_address = self.dbg.format_address(main_arena.address) thread_arena = self.dbg.read_variable("thread_arena") if thread_arena is not None: thread_arena_address = self.dbg.format_address(thread_arena) else: thread_arena_address = arena_address argv = self.dbg.string_to_argv(arg) if len(argv) == 1: arena_address = int(argv[0], 16) elif len(argv): print_error('Too many arguments') return else: arena_address = thread_arena_address ar_ptr = malloc_state(arena_address, debugger=self.dbg, version=self.version) # 8 bytes into struct malloc_state on both 32/64bit # XXX: fixme for glibc <= 2.19 with THREAD_STATS fastbinsY = int(ar_ptr.address) + 8 fb_base = fastbinsY print_title("fastbins", end="") for fb in range(0, ptm.NFASTBINS): offset = int(fb_base + fb * ptm.SIZE_SZ) try: mem = self.dbg.read_memory(offset, ptm.SIZE_SZ) if ptm.SIZE_SZ == 4: fd = struct.unpack("<I", mem)[0] elif ptm.SIZE_SZ == 8: fd = struct.unpack("<Q", mem)[0] except RuntimeError: print_error("Invalid fastbin addr {0:#x}".format(offset)) return print("") print("[ fb {} ] ".format(fb), end="") print("{:#x}{:>{width}}".format(offset, "-> ", width=5), end="") if fd == 0: print("[ {:#x} ] ".format(fd), end="") else: print_value("[ {:#x} ] ".format(fd)) if fd != 0: # fastbin is not empty fb_size = ((ptm.MIN_CHUNK_SIZE) + (ptm.MALLOC_ALIGNMENT) * fb) print("({})".format(int(fb_size)), end="") chunk = malloc_chunk(fd, inuse=False, debugger=self.dbg) while chunk.fd != 0: if chunk.fd is None: # could not read memory section break print_value("\n{:>{width}} {:#x} {} ".format("[", chunk.fd, "]", width=pad_width)) print("({})".format(fb_size), end="") chunk = malloc_chunk(chunk.fd, inuse=False, debugger=self.dbg) print("")
def invoke(self, arg, from_tty): "Specify an optional arena addr: print_mstats main_arena=0x12345" ptm = ptmalloc(debugger=self.dbg) if ptm.SIZE_SZ == 0: ptm.set_globals() try: # XXX: add mp_ address guessing via offset without symbols mp = self.dbg.read_variable("mp_") if arg.find("main_arena") == -1: main_arena = self.dbg.read_variable("main_arena") main_arena_address = self.dbg.format_address( main_arena.address) else: arg = arg.split() for item in arg: if item.find("main_arena") != -1: if len(item) < 12: print_error("Malformed main_arena parameter") return else: main_arena_address = int(item[11:], 16) except RuntimeError: print_error("No frame is currently selected.") return except ValueError: print_error("Debug glibc was not found.") return if main_arena_address == 0: print_error("Invalid main_arena address (0)") return in_use_b = mp['mmapped_mem'] system_b = in_use_b print("Malloc Stats", end="\n\n") arena = 0 ar_ptr = malloc_state(main_arena_address, debugger=self.dbg, version=self.version) while (1): ptm.mutex_lock(ar_ptr) # account for top avail = ptm.chunksize( malloc_chunk(ptm.top(ar_ptr), inuse=True, read_data=False, debugger=self.dbg)) nblocks = 1 nfastblocks = 0 fastavail = 0 # traverse fastbins for i in range(ptm.NFASTBINS): p = ptm.fastbin(ar_ptr, i) while p != 0: p = malloc_chunk(p, inuse=False, debugger=self.dbg) nfastblocks += 1 fastavail += ptm.chunksize(p) p = p.fd avail += fastavail # traverse regular bins for i in range(1, ptm.NBINS): b = ptm.bin_at(ar_ptr, i) first = malloc_chunk(b, inuse=False, debugger=self.dbg) first = ptm.first(first) p = malloc_chunk(first, inuse=False, debugger=self.dbg) while p.address != int(b): nblocks += 1 avail += ptm.chunksize(p) p = malloc_chunk(ptm.first(p), inuse=False, debugger=self.dbg) print_header("Arena {}:".format(arena), end="\n") print("{:16} = ".format("system bytes"), end='') print_value("{}".format(ar_ptr.max_system_mem), end='\n') print("{:16} = ".format("in use bytes"), end='') print_value("{}".format(ar_ptr.max_system_mem - avail), end='\n') system_b += ar_ptr.max_system_mem in_use_b += (ar_ptr.max_system_mem - avail) ptm.mutex_unlock(ar_ptr) if ar_ptr.next == main_arena_address: break else: next_addr = self.dbg.format_address(ar_ptr.next) ar_ptr = malloc_state(next_addr, debugger=self.dbg, version=self.version) arena += 1 print_header("\nTotal (including mmap):", end="\n") print("{:16} = ".format("system bytes"), end='') print_value("{}".format(system_b), end='\n') print("{:16} = ".format("in use bytes"), end='') print_value("{}".format(in_use_b), end='\n') # XXX: max_total_mem removed in 2.24 try: # catch the error before we print anything print_value("{}".format(mp['max_total_mem']), end='\n') print("{:16} = ".format("max system bytes"), end='') except gdb.error: pass print("{:16} = ".format("max mmap regions"), end='') print_value("{}".format(mp['max_n_mmaps']), end='\n') print("{:16} = ".format("max mmap bytes"), end='') print_value("{}".format(mp['max_mmapped_mem']), end='\n')