Python z3.Solver() Examples

The following are 25 code examples of z3.Solver(). You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may also want to check out all available functions/classes of the module z3 , or try the search function .
Example #1
Source File: find_name_for_bits.py    From on-pwning with MIT License 8 votes vote down vote up
def find_name_for_bit_at_index(index, bit):
    solver = z3.Solver()

    NAME_LENGTH = 12  # arbitrary
    name = [z3.BitVec("c%d" % i, 32) for i in range(NAME_LENGTH)]
    for i in range(len(name)):
        solver.add(z3.And(name[i] > 0, name[i] <= 0xff))

    h1 = hash1(name)
    solver.add(h1 == index)

    h2 = hash2(name)
    solver.add(h2 == bit)

    h3 = hash3(name)
    solver.add(z3.Extract(15, 0, h3) == h2)  # for simplicity

    if solver.check() == z3.sat:
        return "".join(chr(solver.model()[c].as_long()) for c in name).encode("latin-1") 
Example #2
Source File: backend_z3.py    From claripy with BSD 2-Clause "Simplified" License 6 votes vote down vote up
def solver(self, timeout=None):
        if not self.reuse_z3_solver or getattr(self._tls, 'solver', None) is None:
            s = z3.Solver(ctx=self._context)
            _add_memory_pressure(1024 * 1024 * 10)
            if self.reuse_z3_solver:
                # Store the Z3 solver to a thread-local storage if the reuse-solver option is enabled
                self._tls.solver = s
        else:
            # Load the existing Z3 solver for this thread
            s = self._tls.solver
            s.reset()

        # for some reason we always reset the solver anyway, so always clear it. REUSE_SOLVER is fundamentally broken
        self._hash_to_constraint.clear()

        # Configure timeouts
        if timeout is not None:
            if 'soft_timeout' in str(s.param_descrs()):
                s.set('soft_timeout', timeout)
                s.set('solver2_timeout', timeout)
            else:
                s.set('timeout', timeout)
        return s 
Example #3
Source File: hoare_opt.py    From qiskit-terra with Apache License 2.0 6 votes vote down vote up
def __init__(self, size=10):
        """
        Args:
            size (int): size of gate cache, in number of gates
        Raises:
            TranspilerError: if unable to import z3 solver
        """
        if not HAS_Z3:
            raise TranspilerError('z3-solver is required to use HoareOptimizer. '
                                  'To install, run "pip install z3-solver".')
        super().__init__()
        self.solver = Solver()
        self.variables = dict()
        self.gatenum = dict()
        self.gatecache = dict()
        self.varnum = dict()
        self.size = size 
Example #4
Source File: z3.py    From pysmt with Apache License 2.0 6 votes vote down vote up
def __init__(self, environment, logic, **options):
        IncrementalTrackingSolver.__init__(self,
                                           environment=environment,
                                           logic=logic,
                                           **options)
        try:
            self.z3 = z3.SolverFor(str(logic))
        except z3.Z3Exception:
            self.z3 = z3.Solver()
        except z3.z3types.Z3Exception:
            self.z3 = z3.Solver()
        self.options(self)
        self.declarations = set()
        self.converter = Z3Converter(environment, z3_ctx=self.z3.ctx)
        self.mgr = environment.formula_manager

        self._name_cnt = 0
        return 
Example #5
Source File: dse.py    From miasm with GNU General Public License v2.0 6 votes vote down vote up
def __init__(self, machine, produce_solution=PRODUCE_SOLUTION_CODE_COV,
                 known_solutions=None,
                 **kwargs):
        """Init a DSEPathConstraint
        @machine: Machine of the targeted architecture instance
        @produce_solution: (optional) if set, new solutions will be computed"""
        super(DSEPathConstraint, self).__init__(machine, **kwargs)

        # Dependency check
        assert z3 is not None

        # Init PathConstraint specifics structures
        self.cur_solver = z3.Solver()
        self.new_solutions = {} # solution identifier -> solution's model
        self._known_solutions = set() # set of solution identifiers
        self.z3_trans = Translator.to_language("z3")
        self._produce_solution_strategy = produce_solution
        self._previous_addr = None
        self._history = None
        if produce_solution == self.PRODUCE_SOLUTION_PATH_COV:
            self._history = [] # List of addresses in the current path 
