mirror of https://github.com/pwndbg/pwndbg.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
504 lines
14 KiB
Python
504 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
from .....host import Controller
|
|
from .. import get_binary
|
|
from .. import launch_to
|
|
from .. import pwndbg_test
|
|
|
|
BINARY = get_binary("heap_bins.native.out")
|
|
|
|
|
|
@pwndbg_test
|
|
async def test_heap_bins(ctrl: Controller) -> None:
|
|
"""
|
|
Tests pwndbg.aglib.heap bins commands
|
|
"""
|
|
import pwndbg
|
|
import pwndbg.aglib.heap
|
|
import pwndbg.aglib.memory
|
|
import pwndbg.aglib.symbol
|
|
import pwndbg.aglib.vmmap
|
|
from pwndbg.aglib.heap.ptmalloc import BinType
|
|
|
|
await ctrl.launch(BINARY)
|
|
await ctrl.execute("set context-output /dev/null")
|
|
await ctrl.execute("b breakpoint")
|
|
await ctrl.cont()
|
|
|
|
# check if all bins are empty at first
|
|
allocator = pwndbg.aglib.heap.current
|
|
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_size")
|
|
tcache_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("tcache_count")
|
|
tcache_count = pwndbg.aglib.memory.u64(addr)
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_size")
|
|
fastbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("fastbin_count")
|
|
fastbin_count = pwndbg.aglib.memory.u64(addr)
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_size")
|
|
smallbin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("smallbin_count")
|
|
smallbin_count = pwndbg.aglib.memory.u64(addr)
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_size")
|
|
largebin_size = allocator._request2size(pwndbg.aglib.memory.u64(addr))
|
|
addr = pwndbg.aglib.symbol.lookup_symbol_addr("largebin_count")
|
|
largebin_count = pwndbg.aglib.memory.u64(addr)
|
|
|
|
result = allocator.tcachebins()
|
|
assert result.bin_type == BinType.TCACHE
|
|
assert tcache_size in result.bins
|
|
assert result.bins[tcache_size].bk_chain is None and len(result.bins[tcache_size].fd_chain) == 1
|
|
|
|
result = allocator.fastbins()
|
|
assert result.bin_type == BinType.FAST
|
|
assert fastbin_size in result.bins
|
|
assert len(result.bins[fastbin_size].fd_chain) == 1
|
|
|
|
result = allocator.unsortedbin()
|
|
assert result.bin_type == BinType.UNSORTED
|
|
assert len(result.bins["all"].fd_chain) == 1
|
|
assert not result.bins["all"].is_corrupted
|
|
|
|
result = allocator.smallbins()
|
|
assert result.bin_type == BinType.SMALL
|
|
assert smallbin_size in result.bins
|
|
assert (
|
|
len(result.bins[smallbin_size].fd_chain) == 1
|
|
and len(result.bins[smallbin_size].bk_chain) == 1
|
|
)
|
|
assert not result.bins[smallbin_size].is_corrupted
|
|
|
|
result = allocator.largebins()
|
|
assert result.bin_type == BinType.LARGE
|
|
largebin_size = list(result.bins.items())[allocator.largebin_index(largebin_size) - 64][0]
|
|
assert largebin_size in result.bins
|
|
assert (
|
|
len(result.bins[largebin_size].fd_chain) == 1
|
|
and len(result.bins[largebin_size].bk_chain) == 1
|
|
)
|
|
assert not result.bins[largebin_size].is_corrupted
|
|
|
|
# check tcache
|
|
await ctrl.cont()
|
|
|
|
result = allocator.tcachebins()
|
|
assert result.bin_type == BinType.TCACHE
|
|
assert tcache_size in result.bins
|
|
assert (
|
|
result.bins[tcache_size].count == tcache_count
|
|
and len(result.bins[tcache_size].fd_chain) == tcache_count + 1
|
|
)
|
|
for addr in result.bins[tcache_size].fd_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
|
|
# check fastbin
|
|
await ctrl.cont()
|
|
|
|
result = allocator.fastbins()
|
|
assert result.bin_type == BinType.FAST
|
|
assert (fastbin_size in result.bins) and (
|
|
len(result.bins[fastbin_size].fd_chain) == fastbin_count + 1
|
|
)
|
|
for addr in result.bins[fastbin_size].fd_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
|
|
# check unsortedbin
|
|
await ctrl.cont()
|
|
|
|
result = allocator.unsortedbin()
|
|
assert result.bin_type == BinType.UNSORTED
|
|
assert (
|
|
len(result.bins["all"].fd_chain) == smallbin_count + 2
|
|
and len(result.bins["all"].bk_chain) == smallbin_count + 2
|
|
)
|
|
assert not result.bins["all"].is_corrupted
|
|
for addr in result.bins["all"].fd_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
for addr in result.bins["all"].bk_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
|
|
# check smallbins
|
|
await ctrl.cont()
|
|
|
|
result = allocator.smallbins()
|
|
assert result.bin_type == "smallbins"
|
|
assert (
|
|
len(result.bins[smallbin_size].fd_chain) == smallbin_count + 2
|
|
and len(result.bins[smallbin_size].bk_chain) == smallbin_count + 2
|
|
)
|
|
assert not result.bins[smallbin_size].is_corrupted
|
|
for addr in result.bins[smallbin_size].fd_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
for addr in result.bins[smallbin_size].bk_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
|
|
# check largebins
|
|
await ctrl.cont()
|
|
|
|
result = allocator.largebins()
|
|
assert result.bin_type == BinType.LARGE
|
|
assert (
|
|
len(result.bins[largebin_size].fd_chain) == largebin_count + 2
|
|
and len(result.bins[largebin_size].bk_chain) == largebin_count + 2
|
|
)
|
|
assert not result.bins[largebin_size].is_corrupted
|
|
for addr in result.bins[largebin_size].fd_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
for addr in result.bins[largebin_size].bk_chain[:-1]:
|
|
assert pwndbg.aglib.vmmap.find(addr)
|
|
|
|
# check corrupted
|
|
await ctrl.cont()
|
|
result = allocator.smallbins()
|
|
assert result.bin_type == BinType.SMALL
|
|
assert result.bins[smallbin_size].is_corrupted
|
|
|
|
result = allocator.largebins()
|
|
assert result.bin_type == BinType.LARGE
|
|
assert result.bins[largebin_size].is_corrupted
|
|
|
|
await ctrl.execute("bins")
|
|
|
|
|
|
@pwndbg_test
|
|
async def test_largebins_size_range_64bit(ctrl: Controller) -> None:
|
|
"""
|
|
Ensure the "largebins" command displays the correct largebin size ranges.
|
|
This test targets 64-bit architectures.
|
|
"""
|
|
await launch_to(ctrl, get_binary("initialized_heap.x86-64.out"), "break_here")
|
|
|
|
command_output = (await ctrl.execute_and_capture("largebins --verbose")).splitlines()[1:]
|
|
|
|
expected = [
|
|
"0x400-0x430",
|
|
"0x440-0x470",
|
|
"0x480-0x4b0",
|
|
"0x4c0-0x4f0",
|
|
"0x500-0x530",
|
|
"0x540-0x570",
|
|
"0x580-0x5b0",
|
|
"0x5c0-0x5f0",
|
|
"0x600-0x630",
|
|
"0x640-0x670",
|
|
"0x680-0x6b0",
|
|
"0x6c0-0x6f0",
|
|
"0x700-0x730",
|
|
"0x740-0x770",
|
|
"0x780-0x7b0",
|
|
"0x7c0-0x7f0",
|
|
"0x800-0x830",
|
|
"0x840-0x870",
|
|
"0x880-0x8b0",
|
|
"0x8c0-0x8f0",
|
|
"0x900-0x930",
|
|
"0x940-0x970",
|
|
"0x980-0x9b0",
|
|
"0x9c0-0x9f0",
|
|
"0xa00-0xa30",
|
|
"0xa40-0xa70",
|
|
"0xa80-0xab0",
|
|
"0xac0-0xaf0",
|
|
"0xb00-0xb30",
|
|
"0xb40-0xb70",
|
|
"0xb80-0xbb0",
|
|
"0xbc0-0xbf0",
|
|
"0xc00-0xc30",
|
|
"0xc40-0xdf0",
|
|
"0xe00-0xff0",
|
|
"0x1000-0x11f0",
|
|
"0x1200-0x13f0",
|
|
"0x1400-0x15f0",
|
|
"0x1600-0x17f0",
|
|
"0x1800-0x19f0",
|
|
"0x1a00-0x1bf0",
|
|
"0x1c00-0x1df0",
|
|
"0x1e00-0x1ff0",
|
|
"0x2000-0x21f0",
|
|
"0x2200-0x23f0",
|
|
"0x2400-0x25f0",
|
|
"0x2600-0x27f0",
|
|
"0x2800-0x29f0",
|
|
"0x2a00-0x2ff0",
|
|
"0x3000-0x3ff0",
|
|
"0x4000-0x4ff0",
|
|
"0x5000-0x5ff0",
|
|
"0x6000-0x6ff0",
|
|
"0x7000-0x7ff0",
|
|
"0x8000-0x8ff0",
|
|
"0x9000-0x9ff0",
|
|
"0xa000-0xfff0",
|
|
"0x10000-0x17ff0",
|
|
"0x18000-0x1fff0",
|
|
"0x20000-0x27ff0",
|
|
"0x28000-0x3fff0",
|
|
"0x40000-0x7fff0",
|
|
"0x80000-∞",
|
|
]
|
|
|
|
for bin_index, size_range in enumerate(command_output):
|
|
assert size_range.split(":")[0] == expected[bin_index]
|
|
|
|
|
|
@pwndbg_test
|
|
async def test_largebins_size_range_32bit_big(ctrl: Controller) -> None:
|
|
"""
|
|
Ensure the "largebins" command displays the correct largebin size ranges.
|
|
This test targets 32-bit architectures with MALLOC_ALIGNMENT == 16.
|
|
"""
|
|
await launch_to(ctrl, get_binary("initialized_heap_big.i386.out"), "break_here")
|
|
|
|
command_output = (await ctrl.execute_and_capture("largebins --verbose")).splitlines()[1:]
|
|
|
|
expected = [
|
|
"0x3f0-0x3f0",
|
|
"0x400-0x430",
|
|
"0x440-0x470",
|
|
"0x480-0x4b0",
|
|
"0x4c0-0x4f0",
|
|
"0x500-0x530",
|
|
"0x540-0x570",
|
|
"0x580-0x5b0",
|
|
"0x5c0-0x5f0",
|
|
"0x600-0x630",
|
|
"0x640-0x670",
|
|
"0x680-0x6b0",
|
|
"0x6c0-0x6f0",
|
|
"0x700-0x730",
|
|
"0x740-0x770",
|
|
"0x780-0x7b0",
|
|
"0x7c0-0x7f0",
|
|
"0x800-0x830",
|
|
"0x840-0x870",
|
|
"0x880-0x8b0",
|
|
"0x8c0-0x8f0",
|
|
"0x900-0x930",
|
|
"0x940-0x970",
|
|
"0x980-0x9b0",
|
|
"0x9c0-0x9f0",
|
|
"0xa00-0xa30",
|
|
"0xa40-0xa70",
|
|
"0xa80-0xab0",
|
|
"0xac0-0xaf0",
|
|
"0xb00-0xb30",
|
|
"0xb40-0xb70",
|
|
"0xb80-0xb70", # Largebin 31 (bin 95) is unused, but its size is used to calculate the previous bin's maximum chunk size.
|
|
"0xb80-0xbf0",
|
|
"0xc00-0xdf0",
|
|
"0xe00-0xff0",
|
|
"0x1000-0x11f0",
|
|
"0x1200-0x13f0",
|
|
"0x1400-0x15f0",
|
|
"0x1600-0x17f0",
|
|
"0x1800-0x19f0",
|
|
"0x1a00-0x1bf0",
|
|
"0x1c00-0x1df0",
|
|
"0x1e00-0x1ff0",
|
|
"0x2000-0x21f0",
|
|
"0x2200-0x23f0",
|
|
"0x2400-0x25f0",
|
|
"0x2600-0x27f0",
|
|
"0x2800-0x29f0",
|
|
"0x2a00-0x2ff0",
|
|
"0x3000-0x3ff0",
|
|
"0x4000-0x4ff0",
|
|
"0x5000-0x5ff0",
|
|
"0x6000-0x6ff0",
|
|
"0x7000-0x7ff0",
|
|
"0x8000-0x8ff0",
|
|
"0x9000-0x9ff0",
|
|
"0xa000-0xfff0",
|
|
"0x10000-0x17ff0",
|
|
"0x18000-0x1fff0",
|
|
"0x20000-0x27ff0",
|
|
"0x28000-0x3fff0",
|
|
"0x40000-0x7fff0",
|
|
"0x80000-∞",
|
|
]
|
|
|
|
for bin_index, size_range in enumerate(command_output):
|
|
assert size_range.split(":")[0] == expected[bin_index]
|
|
|
|
|
|
@pwndbg_test
|
|
async def test_smallbins_sizes_64bit(ctrl: Controller) -> None:
|
|
"""
|
|
Ensure the "smallbins" command displays the correct smallbin sizes.
|
|
This test targets 64-bit architectures.
|
|
"""
|
|
await launch_to(ctrl, get_binary("initialized_heap.x86-64.out"), "break_here")
|
|
|
|
command_output = (await ctrl.execute_and_capture("smallbins --verbose")).splitlines()[1:]
|
|
|
|
expected = [
|
|
"0x20",
|
|
"0x30",
|
|
"0x40",
|
|
"0x50",
|
|
"0x60",
|
|
"0x70",
|
|
"0x80",
|
|
"0x90",
|
|
"0xa0",
|
|
"0xb0",
|
|
"0xc0",
|
|
"0xd0",
|
|
"0xe0",
|
|
"0xf0",
|
|
"0x100",
|
|
"0x110",
|
|
"0x120",
|
|
"0x130",
|
|
"0x140",
|
|
"0x150",
|
|
"0x160",
|
|
"0x170",
|
|
"0x180",
|
|
"0x190",
|
|
"0x1a0",
|
|
"0x1b0",
|
|
"0x1c0",
|
|
"0x1d0",
|
|
"0x1e0",
|
|
"0x1f0",
|
|
"0x200",
|
|
"0x210",
|
|
"0x220",
|
|
"0x230",
|
|
"0x240",
|
|
"0x250",
|
|
"0x260",
|
|
"0x270",
|
|
"0x280",
|
|
"0x290",
|
|
"0x2a0",
|
|
"0x2b0",
|
|
"0x2c0",
|
|
"0x2d0",
|
|
"0x2e0",
|
|
"0x2f0",
|
|
"0x300",
|
|
"0x310",
|
|
"0x320",
|
|
"0x330",
|
|
"0x340",
|
|
"0x350",
|
|
"0x360",
|
|
"0x370",
|
|
"0x380",
|
|
"0x390",
|
|
"0x3a0",
|
|
"0x3b0",
|
|
"0x3c0",
|
|
"0x3d0",
|
|
"0x3e0",
|
|
"0x3f0",
|
|
]
|
|
|
|
for bin_index, bin_size in enumerate(command_output):
|
|
assert bin_size.split(":")[0] == expected[bin_index]
|
|
|
|
|
|
@pwndbg_test
|
|
async def test_smallbins_sizes_32bit_big(ctrl: Controller) -> None:
|
|
"""
|
|
Ensure the "smallbins" command displays the correct smallbin sizes.
|
|
This test targets 32-bit architectures with MALLOC_ALIGNMENT == 16.
|
|
"""
|
|
|
|
await launch_to(ctrl, get_binary("initialized_heap_big.i386.out"), "break_here")
|
|
|
|
command_output = (await ctrl.execute_and_capture("smallbins --verbose")).splitlines()[1:]
|
|
|
|
expected = [
|
|
"0x10",
|
|
"0x20",
|
|
"0x30",
|
|
"0x40",
|
|
"0x50",
|
|
"0x60",
|
|
"0x70",
|
|
"0x80",
|
|
"0x90",
|
|
"0xa0",
|
|
"0xb0",
|
|
"0xc0",
|
|
"0xd0",
|
|
"0xe0",
|
|
"0xf0",
|
|
"0x100",
|
|
"0x110",
|
|
"0x120",
|
|
"0x130",
|
|
"0x140",
|
|
"0x150",
|
|
"0x160",
|
|
"0x170",
|
|
"0x180",
|
|
"0x190",
|
|
"0x1a0",
|
|
"0x1b0",
|
|
"0x1c0",
|
|
"0x1d0",
|
|
"0x1e0",
|
|
"0x1f0",
|
|
"0x200",
|
|
"0x210",
|
|
"0x220",
|
|
"0x230",
|
|
"0x240",
|
|
"0x250",
|
|
"0x260",
|
|
"0x270",
|
|
"0x280",
|
|
"0x290",
|
|
"0x2a0",
|
|
"0x2b0",
|
|
"0x2c0",
|
|
"0x2d0",
|
|
"0x2e0",
|
|
"0x2f0",
|
|
"0x300",
|
|
"0x310",
|
|
"0x320",
|
|
"0x330",
|
|
"0x340",
|
|
"0x350",
|
|
"0x360",
|
|
"0x370",
|
|
"0x380",
|
|
"0x390",
|
|
"0x3a0",
|
|
"0x3b0",
|
|
"0x3c0",
|
|
"0x3d0",
|
|
"0x3e0",
|
|
]
|
|
|
|
for bin_index, bin_size in enumerate(command_output):
|
|
assert bin_size.split(":")[0] == expected[bin_index]
|
|
|
|
|
|
@pwndbg_test
|
|
async def test_heap_corruption_low_dereference(ctrl: Controller) -> None:
|
|
"""
|
|
Tests that the bins corruption check doesn't report
|
|
corrupted bins when heap-dereference-limit is less
|
|
than the number of chunks in a bin.
|
|
"""
|
|
|
|
await ctrl.execute("set context-output /dev/null")
|
|
await launch_to(ctrl, BINARY, "breakpoint")
|
|
|
|
await ctrl.cont()
|
|
await ctrl.cont()
|
|
await ctrl.cont()
|
|
|
|
# unsorted bin now has 3 chunks
|
|
|
|
await ctrl.execute("set heap-dereference-limit 1")
|
|
|
|
bins_output = await ctrl.execute_and_capture("bins")
|
|
assert "corrupted" not in bins_output
|