Revert "mallocng: print information about slot, group, meta at given address …" (#3115)

This reverts commit 169f937326.
pull/3118/head
patryk4815 6 months ago committed by GitHub
parent 169f937326
commit db27d18ee4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -256,9 +256,6 @@
## musl
- [mallocng-explain](musl/mallocng-explain.md) - Gives a quick explanation of musl's mallocng allocator.
- [mallocng-group](musl/mallocng-group.md) - Print out information about a mallocng group 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-user](musl/mallocng-slot-user.md) - Dump information about a mallocng slot, given its user address.
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -1,25 +0,0 @@
<!-- THIS PART OF THIS FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate-docs.sh -->
# mallocng-group
```text
usage: mallocng-group [-h] address
```
Print out information about a mallocng group at the given address.
**Alias:** ng-group
### Positional arguments
|Positional Argument|Help|
| :--- | :--- |
|address|The address of the group object.|
### Optional arguments
|Short|Long|Help|
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -1,25 +0,0 @@
<!-- THIS PART OF THIS FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate-docs.sh -->
# mallocng-meta
```text
usage: mallocng-meta [-h] address
```
Print out information about a mallocng group given the address of its meta.
**Alias:** ng-meta
### Positional arguments
|Positional Argument|Help|
| :--- | :--- |
|address|The address of the meta object.|
### Optional arguments
|Short|Long|Help|
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -1,26 +0,0 @@
<!-- THIS PART OF THIS FILE IS AUTOGENERATED. DO NOT MODIFY IT. See scripts/generate-docs.sh -->
# mallocng-slot-user
```text
usage: mallocng-slot-user [-h] [-a] address
```
Dump information about a mallocng slot, given its user address.
**Alias:** ng-slotu
### Positional arguments
|Positional Argument|Help|
| :--- | :--- |
|address|The start of user memory. Referred to as `p` in the source.|
### Optional arguments
|Short|Long|Help|
| :--- | :--- | :--- |
|-h|--help|show this help message and exit|
|-a|--all|Print out all information. Including meta and group data.|
<!-- END OF AUTOGENERATED PART. Do not modify this line or the line below, they mark the end of the auto-generated part of the file. If you want to extend the documentation in a way which cannot easily be done by adding to the command help description, write below the following line. -->
<!-- ------------\>8---- ----\>8---- ----\>8------------ -->