Example #6
Source File: dse.py    From miasm with GNU General Public License v2.0 6 votes vote down vote up
def restore_snapshot(self, snapshot, keep_known_solutions=True, **kwargs):
        """Restore a DSEPathConstraint snapshot
        @keep_known_solutions: if set, do not forget solutions already found.
        -> They will not appear in 'new_solutions'
        """
        super(DSEPathConstraint, self).restore_snapshot(snapshot, **kwargs)
        self.new_solutions.clear()
        self.new_solutions.update(snapshot["new_solutions"])
        self.cur_solver = z3.Solver()
        self.cur_solver.add(snapshot["cur_constraints"])
        if not keep_known_solutions:
            self._known_solutions.clear()
        if self._produce_solution_strategy == self.PRODUCE_SOLUTION_PATH_COV:
            self._history = list(snapshot["_history"])
        elif self._produce_solution_strategy == self.PRODUCE_SOLUTION_BRANCH_COV:
            self._previous_addr = snapshot["_previous_addr"] 
Example #7
Source File: svm.py    From ilf with Apache License 2.0 6 votes vote down vote up
def update_sha(self, sha_data):
        for arg, log_value, length_bytes in sha_data:
            if log_value in self.log_sha_to_sym_sha:
                continue
            data = z3.BitVecVal(arg, length_bytes * 8)
            if data.size() == 512:
                data_words = svm_utils.split_bv_by_words(data)
                data_words = [d.as_long() for d in data_words]
                data_words = [self.log_sha_to_sym_sha.get(d, z3.BitVecVal(d, 256)) for d in data_words]
                data = z3.simplify(z3.Concat(data_words))
            sha_constraints, hash_vector = svm_utils.symbolic_keccak(self, data)
            self.log_sha_to_sym_sha[log_value] = hash_vector
            self.root_wstate.constraints.extend(sha_constraints)
        solver = z3.Solver()
        solver.add(self.root_wstate.constraints)
        assert solver.check() == z3.sat 
Example #8
Source File: policy_symbolic.py    From ilf with Apache License 2.0 5 votes vote down vote up
def get_state_solver(gstate):
        solver = z3.Solver()
        solver.set('timeout',  3 * 60 * 1000)
        solver.add(gstate.wstate.constraints)
        res = solver.check()
        if res == z3.unknown: logging.info(f'{gstate.wstate.trace} gstate check timeout')
        return solver if res == z3.sat else None 
Example #9
Source File: Z3.py    From jeeves with MIT License 5 votes vote down vote up
def __init__(self):
    self.solver = z3.Solver()

  # TODO: Is this the place to do this?
  #def __del__(self):
  #  z3.delete()

  # Defining variables. 
Example #10
Source File: deflatten.py    From ollvm-breaker with MIT License 5 votes vote down vote up
def compute_reaching_states(bv, mlil, from_bb, to_bb, states):
    visitor = ConditionVisitor(bv)
    path = next(dfs_paths_backward(from_bb, to_bb))
    reaching_conditions = []
    cond = None
    for edge in path:
        terminator = edge.source[-1]
        # assert terminator.operation == MediumLevelILOperation.MLIL_IF
        if terminator.operation == MediumLevelILOperation.MLIL_IF:
            cond = terminator.condition
            if cond.operation == MediumLevelILOperation.MLIL_VAR:
                cond = mlil.get_var_definitions(cond.src)[0].src
            condition = visitor.visit(cond)
            if edge.type == BranchType.TrueBranch:
                reaching_conditions.append(condition)
            else:
                reaching_conditions.append(z3.Not(condition))

    solver = z3.Solver()
    for condition in reaching_conditions:
        solver.add(condition)

    reaching_states = set()
    if cond.operation == MediumLevelILOperation.MLIL_VAR:
        cond = mlil.get_var_definitions(cond.src)[0].src
    symbolic_state = make_variable(cond.left.src)
    for state in states:
        solver.push()
        solver.add(symbolic_state == state)
        if solver.check() == z3.sat:
            reaching_states.add(state)
        solver.pop()
    return list(reaching_states) 
Example #11
Source File: game.py    From syntia with GNU General Public License v2.0 5 votes vote down vote up
def evaluate_expr_z3(self, expr, constraints, output_size):
        """
        Tramsforms an expression to z3
        :param expr: str
        :param constraints: list of constraints
        :return: int
        """
        # to z3 expression
        expr = self.to_z3(expr)
        # initialise solver
        solver = z3.Solver()
        # output variable
        output = z3.BitVec("o", output_size)

        solver.add(expr == output)

        # add constraints
        for c in constraints:
            solver.add(c)

        # check sat
        assert (solver.check() == z3.sat)
        # parse output
        ret = solver.model()[output].as_long()

        return ret 
