diff --git a/.gitignore b/.gitignore index 529d61829..9ff2c8190 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ venv *.egg-info *.core .coverage +.idea diff --git a/docs/source/heap.rst b/docs/source/heap.rst new file mode 100644 index 000000000..956e62041 --- /dev/null +++ b/docs/source/heap.rst @@ -0,0 +1,18 @@ +.. testsetup:: * + + from pwnlib.heap import * + from pwnlib.tubes.process import process + + +:mod:`pwnlib.heap` --- Heap +=================================================== + +.. automodule:: pwnlib.heap + +Allocators +------------- +.. toctree:: + :maxdepth: 1 + :glob: + + heap/glmalloc diff --git a/docs/source/heap/glmalloc.rst b/docs/source/heap/glmalloc.rst new file mode 100644 index 000000000..a132d7f2c --- /dev/null +++ b/docs/source/heap/glmalloc.rst @@ -0,0 +1,23 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc import * + from pwnlib.tubes.process import process + + +:mod:`pwnlib.heap.glmalloc` --- glibc malloc +=================================================== + +Items +------------- +.. toctree:: + :maxdepth: 1 + :glob: + + glmalloc/* + + +Examples +------------ +.. automodule:: pwnlib.heap.glmalloc + + diff --git a/docs/source/heap/glmalloc/arena.rst b/docs/source/heap/glmalloc/arena.rst new file mode 100644 index 000000000..9ca8e344d --- /dev/null +++ b/docs/source/heap/glmalloc/arena.rst @@ -0,0 +1,17 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc.arena import * + +:mod:`pwnlib.heap.glmalloc.arena` --- Arena of libc +======================================================================== + +Arena +------- +.. autoclass:: pwnlib.heap.glmalloc.Arena() + :members: + +NoTcacheError +-------------- +.. autoclass:: pwnlib.heap.glmalloc.NoTcacheError() + :members: + :show-inheritance: diff --git a/docs/source/heap/glmalloc/bins.rst b/docs/source/heap/glmalloc/bins.rst new file mode 100644 index 000000000..3f7f16ec3 --- /dev/null +++ b/docs/source/heap/glmalloc/bins.rst @@ -0,0 +1,18 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc.bins import * + +:mod:`pwnlib.heap.glmalloc.bins` --- Bins of the libc +========================================================= + +This modules contains the classes used to represent the different +bins of the libc. + +Bins +----- +.. toctree:: + :maxdepth: 2 + :glob: + + bins/* + diff --git a/docs/source/heap/glmalloc/bins/bins.rst b/docs/source/heap/glmalloc/bins/bins.rst new file mode 100644 index 000000000..e8ac0fb7f --- /dev/null +++ b/docs/source/heap/glmalloc/bins/bins.rst @@ -0,0 +1,20 @@ +Bins (abstract classes) +========================== +Classes with generic behaviour that are inherit by the all +the bins types. + + +Bins +----- +.. autoclass:: pwnlib.heap.glmalloc.Bins() + :members: + +Bin +---- +.. autoclass:: pwnlib.heap.glmalloc.Bin() + :members: + +BinEntry +--------- +.. autoclass:: pwnlib.heap.glmalloc.BinEntry() + :members: diff --git a/docs/source/heap/glmalloc/bins/fastbins.rst b/docs/source/heap/glmalloc/bins/fastbins.rst new file mode 100644 index 000000000..c72e76445 --- /dev/null +++ b/docs/source/heap/glmalloc/bins/fastbins.rst @@ -0,0 +1,21 @@ +Fast bins +========== +Classes to represent the tcache bins of the libc. + +FastBins +--------- +.. autoclass:: pwnlib.heap.glmalloc.FastBins() + :members: + :show-inheritance: + +FastBin +-------- +.. autoclass:: pwnlib.heap.glmalloc.FastBin() + :members: + :show-inheritance: + +FastBinEntry +------------- +.. autoclass:: pwnlib.heap.glmalloc.FastBinEntry() + :members: + :show-inheritance: diff --git a/docs/source/heap/glmalloc/bins/largebins.rst b/docs/source/heap/glmalloc/bins/largebins.rst new file mode 100644 index 000000000..64a8a6d66 --- /dev/null +++ b/docs/source/heap/glmalloc/bins/largebins.rst @@ -0,0 +1,21 @@ +Large bins +=========== +Classes to represent the large bins of the libc. + +LargeBins +----------- +.. autoclass:: pwnlib.heap.glmalloc.LargeBins() + :members: + :show-inheritance: + +LargeBin +---------- +.. autoclass:: pwnlib.heap.glmalloc.LargeBin() + :members: + :show-inheritance: + +LargeBinEntry +--------------- +.. autoclass:: pwnlib.heap.glmalloc.LargeBinEntry() + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/heap/glmalloc/bins/smallbins.rst b/docs/source/heap/glmalloc/bins/smallbins.rst new file mode 100644 index 000000000..e4e02ea23 --- /dev/null +++ b/docs/source/heap/glmalloc/bins/smallbins.rst @@ -0,0 +1,21 @@ +Small bins +=========== +Classes to represent the small bins of the libc. + +SmallBins +----------- +.. autoclass:: pwnlib.heap.glmalloc.SmallBins() + :members: + :show-inheritance: + +SmallBin +---------- +.. autoclass:: pwnlib.heap.glmalloc.SmallBin() + :members: + :show-inheritance: + +SmallBinEntry +-------------- +.. autoclass:: pwnlib.heap.glmalloc.SmallBinEntry() + :members: + :show-inheritance: diff --git a/docs/source/heap/glmalloc/bins/tcaches.rst b/docs/source/heap/glmalloc/bins/tcaches.rst new file mode 100644 index 000000000..f37a27e09 --- /dev/null +++ b/docs/source/heap/glmalloc/bins/tcaches.rst @@ -0,0 +1,23 @@ +Tcaches +======== +Classes to represent the tcache bins of the libc. Tcaches were +incorporated in libc 2.26, therefore this classes are only +used if the current libc version of the process is at least 2.26. + +Tcaches +-------- +.. autoclass:: pwnlib.heap.glmalloc.Tcaches() + :members: + :show-inheritance: + +Tcache +------- +.. autoclass:: pwnlib.heap.glmalloc.Tcache() + :members: + :show-inheritance: + +TcacheEntry +------------ +.. autoclass:: pwnlib.heap.glmalloc.TcacheEntry() + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/heap/glmalloc/bins/unsortedbin.rst b/docs/source/heap/glmalloc/bins/unsortedbin.rst new file mode 100644 index 000000000..e204be912 --- /dev/null +++ b/docs/source/heap/glmalloc/bins/unsortedbin.rst @@ -0,0 +1,22 @@ +Unsorted bin +============== +Classes to represent the unsorted bin of the libc. + +UnsortedBins +-------------- +.. autoclass:: pwnlib.heap.glmalloc.UnsortedBins() + :members: + :show-inheritance: + +UnsortedBin +------------- +.. autoclass:: pwnlib.heap.glmalloc.UnsortedBin() + :members: + :show-inheritance: + +UnsortedBinEntry +------------------ +.. autoclass:: pwnlib.heap.glmalloc.UnsortedBinEntry() + :members: + :show-inheritance: + diff --git a/docs/source/heap/glmalloc/heap.rst b/docs/source/heap/glmalloc/heap.rst new file mode 100644 index 000000000..b504b8167 --- /dev/null +++ b/docs/source/heap/glmalloc/heap.rst @@ -0,0 +1,16 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc.heap import * + +:mod:`pwnlib.heap.glmalloc.heap` --- Heap managed by the libc +======================================================================== + +Heap +------ +.. autoclass:: pwnlib.heap.glmalloc.Heap() + :members: + +HeapError +----------- +.. autoclass:: pwnlib.heap.glmalloc.HeapError() + :members: \ No newline at end of file diff --git a/docs/source/heap/glmalloc/heap_explorer.rst b/docs/source/heap/glmalloc/heap_explorer.rst new file mode 100644 index 000000000..8bfb73a1a --- /dev/null +++ b/docs/source/heap/glmalloc/heap_explorer.rst @@ -0,0 +1,13 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc import * + from pwnlib.tubes.process import process + from pwnlib.elf.corefile import Corefile + +Heap Explorer +=============== + +HeapExplorer +-------------- +.. autoclass:: pwnlib.heap.glmalloc.HeapExplorer() + :members: \ No newline at end of file diff --git a/docs/source/heap/glmalloc/malloc_chunk.rst b/docs/source/heap/glmalloc/malloc_chunk.rst new file mode 100644 index 000000000..69478e3eb --- /dev/null +++ b/docs/source/heap/glmalloc/malloc_chunk.rst @@ -0,0 +1,11 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc.malloc_chunk import * + +:mod:`pwnlib.heap.glmalloc.malloc_chunk` --- Chunks with metadata managed by libc +=================================================================================== + +MallocChunk +--------------- +.. autoclass:: pwnlib.heap.glmalloc.MallocChunk() + :members: diff --git a/docs/source/heap/glmalloc/malloc_state.rst b/docs/source/heap/glmalloc/malloc_state.rst new file mode 100644 index 000000000..9ab2556d6 --- /dev/null +++ b/docs/source/heap/glmalloc/malloc_state.rst @@ -0,0 +1,24 @@ +.. testsetup:: * + + from pwnlib.heap.glmalloc.malloc_state import * + +:mod:`pwnlib.heap.glmalloc.malloc_state` --- Malloc state of each arena of the libc +===================================================================================== + + +MallocState +------------ +.. autoclass:: pwnlib.heap.glmalloc.MallocState() + :members: + + +FastBinsY +----------- +.. autoclass:: pwnlib.heap.glmalloc.malloc_state.FastBinsY() + :members: + +Bins +------ +.. autoclass:: pwnlib.heap.glmalloc.malloc_state.Bins() + :members: + diff --git a/docs/source/index.rst b/docs/source/index.rst index 50b99bc14..2bd659178 100755 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -58,6 +58,7 @@ Each of the ``pwntools`` modules is documented here. flag fmtstr gdb + heap libcdb log memleak diff --git a/pwn/toplevel.py b/pwn/toplevel.py index adbcb7319..61cb7a1ca 100644 --- a/pwn/toplevel.py +++ b/pwn/toplevel.py @@ -65,6 +65,7 @@ from pwnlib.util.sh_string import sh_string, sh_prepare, sh_command_with from pwnlib.util.splash import * from pwnlib.util.web import * +from pwnlib.heap import * # Promote these modules, so that "from pwn import *" will let you access them diff --git a/pwnlib/__init__.py b/pwnlib/__init__.py index 4f261d240..f99dcf4fc 100644 --- a/pwnlib/__init__.py +++ b/pwnlib/__init__.py @@ -37,6 +37,7 @@ 'util', 'adb', 'update', + 'heap' ] for module in __all__: diff --git a/pwnlib/data/__init__.py b/pwnlib/data/__init__.py index 4c2d57739..6c55d59ad 100644 --- a/pwnlib/data/__init__.py +++ b/pwnlib/data/__init__.py @@ -4,6 +4,7 @@ # These files are not distributed with Pwntools, but # are in the source tree and used for testing. from pwnlib.data import elf + from pwnlib.data import heap except ImportError: pass diff --git a/pwnlib/data/heap/__init__.py b/pwnlib/data/heap/__init__.py new file mode 100644 index 000000000..b6b4f401e --- /dev/null +++ b/pwnlib/data/heap/__init__.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import +from pwnlib.data.heap import x86_64 + +import os +path = os.path.dirname(__file__) + +def get(x): + return os.path.join(path, x) \ No newline at end of file diff --git a/pwnlib/data/heap/x86_64/__init__.py b/pwnlib/data/heap/x86_64/__init__.py new file mode 100644 index 000000000..47d5c4b91 --- /dev/null +++ b/pwnlib/data/heap/x86_64/__init__.py @@ -0,0 +1,5 @@ +import os +path = os.path.dirname(__file__) + +def get(x): + return os.path.join(path, x) diff --git a/pwnlib/data/heap/x86_64/core.23.fast_bins1 b/pwnlib/data/heap/x86_64/core.23.fast_bins1 new file mode 100644 index 000000000..01f66dd1c Binary files /dev/null and b/pwnlib/data/heap/x86_64/core.23.fast_bins1 differ diff --git a/pwnlib/data/heap/x86_64/core.32.large_bins1 b/pwnlib/data/heap/x86_64/core.32.large_bins1 new file mode 100644 index 000000000..b02334692 Binary files /dev/null and b/pwnlib/data/heap/x86_64/core.32.large_bins1 differ diff --git a/pwnlib/data/heap/x86_64/core.32.sample1 b/pwnlib/data/heap/x86_64/core.32.sample1 new file mode 100644 index 000000000..f34ccbcdb Binary files /dev/null and b/pwnlib/data/heap/x86_64/core.32.sample1 differ diff --git a/pwnlib/data/heap/x86_64/core.32.small_bins1 b/pwnlib/data/heap/x86_64/core.32.small_bins1 new file mode 100644 index 000000000..9d229023c Binary files /dev/null and b/pwnlib/data/heap/x86_64/core.32.small_bins1 differ diff --git a/pwnlib/data/heap/x86_64/core.32.tcaches1 b/pwnlib/data/heap/x86_64/core.32.tcaches1 new file mode 100644 index 000000000..721af02ff Binary files /dev/null and b/pwnlib/data/heap/x86_64/core.32.tcaches1 differ diff --git a/pwnlib/data/heap/x86_64/core.32.unsorted_bins1 b/pwnlib/data/heap/x86_64/core.32.unsorted_bins1 new file mode 100644 index 000000000..2d3764818 Binary files /dev/null and b/pwnlib/data/heap/x86_64/core.32.unsorted_bins1 differ diff --git a/pwnlib/data/heap/x86_64/libc-2.23.so b/pwnlib/data/heap/x86_64/libc-2.23.so new file mode 100755 index 000000000..2da3fb6b0 Binary files /dev/null and b/pwnlib/data/heap/x86_64/libc-2.23.so differ diff --git a/pwnlib/data/heap/x86_64/libc-2.32.so b/pwnlib/data/heap/x86_64/libc-2.32.so new file mode 100755 index 000000000..e9e78c34f Binary files /dev/null and b/pwnlib/data/heap/x86_64/libc-2.32.so differ diff --git a/pwnlib/elf/corefile.py b/pwnlib/elf/corefile.py index 6f1667226..8f68d5f93 100644 --- a/pwnlib/elf/corefile.py +++ b/pwnlib/elf/corefile.py @@ -96,6 +96,8 @@ from pwnlib.util.misc import write from pwnlib.util.packing import pack from pwnlib.util.packing import unpack_many +from pwnlib.heap import HeapExplorer +from pwnlib.heap import CoreFileInformer log = getLogger(__name__) @@ -1132,6 +1134,60 @@ def __getattr__(self, attribute): def _populate_got(*a): pass def _populate_plt(*a): pass + def heap_explorer(self, tcache=None, safe_link=None, libc_path="", libc_version=None): + """Returns a heap explorer that allows to inspect the items of the libc + heap. + + Arguments: + tcache(bool): Indicate if tcache is present + safe_link (bool): Indicate if safe-link is present in tcaches and + fastbisn pointers + libc_path (str): Indicate the path to retrieve the libc in case + the path specified in corefile is incorrect (e.g corefile + moved to another machine for analysis) + libc_version (tuple(int, int)): The glibc version in format + (major, minor). To use in case libc version is not + automatically detected. + + Raises: + PwnlibException: In case the libc is not found in the corefile + + Examples: + >>> c = CoreFile('./core') # doctest: +SKIP + >>> hp = c.heap_explorer() # doctest: +SKIP + >>> heap = hp.heap() # doctest: +SKIP + >>> len(heap.chunks) # doctest: +SKIP + 574 + >>> fast_bins = hp.fast_bins() # doctest: +SKIP + >>> print(fast_bins) # doctest: +SKIP + ================================== Fast Bins ================================== + [4] Fast Bin 0x60 (2) => Chunk(0x555635100c20 0x60 PREV_IN_USE) => Chunk(0x55563 + 5100ba0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + + + Returns: + HeapExplorer + """ + libc_map = self.libc + if libc_map is None: + self.error( + "Unable to find the libc library in corefile {}".format( + self.path + ) + ) + + libc_path = libc_path or libc_map.path + libc = ELF(libc_path, checksec=False) + libc.address = libc_map.address + + corefile_informer = CoreFileInformer(self, libc, libc_version=libc_version) + return HeapExplorer( + corefile_informer, + use_tcache=tcache, + safe_link=safe_link, + ) + class Core(Corefile): """Alias for :class:`.Corefile`""" diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py index 8c5694553..fe0b0ef7a 100644 --- a/pwnlib/elf/elf.py +++ b/pwnlib/elf/elf.py @@ -443,6 +443,24 @@ def debug(self, argv=[], *a, **kw): import pwnlib.gdb return pwnlib.gdb.debug([self.path] + argv, *a, **kw) + def describe(self): + """Prints the several information related with the binary protection + measures as well as the architecture. + + .. code-block:: python + + >>> e = ELF("/bin/ls", checksec=False) + >>> e.describe() + [*] '/bin/ls' + Arch: amd64-64-little + RELRO: Partial RELRO + Stack: Canary found + NX: NX enabled + PIE: PIE enabled + FORTIFY: Enabled + """ + return self._describe() + def _describe(self, *a, **kw): log.info_once( '%s\n%-10s%s-%s-%s\n%s', diff --git a/pwnlib/heap/__init__.py b/pwnlib/heap/__init__.py new file mode 100644 index 000000000..92e2770ad --- /dev/null +++ b/pwnlib/heap/__init__.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +""" + +This is the module for exploring the heap. + +""" +from pwnlib.heap.glmalloc.heap_explorer import HeapExplorer +from pwnlib.heap.glmalloc.arena import Arena +from pwnlib.heap.glmalloc.malloc_chunk import MallocChunk +from pwnlib.heap.glmalloc.malloc_state import MallocState +from pwnlib.heap.glmalloc.heap import Heap, HeapError +from pwnlib.heap.glmalloc.bins import \ + Tcaches, \ + Tcache, \ + TcacheEntry, \ + NoTcacheError, \ + FastBins, \ + FastBin, \ + FastBinEntry, \ + UnsortedBins, \ + UnsortedBin,\ + UnsortedBinEntry, \ + SmallBins, \ + SmallBin, \ + SmallBinEntry, \ + LargeBins, \ + LargeBin, \ + LargeBinEntry +from pwnlib.heap.glmalloc.process_informer import ProcessInformer, CoreFileInformer + +__all__ = [ + 'HeapExplorer', + 'Arena', + 'MallocChunk', + 'MallocState', + 'Heap', 'HeapError', + 'Tcaches', 'Tcache', 'TcacheEntry', 'NoTcacheError', + 'FastBins', 'FastBin', 'FastBinEntry', + 'UnsortedBins', 'UnsortedBin', 'UnsortedBinEntry', + 'SmallBins', 'SmallBin', 'SmallBinEntry', + 'LargeBins', 'LargeBin', 'LargeBinEntry', + 'ProcessInformer', 'CoreFileInformer' +] diff --git a/pwnlib/heap/glmalloc/__init__.py b/pwnlib/heap/glmalloc/__init__.py new file mode 100644 index 000000000..c58e275e3 --- /dev/null +++ b/pwnlib/heap/glmalloc/__init__.py @@ -0,0 +1,375 @@ +# -*- coding: utf-8 -*- +""" +During heap exploit development, it is frequently useful to obtain an +image of the heap layout as well as of the bins used by the glibc. + + +To provide access to the heap items, an :class:`HeapExplorer` has to be used, +which can be obtained from :attr:`pwnlib.tubes.process.heap_explorer` + +Examples +---------- + +Get a summary of the items of the arena: + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> print(hp.arena().summary()) + ========================== Arena ========================== + - Malloc State (0x7fad3f0a5c40) + top = 0x564c90e90f20 + last_remainder = 0x0 + next = 0x7fad3f0a5c40 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x564c90e83000) + chunks_count = 0x245 + top: addr = 0x564c90e90f20, size = 0x130e0 + - Tcaches + [23] 0x188 (1) + [41] 0x2a8 (1) + - Fast bins + [-] No chunks found + - Unsorted bins + [-] No chunks found + - Small bins + [-] No chunks found + - Large bins + [-] No chunks found + =========================================================== + +View the malloc state: + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> print(hp.malloc_state()) + ======================== Malloc State (0x7f97053fbc40) ======================== + mutex = 0x0 + flags = 0x1 + have_fastchunks = 0x0 + fastbinsY + [0] 0x20 => 0x0 + [1] 0x30 => 0x0 + [2] 0x40 => 0x0 + [3] 0x50 => 0x0 + [4] 0x60 => 0x55c4669fdc20 + [5] 0x70 => 0x0 + [6] 0x80 => 0x0 + [7] 0x90 => 0x0 + [8] 0xa0 => 0x0 + [9] 0xb0 => 0x0 + top = 0x55c4669ff6e0 + last_remainder = 0x0 + bins + Unsorted bins + [0] fd=0x55c4669fed40 bk=0x55c4669fdca0 + Small bins + [1] 0x20 fd=0x7f97053fbcb0 bk=0x7f97053fbcb0 + [2] 0x30 fd=0x7f97053fbcc0 bk=0x7f97053fbcc0 + ....... + [61] 0x3e0 fd=0x7f97053fc070 bk=0x7f97053fc070 + [62] 0x3f0 fd=0x7f97053fc080 bk=0x7f97053fc080 + Large bins + [63] 0x400 fd=0x7f97053fc090 bk=0x7f97053fc090 + [64] 0x440 fd=0x7f97053fc0a0 bk=0x7f97053fc0a0 + ...... + [125] 0x80000 fd=0x7f97053fc470 bk=0x7f97053fc470 + [126] 0x100000 fd=0x7f97053fc480 bk=0x7f97053fc480 + binmap = [0x0, 0x0, 0x0, 0x0] + next = 0x7f96f8000020 + next_free = 0x0 + attached_threads = 0x1 + system_mem = 0x21000 + max_system_mem = 0x21000 + ================================================================================ + + +List the chunks of the bins: + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> print(hp.tcaches()) + =================================== Tcaches =================================== + [23] Tcache 0x188 (1) => Chunk(0x56383e3c0250 0x190 PREV_IN_USE) => 0x0 + [41] Tcache 0x2a8 (1) => Chunk(0x56383e3bffa0 0x2b0 PREV_IN_USE) => 0x0 + ================================================================================ + >>> print(hp.fast_bins()) + ================================== Fast Bins ================================== + [4] Fast Bin 0x60 (2) => Chunk(0x555635100c20 0x60 PREV_IN_USE) => Chunk(0x55563 + 5100ba0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + >>> print(hp.unsorted_bin()) + ================================ Unsorted Bins ================================ + [0] Unsorted Bin (2) => Chunk(0x555635101d40 0x910 PREV_IN_USE) => Chunk(0x55563 + 5100ca0 0x1010 PREV_IN_USE) => 0x7f8bd66e9ca0 + ================================================================================ + >>> print(hp.small_bins()) + ================================== Small Bins ================================== + [-] No chunks found + ================================================================================ + >>> print(hp.large_bins()) + ================================== Large Bins ================================== + [-] No chunks found + ================================================================================ + + + +List the chunks of the arena heap: + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> print(hp.heap()) + ============================ Heap (0x555635100000) ============================ + 0x555635100000 0x250 PREV_IN_USE + 00 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635100250 0x410 PREV_IN_USE + 61 61 61 0a 0a 20 76 65 72 73 69 6f 6e 20 3d 20 aaa.. version = + 0x555635100660 0x120 PREV_IN_USE + 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635100780 0x120 PREV_IN_USE + 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5556351008a0 0x60 PREV_IN_USE + 00 00 00 00 00 00 00 00 10 00 10 35 56 55 00 00 ...........5VU.. + 0x555635100900 0x60 PREV_IN_USE + b0 08 10 35 56 55 00 00 10 00 10 35 56 55 00 00 ...5VU.....5VU.. + 0x555635100960 0x60 PREV_IN_USE + 10 09 10 35 56 55 00 00 10 00 10 35 56 55 00 00 ...5VU.....5VU.. + 0x5556351009c0 0x60 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635100a20 0x60 PREV_IN_USE + 70 09 10 35 56 55 00 00 10 00 10 35 56 55 00 00 p..5VU.....5VU.. + 0x555635100a80 0x60 PREV_IN_USE + 30 0a 10 35 56 55 00 00 10 00 10 35 56 55 00 00 0..5VU.....5VU.. + 0x555635100ae0 0x60 PREV_IN_USE + 90 0a 10 35 56 55 00 00 10 00 10 35 56 55 00 00 ...5VU.....5VU.. + 0x555635100b40 0x60 PREV_IN_USE + f0 0a 10 35 56 55 00 00 10 00 10 35 56 55 00 00 ...5VU.....5VU.. + 0x555635100ba0 0x60 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635100c00 0x20 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635100c20 0x60 PREV_IN_USE + a0 0b 10 35 56 55 00 00 00 00 00 00 00 00 00 00 ...5VU.......... + 0x555635100c80 0x20 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635100ca0 0x1010 PREV_IN_USE + a0 9c 6e d6 8b 7f 00 00 40 1d 10 35 56 55 00 00 ..n.....@..5VU.. + 0x555635101cb0 0x90 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x555635101d40 0x910 PREV_IN_USE + a0 0c 10 35 56 55 00 00 a0 9c 6e d6 8b 7f 00 00 ...5VU....n..... + 0x555635102650 0x90 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5556351026e0 0x1e920 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + ================================================================================ + +Get all the arena information: + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> print(hp.arena()) + ++++++++++++++++++++++++++++++++++++ Arena ++++++++++++++++++++++++++++++++++++ + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + ======================== Malloc State (0x7f97053fbc40) ======================== + mutex = 0x0 + flags = 0x1 + have_fastchunks = 0x0 + fastbinsY + [0] 0x20 => 0x0 + [1] 0x30 => 0x0 + [2] 0x40 => 0x0 + [3] 0x50 => 0x0 + [4] 0x60 => 0x55c4669fdc20 + [5] 0x70 => 0x0 + [6] 0x80 => 0x0 + [7] 0x90 => 0x0 + [8] 0xa0 => 0x0 + [9] 0xb0 => 0x0 + top = 0x55c4669ff6e0 + last_remainder = 0x0 + bins + Unsorted bins + [0] fd=0x55c4669fed40 bk=0x55c4669fdca0 + Small bins + [1] 0x20 fd=0x7f97053fbcb0 bk=0x7f97053fbcb0 + [2] 0x30 fd=0x7f97053fbcc0 bk=0x7f97053fbcc0 + ........ + [61] 0x3e0 fd=0x7f97053fc070 bk=0x7f97053fc070 + [62] 0x3f0 fd=0x7f97053fc080 bk=0x7f97053fc080 + Large bins + [63] 0x400 fd=0x7f97053fc090 bk=0x7f97053fc090 + [64] 0x440 fd=0x7f97053fc0a0 bk=0x7f97053fc0a0 + ........ + [125] 0x80000 fd=0x7f97053fc470 bk=0x7f97053fc470 + [126] 0x100000 fd=0x7f97053fc480 bk=0x7f97053fc480 + binmap = [0x0, 0x0, 0x0, 0x0] + next = 0x7f96f8000020 + next_free = 0x0 + attached_threads = 0x1 + system_mem = 0x21000 + max_system_mem = 0x21000 + ================================================================================ + ============================ Heap (0x55c4669fd000) ============================ + 0x55c4669fd000 0x250 PREV_IN_USE + 00 00 00 00 07 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x55c4669fd250 0x410 PREV_IN_USE + 61 61 61 0a 0a 20 76 65 72 73 69 6f 6e 20 3d 20 aaa.. version = + 0x55c4669fd660 0x120 PREV_IN_USE + 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + .......... + 0x55c4669fdca0 0x1010 PREV_IN_USE + a0 bc 3f 05 97 7f 00 00 40 ed 9f 66 c4 55 00 00 ..?.....@..f.U.. + 0x55c4669fecb0 0x90 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x55c4669fed40 0x910 PREV_IN_USE + a0 dc 9f 66 c4 55 00 00 a0 bc 3f 05 97 7f 00 00 ...f.U....?..... + 0x55c4669ff650 0x90 + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x55c4669ff6e0 0x1e920 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + ================================================================================ + =================================== Tcaches =================================== + [4] Tcache 0x58 (7) => Chunk(0x55c4669fdb50 0x60 PREV_IN_USE) => Chunk(0x55c4669 + fdaf0 0x60 PREV_IN_USE) => Chunk(0x55c4669fda90 0x60 PREV_IN_USE) => Chunk(0x55c + 4669fda30 0x60 PREV_IN_USE) => Chunk(0x55c4669fd970 0x60 PREV_IN_USE) => Chunk(0 + x55c4669fd910 0x60 PREV_IN_USE) => Chunk(0x55c4669fd8b0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + ================================== Fast Bins ================================== + [4] Fast Bin 0x60 (2) => Chunk(0x55c4669fdc20 0x60 PREV_IN_USE) => Chunk(0x55c46 + 69fdba0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + ================================ Unsorted Bins ================================ + [0] Unsorted Bin (2) => Chunk(0x55c4669fed40 0x910 PREV_IN_USE) => Chunk(0x55c46 + 69fdca0 0x1010 PREV_IN_USE) => 0x7f97053fbca0 + ================================================================================ + ================================== Small Bins ================================== + [-] No chunks found + ================================================================================ + ================================== Large Bins ================================== + [-] No chunks found + ================================================================================ + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +Access to items of the non main arena: + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> hp.arenas_count() + 2 + >>> _ = [print(arena.summary()) for arena in hp.all_arenas()] + ==================================== Arena ==================================== + - Malloc State (0x7f97053fbc40) + top = 0x55c4669ff6e0 + last_remainder = 0x0 + next = 0x7f9700000020 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x55c4669fd000) + chunks_count = 0x15 + top: addr = 0x55c4669ff6e0, size = 0x1e920 + - Tcaches + [4] 0x58 (7) + - Fast bins + [4] 0x60 (2) + - Unsorted bins + [0] 0x0 (2) + - Small bins + [-] No chunks found + - Large bins + [-] No chunks found + ================================================================================ + ==================================== Arena ==================================== + - Malloc State (0x7f9700000020) + top = 0x7f9700002b30 + last_remainder = 0x0 + next = 0x7f97053fbc40 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x7f97000008c0) + chunks_count = 0x4 + top: addr = 0x7f9700002b30, size = 0x1e4d0 + - Tcaches + [-] No chunks found + - Fast bins + [-] No chunks found + - Unsorted bins + [-] No chunks found + - Small bins + [-] No chunks found + - Large bins + [-] No chunks found + ================================================================================ + >>> _ = [print(ms) for ms in hp.all_arenas_fast_bins()] + ================================== Fast Bins ================================== + [4] Fast Bin 0x60 (2) => Chunk(0x55c4669fdc20 0x60 PREV_IN_USE) => Chunk(0x55c46 + 69fdba0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + ================================== Fast Bins ================================== + [-] No chunks found + ================================================================================ + >>> print(hp.fast_bins()) + ================================== Fast Bins ================================== + [4] Fast Bin 0x60 (2) => Chunk(0x55c4669fdc20 0x60 PREV_IN_USE) => Chunk(0x55c4669fdba0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + >>> print(hp.fast_bins(arena_index=1)) + ================================== Fast Bins ================================== + [-] No chunks found + ================================================================================ + +""" + +from pwnlib.heap.glmalloc.heap_explorer import HeapExplorer +from pwnlib.heap.glmalloc.arena import Arena +from pwnlib.heap.glmalloc.malloc_chunk import MallocChunk +from pwnlib.heap.glmalloc.malloc_state import MallocState +from pwnlib.heap.glmalloc.heap import Heap, HeapError +from pwnlib.heap.glmalloc.bins import \ + Bins, \ + Bin, \ + BinEntry, \ + Tcaches, \ + Tcache, \ + TcacheEntry, \ + NoTcacheError, \ + FastBins, \ + FastBin, \ + FastBinEntry, \ + UnsortedBins, \ + UnsortedBin,\ + UnsortedBinEntry, \ + SmallBins, \ + SmallBin, \ + SmallBinEntry, \ + LargeBins, \ + LargeBin, \ + LargeBinEntry +from pwnlib.heap.glmalloc.process_informer import ProcessInformer, CoreFileInformer + +__all__ = [ + 'HeapExplorer', + 'Arena', + 'MallocChunk', + 'MallocState', + 'Heap', 'HeapError', + 'Tcaches', 'Tcache', 'TcacheEntry', 'NoTcacheError', + 'FastBins', 'FastBin', 'FastBinEntry', + 'UnsortedBins', 'UnsortedBin', 'UnsortedBinEntry', + 'SmallBins', 'SmallBin', 'SmallBinEntry', + 'LargeBins', 'LargeBin', 'LargeBinEntry', + 'Bins', 'Bin', 'BinEntry', + 'ProcessInformer', 'CoreFileInformer' +] diff --git a/pwnlib/heap/glmalloc/arena/__init__.py b/pwnlib/heap/glmalloc/arena/__init__.py new file mode 100644 index 000000000..fdf185792 --- /dev/null +++ b/pwnlib/heap/glmalloc/arena/__init__.py @@ -0,0 +1,7 @@ + +from .arena import Arena +from .parser import ArenaParser + +__all__ = [ + 'Arena', 'ArenaParser' +] diff --git a/pwnlib/heap/glmalloc/arena/arena.py b/pwnlib/heap/glmalloc/arena/arena.py new file mode 100644 index 000000000..7ba1b1ea1 --- /dev/null +++ b/pwnlib/heap/glmalloc/arena/arena.py @@ -0,0 +1,116 @@ +from pwnlib.heap.glmalloc.bins import NoTcacheError +from pwnlib.heap.glmalloc.malloc_state import ( + SMALL_BINS_START_INDEX, LARGE_BINS_START_INDEX +) +from pwnlib.heap.glmalloc.basic_formatter import BasicFormatter + + +class Arena(object): + """Class with the information of the arena. + """ + + def __init__(self, malloc_state, heap, unsorted_bin, + small_bins, large_bins, fast_bins, tcaches): + #: :class:`MallocState`: The malloc_state struct of the arena + self.malloc_state = malloc_state + + #: :class:`Heap`: The heap of the arena + self.heap = heap + self._tcaches = tcaches + + #: :class:`FastBins`: The fast bins of the arena + self.fast_bins = fast_bins + + #: :class:`UnsortedBins`: The unsorted bin of the arena + self.unsorted_bin = unsorted_bin + + #: :class:`SmallBins`: The small bins of the arena + self.small_bins = small_bins + + #: :class:`LargeBins`: The large bins of the arena + self.large_bins = large_bins + self._basic_formatter = BasicFormatter() + + @property + def tcaches(self): + """:class:`Tcaches`: The tcaches of the arena. If + tcaches are not available, an exception :class:`NoTcacheError` is + raised. + """ + if self._tcaches is None: + raise NoTcacheError() + + return self._tcaches + + def __str__(self): + msg = [ + self._basic_formatter.super_header("Arena"), + str(self.malloc_state), + str(self.heap), + ] + + try: + msg.append(str(self.tcaches)) + except NoTcacheError: + pass + + msg.append(str(self.fast_bins)) + msg.append(str(self.unsorted_bin)) + msg.append(str(self.small_bins)) + msg.append(str(self.large_bins)) + msg.append(self._basic_formatter.super_footer()) + + return "\n".join(msg) + + def summary(self): + msg = [ + self._basic_formatter.header("Arena"), + self._format_summary(), + self._basic_formatter.footer() + ] + return "\n".join(msg) + + def _format_summary(self): + msg = "" + + malloc_state = self.malloc_state + msg += "- Malloc State ({:#x})\n".format(malloc_state.address) + msg += " top = {:#x}\n".format(malloc_state.top) + msg += " last_remainder = {:#x}\n".format( + malloc_state.last_remainder + ) + msg += " next = {:#x}\n".format(malloc_state.next) + msg += " next_free = {:#x}\n".format(malloc_state.next_free) + msg += " system_mem = {:#x}\n".format(malloc_state.system_mem) + + heap = self.heap + msg += "- Heap ({:#x})\n".format(heap.address) + msg += " chunks_count = {:#x}\n".format(len(heap.chunks)) + msg += " top: addr = {:#x}, size = {:#x}\n".format( + heap.top.address, heap.top.size + ) + + try: + tcaches = self.tcaches + msg += "- Tcaches\n" + msg += "{}\n".format(tcaches.summary()) + except NoTcacheError: + pass + + msg += "- Fast bins\n" + msg += "{}\n".format(self.fast_bins.summary()) + + msg += "- Unsorted bins\n" + msg += "{}\n".format(self.unsorted_bin.summary()) + + msg += "- Small bins\n" + msg += "{}\n".format( + self.small_bins.summary(start_index=SMALL_BINS_START_INDEX) + ) + + msg += "- Large bins\n" + msg += "{}".format( + self.large_bins.summary(start_index=LARGE_BINS_START_INDEX) + ) + + return msg diff --git a/pwnlib/heap/glmalloc/arena/parser.py b/pwnlib/heap/glmalloc/arena/parser.py new file mode 100644 index 000000000..9de33b188 --- /dev/null +++ b/pwnlib/heap/glmalloc/arena/parser.py @@ -0,0 +1,72 @@ +from pwnlib.heap.glmalloc.arena.arena import Arena + + +class ArenaParser: + """Class to parse the arena items and retrieve Arena objects + + Args: + malloc_state_parser (MallocStateParser) + heap_parser (HeapParser) + bin_parser (BinParser) + fast_bin_parser (FastBinParser) + tcache_parser (TcacheParser) + """ + + def __init__(self, malloc_state_parser, heap_parser, bin_parser, + fast_bin_parser, tcache_parser): + self._malloc_state_parser = malloc_state_parser + self._heap_parser = heap_parser + self._bin_parser = bin_parser + self._fast_bin_parser = fast_bin_parser + self._tcache_parser = tcache_parser + + def parse_all_from_main_malloc_state_address(self, main_malloc_state_address): + """Returns all the arenas of the process from the address of the + main arena malloc state + + Args: + main_malloc_state_address(int): The address of the main arena + malloc state + + Returns: + list of Arena + """ + + malloc_states = self._malloc_state_parser.parse_all_from_main_malloc_state_address( + main_malloc_state_address + ) + return [self.parse_from_malloc_state(malloc_state) for malloc_state in malloc_states] + + def parse_from_malloc_state(self, malloc_state): + """Returns all the arena information based on the malloc state. + + Args: + malloc_state (MallocState) + + Returns: + Arena + """ + heap = self._heap_parser.parse_from_malloc_state(malloc_state) + unsorted_bin = self._bin_parser.parse_unsorted_bin_from_malloc_state( + malloc_state + ) + small_bins = self._bin_parser.parse_small_bins_from_malloc_state( + malloc_state + ) + large_bins = self._bin_parser.parse_large_bins_from_malloc_state( + malloc_state + ) + fast_bins = self._fast_bin_parser.parse_all_from_malloc_state( + malloc_state + ) + tcaches = self._tcache_parser.parse_all_from_malloc_state(malloc_state) + + return Arena( + malloc_state, + heap, + unsorted_bin, + small_bins, + large_bins, + fast_bins, + tcaches + ) diff --git a/pwnlib/heap/glmalloc/basic_formatter.py b/pwnlib/heap/glmalloc/basic_formatter.py new file mode 100644 index 000000000..fbc0b680a --- /dev/null +++ b/pwnlib/heap/glmalloc/basic_formatter.py @@ -0,0 +1,46 @@ +import shutil + + +def get_terminal_width(): + try: + return shutil.get_terminal_size().columns + except AttributeError: + import os + _, columns = os.popen('stty size', 'r').read().split() + return columns + + +class BasicFormatter(object): + _MAXIMUM_WIDTH = 80 + _MINIMUM_WIDTH = 40 + + def __init__(self): + term_width = get_terminal_width() + + if term_width > self._MAXIMUM_WIDTH: + self.width = self._MAXIMUM_WIDTH + elif term_width < self._MINIMUM_WIDTH: + self.width = self._MINIMUM_WIDTH + else: + self.width = term_width + + self.title_surrounded_symbol = "=" + + def super_header(self, title): + msg = "{}\n".format(self.header(title, separator="+")) + msg += "+" * self.width + return msg + + def header(self, title, separator="="): + side_len = self._calc_side_len(title) + side = separator * side_len + return "{} {} {}".format(side, title, side) + + def _calc_side_len(self, title): + return int((self.width - len(title) - 2) / 2) + + def footer(self): + return "=" * self.width + + def super_footer(self): + return "+" * self.width diff --git a/pwnlib/heap/glmalloc/bins/__init__.py b/pwnlib/heap/glmalloc/bins/__init__.py new file mode 100644 index 000000000..6fbe9a9c3 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/__init__.py @@ -0,0 +1,25 @@ + +from .bin import Bins, Bin, BinEntry +from .small_bin import SmallBins, SmallBin, SmallBinEntry +from .large_bin import LargeBins, LargeBin, LargeBinEntry +from .unsorted_bin import UnsortedBins, UnsortedBin, UnsortedBinEntry +from .tcache import \ + NoTcacheError, \ + EnabledTcacheParser, \ + DisabledTcacheParser, \ + Tcaches, \ + Tcache, \ + TcacheEntry +from .fast_bin import FastBinParser, FastBins, FastBin, FastBinEntry +from .bin_parser import BinParser + + +__all__ = [ + 'BinParser', 'EnabledTcacheParser', 'DisabledTcacheParser', + 'Tcaches', 'Tcache', 'TcacheEntry', 'NoTcacheError', + 'FastBinParser', 'FastBins', 'FastBin', 'FastBinEntry', + 'UnsortedBins', 'UnsortedBin', 'UnsortedBinEntry', + 'SmallBins', 'SmallBin', 'SmallBinEntry', + 'LargeBins', 'LargeBin', 'LargeBinEntry', + 'Bins', 'Bin', 'BinEntry' +] diff --git a/pwnlib/heap/glmalloc/bins/bin.py b/pwnlib/heap/glmalloc/bins/bin.py new file mode 100644 index 000000000..a938aa478 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/bin.py @@ -0,0 +1,164 @@ +from pwnlib.heap.glmalloc.basic_formatter import BasicFormatter + + +class Bins(object): + """Base class to be inherit by the bins sequences. This class provides + the methods to access the bins array as well as standard implementation + of the __str__ method. + """ + + def __init__(self, bins): + self._bins = bins + self._basic_formatter = BasicFormatter() + + @property + def bins(self): + """(:obj:`list` of :class:`Bin`): The bins of the sequence.""" + return self._bins + + def __getitem__(self, item): + return self._bins[item] + + def __len__(self): + return len(self._bins) + + def __iter__(self): + return iter(self._bins) + + def __str__(self): + msg = [ + self._basic_formatter.header(self._name()), + self._format_bins(self._start_index()), + self._basic_formatter.footer() + ] + return "\n".join(msg) + + def _name(self): + return self.__class__.__name__ + + def _start_index(self): + return 0 + + def _format_bins(self, start_index=0): + bins_str = [] + for i, bin_ in enumerate(self.bins): + if len(bin_) > 0: + bins_str.append("[{}] {}".format( + i + start_index, bin_) + ) + + if bins_str: + return "\n".join(bins_str) + else: + return " [-] No chunks found" + + def summary(self, start_index=0): + bins_str = [] + for i, bin_ in enumerate(self.bins): + if len(bin_) > 0: + bins_str.append( + " [{}] {:#x} ({})".format( + start_index + i, bin_.chunks_size, len(bin_)) + ) + + if bins_str: + return "\n".join(bins_str) + else: + return " [-] No chunks found" + + +class Bin(object): + """Base class to be inherit by the bins. This class provides the basic info + of the bin entry as well as the chunks of the bin. + """ + + def __init__(self, bin_entry, malloc_chunks, safe_link=False): + self._bin_entry = bin_entry + self._malloc_chunks = malloc_chunks + self._safe_link = safe_link + + @property + def bin_entry(self): + """:class:`BinEntry`: The entry of malloc_state or + tcache_perthread_struct for the bin.""" + return self._bin_entry + + @property + def fd(self): + """:class:`int`: Shortcut to the fd pointer of the entry of the current bin.""" + return self.bin_entry.fd + + @property + def bk(self): + """:class:`int`: Shortcut to the bk pointer of the entry of the current bin.""" + return self.bin_entry.bk + + @property + def chunks_size(self): + """:class:`int`: Size which should have the chunks in the bin.""" + return self.bin_entry.chunks_size + + @property + def malloc_chunks(self): + """:obj:`list` of :class:`MallocChunk`: The chunks which are + inserted in the bin.""" + return self._malloc_chunks + + @property + def chunks(self): + """:obj:`list` of :class:`MallocChunk`: Alias for malloc_chunks.""" + return self.malloc_chunks + + def __len__(self): + return len(self.malloc_chunks) + + def __iter__(self): + return iter(self.malloc_chunks) + + def __str__(self): + msg = [self._name()] + + if self.chunks_size != 0: + msg.append(" {:#x}".format(self.chunks_size)) + + msg.append(" ({})".format(len(self.malloc_chunks))) + + next_address = self.fd + for chunk in self.malloc_chunks: + flags = chunk.format_flags_as_str() + msg.append( + " => Chunk({:#x} {:#x}".format(next_address, chunk.size) + ) + if flags: + msg.append(" {}".format(flags)) + msg.append(")") + + if self._safe_link: + next_address = chunk.fd_demangled + else: + next_address = chunk.fd + + msg.append(" => {:#x}".format(next_address)) + return "".join(msg) + + def _name(self): + return self.__class__.__name__ + + +class BinEntry(object): + """Class to contain the common information of each bin entry. + """ + + def __init__(self, address, fd, bk=0, chunks_size=0): + #: :class:`int`: The address of the bin entry. + self.address = address + + #: :class:`int`: The address of first chunk of the bin. + self.fd = fd + + #: :class:`int`: The address of last chunk of the bin. 0 if not used. + self.bk = bk + + #: :class:`int`: Size which should have the chunks in the bin. 0 if + #: not used. + self.chunks_size = chunks_size diff --git a/pwnlib/heap/glmalloc/bins/bin_parser.py b/pwnlib/heap/glmalloc/bins/bin_parser.py new file mode 100644 index 000000000..0ea76b635 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/bin_parser.py @@ -0,0 +1,107 @@ +from pwnlib.heap.glmalloc.bins import SmallBins, SmallBin, SmallBinEntry +from pwnlib.heap.glmalloc.bins import LargeBins, LargeBin, LargeBinEntry +from pwnlib.heap.glmalloc.bins import \ + UnsortedBins, \ + UnsortedBin, \ + UnsortedBinEntry + + +class BinParser: + """Class with the logic to parse the structs of a bin (unsorted, small + and large) from raw memory and create its respective implementation: + UnsortedBin, SmallBin or LargeBin. + + Args: + malloc_chunk_parser (MallocChunkParser): a parser of the chunks in the + heap. + + """ + + def __init__(self, malloc_chunk_parser): + self._pointer_size = malloc_chunk_parser.pointer_size + self._malloc_chunk_parser = malloc_chunk_parser + + def parse_unsorted_bin_from_malloc_state(self, malloc_state): + """Returns the unsorted bin of the arena based on the malloc state + information. + + Args: + malloc_state (MallocState) + + Returns: + UnsortedBin + """ + return UnsortedBins( + self._parse_from_bin_entry(malloc_state.unsorted_bin) + ) + + def parse_small_bins_from_malloc_state(self, malloc_state): + """Returns the small bins of the arena based on the malloc state + information. + + Args: + malloc_state (MallocState) + + Returns: + list of SmallBin + """ + small_bins = [] + for small_entry in malloc_state.small_bins: + small_bins.append( + self._parse_from_bin_entry(small_entry) + ) + return SmallBins(small_bins) + + def parse_large_bins_from_malloc_state(self, malloc_state): + """Returns the small bins of the arena based on the malloc state + information. + + Args: + malloc_state (MallocState) + + Returns: + list of LargeBin + """ + large_bins = [] + for large_entry in malloc_state.large_bins: + large_bins.append( + self._parse_from_bin_entry(large_entry) + ) + return LargeBins(large_bins) + + def _parse_from_bin_entry(self, bin_entry): + chunks = [] + base_bin_address = bin_entry.address - (self._pointer_size*2) + addresses = [base_bin_address] + current_address = bin_entry.fd + while current_address not in addresses: + addresses.append(current_address) + try: + chunk = self._malloc_chunk_parser.parse_from_address( + current_address + ) + chunks.append(chunk) + current_address = chunk.fd + except (OSError, IOError): + # to avoid hanging in case some pointer is corrupted + break + + return BinFactory.create(bin_entry, chunks) + + +class BinFactory: + """Helper class to create different bin classes based on the type of entry + provided. + """ + + @staticmethod + def create(bin_entry, chunks): + + if isinstance(bin_entry, LargeBinEntry): + return LargeBin(bin_entry, chunks) + elif isinstance(bin_entry, SmallBinEntry): + return SmallBin(bin_entry, chunks) + elif isinstance(bin_entry, UnsortedBinEntry): + return UnsortedBin(bin_entry, chunks) + + raise TypeError() diff --git a/pwnlib/heap/glmalloc/bins/fast_bin.py b/pwnlib/heap/glmalloc/bins/fast_bin.py new file mode 100644 index 000000000..3e3b7e1a0 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/fast_bin.py @@ -0,0 +1,93 @@ +from pwnlib.heap.glmalloc.bins import Bins, Bin, BinEntry + + +class FastBinParser: + """Class with the logic to parse the chunks of a fast bin from raw memory + and create FastBin objects. + + Args: + malloc_chunk_parser (MallocChunkParser): a parser of the chunks in the + heap. + safe_link (bool): If fd pointer is protected by safe-link. + """ + + def __init__(self, malloc_chunk_parser, safe_link=False): + self._malloc_chunk_parser = malloc_chunk_parser + self._safe_link = safe_link + + def parse_all_from_malloc_state(self, malloc_state): + """Returns the fast bins of the arena based on the malloc state + information. + + Args: + malloc_state (MallocState) + + Returns: + list of FastBin + """ + fast_bins = [] + for bin_entry in malloc_state.fastbinsY: + fast_bin = self._parse_from_fast_bin_entry(bin_entry) + fast_bins.append(fast_bin) + return FastBins(fast_bins) + + def _parse_from_fast_bin_entry(self, fast_bin_entry): + chunks = [] + addresses = [] + current_address = fast_bin_entry.fd + while current_address != 0x0 and current_address not in addresses: + addresses.append(current_address) + try: + chunk = self._malloc_chunk_parser.parse_from_address( + current_address + ) + chunks.append(chunk) + + if self._safe_link: + current_address = chunk.fd_demangled + else: + current_address = chunk.fd + except (OSError, IOError): + # to avoid hanging in case some pointer is corrupted + break + + return FastBin(fast_bin_entry, chunks, safe_link=self._safe_link) + + +class FastBins(Bins): + """Sequence of fast bins. + """ + + def _name(self): + return "Fast Bins" + + @property + def bins(self): + """:obj:`list` of :class:`FastBin`: The bins of the sequence.""" + return super(FastBins, self).bins + + +class FastBin(Bin): + """Class to represent a fast bin of the glibc. + """ + + def __init__(self, bin_entry, malloc_chunks, safe_link): + super(FastBin, self).__init__(bin_entry, malloc_chunks, safe_link=safe_link) + + def _name(self): + return "Fast Bin" + + @property + def bin_entry(self): + """:class:`FastBinEntry`: The entry of malloc_state for the fast bin.""" + return super(FastBin, self).bin_entry + + +class FastBinEntry(BinEntry): + """Class to contain the information of a entry in `fastbinsY` attribute + of `malloc_state` struct. + """ + + def __init__(self, address, fd, chunks_size): + super(FastBinEntry, self).__init__( + address, fd, chunks_size=chunks_size) diff --git a/pwnlib/heap/glmalloc/bins/large_bin.py b/pwnlib/heap/glmalloc/bins/large_bin.py new file mode 100644 index 000000000..c7438e021 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/large_bin.py @@ -0,0 +1,53 @@ +from pwnlib.heap.glmalloc.bins import Bins, Bin, BinEntry + + +class LargeBins(Bins): + """Sequence of large bins. + """ + + def _name(self): + return "Large Bins" + + @property + def bins(self): + """:obj:`list` of :class:`LargeBin`: The bins of the sequence.""" + return super(LargeBins, self).bins + + +class LargeBin(Bin): + """Class to represent an large bin of the glibc. + """ + + def __init__(self, bin_entry, malloc_chunks): + super(LargeBin, self).__init__(bin_entry, malloc_chunks) + + @property + def min_chunks_size(self): + return self.chunks_size + + def _name(self): + return "Large Bin" + + @property + def bin_entry(self): + """:class:`LargeBinEntry`: The entry of malloc_state for the large bin.""" + return super(LargeBin, self).bin_entry + + +class LargeBinEntry(BinEntry): + """Class to contain the information of a large bin entry in `bins` of + malloc state. + """ + + def __init__(self, address, fd, bk, chunks_size): + super(LargeBinEntry, self).__init__( + address=address, fd=fd, bk=bk, chunks_size=chunks_size) + + @property + def min_chunks_size(self): + return self.chunks_size + + def __str__(self): + return "Large Bin [>={:#x}]: fd={:#x}, bk={:#x}".format( + self.chunks_size, self.fd, self.bk + ) diff --git a/pwnlib/heap/glmalloc/bins/small_bin.py b/pwnlib/heap/glmalloc/bins/small_bin.py new file mode 100644 index 000000000..dfa62d7c9 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/small_bin.py @@ -0,0 +1,45 @@ +from pwnlib.heap.glmalloc.bins import Bins, Bin, BinEntry + + +class SmallBins(Bins): + """Sequence of small bins. + """ + + def _name(self): + return "Small Bins" + + @property + def bins(self): + """:obj:`list` of :class:`SmallBin`: The bins of the sequence.""" + return super(SmallBins, self).bins + + +class SmallBin(Bin): + """Class to represent an small bin of the glibc. + """ + + def __init__(self, bin_entry, malloc_chunks): + super(SmallBin, self).__init__(bin_entry, malloc_chunks) + + def _name(self): + return "Small Bin" + + @property + def bin_entry(self): + """:class:`SmallBinEntry`: The entry of malloc_state for the small bin.""" + return super(SmallBin, self).bin_entry + + +class SmallBinEntry(BinEntry): + """Class to contain the information of a small bin entry in `bins` of + malloc state. + """ + + def __init__(self, address, fd, bk, chunks_size): + super(SmallBinEntry, self).__init__( + address, fd, bk=bk, chunks_size=chunks_size) + + def __str__(self): + return "Small Bin [{:#x}]: fd={:#x}, bk={:#x}".format( + self.chunks_size, self.fd, self.bk + ) diff --git a/pwnlib/heap/glmalloc/bins/tcache/__init__.py b/pwnlib/heap/glmalloc/bins/tcache/__init__.py new file mode 100644 index 000000000..cc7bcffe8 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/tcache/__init__.py @@ -0,0 +1,9 @@ + +from .error import NoTcacheError +from .parser import EnabledTcacheParser, DisabledTcacheParser +from .tcache import Tcaches, Tcache, TcacheEntry + +__all__ = [ + 'EnabledTcacheParser', 'DisabledTcacheParser', + 'Tcaches', 'Tcache', 'TcacheEntry', 'NoTcacheError', +] \ No newline at end of file diff --git a/pwnlib/heap/glmalloc/bins/tcache/error.py b/pwnlib/heap/glmalloc/bins/tcache/error.py new file mode 100644 index 000000000..f22d1158b --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/tcache/error.py @@ -0,0 +1,9 @@ + + +class NoTcacheError(Exception): + """Exception raised when tries to access to tcaches and these are + unavailable in the current libc. + """ + + def __init__(self, message="Tcache are not available in the current libc"): + super(NoTcacheError, self).__init__(message) diff --git a/pwnlib/heap/glmalloc/bins/tcache/parser.py b/pwnlib/heap/glmalloc/bins/tcache/parser.py new file mode 100644 index 000000000..5cc02af7e --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/tcache/parser.py @@ -0,0 +1,95 @@ +from .tcache_per_thread_struct import TcachePerthreadStructParser +from .tcache import Tcaches, Tcache + + +class TcacheParser: + """Abstract class that defines the public methods of a Tcache + """ + + def parse_all_from_malloc_state(self, malloc_state): + raise NotImplementedError() + + +class DisabledTcacheParser: + """Class to use when tcaches are disabled + """ + + def parse_all_from_malloc_state(self, malloc_state): + """Fake method that always returns None + """ + return None + + +class EnabledTcacheParser: + """Class with the logic to parse the chunks of a tcache from raw memory + and create Tcache objects. + + Args: + malloc_chunk_parser (MallocChunkParser): a parser of the chunks in the + heap. + heap_parser (HeapParser): a parser of the heap metadata and chunks. + safe_link (bool): If fd pointer is protected by safe-link. + """ + + def __init__(self, malloc_chunk_parser, heap_parser, safe_link=False): + self._process_informer = malloc_chunk_parser.process_informer + self._pointer_size = malloc_chunk_parser.pointer_size + self._malloc_chunk_parser = malloc_chunk_parser + self._tcache_perthread_parser = TcachePerthreadStructParser( + malloc_chunk_parser.process_informer + ) + self._heap_parser = heap_parser + + self._safe_link = safe_link + + def are_tcache_enabled(self): + return True + + def parse_all_from_malloc_state(self, malloc_state): + """Returns the tcaches of the arena based on the malloc state + information. + + Args: + malloc_state (MallocState) + + Returns: + list of Tcache + """ + heap_address, _ = self._heap_parser.calculate_heap_address_and_size_from_malloc_state( + malloc_state) + return self._parse_all_from_heap_address(heap_address) + + def _parse_all_from_heap_address(self, heap_address): + tcache_perthread_struct_address = heap_address + 0x10 + tcache_perthread = self._tcache_perthread_parser.parse_from_address( + tcache_perthread_struct_address + ) + + tcaches = [] + for entry in tcache_perthread.entries: + tcache = self._parse_from_tcache_entry(entry) + tcaches.append(tcache) + return Tcaches(tcaches) + + def _parse_from_tcache_entry(self, entry): + pointer = entry.fd + addresses = [entry.address] + chunks = [] + while pointer != 0x0 and pointer not in addresses: + addresses.append(pointer) + try: + chunk_base_address = pointer - self._pointer_size*2 + chunk = self._malloc_chunk_parser.parse_from_address( + chunk_base_address + ) + chunks.append(chunk) + if self._safe_link: + pointer = chunk.fd_demangled + else: + pointer = chunk.fd + + except (OSError, IOError): + # to avoid hanging in case some pointer is corrupted + break + + return Tcache(entry, chunks, safe_link=self._safe_link) diff --git a/pwnlib/heap/glmalloc/bins/tcache/tcache.py b/pwnlib/heap/glmalloc/bins/tcache/tcache.py new file mode 100644 index 000000000..d99505bbe --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/tcache/tcache.py @@ -0,0 +1,35 @@ +from pwnlib.heap.glmalloc.bins import Bins, Bin, BinEntry + + +class Tcaches(Bins): + """Sequence of tcache bins. + """ + + @property + def bins(self): + """:obj:`list` of :class:`Tcache`: The bins of the sequence.""" + return super(Tcaches, self).bins + + +class Tcache(Bin): + """Class to represent a tcache of the glibc + """ + + def __init__(self, bin_entry, malloc_chunks, safe_link): + super(Tcache, self).__init__(bin_entry, malloc_chunks, safe_link=safe_link) + + @property + def bin_entry(self): + """:class:`TcacheEntry`: The entry of tcache_perthread_struct for the + bin.""" + return super(Tcache, self).bin_entry + + +class TcacheEntry(BinEntry): + """Class to contain the information of each entry in + tcache_perthread_struct struct. + """ + + def __init__(self, address, fd, chunks_size): + super(TcacheEntry, self).__init__(address, fd, chunks_size=chunks_size) + diff --git a/pwnlib/heap/glmalloc/bins/tcache/tcache_per_thread_struct.py b/pwnlib/heap/glmalloc/bins/tcache/tcache_per_thread_struct.py new file mode 100644 index 000000000..809a95a16 --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/tcache/tcache_per_thread_struct.py @@ -0,0 +1,83 @@ +from .tcache import TcacheEntry + + +class TcachePerthreadStructParser: + """Class with the logic to parsing the tcache_perthread_struct struct + from binary data. + + + Args: + process_informer (ProcessInformer): Helper to perform operations over + memory + + """ + TCACHE_MAX_BINS = 64 + + def __init__(self, process_informer): + self._process_informer = process_informer + self._pointer_size = process_informer.pointer_size + self._unpack_pointer = process_informer.unpack_pointer + self._tcache_perthread_struct_size = self.TCACHE_MAX_BINS + \ + self._pointer_size * \ + self.TCACHE_MAX_BINS + + def parse_from_address(self, address): + """Returns a TcachePerthreadStruct object by parsing the + `tcache_perthread_struct` struct at the given address. + + Args: + address (int): Address of the `tcache_perthread_struct`. + + Returns: + TcachePerthreadStruct + """ + raw_tcache_perthread_struct = self._process_informer.read_memory( + address, + self._tcache_perthread_struct_size + ) + + return self._parse_from_raw(address, raw_tcache_perthread_struct) + + def _parse_from_raw(self, address, raw_tcache_perthread_struct): + counts = [] + for i in range(self.TCACHE_MAX_BINS): + count = raw_tcache_perthread_struct[i] + counts.append(count) + + entries = [] + for i in range(self.TCACHE_MAX_BINS): + index = self.TCACHE_MAX_BINS + i * self._pointer_size + current_address = address + index + fd = self._unpack_pointer( + raw_tcache_perthread_struct[index:index+self._pointer_size] + ) + tcache_chunks_size = self._pointer_size * 3 + i * self._pointer_size * 2 + entries.append( + TcacheEntry(current_address, fd, tcache_chunks_size) + ) + + return TcachePerthreadStruct(address, counts, entries) + + +class TcachePerthreadStruct: + """Class to contain the information of tcache_perthread_struct struct. + + typedef struct tcache_perthread_struct + { + char counts[TCACHE_MAX_BINS]; + tcache_entry *entries[TCACHE_MAX_BINS]; + } tcache_perthread_struct; + """ + TCACHE_MAX_BINS = 64 + + def __init__(self, address, counts, entries): + #: :class:`int`: Address of the structure + self.address = address + + #: :class:`list` of :class:`int`: List with indicative counts of chunks of each + #: tcache + self.counts = counts + + #: :class:`list` of :class:`TcacheEntry`: One entrie per tcache, + #: which indicates the address of the first chunk in the bin + self.entries = entries diff --git a/pwnlib/heap/glmalloc/bins/unsorted_bin.py b/pwnlib/heap/glmalloc/bins/unsorted_bin.py new file mode 100644 index 000000000..48661436c --- /dev/null +++ b/pwnlib/heap/glmalloc/bins/unsorted_bin.py @@ -0,0 +1,50 @@ +from pwnlib.heap.glmalloc.bins import Bins, Bin, BinEntry + + +class UnsortedBins(Bins): + """Sequence of unsorted bins. There is only 1 bin in this sequence, however + it is encapsulated in this class for compatibility with the other bin + types. + """ + + def __init__(self, bin): + super(UnsortedBins, self).__init__([bin]) + + def _name(self): + return "Unsorted Bins" + + @property + def bins(self): + """:obj:`list` of :class:`UnsortedBin`: The bins of the sequence.""" + return super(UnsortedBins, self).bins + + +class UnsortedBin(Bin): + """Class to represent an unsorted bin of the glibc + """ + + def __init__(self, bin_entry, malloc_chunks): + super(UnsortedBin, self).__init__(bin_entry, malloc_chunks) + + def _name(self): + return "Unsorted Bin" + + @property + def bin_entry(self): + """:class:`UnsortedBinEntry`: The entry of malloc_state for the unsorted + bin.""" + return super(UnsortedBin, self).bin_entry + + +class UnsortedBinEntry(BinEntry): + """Class to contain the information of a unsorted bin entry in `bins` of + malloc state. + """ + + def __init__(self, address, fd, bk): + super(UnsortedBinEntry, self).__init__(address, fd, bk=bk) + + def __str__(self): + return "Unsorted Bin : fd={:#x}, bk={:#x}".format( + self.fd, self.bk + ) diff --git a/pwnlib/heap/glmalloc/heap/__init__.py b/pwnlib/heap/glmalloc/heap/__init__.py new file mode 100644 index 000000000..c8b5c4bfa --- /dev/null +++ b/pwnlib/heap/glmalloc/heap/__init__.py @@ -0,0 +1,8 @@ + +from .heap import Heap +from .heap_parser import HeapParser +from .error import HeapError + +__all__ = [ + 'Heap', 'HeapParser', 'HeapError' +] diff --git a/pwnlib/heap/glmalloc/heap/error.py b/pwnlib/heap/glmalloc/heap/error.py new file mode 100644 index 000000000..99276e06e --- /dev/null +++ b/pwnlib/heap/glmalloc/heap/error.py @@ -0,0 +1,7 @@ + +class HeapError(Exception): + """Exception raised when there are problems with the heap. + """ + + def __init__(self, message): + super(HeapError, self).__init__(message) diff --git a/pwnlib/heap/glmalloc/heap/heap.py b/pwnlib/heap/glmalloc/heap/heap.py new file mode 100644 index 000000000..19c9942b5 --- /dev/null +++ b/pwnlib/heap/glmalloc/heap/heap.py @@ -0,0 +1,44 @@ +from pwnlib.heap.glmalloc.basic_formatter import BasicFormatter + + +class Heap: + """Class to represent a heap with some useful metadata. Heap is considered + a collection of chunks. + """ + + def __init__(self, address, malloc_chunks, pointer_size): + + #: :class:`int`: The start address of the heap + self.address = address + + #: :class:`list` of :class:`MallocChunk`: The list of chunks of the heap + self.chunks = malloc_chunks + self._pointer_size = pointer_size + self._basic_formatter = BasicFormatter() + + @property + def top(self): + """:class:`MallocChunk`: The Top chunk of the heap""" + return self.chunks[-1] + + def __str__(self): + msg = [ + self._basic_formatter.header("Heap ({:#x})".format(self.address)), + self._format_heap(), + self._basic_formatter.footer() + ] + return "\n".join(msg) + + def _format_heap(self): + chunks_str = [ + self._format_chunk_as_str(chunk) for chunk in self.chunks + ] + return "\n".join(chunks_str) + + def _format_chunk_as_str(self, chunk): + flags = chunk.format_flags_as_str() + msg = [ + "{:#x} {:#x} {}".format(chunk.address, chunk.size, flags), + " " + chunk.format_first_bytes_as_hexdump_str() + ] + return "\n".join(msg) diff --git a/pwnlib/heap/glmalloc/heap/heap_info.py b/pwnlib/heap/glmalloc/heap/heap_info.py new file mode 100644 index 000000000..a8f314f7e --- /dev/null +++ b/pwnlib/heap/glmalloc/heap/heap_info.py @@ -0,0 +1,76 @@ +from construct import Int64ul, Int32ul, Struct + + +class HeapInfoParser: + """Class to parse the `heap_info` structure + + Args: + process_informer (ProcessInformer): Helper to perform operations over + memory and obtain info of the process + """ + + def __init__(self, process_informer): + self._process_informer = process_informer + self._pointer_size = process_informer.pointer_size + if self._pointer_size == 8: + pointer_type = Int64ul + else: + pointer_type = Int32ul + + self.heap_info_definition = Struct( + "ar_ptr"/ pointer_type, + "prev"/ pointer_type, + "size" / pointer_type, + "mprotect_size" / pointer_type + ) + + def parse_from_address(self, address): + raw_heap_info = self._process_informer.read_memory( + address, + self.heap_info_definition.sizeof() + ) + return self._parse_from_raw(raw_heap_info) + + def _parse_from_raw(self, raw): + heap_info_collection = self.heap_info_definition.parse(raw) + + return HeapInfo( + ar_ptr=heap_info_collection.ar_ptr, + prev=heap_info_collection.prev, + size=heap_info_collection.size, + mprotect_size=heap_info_collection.mprotect_size + ) + + +class HeapInfo: + """Class with the information of the `heap_info` structure. + + ```c + typedef struct _heap_info + { + mstate ar_ptr; /* Arena for this heap. */ + struct _heap_info *prev; /* Previous heap. */ + size_t size; /* Current size in bytes. */ + size_t mprotect_size; /* Size in bytes that has been mprotected + PROT_READ|PROT_WRITE. */ + /* Make sure the following data is properly aligned, particularly + that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of + MALLOC_ALIGNMENT. */ + char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; + } heap_info; + ``` + """ + + def __init__(self, ar_ptr, prev, size, mprotect_size): + #: :class:`int`: Address of the arena `malloc_state` structure. + self.ar_ptr = ar_ptr + + #: :class:`int`: Address of the previous heap. + self.prev = prev + + #: :class:`int`: Size of the heap. + self.size = size + + #: :class:`int`: Size of the heap which has been mprotected with + #: PROT_READ|PROT_WRITE. + self.mprotect_size = mprotect_size diff --git a/pwnlib/heap/glmalloc/heap/heap_parser.py b/pwnlib/heap/glmalloc/heap/heap_parser.py new file mode 100644 index 000000000..44a480163 --- /dev/null +++ b/pwnlib/heap/glmalloc/heap/heap_parser.py @@ -0,0 +1,105 @@ +from pwnlib.heap.glmalloc.utils import align_address +from pwnlib.heap.glmalloc.heap.heap import Heap +from pwnlib.heap.glmalloc.heap.heap_info import HeapInfoParser +from .error import HeapError + + +class HeapParser: + """Class to parse the chunks of a heap and return a Heap object + + Args: + malloc_chunk_parser (MallocChunkParser): parser for the raw malloc + chunks + malloc_state_parser (MallocStateParser): parser for the raw malloc_state + struct + """ + + def __init__(self, malloc_chunk_parser, malloc_state_parser): + self._process_informer = malloc_chunk_parser.process_informer + self._pointer_size = malloc_chunk_parser.pointer_size + self._malloc_alignment = self._pointer_size * 2 + self._raw_heap_info_size = self._pointer_size * 4 + + self._malloc_chunk_parser = malloc_chunk_parser + self._malloc_state_parser = malloc_state_parser + self._heap_info_parser = HeapInfoParser(self._process_informer) + + def parse_from_malloc_state(self, malloc_state): + """Returns the heap of the arena of the given malloc state + + Args: + malloc_state (MallocState) + + Returns: + Heap + """ + + heap_address, heap_size = self.calculate_heap_address_and_size_from_malloc_state(malloc_state) + return self._parse_from_address(heap_address, heap_size) + + def calculate_heap_address_and_size_from_malloc_state(self, malloc_state): + """Returns the heap start address and heap size, based on the + information of the provided malloc state + + Args: + malloc_state (MallocState) + + Raises: + HeapError: When the malloc state indicates no heap + + Returns: + tuple(int, int) + """ + + try: + current_heap_map = self._process_informer.map_with_address(malloc_state.top) + except IndexError: + if malloc_state.top == 0x0: + raise HeapError("No heap available") + else: + raise HeapError("No heap in address {}".format(malloc_state.top)) + + is_main_heap = malloc_state.address == self._process_informer.main_arena_address + if is_main_heap: + heap_start_address = current_heap_map.address + else: + heap_start_address = self._calculate_non_main_heap_start_address( + current_heap_map.address + ) + + heap_size = current_heap_map.size - \ + (heap_start_address - current_heap_map.start) + return heap_start_address, heap_size + + def _calculate_non_main_heap_start_address(self, heap_map_start_address): + heap_info = self._heap_info_parser.parse_from_address(heap_map_start_address) + heap_start_address = heap_info.ar_ptr + \ + self._malloc_state_parser.raw_malloc_state_size + return align_address(heap_start_address, self._malloc_alignment) + + def _parse_from_address(self, address, size): + raw_heap = self._process_informer.read_memory(address, size) + return self._parse_from_raw(address, raw_heap) + + def _parse_from_raw(self, heap_address, raw_heap): + offset = 0 + chunks = [] + + first_chunk = self._malloc_chunk_parser.parse_from_raw( + heap_address, + raw_heap + ) + + if first_chunk.size == 0: + offset += self._pointer_size*2 + + while offset < len(raw_heap): + current_address = heap_address + offset + chunk = self._malloc_chunk_parser.parse_from_raw( + current_address, + raw_heap[offset:] + ) + offset += chunk.size + chunks.append(chunk) + + return Heap(heap_address, chunks, self._pointer_size) diff --git a/pwnlib/heap/glmalloc/heap_explorer.py b/pwnlib/heap/glmalloc/heap_explorer.py new file mode 100644 index 000000000..05159a8a2 --- /dev/null +++ b/pwnlib/heap/glmalloc/heap_explorer.py @@ -0,0 +1,1226 @@ +from pwnlib.heap.glmalloc.bins import \ + BinParser, \ + FastBinParser, \ + EnabledTcacheParser, \ + DisabledTcacheParser, \ + NoTcacheError +from pwnlib.heap.glmalloc.arena import ArenaParser +from pwnlib.heap.glmalloc.malloc_state import MallocStateParser +from pwnlib.heap.glmalloc.heap import HeapParser, HeapError +from pwnlib.heap.glmalloc.malloc_chunk import MallocChunkParser + + +class HeapExplorer: + """Main class of the library. which functions to access to all items of the + glibc heap management, such as arenas, malloc_state, heap and bins. + + """ + + def __init__(self, process_informer, use_tcache=None, safe_link=None): + self._process_informer = process_informer + + if use_tcache is None: + use_tcache = self._are_tcaches_enabled() + + #: :class:`bool`: Indicates if tcaches are enabled for the current + #: glibc version. + self.tcaches_enabled = use_tcache + + if safe_link is None: + safe_link = self._is_safe_link_enabled() + + #: :class:`bool`: Indicates if tcaches and fastbin are protected + # by safe-link + self.safe_link = safe_link + + def _is_safe_link_enabled(self): + # safe-link protection is included in version 2.32 + return self._process_informer.is_libc_version_higher_than((2, 31)) + + def _are_tcaches_enabled(self): + # tcaches were added in version 2.26 + return self._process_informer.is_libc_version_higher_than((2, 25)) + + def arenas_count(self): + """Returns the number of arenas + + Returns: + int + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> hp.arenas_count() + 2 + + Tests: + >>> c = Corefile(pwnlib.data.heap.x86_64.get("core.23.fast_bins1")) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.23.so')) + >>> he.arenas_count() + 1 + + """ + return len(self.all_arenas_malloc_states()) + + def malloc_state(self, arena_index=0): + """Returns the malloc_arena of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`MallocState` + + :: + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> ms = he.malloc_state() + >>> hex(ms.top) + '0x55f7cec4df30' + >>> hex(ms.address) + '0x7f6c0f17eba0' + >>> print(ms) + ======================== Malloc State (0x7f6c0f17eba0) ======================== + mutex = 0x0 + flags = 0x0 + have_fastchunks = 0x0 + fastbinsY + [0] 0x20 => 0x0 + [1] 0x30 => 0x0 + [2] 0x40 => 0x0 + [3] 0x50 => 0x0 + [4] 0x60 => 0x0 + [5] 0x70 => 0x0 + [6] 0x80 => 0x0 + [7] 0x90 => 0x0 + [8] 0xa0 => 0x0 + [9] 0xb0 => 0x0 + top = 0x55f7cec4df30 + last_remainder = 0x0 + bins + Unsorted bins + [0] fd=0x7f6c0f17ec00 bk=0x7f6c0f17ec00 + Small bins + [1] 0x20 fd=0x7f6c0f17ec10 bk=0x7f6c0f17ec10 + [2] 0x30 fd=0x7f6c0f17ec20 bk=0x7f6c0f17ec20 + [3] 0x40 fd=0x7f6c0f17ec30 bk=0x7f6c0f17ec30 + ........... + [60] 0x3d0 fd=0x7f6c0f17efc0 bk=0x7f6c0f17efc0 + [61] 0x3e0 fd=0x7f6c0f17efd0 bk=0x7f6c0f17efd0 + [62] 0x3f0 fd=0x7f6c0f17efe0 bk=0x7f6c0f17efe0 + Large bins + [63] 0x400 fd=0x7f6c0f17eff0 bk=0x7f6c0f17eff0 + [64] 0x440 fd=0x7f6c0f17f000 bk=0x7f6c0f17f000 + [65] 0x480 fd=0x7f6c0f17f010 bk=0x7f6c0f17f010 + [66] 0x4c0 fd=0x7f6c0f17f020 bk=0x7f6c0f17f020 + ........... + [124] 0x40000 fd=0x7f6c0f17f3c0 bk=0x7f6c0f17f3c0 + [125] 0x80000 fd=0x7f6c0f17f3d0 bk=0x7f6c0f17f3d0 + [126] 0x100000 fd=0x7f6c0f17f3e0 bk=0x7f6c0f17f3e0 + binmap = [0x0, 0x0, 0x0, 0x0] + next = 0x7f6c0f17eba0 + next_free = 0x0 + attached_threads = 0x1 + system_mem = 0x21000 + max_system_mem = 0x21000 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get("core.32.sample1")) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.malloc_state()) # doctest: +ELLIPSIS + ===... Malloc State (0x7ff101adaba0) ===... + mutex = 0x0 + flags = 0x0 + have_fastchunks = 0x0 + fastbinsY + [0] 0x20 => 0x0 + [1] 0x30 => 0x0 + [2] 0x40 => 0x0 + [3] 0x50 => 0x0 + [4] 0x60 => 0x0 + [5] 0x70 => 0x0 + [6] 0x80 => 0x0 + [7] 0x90 => 0x0 + [8] 0xa0 => 0x0 + [9] 0xb0 => 0x0 + top = 0x55c9539e11a0 + last_remainder = 0x0 + bins + Unsorted bins + [0] fd=0x7ff101adac00 bk=0x7ff101adac00 + Small bins + [1] 0x20 fd=0x7ff101adac10 bk=0x7ff101adac10 + [2] 0x30 fd=0x7ff101adac20 bk=0x7ff101adac20 + [3] 0x40 fd=0x55c9539df610 bk=0x55c9539e0110 + [4] 0x50 fd=0x7ff101adac40 bk=0x7ff101adac40 + [5] 0x60 fd=0x7ff101adac50 bk=0x7ff101adac50 + [6] 0x70 fd=0x7ff101adac60 bk=0x7ff101adac60 + [7] 0x80 fd=0x7ff101adac70 bk=0x7ff101adac70 + [8] 0x90 fd=0x7ff101adac80 bk=0x7ff101adac80 + [9] 0xa0 fd=0x7ff101adac90 bk=0x7ff101adac90 + [10] 0xb0 fd=0x7ff101adaca0 bk=0x7ff101adaca0 + [11] 0xc0 fd=0x7ff101adacb0 bk=0x7ff101adacb0 + [12] 0xd0 fd=0x7ff101adacc0 bk=0x7ff101adacc0 + [13] 0xe0 fd=0x7ff101adacd0 bk=0x7ff101adacd0 + [14] 0xf0 fd=0x7ff101adace0 bk=0x7ff101adace0 + [15] 0x100 fd=0x7ff101adacf0 bk=0x7ff101adacf0 + [16] 0x110 fd=0x7ff101adad00 bk=0x7ff101adad00 + [17] 0x120 fd=0x7ff101adad10 bk=0x7ff101adad10 + [18] 0x130 fd=0x7ff101adad20 bk=0x7ff101adad20 + [19] 0x140 fd=0x7ff101adad30 bk=0x7ff101adad30 + [20] 0x150 fd=0x7ff101adad40 bk=0x7ff101adad40 + [21] 0x160 fd=0x7ff101adad50 bk=0x7ff101adad50 + [22] 0x170 fd=0x7ff101adad60 bk=0x7ff101adad60 + [23] 0x180 fd=0x7ff101adad70 bk=0x7ff101adad70 + [24] 0x190 fd=0x7ff101adad80 bk=0x7ff101adad80 + [25] 0x1a0 fd=0x7ff101adad90 bk=0x7ff101adad90 + [26] 0x1b0 fd=0x7ff101adada0 bk=0x7ff101adada0 + [27] 0x1c0 fd=0x7ff101adadb0 bk=0x7ff101adadb0 + [28] 0x1d0 fd=0x7ff101adadc0 bk=0x7ff101adadc0 + [29] 0x1e0 fd=0x7ff101adadd0 bk=0x7ff101adadd0 + [30] 0x1f0 fd=0x7ff101adade0 bk=0x7ff101adade0 + [31] 0x200 fd=0x7ff101adadf0 bk=0x7ff101adadf0 + [32] 0x210 fd=0x7ff101adae00 bk=0x7ff101adae00 + [33] 0x220 fd=0x7ff101adae10 bk=0x7ff101adae10 + [34] 0x230 fd=0x7ff101adae20 bk=0x7ff101adae20 + [35] 0x240 fd=0x7ff101adae30 bk=0x7ff101adae30 + [36] 0x250 fd=0x7ff101adae40 bk=0x7ff101adae40 + [37] 0x260 fd=0x7ff101adae50 bk=0x7ff101adae50 + [38] 0x270 fd=0x7ff101adae60 bk=0x7ff101adae60 + [39] 0x280 fd=0x7ff101adae70 bk=0x7ff101adae70 + [40] 0x290 fd=0x7ff101adae80 bk=0x7ff101adae80 + [41] 0x2a0 fd=0x7ff101adae90 bk=0x7ff101adae90 + [42] 0x2b0 fd=0x7ff101adaea0 bk=0x7ff101adaea0 + [43] 0x2c0 fd=0x7ff101adaeb0 bk=0x7ff101adaeb0 + [44] 0x2d0 fd=0x7ff101adaec0 bk=0x7ff101adaec0 + [45] 0x2e0 fd=0x7ff101adaed0 bk=0x7ff101adaed0 + [46] 0x2f0 fd=0x7ff101adaee0 bk=0x7ff101adaee0 + [47] 0x300 fd=0x7ff101adaef0 bk=0x7ff101adaef0 + [48] 0x310 fd=0x7ff101adaf00 bk=0x7ff101adaf00 + [49] 0x320 fd=0x7ff101adaf10 bk=0x7ff101adaf10 + [50] 0x330 fd=0x7ff101adaf20 bk=0x7ff101adaf20 + [51] 0x340 fd=0x7ff101adaf30 bk=0x7ff101adaf30 + [52] 0x350 fd=0x7ff101adaf40 bk=0x7ff101adaf40 + [53] 0x360 fd=0x7ff101adaf50 bk=0x7ff101adaf50 + [54] 0x370 fd=0x7ff101adaf60 bk=0x7ff101adaf60 + [55] 0x380 fd=0x7ff101adaf70 bk=0x7ff101adaf70 + [56] 0x390 fd=0x7ff101adaf80 bk=0x7ff101adaf80 + [57] 0x3a0 fd=0x7ff101adaf90 bk=0x7ff101adaf90 + [58] 0x3b0 fd=0x7ff101adafa0 bk=0x7ff101adafa0 + [59] 0x3c0 fd=0x7ff101adafb0 bk=0x7ff101adafb0 + [60] 0x3d0 fd=0x7ff101adafc0 bk=0x7ff101adafc0 + [61] 0x3e0 fd=0x7ff101adafd0 bk=0x7ff101adafd0 + [62] 0x3f0 fd=0x7ff101adafe0 bk=0x7ff101adafe0 + Large bins + [63] 0x400 fd=0x7ff101adaff0 bk=0x7ff101adaff0 + [64] 0x440 fd=0x7ff101adb000 bk=0x7ff101adb000 + [65] 0x480 fd=0x7ff101adb010 bk=0x7ff101adb010 + [66] 0x4c0 fd=0x7ff101adb020 bk=0x7ff101adb020 + [67] 0x500 fd=0x7ff101adb030 bk=0x7ff101adb030 + [68] 0x540 fd=0x7ff101adb040 bk=0x7ff101adb040 + [69] 0x580 fd=0x7ff101adb050 bk=0x7ff101adb050 + [70] 0x5c0 fd=0x7ff101adb060 bk=0x7ff101adb060 + [71] 0x600 fd=0x7ff101adb070 bk=0x7ff101adb070 + [72] 0x640 fd=0x7ff101adb080 bk=0x7ff101adb080 + [73] 0x680 fd=0x7ff101adb090 bk=0x7ff101adb090 + [74] 0x6c0 fd=0x7ff101adb0a0 bk=0x7ff101adb0a0 + [75] 0x700 fd=0x7ff101adb0b0 bk=0x7ff101adb0b0 + [76] 0x740 fd=0x7ff101adb0c0 bk=0x7ff101adb0c0 + [77] 0x780 fd=0x7ff101adb0d0 bk=0x7ff101adb0d0 + [78] 0x7c0 fd=0x7ff101adb0e0 bk=0x7ff101adb0e0 + [79] 0x800 fd=0x7ff101adb0f0 bk=0x7ff101adb0f0 + [80] 0x840 fd=0x7ff101adb100 bk=0x7ff101adb100 + [81] 0x880 fd=0x7ff101adb110 bk=0x7ff101adb110 + [82] 0x8c0 fd=0x7ff101adb120 bk=0x7ff101adb120 + [83] 0x900 fd=0x7ff101adb130 bk=0x7ff101adb130 + [84] 0x940 fd=0x7ff101adb140 bk=0x7ff101adb140 + [85] 0x980 fd=0x7ff101adb150 bk=0x7ff101adb150 + [86] 0x9c0 fd=0x7ff101adb160 bk=0x7ff101adb160 + [87] 0xa00 fd=0x7ff101adb170 bk=0x7ff101adb170 + [88] 0xa40 fd=0x7ff101adb180 bk=0x7ff101adb180 + [89] 0xa80 fd=0x7ff101adb190 bk=0x7ff101adb190 + [90] 0xac0 fd=0x7ff101adb1a0 bk=0x7ff101adb1a0 + [91] 0xb00 fd=0x7ff101adb1b0 bk=0x7ff101adb1b0 + [92] 0xb40 fd=0x7ff101adb1c0 bk=0x7ff101adb1c0 + [93] 0xb80 fd=0x7ff101adb1d0 bk=0x7ff101adb1d0 + [94] 0xbc0 fd=0x7ff101adb1e0 bk=0x7ff101adb1e0 + [95] 0xc00 fd=0x7ff101adb1f0 bk=0x7ff101adb1f0 + [96] 0xc40 fd=0x7ff101adb200 bk=0x7ff101adb200 + [97] 0xe00 fd=0x7ff101adb210 bk=0x7ff101adb210 + [98] 0x1000 fd=0x7ff101adb220 bk=0x7ff101adb220 + [99] 0x1200 fd=0x7ff101adb230 bk=0x7ff101adb230 + [100] 0x1400 fd=0x7ff101adb240 bk=0x7ff101adb240 + [101] 0x1600 fd=0x7ff101adb250 bk=0x7ff101adb250 + [102] 0x1800 fd=0x7ff101adb260 bk=0x7ff101adb260 + [103] 0x1a00 fd=0x7ff101adb270 bk=0x7ff101adb270 + [104] 0x1c00 fd=0x7ff101adb280 bk=0x7ff101adb280 + [105] 0x1e00 fd=0x7ff101adb290 bk=0x7ff101adb290 + [106] 0x2000 fd=0x7ff101adb2a0 bk=0x7ff101adb2a0 + [107] 0x2200 fd=0x7ff101adb2b0 bk=0x7ff101adb2b0 + [108] 0x2400 fd=0x7ff101adb2c0 bk=0x7ff101adb2c0 + [109] 0x2600 fd=0x7ff101adb2d0 bk=0x7ff101adb2d0 + [110] 0x2800 fd=0x7ff101adb2e0 bk=0x7ff101adb2e0 + [111] 0x2a00 fd=0x7ff101adb2f0 bk=0x7ff101adb2f0 + [112] 0x3000 fd=0x7ff101adb300 bk=0x7ff101adb300 + [113] 0x4000 fd=0x7ff101adb310 bk=0x7ff101adb310 + [114] 0x5000 fd=0x7ff101adb320 bk=0x7ff101adb320 + [115] 0x6000 fd=0x7ff101adb330 bk=0x7ff101adb330 + [116] 0x7000 fd=0x7ff101adb340 bk=0x7ff101adb340 + [117] 0x8000 fd=0x7ff101adb350 bk=0x7ff101adb350 + [118] 0x9000 fd=0x7ff101adb360 bk=0x7ff101adb360 + [119] 0xa000 fd=0x7ff101adb370 bk=0x7ff101adb370 + [120] 0x10000 fd=0x7ff101adb380 bk=0x7ff101adb380 + [121] 0x18000 fd=0x7ff101adb390 bk=0x7ff101adb390 + [122] 0x20000 fd=0x7ff101adb3a0 bk=0x7ff101adb3a0 + [123] 0x28000 fd=0x7ff101adb3b0 bk=0x7ff101adb3b0 + [124] 0x40000 fd=0x7ff101adb3c0 bk=0x7ff101adb3c0 + [125] 0x80000 fd=0x7ff101adb3d0 bk=0x7ff101adb3d0 + [126] 0x100000 fd=0x7ff101adb3e0 bk=0x7ff101adb3e0 + binmap = [0x10, 0x0, 0x0, 0x0] + next = 0x7ff101adaba0 + next_free = 0x0 + attached_threads = 0x1 + system_mem = 0x21000 + max_system_mem = 0x21000 + =====... + + """ + return self.all_arenas_malloc_states()[arena_index] + + def all_arenas_malloc_states(self): + """Returns the malloc states of all arenas + + Returns: + list of :class:`MallocState` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas_malloc_states()[0]) + ======================== Malloc State (0x7f6c0f17eba0) ======================== + mutex = 0x0 + flags = 0x0 + have_fastchunks = 0x0 + fastbinsY + [0] 0x20 => 0x0 + [1] 0x30 => 0x0 + [2] 0x40 => 0x0 + [3] 0x50 => 0x0 + [4] 0x60 => 0x0 + [5] 0x70 => 0x0 + [6] 0x80 => 0x0 + [7] 0x90 => 0x0 + [8] 0xa0 => 0x0 + [9] 0xb0 => 0x0 + top = 0x55f7cec4df30 + last_remainder = 0x0 + bins + Unsorted bins + [0] fd=0x7f6c0f17ec00 bk=0x7f6c0f17ec00 + Small bins + [1] 0x20 fd=0x7f6c0f17ec10 bk=0x7f6c0f17ec10 + [2] 0x30 fd=0x7f6c0f17ec20 bk=0x7f6c0f17ec20 + [3] 0x40 fd=0x7f6c0f17ec30 bk=0x7f6c0f17ec30 + ........... + [60] 0x3d0 fd=0x7f6c0f17efc0 bk=0x7f6c0f17efc0 + [61] 0x3e0 fd=0x7f6c0f17efd0 bk=0x7f6c0f17efd0 + [62] 0x3f0 fd=0x7f6c0f17efe0 bk=0x7f6c0f17efe0 + Large bins + [63] 0x400 fd=0x7f6c0f17eff0 bk=0x7f6c0f17eff0 + [64] 0x440 fd=0x7f6c0f17f000 bk=0x7f6c0f17f000 + [65] 0x480 fd=0x7f6c0f17f010 bk=0x7f6c0f17f010 + [66] 0x4c0 fd=0x7f6c0f17f020 bk=0x7f6c0f17f020 + ........... + [124] 0x40000 fd=0x7f6c0f17f3c0 bk=0x7f6c0f17f3c0 + [125] 0x80000 fd=0x7f6c0f17f3d0 bk=0x7f6c0f17f3d0 + [126] 0x100000 fd=0x7f6c0f17f3e0 bk=0x7f6c0f17f3e0 + binmap = [0x0, 0x0, 0x0, 0x0] + next = 0x7f6c0f17eba0 + next_free = 0x0 + attached_threads = 0x1 + system_mem = 0x21000 + max_system_mem = 0x21000 + ================================================================================ + + Tests: + >>> c = Corefile(pwnlib.data.heap.x86_64.get("core.32.sample1")) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.all_arenas_malloc_states()[0]) # doctest: +ELLIPSIS + ===... Malloc State (0x7ff101adaba0) ===... + mutex = 0x0 + flags = 0x0 + have_fastchunks = 0x0 + fastbinsY + [0] 0x20 => 0x0 + [1] 0x30 => 0x0 + [2] 0x40 => 0x0 + [3] 0x50 => 0x0 + [4] 0x60 => 0x0 + [5] 0x70 => 0x0 + [6] 0x80 => 0x0 + [7] 0x90 => 0x0 + [8] 0xa0 => 0x0 + [9] 0xb0 => 0x0 + top = 0x55c9539e11a0 + last_remainder = 0x0 + bins + Unsorted bins + [0] fd=0x7ff101adac00 bk=0x7ff101adac00 + Small bins + [1] 0x20 fd=0x7ff101adac10 bk=0x7ff101adac10 + [2] 0x30 fd=0x7ff101adac20 bk=0x7ff101adac20 + [3] 0x40 fd=0x55c9539df610 bk=0x55c9539e0110 + [4] 0x50 fd=0x7ff101adac40 bk=0x7ff101adac40 + [5] 0x60 fd=0x7ff101adac50 bk=0x7ff101adac50 + [6] 0x70 fd=0x7ff101adac60 bk=0x7ff101adac60 + [7] 0x80 fd=0x7ff101adac70 bk=0x7ff101adac70 + [8] 0x90 fd=0x7ff101adac80 bk=0x7ff101adac80 + [9] 0xa0 fd=0x7ff101adac90 bk=0x7ff101adac90 + [10] 0xb0 fd=0x7ff101adaca0 bk=0x7ff101adaca0 + [11] 0xc0 fd=0x7ff101adacb0 bk=0x7ff101adacb0 + [12] 0xd0 fd=0x7ff101adacc0 bk=0x7ff101adacc0 + [13] 0xe0 fd=0x7ff101adacd0 bk=0x7ff101adacd0 + [14] 0xf0 fd=0x7ff101adace0 bk=0x7ff101adace0 + [15] 0x100 fd=0x7ff101adacf0 bk=0x7ff101adacf0 + [16] 0x110 fd=0x7ff101adad00 bk=0x7ff101adad00 + [17] 0x120 fd=0x7ff101adad10 bk=0x7ff101adad10 + [18] 0x130 fd=0x7ff101adad20 bk=0x7ff101adad20 + [19] 0x140 fd=0x7ff101adad30 bk=0x7ff101adad30 + [20] 0x150 fd=0x7ff101adad40 bk=0x7ff101adad40 + [21] 0x160 fd=0x7ff101adad50 bk=0x7ff101adad50 + [22] 0x170 fd=0x7ff101adad60 bk=0x7ff101adad60 + [23] 0x180 fd=0x7ff101adad70 bk=0x7ff101adad70 + [24] 0x190 fd=0x7ff101adad80 bk=0x7ff101adad80 + [25] 0x1a0 fd=0x7ff101adad90 bk=0x7ff101adad90 + [26] 0x1b0 fd=0x7ff101adada0 bk=0x7ff101adada0 + [27] 0x1c0 fd=0x7ff101adadb0 bk=0x7ff101adadb0 + [28] 0x1d0 fd=0x7ff101adadc0 bk=0x7ff101adadc0 + [29] 0x1e0 fd=0x7ff101adadd0 bk=0x7ff101adadd0 + [30] 0x1f0 fd=0x7ff101adade0 bk=0x7ff101adade0 + [31] 0x200 fd=0x7ff101adadf0 bk=0x7ff101adadf0 + [32] 0x210 fd=0x7ff101adae00 bk=0x7ff101adae00 + [33] 0x220 fd=0x7ff101adae10 bk=0x7ff101adae10 + [34] 0x230 fd=0x7ff101adae20 bk=0x7ff101adae20 + [35] 0x240 fd=0x7ff101adae30 bk=0x7ff101adae30 + [36] 0x250 fd=0x7ff101adae40 bk=0x7ff101adae40 + [37] 0x260 fd=0x7ff101adae50 bk=0x7ff101adae50 + [38] 0x270 fd=0x7ff101adae60 bk=0x7ff101adae60 + [39] 0x280 fd=0x7ff101adae70 bk=0x7ff101adae70 + [40] 0x290 fd=0x7ff101adae80 bk=0x7ff101adae80 + [41] 0x2a0 fd=0x7ff101adae90 bk=0x7ff101adae90 + [42] 0x2b0 fd=0x7ff101adaea0 bk=0x7ff101adaea0 + [43] 0x2c0 fd=0x7ff101adaeb0 bk=0x7ff101adaeb0 + [44] 0x2d0 fd=0x7ff101adaec0 bk=0x7ff101adaec0 + [45] 0x2e0 fd=0x7ff101adaed0 bk=0x7ff101adaed0 + [46] 0x2f0 fd=0x7ff101adaee0 bk=0x7ff101adaee0 + [47] 0x300 fd=0x7ff101adaef0 bk=0x7ff101adaef0 + [48] 0x310 fd=0x7ff101adaf00 bk=0x7ff101adaf00 + [49] 0x320 fd=0x7ff101adaf10 bk=0x7ff101adaf10 + [50] 0x330 fd=0x7ff101adaf20 bk=0x7ff101adaf20 + [51] 0x340 fd=0x7ff101adaf30 bk=0x7ff101adaf30 + [52] 0x350 fd=0x7ff101adaf40 bk=0x7ff101adaf40 + [53] 0x360 fd=0x7ff101adaf50 bk=0x7ff101adaf50 + [54] 0x370 fd=0x7ff101adaf60 bk=0x7ff101adaf60 + [55] 0x380 fd=0x7ff101adaf70 bk=0x7ff101adaf70 + [56] 0x390 fd=0x7ff101adaf80 bk=0x7ff101adaf80 + [57] 0x3a0 fd=0x7ff101adaf90 bk=0x7ff101adaf90 + [58] 0x3b0 fd=0x7ff101adafa0 bk=0x7ff101adafa0 + [59] 0x3c0 fd=0x7ff101adafb0 bk=0x7ff101adafb0 + [60] 0x3d0 fd=0x7ff101adafc0 bk=0x7ff101adafc0 + [61] 0x3e0 fd=0x7ff101adafd0 bk=0x7ff101adafd0 + [62] 0x3f0 fd=0x7ff101adafe0 bk=0x7ff101adafe0 + Large bins + [63] 0x400 fd=0x7ff101adaff0 bk=0x7ff101adaff0 + [64] 0x440 fd=0x7ff101adb000 bk=0x7ff101adb000 + [65] 0x480 fd=0x7ff101adb010 bk=0x7ff101adb010 + [66] 0x4c0 fd=0x7ff101adb020 bk=0x7ff101adb020 + [67] 0x500 fd=0x7ff101adb030 bk=0x7ff101adb030 + [68] 0x540 fd=0x7ff101adb040 bk=0x7ff101adb040 + [69] 0x580 fd=0x7ff101adb050 bk=0x7ff101adb050 + [70] 0x5c0 fd=0x7ff101adb060 bk=0x7ff101adb060 + [71] 0x600 fd=0x7ff101adb070 bk=0x7ff101adb070 + [72] 0x640 fd=0x7ff101adb080 bk=0x7ff101adb080 + [73] 0x680 fd=0x7ff101adb090 bk=0x7ff101adb090 + [74] 0x6c0 fd=0x7ff101adb0a0 bk=0x7ff101adb0a0 + [75] 0x700 fd=0x7ff101adb0b0 bk=0x7ff101adb0b0 + [76] 0x740 fd=0x7ff101adb0c0 bk=0x7ff101adb0c0 + [77] 0x780 fd=0x7ff101adb0d0 bk=0x7ff101adb0d0 + [78] 0x7c0 fd=0x7ff101adb0e0 bk=0x7ff101adb0e0 + [79] 0x800 fd=0x7ff101adb0f0 bk=0x7ff101adb0f0 + [80] 0x840 fd=0x7ff101adb100 bk=0x7ff101adb100 + [81] 0x880 fd=0x7ff101adb110 bk=0x7ff101adb110 + [82] 0x8c0 fd=0x7ff101adb120 bk=0x7ff101adb120 + [83] 0x900 fd=0x7ff101adb130 bk=0x7ff101adb130 + [84] 0x940 fd=0x7ff101adb140 bk=0x7ff101adb140 + [85] 0x980 fd=0x7ff101adb150 bk=0x7ff101adb150 + [86] 0x9c0 fd=0x7ff101adb160 bk=0x7ff101adb160 + [87] 0xa00 fd=0x7ff101adb170 bk=0x7ff101adb170 + [88] 0xa40 fd=0x7ff101adb180 bk=0x7ff101adb180 + [89] 0xa80 fd=0x7ff101adb190 bk=0x7ff101adb190 + [90] 0xac0 fd=0x7ff101adb1a0 bk=0x7ff101adb1a0 + [91] 0xb00 fd=0x7ff101adb1b0 bk=0x7ff101adb1b0 + [92] 0xb40 fd=0x7ff101adb1c0 bk=0x7ff101adb1c0 + [93] 0xb80 fd=0x7ff101adb1d0 bk=0x7ff101adb1d0 + [94] 0xbc0 fd=0x7ff101adb1e0 bk=0x7ff101adb1e0 + [95] 0xc00 fd=0x7ff101adb1f0 bk=0x7ff101adb1f0 + [96] 0xc40 fd=0x7ff101adb200 bk=0x7ff101adb200 + [97] 0xe00 fd=0x7ff101adb210 bk=0x7ff101adb210 + [98] 0x1000 fd=0x7ff101adb220 bk=0x7ff101adb220 + [99] 0x1200 fd=0x7ff101adb230 bk=0x7ff101adb230 + [100] 0x1400 fd=0x7ff101adb240 bk=0x7ff101adb240 + [101] 0x1600 fd=0x7ff101adb250 bk=0x7ff101adb250 + [102] 0x1800 fd=0x7ff101adb260 bk=0x7ff101adb260 + [103] 0x1a00 fd=0x7ff101adb270 bk=0x7ff101adb270 + [104] 0x1c00 fd=0x7ff101adb280 bk=0x7ff101adb280 + [105] 0x1e00 fd=0x7ff101adb290 bk=0x7ff101adb290 + [106] 0x2000 fd=0x7ff101adb2a0 bk=0x7ff101adb2a0 + [107] 0x2200 fd=0x7ff101adb2b0 bk=0x7ff101adb2b0 + [108] 0x2400 fd=0x7ff101adb2c0 bk=0x7ff101adb2c0 + [109] 0x2600 fd=0x7ff101adb2d0 bk=0x7ff101adb2d0 + [110] 0x2800 fd=0x7ff101adb2e0 bk=0x7ff101adb2e0 + [111] 0x2a00 fd=0x7ff101adb2f0 bk=0x7ff101adb2f0 + [112] 0x3000 fd=0x7ff101adb300 bk=0x7ff101adb300 + [113] 0x4000 fd=0x7ff101adb310 bk=0x7ff101adb310 + [114] 0x5000 fd=0x7ff101adb320 bk=0x7ff101adb320 + [115] 0x6000 fd=0x7ff101adb330 bk=0x7ff101adb330 + [116] 0x7000 fd=0x7ff101adb340 bk=0x7ff101adb340 + [117] 0x8000 fd=0x7ff101adb350 bk=0x7ff101adb350 + [118] 0x9000 fd=0x7ff101adb360 bk=0x7ff101adb360 + [119] 0xa000 fd=0x7ff101adb370 bk=0x7ff101adb370 + [120] 0x10000 fd=0x7ff101adb380 bk=0x7ff101adb380 + [121] 0x18000 fd=0x7ff101adb390 bk=0x7ff101adb390 + [122] 0x20000 fd=0x7ff101adb3a0 bk=0x7ff101adb3a0 + [123] 0x28000 fd=0x7ff101adb3b0 bk=0x7ff101adb3b0 + [124] 0x40000 fd=0x7ff101adb3c0 bk=0x7ff101adb3c0 + [125] 0x80000 fd=0x7ff101adb3d0 bk=0x7ff101adb3d0 + [126] 0x100000 fd=0x7ff101adb3e0 bk=0x7ff101adb3e0 + binmap = [0x10, 0x0, 0x0, 0x0] + next = 0x7ff101adaba0 + next_free = 0x0 + attached_threads = 0x1 + system_mem = 0x21000 + max_system_mem = 0x21000 + =====... + + """ + return self._malloc_state_parser.parse_all_from_main_malloc_state_address( + self._main_arena_address + ) + + def heap(self, arena_index=0): + """Returns the heap of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`Heap` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> heap = he.heap() + >>> len(heap.chunks) + 574 + >>> hex(heap.top.address) + '0x5575a9e19080' + >>> print(heap) + ============================ Heap (0x5575a9e08000) ============================ + 0x5575a9e08000 0x290 PREV_IN_USE + 05 00 01 00 00 00 00 00 00 00 00 00 00 00 02 00 ................ + 0x5575a9e08290 0x20 PREV_IN_USE + 65 73 5f 45 53 2e 55 54 46 2d 38 00 00 00 00 00 es_ES.UTF-8..... + 0x5575a9e082b0 0x80 PREV_IN_USE + 00 00 00 00 00 00 00 00 a0 82 e0 a9 75 55 00 00 ............uU.. + 0x5575a9e08330 0x310 PREV_IN_USE + a0 82 e0 a9 75 55 00 00 40 fa 05 31 1a 7f 00 00 ....uU..@..1.... + ............. + 0x5575a9e18110 0x20 PREV_IN_USE + 4f 4c 44 50 57 44 3d 2f 68 6f 6d 65 2f 75 73 65 OLDPWD=/home/use + 0x5575a9e18130 0x130 PREV_IN_USE + 4c 43 5f 43 54 59 50 45 3d 65 6e 5f 55 53 2e 55 LC_CTYPE=en_US.U + 0x5575a9e18380 0x20 + 65 73 5f 45 53 2e 55 54 46 2d 38 00 00 00 00 00 es_ES.UTF-8..... + 0x5575a9e19080 0xff80 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + ================================================================================ + + + Tests: + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.tcaches1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.heap()) # doctest: +ELLIPSIS + ===... Heap (0x5597c998c000) ===... + 0x5597c998c000 0x290 PREV_IN_USE + 00 00 00 00 03 00 00 00 03 00 00 00 00 00 00 00 ................ + 0x5597c998c290 0x40 PREV_IN_USE + 8c 99 7c 59 05 00 00 00 10 c0 98 c9 97 55 00 00 ..|Y.........U.. + 0x5597c998c2d0 0x60 PREV_IN_USE + 8c 99 7c 59 05 00 00 00 10 c0 98 c9 97 55 00 00 ..|Y.........U.. + 0x5597c998c330 0x40 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998c370 0x40 PREV_IN_USE + 2c 5b e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 ,[...U.......U.. + 0x5597c998c3b0 0x60 PREV_IN_USE + 6c 5b e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 l[...U.......U.. + 0x5597c998c410 0x40 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998c450 0x40 PREV_IN_USE + 0c 5a e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 .Z...U.......U.. + 0x5597c998c490 0x60 PREV_IN_USE + 4c 5a e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 LZ...U.......U.. + 0x5597c998c4f0 0x40 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998c530 0x1010 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998d540 0x1fac0 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + ====... + + """ + malloc_state = self.malloc_state(arena_index) + return self._heap_parser.parse_from_malloc_state(malloc_state) + + def all_arenas_heaps(self): + """Returns the heaps of all arenas + + Returns: + :obj:`list` of :class:`Heap` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas_heaps()[0]) + ============================ Heap (0x5575a9e08000) ============================ + 0x5575a9e08000 0x290 PREV_IN_USE + 05 00 01 00 00 00 00 00 00 00 00 00 00 00 02 00 ................ + 0x5575a9e08290 0x20 PREV_IN_USE + 65 73 5f 45 53 2e 55 54 46 2d 38 00 00 00 00 00 es_ES.UTF-8..... + 0x5575a9e082b0 0x80 PREV_IN_USE + 00 00 00 00 00 00 00 00 a0 82 e0 a9 75 55 00 00 ............uU.. + 0x5575a9e08330 0x310 PREV_IN_USE + a0 82 e0 a9 75 55 00 00 40 fa 05 31 1a 7f 00 00 ....uU..@..1.... + ............. + 0x5575a9e18110 0x20 PREV_IN_USE + 4f 4c 44 50 57 44 3d 2f 68 6f 6d 65 2f 75 73 65 OLDPWD=/home/use + 0x5575a9e18130 0x130 PREV_IN_USE + 4c 43 5f 43 54 59 50 45 3d 65 6e 5f 55 53 2e 55 LC_CTYPE=en_US.U + 0x5575a9e18380 0x20 + 65 73 5f 45 53 2e 55 54 46 2d 38 00 00 00 00 00 es_ES.UTF-8..... + 0x5575a9e19080 0xff80 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + ================================================================================ + + + Tests: + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.tcaches1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.all_arenas_heaps()[0]) # doctest: +ELLIPSIS + ===... Heap (0x5597c998c000) ===... + 0x5597c998c000 0x290 PREV_IN_USE + 00 00 00 00 03 00 00 00 03 00 00 00 00 00 00 00 ................ + 0x5597c998c290 0x40 PREV_IN_USE + 8c 99 7c 59 05 00 00 00 10 c0 98 c9 97 55 00 00 ..|Y.........U.. + 0x5597c998c2d0 0x60 PREV_IN_USE + 8c 99 7c 59 05 00 00 00 10 c0 98 c9 97 55 00 00 ..|Y.........U.. + 0x5597c998c330 0x40 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998c370 0x40 PREV_IN_USE + 2c 5b e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 ,[...U.......U.. + 0x5597c998c3b0 0x60 PREV_IN_USE + 6c 5b e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 l[...U.......U.. + 0x5597c998c410 0x40 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998c450 0x40 PREV_IN_USE + 0c 5a e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 .Z...U.......U.. + 0x5597c998c490 0x60 PREV_IN_USE + 4c 5a e4 90 92 55 00 00 10 c0 98 c9 97 55 00 00 LZ...U.......U.. + 0x5597c998c4f0 0x40 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998c530 0x1010 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 0x5597c998d540 0x1fac0 PREV_IN_USE + 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + =====... + + """ + malloc_states = self.all_arenas_malloc_states() + heaps = [] + for malloc_state in malloc_states: + heaps.append( + self._heap_parser.parse_from_malloc_state(malloc_state) + ) + return heaps + + def unsorted_bin(self, arena_index=0): + """Returns the unsorted bin of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`UnsortedBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.unsorted_bin()) + ================================ Unsorted Bins ================================ + [0] Unsorted Bin (2) => Chunk(0x5597afccc330 0x1010 PREV_IN_USE) => Chunk(0x5597afccb2e0 0x1010 PREV_IN_USE) => 0x7f1c523a4c00 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.unsorted_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.unsorted_bin()) # doctest: +ELLIPSIS + ===... Unsorted Bins ===... + [0] Unsorted Bin (2) => Chunk(0x55c9a83ed330 0x1010 PREV_IN_USE) => Chunk(0x55c9a83ec2e0 0x1010 PREV_IN_USE) => 0x7fcbe729bc00 + ======... + + """ + malloc_state = self.malloc_state(arena_index) + return self._bin_parser.parse_unsorted_bin_from_malloc_state( + malloc_state + ) + + def all_arenas_unsorted_bins(self): + """Returns the unsorted bins of all arenas + + Returns: + list of :class:`UnsortedBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas_unsorted_bins()[0]) + ================================ Unsorted Bins ================================ + [0] Unsorted Bin (2) => Chunk(0x5597afccc330 0x1010 PREV_IN_USE) => Chunk(0x5597afccb2e0 0x1010 PREV_IN_USE) => 0x7f1c523a4c00 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.unsorted_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.all_arenas_unsorted_bins()[0]) # doctest: +ELLIPSIS + ===... Unsorted Bins ===... + [0] Unsorted Bin (2) => Chunk(0x55c9a83ed330 0x1010 PREV_IN_USE) => Chunk(0x55c9a83ec2e0 0x1010 PREV_IN_USE) => 0x7fcbe729bc00 + ======... + """ + unsorted_bins = [] + for malloc_state in self.all_arenas_malloc_states(): + unsorted_bins.append( + self._bin_parser.parse_unsorted_bin_from_malloc_state( + malloc_state + ) + ) + return unsorted_bins + + def small_bins(self, arena_index=0): + """Returns the small bins of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`SmallBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.small_bins()) + ================================== Small Bins ================================== + [6] Small Bin 0x80 (3) => Chunk(0x55f241d7fed0 0x80 PREV_IN_USE) => Chunk(0x55f241d80070 0x80 PREV_IN_USE) => Chunk(0x55f241d80210 0x80 PREV_IN_USE) => 0x7ff159b83c70 + [8] Small Bin 0xa0 (3) => Chunk(0x55f241d80130 0xa0 PREV_IN_USE) => Chunk(0x55f241d7ff90 0xa0 PREV_IN_USE) => Chunk(0x55f241d7fdf0 0xa0 PREV_IN_USE) => 0x7ff159b83c90 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.small_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.small_bins()) # doctest: +ELLIPSIS + ===... Small Bins ===... + [6] Small Bin 0x80 (3) => Chunk(0x55bed6e87ed0 0x80 PREV_IN_USE) => Chunk(0x55bed6e88070 0x80 PREV_IN_USE) => Chunk(0x55bed6e88210 0x80 PREV_IN_USE) => 0x7feeee344c70 + [8] Small Bin 0xa0 (3) => Chunk(0x55bed6e88130 0xa0 PREV_IN_USE) => Chunk(0x55bed6e87f90 0xa0 PREV_IN_USE) => Chunk(0x55bed6e87df0 0xa0 PREV_IN_USE) => 0x7feeee344c90 + ======... + """ + malloc_state = self.malloc_state(arena_index) + return self._bin_parser.parse_small_bins_from_malloc_state( + malloc_state, + ) + + def all_arenas_small_bins(self): + """Returns the small bins of all arenas + + Returns: + list of :class:`SmallBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas_small_bins()[0]) + ================================== Small Bins ================================== + [6] Small Bin 0x80 (3) => Chunk(0x55f241d7fed0 0x80 PREV_IN_USE) => Chunk(0x55f241d80070 0x80 PREV_IN_USE) => Chunk(0x55f241d80210 0x80 PREV_IN_USE) => 0x7ff159b83c70 + [8] Small Bin 0xa0 (3) => Chunk(0x55f241d80130 0xa0 PREV_IN_USE) => Chunk(0x55f241d7ff90 0xa0 PREV_IN_USE) => Chunk(0x55f241d7fdf0 0xa0 PREV_IN_USE) => 0x7ff159b83c90 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.small_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.all_arenas_small_bins()[0]) # doctest: +ELLIPSIS + ===... Small Bins ===... + [6] Small Bin 0x80 (3) => Chunk(0x55bed6e87ed0 0x80 PREV_IN_USE) => Chunk(0x55bed6e88070 0x80 PREV_IN_USE) => Chunk(0x55bed6e88210 0x80 PREV_IN_USE) => 0x7feeee344c70 + [8] Small Bin 0xa0 (3) => Chunk(0x55bed6e88130 0xa0 PREV_IN_USE) => Chunk(0x55bed6e87f90 0xa0 PREV_IN_USE) => Chunk(0x55bed6e87df0 0xa0 PREV_IN_USE) => 0x7feeee344c90 + ======... + """ + all_small_bins = [] + for malloc_state in self.all_arenas_malloc_states(): + all_small_bins.append( + self._bin_parser.parse_small_bins_from_malloc_state( + malloc_state + ) + ) + return all_small_bins + + def large_bins(self, arena_index=0): + """Returns the large bins of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`LargeBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.large_bins()) + ================================== Large Bins ================================== + [35] Large Bin 0x1000 (3) => Chunk(0x55efb3ce9290 0x1010 PREV_IN_USE) => Chunk(0x55efb3cee1d0 0x1010 PREV_IN_USE) => Chunk(0x55efb3ceba30 0x1010 PREV_IN_USE) => 0x7f3cddff8220 + [38] Large Bin 0x1600 (4) => Chunk(0x55efb3cea2e0 0x1710 PREV_IN_USE) => Chunk(0x55efb3cf19c0 0x1710 PREV_IN_USE) => Chunk(0x55efb3cef220 0x1710 PREV_IN_USE) => Chunk(0x55efb3ceca80 0x1710 PREV_IN_USE) => 0x7f3cddff8250 + ================================================================================ + + + Tests: + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.large_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.large_bins()) # doctest: +ELLIPSIS + ===... Large Bins ===... + [35] Large Bin 0x1000 (3) => Chunk(0x558fce0db290 0x1010 PREV_IN_USE) => Chunk(0x558fce0e01d0 0x1010 PREV_IN_USE) => Chunk(0x558fce0dda30 0x1010 PREV_IN_USE) => 0x7f90c8f3e220 + [38] Large Bin 0x1600 (4) => Chunk(0x558fce0dc2e0 0x1710 PREV_IN_USE) => Chunk(0x558fce0e39c0 0x1710 PREV_IN_USE) => Chunk(0x558fce0e1220 0x1710 PREV_IN_USE) => Chunk(0x558fce0dea80 0x1710 PREV_IN_USE) => 0x7f90c8f3e250 + ======... + """ + malloc_state = self.malloc_state(arena_index) + return self._bin_parser.parse_large_bins_from_malloc_state( + malloc_state, + ) + + def all_arenas_large_bins(self): + """Returns the large bins of all arenas + + Returns: + :obj:`list` of :class:`LargeBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas_large_bins()) # doctest: +ELLIPSIS + ================================== Large Bins ================================== + [35] Large Bin 0x1000 (3) => Chunk(0x55efb3ce9290 0x1010 PREV_IN_USE) => Chunk(0x55efb3cee1d0 0x1010 PREV_IN_USE) => Chunk(0x55efb3ceba30 0x1010 PREV_IN_USE) => 0x7f3cddff8220 + [38] Large Bin 0x1600 (4) => Chunk(0x55efb3cea2e0 0x1710 PREV_IN_USE) => Chunk(0x55efb3cf19c0 0x1710 PREV_IN_USE) => Chunk(0x55efb3cef220 0x1710 PREV_IN_USE) => Chunk(0x55efb3ceca80 0x1710 PREV_IN_USE) => 0x7f3cddff8250 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.large_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.all_arenas_large_bins()[0]) # doctest: +ELLIPSIS + ===... Large Bins ===... + [35] Large Bin 0x1000 (3) => Chunk(0x558fce0db290 0x1010 PREV_IN_USE) => Chunk(0x558fce0e01d0 0x1010 PREV_IN_USE) => Chunk(0x558fce0dda30 0x1010 PREV_IN_USE) => 0x7f90c8f3e220 + [38] Large Bin 0x1600 (4) => Chunk(0x558fce0dc2e0 0x1710 PREV_IN_USE) => Chunk(0x558fce0e39c0 0x1710 PREV_IN_USE) => Chunk(0x558fce0e1220 0x1710 PREV_IN_USE) => Chunk(0x558fce0dea80 0x1710 PREV_IN_USE) => 0x7f90c8f3e250 + ======... + """ + all_large_bins = [] + for malloc_state in self.all_arenas_malloc_states(): + all_large_bins.append( + self._bin_parser.parse_large_bins_from_malloc_state( + malloc_state + ) + ) + return all_large_bins + + def fast_bins(self, arena_index=0): + """Returns the fast_bins of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`FastBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.fast_bins()) + ================================== Fast Bins ================================== + [0] Fast Bin 0x20 (2) => Chunk(0x1330140 0x20 PREV_IN_USE) => Chunk(0x1330070 0x20 PREV_IN_USE) => 0x0 + [3] Fast Bin 0x50 (2) => Chunk(0x13300d0 0x50 PREV_IN_USE) => Chunk(0x1330000 0x50 PREV_IN_USE) => 0x0 + ================================================================================ + + Tests: + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.23.fast_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.23.so')) + >>> print(he.fast_bins()) # doctest: +ELLIPSIS + ===... Fast Bins ===... + [0] Fast Bin 0x20 (2) => Chunk(0x1b5c140 0x20 PREV_IN_USE) => Chunk(0x1b5c070 0x20 PREV_IN_USE) => 0x0 + [3] Fast Bin 0x50 (2) => Chunk(0x1b5c0d0 0x50 PREV_IN_USE) => Chunk(0x1b5c000 0x50 PREV_IN_USE) => 0x0 + ======... + """ + malloc_state = self.malloc_state(arena_index) + return self._fast_bin_parser.parse_all_from_malloc_state(malloc_state) + + def all_arenas_fast_bins(self): + """Returns the small bins of all arenas + + Returns: + :obj:`list` of :class:`FastBins` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas_fast_bins()[0]) + ================================== Fast Bins ================================== + [0] Fast Bin 0x20 (2) => Chunk(0x1b5c140 0x20 PREV_IN_USE) => Chunk(0x1b5c070 0x20 PREV_IN_USE) => 0x0 + [3] Fast Bin 0x50 (2) => Chunk(0x1b5c0d0 0x50 PREV_IN_USE) => Chunk(0x1b5c000 0x50 PREV_IN_USE) => 0x0 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.23.fast_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.23.so')) + >>> print(he.all_arenas_fast_bins()[0]) # doctest: +ELLIPSIS + ===... Fast Bins ===... + [0] Fast Bin 0x20 (2) => Chunk(0x1b5c140 0x20 PREV_IN_USE) => Chunk(0x1b5c070 0x20 PREV_IN_USE) => 0x0 + [3] Fast Bin 0x50 (2) => Chunk(0x1b5c0d0 0x50 PREV_IN_USE) => Chunk(0x1b5c000 0x50 PREV_IN_USE) => 0x0 + ======... + """ + malloc_states = self.all_arenas_malloc_states() + all_fast_bins = [] + for malloc_state in malloc_states: + all_fast_bins.append( + self._fast_bin_parser.parse_all_from_malloc_state(malloc_state) + ) + return all_fast_bins + + def tcaches(self, arena_index=0): + """Returns the tcaches of the arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`Tcaches` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> he.tcaches_enabled + True + >>> print(he.tcaches()) + =================================== Tcaches =================================== + [23] Tcache 0x188 (1) => Chunk(0x56383e3c0250 0x190 PREV_IN_USE) => 0x0 + [41] Tcache 0x2a8 (1) => Chunk(0x56383e3bffa0 0x2b0 PREV_IN_USE) => 0x0 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.tcaches1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> he.tcaches_enabled + True + >>> print(he.tcaches()) # doctest: +ELLIPSIS + ===... Tcaches ===... + [10] Tcache 0xb8 (3) => Chunk(0x5597c998c460 0x40 PREV_IN_USE) => Chunk(0x5597c998c380 0x40 PREV_IN_USE) => Chunk(0x5597c998c2a0 0x40 PREV_IN_USE) => 0x0 + [12] Tcache 0xd8 (3) => Chunk(0x5597c998c4a0 0x60 PREV_IN_USE) => Chunk(0x5597c998c3c0 0x60 PREV_IN_USE) => Chunk(0x5597c998c2e0 0x60 PREV_IN_USE) => 0x0 + ======... + + Tcaches were included in libc 2.26 so trying to access them in previous versions raises an error + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.23.fast_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.23.so')) + >>> try: + ... he.tcaches() + ... except NoTcacheError as e: + ... print(e) + Tcache are not available in the current libc + + """ + if not self.tcaches_enabled: + raise NoTcacheError() + + malloc_state = self.malloc_state(arena_index) + return self._tcache_parser.parse_all_from_malloc_state(malloc_state) + + def all_arenas_tcaches(self): + """Returns the tcaches of all arenas + + Returns: + :obj:`list` of :class:`Tcaches` + + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> he.tcaches_enabled + True + >>> print(he.all_arenas_tcaches()[0]) # doctest: +SKIP + =================================== Tcaches =================================== + [10] Tcache 0xb8 (3) => Chunk(0x5597c998c460 0x40 PREV_IN_USE) => Chunk(0x5597c998c380 0x40 PREV_IN_USE) => Chunk(0x5597c998c2a0 0x40 PREV_IN_USE) => 0x0 + [12] Tcache 0xd8 (3) => Chunk(0x5597c998c4a0 0x60 PREV_IN_USE) => Chunk(0x5597c998c3c0 0x60 PREV_IN_USE) => Chunk(0x5597c998c2e0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.tcaches1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> he.tcaches_enabled + True + >>> print(he.all_arenas_tcaches()[0]) # doctest: +ELLIPSIS + ===... Tcaches ===... + [10] Tcache 0xb8 (3) => Chunk(0x5597c998c460 0x40 PREV_IN_USE) => Chunk(0x5597c998c380 0x40 PREV_IN_USE) => Chunk(0x5597c998c2a0 0x40 PREV_IN_USE) => 0x0 + [12] Tcache 0xd8 (3) => Chunk(0x5597c998c4a0 0x60 PREV_IN_USE) => Chunk(0x5597c998c3c0 0x60 PREV_IN_USE) => Chunk(0x5597c998c2e0 0x60 PREV_IN_USE) => 0x0 + ======... + + Tcaches were included in libc 2.26 so trying to access them in previous versions raises an error + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.23.fast_bins1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.23.so')) + >>> try: + ... he.all_arenas_tcaches() + ... except NoTcacheError as e: + ... print(e) + Tcache are not available in the current libc + """ + if not self.tcaches_enabled: + raise NoTcacheError() + + malloc_states = self.all_arenas_malloc_states() + all_tcaches = [] + for malloc_state in malloc_states: + all_tcaches.append( + self._tcache_parser.parse_all_from_malloc_state(malloc_state) + ) + return all_tcaches + + def arena(self, arena_index=0): + """Returns the selected arena + + Args: + arena_index (int, optional): The index of the desired arena. If none + is specified, then the index of the main arena will be selected + + Returns: + :class:`Arena` + + .. code-block:: python + + >>> p = process('sh') + >>> hp = p.heap_explorer() + >>> arena = hp.arena() + >>> print(arena.summary()) + ==================================== Arena ==================================== + - Malloc State (0x7f4ca8fe8ba0) + top = 0x55587a7bf380 + last_remainder = 0x0 + next = 0x7f4ca8fe8ba0 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x55587a7bc000) + chunks_count = 0x8 + top: addr = 0x55587a7bf380, size = 0x1dc80 + - Tcaches + [-] No chunks found + - Fast bins + [-] No chunks found + - Unsorted bins + [0] 0x0 (2) + - Small bins + [-] No chunks found + - Large bins + [-] No chunks found + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.sample1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.arena().summary()) # doctest: +ELLIPSIS + ===... Arena ===... + - Malloc State (0x7ff101adaba0) + top = 0x55c9539e11a0 + last_remainder = 0x0 + next = 0x7ff101adaba0 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x55c9539df000) + chunks_count = 0x3f + top: addr = 0x55c9539e11a0, size = 0x1ee60 + - Tcaches + [10] 0xb8 (7) + - Fast bins + [-] No chunks found + - Unsorted bins + [-] No chunks found + - Small bins + [3] 0x40 (23) + - Large bins + [-] No chunks found + ======... + + """ + malloc_state = self.malloc_state(arena_index) + return self._arena_parser.parse_from_malloc_state(malloc_state) + + def all_arenas(self): + """Returns all arenas + + Returns: + :obj:`list` of :class:`Arena` + + .. code-block:: python + + >>> p = process('sh') + >>> he = p.heap_explorer() + >>> print(he.all_arenas()[0].summary()) # doctest: +SKIP + ==================================== Arena ==================================== + - Malloc State (0x7f4ca8fe8ba0) + top = 0x55587a7bf380 + last_remainder = 0x0 + next = 0x7f4ca8fe8ba0 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x55587a7bc000) + chunks_count = 0x8 + top: addr = 0x55587a7bf380, size = 0x1dc80 + - Tcaches + [-] No chunks found + - Fast bins + [-] No chunks found + - Unsorted bins + [0] 0x0 (2) + - Small bins + [-] No chunks found + - Large bins + [-] No chunks found + ================================================================================ + + Tests: + + >>> c = Corefile(pwnlib.data.heap.x86_64.get('core.32.sample1')) + >>> he = c.heap_explorer(libc_path=pwnlib.data.heap.x86_64.get('libc-2.32.so')) + >>> print(he.all_arenas()[0].summary()) # doctest: +ELLIPSIS + ===... Arena ===... + - Malloc State (0x7ff101adaba0) + top = 0x55c9539e11a0 + last_remainder = 0x0 + next = 0x7ff101adaba0 + next_free = 0x0 + system_mem = 0x21000 + - Heap (0x55c9539df000) + chunks_count = 0x3f + top: addr = 0x55c9539e11a0, size = 0x1ee60 + - Tcaches + [10] 0xb8 (7) + - Fast bins + [-] No chunks found + - Unsorted bins + [-] No chunks found + - Small bins + [3] 0x40 (23) + - Large bins + [-] No chunks found + ======... + """ + return self._arena_parser.parse_all_from_main_malloc_state_address( + self._main_arena_address + ) + + @property + def _main_arena_address(self): + return self._process_informer.main_arena_address + + @property + def _pointer_size(self): + return self._process_informer.pointer_size + + @property + def _malloc_state_parser(self): + return MallocStateParser(self._process_informer) + + @property + def _malloc_chunk_parser(self): + return MallocChunkParser(self._process_informer) + + @property + def _heap_parser(self): + return HeapParser( + self._malloc_chunk_parser, + self._malloc_state_parser + ) + + @property + def _bin_parser(self): + return BinParser(self._malloc_chunk_parser) + + @property + def _fast_bin_parser(self): + return FastBinParser( + self._malloc_chunk_parser, + safe_link=self.safe_link + ) + + @property + def _tcache_parser(self): + if self.tcaches_enabled: + return EnabledTcacheParser( + self._malloc_chunk_parser, + self._heap_parser, + safe_link=self.safe_link, + ) + else: + return DisabledTcacheParser() + + @property + def _arena_parser(self): + return ArenaParser( + self._malloc_state_parser, + self._heap_parser, + self._bin_parser, + self._fast_bin_parser, + self._tcache_parser + ) \ No newline at end of file diff --git a/pwnlib/heap/glmalloc/malloc_chunk.py b/pwnlib/heap/glmalloc/malloc_chunk.py new file mode 100644 index 000000000..ddcd147b6 --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_chunk.py @@ -0,0 +1,207 @@ +from pwnlib.util.packing import p32, p64 + + +class MallocChunkParser: + """Class with the logic to parsing the malloc_chunk struct from binary + data. + + Attributes: + pointer_size (int): Size of pointers in target process + unpack_pointer (func(bytes)): Function to get an int from the + pointer bytes + + Args: + process_informer (ProcessInformer): Helper to perform operations over + memory + """ + + def __init__(self, process_informer): + self.process_informer = process_informer + self.pointer_size = process_informer.pointer_size + self.unpack_pointer = process_informer.unpack_pointer + + self._raw_chunk_size = self.pointer_size * 6 + + def parse_from_address(self, address): + raw_chunk = self.process_informer.read_memory( + address, + self._raw_chunk_size + ) + return self.parse_from_raw(address, raw_chunk) + + def parse_from_raw(self, address, raw_chunk): + + offset = 0 + previous_size = self.unpack_pointer( + raw_chunk[offset: offset + self.pointer_size] + ) + + offset += self.pointer_size + size_with_flags = self.unpack_pointer( + raw_chunk[offset: offset + self.pointer_size] + ) + + offset += self.pointer_size + fd = self.unpack_pointer( + raw_chunk[offset: offset + self.pointer_size] + ) + + offset += self.pointer_size + bk = self.unpack_pointer( + raw_chunk[offset: offset + self.pointer_size] + ) + + offset += self.pointer_size + fd_nextsize = self.unpack_pointer( + raw_chunk[offset: offset + self.pointer_size] + ) + + offset += self.pointer_size + bk_nextsize = self.unpack_pointer( + raw_chunk[offset: offset + self.pointer_size] + ) + + size, prev_in_use, mmapped, main_arena = self._parse_size_and_flags( + size_with_flags + ) + + return MallocChunk( + address, + previous_size, + size, + prev_in_use, + mmapped, + main_arena, + fd, + bk, + fd_nextsize, + bk_nextsize, + self.pointer_size + ) + + def _parse_size_and_flags(self, size_with_flags): + size = size_with_flags & ~0x7 + prev_in_use = bool(size_with_flags & 0x1) + mmapped = bool(size_with_flags & 0x2) + main_arena = bool(size_with_flags & 0x4) + + return size, prev_in_use, mmapped, main_arena + + +class MallocChunk: + """Class to represent the information contained in the malloc_chunk struct + of the glibc. + + .. highlight:: c + .. code-block:: c + + struct malloc_chunk { + INTERNAL_SIZE_T mchunk_prev_size; + INTERNAL_SIZE_T mchunk_size; + struct malloc_chunk* fd; + struct malloc_chunk* bk; + struct malloc_chunk* fd_nextsize; + struct malloc_chunk* bk_nextsize; + }; + """ + + def __init__(self, address, previous_size, size, prev_in_use, mmapped, + non_main_arena, fd, bk, fd_nextsize, bk_nextsize, pointer_size): + #: :class:`int`: The start address of the chunk + self.address = address + + #: :class:`int`: Size of the previous chunk (if previous is free) + self.previous_size = previous_size + + #: :class:`int`: Size of the chunk + self.size = size + + #: :class:`bool`: True if previous chunk is being used (not free) + self.prev_in_use = prev_in_use + + #: :class:`bool`: True if chunk is allocated through mmap + self.mmapped = mmapped + + #: :class:`bool`: True if chunk resides in the main arena + self.non_main_arena = non_main_arena + + #: :class:`int`: Pointer to the next chunk of the bins (if chunk + #: is free) + self.fd = fd + + #: :class:`int`: Pointer to the previous chunk of the bins (if chunk + #: is free) + self.bk = bk + + #: :class:`int`: Pointer to the next chunk with a larger size + #: (only used in large bins) + self.fd_nextsize = fd_nextsize + + #: :class:`int`: Pointer to the next chunk with an smaller size + #: (only used in large bins) + self.bk_nextsize = bk_nextsize + + #: :class:`int`: Address where starts the chunk user data + #: (the address returned by malloc) + self.data_address = self.address + pointer_size * 2 + + #: :class:`int`: The raw size value of the chunk, which includes + #: the NON_MAIN_ARENA, MMAPPED and PREV_IN_USE flags in the + #: lowest-value bits + self.size_with_flags = size | int(non_main_arena)*4 | int(mmapped)*2 | int(prev_in_use) + + if pointer_size == 8: + self._pack_pointer = p64 + else: + self._pack_pointer = p32 + + @property + def fd_demangled(self): + """:class:`int`: The fd pointer demangled to return the original + value when safe-linking is used.""" + return self.data_address >> 12 ^ self.fd + + def __str__(self): + string = "" + string += "previous_size = {:#x}\n".format(self.previous_size) + string += "size = {:#x}\n".format(self.size_with_flags) + string += "fd = {:#x}\n".format(self.fd) + string += "bk = {:#x}\n".format(self.bk) + string += "fd_nextsize = {:#x}\n".format(self.fd_nextsize) + string += "bk_nextsize = {:#x}\n".format(self.bk_nextsize) + + return string + + def format_first_bytes_as_hexdump_str(self): + msg = [] + raw_bytes = self._fd_bytes() + raw_bytes += self._bk_bytes() + + for byte in raw_bytes: + msg.append("{:02x} ".format(byte)) + + msg.append(" ") + + for byte in raw_bytes: + msg.append( + chr(byte) if 0x20 <= byte < 0x7F else "." + ) + + return "".join(msg) + + def _fd_bytes(self): + return bytearray(self._pack_pointer(self.fd)) + + def _bk_bytes(self): + return bytearray(self._pack_pointer(self.bk)) + + def format_flags_as_str(self): + flags = [] + if self.non_main_arena: + flags.append("NON_MAIN_ARENA") + if self.mmapped: + flags.append("MMAPPED") + if self.prev_in_use: + flags.append("PREV_IN_USE") + + return "|".join(flags) diff --git a/pwnlib/heap/glmalloc/malloc_state/__init__.py b/pwnlib/heap/glmalloc/malloc_state/__init__.py new file mode 100644 index 000000000..929b47421 --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/__init__.py @@ -0,0 +1,14 @@ + +from .malloc_state import MallocStateParser, MallocState +from .fastbinsy import FastBinsY +from .bins import \ + Bins, \ + UNSORTED_BIN_INDEX, \ + SMALL_BINS_START_INDEX, \ + LARGE_BINS_START_INDEX + +__all__ = [ + 'MallocState', 'MallocStateParser', + 'FastBinsY', 'Bins', + 'UNSORTED_BIN_INDEX', 'SMALL_BINS_START_INDEX', 'LARGE_BINS_START_INDEX' +] diff --git a/pwnlib/heap/glmalloc/malloc_state/bins/__init__.py b/pwnlib/heap/glmalloc/malloc_state/bins/__init__.py new file mode 100644 index 000000000..fc17a07de --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/bins/__init__.py @@ -0,0 +1,10 @@ + +from .bins import Bins +from .parser import BinsParser +from .bins_indexes import \ + UNSORTED_BIN_INDEX, SMALL_BINS_START_INDEX, LARGE_BINS_START_INDEX + +__all__ = [ + 'Bins', 'BinsParser', + 'UNSORTED_BIN_INDEX', 'SMALL_BINS_START_INDEX', 'LARGE_BINS_START_INDEX' +] diff --git a/pwnlib/heap/glmalloc/malloc_state/bins/bins.py b/pwnlib/heap/glmalloc/malloc_state/bins/bins.py new file mode 100644 index 000000000..e82924b4e --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/bins/bins.py @@ -0,0 +1,41 @@ +from .bins_indexes import \ + LARGE_BINS_START_INDEX, SMALL_BINS_START_INDEX, UNSORTED_BIN_INDEX + + +class Bins: + """Class to represent the `bins` attribute of malloc_state struct. + """ + + def __init__(self, entries): + #: :class:`list` of :class:`BinEntry`: Entries with pointers to the first + #: and last chunks of the each double linked bin. + self.entries = entries + + @property + def unsorted_bin_entry(self): + """:class:`BinEntry`: Returns the entry of the unsorted bin. + """ + return self.entries[UNSORTED_BIN_INDEX] + + @property + def small_bins_entries(self): + """:obj:`list` of :class:`BinEntry`: Returns the entries of + the small bins. + """ + return self.entries[SMALL_BINS_START_INDEX:LARGE_BINS_START_INDEX] + + @property + def large_bins_entries(self): + """:obj:`list` of :class:`BinEntry`: Returns the entries of + the large bins. + """ + return self.entries[LARGE_BINS_START_INDEX:] + + def __getitem__(self, index): + return self.entries[index] + + def __iter__(self): + return iter(self.entries) + + def __len__(self): + return len(self.entries) diff --git a/pwnlib/heap/glmalloc/malloc_state/bins/bins_indexes.py b/pwnlib/heap/glmalloc/malloc_state/bins/bins_indexes.py new file mode 100644 index 000000000..a47bbc733 --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/bins/bins_indexes.py @@ -0,0 +1,4 @@ +UNSORTED_BIN_INDEX = 0 +SMALL_BINS_START_INDEX = 1 +LARGE_BINS_START_INDEX = 63 +LARGE_BINS_END_INDEX = 126 diff --git a/pwnlib/heap/glmalloc/malloc_state/bins/parser.py b/pwnlib/heap/glmalloc/malloc_state/bins/parser.py new file mode 100644 index 000000000..bd5e4ce4b --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/bins/parser.py @@ -0,0 +1,200 @@ +from pwnlib.heap.glmalloc.bins import SmallBinEntry, LargeBinEntry, UnsortedBinEntry +from .bins_indexes import \ + LARGE_BINS_START_INDEX, SMALL_BINS_START_INDEX, LARGE_BINS_END_INDEX +from .bins import Bins + + +class BinsParser: + """Class with the logic to parse the `bins` attribute of the + `malloc_state` struct. + + Args: + pointer_size (int): The pointer size in bytes of the process. + """ + + def __init__(self, pointer_size): + self._pointer_size = pointer_size + if pointer_size == 8: + self._largebin_index_to_min_size = largebin64_index_to_min_size + else: + self._largebin_index_to_min_size = largebin32_index_to_min_size + + def parse_from_collection(self, base_address, collection_array): + """Returns a FastBinsY object by parsing a `bins` binary array. + + Args: + collection_array (bytes): Binary `bins` array of `malloc_state` struct. + + Returns: + Bins + """ + + num_entries = len(collection_array) + entries = [] + + for i in range(0, num_entries, 2): + fd = collection_array[i] + bk = collection_array[i + 1] + address = base_address + i * self._pointer_size + + index = int(i / 2) + entry = self._to_bin_entry(index, address, fd, bk) + entries.append(entry) + + return Bins(entries) + + def _to_bin_entry(self, index, address, fd, bk): + if index < SMALL_BINS_START_INDEX: + return UnsortedBinEntry(address, fd, bk) + elif index < LARGE_BINS_START_INDEX: + chunks_size = self._bin_index_to_size(index) + return SmallBinEntry(address, fd, bk, chunks_size) + else: + min_chunks_size = self._bin_index_to_size(index) + return LargeBinEntry(address, fd, bk, min_chunks_size) + + def _bin_index_to_size(self, index): + if index < SMALL_BINS_START_INDEX: + return 0 + elif index < LARGE_BINS_START_INDEX: + return (self._pointer_size * 4) + (index - 1) * (self._pointer_size * 2) + elif index < LARGE_BINS_END_INDEX: + return self._largebin_index_to_min_size[index+1] + else: + return 0x100000 + + +largebin32_index_to_min_size = { + 64: 0x200, + 65: 0x240, + 66: 0x280, + 67: 0x2c0, + 68: 0x300, + 69: 0x340, + 70: 0x380, + 71: 0x3c0, + 72: 0x400, + 73: 0x440, + 74: 0x480, + 75: 0x4c0, + 76: 0x500, + 77: 0x540, + 78: 0x580, + 79: 0x5c0, + 80: 0x600, + 81: 0x640, + 82: 0x680, + 83: 0x6c0, + 84: 0x700, + 85: 0x740, + 86: 0x780, + 87: 0x7c0, + 88: 0x800, + 89: 0x840, + 90: 0x880, + 91: 0x8c0, + 92: 0x900, + 93: 0x940, + 94: 0x980, + 95: 0x9c0, + 96: 0xa00, + 97: 0xc00, + 98: 0xe00, + 99: 0x1000, + 100: 0x1200, + 101: 0x1400, + 102: 0x1600, + 103: 0x1800, + 104: 0x1a00, + 105: 0x1c00, + 106: 0x1e00, + 107: 0x2000, + 108: 0x2200, + 109: 0x2400, + 110: 0x2600, + 111: 0x2800, + 112: 0x2a00, + 113: 0x3000, + 114: 0x4000, + 115: 0x5000, + 116: 0x6000, + 117: 0x7000, + 118: 0x8000, + 119: 0x9000, + 120: 0xa000, + 121: 0x10000, + 122: 0x18000, + 123: 0x20000, + 124: 0x28000, + 125: 0x40000, + 126: 0x80000 +} + + +largebin64_index_to_min_size = { + 64: 0x400, + 65: 0x440, + 66: 0x480, + 67: 0x4c0, + 68: 0x500, + 69: 0x540, + 70: 0x580, + 71: 0x5c0, + 72: 0x600, + 73: 0x640, + 74: 0x680, + 75: 0x6c0, + 76: 0x700, + 77: 0x740, + 78: 0x780, + 79: 0x7c0, + 80: 0x800, + 81: 0x840, + 82: 0x880, + 83: 0x8c0, + 84: 0x900, + 85: 0x940, + 86: 0x980, + 87: 0x9c0, + 88: 0xa00, + 89: 0xa40, + 90: 0xa80, + 91: 0xac0, + 92: 0xb00, + 93: 0xb40, + 94: 0xb80, + 95: 0xbc0, + 96: 0xc00, + 97: 0xc40, + 98: 0xe00, + 99: 0x1000, + 100: 0x1200, + 101: 0x1400, + 102: 0x1600, + 103: 0x1800, + 104: 0x1a00, + 105: 0x1c00, + 106: 0x1e00, + 107: 0x2000, + 108: 0x2200, + 109: 0x2400, + 110: 0x2600, + 111: 0x2800, + 112: 0x2a00, + 113: 0x3000, + 114: 0x4000, + 115: 0x5000, + 116: 0x6000, + 117: 0x7000, + 118: 0x8000, + 119: 0x9000, + 120: 0xa000, + 121: 0x10000, + 122: 0x18000, + 123: 0x20000, + 124: 0x28000, + 125: 0x40000, + 126: 0x80000, +} + + diff --git a/pwnlib/heap/glmalloc/malloc_state/fastbinsy.py b/pwnlib/heap/glmalloc/malloc_state/fastbinsy.py new file mode 100644 index 000000000..807255787 --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/fastbinsy.py @@ -0,0 +1,56 @@ +from pwnlib.util.packing import u64, u32 +from pwnlib.heap.glmalloc.bins import FastBinEntry + + +class FastBinsYParser: + """Class with the logic to parse the `fastbinsY` attribute of the + `malloc_state` struct. + + Args: + pointer_size (int): The pointer size in bytes of the process. + """ + + def __init__(self, pointer_size): + self._pointer_size = pointer_size + if pointer_size == 8: + self._u = u64 + else: + self._u = u32 + + def parse_from_collection(self, base_address, collection_array): + """Returns a FastBinsY object by parsing a `fastbinsY` binary array. + + Args: + collection_array (bytes): Binary `fastbinsY` array of `malloc_state` struct. + + Returns: + FastBinsY + """ + + base_size = self._pointer_size * 4 + entries = [] + for i, fd in enumerate(collection_array): + address = base_address + i * self._pointer_size + chunks_size = base_size + i * self._pointer_size * 2 + entries.append(FastBinEntry(address, fd, chunks_size)) + + return FastBinsY(entries) + + +class FastBinsY: + """Class to represent the `fastbinsY` attribute of malloc_state struct. + """ + + def __init__(self, entries): + #: :class:`list` of :class:`FastBinEntry`: Pointers to the first chunks + #: of the each fast bin. + self.entries = entries + + def __getitem__(self, index): + return self.entries[index] + + def __iter__(self): + return iter(self.entries) + + def __len__(self): + return len(self.entries) diff --git a/pwnlib/heap/glmalloc/malloc_state/malloc_state/__init__.py b/pwnlib/heap/glmalloc/malloc_state/malloc_state/__init__.py new file mode 100644 index 000000000..8b5bc5afb --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/malloc_state/__init__.py @@ -0,0 +1,7 @@ + +from .malloc_state import MallocState +from .parser import MallocStateParser + +__all__ = [ + 'MallocState', 'MallocStateParser', +] diff --git a/pwnlib/heap/glmalloc/malloc_state/malloc_state/malloc_state.py b/pwnlib/heap/glmalloc/malloc_state/malloc_state/malloc_state.py new file mode 100644 index 000000000..01afa67f4 --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/malloc_state/malloc_state.py @@ -0,0 +1,184 @@ +from pwnlib.heap.glmalloc.basic_formatter import BasicFormatter + + +class MallocState: + """Representation of the glibc malloc_state struct. + + + Here is the definition of the malloc_state struct in libc 2.27: + + .. highlight:: c + .. code-block:: c + + struct malloc_state + { + __libc_lock_define (, mutex); + int flags; + int have_fastchunks; + mfastbinptr fastbinsY[NFASTBINS]; + mchunkptr top; + mchunkptr last_remainder; + mchunkptr bins[NBINS * 2 - 2]; + unsigned int binmap[BINMAPSIZE]; + struct malloc_state *next; + struct malloc_state *next_free; + INTERNAL_SIZE_T attached_threads; + INTERNAL_SIZE_T system_mem; + INTERNAL_SIZE_T max_system_mem; + }; + + Notes: + The field have_fastchunks was introduced in libc version 2.27. In case + libc version is inferior that field value will be None. + + The field attached_threads was introduced in libc version 2.23. In case + libc version is inferior that field value will be None. + """ + + def __init__(self, address, mutex, flags, have_fastchunks, + fastbinsY, top, last_remainder, bins, binmap, next_, + next_free, attached_threads, system_mem, max_system_mem): + + #: :class:`int`: Address of the malloc_state + self.address = address + + #: :class:`int`: Mutex value of the malloc_state + self.mutex = mutex + + #: :class:`int`: Flags of the malloc_state + self.flags = flags + + #: :class:`int` or :class:`None`: Indicates if there are chunks in the + #: fastbins + self.have_fastchunks = have_fastchunks + + #: :class:`FastBinsY`: Fast bin entries + self.fastbinsY = fastbinsY + + #: :class:`int`: Pointer to the top chunk of the heap + self.top = top + + #: :class:`int`: Pointer to the last remainder chunk + self.last_remainder = last_remainder + + #: :class:`Bins`: All bins entries of the malloc_state (Unsorted, + #: Small and Large) + self.bins = bins + + #: :class:`list[4]` of :class:`int`: Bitmap which indicates the bins + #: with chunks + self.binmap = binmap + + #: :class:`int`: Address of the next malloc_state struct + self.next = next_ + + #: :class:`int` or :class:`None` + self.next_free = next_free + + #: :class:`int`: Number of threads attached to the arena + self.attached_threads = attached_threads + + #: :class:`int`: Available heap size + self.system_mem = system_mem + + #: :class:`int`: Maximum heap size + self.max_system_mem = max_system_mem + self._basic_formatter = BasicFormatter() + + @property + def unsorted_bin(self): + """:class:`BinEntry`: Unsorted bin entry of the malloc_state""" + return self.bins.unsorted_bin_entry + + @property + def small_bins(self): + """:class:`list` of :class:`BinEntry`: Small bins entries of the + malloc_state""" + return self.bins.small_bins_entries + + @property + def large_bins(self): + """:class:`list` of :class:`BinEntry`: Large bins entries of the + malloc_state""" + return self.bins.large_bins_entries + + def __str__(self): + msg = [ + self._basic_formatter.header( + "Malloc State ({:#x})".format(self.address) + ), + self._format_malloc_state(), + self._basic_formatter.footer() + ] + return "\n".join(msg) + + def _format_malloc_state(self): + string = [ + "mutex = {:#x}".format(self.mutex), + "flags = {:#x}".format(self.flags) + ] + + if self.have_fastchunks is not None: + string.append( + "have_fastchunks = {:#x}".format(self.have_fastchunks) + ) + + string.append(self._format_malloc_state_fastbinsY_as_str()) + + string.append("top = {:#x}".format(self.top)) + string.append("last_remainder = {:#x}".format(self.last_remainder)) + string.append(self._format_malloc_state_bins_as_str()) + + string.append("binmap = [{:#x}, {:#x}, {:#x}, {:#x}]".format( + self.binmap[0], + self.binmap[1], + self.binmap[2], + self.binmap[3] + )) + string.append("next = {:#x}".format(self.next)) + string.append("next_free = {:#x}".format(self.next_free)) + + if self.attached_threads is not None: + string.append("attached_threads = {:#x}".format( + self.attached_threads + )) + string.append("system_mem = {:#x}".format(self.system_mem)) + string.append("max_system_mem = {:#x}".format(self.max_system_mem)) + + return "\n".join(string) + + def _format_malloc_state_fastbinsY_as_str(self): + string = ["fastbinsY"] + for i, entry in enumerate(self.fastbinsY): + string.append(" [{}] {:#x} => {:#x}".format( + i, entry.chunks_size, entry.fd + )) + + return "\n".join(string) + + def _format_malloc_state_bins_as_str(self): + string = ["bins"] + index = 0 + + string.append(" Unsorted bins") + unsorted_entry = self.bins.unsorted_bin_entry + string.append(" [{}] fd={:#x} bk={:#x}".format( + index, unsorted_entry.fd, unsorted_entry.bk + )) + + index += 1 + string.append(" Small bins") + for small_entry in self.bins.small_bins_entries: + string.append(" [{}] {:#x} fd={:#x} bk={:#x}".format( + index, small_entry.chunks_size, small_entry.fd, small_entry.bk + )) + index += 1 + + string.append(" Large bins") + for small_entry in self.bins.large_bins_entries: + string.append(" [{}] {:#x} fd={:#x} bk={:#x}".format( + index, small_entry.chunks_size, small_entry.fd, small_entry.bk + )) + index += 1 + + return "\n".join(string) diff --git a/pwnlib/heap/glmalloc/malloc_state/malloc_state/parser.py b/pwnlib/heap/glmalloc/malloc_state/malloc_state/parser.py new file mode 100644 index 000000000..33883fcc4 --- /dev/null +++ b/pwnlib/heap/glmalloc/malloc_state/malloc_state/parser.py @@ -0,0 +1,260 @@ +from construct import Int32ul, Int64ul, Struct, Padding +from pwnlib.heap.glmalloc.malloc_state.bins import BinsParser +from ..fastbinsy import FastBinsYParser +from .malloc_state import MallocState + +NFASTBINS = 10 +NBINS = 128 +INT_SIZE = 4 +BINMAPSIZE = 4 + + +class MallocStateParser: + """Class with the logic of parsing the malloc_state struct from binary + data. Handles special cases which depends on the glibc version. + + Args: + process_informer (ProcessInformer): Helper to perform operations over + memory + """ + + def __init__(self, process_informer): + self._process_informer = process_informer + self._pointer_size = process_informer.pointer_size + + self._bins_parser = BinsParser(self._pointer_size) + self._fastbinsy_parser = FastBinsYParser(self._pointer_size) + + self._libc_version = self._process_informer.libc_version + + self._malloc_state_struct_definition = MallocStateStructSelector( + self._process_informer.libc_version, + process_informer.pointer_size + ).select_malloc_state_struct_definition() + + self.raw_malloc_state_size = self._calculate_raw_malloc_state_size() + + + + def parse_all_from_main_malloc_state_address(self, base_address): + """Parses the whole list of malloc_state structs from the + process memory, by following the `next` pointer in the struct + + Returns: + list of MallocState + """ + malloc_state_address = base_address + addresses = [] + malloc_states = [] + while malloc_state_address not in addresses: + addresses.append(malloc_state_address) + malloc_state = self.parse_from_address(malloc_state_address) + malloc_states.append(malloc_state) + malloc_state_address = malloc_state.next + + return malloc_states + + def parse_from_address(self, address): + """Parses a malloc_state struct from the process memory + + Returns: + MallocState + """ + raw_malloc_state = self._process_informer.read_memory( + address, + self.raw_malloc_state_size + ) + return self.parse_from_raw(address, raw_malloc_state) + + def parse_from_raw(self, address, raw_malloc_state): + """Parses a binary malloc_state struct + + Args: + address (int): address of the data, useful as metadata for bins arrays + raw_malloc_state (bytearray): malloc_state in binary form + + Returns: + MallocState + """ + malloc_state_collection = self._malloc_state_struct_definition.parse( + raw_malloc_state + ) + + mutex = malloc_state_collection.mutex + flags = malloc_state_collection.flags + try: + have_fastchunks = malloc_state_collection.have_fastchunks + except AttributeError: + have_fastchunks = None + + fastbinsY_address = address + \ + self._malloc_state_struct_definition.fastbinsY_offset + fastbinsY = self._fastbinsy_parser.parse_from_collection( + fastbinsY_address, + malloc_state_collection.fastbinsY + ) + + top = malloc_state_collection.top + last_remainder = malloc_state_collection.last_remainder + + bins_address = address + \ + self._malloc_state_struct_definition.bins_offset + bins = self._bins_parser.parse_from_collection( + bins_address, + malloc_state_collection.bins + ) + + binmap = list(malloc_state_collection.binmap) + next_ = malloc_state_collection.next + next_free = malloc_state_collection.next_free + try: + attached_threads = malloc_state_collection.attached_threads + except AttributeError: + attached_threads = None + + system_mem = malloc_state_collection.system_mem + max_system_mem = malloc_state_collection.max_system_mem + + return MallocState( + address, + mutex, + flags, + have_fastchunks, + fastbinsY, + top, + last_remainder, + bins, + binmap, + next_, + next_free, + attached_threads, + system_mem, + max_system_mem + ) + + def _calculate_raw_malloc_state_size(self): + return self._malloc_state_struct_definition.size + + +class MallocStateStructSelector: + + def __init__(self, libc_version, pointer_size): + self._pointer_size = pointer_size + if pointer_size == 8: + self._pointer_type = Int64ul + else: + self._pointer_type = Int32ul + + self._libc_version = libc_version + + def select_malloc_state_struct_definition(self): + if self._libc_version >= (2, 27): + return self._define_malloc_state_struct_27() + elif self._libc_version >= (2, 23): + return self._define_malloc_state_struct_23() + else: + return self._define_malloc_state_struct_19() + + def _define_malloc_state_struct_27(self): + pointer_type = self._pointer_type + if self._pointer_size == 8: + padding_bytes = 0 + else: + padding_bytes = 4 + + fastbinsY_offset = pointer_type.sizeof() + Int32ul.sizeof()*2 + bins_offset = fastbinsY_offset + pointer_type.sizeof()*NFASTBINS \ + + padding_bytes + pointer_type.sizeof()*2 + + struct_definition = Struct( + "mutex" / pointer_type, + "flags" / Int32ul, + "have_fastchunks" / Int32ul, + "fastbinsY" / pointer_type[NFASTBINS], + Padding(padding_bytes), + "top" / pointer_type, + "last_remainder" / pointer_type, + "bins" / pointer_type[NBINS * 2 - 2], + "binmap" / Int32ul[BINMAPSIZE], + "next" / pointer_type, + "next_free" / pointer_type, + "attached_threads" / pointer_type, + "system_mem" / pointer_type, + "max_system_mem" / pointer_type + ) + + return MallocStateStructDefinition( + struct_definition, + fastbinsY_offset, + bins_offset + ) + + def _define_malloc_state_struct_23(self): + pointer_type = self._pointer_type + + fastbinsY_offset = Int32ul.sizeof()*2 + bins_offset = fastbinsY_offset + pointer_type.sizeof() * NFASTBINS \ + + pointer_type.sizeof()*2 + + struct_definition = Struct( + "mutex" / Int32ul, + "flags" / Int32ul, + "fastbinsY" / pointer_type[NFASTBINS], + "top" / pointer_type, + "last_remainder" / pointer_type, + "bins" / pointer_type[NBINS * 2 - 2], + "binmap" / Int32ul[BINMAPSIZE], + "next" / pointer_type, + "next_free" / pointer_type, + "attached_threads" / pointer_type, + "system_mem" / pointer_type, + "max_system_mem" / pointer_type + ) + + return MallocStateStructDefinition( + struct_definition, + fastbinsY_offset, + bins_offset + ) + + def _define_malloc_state_struct_19(self): + pointer_type = self._pointer_type + + fastbinsY_offset = Int32ul.sizeof()*2 + bins_offset = fastbinsY_offset + pointer_type.sizeof() * NFASTBINS \ + + pointer_type.sizeof()*2 + + struct_definition = Struct( + "mutex" / Int32ul, + "flags" / Int32ul, + "fastbinsY" / pointer_type[NFASTBINS], + "top" / pointer_type, + "last_remainder" / pointer_type, + "bins" / pointer_type[NBINS * 2 - 2], + "binmap" / Int32ul[BINMAPSIZE], + "next" / pointer_type, + "next_free" / pointer_type, + "system_mem" / pointer_type, + "max_system_mem" / pointer_type + ) + + return MallocStateStructDefinition( + struct_definition, + fastbinsY_offset, + bins_offset + ) + + +class MallocStateStructDefinition(object): + + def __init__(self, struct_definition, fastbinsY_offset, bins_offset): + self.struct_definition = struct_definition + self.fastbinsY_offset = fastbinsY_offset + self.bins_offset = bins_offset + + def parse(self, raw): + return self.struct_definition.parse(raw) + + @property + def size(self): + return self.struct_definition.sizeof() diff --git a/pwnlib/heap/glmalloc/process_informer.py b/pwnlib/heap/glmalloc/process_informer.py new file mode 100644 index 000000000..3482d0ddb --- /dev/null +++ b/pwnlib/heap/glmalloc/process_informer.py @@ -0,0 +1,104 @@ +import os.path +import pwnlib.util.proc +from pwnlib.heap.glmalloc.utils import ( + get_libc_version_from_name, get_main_arena_addr +) +from pwnlib.util.packing import u32, u64 + + +class ProcessInformer: + """Helper class with information about process memory such as pointer_size, + and which allows perform operations over memory. + + Attributes: + process : The process to explore + libc_version (tuple(int, int)): The glibc version in + format (major, minor) + pointer_size (int): size in bytes of a pointer in the process + main_arena_address (int): address of the main arena malloc state + + """ + + def __init__(self, process, libc_version=None): + self.process = process + libc = process._libc() + + if "64" in libc.get_machine_arch(): + self.pointer_size = 8 + self.unpack_pointer = u64 + else: + self.pointer_size = 4 + self.unpack_pointer = u32 + + self.unpack_int = u32 + + libc_name = os.path.basename(libc.path) + self.libc_version = libc_version or get_libc_version_from_name(libc_name) + self.main_arena_address = get_main_arena_addr(libc, self.pointer_size) + + def read_memory(self, address, size): + return self.process.leak(address, size) + + def map_with_address(self, addr): + for mapping in self.process.mappings: + if mapping.start <= addr < mapping.stop: + return mapping + + raise IndexError("address {:#x} out of range".format(addr)) + + def is_libc_version_higher_than(self, version): + return self.libc_version > version + + def is_libc_version_lower_than(self, version): + return self.libc_version < version + + +class CoreFileInformer: + """Helper class with information about corefile such as pointer_size, + and which allows perform operations over memory. + + Attributes: + corefile : The corefile to explore + libc_version (tuple(int, int)): The glibc version in + format (major, minor) + pointer_size (int): size in bytes of a pointer in the process + main_arena_address (int): address of the main arena malloc state + + """ + + def __init__(self, corefile, libc, libc_version=None): + self.corefile = corefile + + if "64" in self.corefile.get_machine_arch(): + self.pointer_size = 8 + self.unpack_pointer = u64 + else: + self.pointer_size = 4 + self.unpack_pointer = u32 + + self.unpack_int = u32 + + # read the libc version from corefile since libc can be provided by an user + # from a different path that does not include the version + libc_map = corefile.libc + libc_name = os.path.basename(libc_map.path) + self.libc_version = libc_version or get_libc_version_from_name(libc_name) + + # libc required to read symbols and locate the main arena address + self.main_arena_address = get_main_arena_addr(libc, self.pointer_size) + + def read_memory(self, address, size): + return self.corefile.read(address, size) + + def map_with_address(self, addr): + for mapping in self.corefile.mappings: + if mapping.start <= addr < mapping.stop: + return mapping + + raise IndexError("address {:#x} out of range".format(addr)) + + def is_libc_version_higher_than(self, version): + return self.libc_version > version + + def is_libc_version_lower_than(self, version): + return self.libc_version < version diff --git a/pwnlib/heap/glmalloc/utils.py b/pwnlib/heap/glmalloc/utils.py new file mode 100644 index 000000000..8aee7f2ec --- /dev/null +++ b/pwnlib/heap/glmalloc/utils.py @@ -0,0 +1,41 @@ +import re + + +def align_address(address, align): + """Align the address to the given size.""" + return address + ((align - (address % align)) % align) + + +def get_libc_version_from_name(name): + """Tries to identify the glibc version based on the filename + + Args: + name: The filename of the libc, which usually is something + like libc-2.32.so + + Returns: + tuple(int, int): The glibc version in format (major, minor) + + Examples: + >>> get_libc_version_from_name("libc-2.23.so") + (2, 23) + + >>> try: + ... get_libc_version_from_name("libc.so") + ... except ValueError as e: + ... print(e) + Unable to get libc version from filename libc.so + + """ + matches = re.search(r"libc6?[-_](\d+)\.(\d+)\.so", name) + + if matches is None: + raise ValueError("Unable to get libc version from filename %s" % name) + + libc_version = tuple(int(_) for _ in matches.groups()) + return libc_version + + +def get_main_arena_addr(libc, pointer_size): + malloc_hook_addr = libc.symbols["__malloc_hook"] + return align_address(malloc_hook_addr + pointer_size, 0x20) # only for x86, for arm is other technique diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py index 8730031ab..860593951 100644 --- a/pwnlib/tubes/process.py +++ b/pwnlib/tubes/process.py @@ -26,6 +26,9 @@ from pwnlib.util.hashes import sha256file from pwnlib.util.misc import parse_ldd_output from pwnlib.util.misc import which +import pwnlib.util.proc +from pwnlib.exception import PwnlibException +from pwnlib.heap import HeapExplorer, ProcessInformer log = getLogger(__name__) @@ -876,6 +879,66 @@ def libs(self): return maps + @property + def mappings(self): + """mappings() -> list[Mapping] + + Returns a object which contains a list of the maps created by the + process. Each item of the list includes the information of the lines + of /proc//maps such as the path of the mapped file, the starting + and ending address or the flags of the map. + + Returns: + list[Mapping]: the list of maps of the current process + + :: + + >>> p = process('bash') + >>> print(p.mappings[0]) + Mapping('/usr/bin/bash', start=0x55609a056000, stop=0x55609a083000, size=0x2d000, flags=0x4, page_offset=0x0) + + """ + + with open('/proc/%d/maps' % self.pid) as fmap: + maps_string = fmap.read() + + mappings = [] + for line in maps_string.splitlines(): + parts = line.split() + + start_address, end_address = parts[0].split("-") + start_address = int(start_address, 16) + end_address = int(end_address, 16) + + flags_str = parts[1] + flags = 0 + if flags_str[0] == "r": + flags = flags | 4 + + if flags_str[1] == "w": + flags = flags | 2 + + if flags_str[2] == "x": + flags = flags | 1 + + offset = int(parts[2], 16) + + try: + path = parts[5] + except IndexError: + path = "" + + mappings.append(pwnlib.elf.corefile.Mapping( + core=None, + name=path, + start=start_address, + stop=end_address, + flags=flags, + page_offset=offset + )) + + return mappings + @property def libc(self): """libc() -> ELF @@ -884,6 +947,9 @@ def libc(self): If possible, it is adjusted to the correct address automatically. + Raises: + PwnlibException: In case the libc is not found + Example: >>> p = process("/bin/cat") @@ -892,14 +958,96 @@ def libc(self): ELF('/lib64/libc-...so') >>> p.close() """ + lib = self._waitfor_libc(timeout=0) + lib.describe() + return lib + + def _libc(self): + """Function to retrieve the libc library from the current process + + Returns: + ELF or None + """ + from pwnlib.elf import ELF for lib, address in self.libs().items(): if 'libc.so' in lib or 'libc-' in lib: - e = ELF(lib) + e = ELF(lib, checksec=False) e.address = address return e + def _waitfor_libc(self, timeout=1): + """Function to retrieve the libc library from the current process waiting for + it to be loaded + + Arguments: + timeout(int): Time to wait for libc to being loaded + + Raises: + PwnlibException: In case the libc is not found in the given timeout + + Returns: + ELF + """ + + t = Timeout() + with t.countdown(timeout): + while t.timeout: + libc = self._libc() + if libc is not None: + return libc + + time.sleep(0.01) + + self.error( + "Unable to find the libc library in process {}".format( + self.executable + ) + ) + + def heap_explorer(self, timeout=1, tcache=None, safe_link=None, libc_version=None): + """Returns a heap explorer that allows to inspect the items of the libc + heap. + + Arguments: + timeout(int): Time to wait for libc to being loaded + tcache(bool): Indicate if tcache is present + safe_link (bool): Indicate if safe-link is present in tcaches and + fastbisn pointers + libc_version (tuple(int, int)): The glibc version in format + (major, minor). To use in case libc version is not + automatically detected. + + Raises: + PwnlibException: In case the libc is not found in the given timeout + + Examples: + >>> p = process('sh') # doctest: +SKIP + >>> hp = p.heap_explorer() # doctest: +SKIP + >>> heap = hp.heap() # doctest: +SKIP + >>> len(heap.chunks) # doctest: +SKIP + 574 + >>> fast_bins = hp.fast_bins() # doctest: +SKIP + >>> print(fast_bins) # doctest: +SKIP + ================================== Fast Bins ================================== + [4] Fast Bin 0x60 (2) => Chunk(0x555635100c20 0x60 PREV_IN_USE) => Chunk(0x55563 + 5100ba0 0x60 PREV_IN_USE) => 0x0 + ================================================================================ + + + Returns: + HeapExplorer + """ + # make sure that libc is loaded + self._waitfor_libc(timeout=timeout) + + return HeapExplorer( + ProcessInformer(self, libc_version=libc_version), + use_tcache=tcache, + safe_link=safe_link, + ) + @property def elf(self): """elf() -> pwnlib.elf.elf.ELF diff --git a/setup.py b/setup.py index 2dc306a7f..853405314 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,7 @@ 'intervaltree>=3.0', 'sortedcontainers', 'unicorn>=1.0.2rc1', # see unicorn-engine/unicorn#1100, unicorn-engine/unicorn#1170 + 'construct', ] # Check that the user has installed the Python development headers