diff --git a/HeapInspect.py b/HeapInspect.py old mode 100755 new mode 100644 diff --git a/PANDAHeapInspect.py b/PANDAHeapInspect.py new file mode 100755 index 0000000..16d3e27 --- /dev/null +++ b/PANDAHeapInspect.py @@ -0,0 +1,76 @@ +import argparse +from heapinspect.core import * +from pandare import Panda + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + prog='HeapInspect.py', + description='''Inspect your heap by a given pid. +Author:lacraig2 (forked from matrix1001) +Github:https://github.com/lacraig2/pandaheapinspect (forked from https://github.com/matrix1001/heapinspect)''') + parser.add_argument( + '--raw', + action='store_true', + help='show more detailed chunk info' + ) + parser.add_argument( + '--rela', + action='store_true', + help='show relative detailed chunk info' + ) + parser.add_argument( + '-x', + action='store_false', + help='''ignore: heapchunks''' + ) + + args = parser.parse_args() + + panda = Panda(generic="x86_64") + + @panda.hook_symbol("libc","malloc") + def hook(cpu, tb, h): + print(f"Caught libc:malloc in {panda.get_process_name(cpu)}") + try: + global pid, args + arena_info = {"main_arena_offset": 4111432,"tcache_enable": True} + hi = HeapInspector(0,panda=panda,arena_info=arena_info) + if args.rela: + hs = HeapShower(hi) + hs.relative = True + if args.x: + print(hs.heap_chunks) + print(hs.fastbins) + print(hs.unsortedbins) + print(hs.smallbins) + print(hs.largebins) + print(hs.tcache_chunks) + elif args.raw: + hs = HeapShower(hi) + if args.x: + print(hs.heap_chunks) + print(hs.fastbins) + print(hs.unsortedbins) + print(hs.smallbins) + print(hs.largebins) + print(hs.tcache_chunks) + else: + pp = PrettyPrinter(hi) + print(pp.all) + except Exception as e: + raise e + h.enabled = False + panda.end_analysis() + + + + + @panda.queue_async + def runner(): + panda.revert_sync("root") + panda.run_serial_cmd("ls -la && sleep 10") + panda.end_analysis() + + + panda.run() + diff --git a/heapinspect/core.py b/heapinspect/core.py index 3ea24fb..caaefbb 100644 --- a/heapinspect/core.py +++ b/heapinspect/core.py @@ -32,9 +32,10 @@ class HeapInspector(object): Raises: NotImplementedError: for none supported arch. ''' - def __init__(self, pid): + def __init__(self, pid, panda=None, arena_info=None): self.pid = pid - self.proc = Proc(pid) + self.panda = panda + self.proc = Proc(pid, panda) self.arch = self.proc.arch self.path = self.proc.path self.libc_path = self.proc.libc @@ -51,7 +52,7 @@ def __init__(self, pid): else: raise NotImplementedError('invalid arch') - libc_info = get_libc_info(self.libc_path, self.proc.ld) + libc_info = get_libc_info(self.proc.arch,self.libc_path, self.proc.ld,arena_info,panda) self.libc_version = libc_info['version'] self.tcache_enable = libc_info['tcache_enable'] self.main_arena_offset = libc_info['main_arena_offset'] diff --git a/heapinspect/libc.py b/heapinspect/libc.py index c64f5a1..c570930 100644 --- a/heapinspect/libc.py +++ b/heapinspect/libc.py @@ -31,7 +31,7 @@ def build_helper(out_dir, size_t=8): return out_path -def get_libc_version(path): +def get_libc_version(path,panda=None): '''Get the libc version. Args: @@ -39,8 +39,11 @@ def get_libc_version(path): Returns: str: Libc version. Like '2.29', '2.26' ... ''' - content = subprocess.Popen(['strings', path], stdout=subprocess.PIPE).stdout.read() - content = content.decode() #py3 problem + if panda: + content = path + else: + content = subprocess.Popen(['strings', path], stdout=subprocess.PIPE).stdout.read() + content = content.decode() #py3 problem pattern = "libc[- ]([0-9]+\.[0-9]+)" result = re.findall(pattern, content) if result: @@ -49,7 +52,7 @@ def get_libc_version(path): return "" -def get_arena_info(libc_path, ld_path): +def get_arena_info(arch,libc_path, ld_path, panda=None): '''Get the main arena infomation of the libc. Args: @@ -59,8 +62,9 @@ def get_arena_info(libc_path, ld_path): dict: like {'main_arena_offset':0x1e430, 'tcache_enable':False} ''' cur_dir = os.path.dirname(os.path.realpath(__file__)) - arch = get_arch(libc_path) - libc_version = get_libc_version(libc_path) + if not panda: + arch = get_arch(libc_path) + libc_version = get_libc_version(libc_path,panda=panda) dir_path = tempfile.mkdtemp() # use this to build helper # helper_path = build_helper(dir_path, size_t=size_t) @@ -113,7 +117,7 @@ def get_arena_info(libc_path, ld_path): # dc = json.JSONDecoder() # return dc.decode(result) -def get_libc_info(libc_path, ld_path): +def get_libc_info(arch,libc_path, ld_path, arena_info=None, panda=None): '''Get the infomation of the libc. Args: @@ -123,15 +127,18 @@ def get_libc_info(libc_path, ld_path): dict: like {'main_arena_offset':0x1e430, 'tcache_enable':True, 'version':2.27} ''' - arch = get_arch(libc_path) + #arch = get_arch(libc_path) if arch == '64': size_t = 8 elif arch == '32': size_t = 4 else: raise NotImplementedError - info = {'version': get_libc_version(libc_path)} - info.update(get_arena_info(libc_path, ld_path)) + info = {'version': get_libc_version(libc_path, panda=panda)} + if arena_info: + info.update(arena_info) + else: + info.update(get_arena_info(arch, libc_path,ld_path,panda=panda)) # malloc_state adjust if info['version'] in ['2.27', '2.28']: info['main_arena_offset'] -= size_t diff --git a/heapinspect/proc.py b/heapinspect/proc.py index 44c2a05..7d18b71 100644 --- a/heapinspect/proc.py +++ b/heapinspect/proc.py @@ -58,7 +58,7 @@ def isin(self, addr): return addr >= self.start and addr < self.end -def vmmap(pid): +def vmmap(pid, panda = None): '''Get the vmmap of a process. Note: @@ -70,20 +70,25 @@ def vmmap(pid): ''' maps = [] - mpath = "/proc/%s/maps" % pid - # 00400000-0040b000 r-xp 00000000 08:02 538840 /path/to/file - pattern = re.compile( - "([0-9a-f]*)-([0-9a-f]*) ([rwxps-]*)(?: [^ ]*){3} *(.*)" - ) - out = open(mpath).read() - matches = pattern.findall(out) - if matches: - for (start, end, perm, mapname) in matches: - start = int("0x%s" % start, 16) - end = int("0x%s" % end, 16) - if mapname == "": - mapname = "mapped" - maps.append(Map(start, end, perm, mapname)) + if panda: + for mapping in panda.get_mappings(panda.get_cpu()): + # we don't know the permissions + maps.append(Map(mapping.base, mapping.base+mapping.size, 'rwx', panda.ffi.string(mapping.name).decode())) + else: + mpath = "/proc/%s/maps" % pid + # 00400000-0040b000 r-xp 00000000 08:02 538840 /path/to/file + pattern = re.compile( + "([0-9a-f]*)-([0-9a-f]*) ([rwxps-]*)(?: [^ ]*){3} *(.*)" + ) + out = open(mpath).read() + matches = pattern.findall(out) + if matches: + for (start, end, perm, mapname) in matches: + start = int("0x%s" % start, 16) + end = int("0x%s" % end, 16) + if mapname == "": + mapname = "mapped" + maps.append(Map(start, end, perm, mapname)) return maps @@ -96,21 +101,32 @@ class Proc(object): Args: pid (int): The pid of the process. ''' - def __init__(self, pid): + def __init__(self, pid, panda=None): self.pid = pid - self.arch = get_arch(self.path) + self.panda = panda # even if none + if self.panda: + if self.panda.arch_name == "i386": #get_arch(self.path) + self.arch = '32' + elif self.panda.arch_name == "x86_64": + self.arch = '64' + else: + raise NotImplementedError("Other architectures probably work, but just haven't checked") + else: + self.arch = get_arch(self.path) @property def path(self): '''str: The path to the binary of the process. ''' + if self.panda: + return "panda" return os.path.realpath("/proc/{}/exe".format(self.pid)) @property def vmmap(self): '''list(`Map`): The vmmap of the process. ''' - return vmmap(self.pid) + return vmmap(self.pid, self.panda) def _range_merge(self, lst, range_vec): merged = 0 @@ -265,16 +281,34 @@ def read(self, addr, size): Returns: str: The readed memory. return '' if error. ''' - mem = "/proc/{}/mem".format(self.pid) - f = open(mem, 'rb') - f.seek(addr) - try: - result = f.read(size) - except: - result = "" - print("error reading: {}:{}".format(hex(addr), hex(size))) - f.close() - return result + if self.panda: + any_output = False + output = b"" + while len(output) < size: + try: + cpu = self.panda.get_cpu() + if size > 0x1000: + output += self.panda.virtual_memory_read(cpu, addr, 0x1000) + else: + output += self.panda.virtual_memory_read(cpu, addr, size-len(output)) + any_output = True + except: + #print(f"couldn't read {addr:x}-{addr+0x1000:x}") + output += b"\x00"*0x1000 + addr += 0x1000 + return output if any_output else '' + else: + mem = "/proc/{}/mem".format(self.pid) + f = open(mem, 'rb') + f.seek(addr) + try: + result = f.read(size) + except: + result = "" + print("error reading: {}:{}".format(hex(addr), hex(size))) + f.close() + return result + def search_in_prog(self, search): '''Search in prog. @@ -326,7 +360,7 @@ def search_in_all(self, search): ''' result = [] ignore_list = ['[vvar]', '[vsyscall]'] - for m in vmmap(self.pid): + for m in vmmap(self.panda,self.pid): if "r" in m.perm and m.mapname not in ignore_list: result += self.searchmem(m.start, m.end, search) return result @@ -342,7 +376,7 @@ def searchmem_by_mapname(self, mapname, search): ''' result = [] maps = [] - for m in vmmap(self.pid): + for m in vmmap(self.panda,self.pid): if m.mapname == mapname: maps.append(m) for m in maps: @@ -432,7 +466,7 @@ def libc(self): Raises: Exception: if cannot find the glibc. ''' - for m in vmmap(self.pid): + for m in vmmap(self.pid,panda=self.panda): if re.match(LIBC_REGEX, m.mapname): return m.mapname raise Exception('cannot find libc path') @@ -444,7 +478,7 @@ def ld(self): Raises: Exception: if cannot find ld path. ''' - for m in vmmap(self.pid): + for m in vmmap(self.pid,panda=self.panda): if re.match(LD_REGEX, m.mapname): return m.mapname raise Exception('cannot find ld path') \ No newline at end of file