Example #12
Source File: z3_common.py    From acsploit with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def _get_collisions(hash_func, target, target_type, length, n_collisions, hash_table_size, *args):
    ret = []
    s = z3.Solver()
    # houses the z3 variables for the potential hash match
    res = _generate_ascii_printable_string('res', length, s)
    # enforces the z3 constraint that the hash matches the target
    # if the target_type is 'preimage', then we compare the hash to the hash of target
    if target_type == 'preimage':
        s.add(hash_func(res, hash_table_size, *args) == hash_func(_str_to_BitVecVals8(target), hash_table_size, *args))
        if length == len(target):  # don't generate the given preimage as an output if generating inputs of that length
            s.add(z3.Or([r != ord(t) for r, t in zip(res, target)]))
    # otherwise the target_type is 'image', and we compare the hash to the target itself
    else:
        s.add(hash_func(res, hash_table_size, *args) == target)
    count = 0
    # z3 isn't stateful; you have to run it again and again while adding constraints to ignore previous solutions
    while s.check() == z3.sat:
        x = s.model()  # This is a z3 solution
        y = ''.join(chr(x[i].as_long()) for i in res)
        ret.append(y)
        count += 1
        # add constraints
        s.add(z3.Or([r != x[r] for r in res]))
        if count >= n_collisions:
            ret.sort()
            break
    return ret 
Example #13
Source File: svm_utils.py    From ilf with Apache License 2.0 5 votes vote down vote up
def check_wstate_reachable(wstate, timeout=None):
    s = z3.Solver()
    if timeout is not None:
        s.set('timeout', timeout)
    s.add(wstate.constraints)
    res = s.check()
    if res != z3.unsat:
        return True
    else:
        logging.debug(f'deleted wstate {wstate.trace}; len constraints:{len(wstate.constraints)}')
        return False 
Example #14
Source File: svm_utils.py    From ilf with Apache License 2.0 5 votes vote down vote up
def solve_wstate(wstate):
    solver = z3.Solver()
    solver.add(wstate.constraints)
    res = solver.check()
    if res == z3.unknown: logging.info('gstate check timeout')
    return solver if res == z3.sat else None 
Example #15
Source File: svm.py    From ilf with Apache License 2.0 5 votes vote down vote up
def trim_unrechable_states(self):
        # (parent, trace, child) tuples
        pending_parent_trace_child_tuples = [(None, None, self.root_wstate)]
        deleted_counter = 0
        s = Solver()
        while(len(pending_parent_trace_child_tuples)):
            s.push()
            parent_wstate, trace, curr_wstate = pending_parent_trace_child_tuples.pop()
            if curr_wstate.status != WorldStateStatus.REACHABLE:
                s.add(curr_wstate.constraints)
                res = s.check()
                if res == sat:
                    curr_wstate.status = WorldStateStatus.REACHABLE
                elif res == unsat:
                    curr_wstate.status = WorldStateStatus.UNREACHABLE
                elif res == z3.unknown:
                    print(curr_wstate.get_full_trace())
                    raise Exception("pdb")
            if curr_wstate.status == WorldStateStatus.REACHABLE:
                if curr_wstate != self.root_wstate:
                    parent_wstate.trace_to_children[trace].append(curr_wstate)
                for child_trace, children in curr_wstate.trace_to_children.items():
                    for child_wstate in children:
                        pending_parent_trace_child_tuples.append((curr_wstate, child_trace, child_wstate))
                curr_wstate.trace_to_children.clear()
            else:
                curr_wstate.status = WorldStateStatus.DELETED
                self.gen_to_wstates[curr_wstate.gen].remove(curr_wstate)
                deleted_counter += 1
            s.pop()
        logging.info('%d WorldStates are deleted', deleted_counter)

        logging.info('SVM initialized') 
Example #16
Source File: policy_sym_plus.py    From ilf with Apache License 2.0 5 votes vote down vote up
def get_state_solver(gstate):
        if gstate.wstate.status == WorldStateStatus.INFEASIBLE:
            return None
        solver = z3.Solver()
        solver.set('timeout',  3 * 60 * 1000)
        solver.add(gstate.wstate.constraints)
        res = solver.check()
        if res == z3.unknown: logging.info(f'{gstate.wstate.trace} gstate check timeout')
        gstate.wstate.status = WorldStateStatus.FEASIBLE if res == z3.sat else WorldStateStatus.INFEASIBLE
        return solver if res == z3.sat else None 