@ -1,9 +1,29 @@
from __future__ import annotations
from typing import Any
class MemoryAllocator:
"""Heap abstraction layer."""
# This function isn't actually implemented anywhere. It originally returned
# `gdb.Breakpoint`, but, in order to facilitate the port to aglib, that
# type association was removed. It should be put back as soon as the
# Debugger-agnostic API gains the ability to set breakpoints.
#
# TODO: Change `Any` to the Debugger-agnostic breakpoint type when it gets created
def summarize(self, address: int, **kwargs: Any) -> str:
"""Returns a textual summary of the specified address.
Arguments:
address: Address of the heap block to summarize.
Returns:
A string.
"""
raise NotImplementedError()
def containing(self, address: int) -> int:
"""Returns the address of the allocation which contains 'address'.

@ -1,537 +0,0 @@
"""
Implements handling of musl's allocator mallocng.
https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng
"""
from __future__ import annotations
from typing import List
import pwndbg
import pwndbg.aglib.arch
import pwndbg.aglib.memory as memory
import pwndbg.aglib.typeinfo
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L14
# Slot granularity.
UNIT = 16
# Size of in-band metadata.
IB = 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`.
# fmt: off
size_classes: List[int] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 18, 20,
25, 31, 36, 42, 50, 63, 72, 84, 102, 127, 146,
170, 204, 255, 292, 340, 409, 511, 584, 682, 818,
1023, 1169, 1364, 1637, 2047, 2340, 2730, 3276,
4095, 4680, 5460, 6552, 8191,
]
# fmt: on
# Shorthand
def int_size():
return pwndbg.aglib.typeinfo.sint.sizeof
class Group:
"""
A group is an array of slots.
https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L17
struct group {
struct meta *meta;
unsigned char active_idx:5;
char pad[UNIT - sizeof(struct meta *) - 1];
unsigned char storage[];
};
"""
def __init__(self, addr: int) -> None:
self.addr = addr
self._meta = None
self._active_idx = None
def preload(self) -> None:
"""
Read all the necessary process memory to populate the group's
fields.
Do this if you know you will be using most of the
fields of the group. It will be faster, since we can do one
reads instead of two small ones. You may also catch
inaccessible memory exceptions here and not worry about it later.
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
data = memory.read(self.addr, pwndbg.aglib.arch.ptrsize + 1)
self._meta = Meta(pwndbg.aglib.arch.unpack(data[: pwndbg.aglib.arch.ptrsize]))
self._active_idx = data[-1] & 0b11111
@property
def meta(self) -> Meta:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._meta is None:
self._meta = Meta(memory.read_pointer_width(self.addr))
return self._meta
@property
def active_idx(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._active_idx is None:
self._active_idx = memory.u8(self.addr + pwndbg.aglib.arch.ptrsize) & 0b11111
return self._active_idx
@property
def storage(self) -> int:
return self.addr + UNIT
@property
def group_size(self) -> int:
"""
The size of this group, in bytes.
Raises:
pwndbg.dbg_mod.Error: When reading meta fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/malloc.c#L234
return self.meta.stride * self.meta.cnt + UNIT
class Slot:
"""
The "unit of allocation" (analogous to glibc's "chunk").
There is no struct in the source code that describes it.
"""
def __init__(self, p: int) -> None:
# The start of user memory. It may
# not be the actual start of the slot.
self.p: int = p
self._offset: int = None
self._idx: int = None
# Not exactly sure what this is.
self._check4: int = None
self._group: Group = None
self._meta: Meta = None
self._reserved: int = None
def preload(self) -> None:
"""
Read all the necessary process memory to populate the slot's
fields.
Do this if you know you will be using most of the
fields of the slot. It will be faster, since we can do a few
big reads instead of many small ones. You may also catch
inaccessible memory exceptions here and not worry about it later.
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
# Read all the in-band data.
inband_data = memory.read(self.p - 8, 8)
self._check4 = inband_data[4]
if self._check4:
self._offset = int.from_bytes(inband_data[0:4], pwndbg.aglib.arch.endian, signed=False)
else:
self._offset = int.from_bytes(inband_data[6:8], pwndbg.aglib.arch.endian, signed=False)
idxv = inband_data[5]
if idxv != 255:
self._idx = idxv & 31
else:
self._idx = 0
# Read the group's meta pointer.
_ = self.meta
# Need this loaded for lots of fields,
# but we will let it be since we want to be able to
# say stuff about this slot even with a corrupt meta.
# _ = self.meta.stride
self._reserved = inband_data[5] >> 5
if self._reserved == 5:
# self.end doesn't need a read.
self._reserved = memory.u32(self.end - 4)
# All the other fields are calculated without
# memory reads.
@property
def check4(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L134
if self._check4 is None:
self._check4 = memory.u8(self.p - 4)
return self._check4
@property
def offset(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L132
if self._offset is None:
if self.check4:
# assert(!offset);
self._offest = memory.u32(self.p - 8)
# assert(offset > 0xffff);
else:
self._offset = memory.u16(self.p - 2)
return self._offset
@property
def idx(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L133
if self._idx is None:
v = memory.u8(self.p - 3)
if v != 255:
self._idx = v & 31
else:
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/donate.c#L29
self._idx = 0
return self._idx
@property
def group(self) -> Group:
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L139
if self._group is None:
self._group = Group(self.p - UNIT * self.offset - UNIT)
return self._group
@property
def meta(self) -> Meta:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L140
if self._meta is None:
self._meta = Meta(memory.read_pointer_width(self.group.addr))
return self._meta
@property
def start(self) -> int:
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/free.c#L108
return self.group.storage + self.meta.stride * self.idx
@property
def end(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading meta fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/free.c#L109
return self.start + self.meta.stride - IB
@property
def reserved(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L161
# Lots of asserts here..
if self._reserved is None:
self._reserved = memory.u8(self.p - 3) >> 5
if self._reserved == 5:
self._reserved = memory.u32(self.end - 4)
return self._reserved
@property
def nominal_size(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading meta fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L159
return self.end - self.reserved - self.p
@property
def user_size(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading meta fails.
"""
return self.nominal_size
@property
def slack(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading meta fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L199
return (self.meta.stride - self.nominal_size - IB) // UNIT
@property
def internal_offset(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading meta fails.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L204
# Not sure why musl saves it, it doesn't seem to use it.
# We can calculate it more easily than musl does:
return (self.p - self.start) // UNIT
class Meta:
"""
The metadata of a group.
https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L24
struct meta {
struct meta *prev, *next;
struct group *mem;
volatile int avail_mask, freed_mask;
uintptr_t last_idx:5;
uintptr_t freeable:1;
uintptr_t sizeclass:6;
uintptr_t maplen:8*sizeof(uintptr_t)-12;
};
"""
def __init__(self, addr: int) -> None:
self.addr: int = addr
self._prev: int = None
self._next: int = None
self._mem: int = None
self._avail_mask: int = None
self._freed_mask: int = None
self._last_idx: int = None
self._freeable: int = None
self._sizeclass: int = None
self._maplen: int = None
self._stride: int = None
def preload(self) -> None:
"""
Read all the necessary process memory to populate the meta's
fields.
Do this if you know you will be using most of the
fields of the meta. It will be faster, since we can do a one
big read instead of many small ones. You may also catch
inaccessible memory exceptions here and not worry about it later.
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
ptrsize = pwndbg.aglib.arch.ptrsize
endian = pwndbg.aglib.arch.endian
# Read the whole struct.
data = memory.read(self.addr, ptrsize * 3 + 2 * int_size() + 8 * ptrsize)
cur_offset = 0
self._prev = pwndbg.aglib.arch.unpack(data[cur_offset:ptrsize])
cur_offset += ptrsize
self._next = pwndbg.aglib.arch.unpack(data[cur_offset : (cur_offset + ptrsize)])
cur_offset += ptrsize
self._mem = pwndbg.aglib.arch.unpack(data[cur_offset : (cur_offset + ptrsize)])
cur_offset += ptrsize
self._avail_mask = int.from_bytes(
data[cur_offset : (cur_offset + int_size())], endian, signed=False
)
cur_offset += int_size()
self._freed_mask = int.from_bytes(
data[cur_offset : (cur_offset + int_size())], endian, signed=False
)
cur_offset += int_size()
# I think this is how I should read a bitfield.
# http://mjfrazer.org/mjfrazer/bitfields/
flags = int.from_bytes(data[cur_offset : (cur_offset + ptrsize)], endian, signed=False)
self._last_idx = flags & 0b11111
self._freeable = (flags >> 5) & 1
self._sizeclass = (flags >> 6) & 0b111111
self._maplen = flags >> 12
# All the other fields are calculated without
# memory reads.
@property
def prev(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._prev is None:
self._prev = memory.read_pointer_width(self.addr)
return self._prev
@property
def next(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._next is None:
self._next = memory.read_pointer_width(self.addr + pwndbg.aglib.arch.ptrsize)
return self._next
@property
def mem(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._mem is None:
self._mem = memory.read_pointer_width(self.addr + pwndbg.aglib.arch.ptrsize * 2)
return self._mem
@property
def avail_mask(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._avail_mask is None:
# While the type is technically a signed int, it makes more
# sense to interpret it as unsigned semantically.
self._avail_mask = memory.uint(self.addr + pwndbg.aglib.arch.ptrsize * 3)
return self._avail_mask
@property
def freed_mask(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._freed_mask is None:
offset = pwndbg.aglib.arch.ptrsize * 3 + int_size()
# Technically signed.
self._freed_mask = memory.uint(self.addr + offset)
return self._freed_mask
@property
def last_idx(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._last_idx is None:
offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2
# reading pointer width so it works regardless of endianness
self._last_idx = memory.read_pointer_width(self.addr + offset) & 0b11111
return self._last_idx
@property
def freeable(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._freeable is None:
offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2
self._freeable = (memory.read_pointer_width(self.addr + offset) >> 5) & 1
return self._freeable
@property
def sizeclass(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._sizeclass is None:
offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2
self._sizeclass = (memory.read_pointer_width(self.addr + offset) >> 6) & 0b111111
return self._sizeclass
@property
def maplen(self) -> int:
"""
Raises:
pwndbg.dbg_mod.Error: When reading memory fails.
"""
if self._maplen is None:
offset = pwndbg.aglib.arch.ptrsize * 3 + int_size() * 2
self._maplen = memory.read_pointer_width(self.addr + offset) >> 12
return self._maplen
@property
def stride(self):
"""
Returns -1 if sizeclass >= len(size_classes).
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L175
if self._stride is None:
if not self.last_idx and self.maplen:
self._stride = self.maplen * 4096 - UNIT
else:
if self.sizeclass < len(size_classes):
self._stride = UNIT * size_classes[self.sizeclass]
else:
# The meta is corrupted.
self._stride = -1
return self._stride
@property
def cnt(self):
"""
Number of slots in the group.
"""
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/free.c#L60
return self.last_idx + 1
@property
def slot_size(self):
"""
The size of a slot in this group, in bytes.
Returns -1 if sizeclass >= len(size_classes).
"""
if self.sizeclass < len(size_classes):
return size_classes[self.sizeclass] * UNIT
else:
# The meta is corrupted.
return -1
class MetaArea:
def __init__(self, addr: int) -> None:
self.addr = addr
class Mallocng:
pass

@ -269,14 +269,6 @@ def s64(addr: int) -> int:
return readtype(pwndbg.aglib.typeinfo.int64, addr)
def sint(addr: int) -> int:
"""
Read one `signed int` from the specified
address.
"""
return readtype(pwndbg.aglib.typeinfo.sint, addr)
def cast_pointer(
type: pwndbg.dbg_mod.Type, addr: int | pwndbg.dbg_mod.Value
) -> pwndbg.dbg_mod.Value:

@ -18,7 +18,6 @@ long: pwndbg.dbg_mod.Type
uchar: pwndbg.dbg_mod.Type
ushort: pwndbg.dbg_mod.Type
uint: pwndbg.dbg_mod.Type
sint: pwndbg.dbg_mod.Type
void: pwndbg.dbg_mod.Type
uint8: pwndbg.dbg_mod.Type
@ -62,7 +61,6 @@ def update() -> None:
module.uchar = lookup_types("unsigned char", "ubyte", "u8", "uint8")
module.ushort = lookup_types("unsigned short", "ushort", "u16", "uint16", "uint16_t")
module.uint = lookup_types("unsigned int", "uint", "u32", "uint32")
module.sint = lookup_types("signed int", "signed", "int")
module.void = lookup_types("void", "()")
module.uint8 = module.uchar

@ -4,17 +4,10 @@ Commands that help with debugging musl's allocator, mallocng.
from __future__ import annotations
import argparse
import pwndbg
import pwndbg.aglib.heap.mallocng as mallocng
import pwndbg.aglib.memory as memory
import pwndbg.aglib.typeinfo as typeinfo
import pwndbg.aglib.heap
import pwndbg.color as C
import pwndbg.color.message as message
from pwndbg.commands import CommandCategory
from pwndbg.lib.pretty_print import Property
from pwndbg.lib.pretty_print import PropertyPrinter
@pwndbg.commands.Command(
@ -151,322 +144,3 @@ def mallocng_explain() -> None:
# TODO: explain what a slot looks like.
print(txt)
def dump_group(group: mallocng.Group) -> str:
try:
# May fail on corrupt meta.
group_size = group.group_size
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Error while reading meta: {e}"))
print(C.bold("Cannot determine group size."))
group_size = -1
group_range = "@ " + C.memory.get(group.addr)
if group_size != -1:
group_range += " - " + C.memory.get(group.addr + group_size)
pp = PropertyPrinter()
pp.start_section("group", group_range)
pp.set_padding(2)
pp.add(
[
Property(name="meta", value=group.meta.addr, is_addr=True),
Property(name="active_idx", value=group.active_idx),
Property(name="storage", value=group.storage, is_addr=True, extra="start of slots"),
]
)
if group_size != -1:
pp.write("---\n")
pp.set_padding(3)
pp.add(
[
Property(name="group size", value=group_size),
]
)
pp.end_section()
return pp.dump()
def dump_meta(meta: mallocng.Meta) -> str:
int_size = str(typeinfo.sint.sizeof * 8)
avail_binary = "0b" + format(meta.avail_mask, f"0{int_size}b")
freed_binary = "0b" + format(meta.freed_mask, f"0{int_size}b")
pp = PropertyPrinter()
pp.start_section("meta", "@ " + C.memory.get(meta.addr))
pp.set_padding(2)
pp.add(
[
Property(name="prev", value=meta.prev, is_addr=True),
Property(name="next", value=meta.next, is_addr=True),
Property(name="mem", value=meta.mem, is_addr=True, extra="the group"),
Property(name="avail_mask", value=meta.avail_mask, extra=avail_binary),
Property(name="freed_mask", value=meta.freed_mask, extra=freed_binary),
Property(name="last_idx", value=meta.last_idx, extra="index of last slot"),
Property(name="freeable", value=str(bool(meta.freeable))),
Property(name="sizeclass", value=meta.sizeclass),
Property(name="maplen", value=meta.maplen),
]
)
pp.write("---\n")
pp.set_padding(3)
pp.add(
[
Property(name="cnt", value=meta.cnt, extra="the number of slots"),
Property(name="slot size", value=meta.slot_size, extra='aka "stride"'),
]
)
pp.end_section()
output = pp.dump()
if not meta.freeable:
# When mapped object files contain unused memory, they are donated
# to the heap. See https://elixir.bootlin.com/musl/v1.2.5/source/ldso/dynlink.c#L600
# and https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/donate.c#L36 .
# Only in this case is `meta.freeable = 0;`
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/donate.c#L25
output += C.bold("\nGroup donated by ld as unused part of ")
try:
mapping = pwndbg.aglib.vmmap.find(mallocng.Group(meta.mem).addr)
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Could not fetch parent group: {e}"))
mapping = None
if mapping is None:
output += C.red("<cannot determine>")
else:
output += C.bold(f'"{mapping.objfile}"')
output += C.bold(".\n")
elif not meta.last_idx and meta.maplen:
# https://elixir.bootlin.com/musl/v1.2.5/source/src/malloc/mallocng/meta.h#L177
output += C.bold("\nGroup allocated with mmap().\n")
else:
output += C.bold("\nGroup nested in slot of another group")
try:
parent_group = mallocng.Slot(mallocng.Group(meta.mem).addr).group.addr
output += " (" + C.memory.get(parent_group) + ")"
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Could not fetch parent group: {e}"))
output += C.bold(".\n")
return output
parser = argparse.ArgumentParser(
description="""
Dump information about a mallocng slot, given its user address.
""",
)
parser.add_argument(
"address",
type=int,
help="The start of user memory. Referred to as `p` in the source.",
)
parser.add_argument(
"-a",
"--all",
action="store_true",
help="Print out all information. Including meta and group data.",
)
@pwndbg.commands.Command(
parser,
category=CommandCategory.MUSL,
aliases=["ng-slotu"],
)
@pwndbg.commands.OnlyWhenRunning
def mallocng_slot_user(address: int, all: bool) -> None:
if not memory.is_readable_address(address):
print(message.error(f"Address {address:#x} not readable."))
return
slot = mallocng.Slot(address)
try:
slot.preload()
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Error while reading slot: {e}"))
return
read_success: bool = True
try:
slot.group.preload()
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Error while reading group: {e}"))
read_success = False
try:
slot.meta.preload()
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Error while reading meta: {e}"))
read_success = False
if not read_success:
print(message.info("Only showing partial information."))
all = False
pp = PropertyPrinter()
if not all:
pp.start_section("slab")
pp.set_padding(7)
if read_success:
pp.add(
[
Property(name="group", value=slot.group.addr, is_addr=True),
Property(name="meta", value=slot.meta.addr, is_addr=True),
]
)
else:
pp.add(
[
Property(name="group", value=slot.group.addr, is_addr=True),
]
)
pp.end_section()
if read_success:
pp.start_section("general")
pp.set_padding(2)
pp.add(
[
Property(name="start", value=slot.start, is_addr=True),
Property(name="user start", value=slot.p, is_addr=True, extra="aka `p`"),
Property(name="end", value=slot.end, is_addr=True, extra="start + stride - 4"),
Property(
name="stride", value=slot.meta.stride, extra="distance between adjacent slots"
),
Property(name="user size", value=slot.user_size, extra='aka "nominal size", `n`'),
Property(name="slack", value=slot.slack, extra="slot's unused memory / 0x10"),
]
)
pp.end_section()
pp.start_section("in-band")
pp.set_padding(4)
reserved_extra = ["end - p - n", ""]
if slot.reserved >= 5:
reserved_extra[1] = "located near slot end"
if slot.reserved == 6:
reserved_extra.append("this slot is a nested group")
else:
reserved_extra[1] = "located in slot header"
inband_group = [
Property(name="offset", value=slot.offset, extra="distance to first slot / 0x10"),
Property(name="index", value=slot.idx, extra="index of slot in its group"),
Property(name="reserved", value=slot.reserved, extra=reserved_extra),
]
if read_success:
# While it is technically saved in-band, there is no way
# for us to locate it without metadata.
inband_group.append(
Property(
name="rnd-off",
value=slot.internal_offset,
extra="prevents double free, (p - start) / 0x10",
),
)
pp.add(inband_group)
pp.end_section()
pp.print()
if all:
print(dump_group(slot.group), end="")
print(dump_meta(slot.meta), end="")
parser = argparse.ArgumentParser(
description="""
Print out information about a mallocng group given the address of its meta.
""",
)
parser.add_argument(
"address",
type=int,
help="The address of the meta object.",
)
@pwndbg.commands.Command(
parser,
category=CommandCategory.MUSL,
aliases=["ng-meta"],
)
@pwndbg.commands.OnlyWhenRunning
def mallocng_meta(address: int) -> None:
if not memory.is_readable_address(address):
print(message.error(f"Address {address:#x} not readable."))
return
meta = mallocng.Meta(address)
try:
meta.preload()
except pwndbg.dbg_mod.Error as e:
print(message.error(str(e)))
return
try:
group = mallocng.Group(meta.mem)
group.preload()
print(dump_group(group), end="")
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Failed loading group: {e}"))
print(dump_meta(meta), end="")
parser = argparse.ArgumentParser(
description="""
Print out information about a mallocng group at the given address.
""",
)
parser.add_argument(
"address",
type=int,
help="The address of the group object.",
)
@pwndbg.commands.Command(
parser,
category=CommandCategory.MUSL,
aliases=["ng-group"],
)
@pwndbg.commands.OnlyWhenRunning
def mallocng_group(address: int) -> None:
if not memory.is_readable_address(address):
print(message.error(f"Address {address:#x} not readable."))
return
group = mallocng.Group(address)
try:
group.preload()
except pwndbg.dbg_mod.Error as e:
print(message.error(str(e)))
return
print(dump_group(group), end="")
try:
meta = group.meta
meta.preload()
print(dump_meta(meta), end="")
except pwndbg.dbg_mod.Error as e:
print(message.error(f"Failed loading meta: {e}"))
return

@ -922,7 +922,7 @@ class Value:
def dereference(self) -> Value:
"""
If this is a pointer value, dereferences the pointer and returns a new
If this is a poitner value, dereferences the pointer and returns a new
instance of Value, containing the value pointed to by this pointer.
"""
raise NotImplementedError()

@ -20,7 +20,7 @@ class Property:
name: str
value: Any
extra: str | List[str] = ""
extra: str = ""
is_addr: bool = False
use_hex: bool = True
@ -61,54 +61,24 @@ class PropertyPrinter:
"""
Add a group of properties that should be aligned.
"""
# Transform prop values to string representation
for prop in prop_group:
if isinstance(prop.value, int):
if prop.use_hex:
prop.value = hex(prop.value)
else:
prop.value = str(prop.value)
# Get max lengths to calculate proper ljust
# + 1 to account for the ":"
max_name_len = max(len(prop.name) for prop in prop_group) + 1
# max_value_len = max(len(prop.value) for prop in prop_group)
# Use constant so it works between different groups
max_value_len = 16
indentation_str = self.indent_level * self.indent_size * " "
padding_str = self.padding * " "
name_pad_str = max_name_len * " "
val_pad_str = max_value_len * " "
extra_list_pad_str = indentation_str + name_pad_str + padding_str + val_pad_str
max_name_len = max(len(self.name_color_func(prop.name)) for prop in prop_group)
for prop in prop_group:
self.text += (
indentation_str
+ color.ljust_colored(self.name_color_func(prop.name) + ":", max_name_len)
+ padding_str
)
self.text += self.indent_level * self.indent_size * " "
colored_name = self.name_color_func(prop.name) + ":"
self.text += colored_name.ljust(max_name_len + 1, " ")
self.text += self.padding * " "
if prop.is_addr:
base = 16 if prop.use_hex else 10
colored_val = color.memory.get(int(prop.value, base))
self.text += color.memory.get(prop.value)
else:
colored_val = self.value_color_func(prop.value)
self.text += color.ljust_colored(colored_val, max_value_len)
if isinstance(prop.extra, str):
self.text += " " + prop.extra
if isinstance(prop.value, int) and prop.use_hex:
val = hex(prop.value)
else:
# list of strings, we want each one under the other
assert isinstance(prop.extra, list)
assert len(prop.extra) > 1
val = prop.value
self.text += self.value_color_func(val)
self.text += " " + prop.extra[0]
for i in range(1, len(prop.extra)):
self.text += "\n"
self.text += extra_list_pad_str
self.text += " " + prop.extra[i]
self.text += " " + prop.extra
self.text += "\n"
@ -122,7 +92,7 @@ class PropertyPrinter:
"""
Print the built up string.
"""
print(self.text, end="")
print(self.text)
def clear(self) -> None:
"""
@ -149,22 +119,14 @@ class PropertyPrinter:
"""
self.text += string
def start_section(self, title: str, preamble: str = "") -> None:
def start_section(self, title: str) -> None:
"""
Start a named section of properties that will have
increased indentation.
Don't forget to call end_section()!
"""
self.text += " " * self.indent_level * self.indent_size
self.text += self.section_color_func(title)
if preamble:
self.text += "\n"
self.text += " " * (self.indent_level + 1) * self.indent_size
self.text += preamble
self.text += "\n"
self.text += self.section_color_func(title) + "\n"
self.indent()
def end_section(self) -> None:

Loading…
Cancel
Save