def _malloc_callback(state): # rax contains return address of malloc state.project.unhook(ret_addr) rax = state.regs.rax assert (rax.concrete) rax = rax.args[0] message = "Memory allocate: malloc(%s) => %s" % (hex(size), hex(rax)) state.project.report_logger.info( message, size=size, addr=rax, type="malloc", state_timestamp=state_timestamp(state)) # TODO: check if return addr is sane. symbol = state.project.symbol_resolve.reverse_resolve( rax) # dirty but easy if symbol: print(symbol) message = "Chunk allocated (%s <- %s%+d)not in heap." % ( hex(rax), symbol[0], symbol[1]) backtrace = printable_callstack(state) state.project.report_logger.warn( message, symbol=symbol[0], offset=symbol[1], backtrace=backtrace, type='alloc_warn', state_timestamp=state_timestamp(state)) state.project.heap_analysis.add_chunk(rax, size, state) # # TEST # ms, arena_addr = get_malloc_state(state, rax) # assert(ms) # fastbin_check(state, ms, arena_addr) # bin_check(state, ms, arena_addr) # set bp #if rax - 0x10 in state.project.heap_analysis.bps: # bps = state.project.heap_analysis.bps # for addr, bp in bps.items(): # if addr >= rax-0x10 and addr < rax-0x10 + size: # state.inspect.remove_breakpoint(event_type = 'mem_write', bp = bp) bps = state.project.heap_analysis.free_bps if rax - 0x10 in bps: state.inspect.remove_breakpoint(event_type='mem_write', bp=bps[rax - 0x10]) state.project.heap_analysis.free_bps.pop(rax - 0x10) if rax in bps: state.inspect.remove_breakpoint(event_type='mem_write', bp=bps[rax]) state.project.heap_analysis.free_bps.pop(rax) bp_content = state.inspect.b("mem_write", action=bp_overflow( state.project.report_logger, rax, origin_size)) bp_metadata = state.inspect.b("mem_write", action = \ bp_redzone(state.project.report_logger, rax-0x10, 0x10, allow_heap_ops_size = 0x10, mtype = 'chunk header')) state.project.heap_analysis.inuse_bps[rax] = bp_content state.project.heap_analysis.inuse_bps[rax - 0x10] = bp_metadata
def del_chunk(self, addr, size, state): """ when a chunk is freed, this func is called to do record and check. """ # check if the chunk is allocated backtrace = printable_callstack(state) if addr in self.chunks_av: sizes = self.chunks_av[addr] # check if size matchess if isinstance(sizes, list): if size in sizes: sizes.remove(size) if sizes == []: self.chunks_av.pop(addr) else: self.abused_chunks.append({ "addr": addr, "size": size, "type": "freed with modified size" }) self.project.report_logger.warn( "Chunk freed with modified size", backtrace=backtrace, addr=addr, size=size, type='free_warn', state_timestamp=state_timestamp(state)) # target chunk doesn't been allocated more than one time, # so sizes is an int elif size == sizes: self.chunks_av.pop(addr) # do log in chunks_sv if size in self.chunks_sv: if addr in self.chunks_sv[size]: self.chunks_sv[size].remove(addr) else: # this chunk is not allocated by c/m/relloc self.abused_chunks.append({ "addr": addr, "size": size, "type": "chunk not allocated is freed" }) self.project.report_logger.warn( "Unallocated chunk is freed", backtrace=backtrace, addr=addr, size=size, type='free_warn', state_timestamp=state_timestamp(state))
def add_chunk(self, addr, size, state): """ when a chunk is alloced, this func is called to do record and check. """ backtrace = printable_callstack(state) # do log in chunks_av if addr in self.chunks_av: # why allocated again? sizes = self.chunks_av[addr] if isinstance(sizes, list): sizes.append(size) else: self.chunks_av[addr] = [sizes, size] self.abused_chunks.append({ "addr": addr, "size": size, "type": "allocated mutiple times" }) self.project.report_logger.warn( "Double allocated chunk", backtrace=backtrace, addr=addr, size=size, type='alloc_warn', state_timestamp=state_timestamp(state)) else: self.chunks_av[addr] = size # do log in chunks_sv if size in self.chunks_sv: self.chunks_sv[size].append(addr) else: self.chunks_sv[size] = [addr]
def _calloc_callback(state): # rax contains return address of malloc rax = state.regs.rax assert (rax.concrete) #rax = rax.args[0] state.project.report_logger.info( "Calloc", type='calloc', size=size * count, ret_addr=rax, state_timestamp=state_timestamp(state)) state.project.unhook(ret_addr)
def write_bp(state): nonlocal start_addr, size target_addr = state.inspect.mem_write_address target_size = state.inspect.mem_write_length if type(target_addr) != int: target_addr = target_addr.args[0] if type(target_size) != int: target_size = target_size.args[0] if (target_addr >= start_addr + size) \ or (start_addr >= target_addr + target_size): return info_pos = target_addr info_size = target_size # true analysis starts from here # XXX: at present we extract the write content within the redzone, should we display whole # write content? if target_addr < start_addr: offset = start_addr - target_addr write_size = min(target_size - offset, size) target_addr = start_addr else: offset = 0 write_size = min(target_size, size) assert (write_size) # figure out if this write comes from heap operations if write_size < allow_heap_ops_size: bt = printable_callstack(state) if 'alloc' in bt or 'free' in bt: #print(frame) return write_expr = state.inspect.mem_write_expr write_expr = write_expr[(target_size - offset) * 8 - 1:(target_size - offset - write_size) * 8] if size > 0x40: start_addr = ((target_addr >> 4) << 4) - 0x10 size = ((write_size >> 4) << 4) + 0x10 memory = printable_memory(state, min(start_addr, info_pos)\ , max(size, info_size), warn_pos = target_addr,\ warn_size = write_size, info_pos = info_pos, info_size = info_size) backtrace = printable_callstack(state) message = "Redzone(%s) at %s overwritten." % (mtype, hex(start_addr)) report_logger.warn(message, type="redzone_write",mtype = mtype, start_addr = start_addr, target_addr = target_addr, \ write_size = write_size, write_expr = write_expr, backtrace = backtrace, memory = memory, state_timestamp = state_timestamp(state)) return
def write_bp(state): target_addr = state.inspect.mem_write_address target_size = state.inspect.mem_write_length if type(target_addr) != int: target_addr = target_addr.args[0] if type(target_size) != int: target_size = target_size.args[0] if (target_addr >= start_addr + size) \ or (start_addr >= target_addr + target_size): return if (target_addr + target_size > start_addr + size): overflow_len = target_addr + target_size - (start_addr + size) overflow_content = state.inspect.mem_write_expr[overflow_len * 8 - 1:0] memory = printable_memory(state, min(start_addr, target_addr), max(size,target_size)\ ,warn_pos = start_addr+size, warn_size = overflow_len, info_pos = target_addr\ ,info_size = target_size) message = "Found chunk overflow at %s." % hex(start_addr) report_logger.warn(message, type='heap_overflow', start_addr = start_addr, size = size, target_addr = target_addr, \ target_size = target_size, overflow_len = overflow_len, overflow_content = overflow_content, memory = memory, state_timestamp = state_timestamp(state)) return
def _free_hook(state): """ free hook. Use chunk's metadata to decide chunk size. """ # get addr and chunk_size addr = state.regs.rdi assert (addr.concrete) addr = addr.args[0] size = state.memory.load(addr - 8, 8, endness='Iend_LE') assert (size.concrete) size = size.args[0] size = (size >> 4) << 4 # print("Free called to free %s with size %s" % (hex(addr), hex(size))) message = "Memory free: free(%s) (size: %s)" % (hex(addr), hex(size)) state.project.report_logger.info(message, addr=addr, size=size, type="free", state_timestamp=state_timestamp(state)) # get info for ret callback # stack frame haven't been created, so return address is in rsp ret_addr = state.memory.load(state.regs.rsp, endness='Iend_LE') ret_addr = ret_addr.args[0] state.project.heap_analysis.del_chunk(addr, size, state) # since the chunk is freed, remove write bps bps = state.project.heap_analysis.inuse_bps if addr in bps: state.inspect.remove_breakpoint(event_type='mem_write', bp=bps[addr]) bps.pop(addr) if addr - 0x10 in bps: state.inspect.remove_breakpoint(event_type='mem_write', bp=bps[addr - 0x10]) bps.pop(addr - 0x10) for addr, bp in state.project.heap_analysis.free_bps.items(): #print(state.inspect._breakpoints) state.inspect.remove_breakpoint(event_type='mem_write', bp=bp) state.project.heap_analysis.free_bps = {} def _free_callback(state): state.project.heap_analysis.parse_arena(state) state.project.unhook(ret_addr) # # TEST: to be tested # ms, arena_addr = get_malloc_state(state, addr) # assert(ms) # fastbin_check(state, ms, arena_addr) # bin_check(state, ms, arena_addr) arena = Arena(state, addr=addr) chks = arena.get_all_chunks() for chk in chks: chk_size = (chk[1] >> 4) << 4 bp = state.inspect.b('mem_write', when=angr.BP_AFTER, action=bp_redzone(state.project.report_logger, chk[0], chk_size, allow_heap_ops_size=0x20, mtype="freed chunk")) state.project.heap_analysis.free_bps[chk[0]] = bp #assert(not state.project.is_hooked(ret_addr)) state.project.hook(ret_addr, _free_callback)
def write_bp(state): target_addr = state.inspect.mem_write_address target_size = state.inspect.mem_write_length write_expr = state.inspect.mem_write_expr if type(target_addr) != int: target_addr = target_addr.args[0] if type(target_size) != int: target_size = target_size.args[0] # make sure the write covers our addr if (target_addr >= addr + size): return if (target_addr + target_size <= addr): return # break on BP_AFTER, so we only need to get the value we care about origin = state.memory.load(addr, 8, endness='Iend_LE').args[0] #print(write_expr) bt = stack_backtrace(state) backup = state.memory.load(target_addr, size) state.memory.store(target_addr, write_expr, disable_actions=True, inspect=False) modified = state.memory.load(addr, 8, endness='Iend_LE').args[0] ana.overflow_pos.add(addr) state.memory.store(target_addr, backup, disable_actions=True, inspect=False) if origin == modified: return message = "Return address at %s overwritten to %s" % (hex(addr), hex(modified)) state.project.report_logger.warn(message, type='return_address_overwritten',stack_address = addr, origin_return_address = origin, modified_return_address = modified, \ backtrace = printable_backtrace(bt), state_timestamp = state_timestamp(state)) #print(state.callstack) return
def ret_callback(state): # filter simprocedures at = state.history.bbl_addrs[-1] #print(hex(at)) if at >> 12 == 0x3000: return ret_origin = 0 ret_addr = state.regs.rip origin_rsp = state.regs.rsp.args[0] - 8 assert (ret_addr.concrete) ret_addr = ret_addr.args[0] ana.call_history.append((ret_addr, 'ret')) # try to match ret address if ana.call_stack: if ret_addr in ana.call_stack: while 1: temp = ana.call_stack.pop() if temp == ret_addr: break # TODO: fix call track if ana._last_depth and ana.call_track: ana._last_depth -= 1 else: # FIXME: only reset _last_depth on mismatching???? ana.call_stack.pop() ana._last_depth = 0 ana.call_track = 1 ana.abnormal_calls.append({ "at": state.history.bbl_addrs[-1], "to": ret_addr, "type": "mismatch" }) message = "Strange return to %s" % hex(ret_addr) state.project.report_logger.warn( message, type='strange_return', return_to=ret_addr, ret_information=ret_info(state), state_timestamp=state_timestamp(state)) else: # no frame? must be rop ana.call_track = 1 ana.abnormal_calls.append({ "at": state.history.bbl_addrs[-1], "to": ret_addr, "type": "unrecorded" }) message = "Unrecorded return to %s" % hex(ret_addr) state.project.report_logger.warn( message, type='unrecorded_strange_return', return_to=ret_addr, ret_information=ret_info(state), state_timestamp=state_timestamp(state)) # remove the breakpoint removed = [] for rsp, bp in ana.ret_bps.items(): if rsp <= origin_rsp: removed.append(rsp) state.inspect.remove_breakpoint(event_type='mem_write', bp=bp) for i in removed: ana.ret_bps.pop(i) return