Example #17
Source File: test_cse.py    From sspam with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def test_xor36(self):
        'Test that CSE of the xor36 function is equivalent to original'
        # pylint: disable=exec-used
        pwd = os.path.dirname(os.path.realpath(__file__))
        input_file = open(os.path.join(pwd, 'xor36_flat'), 'r')
        input_string = input_file.read()
        input_ast = ast.parse(input_string)
        coderef = compile(ast.Expression(input_ast.body[0].value),
                          '<string>', 'eval')
        jack = asttools.GetIdentifiers()
        jack.visit(input_ast)

        cse_string = cse.apply_cse(input_string)[0]
        # get all assignment in one ast
        assigns = cse_string[:cse_string.rfind('\n')]
        cse_assign_ast = ast.parse(assigns, mode='exec')
        assign_code = compile(cse_assign_ast, '<string>', mode='exec')
        # get final expression in one ast
        result_string = cse_string.splitlines()[-1]
        result_ast = ast.Expression(ast.parse(result_string).body[0].value)
        result_code = compile(result_ast, '<string>', mode='eval')

        for var in list(jack.variables):
            exec("%s = z3.BitVec('%s', 8)" % (var, var))
        exec(assign_code)
        sol = z3.Solver()
        sol.add(eval(coderef) != eval(result_code))
        self.assertEqual(sol.check().r, -1) 
Example #18
Source File: simplifications.py    From miasm with GNU General Public License v2.0 5 votes vote down vote up
def check(expr_in, expr_out):
        """Check that expr_in is always equals to expr_out"""
        print("Ensure %s = %s" % (expr_in, expr_out))
        solver = z3.Solver()
        solver.add(trans.from_expr(expr_in) != trans.from_expr(expr_out))

        result = solver.check()

        if result != z3.unsat:
            print("ERROR: a counter-example has been founded:")
            model = solver.model()
            print(model)

            print("Reinjecting in the simplifier:")
            to_rep = {}
            expressions = expr_in.get_r().union(expr_out.get_r())
            for expr in expressions:
                value = model.eval(trans.from_expr(expr))
                if hasattr(value, "as_long"):
                    new_val = ExprInt(value.as_long(), expr.size)
                else:
                    raise RuntimeError("Unable to reinject %r" % value)

                to_rep[expr] = new_val

            new_expr_in = expr_in.replace_expr(to_rep)
            new_expr_out = expr_out.replace_expr(to_rep)

            print("Check %s = %s" % (new_expr_in, new_expr_out))
            simp_in = expr_simp_explicit(new_expr_in)
            simp_out =  expr_simp_explicit(new_expr_out)
            print("[%s] %s = %s" % (simp_in == simp_out, simp_in, simp_out))

            # Either the simplification does not stand, either the test is wrong
            raise RuntimeError("Bad simplification") 
Example #19
Source File: z3_ir.py    From miasm with GNU General Public License v2.0 5 votes vote down vote up
def equiv(z3_expr1, z3_expr2):
    s = z3.Solver()
    s.add(z3.Not(z3_expr1 == z3_expr2))
    return s.check() == z3.unsat 
Example #20
Source File: depgraph.py    From miasm with GNU General Public License v2.0 5 votes vote down vote up
def emul(self, ir_arch, ctx=None, step=False):
        # Init
        ctx_init = {}
        if ctx is not None:
            ctx_init.update(ctx)
        solver = z3.Solver()
        symb_exec = SymbolicExecutionEngine(ir_arch, ctx_init)
        history = self.history[::-1]
        history_size = len(history)
        translator = Translator.to_language("z3")
        size = self._ircfg.IRDst.size

        for hist_nb, loc_key in enumerate(history, 1):
            if hist_nb == history_size and loc_key == self.initial_state.loc_key:
                line_nb = self.initial_state.line_nb
            else:
                line_nb = None
            irb = self.irblock_slice(self._ircfg.blocks[loc_key], line_nb)

            # Emul the block and get back destination
            dst = symb_exec.eval_updt_irblock(irb, step=step)

            # Add constraint
            if hist_nb < history_size:
                next_loc_key = history[hist_nb]
                expected = symb_exec.eval_expr(ExprLoc(next_loc_key, size))
                solver.add(self._gen_path_constraints(translator, dst, expected))
        # Save the solver
        self._solver = solver

        # Return only inputs values (others could be wrongs)
        return {
            element: symb_exec.eval_expr(element)
            for element in self.inputs
        } 
