Exemplo n.º 1
0
    def _sym_exec(self, gblState, depth=0, constraints=[]):

        environment = gblState.environment
        disassembly = environment.code
        state = gblState.mstate
        depth = depth

        start_addr = disassembly.instruction_list[state.pc]['address']

        if start_addr == 0:
            self.current_func = "fallback"
            self.current_func_addr = start_addr

        node = Node(environment.active_account.contract_name, start_addr, constraints)

        logging.debug("- Entering node " + str(node.uid) + ", index = " + str(state.pc) + ", address = " + str(start_addr) + ", depth = " + str(depth))

        if start_addr in disassembly.addr_to_func:
            # Enter a new function

            function_name = disassembly.addr_to_func[start_addr]
            self.current_func = function_name
            node.flags |= NodeFlags.FUNC_ENTRY

            logging.info("- Entering function " + environment.active_account.contract_name + ":" + function_name)

        node.function_name = self.current_func

        halt = False

        while not halt:

            try:
                instr = disassembly.instruction_list[state.pc]
            except IndexError:
                logging.debug("Invalid PC")
                return node

            # Save state before modifying anything

            node.states.append(gblState)
            gblState = self.copy_global_state(gblState)

            state = gblState.mstate

            self.total_states += 1

            # Point program counter to next instruction

            state.pc += 1
            op = instr['opcode']

            # logging.debug("[" + environment.active_account.contract_name + "] " + helper.get_trace_line(instr, state))
            # slows down execution significantly.

            # Stack ops

            if op.startswith("PUSH"):
                value = BitVecVal(int(instr['argument'][2:], 16), 256)
                state.stack.append(value)

            elif op.startswith('DUP'):
                dpth = int(op[3:])

                try:
                    state.stack.append(state.stack[-dpth])
                except:
                    halt = True

            elif op.startswith('SWAP'):

                dpth = int(op[4:])

                try:
                    temp = state.stack[-dpth - 1]

                    state.stack[-dpth - 1] = state.stack[-1]
                    state.stack[-1] = temp
                except IndexError:  # Stack underflow
                    halt = True

            elif op == 'POP':
                try:
                    state.stack.pop()
                except IndexError:  # Stack underflow
                    halt = True

            # Bitwise ops

            elif op == 'AND':
                try:
                    op1, op2 = state.stack.pop(), state.stack.pop()
                    if (type(op1) == BoolRef):
                        op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))

                    if (type(op2) == BoolRef):
                        op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))

                    state.stack.append(op1 & op2)
                except IndexError:  # Stack underflow
                    halt = True

            elif op == 'OR':
                try:
                    op1, op2 = state.stack.pop(), state.stack.pop()

                    if (type(op1) == BoolRef):
                        op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))

                    if (type(op2) == BoolRef):
                        op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))

                    state.stack.append(op1 | op2)
                except IndexError:  # Stack underflow
                    halt = True

            elif op == 'XOR':
                state.stack.append(state.stack.pop() ^ state.stack.pop())

            elif op == 'NOT':
                state.stack.append(TT256M1 - state.stack.pop())

            elif op == 'BYTE':
                s0, s1 = state.stack.pop(), state.stack.pop()

                state.stack.append(BitVecVal(0, 256))

            # Arithmetics

            elif op == 'ADD':
                state.stack.append((helper.pop_bitvec(state) + helper.pop_bitvec(state)))

            elif op == 'SUB':
                state.stack.append((helper.pop_bitvec(state) - helper.pop_bitvec(state)))

            elif op == 'MUL':
                state.stack.append(helper.pop_bitvec(state) * helper.pop_bitvec(state))

            elif op == 'DIV':
                state.stack.append(UDiv(helper.pop_bitvec(state), helper.pop_bitvec(state)))

            elif op == 'MOD':
                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
                state.stack.append(0 if s1 == 0 else URem(s0, s1))

            elif op == 'SDIV':
                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
                state.stack.append(s0 / s1)

            elif op == 'SMOD':
                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
                state.stack.append(0 if s1 == 0 else s0 % s1)

            elif op == 'ADDMOD':
                s0, s1, s2 = helper.pop_bitvec(state), helper.pop_bitvec(state), helper.pop_bitvec(state)
                state.stack.append((s0 + s1) % s2 if s2 else 0)

            elif op == 'MULMOD':
                s0, s1, s2 = helper.pop_bitvec(state), helper.pop_bitvec(state), helper.pop_bitvec(state)
                state.stack.append((s0 * s1) % s2 if s2 else 0)

            elif op == 'EXP':
                # we only implement 2 ** x
                base, exponent = helper.pop_bitvec(state), helper.pop_bitvec(state)

                if (type(base) != BitVecNumRef) or (type(exponent) != BitVecNumRef):
                    state.stack.append(BitVec(str(base) + "_EXP_" + str(exponent), 256))
                elif (base.as_long() == 2):
                    if exponent.as_long() == 0:
                        state.stack.append(BitVecVal(1, 256))
                    else:
                        state.stack.append(base << (exponent - 1))

                else:
                    state.stack.append(base)

            elif op == 'SIGNEXTEND':
                s0, s1 = state.stack.pop(), state.stack.pop()

                try:
                    s0 = helper.get_concrete_int(s0)
                    s1 = helper.get_concrete_int(s1)

                    if s0 <= 31:
                        testbit = s0 * 8 + 7
                        if s1 & (1 << testbit):
                            state.stack.append(s1 | (TT256 - (1 << testbit)))
                        else:
                            state.stack.append(s1 & ((1 << testbit) - 1))
                    else:
                        state.stack.append(s1)    
                except:
                    halt = True
                    continue

            # Comparisons

            elif op == 'LT':

                exp = ULT(helper.pop_bitvec(state), helper.pop_bitvec(state))
                state.stack.append(exp)

            elif op == 'GT':

                exp = UGT(helper.pop_bitvec(state), helper.pop_bitvec(state))
                state.stack.append(exp)

            elif op == 'SLT':

                exp = helper.pop_bitvec(state) < helper.pop_bitvec(state)
                state.stack.append(exp)

            elif op == 'SGT':

                exp = helper.pop_bitvec(state) > helper.pop_bitvec(state)
                state.stack.append(exp)

            elif op == 'EQ':

                op1 = state.stack.pop()
                op2 = state.stack.pop()

                if(type(op1) == BoolRef):
                    op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))

                if(type(op2) == BoolRef):
                    op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))

                exp = op1 == op2

                state.stack.append(exp)

            elif op == 'ISZERO':

                val = state.stack.pop()

                if (type(val) == BoolRef):
                    exp = val == False
                else:
                    exp = val == 0

                state.stack.append(exp)

            # Call data

            elif op == 'CALLVALUE':
                state.stack.append(environment.callvalue)

            elif op == 'CALLDATALOAD':
                # unpack 32 bytes from calldata into a word and put it on the stack

                op0 = state.stack.pop()

                try:
                    offset = helper.get_concrete_int(simplify(op0))
                    b = environment.calldata[offset]

                except AttributeError:
                    logging.debug("CALLDATALOAD: Unsupported symbolic index")
                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
                    continue
                except IndexError:
                    logging.debug("Calldata not set, using symbolic variable instead")
                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
                    continue

                if type(b) == int:

                    val = b''

                    try:
                        for i in range(offset, offset + 32):
                            val += environment.calldata[i].to_bytes(1, byteorder='big')

                        logging.debug("Final value: " + str(int.from_bytes(val, byteorder='big')))
                        state.stack.append(BitVecVal(int.from_bytes(val, byteorder='big'), 256))

                    except:
                        state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))
                else:
                    # symbolic variable
                    state.stack.append(BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(op0), 256))

            elif op == 'CALLDATASIZE':

                if environment.calldata_type == CalldataType.SYMBOLIC:
                    state.stack.append(BitVec("calldatasize_" + environment.active_account.contract_name, 256))
                else:
                    state.stack.append(BitVecVal(len(environment.calldata), 256))

            elif op == 'CALLDATACOPY':
                op0, op1, op2 = state.stack.pop(), state.stack.pop(), state.stack.pop()

                try:
                    mstart = helper.get_concrete_int(op0)
                except:
                    logging.debug("Unsupported symbolic memory offset in CALLDATACOPY")
                    continue

                try:
                    dstart = helper.get_concrete_int(op1)
                except:
                    logging.debug("Unsupported symbolic calldata offset in CALLDATACOPY")
                    state.mem_extend(mstart, 1)
                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_cpy", 256)
                    continue

                try:
                    size = helper.get_concrete_int(op2)
                except:
                    logging.debug("Unsupported symbolic size in CALLDATACOPY")
                    state.mem_extend(mstart, 1)
                    state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
                    continue

                if size > 0:

                    try:
                        state.mem_extend(mstart, size)
                    except:
                        logging.debug("Memory allocation error: mstart = " + str(mstart) + ", size = " + str(size))
                        state.mem_extend(mstart, 1)
                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)
                        continue

                    try:
                        i_data = environment.calldata[dstart]

                        for i in range(mstart, mstart + size):
                            state.memory[i] = environment.calldata[i_data]
                            i_data += 1
                    except:
                        logging.debug("Exception copying calldata to memory")

                        state.memory[mstart] = BitVec("calldata_" + str(environment.active_account.contract_name) + "_" + str(dstart), 256)

            elif op == 'STOP':
                if len(self.call_stack):
                    self.pending_returns[self.call_stack[-1]].append(node.uid)

                halt = True
                continue

            # Environment

            elif op == 'ADDRESS':
                state.stack.append(environment.sender)

            elif op == 'BALANCE':
                addr = state.stack.pop()
                state.stack.append(BitVec("balance_at_" + str(addr), 256))

            elif op == 'ORIGIN':
                state.stack.append(environment.origin)

            elif op == 'CALLER':
                state.stack.append(environment.sender)

            elif op == 'CODESIZE':
                state.stack.append(len(disassembly.instruction_list))

            if op == 'SHA3':
                op0, op1 = state.stack.pop(), state.stack.pop()

                try:
                    index, length = helper.get_concrete_int(op0), helper.get_concrete_int(op1)

                except:
                    # Can't access symbolic memory offsets
                    state.stack.append(BitVec("KECCAC_mem_" + str(op0) + ")", 256))
                    continue

                try:
                    data = b''

                    for i in range(index, index + length):
                        data += helper.get_concrete_int(state.memory[i]).to_bytes(1, byteorder='big')
                        i += 1
                except:

                    svar = str(state.memory[index])

                    svar = svar.replace(" ", "_")

                    state.stack.append(BitVec("keccac_" + svar, 256))
                    continue

                keccac = utils.sha3(utils.bytearray_to_bytestr(data))
                logging.debug("Computed SHA3 Hash: " + str(binascii.hexlify(keccac)))

                state.stack.append(BitVecVal(helper.concrete_int_from_bytes(keccac, 0), 256))

            elif op == 'GASPRICE':
                state.stack.append(BitVec("gasprice", 256))

            elif op == 'CODECOPY':
                # Not implemented
                start, s1, size = state.stack.pop(), state.stack.pop(), state.stack.pop()

            elif op == 'EXTCODESIZE':
                addr = state.stack.pop()
                state.stack.append(BitVec("extcodesize", 256))

            elif op == 'EXTCODECOPY':
                # Not implemented

                addr = state.stack.pop()
                start, s2, size = state.stack.pop(), state.stack.pop(), state.stack.pop()

            elif op == 'BLOCKHASH':
                blocknumber = state.stack.pop()
                state.stack.append(BitVec("blockhash_block_" + str(blocknumber), 256))

            elif op == 'COINBASE':
                state.stack.append(BitVec("coinbase", 256))

            elif op == 'TIMESTAMP':
                state.stack.append(BitVec("timestamp", 256))

            elif op == 'NUMBER':
                state.stack.append(BitVec("block_number", 256))

            elif op == 'DIFFICULTY':
                state.stack.append(BitVec("block_difficulty", 256))

            elif op == 'GASLIMIT':
                state.stack.append(BitVec("block_gaslimit", 256))

            elif op == 'MLOAD':

                op0 = state.stack.pop()

                logging.debug("MLOAD[" + str(op0) + "]")

                try:
                    offset = helper.get_concrete_int(op0)
                except AttributeError:
                    logging.debug("Can't MLOAD from symbolic index")
                    data = BitVec("mem_" + str(op0), 256)
                    continue

                try:
                    data = helper.concrete_int_from_bytes(state.memory, offset)
                except IndexError:  # Memory slot not allocated
                    data = BitVec("mem_" + str(offset), 256)
                except TypeError:  # Symbolic memory
                    data = state.memory[offset]

                logging.debug("Load from memory[" + str(offset) + "]: " + str(data))

                state.stack.append(data)

            elif op == 'MSTORE':

                op0, value = state.stack.pop(), state.stack.pop()

                try:
                    mstart = helper.get_concrete_int(op0)
                except AttributeError:
                    logging.debug("MSTORE to symbolic index. Not supported")
                    continue

                try:
                    state.mem_extend(mstart, 32)
                except Exception:
                    logging.debug("Error extending memory, mstart = " + str(mstart) + ", size = 32")

                logging.debug("MSTORE to mem[" + str(mstart) + "]: " + str(value))

                try:
                    # Attempt to concretize value
                    _bytes = helper.concrete_int_to_bytes(value)

                    i = 0

                    for b in _bytes:
                        state.memory[mstart + i] = _bytes[i]
                        i += 1

                except:
                    try:
                        state.memory[mstart] = value
                    except:
                        logging.debug("Invalid memory access")
                        continue

            elif op == 'MSTORE8':
                op0, value = state.stack.pop(), state.stack.pop()

                try:
                    offset = helper.get_concrete_int(op0)
                except AttributeError:
                    logging.debug("MSTORE to symbolic index. Not supported")
                    continue

                state.mem_extend(offset, 1)

                state.memory[offset] = value % 256

            elif op == 'SLOAD':
                index = state.stack.pop()
                logging.debug("Storage access at index " + str(index))

                try:
                    index = helper.get_concrete_int(index)
                except AttributeError:
                    index = str(index)

                try:
                    data = gblState.accounts[gblState.environment.sender].storage[index]
                except KeyError:
                    data = BitVec("storage_" + str(index), 256)
                    gblState.environment.active_account.storage[index] = data

                state.stack.append(data)

            elif op == 'SSTORE':
                index, value = state.stack.pop(), state.stack.pop()

                logging.debug("Write to storage[" + str(index) + "] at node " + str(start_addr))

                try:
                    index = helper.get_concrete_int(index)
                except AttributeError:
                    index = str(index)

                try:
                    # Create a fresh copy of the account object before modifying storage

                    for k in gblState.accounts:
                        if gblState.accounts[k] == gblState.environment.active_account:
                            gblState.accounts[k] = copy.deepcopy(gblState.accounts[k])
                            gblState.environment.active_account = gblState.accounts[k]
                            break

                    gblState.environment.active_account.storage[index] = value
                except KeyError:
                    logging.debug("Error writing to storage: Invalid index")
                    continue

            elif op == 'JUMP':

                try:
                    jump_addr = helper.get_concrete_int(state.stack.pop())
                except AttributeError:
                    logging.debug("Invalid jump argument (symbolic address)")
                    halt = True
                    continue
                except IndexError:  # Stack Underflow
                    halt = True
                    continue

                if (depth < self.max_depth):

                    i = helper.get_instruction_index(disassembly.instruction_list, jump_addr)

                    if i is None:
                        logging.debug("JUMP to invalid address")
                        halt = True
                        continue

                    opcode = disassembly.instruction_list[i]['opcode']

                    if opcode == "JUMPDEST":

                        new_gblState = self.copy_global_state(gblState)
                        new_gblState.mstate.pc = i

                        new_node = self._sym_exec(new_gblState, depth=depth + 1, constraints=constraints)
                        self.nodes[new_node.uid] = new_node

                        self.edges.append(Edge(node.uid, new_node.uid, JumpType.UNCONDITIONAL))
                        halt = True
                        continue

                    else:
                        logging.debug("Skipping JUMP to invalid destination (not JUMPDEST): " + str(jump_addr))
                        halt = True
                        # continue
                else:
                    logging.debug("Max depth reached, skipping JUMP")
                    halt = True
                    # continue

            elif op == 'JUMPI':
                op0, condition = state.stack.pop(), state.stack.pop()

                try:
                    jump_addr = helper.get_concrete_int(op0)
                except:
                    logging.debug("Skipping JUMPI to invalid destination.")

                if (depth < self.max_depth):

                    i = helper.get_instruction_index(disassembly.instruction_list, jump_addr)

                    if not i:
                        logging.debug("Invalid jump destination: " + str(jump_addr))

                    else:
                        instr = disassembly.instruction_list[i]

                        if instr['opcode'] != "JUMPDEST":
                            logging.debug("Invalid jump destination: " + str(jump_addr))
                            halt = True
                            continue

                        elif (type(condition) == BoolRef):

                            if not is_false(simplify(condition)):

                                # Create new node for condition == True

                                new_gblState = self.copy_global_state(gblState)
                                new_gblState.mstate.pc = i

                                new_constraints = copy.deepcopy(constraints)
                                new_constraints.append(condition)

                                new_node = self._sym_exec(new_gblState, depth=depth + 1, constraints=new_constraints)
                                self.nodes[new_node.uid] = new_node
                                self.edges.append(Edge(node.uid, new_node.uid, JumpType.CONDITIONAL, condition))

                            else:
                                logging.debug("Pruned unreachable states.")

                        else:
                            logging.debug("Invalid condition: " + str(condition) + "(type " + str(type(condition)) + ")")
                            halt = True
                            continue

                        new_gblState = self.copy_global_state(gblState)

                        if (type(condition) == BoolRef):
                            negated = Not(condition)
                        else:
                            negated = condition == 0

                        if not is_false(simplify(negated)):

                            new_constraints = copy.deepcopy(constraints)
                            new_constraints.append(negated)

                            new_node = self._sym_exec(new_gblState, depth=depth, constraints=new_constraints)
                            self.nodes[new_node.uid] = new_node
                            self.edges.append(Edge(node.uid, new_node.uid, JumpType.CONDITIONAL, negated))

                        halt = True
                        # continue

                else:
                    logging.debug("Max depth reached, skipping JUMPI")

            elif op == 'PC':
                state.stack.append(state.pc - 1)

            elif op == 'MSIZE':
                state.stack.append(BitVec("msize", 256))

            elif op == 'GAS':
                state.stack.append(BitVec("gas", 256))

            elif op.startswith('LOG'):
                dpth = int(op[3:])
                state.stack.pop(), state.stack.pop()
                [state.stack.pop() for x in range(dpth)]
                # Not supported

            elif op == 'CREATE':
                state.stack.pop(), state.stack.pop(), state.stack.pop()
                # Not supported
                state.stack.append(0)

            elif op in ('CALL', 'CALLCODE', 'DELEGATECALL', 'STATICCALL'):

                if op in ('CALL', 'CALLCODE'):
                    gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \
                        state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()

                else:
                    gas, to, meminstart, meminsz, memoutstart, memoutsz = \
                        state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()

                try:
                    callee_address = hex(helper.get_concrete_int(to))

                except AttributeError:
                    # Not a concrete call address. Call target may be an address in storage.

                    m = re.search(r'storage_(\d+)', str(simplify(to)))

                    logging.debug("CALL to: " + str(simplify(to)))

                    if (m and self.dynamic_loader is not None):
                        idx = int(m.group(1))
                        logging.info("Dynamic contract address at storage index " + str(idx))

                        # attempt to read the contract address from instance storage

                        try:
                            callee_address = self.dynamic_loader.read_storage(environment.active_account.address, idx)
                        except:
                            logging.debug("Error accessing contract storage.")
                            ret = BitVec("retval_" + str(instr['address']), 256)
                            state.stack.append(ret)
                            continue

                        # testrpc simply returns the address, geth response is more elaborate.

                        if not re.match(r"^0x[0-9a-f]{40}$", callee_address):

                            callee_address = "0x" + callee_address[26:]

                    else:
                        ret = BitVec("retval_" + str(instr['address']), 256)
                        state.stack.append(ret)
                        continue

                if not re.match(r"^0x[0-9a-f]{40}", callee_address):
                        logging.debug("Invalid address: " + str(callee_address))
                        ret = BitVec("retval_" + str(instr['address']), 256)
                        state.stack.append(ret)
                        continue

                if (int(callee_address, 16) < 5):

                    logging.info("Native contract called: " + callee_address)

                    # Todo: Implement native contracts

                    ret = BitVec("retval_" + str(instr['address']), 256)
                    state.stack.append(ret)
                    continue

                try:

                    callee_account = self.accounts[callee_address]

                except KeyError:
                    # We have a valid call address, but contract is not in the modules list

                    logging.info("Module with address " + callee_address + " not loaded.")

                    if self.dynamic_loader is not None:

                        logging.info("Attempting to load dependency")

                        try:
                            code = self.dynamic_loader.dynld(environment.active_account.address, callee_address)
                        except Exception as e:
                            logging.info("Unable to execute dynamic loader.")

                        if code is None:

                            logging.info("No code returned, not a contract account?")
                            ret = BitVec("retval_" + str(instr['address']), 256)
                            state.stack.append(ret)
                            continue

                        # New contract bytecode loaded successfully, create a new contract account

                        self.accounts[callee_address] = Account(callee_address, code, callee_address)

                        logging.info("Dependency loaded: " + callee_address)

                    else:
                        logging.info("Dynamic loader unavailable. Skipping call")
                        ret = BitVec("retval_" + str(instr['address']), 256)
                        state.stack.append(ret)
                        continue

                logging.info("Executing " + op + " to: " + callee_address)

                try:
                    callee_account = self.accounts[callee_address]
                except KeyError:
                    logging.info("Contract " + str(callee_address) + " not loaded.")
                    logging.info((str(self.accounts)))

                    ret = BitVec("retval_" + str(instr['address']), 256)
                    state.stack.append(ret)
                    continue

                try:
                    # TODO: This only allows for either fully concrete or fully symbolic calldata.
                    # Improve management of memory and callata to support a mix between both types.

                    calldata = state.memory[helper.get_concrete_int(meminstart):helper.get_concrete_int(meminstart + meminsz)]

                    if (len(calldata) < 32):
                        calldata += [0] * (32 - len(calldata))

                    calldata_type = CalldataType.CONCRETE
                    logging.debug("Calldata: " + str(calldata))

                except AttributeError:

                    logging.info("Unsupported symbolic calldata offset")
                    calldata_type = CalldataType.SYMBOLIC
                    calldata = []

                self.call_stack.append(instr['address'])
                self.pending_returns[instr['address']] = []

                if (op == 'CALL'):

                    callee_environment = Environment(callee_account, BitVecVal(int(environment.active_account.address, 16), 256), calldata, environment.gasprice, value, environment.origin, calldata_type=calldata_type)
                    new_gblState = GlobalState(gblState.accounts, callee_environment, MachineState(gas))

                    new_node = self._sym_exec(new_gblState, depth=depth + 1, constraints=constraints)

                    self.nodes[new_node.uid] = new_node

                elif (op == 'CALLCODE'):

                    temp_callvalue = environment.callvalue
                    temp_caller = environment.caller
                    temp_calldata = environment.calldata

                    environment.callvalue = value
                    environment.caller = environment.address
                    environment.calldata = calldata

                    new_gblState = GlobalState(gblState.accounts, environment, MachineState(gas))

                    new_node = self._sym_exec(new_gblState, depth=depth + 1, constraints=constraints)
                    self.nodes[new_node.uid] = new_node

                    environment.callvalue = temp_callvalue
                    environment.caller = temp_caller
                    environment.calldata = temp_calldata

                elif (op == 'DELEGATECALL'):
                    temp_code = environment.code
                    temp_calldata = environment.calldata

                    environment.code = callee_account.code
                    environment.calldata = calldata

                    new_gblState = GlobalState(gblState.accounts, environment, MachineState(gas))

                    new_node = self._sym_exec(new_gblState, depth=depth + 1, constraints=constraints)
                    self.nodes[new_node.uid] = new_node

                    environment.code = temp_code
                    environment.calldata = temp_calldata

                self.edges.append(Edge(node.uid, new_node.uid, JumpType.CALL))

                '''
                There may be multiple possible returns from the callee contract. Currently, we don't create separate nodes on the CFG
                for each of them. Instead, a single "return node" is created and a separate edge is added for each return path.
                The return value is always symbolic.
                '''

                ret = BitVec("retval_" + str(disassembly.instruction_list[state.pc]['address']), 256)
                state.stack.append(ret)

                return_address = self.call_stack.pop()

                new_gblState = self.copy_global_state(gblState)
                new_node = self._sym_exec(gblState, depth=depth + 1, constraints=constraints)
                new_node.flags |= NodeFlags.CALL_RETURN

                self.nodes[new_node.uid] = new_node

                for ret_uid in self.pending_returns[return_address]:
                    self.edges.append(Edge(ret_uid, new_node.uid, JumpType.RETURN))

                state.stack.append(BitVec("retval", 256))

                halt = True

            elif op == 'RETURN':
                offset, length = state.stack.pop(), state.stack.pop()

                try:
                    self.last_returned = state.memory[helper.get_concrete_int(offset):helper.get_concrete_int(offset + length)]
                except AttributeError:
                    logging.debug("Return with symbolic length or offset. Not supported")

                if len(self.call_stack):
                    self.pending_returns[self.call_stack[-1]].append(node.uid)

                halt = True

            elif op == 'SUICIDE':
                halt = True

            elif op == 'REVERT':
                if len(self.call_stack):
                    self.pending_returns[self.call_stack[-1]].append(node.uid)

                halt = True

            elif op == 'ASSERT_FAIL' or op == 'INVALID':
                if len(self.call_stack):
                    self.pending_returns[self.call_stack[-1]].append(node.uid)

                halt = True

        logging.debug("Returning from node " + str(node.uid))
        return node
