diff --git a/docs/commands/index.md b/docs/commands/index.md index 6b3b93151..0f9483c98 100644 --- a/docs/commands/index.md +++ b/docs/commands/index.md @@ -260,6 +260,8 @@ - [mallocng-explain](musl/mallocng-explain.md) - Gives a quick explanation of musl's mallocng allocator. - [mallocng-find](musl/mallocng-find.md) - Find slot which contains the given address. - [mallocng-group](musl/mallocng-group.md) - Print out information about a mallocng group at the given address. +- [mallocng-malloc-context](musl/mallocng-malloc-context.md) - Print out the mallocng __malloc_context (ctx) object. +- [mallocng-meta-area](musl/mallocng-meta-area.md) - Print out a mallocng meta_area object at the given address. - [mallocng-meta](musl/mallocng-meta.md) - Print out information about a mallocng group given the address of its meta. - [mallocng-slot-start](musl/mallocng-slot-start.md) - Dump information about a mallocng slot, given its start address. - [mallocng-slot-user](musl/mallocng-slot-user.md) - Dump information about a mallocng slot, given its user address. diff --git a/docs/commands/musl/mallocng-malloc-context.md b/docs/commands/musl/mallocng-malloc-context.md new file mode 100644 index 000000000..26beafff9 --- /dev/null +++ b/docs/commands/musl/mallocng-malloc-context.md @@ -0,0 +1,25 @@ + +# mallocng-malloc-context + +```text +usage: mallocng-malloc-context [-h] [address] + +``` + +Print out the mallocng __malloc_context (ctx) object. + +**Alias:** ng-ctx +### Positional arguments + +|Positional Argument|Help| +| :--- | :--- | +|address|Use the provided address instead of the one Pwndbg found.| + +### Optional arguments + +|Short|Long|Help| +| :--- | :--- | :--- | +|-h|--help|show this help message and exit| + + + diff --git a/docs/commands/musl/mallocng-meta-area.md b/docs/commands/musl/mallocng-meta-area.md new file mode 100644 index 000000000..fd79b9851 --- /dev/null +++ b/docs/commands/musl/mallocng-meta-area.md @@ -0,0 +1,25 @@ + +# mallocng-meta-area + +```text +usage: mallocng-meta-area [-h] address + +``` + +Print out a mallocng meta_area object at the given address. + +**Alias:** ng-metaarea +### Positional arguments + +|Positional Argument|Help| +| :--- | :--- | +|address|The address of the meta_area object.| + +### Optional arguments + +|Short|Long|Help| +| :--- | :--- | :--- | +|-h|--help|show this help message and exit| + + + diff --git a/pwndbg/aglib/heap/mallocng.py b/pwndbg/aglib/heap/mallocng.py index 568a9f412..f88826eec 100644 --- a/pwndbg/aglib/heap/mallocng.py +++ b/pwndbg/aglib/heap/mallocng.py @@ -22,9 +22,9 @@ import pwndbg.color.message as message # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L14 # Slot granularity. -UNIT = 16 +UNIT: int = 16 # Size of in-band metadata. -IB = 4 +IB: int = 4 # https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/malloc.c#L12 # Describes the possible sizes a slot can be. These are `/ UNIT`. @@ -47,7 +47,7 @@ class SlotState(Enum): # Shorthand -def int_size(): +def int_size() -> int: return pwndbg.aglib.typeinfo.sint.sizeof @@ -548,13 +548,11 @@ class Slot: sn3 = memory.u8(start - 3) if sn3 == 224: off = memory.u16(start - 2) - p = start + off * UNIT - obj = cls(p) + obj = cls(start + off * UNIT) obj._sn3 = sn3 else: # freed / avail slots will also go into this branch. - p = start - obj = cls(p) + obj = cls(start) obj._sn3 = obj._pn3 = sn3 obj._start = start @@ -784,7 +782,7 @@ class Meta: # Semi-custom methods.. @property - def stride(self): + def stride(self) -> int: """ Returns -1 if sizeclass >= len(size_classes). """ @@ -804,7 +802,7 @@ class Meta: # Custom methods.. @property - def cnt(self): + def cnt(self) -> int: """ Number of slots in the group. """ @@ -851,7 +849,7 @@ class Meta: return SlotState.ALLOCATED @staticmethod - def sizeof(): + def sizeof() -> int: return 2 * int_size() + 4 * pwndbg.aglib.arch.ptrsize @@ -878,7 +876,7 @@ class MetaArea: self.load() - def load(self): + def load(self) -> None: ptrsize = pwndbg.aglib.arch.ptrsize uint64size = pwndbg.aglib.typeinfo.uint64.sizeof endian = pwndbg.aglib.arch.endian @@ -909,6 +907,14 @@ class MetaArea: """ return self.slots + idx * Meta.sizeof() + @property + def area_size(self) -> int: + """ + Returns not the size of `struct meta_area` but rather + the size of the memory this object represents. + """ + return (self.slots - self.addr) + self.nslots * Meta.sizeof() + class MallocContext: """ @@ -965,7 +971,7 @@ class MallocContext: # evaluation. self.load() - def load(self): + def load(self) -> None: ptrsize = pwndbg.aglib.arch.ptrsize size_tsize = pwndbg.aglib.typeinfo.size_t.sizeof unsignedsize = pwndbg.aglib.typeinfo.uint.sizeof @@ -1059,7 +1065,7 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator): before you used the object. """ - def __init__(self): + def __init__(self) -> None: self.finished_init: bool = False self.ctx_addr: int = 0 @@ -1068,16 +1074,21 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator): self.secret: bytearray = b"" self.hope: bool = True - def init_if_needed(self): + def init_if_needed(self) -> bool: """ We want this class to be a singleton, but also we can't initialize it as soon as pwndbg is loaded. Users of the object are responsible for calling this to make sure the object is initialized. + + Returns: + True if this object is successfully initialized (whether + now or before). False otherswise. If this returns False + you may not use this object for heap operations. """ if self.finished_init: - return + return self.hope self.ctx_addr = 0 self.ctx = None @@ -1090,9 +1101,13 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator): if self.ctx_addr and self.hope: self.ctx = MallocContext(self.ctx_addr) - self.finished_init = True + # We will try to reinitialize again if we failed now. + if self.hope: + self.finished_init = True + + return self.hope - def set_ctx_addr(self): + def set_ctx_addr(self) -> None: """ Find where the __malloc_context global symbol is. Try using debug information, but if it isn't available try using a heuristic. @@ -1152,7 +1167,7 @@ class Mallocng(pwndbg.aglib.heap.heap.MemoryAllocator): return for addr, mapname in possible: - if mapname.contains("libc"): + if "libc" in mapname: self.ctx_addr = addr return diff --git a/pwndbg/commands/mallocng.py b/pwndbg/commands/mallocng.py index 4962ea721..c1cb0ebd6 100644 --- a/pwndbg/commands/mallocng.py +++ b/pwndbg/commands/mallocng.py @@ -554,8 +554,12 @@ def smart_dump_slot( # If it wasn't provided to us, let's try to search for it now. output += "Could not load valid meta from local information, searching the heap.. " - ng.init_if_needed() - gslot, fslot = ng.find_slot(slot.p, False, False) + + if not ng.init_if_needed(): + output += message.error("\nCouldn't find the allocator, aborting the search. ") + gslot, fslot = None, None + else: + gslot, fslot = ng.find_slot(slot.p, False, False) if gslot is None: output += "Not found.\n\n" @@ -581,6 +585,84 @@ def smart_dump_slot( return output +def dump_meta_area(meta_area: mallocng.MetaArea) -> str: + area_range = ( + "@ " + + C.memory.get(meta_area.addr) + + " - " + + C.memory.get(meta_area.addr + meta_area.area_size) + ) + + pp = PropertyPrinter() + + pp.start_section("meta_area", area_range) + pp.add( + [ + Property(name="check", value=meta_area.check), + Property(name="next", value=meta_area.next, is_addr=True), + Property(name="nslots", value=meta_area.nslots), + Property(name="slots", value=meta_area.slots, is_addr=True), + ] + ) + return pp.dump() + + +def dump_malloc_context(ctx: mallocng.MallocContext) -> str: + ctx_addr = "@ " + C.memory.get(ctx.addr) + + pp = PropertyPrinter(22) + pp.start_section("ctx", ctx_addr) + props = [ + Property(name="secret", value=ctx.secret), + ] + if ctx.has_pagesize_field: + props.append( + Property(name="pagesize", value=ctx.pagesize), + ) + + props.extend( + [ + Property(name="init_done", value=ctx.init_done), + Property(name="mmap_counter", value=ctx.mmap_counter), + Property(name="free_meta_head", value=ctx.free_meta_head, is_addr=True), + Property(name="avail_meta", value=ctx.avail_meta, is_addr=True), + Property(name="avail_meta_count", value=ctx.avail_meta_count), + Property(name="avail_meta_area_count", value=ctx.avail_meta_area_count), + Property(name="meta_alloc_shift", value=ctx.meta_alloc_shift), + Property(name="meta_area_head", value=ctx.meta_area_head, is_addr=True), + Property(name="meta_area_tail", value=ctx.meta_area_tail, is_addr=True), + Property(name="avail_meta_areas", value=ctx.avail_meta_areas, is_addr=True), + ] + ) + + for i in range(len(ctx.active)): + if ctx.active[i] != 0: + props.append(Property(name=f"active[{i}]", value=ctx.active[i], is_addr=True)) + + for i in range(len(ctx.usage_by_class)): + if ctx.usage_by_class[i] != 0: + props.append(Property(name=f"usage_by_class[{i}]", value=ctx.usage_by_class[i])) + + for i in range(len(ctx.unmap_seq)): + if ctx.unmap_seq[i] != 0: + props.append(Property(name=f"unmap_seq[{i}]", value=ctx.unmap_seq[i])) + + for i in range(len(ctx.bounces)): + if ctx.bounces[i] != 0: + props.append(Property(name=f"bounces[{i}]", value=ctx.bounces[i])) + + props.extend( + [ + Property(name="seq", value=ctx.seq), + Property(name="brk", value=ctx.brk, is_addr=True), + ] + ) + + pp.add(props) + + return pp.dump() + + parser = argparse.ArgumentParser( description=""" Dump information about a mallocng slot, given its user address. @@ -730,6 +812,77 @@ def mallocng_group(address: int) -> None: return +parser = argparse.ArgumentParser( + description=""" +Print out a mallocng meta_area object at the given address. + """, +) +parser.add_argument( + "address", + type=int, + help="The address of the meta_area object.", +) + + +@pwndbg.commands.Command( + parser, + category=CommandCategory.MUSL, + aliases=["ng-metaarea"], +) +@pwndbg.commands.OnlyWhenRunning +def mallocng_meta_area(address: int) -> None: + if not memory.is_readable_address(address): + print(message.error(f"Address {address:#x} not readable.")) + return + + try: + meta_area = mallocng.MetaArea(address) + print(dump_meta_area(meta_area), end="") + except pwndbg.dbg_mod.Error as e: + print(message.error(str(e))) + return + + +parser = argparse.ArgumentParser( + description=""" +Print out the mallocng __malloc_context (ctx) object. + """, +) +parser.add_argument( + "address", + nargs="?", + type=int, + help="Use the provided address instead of the one Pwndbg found.", +) + + +@pwndbg.commands.Command( + parser, + category=CommandCategory.MUSL, + aliases=["ng-ctx"], +) +@pwndbg.commands.OnlyWhenRunning +def mallocng_malloc_context(address: Optional[int] = None) -> None: + if address is None: + if not ng.init_if_needed(): + print(message.error("Couldn't find the allocator, aborting the command.")) + return + + ctx = ng.ctx + else: + if not memory.is_readable_address(address): + print(message.error(f"Address {address:#x} not readable.")) + return + + try: + ctx = mallocng.MallocContext(address) + except pwndbg.dbg_mod.Error as e: + print(message.error(str(e))) + return + + print(dump_malloc_context(ctx), end="") + + parser = argparse.ArgumentParser( description=""" Find slot which contains the given address. @@ -779,7 +932,9 @@ def mallocng_find( print(message.error(f"Address {hex(address)} not readable.")) return - ng.init_if_needed() + if not ng.init_if_needed(): + print(message.error("Couldn't find the allocator, aborting the command.")) + return grouped_slot, slot = ng.find_slot(address, metadata, shallow)