Example #21
Source File: solver.py    From cozy with Apache License 2.0 5 votes vote down vote up
def __init__(self,
            vars = None,
            funcs = None,
            collection_depth : int = None,
            min_collection_depth : int = 0,
            validate_model : bool = True,
            model_callback = None,
            logic : str = None,
            timeout : float = None,
            do_cse : bool = True):

        if collection_depth is None:
            collection_depth = collection_depth_opt.value

        self.vars = OrderedSet()
        self.funcs = OrderedDict()
        self.min_collection_depth = min_collection_depth
        self.collection_depth = collection_depth
        self.validate_model = validate_model
        self.model_callback = model_callback
        self._env = OrderedDict()
        self.stk = []
        self.do_cse = do_cse

        with _LOCK:
            ctx = z3.Context()
            solver = z3.Solver(ctx=ctx) if logic is None else z3.SolverFor(logic, ctx=ctx)
            if timeout is not None:
                solver.set("timeout", int(timeout * 1000))
            solver.set("core.validate", validate_model)
            visitor = ToZ3(ctx, solver)

            self.visitor = visitor
            self.z3_solver = solver
            self._create_vars(vars=vars or (), funcs=funcs or {}) 
Example #22
Source File: pattern_matcher.py    From sspam with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def get_model(self, target, pattern):
        'When target is constant and wildcards have no value yet'
        # pylint: disable=exec-used
        if target.n == 0:
            # zero is too permissive
            return False
        getwild = asttools.GetIdentifiers()
        getwild.visit(pattern)
        if getwild.functions:
            # not getting model for expr with functions
            return False
        wilds = getwild.variables
        # let's reduce the model to one wildcard for now
        # otherwise it adds a lot of checks...
        if len(wilds) > 1:
            return False

        wil = wilds.pop()
        if wil in self.wildcards:
            if not isinstance(self.wildcards[wil], ast.Num):
                return False
            folded = deepcopy(pattern)
            folded = Unflattening().visit(folded)
            EvalPattern(self.wildcards).visit(folded)
            folded = asttools.ConstFolding(folded, self.nbits).visit(folded)
            return folded.n == target.n
        else:
            exec("%s = z3.BitVec('%s', %d)" % (wil, wil, self.nbits))
        eval_pattern = deepcopy(pattern)
        eval_pattern = Unflattening().visit(eval_pattern)
        ast.fix_missing_locations(eval_pattern)
        code = compile(ast.Expression(eval_pattern), '<string>', mode='eval')
        sol = z3.Solver()
        sol.add(target.n == eval(code))
        if sol.check().r == 1:
            model = sol.model()
            for inst in model.decls():
                self.wildcards[str(inst)] = ast.Num(int(model[inst].as_long()))
            return True
        return False 
Example #23
Source File: pattern_matcher.py    From sspam with BSD 3-Clause "New" or "Revised" License 5 votes vote down vote up
def check_eq_z3(self, target, pattern):
        'Check equivalence with z3'
        # pylint: disable=exec-used
        getid = asttools.GetIdentifiers()
        getid.visit(target)
        if getid.functions:
            # not checking exprs with functions for now, because Z3
            # does not seem to support function declaration with
            # arbitrary number of arguments
            return False
        for var in self.variables:
            exec("%s = z3.BitVec('%s', %d)" % (var, var, self.nbits))
        target_ast = deepcopy(target)
        target_ast = Unflattening().visit(target_ast)
        ast.fix_missing_locations(target_ast)
        code1 = compile(ast.Expression(target_ast), '<string>', mode='eval')
        eval_pattern = deepcopy(pattern)
        EvalPattern(self.wildcards).visit(eval_pattern)
        eval_pattern = Unflattening().visit(eval_pattern)
        ast.fix_missing_locations(eval_pattern)
        getid.reset()
        getid.visit(eval_pattern)
        if getid.functions:
            # same reason as before, not using Z3 if there are
            # functions
            return False
        gvar = asttools.GetIdentifiers()
        gvar.visit(eval_pattern)
        if any(var.isupper() for var in gvar.variables):
            # do not check if all patterns have not been replaced
            return False
        code2 = compile(ast.Expression(eval_pattern), '<string>', mode='eval')
        sol = z3.Solver()
        if isinstance(eval(code1), int) and eval(code1) == 0:
            # cases where target == 0 are too permissive
            return False
        sol.add(eval(code1) != eval(code2))
        return sol.check().r == -1 
