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