Skip to content

ptmalloc2_tracking ¤

Heap Tracking

This module implements runtime tracking of the heap, allowing pwndbg to detect heap related misbehavior coming from an inferior in real time, which lets us catch UAF bugs, double frees (and more), and report them to the user.

Approach¤

The approach used starting with using breakpoints to hook into the following libc symbols: malloc, free, calloc, and realloc. Each hook has a reference to a shared instance of the Tracker class, which is responsible for handling the tracking of the chunks of memory from the heap.

The tracker keeps two sorted maps of chunks, for freed and in use chunks, keyed by their base address. Newly allocated chunks are added to the map of in use chunks right before an allocating call returns, and newly freed chunks are moved from the map of in use chunks to the map of free ones right before a freeing call returns. The tracker is also responsible for installing watchpoints for free chunks when they're added to the free chunk map and deleting them when their corresponding chunks are removed from the map.

Additionally, because going through the data structures inside of libc to determine whether a chunk is free or not is, more often than not, a fairly slow operation, this module will only do so when it determines its view of the chunks has diverged from the one in libc in a way that would affect behavior. When such a diffence is detected, this module will rebuild the chunk maps in the range it determines to have been affected.

Currently, the way it does this is by deleting and querying from libc the new status of all chunks that overlap the region of a new allocation when it detects that allocation overlaps chunks it previously considered free.

This approach lets us avoid a lot of the following linked lists that comes with trying to answer the allocation status of a chunk, by keeping at hand as much known-good information as possible about them. Keep in mind that, although it is much faster than going to libc every time we need to know the allocation status of a chunk, this approach does have drawbacks when it comes to memory usage.

Compatibility¤

Currently module assumes the inferior is using GLibc.

There are points along the code in this module where the assumptions it makes are explicitly documented and checked to be valid for the current inferior, so that it may be immediately clear to the user that something has gone wrong if they happen to not be valid. However, be aware that there may be assumptions that were not made explicit.

Classes:

Functions:

Attributes:

LIBC_NAME module-attribute ¤

LIBC_NAME = 'libc.so.6'

MALLOC_NAME module-attribute ¤

MALLOC_NAME = 'malloc'

CALLOC_NAME module-attribute ¤

CALLOC_NAME = 'calloc'

REALLOC_NAME module-attribute ¤

REALLOC_NAME = 'realloc'

FREE_NAME module-attribute ¤

FREE_NAME = 'free'

last_issue module-attribute ¤

last_issue: str | None = None

PRINT_DEBUG module-attribute ¤

PRINT_DEBUG = False

DEFERED_DELETE module-attribute ¤

DEFERED_DELETE: list[Breakpoint] = []

malloc_enter module-attribute ¤

malloc_enter = None

calloc_enter module-attribute ¤

calloc_enter = None

realloc_enter module-attribute ¤

realloc_enter = None

free_enter module-attribute ¤

free_enter = None

stop_on_error module-attribute ¤

stop_on_error = True

FreeChunkWatchpoint ¤

FreeChunkWatchpoint(chunk: Chunk, tracker: Tracker)

Bases: Breakpoint

Methods:

Attributes:

chunk instance-attribute ¤

chunk = chunk

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop()

AllocChunkWatchpoint ¤

AllocChunkWatchpoint(chunk: Chunk)

Bases: Breakpoint

Methods:

Attributes:

chunk instance-attribute ¤

chunk = chunk

stop ¤

stop() -> bool

Chunk ¤

Chunk(address: int, size: int, requested_size: int, flags: int)

Attributes:

address instance-attribute ¤

address = address

size instance-attribute ¤

size = size

requested_size instance-attribute ¤

requested_size = requested_size

flags instance-attribute ¤

flags = flags

Tracker ¤

Tracker()

Methods:

Attributes:

free_chunks instance-attribute ¤

free_chunks: SortedDict[int, Chunk] = SortedDict()

alloc_chunks instance-attribute ¤

alloc_chunks: SortedDict[int, Chunk] = SortedDict()

free_watchpoints instance-attribute ¤

free_watchpoints: dict[int, FreeChunkWatchpoint] = {}

memory_management_calls instance-attribute ¤

memory_management_calls: dict[int, bool] = {}

is_performing_memory_management ¤

is_performing_memory_management()

enter_memory_management ¤

enter_memory_management(name: str) -> None

exit_memory_management ¤

exit_memory_management() -> None

malloc ¤

malloc(chunk: Chunk) -> None

free ¤

free(address: int) -> bool

MallocEnterBreakpoint ¤

MallocEnterBreakpoint(address, tracker)

Bases: Breakpoint

Methods:

Attributes:

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop() -> bool

CallocEnterBreakpoint ¤

CallocEnterBreakpoint(address, tracker)

Bases: Breakpoint

Methods:

Attributes:

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop() -> bool

AllocExitBreakpoint ¤

AllocExitBreakpoint(tracker, requested_size, name)

Bases: FinishBreakpoint

Methods:

Attributes:

requested_size instance-attribute ¤

requested_size = requested_size

tracker instance-attribute ¤

tracker = tracker

name instance-attribute ¤

name = name

stop ¤

stop() -> bool

out_of_scope ¤

out_of_scope() -> None

ReallocEnterBreakpoint ¤

ReallocEnterBreakpoint(address, tracker)

Bases: Breakpoint

Methods:

Attributes:

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop() -> bool

ReallocExitBreakpoint ¤

ReallocExitBreakpoint(tracker, freed_ptr, requested_size)

Bases: FinishBreakpoint

Methods:

Attributes:

freed_ptr instance-attribute ¤

freed_ptr = freed_ptr

requested_size instance-attribute ¤

requested_size = requested_size

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop()

out_of_scope ¤

out_of_scope() -> None

FreeEnterBreakpoint ¤

FreeEnterBreakpoint(address, tracker)

Bases: Breakpoint

Methods:

Attributes:

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop() -> bool

FreeExitBreakpoint ¤

FreeExitBreakpoint(tracker, ptr)

Bases: FinishBreakpoint

Methods:

Attributes:

ptr instance-attribute ¤

ptr = ptr

tracker instance-attribute ¤

tracker = tracker

stop ¤

stop()

out_of_scope ¤

out_of_scope() -> None

is_enabled ¤

is_enabled() -> bool

Whether the heap tracker in enabled.

resolve_address ¤

resolve_address(name: str) -> int | None

Checks whether a given symbol is available and part of libc, and returns its address.

get_chunk ¤

get_chunk(address, requested_size)

Reads a chunk from a given address.

in_program_code_stack ¤

in_program_code_stack() -> bool

install ¤

install(disable_hardware_watchpoints=True) -> None

uninstall ¤

uninstall() -> None