@ -5,6 +5,7 @@ Commands that help with debugging musl's allocator, mallocng.
from __future__ import annotations
import argparse
from typing import Optional
import pwndbg
import pwndbg . aglib . heap . mallocng as mallocng
@ -12,11 +13,27 @@ import pwndbg.aglib.memory as memory
import pwndbg . aglib . typeinfo as typeinfo
import pwndbg . color as C
import pwndbg . color . message as message
from pwndbg import config
from pwndbg . aglib . heap . mallocng import mallocng as ng
from pwndbg . commands import CommandCategory
from pwndbg . lib . pretty_print import Property
from pwndbg . lib . pretty_print import PropertyPrinter
search_on_fail = config . add_param (
" ng-search-on-fail " ,
True ,
" let the ng-slot* commands search the heap if necessary " ,
help_docstring = """
For freed , avail ( able ) and corrupted slots , it may be
impossible to recover the start of the group and meta .
When this option is set to True , the ng - slotu and ng - slots
commands will search the heap to try to find the correct meta / group .
""" ,
param_class = pwndbg . lib . config . PARAM_BOOLEAN ,
scope = pwndbg . lib . config . Scope . heap ,
)
@pwndbg.commands.Command (
" Gives a quick explanation of musl ' s mallocng allocator. " ,
@ -245,7 +262,6 @@ def dump_group(group: mallocng.Group) -> str:
pp = PropertyPrinter ( )
pp . start_section ( " group " , group_range )
pp . set_padding ( 5 )
pp . add (
[
Property ( name = " meta " , value = group . meta . addr , is_addr = True ) ,
@ -256,7 +272,6 @@ def dump_group(group: mallocng.Group) -> str:
if group_size != - 1 :
pp . write ( " --- \n " )
pp . set_padding ( 5 )
pp . add (
[
Property ( name = " group size " , value = group_size ) ,
@ -274,7 +289,6 @@ def dump_meta(meta: mallocng.Meta) -> str:
pp = PropertyPrinter ( )
pp . start_section ( " meta " , " @ " + C . memory . get ( meta . addr ) )
pp . set_padding ( 5 )
pp . add (
[
Property ( name = " prev " , value = meta . prev , is_addr = True ) ,
@ -282,20 +296,17 @@ def dump_meta(meta: mallocng.Meta) -> str:
Property ( name = " mem " , value = meta . mem , is_addr = True , extra = " the group " ) ,
Property ( name = " avail_mask " , value = meta . avail_mask , extra = avail_binary ) ,
Property ( name = " freed_mask " , value = meta . freed_mask , extra = freed_binary ) ,
Property ( name = " last_idx " , value = meta . last_idx , extra = " index of last slot " ) ,
Property (
name = " last_idx " ,
value = meta . last_idx ,
alt_value = f " cnt: { meta . cnt : #x } " ,
extra = " index of last slot " ,
) ,
Property ( name = " freeable " , value = str ( bool ( meta . freeable ) ) ) ,
Property ( name = " sizeclass " , value = meta . sizeclass ) ,
Property ( name = " sizeclass " , value = meta . sizeclass , alt_value = f " stride: { meta . stride : #x } " ),
Property ( name = " maplen " , value = meta . maplen ) ,
]
)
pp . write ( " --- \n " )
pp . set_padding ( 9 )
pp . add (
[
Property ( name = " cnt " , value = meta . cnt , extra = " the number of slots " ) ,
Property ( name = " stride " , value = meta . stride ) ,
]
)
pp . end_section ( )
output = pp . dump ( )
@ -331,43 +342,60 @@ def dump_meta(meta: mallocng.Meta) -> str:
return output
def dump_slot ( slot : mallocng . Slot , all : bool ) - > str :
try :
slot . preload ( )
except pwndbg . dbg_mod . Error as e :
print ( message . error ( f " Error while reading slot: { e } " ) )
return " "
def get_colored_slot_state ( ss : mallocng . SlotState ) - > str :
match ss :
case mallocng . SlotState . ALLOCATED :
return C . green ( ss . value )
case mallocng . SlotState . FREED :
return C . red ( ss . value )
case mallocng . SlotState . AVAIL :
return C . blue ( ss . value )
read_success : bool = True
try :
slot . group . preload ( )
except pwndbg . dbg_mod . Error as e :
print ( message . error ( f " Error while reading group: { e } " ) )
read_success = False
def dump_grouped_slot ( gslot : mallocng . GroupedSlot , all : bool ) - > str :
pp = PropertyPrinter ( )
try :
slot . meta . preload ( )
try :
slot . preload_meta_dependants ( )
except pwndbg . dbg_mod . Error as e1 :
print ( message . error ( f " Error while loading slot fields that depend on the meta: \n { e1 } " ) )
read_success = False
if not all :
pp . start_section ( " slab " )
pp . add (
[
Property ( name = " group " , value = gslot . group . addr , is_addr = True ) ,
Property ( name = " meta " , value = gslot . meta . addr , is_addr = True ) ,
]
)
pp . end_section ( )
except pwndbg . dbg_mod . Error as e2 :
print ( message . error ( f " Error while reading meta: { e2 } " ) )
read_success = False
pp . start_section ( " slot " )
pp . add (
[
Property ( name = " start " , value = gslot . start , is_addr = True ) ,
Property ( name = " end " , value = gslot . end , is_addr = True ) ,
Property ( name = " index " , value = gslot . idx ) ,
Property ( name = " stride " , value = gslot . stride ) ,
Property ( name = " state " , value = get_colored_slot_state ( gslot . slot_state ) ) ,
]
)
pp . end_section ( )
output = pp . dump ( )
if all :
output + = dump_group ( gslot . group )
output + = dump_meta ( gslot . meta )
if not read_success :
print ( message . info ( " Only showing partial information. " ) )
all = False
return output
def dump_slot (
slot : mallocng . Slot , all : bool , successful_preload : bool , will_dump_gslot : bool
) - > str :
pp = PropertyPrinter ( )
all = all and successful_preload and not will_dump_gslot
if not all :
pp . start_section ( " slab " )
pp . set_padding ( 10 )
if read_success :
if successful_preload :
pp . add (
[
Property ( name = " group " , value = slot . group . addr , is_addr = True ) ,
@ -382,9 +410,8 @@ def dump_slot(slot: mallocng.Slot, all: bool) -> str:
)
pp . end_section ( )
if read_ success:
if successful_preload :
pp . start_section ( " general " )
pp . set_padding ( 5 )
pp . add (
[
Property ( name = " start " , value = slot . start , is_addr = True ) ,
@ -405,7 +432,6 @@ def dump_slot(slot: mallocng.Slot, all: bool) -> str:
pp . end_section ( )
pp . start_section ( " in-band " )
pp . set_padding ( 2 )
reserved_extra = [ " describes: end - p - n " ]
if slot . reserved_in_header == 5 :
@ -428,12 +454,12 @@ def dump_slot(slot: mallocng.Slot, all: bool) -> str:
if slot . reserved_in_header == 5 :
ftrsv = " NA (meta error) "
if read_ success:
if successful_preload :
ftrsv = slot . reserved_in_footer
inband_group . append ( Property ( name = " ftr reserved " , value = ftrsv ) )
if read_ success:
if successful_preload :
# Start header fields.
if slot . is_cyclic ( ) :
cyc_val = slot . cyclic_offset
@ -455,6 +481,13 @@ def dump_slot(slot: mallocng.Slot, all: bool) -> str:
output = pp . dump ( )
if not will_dump_gslot :
# The grouped_slot will have accurate information on this,
# no need for us to guess.
output + = C . bold (
" \n The slot is (probably) " + get_colored_slot_state ( slot . slot_state ) + " . \n \n "
)
if all :
output + = dump_group ( slot . group )
output + = dump_meta ( slot . meta )
@ -462,6 +495,92 @@ def dump_slot(slot: mallocng.Slot, all: bool) -> str:
return output
def smart_dump_slot (
slot : mallocng . Slot , all : bool , gslot : Optional [ mallocng . GroupedSlot ] = None
) - > str :
try :
slot . preload ( )
except pwndbg . dbg_mod . Error as e :
print ( message . error ( f " Error while reading slot: { e } " ) )
return " "
successful_preload : bool = True
err_msg = " "
try :
slot . group . preload ( )
except pwndbg . dbg_mod . Error as e :
err_msg = message . error ( f " Error while reading group: { e } " )
successful_preload = False
if successful_preload :
try :
slot . meta . preload ( )
try :
slot . preload_meta_dependants ( )
except pwndbg . dbg_mod . Error as e1 :
err_msg = message . error (
f " Error while loading slot fields that depend on the meta: \n { e1 } "
)
successful_preload = False
except pwndbg . dbg_mod . Error as e2 :
err_msg = message . error ( f " Error while reading meta: { e2 } " )
successful_preload = False
if successful_preload :
# If we successfully got the group and meta, using the grouped_slot won't
# give us any new information.
# (Unless the grouped_slot reports a different group than slot.group, which
# could be possible in exploitation I suppose).
return dump_slot ( slot , all , True , False )
if not ( slot . _pn3 == 0xFF or slot . _offset == 0 ) :
# If the group/meta read failed because the slot is freed/avail,
# we won't throw an error. This is just a heuristic check for
# better UX. I'm using the private fields for the check so we
# don't accidentally cause an exception here if we are bordering
# unreadable memory.
print ( err_msg )
output = " "
if gslot is None :
if not search_on_fail :
output + = " Could not load valid meta from local information. \n "
output + = " Will not attempt to search the heap because ng-search-on-fail = False. \n \n "
output + = dump_slot ( slot , all , False , False )
return output
# If it wasn't provided to us, let's try to search for it now.
output + = " Could not load valid meta from local information, searching the heap.. "
ng . init_if_needed ( )
gslot , fslot = ng . find_slot ( slot . p , False , False )
if gslot is None :
output + = " Not found. \n \n "
output + = dump_slot ( slot , all , False , False )
return output
else :
if fslot . p == slot . p :
output + = " Found it. \n \n "
else :
output + = " \n Found a slot with p @ " + C . memory . get ( fslot . p ) + " . "
output + = " The slot you are looking for \n doesn ' t seem to exist. Maybe its group got freed? \n \n "
output + = " Local memory: \n "
output + = dump_slot ( slot , all , False , False )
return output
# Now we have a valid gslot.
output + = " Local slot memory: \n "
output + = dump_slot ( slot , all , False , True )
output + = " \n Slot information from the group/meta: \n "
output + = dump_grouped_slot ( gslot , all )
return output
parser = argparse . ArgumentParser (
description = """
Dump information about a mallocng slot , given its user address .
@ -492,7 +611,7 @@ def mallocng_slot_user(address: int, all: bool) -> None:
return
slot = mallocng . Slot ( address )
print ( dump_slot( slot , all ) , end = " " )
print ( smart_ dump_slot( slot , all , None ) , end = " " )
parser = argparse . ArgumentParser (
@ -525,7 +644,7 @@ def mallocng_slot_start(address: int, all: bool) -> None:
return
slot = mallocng . Slot . from_start ( address )
print ( dump_slot( slot , all ) , end = " " )
print ( smart_ dump_slot( slot , all , None ) , end = " " )
parser = argparse . ArgumentParser (
@ -662,10 +781,10 @@ def mallocng_find(
ng . init_if_needed ( )
slot_start = ng . containing ( address , metadata , shallow )
grouped_slot, slot = ng . find_slot ( address , metadata , shallow )
if slot _start == 0 :
if slot is None :
print ( message . info ( " No slot found containing that address. " ) )
return
mallocng_slot_start ( slot_start , all = all )
print ( smart_dump_slot ( slot , all , grouped_slot ) , end = " " )