Example #24
Source File: execution.py    From ilf with Apache License 2.0 4 votes vote down vote up
def evaluate(self, gstate):
        stack_len_start = len(gstate.mstate.stack)
        self.pre_evaluate(gstate)
        if gstate.halt:
            return

        instr = gstate.environment.disassembly.instruction_list[gstate.mstate.pc]
        instr_address = instr['address']


        op = instr['opcode']
        match = re.match(r'^(PUSH|DUP|LOG|SWAP)\d{1,2}', op)
        op = match.group(1) if match else op
        eval_func = getattr(self, op, None)
        if eval_func is None:
            raise SVMRuntimeError(f'op evaluator not found: {op}')

        active_account = gstate.wstate.address_to_account[gstate.environment.active_address]
        current_func = '?'  if gstate.wstate.trace is None else gstate.wstate.trace
        arg = instr.get('argument', '')
        arg = (arg[0:10] + '..') if len(arg) > 12 else arg.ljust(12)
        logging.debug(f'{BColors.BLUE}{BColors.BOLD}OP{BColors.ENDC} '
                      f'{op.ljust(12)}\t'
                      f'{arg},\t'
                      f'addr={instr_address},\t'
                      f'pc={gstate.mstate.pc},\t'
                      f'contract={active_account.contract.name}\t'
                      f'func={current_func}\t'
                      f'sender={gstate.environment.sender}')
        arglist = inspect.getargspec(eval_func).args
        try:
            stack_args = [gstate.mstate.stack.pop() for arg in arglist[2:]]
            res = eval_func(gstate, *stack_args)
            gstate.mstate.pc += 1
            stack_len_stop = len(gstate.mstate.stack)
            self.op_to_stack_len[op] = (stack_len_stop - stack_len_start)
            return res
        except Exception as e:
            s = z3.Solver()
            s.add(gstate.wstate.constraints)
            if s.check() == z3.sat:
                raise e 
Example #25
Source File: custom_hash.py    From acsploit with BSD 3-Clause "New" or "Revised" License 4 votes vote down vote up
def run(output):
    ast = parse_input(options['hash'])
    variables = {}  # map from names to z3_vars
    z3_expression = ast.convert_to_z3(variables)

    solver = z3.Solver()
    if options['target_type'] == 'image':
        solver.add(options['image'] == z3_expression)
    elif options['target_type'] == 'preimage':
        # extract and validate the user-provided preimage
        preimage = options['preimage']
        var_defs = preimage.split(',')
        variable_values = {}
        if len(var_defs) < len(variables):
            raise ValueError('Not enough preimage values given for all variables used in the equation')
        for var_def in var_defs:
            try:
                variable_name, value = var_def.split('=', 1)
            except ValueError:
                raise ValueError('Invalid syntax for preimage values')
            variable_name = variable_name.strip()
            if variable_name in variable_values:
                raise ValueError('Multiple preimage values given for variable "%s"' % variable_name)
            try:
                value = int(value)
            except ValueError:
                raise ValueError('Preimage value "%s" for variable "%s" is not an integer' % (value, variable_name))
            variable_values[variable_name] = value
        for variable_name in variables:
            if variable_name not in variable_values:
                raise ValueError('Preimage value not given for variable "%s"' % variable_name)

        # we have a preimage but we want an image to set z3_expression equal to for solving
        # so we set a new variable equal to z3_expression, provide the preimage values,
        #  and then ask Z3 to solve for our new variable
        target_var = z3.BitVec('__v', ast.target_width)
        sub_solver = z3.Solver()
        sub_solver.add(target_var == z3_expression)
        for variable in variables:
            sub_solver.add(variables[variable] == variable_values[variable])
        assert sub_solver.check() == z3.sat  # this should always hold, since the target var is unbounded
        solution = sub_solver.model()
        target_value = solution[target_var]

        # we can now set z3_expression equal to the appropriate image
        solver.add(target_value == z3_expression)
        # and also prevent the preimage values being generated as a solution
        solver.add(z3.Or([var() != solution[var] for var in solution if var.name() != '__v']))

    solutions = []
    while solver.check() == z3.sat and len(solutions) < options['n_collisions']:
        solution = solver.model()
        solutions.append(solution)
        # prevent duplicate solutions
        solver.add(z3.Or([var() != solution[var] for var in solution]))

    output.output(solutions)