Exemplo n.º 2
0
    def _sym_exec(self, context, state, depth=0, constraints=[]):

        disassembly = context.module['disassembly']
        depth = depth

        start_addr = disassembly.instruction_list[state.pc]['address']

        if start_addr == 0:
            self.execution_state['current_func'] = "fallback"
            self.execution_state['current_func_addr'] = start_addr

        node = Node(context.module['name'], start_addr, constraints)

        logging.debug("DEBUG Node(type=" + str(type(node)) + "): " + str(node))

        logging.debug("- Entering block " + str(node.uid) + ", index = " +
                      str(state.pc) + ", address = " + str(start_addr) +
                      ", depth = " + str(depth))

        if start_addr in disassembly.addr_to_func:
            # Enter a new function

            function_name = disassembly.addr_to_func[start_addr]
            self.execution_state['current_func'] = function_name

            logging.info("- Entering function " + context.module['name'] +
                         ":" + function_name)

            node.instruction_list.append({
                'opcode':
                function_name,
                'address':
                disassembly.instruction_list[state.pc]['address']
            })

            state.pc += 1

        node.function_name = self.execution_state['current_func']

        halt = False

        instr = disassembly.instruction_list[state.pc]

        while not halt:

            try:
                instr = disassembly.instruction_list[state.pc]
            except IndexError:
                logging.debug("Invalid PC")
                return node

            # Save instruction and state

            node.instruction_list.append(instr)
            node.states[instr['address']] = state

            state = copy.deepcopy(state)
            self.total_states += 1
            state.pc += 1

            op = instr['opcode']

            logging.debug("[" + context.module['name'] + "] " +
                          helper.get_trace_line(instr, state))
            # slows down execution significantly.

            # stack ops

            if op.startswith("PUSH"):
                value = BitVecVal(int(instr['argument'][2:], 16), 256)
                state.stack.append(value)

            elif op.startswith('DUP'):
                dpth = int(op[3:])

                try:
                    state.stack.append(state.stack[-dpth])
                except:
                    halt = True
                    continue

            elif op.startswith('SWAP'):

                dpth = int(op[4:])

                try:
                    temp = state.stack[-dpth - 1]
                except IndexError:  # Stack underflow
                    halt = True
                    continue

                state.stack[-dpth - 1] = state.stack[-1]
                state.stack[-1] = temp

            elif op == 'POP':
                try:
                    state.stack.pop()
                except IndexError:  # Stack underflow
                    halt = True
                    continue

            # Bitwise ops

            elif op == 'AND':
                try:
                    state.stack.append(state.stack.pop() & state.stack.pop())
                except IndexError:  # Stack underflow
                    halt = True
                    continue

            elif op == 'OR':
                try:
                    op1, op2 = state.stack.pop(), state.stack.pop()
                except IndexError:  # Stack underflow
                    halt = True
                    continue

                if (type(op1) == BoolRef):
                    op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))

                if (type(op2) == BoolRef):
                    op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))

                state.stack.append(op1 | op2)

            elif op == 'XOR':
                state.stack.append(state.stack.pop() ^ state.stack.pop())

            elif op == 'NOT':
                state.stack.append(TT256M1 - state.stack.pop())

            elif op == 'BYTE':
                s0, s1 = state.stack.pop(), state.stack.pop()

                state.stack.append(BitVecVal(0, 256))

            # Arithmetics

            elif op == 'ADD':
                state.stack.append(
                    (helper.pop_bitvec(state) + helper.pop_bitvec(state)))

            elif op == 'SUB':
                state.stack.append(
                    (helper.pop_bitvec(state) - helper.pop_bitvec(state)))

            elif op == 'MUL':
                state.stack.append(
                    helper.pop_bitvec(state) * helper.pop_bitvec(state))

            elif op == 'DIV':
                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)

                state.stack.append(UDiv(s0, s1))

            elif op == 'MOD':
                s0, s1 = helper.pop_bitvec(state), helper.pop_bitvec(state)
                state.stack.append(0 if s1 == 0 else s0 % s1)

            elif op == 'SDIV':
                s0, s1 = helper.to_signed(
                    helper.pop_bitvec(state)), helper.to_signed(
                        helper.pop_bitvec(state))
                state.stack.append(0 if s1 == 0 else (
                    abs(s0) // abs(s1) * (-1 if s0 * s1 < 0 else 1)) & TT256M1)

            elif op == 'SMOD':
                s0, s1 = helper.to_signed(
                    helper.pop_bitvec(state)), helper.to_signed(
                        helper.pop_bitvec(state))
                state.stack.append(0 if s1 == 0 else (abs(s0) % abs(s1) *
                                                      (-1 if s0 < 0 else 1))
                                   & TT256M1)

            elif op == 'ADDMOD':
                s0, s1, s2 = helper.pop_bitvec(state), helper.pop_bitvec(
                    state), helper.pop_bitvec(state)
                state.stack.append((s0 + s1) % s2 if s2 else 0)

            elif op == 'MULMOD':
                s0, s1, s2 = helper.pop_bitvec(state), helper.pop_bitvec(
                    state), helper.pop_bitvec(state)
                state.stack.append((s0 * s1) % s2 if s2 else 0)

            elif op == 'EXP':
                # we only implement 2 ** x
                base, exponent = state.stack.pop(), state.stack.pop()

                if (type(base) != BitVecNumRef):
                    state.stack.append(
                        BitVec(str(base) + "_EXP_" + str(exponent), 256))
                elif (base.as_long() == 2):
                    if exponent == 0:
                        state.stack.append(BitVecVal(1, 256))
                    else:
                        state.stack.append(base << (exponent - 1))

                else:
                    state.stack.append(base)

            elif op == 'SIGNEXTEND':
                s0, s1 = state.stack.pop(), state.stack.pop()

                try:
                    s0 = get_concrete_int(s0)
                    s1 = get_concrete_int(s1)
                except:
                    halt = True
                    continue

                if s0 <= 31:
                    testbit = s0 * 8 + 7
                    if s1 & (1 << testbit):
                        state.stack.append(s1 | (TT256 - (1 << testbit)))
                    else:
                        state.stack.append(s1 & ((1 << testbit) - 1))
                else:
                    state.stack.append(s1)

            # Comparisons

            elif op == 'LT':

                exp = ULT(helper.pop_bitvec(state), helper.pop_bitvec(state))
                state.stack.append(exp)

            elif op == 'GT':

                exp = UGT(helper.pop_bitvec(state), helper.pop_bitvec(state))
                state.stack.append(exp)

            elif op == 'SLT':

                exp = helper.pop_bitvec(state) < helper.pop_bitvec(state)
                state.stack.append(exp)

            elif op == 'SGT':

                exp = helper.pop_bitvec(state) > helper.pop_bitvec(state)
                state.stack.append(exp)

            elif op == 'EQ':

                op1 = state.stack.pop()
                op2 = state.stack.pop()

                if (type(op1) == BoolRef):
                    op1 = If(op1, BitVecVal(1, 256), BitVecVal(0, 256))

                if (type(op2) == BoolRef):
                    op2 = If(op2, BitVecVal(1, 256), BitVecVal(0, 256))

                exp = op1 == op2

                state.stack.append(exp)

            elif op == 'ISZERO':

                val = state.stack.pop()

                if (type(val) == BoolRef):
                    exp = val == False
                else:
                    exp = val == 0

                state.stack.append(exp)

            # Call data

            elif op == 'CALLVALUE':
                state.stack.append(context.callvalue)

            elif op == 'CALLDATALOAD':
                # unpack 32 bytes from calldata into a word and put it on the stack

                op0 = state.stack.pop()

                try:
                    offset = helper.get_concrete_int(simplify(op0))
                except AttributeError:
                    logging.debug("CALLDATALOAD: Unsupported symbolic index")
                    state.stack.append(
                        BitVec(
                            "calldata_" + str(context.module['name']) + "_" +
                            str(op0), 256))
                    continue

                try:
                    b = context.calldata[offset]
                except IndexError:
                    logging.debug(
                        "Calldata not set, using symbolic variable instead")
                    state.stack.append(
                        BitVec(
                            "calldata_" + str(context.module['name']) + "_" +
                            str(op0), 256))
                    continue

                if type(b) == int:
                    # 32 byte concrete value

                    val = b''

                    try:
                        for i in range(offset, offset + 32):
                            val += context.calldata[i].to_bytes(
                                1, byteorder='big')

                        state.stack.append(
                            BitVecVal(int.from_bytes(val, byteorder='big'),
                                      256))

                    except:
                        state.stack.append(b)
                else:
                    # symbolic variable
                    state.stack.append(b)

            elif op == 'CALLDATASIZE':

                if context.calldata_type == CalldataType.SYMBOLIC:
                    state.stack.append(
                        BitVec("calldatasize_" + context.module['name'], 256))
                else:
                    state.stack.append(BitVecVal(len(context.calldata), 256))

            elif op == 'CALLDATACOPY':
                op0, op1, op2 = state.stack.pop(), state.stack.pop(
                ), state.stack.pop()

                try:
                    mstart = helper.get_concrete_int(op0)
                except:
                    logging.debug(
                        "Unsupported symbolic memory offset in CALLDATACOPY")
                    continue

                try:
                    dstart = helper.get_concrete_int(op1)
                except:
                    logging.debug(
                        "Unsupported symbolic calldata offset in CALLDATACOPY")
                    state.mem_extend(mstart, 1)
                    state.memory[mstart] = BitVec(
                        "calldata_" + str(context.module['name']) + "_cpy",
                        256)
                    continue

                try:
                    size = helper.get_concrete_int(op2)
                except:
                    logging.debug("Unsupported symbolic size in CALLDATACOPY")
                    state.mem_extend(mstart, 1)
                    state.memory[mstart] = BitVec(
                        "calldata_" + str(context.module['name']) + "_" +
                        str(dstart), 256)
                    continue

                if size > 0:

                    try:
                        state.mem_extend(mstart, size)
                    except:
                        logging.debug("Memory allocation error: mstart = " +
                                      str(mstart) + ", size = " + str(size))
                        state.mem_extend(mstart, 1)
                        state.memory[mstart] = BitVec(
                            "calldata_" + str(context.module['name']) + "_" +
                            str(dstart), 256)
                        continue

                    try:
                        i_data = context.calldata[dstart]

                        for i in range(mstart, mstart + size):
                            state.memory[i] = context.calldata[i_data]
                            i_data += 1
                    except:
                        logging.debug("Exception copying calldata to memory")

                        state.memory[mstart] = BitVec(
                            "calldata_" + str(context.module['name']) + "_" +
                            str(dstart), 256)

                        # continue

            # Control flow

            elif op == 'STOP':
                if self.last_call_address is not None:
                    self.pending_returns[self.last_call_address].append(
                        node.uid)

                halt = True
                continue

            # Environment

            elif op == 'ADDRESS':
                state.stack.append(context.address)

            elif op == 'BALANCE':
                addr = state.stack.pop()
                state.stack.append(BitVec("balance_at_" + str(addr), 256))

            elif op == 'ORIGIN':
                state.stack.append(context.origin)

            elif op == 'CALLER':
                state.stack.append(context.caller)

            elif op == 'CODESIZE':
                state.stack.append(len(disassembly.instruction_list))

            if op == 'SHA3':
                op0, op1 = state.stack.pop(), state.stack.pop()

                try:
                    index, length = helper.get_concrete_int(
                        op0), helper.get_concrete_int(op1)

                except:
                    # Can't access symbolic memory offsets
                    state.stack.append(
                        BitVec("KECCAC_mem_" + str(op0) + ")", 256))
                    continue

                try:
                    data = b''

                    for i in range(index, index + length):
                        data += helper.get_concrete_int(
                            state.memory[i]).to_bytes(1, byteorder='big')
                        i += 1

                except:

                    svar = str(state.memory[index])

                    svar = svar.replace(" ", "_")

                    state.stack.append(BitVec("keccac_" + svar, 256))
                    continue

                logging.debug("SHA3 Data: " + str(data))

                keccac = utils.sha3(utils.bytearray_to_bytestr(data))

                logging.debug("SHA3 Hash: " + str(binascii.hexlify(keccac)))

                state.stack.append(
                    BitVecVal(helper.concrete_int_from_bytes(keccac, 0), 256))

            elif op == 'GASPRICE':
                state.stack.append(BitVecVal(1, 256))

            elif op == 'CODECOPY':
                # Not implemented
                start, s1, size = state.stack.pop(), state.stack.pop(
                ), state.stack.pop()

            elif op == 'EXTCODESIZE':
                addr = state.stack.pop()
                state.stack.append(BitVec("extcodesize", 256))

            elif op == 'EXTCODECOPY':
                # Not implemented

                addr = state.stack.pop()
                start, s2, size = state.stack.pop(), state.stack.pop(
                ), state.stack.pop()

            elif op == 'BLOCKHASH':
                blocknumber = state.stack.pop()
                state.stack.append(
                    BitVec("blockhash_block_" + str(blocknumber), 256))

            elif op == 'COINBASE':
                state.stack.append(BitVec("coinbase", 256))

            elif op == 'TIMESTAMP':
                state.stack.append(BitVec("timestamp", 256))

            elif op == 'NUMBER':
                state.stack.append(BitVec("block_number", 256))

            elif op == 'DIFFICULTY':
                state.stack.append(BitVec("block_difficulty", 256))

            elif op == 'GASLIMIT':
                state.stack.append(BitVec("block_gaslimit", 256))

            elif op == 'MLOAD':

                op0 = state.stack.pop()

                logging.debug("MLOAD[" + str(op0) + "]")

                try:
                    offset = helper.get_concrete_int(op0)
                except AttributeError:
                    logging.debug("Can't MLOAD from symbolic index")
                    data = BitVec("mem_" + str(op0), 256)
                    continue

                try:
                    data = helper.concrete_int_from_bytes(state.memory, offset)
                except IndexError:  # Memory slot not allocated
                    data = BitVec("mem_" + str(offset), 256)
                except TypeError:  # Symbolic memory
                    data = state.memory[offset]

                logging.debug("Load from memory[" + str(offset) + "]: " +
                              str(data))

                state.stack.append(data)

            elif op == 'MSTORE':

                op0, value = state.stack.pop(), state.stack.pop()

                try:
                    mstart = helper.get_concrete_int(op0)
                except AttributeError:
                    logging.debug("MSTORE to symbolic index. Not supported")
                    continue

                try:
                    state.mem_extend(mstart, 32)
                except Exception:
                    logging.debug("Error extending memory, mstart = " +
                                  str(mstart) + ", size = 32")

                logging.debug("MSTORE to mem[" + str(mstart) + "]: " +
                              str(value))

                try:
                    # Attempt to concretize value
                    _bytes = helper.concrete_int_to_bytes(value)

                    i = 0

                    for b in _bytes:
                        state.memory[mstart + i] = _bytes[i]
                        i += 1

                except:
                    try:
                        state.memory[mstart] = value
                    except:
                        logging.debug("Invalid memory access")
                        continue

                # logging.debug("MEM: " + str(state.memory))

            elif op == 'MSTORE8':
                # Is this ever used?
                op0, value = state.stack.pop(), state.stack.pop()

                try:
                    offset = helper.get_concrete_int(op0)
                except AttributeError:
                    logging.debug("MSTORE to symbolic index. Not supported")
                    continue

                state.mem_extend(offset, 1)

                state.memory[offset] = value % 256

            elif op == 'SLOAD':
                index = state.stack.pop()
                logging.debug("Storage access at index " + str(index))

                if type(index) == BitVecRef:
                    # SLOAD from hash offset

                    # k = sha3.keccak_512()
                    # k.update(bytes(str(index), 'utf-8'))
                    # index = k.hexdigest()[:8]

                    index = str(index)

                try:
                    data = state.storage[index]
                except KeyError:
                    data = BitVec("storage_" + str(index), 256)
                    state.storage[index] = data

                state.stack.append(data)

            elif op == 'SSTORE':
                index, value = state.stack.pop(), state.stack.pop()

                logging.debug("Write to storage[" + str(index) + "] at node " +
                              str(start_addr))

                if type(index) == BitVecRef:
                    index = str(index)

                try:
                    state.storage[index] = value
                except KeyError:
                    logging.debug("Error writing to storage: Invalid index")
                    continue

            elif op == 'JUMP':

                try:
                    jump_addr = helper.get_concrete_int(state.stack.pop())
                except AttributeError:
                    logging.debug("Invalid jump argument (symbolic address)")
                    halt = True
                    continue
                except IndexError:  # Stack Underflow
                    halt = True
                    continue

                if (depth < self.max_depth):

                    i = helper.get_instruction_index(
                        disassembly.instruction_list, jump_addr)

                    if i is None:
                        logging.debug("JUMP to invalid address")
                        halt = True
                        continue

                    opcode = disassembly.instruction_list[i]['opcode']

                    if opcode == "JUMPDEST":

                        if (self.can_jump(jump_addr)):

                            new_state = copy.deepcopy(state)
                            new_state.pc = i

                            new_node = self._sym_exec(context,
                                                      new_state,
                                                      depth=depth + 1,
                                                      constraints=constraints)
                            self.nodes[new_node.uid] = new_node

                            self.edges.append(
                                Edge(node.uid, new_node.uid,
                                     JumpType.UNCONDITIONAL))
                            halt = True
                            continue
                        else:
                            logging.debug("JUMP target limit reached")
                            halt = True
                            continue
                    else:
                        logging.debug(
                            "Skipping JUMP to invalid destination (not JUMPDEST): "
                            + str(jump_addr))
                        halt = True
                        continue
                else:
                    logging.debug("Max depth reached, skipping JUMP")
                    halt = True
                    continue

            elif op == 'JUMPI':
                op0, condition = state.stack.pop(), state.stack.pop()

                try:
                    jump_addr = helper.get_concrete_int(op0)
                except:
                    logging.debug("Skipping JUMPI to invalid destination.")

                if (depth < self.max_depth):

                    i = helper.get_instruction_index(
                        disassembly.instruction_list, jump_addr)

                    if not i:
                        logging.debug("Invalid jump destination: " +
                                      str(jump_addr))

                    else:
                        instr = disassembly.instruction_list[i]

                        # Add new node for condition == True

                        if instr['opcode'] != "JUMPDEST":
                            logging.debug("Invalid jump destination: " +
                                          str(jump_addr))

                        else:

                            if (type(condition) == bool):
                                logging.debug("BOOL CONDITION TYPE")
                                # continue

                            elif (type(condition) == BoolRef):

                                if (self.can_jump(jump_addr)):

                                    new_state = copy.deepcopy(state)
                                    new_state.pc = i

                                    new_constraints = copy.deepcopy(
                                        constraints)
                                    new_constraints.append(condition)

                                    new_node = self._sym_exec(
                                        context,
                                        new_state,
                                        depth=depth + 1,
                                        constraints=new_constraints)
                                    self.nodes[new_node.uid] = new_node

                                    self.edges.append(
                                        Edge(node.uid, new_node.uid,
                                             JumpType.CONDITIONAL, condition))

                                else:
                                    logging.debug(
                                        "JUMP target limit reached (JUMPI)")

                            else:
                                logging.debug("Invalid condition: " +
                                              str(condition) + "(type " +
                                              str(type(condition)) + ")")
                                halt = True
                                continue

                        new_state = copy.deepcopy(state)

                        if (type(condition) == BoolRef):
                            negated = Not(condition)
                        else:
                            negated = condition == 0

                        new_constraints = copy.deepcopy(constraints)
                        new_constraints.append(negated)

                        new_node = self._sym_exec(context,
                                                  new_state,
                                                  depth=depth,
                                                  constraints=new_constraints)
                        self.nodes[new_node.uid] = new_node

                        self.edges.append(
                            Edge(node.uid, new_node.uid, JumpType.CONDITIONAL,
                                 negated))

                        halt = True
                        continue

                else:
                    logging.debug("Max depth reached, skipping JUMPI")

            elif op == 'PC':
                state.stack.append(state.pc - 1)

            elif op == 'MSIZE':
                state.stack.append(BitVec("msize", 256))

            elif op == 'GAS':
                state.stack.append(10000000)

            elif op.startswith('LOG'):
                dpth = int(op[3:])
                state.stack.pop(), state.stack.pop()
                [state.stack.pop() for x in range(dpth)]
                # Not supported

            elif op == 'CREATE':
                state.stack.pop(), state.stack.pop(), state.stack.pop()
                # Not supported
                state.stack.append(0)

            elif op in ('CALL', 'CALLCODE', 'DELEGATECALL', 'STATICCALL'):

                if op in ('CALL', 'CALLCODE'):
                    gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \
                        state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()

                else:
                    gas, to, meminstart, meminsz, memoutstart, memoutsz = \
                        state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop(), state.stack.pop()

                try:
                    callee_address = hex(helper.get_concrete_int(to))

                except AttributeError:
                    # Not a concrete call address. Call target may be an address in storage.

                    m = re.search(r'storage_(\d+)', str(simplify(to)))

                    logging.debug("CALL to: " + str(simplify(to)))

                    if (m and self.dynamic_loader is not None):
                        idx = int(m.group(1))
                        logging.info(
                            "Dynamic contract address at storage index " +
                            str(idx))

                        # attempt to read the contract address from instance storage

                        callee_address = self.dynamic_loader.read_storage(
                            context.module['address'], idx)

                        # testrpc simply returns the address, geth response is more elaborate.

                        if not re.match(r"^0x[0-9a-f]{40}$", callee_address):

                            callee_address = "0x" + callee_address[26:]

                    else:
                        logging.info("Unable to resolve address from storage.")
                        ret = BitVec(
                            "retval_" + str(disassembly.instruction_list[
                                state.pc]['address']), 256)
                        state.stack.append(ret)
                        continue

                if (int(callee_address, 16) < 5):

                    logging.info("Native contract called: " + callee_address)

                    # Todo: Implement native contracts

                    ret = BitVec(
                        "retval_" +
                        str(disassembly.instruction_list[state.pc]['address']),
                        256)
                    state.stack.append(ret)
                    continue

                if not re.match(r"^0x[0-9a-f]{40}", callee_address):
                    logging.debug("Invalid address: " + str(callee_address))
                    ret = BitVec(
                        "retval_" +
                        str(disassembly.instruction_list[state.pc]['address']),
                        256)
                    state.stack.append(ret)

                    continue

                try:

                    module = self.modules[callee_address]

                except KeyError:
                    # We have a valid call address, but contract is not in the modules list

                    logging.info("Module with address " + callee_address +
                                 " not loaded.")

                    if self.dynamic_loader is not None:

                        logging.info("Attempting to load dependency")

                        module = self.dynamic_loader.dynld(
                            context.module['address'], callee_address)

                        if module is None:

                            logging.info(
                                "No code returned, not a contract account?")
                            ret = BitVec(
                                "retval_" + str(disassembly.instruction_list[
                                    state.pc]['address']), 256)
                            state.stack.append(ret)
                            continue

                        # New contract loaded successfully, add it to the modules list

                        self.modules[callee_address] = module
                        self.addr_visited[callee_address] = []

                        logging.info("Dependency loaded: " + module['address'])

                    else:
                        logging.info(
                            "Dynamic loader unavailable. Skipping call")
                        ret = BitVec(
                            "retval_" + str(disassembly.instruction_list[
                                state.pc]['address']), 256)
                        state.stack.append(ret)
                        continue

                logging.info("Executing " + op + " to: " + callee_address)

                try:
                    callee_module = self.modules[callee_address]
                except KeyError:
                    logging.info("Contract " + str(callee_address) +
                                 " not loaded.")
                    logging.info((str(self.modules)))

                    ret = BitVec(
                        "retval_" +
                        str(disassembly.instruction_list[state.pc]['address']),
                        256)
                    state.stack.append(ret)

                    continue

                # Attempt to write concrete calldata

                try:
                    calldata = state.memory[helper.get_concrete_int(
                        meminstart):helper.get_concrete_int(meminstart +
                                                            meminsz)]
                    calldata_type = CalldataType.CONCRETE
                    logging.debug("calldata: " + str(calldata))

                except AttributeError:

                    logging.info("Unsupported symbolic calldata offset")
                    calldata_type = CalldataType.SYMBOLIC
                    calldata = []

                self.last_call_address = disassembly.instruction_list[
                    state.pc]['address']
                self.pending_returns[self.last_call_address] = []

                callee_context = Context(callee_module,
                                         calldata=calldata,
                                         caller=context.address,
                                         origin=context.origin,
                                         calldata_type=calldata_type)

                if (op == 'CALL'):

                    new_node = self._sym_exec(callee_context,
                                              State(),
                                              depth=depth + 1,
                                              constraints=constraints)
                    self.nodes[new_node.uid] = new_node

                elif (op == 'CALLCODE'):

                    temp_module = context.module
                    temp_callvalue = context.callvalue
                    temp_caller = context.caller
                    temp_calldata = context.calldata

                    context.module = callee_module
                    context.callvalue = value
                    context.caller = context.address
                    context.calldata = calldata

                    new_node = self._sym_exec(context,
                                              State(),
                                              depth=depth + 1,
                                              constraints=constraints)
                    self.nodes[new_node.uid] = new_node

                    context.module = temp_module
                    context.callvalue = temp_callvalue
                    context.caller = temp_caller
                    context.calldata = temp_calldata

                elif (op == 'DELEGATECALL'):

                    temp_module = context.module
                    temp_calldata = context.calldata

                    context.module = callee_module
                    context.calldata = calldata

                    new_node = self._sym_exec(context,
                                              State(),
                                              depth=depth + 1,
                                              constraints=constraints)
                    self.nodes[new_node.uid] = new_node

                    context.module = temp_module
                    context.calldata = temp_calldata

                self.edges.append(Edge(node.uid, new_node.uid, JumpType.CALL))

                ret = BitVec(
                    "retval_" +
                    str(disassembly.instruction_list[state.pc]['address']),
                    256)
                state.stack.append(ret)

                new_state = copy.deepcopy(state)
                new_node = self._sym_exec(context,
                                          new_state,
                                          depth=depth + 1,
                                          constraints=constraints)

                self.nodes[new_node.uid] = new_node

                for ret_uid in self.pending_returns[self.last_call_address]:
                    self.edges.append(
                        Edge(ret_uid, new_node.uid, JumpType.RETURN))

                state.stack.append(BitVec("retval", 256))

                continue

            elif op == 'RETURN':
                offset, length = state.stack.pop(), state.stack.pop()

                try:
                    self.last_returned = state.memory[helper.get_concrete_int(
                        offset):helper.get_concrete_int(offset + length)]
                except AttributeError:
                    logging.debug(
                        "Return with symbolic length or offset. Not supported")

                if self.last_call_address is not None:
                    self.pending_returns[self.last_call_address].append(
                        node.uid)

                halt = True
                continue

            elif op == 'SUICIDE':
                halt = True
                continue

            elif op == 'REVERT':
                if self.last_call_address is not None:
                    self.pending_returns[self.last_call_address].append(
                        node.uid)

                halt = True
                continue

            elif op == 'INVALID':
                halt = True
                continue

        logging.debug("Returning from node " + str(node.uid))
        return node