def eval(self, ctx): [lhs, rhs, width, signed] = match_types(try_eval(ctx, self.lhs), try_eval(ctx, self.rhs)) if self.op == '+': result = lhs + rhs elif self.op == '-': result = lhs - rhs elif self.op == '*': # Double the width if these are bitvectors if width is not None: width *= 2 lhs = extend(lhs, width, signed=signed) rhs = extend(rhs, width, signed=signed) result = lhs * rhs elif self.op == '/': result = lhs / rhs elif self.op == 'AND': result = lhs & rhs elif self.op == 'OR': result = lhs | rhs elif self.op == 'XOR': result = lhs ^ rhs elif self.op == '<<': # Add more bits to the left if we know the rhs if isinstance(rhs, int): width += rhs lhs = extend(lhs, width, signed=signed) rhs = extend(rhs, width, signed=signed) result = lhs << rhs elif self.op == '>>': result = lhs >> rhs elif self.op == '<': result = lhs < rhs elif self.op == '>': result = lhs > rhs elif self.op == '==': result = lhs == rhs elif self.op == '!=': result = lhs != rhs elif self.op == '%': result = lhs % rhs # Handle (a OP b): we allow OP to be assigned as a normal variable # (despite OP being a keyword) with the various _MM_CMPINT_* enum # values actually being z3 lambdas defined in intr_builtins.py. We # look up the current value of OP and call the function via array # access elif self.op == 'OP': op = ctx.get('OP') assert z3.is_array_sort(op), op lhs = z3.BV2Int(lhs, is_signed=signed) rhs = z3.BV2Int(rhs, is_signed=signed) result = z3_multi_array_index(op, lhs, rhs) else: assert False, 'unknown binop %s' % self.op return Value(result, width=width, signed=signed)
def test1(self): b = multiagent.Board('b1', 3, 3) s = b.sum_of_adj(1, 1) logger.debug(s) smt = z3.BV2Int(z3.Extract(1, 1, b.BV)) +\ z3.BV2Int(z3.Extract(7, 7, b.BV)) +\ z3.BV2Int(z3.Extract(3, 3, b.BV)) +\ z3.BV2Int(z3.Extract(5, 5, b.BV)) self.assertTrue(z3.simplify(smt == s))
def find_seed_nextLong(sequence_length: int, slope: int = 0x5DEECE66D, intercept: int = 0xB): # Define some constants addend = z.BitVecVal(intercept, 64) multiplier = z.BitVecVal(slope, 64) mask = z.BitVecVal((1 << 48) - 1, 64) # Note we're generating double the constraints in the sequence_length + 1 # This is required since we'll be using seed_0 to extract the Random() instantiation value # Furthermore, each nextLong call consumes two outputs from next() next_constraints, seeds, next_outputs = make_constraints_next( n_constraints=2 * sequence_length + 1, gen_bits=32) # Define a symbolic variable that we'll use to get the value that instantiated Random() original_seed = z.BitVec('original_seed', 64) # Build a solver object s = z.Solver() # Build a constraint that relates seed_0 and the value used to instantiate Random() s.add(seeds[f'seed_0'] == (original_seed ^ multiplier) & mask) # Next let's add the constraints we've built for next() s.add(next_constraints) # Define symbolic variables for the output from nextLong # Notice: we're using a symbolic variable of type Int # Since nextLong does a sum on ints, it's easier to model this using Z3 arithmetic models # This differs from previous examples where we used BitVec objects exclusively # Consequently, we'll be using the conversion method BV2Int that takes a BitVec object and turns it into an Int object nextLong_outputs = { f'nextLong_output_{i}': z.Int(f'nextLong_output_{i}') for i in range(1, sequence_length + 1) } # Finally, let's add the constraints for nextLong for i, j in zip(range(1, sequence_length + 1), range(1, sequence_length * 2 + 1, 2)): # Notice: we've replaced the bit shift operator in the source with an enquivalent multiplication # Z3 doesn't support bit shift operations on Int objects first_next = z.BV2Int(next_outputs[f'next_output_{j}'], is_signed=True) * 2**32 second_next = z.BV2Int(next_outputs[f'next_output_{j + 1}'], is_signed=True) s.add(nextLong_outputs[f'nextLong_output_{i}'] == first_next + second_next) # Lastly, let's return all the objects we've constructed so far return s, original_seed, nextLong_outputs
def __cast_to_numb__(self, reference): self.__add_assume__(None) if isinstance(reference, z3.BoolRef): return z3.If(reference, 1, 0) elif isinstance(reference, z3.BitVecRef) or isinstance( reference, z3.BitVecNumRef): return z3.BV2Int(reference, True) else: return reference
def opcode_exp(opcode_data1, opcode_data2): if not is_z3_express(opcode_data1) and not is_z3_express(opcode_data2): op1 = format_to_int(opcode_data1) op2 = format_to_int(opcode_data2) result = op1**op2 return z3.BitVecVal(result, 256) size = 0 if is_z3_express( opcode_data1) or opcode_data1.__class__ == z3.z3.BitVecNumRef: size = opcode_data1.size() opcode_data1 = z3.BV2Int(opcode_data1) if is_z3_express( opcode_data2) or opcode_data1.__class__ == z3.z3.BitVecNumRef: if opcode_data2.size() > size: size = opcode_data2.size() opcode_data2 = z3.BV2Int(opcode_data2) return (z3.simplify(z3.Int2BV(opcode_data1**opcode_data2, size)))
def cast(self, value, is_type, to_type): if is_type is Types.STRING and isinstance(to_type, z3.z3.DatatypeSortRef): # the value is a string and it should be cast to a datatyperef # (i.e. an enum-type), just return the value because we can deal with it return value value_is_int = isinstance(value, int) value_is_float = isinstance(value, float) if is_type is to_type: # already correct type, just return the value return value """ INT <---> INTEGER """ elif is_type is Types.INT and to_type is Types.INTEGER: if value_is_int or value_is_float: return value # this happens if it is an int numeral (e.g. 2) else: return z3.BV2Int(value) elif is_type is Types.INTEGER and to_type is Types.INT: if value_is_int or value_is_float: return value else: return z3.Int2BV(value, 32) """ INT <---> FLOAT """ elif is_type is Types.FLOAT and to_type is Types.INT: if value_is_float: return value # this happens if it is a float numeral (e.g. 3.14) else: return z3.fpToSBV(z3.RNE(), value, z3.BitVecSort(32)) elif is_type is Types.INT and to_type is Types.FLOAT: if value_is_int: return value else: return z3.fpSignedToFP(z3.RNE(), value, z3.Float32()) """ INTEGER <---> FLOAT """ elif is_type is Types.FLOAT and to_type is Types.INTEGER: if value_is_float: return value # this happens if it is a float numeral (e.g. 3.14) else: return self.cast(self.cast(value, Types.FLOAT, Types.INT), Types.INT, Types.INTEGER) elif is_type is Types.INTEGER and to_type is Types.FLOAT: if value_is_int: return value else: return self.cast(self.cast(value, Types.INTEGER, Types.INT), Types.INT, Types.FLOAT) """ from REAL """ elif is_type is Types.REAL and to_type is Types.INTEGER: if value_is_float: return value else: return z3.ToInt(value) elif is_type is Types.REAL and to_type is Types.INT: return self.cast(self.cast(value, Types.REAL, Types.INTEGER), Types.INTEGER, Types.INT) elif is_type is Types.REAL and to_type is Types.FLOAT: """ Rounding modes: probably should make these parameterizable! roundNearestTiesToEven ... RNE() = default roundNearestTiesToAway ... RNA() roundTowardPositive ...... RTP() roundTowardNegative ...... RTN() roundTowardZero .......... RTZ() """ if value_is_int or value_is_float: # int numeral return value else: return z3.fpRealToFP(z3.RNE(), value, z3.Float32()) """ to REAL """ elif is_type is Types.INT and to_type is Types.REAL: if value_is_int or value_is_float: # int numeral return value else: return z3.ToReal(self.cast(value, Types.INT, Types.INTEGER)) elif is_type is Types.INTEGER and to_type is Types.REAL: if value_is_int or value_is_float: # int numeral return value else: return z3.ToReal(value) elif is_type is Types.FLOAT and to_type is Types.REAL: if value_is_int or value_is_float: return value # this happens if it is a float numeral (e.g. 3.14) else: return z3.fpToReal(value) """ FROM BOOL conversions """ elif is_type is Types.BOOL and to_type is Types.INT: return z3.If(value, z3.BitVecVal(1, 32), z3.BitVecVal(0, 32)) elif is_type is Types.BOOL and to_type is Types.INTEGER: return z3.If(value, 1, 0) elif is_type is Types.BOOL and to_type is Types.REAL: return z3.If(value, 1.0, 0.0) elif is_type is Types.BOOL and to_type is Types.FLOAT: return z3.If(value, z3.FPVal(1.0, z3.Float32()), z3.FPVal(0.0, z3.Float32())) """ TO BOOL conversions """ elif is_type is Types.INT and to_type is Types.BOOL: return value == 1 elif is_type is Types.INTEGER and to_type is Types.BOOL: return value == 1 elif is_type is Types.REAL and to_type is Types.BOOL: return value == 1 elif is_type is Types.FLOAT and to_type is Types.BOOL: return value == 1 raise TypeError(f"Don't know how to cast from {is_type} to {to_type}!")
def bv_sampler(self, solver, exprs): global CON_SOL_TIME, CON_SOL_COUNT, OTHER_TIME, OTHER_COUNT,\ NO_MODEL_COUNT, IS_MODEL_COUNT, DP_MODEL_COUNT, \ FROM_FUZZING_COUNT, FROM_FUZZING_TIME, CINR, HIGH_FUZZING_COUNT # A collection of results (via constraint solving) # and candidates (via bit flipping) # in the format of {value: level} mutations = {} target = exprs[0] assert len(exprs) == 1 n = target.size() flippable_bits = [i for i in range(n)] delta = z3.BitVec('delta', n) result = z3.BitVec('result', n) # solver = self.solver() solver.add(result == target) solver.minimize(z3.BV2Int(delta)) results = set() while True: # LOGGER.info('---------------------------') LOGGER.info("results len:", len(results)) guess = z3.BitVecVal( random.getrandbits(min(len(results), n)) if results else 0, n) # LOGGER.info('------------0--------------') solver.push() for value in mutations: LOGGER.error( "************** Expensive to generate a new sigma ****************" ) solver.add(result != value) solver.add(result ^ delta == guess) LOGGER.info('------------1--------------') import time pre = time.time() if solver.check() != z3.sat: LOGGER.info("**************No solution ****************") break model = solver.model() result0 = model[result].as_long() post = time.time() CON_SOL_TIME += post - pre CON_SOL_COUNT += 1 solver.pop() LOGGER.info('------------2--------------') results.add(result0) self.log_sampler_status() yield result0 yield None LOGGER.info('------------3--------------') LOGGER.info('solver: ' + str(solver)) LOGGER.info('guess: ' + str(guess)) LOGGER.info('model: ' + str(model)) LOGGER.info('------------4--------------') nresults = 0 # From 0 to n has a low probability of finding a valid model # for i in range(n-1, -1, -1): # for i in [(n-1)*(i%2)+(i//2*(-1) if i%2 else i//2) for i in range(n)]: while flippable_bits: i = flippable_bits.pop( random.randint(0, len(flippable_bits) - 1)) # Generating a result with the ith bit flipped LOGGER.info('mutating bit ' + str(i)) solver.push() goal = z3.BitVecVal(result0, n) solver.add(result ^ delta == goal) solver.add(z3.Extract(i, i, delta) == 0x1) pre = time.time() if solver.check() == z3.sat: model = solver.model() else: model = None post = time.time() OTHER_TIME += post - pre OTHER_COUNT += 1 self.log_sampler_status() solver.pop() # Try the next bit if the model is unsat if not model: LOGGER.info("No model found") NO_MODEL_COUNT += 1 self.log_sampler_status() continue # A new result is found by the model new_result = model[result].as_long() # Try the next bit if new result is a duplicate if new_result in mutations: LOGGER.info("Duplicated new result") DP_MODEL_COUNT += 1 self.log_sampler_status() continue LOGGER.info("New result found ") IS_MODEL_COUNT += 1 nresults += 1 self.log_sampler_status() yield new_result # Start combining existing mutations new_mutations = {new_result: 1} # Needs at least one result in the mutations (e.g. sigma_a) # to combine with the new result (e.g. sigma_b) # When mutations is empty, this for loop will be skipped for existing_result in mutations: pre = time.time() # print("Combining with ", existing_result) level = mutations[existing_result] if level > MAX_LEVEL: HIGH_FUZZING_COUNT += 1 self.log_sampler_status() continue candidate = (result0 ^ ((result0 ^ existing_result) | (result0 ^ new_result))) LOGGER.info('yielding candidate ' + str(candidate) + ' at level ' + str(level)) # Try the next existing result in mutations if # this candidate is a duplicate if candidate in mutations: CINR += 1 self.log_sampler_status() continue # The candidate is new new_mutations[candidate] = level + 1 nresults += 1 FROM_FUZZING_COUNT += 1 post = time.time() FROM_FUZZING_TIME += (post - pre) self.log_sampler_status() yield candidate mutations.update(new_mutations) yield None # all cheap results are generated # LOGGER.info("============== Looping forever===========-") if not nresults: break
def _cmp(cmp, a, b, **kwargs): [a, b, width, signed] = match_types(a, b) a = z3.BV2Int(a, is_signed=signed) b = z3.BV2Int(b, is_signed=signed) return z3.Int2BV(z3.If(cmp(a, b), a, b), width)
b2 = z3.BitVec('rbx', bvl) s = z3.Solver() s.push() var1 = z3.If(z3.And(x1 + cf >= 0, x1 + cf <= 4294967295), x1 + cf, (x1 + cf) % 4294967296) var2 = (x1 + cf) % 4294967296 s.add(var1 != var2) solve(s) s.pop() ## addition of two ints followed by mod === addition of two bvs s.push() var1 = (z3.BV2Int(b1) + z3.BV2Int(b2)) % maxbvl var2 = z3.BV2Int(b1 + b2) s.add(var1 != var2) solve(s) s.pop() ## addition of 2 bvs is in 2's complement s.push() var1 = b1 + b2 var2 = (b1 + b2) & z3.BitVecVal(maxbvl - 1, bvl) s.add(var1 != var2) solve(s) s.pop()
def log_gas(state, _, sz, *topics): return z3.BV2Int(sz) * Glogdata
def words_ceil(n): # ceil(n / 32) return z3.BV2Int((n + 31) >> 5)
assert(bar2020.same_constraint_single_variable(answer,z3.Int('y_intle:32') == -2)) #another example bv_value2 = z3.And(ex3124 == z3.BitVecVal(-2,8), ex2316 == z3.BitVecVal(-1,8), ex158 == z3.BitVecVal(-1,8), ex70 == z3.BitVecVal(-1,8)) answer = bar2020.from_bv_to_int_domain(bv_value2) print("As bit-vector: " + str(bv_value2) + " in integer domain: " + str(answer)) assert(bar2020.same_constraint_single_variable(answer,z3.Int('y_intle:32') == -2)) #check two values are the same assert(bar2020.same_value_single_variable(bv_value1, bv_value2)) #example of conversion not precise variable_relationship = (z3.BV2Int(yconcat) == i_y) i_value = i_y + 1 bv_value3 = yconcat + z3.BitVecVal(1, 32) value_relationship = (z3.BV2Int(bv_value3) == i_value) (same, model, explanation) = ( bar2020.single_variable_bit_vector_domain_equals_integer_domain( i_y, y, variable_relationship, i_value, bv_value3, value_relationship, True, True, bv_type='intle:32')) print("Is conversion precise for bit-vector:" + str(bv_value3) + " in integer domain:" + str(i_value)) print(explanation) #check given constraints that should make them precise i_value = i_y - 2 bv_value4 = yconcat + z3.BitVecVal(-2, 32)
def interp(tree, lookup, z3_mode): """Evaluate the arithmetic expression. Pass a tree as a Lark `Tree` object for the parsed expression. For `lookup`, provide a function for mapping variable names to values. """ op = tree.data if op in ('add', 'sub', 'mul', 'div', 'shl', 'shr'): # Binary operators. lhs = interp(tree.children[0], lookup, z3_mode) rhs = interp(tree.children[1], lookup, z3_mode) if op == 'add': return lhs + rhs elif op == 'sub': return lhs - rhs elif op == 'mul': return lhs * rhs elif op == 'div': return lhs / rhs elif op == 'shl': return lhs << rhs elif op == 'shr': return lhs >> rhs elif op == 'neg': # Negation. sub = interp(tree.children[0], lookup, z3_mode) return -sub elif op == 'int': # Literal number. if z3_mode: return z3.BitVecVal(int(tree.children[0]), 32) else: return int(tree.children[0]) elif op == 'float': # Literal number. if z3_mode: return z3.BitVecVal(float(tree.children[0]), 32) else: return float(tree.children[0]) elif op == 'var': # Variable lookup. return lookup(tree.children[0]) elif op == 'if': # Conditional. cond = interp(tree.children[0], lookup, z3_mode) true = interp(tree.children[1], lookup, z3_mode) false = interp(tree.children[2], lookup, z3_mode) return (cond != 0) * true + (cond == 0) * false elif op == "intcast": child = interp(tree.children[0], lookup, z3_mode) if z3_mode: return z3.BV2Int(child) else: return int(child) elif op == "floatcast": child = interp(tree.children[0], lookup, z3_mode) if z3_mode: return z3.fpBVToFP(child, z3.Float32()) else: return float(child) elif op == "int2bv": child = interp(tree.children[0], lookup, z3_mode) if z3_mode: return z3.Int2BV(child, 32) else: return child elif op == "float2bv": child = interp(tree.children[0], lookup, z3_mode) if z3_mode: return z3.fpToIEEEBV(child) else: return child
def run_block(s, solver, log_trace=False): def end_trace(reason, *args): s.end_type = reason s.end_info = args pass while True: op = s.code[s.pc] try: instr = vm_isa.opcodes[op] except KeyError: end_trace('invalid') return if log_trace: print('{:04}: {}'.format(s.pc, instr.name)) print('> ' + ';; '.join(str(z3.simplify(x)) for x in s.stack)) #print('> {} | {} | {}'.format(stack, mem, store)) try: instr_args = [s.stack.pop() for i in range(instr.pop)] except IndexError: end_trace('stack underflow') return def reducestack(fn): s.stack.append(fn(*instr_args)) oplen = 1 s.gas = s.gas - instr.base_gas if instr.extra_gas is not None: s.gas = s.gas - instr.extra_gas(s, *instr_args) s.gas = z3.simplify(s.gas) if op >= 0x80 and op <= 0x8f: # DUPn # instr_args[0] = old top of stack for v in reversed(instr_args): s.stack.append(v) s.stack.append(instr_args[-1]) elif op >= 0x90 and op <= 0x9f: #SWAPn # Old top of stack pushed first s.stack.append(instr_args[0]) # Then the middle section (in original order) for v in reversed(instr_args[1:-1]): s.stack.append(v) # Then bottom value on top s.stack.append(instr_args[-1]) elif op >= 0x60 and op <= 0x7f: #PUSHn npush = op - 0x60 + 1 s.stack.append(z3.BitVecVal(util.pushval(s.code, s.pc), 256)) oplen += npush elif instr.name == 'ADD': reducestack(lambda x, y: x + y) elif instr.name == 'MUL': reducestack(lambda x, y: x * y) elif instr.name == 'SUB': reducestack(lambda x, y: x - y) elif instr.name == 'DIV': reducestack(lambda x, y: z3.If(y == 0, z3.BitVecVal(0, 256), z3.UDiv(x, y))) elif instr.name == 'SDIV': reducestack(lambda x, y: z3.If( y == 0, z3.BitVecVal(0, 256), z3.If(x == -2**255 and y == -1, z3.BitVecVal(-2**255, 256), x / y))) elif instr.name == 'MOD': reducestack(lambda x, y: z3.If(y == 0, z3.BitVecVal(0, 256), z3.URem(x, y))) elif instr.name == 'SMOD': reducestack(lambda x, y: z3.If(y == 0, z3.BitVecVal(0, 256), z3.SRem(x, y))) elif instr.name == 'ADDMOD': reducestack(lambda x, y, z: z3.If( z == 0, z3.BitVecVal(0, 256), z3.Extract( 255, 0, z3.URem( z3.ZeroExt(1, x) + z3.ZeroExt(1, y), z3.ZeroExt(1, z))) )) elif instr.name == 'MULMOD': reducestack(lambda x, y, z: z3.If( z == 0, z3.BitVecVal(0, 256), z3.Extract( 255, 0, z3.URem( z3.ZeroExt(256, x) * z3.ZeroExt(256, y), z3.ZeroExt(256, z))))) elif instr.name == 'EXP': # TODO z3 currently doesn't seem to provide __pow__ on BitVecs? reducestack(lambda x, y: z3.BitVecVal( pow(x.as_long(), y.as_long(), 1 << 256), 256)) elif instr.name == 'LT': reducestack(lambda x, y: _bool_to_01(z3.ULT(x, y))) elif instr.name == 'GT': reducestack(lambda x, y: _bool_to_01(z3.UGT(x, y))) elif instr.name == 'SLT': reducestack(lambda x, y: _bool_to_01(x < y)) elif instr.name == 'SGT': reducestack(lambda x, y: _bool_to_01(x > y)) elif instr.name == 'EQ': reducestack(lambda x, y: _bool_to_01(x == y)) elif instr.name == 'ISZERO': reducestack(lambda x: _bool_to_01(x == 0)) elif instr.name == 'AND': reducestack(lambda x, y: x & y) elif instr.name == 'OR': reducestack(lambda x, y: x | y) elif instr.name == 'XOR': reducestack(lambda x, y: x ^ y) elif instr.name == 'NOT': reducestack(lambda x: ~x) elif instr.name == 'BYTE': idx, val = instr_args bidx = as_concrete(idx) if bidx <= 31: s.stack.append(z3.ZeroExt(248, get_byte(val, bidx))) else: s.stack.append(z3.BitVecVal(0, 256)) elif instr.name == 'SIGNEXTEND': idx, val = instr_args bidx = as_concrete(idx) if bidx <= 31: nbits = 8 * (bidx + 1) to_extend = z3.Extract(nbits - 1, 0, val) s.stack.append(z3.SignExt(256 - nbits, to_extend)) else: s.stack.append(val) elif instr.name == 'CODESIZE': s.stack.append(z3.BitVecVal(s.code.size(), 256)) elif instr.name == 'SHA3': start, sz = instr_args v = MemoryEmpty n = as_concrete(sz) for i in range(n): v = z3.Store(v, i, s.memory.select(start + i)) s.stack.append(sha3(v)) # TODO when n == 0 or all values are concrete, simplify! #start, sz = as_concrete(start), as_concrete(sz) #stack.append(ethereum.utils.sha3_256([as_concrete( elif instr.name in { 'GASPRICE', 'COINBASE', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'GASLIMIT', 'ORIGIN' }: reducestack(getattr(s.transaction, instr.name.lower())) elif instr.name in {'BALANCE', 'BLOCKHASH', 'EXTCODESIZE'}: reducestack(lambda x: (getattr(s.transaction, instr.name.lower()) ())(x)) elif instr.name == 'ADDRESS': s.stack.append(s.addr) elif instr.name == 'CALLVALUE': s.stack.append(s.callinfo.value) elif instr.name == 'CALLDATASIZE': s.stack.append(s.callinfo.calldata.size) elif instr.name == 'CALLER': s.stack.append(s.caller) elif instr.name == 'CODECOPY': # TODO handle non-concrete size start_mem, start_code, sz = instr_args start_code = as_concrete(start_code) for i in range(as_concrete(sz)): s.memory.store(start_mem + i, s.code[start_code + i]) elif instr.name == 'CALLDATACOPY': src, dest, sz = instr_args cd_mem, cd_off, cd_sz = s.callinfo.calldata # TODO cache this limited calldata memory object - this is so that # out of range calldata reads correctly return 0s limited_cdmem = mem.Memory() limited_cdmem.overlay(cd_mem, 0, cd_off, cd_sz) s.memory.overlay(limited_cdmem, dest, cd_off + src, sz) elif instr.name == 'CALLDATALOAD': addr, = instr_args cd_mem, cd_off, cd_sz, *_ = s.callinfo.calldata s.stack.append( z3.simplify( z3.Concat(*[ z3.If(addr + i < cd_sz, cd_mem.select(cd_off + addr + i), 0) for i in range(32) ]))) elif instr.name == 'RETURNDATASIZE': if hasattr(s, retdata): s.stack.append(s.retdata.size) else: s.stack.append(z3.BitVecVal(0, 256)) elif instr.name == 'RETURNDATACOPY': src, dest, sz = instr_args # TODO non-concrete length, retdata overflow (should terminate) if hasattr(s, retdata): for i in range(sz.as_long()): s.memory.store( dest + i, z3.Select(s.retdata.mem, s.retdata.offset + src + i)) elif instr.name == 'POP': pass elif instr.name == 'MLOAD': addr, = instr_args s.stack.append( z3.simplify( z3.Concat(*[s.memory.select(addr + i) for i in range(32)]))) elif instr.name == 'MSTORE': dst, word = instr_args for i in range(32): s.memory.store(dst + i, get_byte(word, i)) elif instr.name == 'MSTORE8': dst, word = instr_args s.memory.store(dst, get_byte(word, 31)) elif instr.name == 'SLOAD': addr, = instr_args s.stack.append(z3.simplify(z3.Select(s.storage, addr))) elif instr.name == 'SSTORE': addr, word = instr_args s.storage = z3.Store(s.storage, addr, word) elif instr.name == 'PC': s.stack.append(z3.BitVecVal(s.pc, 256)) elif instr.name == 'GAS': # TODO actually track gas usage? s.stack.append(z3.BitVec('{}:GAS'.format(s.pc), 256)) elif instr.name in 'STOP': end_trace('stop') return elif instr.name == 'RETURN': ret_start, ret_size = instr_args s.make_child_return(ret_start, ret_size) return elif instr.name == 'REVERT': ret_start, ret_size = instr_args s.make_child_revert(ret_start, ret_size) return elif instr.name in {'CALL', 'CALLCODE', 'DELEGATECALL'}: if instr.name in {'CALL', 'CALLCODE'}: gas, addr, value, in_off, in_sz, out_off, out_sz = instr_args caller = s.addr elif instr.name == 'DELEGATECALL': gas, addr, in_off, in_sz, out_off, out_sz = instr_args value = s.callinfo.value caller = s.caller else: assert False, instr.name addr = z3.simplify(addr) if instr.name == 'CALL': call_addr = addr code_addr = addr else: call_addr = z3.BitVecVal(s.addr, 256) code_addr = addr callres = z3.BitVec( '{}:{}({})'.format(s.pc, instr.name, z3.simplify(call_addr)), 256) s.stack.append(callres) if is_concrete(call_addr): s.make_child_call(addr=call_addr.as_long(), code_addr=code_addr.as_long(), caller=caller, retinfo=ReturnInfo(s, s.pc + 1, out_off, out_sz, callres), callinfo=CallInfo( MemRange(s.memory, in_off, in_sz), z3.BV2Int(gas), value)) return else: end_trace('call', call_addr, value, gas) s.make_child_branch( new_pc=s.pc + 1, preds=[s.gas > 0, z3.Or(callres == 1, callres == 0)]) return elif instr.name == 'CREATE': value, in_off, in_sz = instr_args res = z3.BitVec('{}:CREATE({})'.format(s.pc, value), 256) s.stack.append(res) end_trace('create', value) s.make_child_branch(new_pc=s.pc + 1, preds=[s.gas > 0, z3.Or(res == 0, res == 1)]) return elif instr.name == 'SELFDESTRUCT': to_addr = instr_args end_trace('suicide', to_addr) # No successors return elif instr.name == 'JUMPI': end_trace(None) loc, cond = instr_args fallthrough_pc = None solver.push() solver.add(cond == 0) fallthrough_state = None if solver.check() == z3.sat: # Also might not take the jump fallthrough_pc = s.pc + 1 solver.pop() solver.push() solver.add(cond != 0) if solver.check() == z3.sat: # OK, can take the jump if is_concrete(loc): loc_conc = loc.as_long() if loc_conc == fallthrough_pc: # Fuse fallthrough and jump if to same location fallthrough_pc = None s.make_child_branch(new_pc=loc_conc, preds=[s.gas > 0]) else: s.make_child_branch(new_pc=loc_conc, preds=[s.gas > 0, cond != 0]) else: for dest in s.code.all_jumpdests(): solver.push() solver.add(loc == dest) if solver.check() == z3.sat: if dest == fallthrough_pc: fallthrough_pc = None s.make_child_branch( new_pc=dest, preds=[s.gas > 0, loc == dest]) else: s.make_child_branch( new_pc=dest, preds=[s.gas > 0, cond != 0, loc == dest]) solver.pop() solver.pop() if fallthrough_pc is not None: s.make_child_branch(new_pc=fallthrough_pc, preds=[s.gas > 0, cond == 0]) return elif instr.name == 'JUMP': end_trace(None) (loc, ) = instr_args if is_concrete(loc): s.make_child_branch(new_pc=loc.as_long(), preds=[s.gas > 0]) else: successors = [] for dest in s.code.all_jumpdests(): solver.push() solver.add(loc == dest) if solver.check() == z3.sat: s.make_child_branch(new_pc=dest, preds=[s.gas > 0, loc == dest]) solver.pop() # No fallthrough return elif instr.name == 'JUMPDEST': s.jumpdests.add(s.pc) elif instr.name in {'LOG0', 'LOG1', 'LOG2', 'LOG3', 'LOG4'}: pass else: raise NotImplementedError(instr.name) if log_trace: print('< ' + ';; '.join(str(z3.simplify(x)) for x in s.stack)) s.pc += oplen