Skip to content

Instantly share code, notes, and snippets.

@po6ix
Last active May 23, 2023 22:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save po6ix/825d518ecc55cce950878b50ef0def97 to your computer and use it in GitHub Desktop.
Save po6ix/825d518ecc55cce950878b50ef0def97 to your computer and use it in GitHub Desktop.
libsystem_malloc.dylib free list parser
import lldb, struct, shutil
COLOR_YELLOW = '\x1b[33m'
COLOR_GREEN = '\x1b[32m'
COLOR_BLUE = '\x1b[34m'
COLOR_RED = '\x1b[31m'
COLOR_GREY = '\x1b[90m'
COLOR_WHITE = '\x1b[0m'
COLOR_MAGENTA = '\x1b[35m'
COLOR_CYAN = '\x1b[36m'
debugger = None
def format_hex(n, pad = 0):
return '0x' + hex(n)[2:].rjust(pad, '0')
def read(addr, size):
error = lldb.SBError()
mem = process.ReadMemory(addr, size, error)
if not error.Success():
return None
return mem
def runCmd(cmd):
ci = debugger.GetCommandInterpreter()
res = lldb.SBCommandReturnObject()
ci.HandleCommand(cmd, res)
if not res.Succeeded():
return None
return res.GetOutput()
def getImageBase(imageName):
return int(runCmd(f'image list {imageName}').split('0x')[1].split()[0], 16)
def exprToUint64(expr):
return int(runCmd(f'p/x (uint64_t)({expr})').split('=')[1], 16)
def exprToStr(expr):
return runCmd(expr).strip()
def readUint64(addr):
return struct.unpack('Q', read(addr, 8))[0]
def readUint32(addr):
return struct.unpack('I', read(addr, 4))[0]
def readUint16(addr):
return struct.unpack('H', read(addr, 2))[0]
def readUint8(addr):
return struct.unpack('B', read(addr, 1))[0]
def set_global(dbg):
global debugger, target, process
debugger = dbg
target = debugger.GetSelectedTarget()
process = target.GetProcess()
def print_banner(s):
columns = shutil.get_terminal_size().columns
mul = (columns - len(s) - 4) // 2
print('-'*mul + f'[ {s} ]' + '-'*mul)
def print_node(name, address, level = 0):
if address == 0:
return
if 'TINY' in name:
prev = readUint64(address)
next = readUint64(address + 8)
msize = readUint16(address + 0x10)
print(f' ({str(level).rjust(3)}) [ {COLOR_RED}{hex(address)}{COLOR_WHITE} prev: {COLOR_BLUE}{hex(prev)}{COLOR_WHITE} next: {COLOR_BLUE}{hex(next)}{COLOR_WHITE} msize: {COLOR_BLUE}{hex(msize)}{COLOR_WHITE}0 ]')
print_node(name, next, level + 1)
elif 'SMALL' in name:
prev = readUint64(address)
prev_checksum = readUint64(address + 0x8) & 0xff
next = readUint64(address + 0x10)
next_checksum = readUint64(address + 0x18) & 0xff
print(f' ({str(level).rjust(3)}) [ {COLOR_RED}{hex(address)}{COLOR_WHITE} prev: {COLOR_BLUE}{hex(prev)}{COLOR_WHITE} prev_checksum: {COLOR_BLUE}{hex(prev_checksum)}{COLOR_WHITE} next: {COLOR_BLUE}{hex(next)}{COLOR_WHITE} next_checksum: {COLOR_BLUE}{hex(next_checksum)}{COLOR_WHITE} ]')
print_node(name, next, level + 1)
elif 'MEDIUM' in name:
prev = readUint64(address)
next = readUint64(address + 8)
print(f' ({str(level).rjust(3)}) [ {COLOR_RED}{hex(address)}{COLOR_WHITE} prev: {COLOR_BLUE}{hex(prev)}{COLOR_WHITE} next: {COLOR_BLUE}{hex(next)}{COLOR_WHITE} ]')
print_node(name, next, level + 1)
else:
print('THIS SHOULDN\'T BE PRINTED')
return
def print_rack(name, rack, unit):
num_magazines = readUint32(rack + 0x260)
megazines = readUint64(rack + 0x270)
print(f'( {name}->num_magazines: {COLOR_BLUE}{hex(num_magazines)}{COLOR_WHITE} )')
print(f'( {name}->megazines: {COLOR_BLUE}{hex(megazines)}{COLOR_WHITE} )')
for mag_index in range(num_magazines):
megazine = megazines + 0xa00 * mag_index
print(f'( {name}->megazines[{mag_index}] @ {COLOR_RED}{hex(megazine)}{COLOR_WHITE} ', end = '')
magazine_lock = readUint64(megazine)
mag_last_free = readUint64(megazine + 8)
mag_last_free_msize = readUint64(megazine + 0x10)
mag_last_free_rgn = readUint64(megazine + 0x18)
mag_bytes_free_at_end = readUint64(megazine + 0x850)
mag_last_region = readUint64(megazine + 0x860)
firstNode = readUint64(megazine + 0x880)
lastNode = readUint64(megazine + 0x880)
print(f'magazine_lock: {COLOR_BLUE}{hex(magazine_lock)}{COLOR_WHITE} ', end = '')
print(f'mag_last_free: {COLOR_BLUE}{hex(mag_last_free)}{COLOR_WHITE} ', end = '')
if mag_last_free_msize and mag_last_free:
print(f'mag_last_free_msize: {COLOR_BLUE}{hex(mag_last_free_msize)}{COLOR_WHITE} ({COLOR_YELLOW}{hex(mag_last_free_msize * unit)}{COLOR_WHITE}) ', end = '')
else:
print(f'mag_last_free_msize: {COLOR_BLUE}{hex(mag_last_free_msize)}{COLOR_WHITE} ', end = '')
print(f'mag_last_free_rgn: {COLOR_BLUE}{hex(mag_last_free_rgn)}{COLOR_WHITE} ', end = '')
print(f'mag_bytes_free_at_end: {COLOR_BLUE}{hex(mag_bytes_free_at_end)}{COLOR_WHITE} ', end = '')
print(f'mag_last_region: {COLOR_BLUE}{hex(mag_last_region)}{COLOR_WHITE} ', end = ')\n')
for k in range(64):
addr = megazine + 0x20 + k * 8
val = readUint64(addr)
if val != 0:
print(f' [>] mag_free_list[{k}] ({COLOR_BLUE}{hex((k + 1) * unit)}{COLOR_WHITE}) -> {COLOR_RED}{hex(val)}{COLOR_WHITE}')
print_node(name, val)
def findSymbol(name):
return target.FindSymbols(name)[0].symbol.addr.GetLoadAddress(target)
def readString(address):
content = b''
for i in range(100):
content += read(address, 0x10)
address += 0x10
index = content.find(b'\0')
if index != -1:
return content[:index].decode()
return ''
def parse_malloc(debugger, command, result, internal_dict):
set_global(debugger)
malloc_zones = readUint64(findSymbol('malloc_zones'))
print(f'[*] malloc_zones: {COLOR_BLUE}{hex(malloc_zones)}{COLOR_WHITE}')
for k in range(0x10):
zone = readUint64(malloc_zones + k * 8)
if not zone:
break
zone_name = readString(readUint64(zone + 0x48))
print_banner(f'{COLOR_RED}{zone_name}{COLOR_WHITE} @ {COLOR_BLUE}{hex(zone)}{COLOR_WHITE}')
is_medium_engaged = readUint8(zone + 0x4fd8)
tiny_rack = zone + 0x4080
small_rack = zone + 0x4380
medium_rack = zone + 0x4680
print_rack(f'\x1b[44mTINY{COLOR_WHITE}', tiny_rack, 0x10)
print_rack(f'\x1b[43mSMALL{COLOR_WHITE}', small_rack, 0x200)
if is_medium_engaged:
print_rack(f'\x1b[45mMEDIUM{COLOR_WHITE}', medium_rack, 0x8000)
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand('command script add -o -f {}.parse_malloc parse_malloc'.format(__name__))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment