Using Concolic Execution — cozy 1.1 documentation

# Using Concolic Execution

Concolic execution is a state exploration strategy that uses concrete values to guide symbolic execution. cozy performs concolic execution slightly differently than what you might be used to with angr, including angr’s Unicorn engine. In our implementation of concolic execution, concrete values for each symbol are chosen, and symbolic execution proceeds as normal. When a branch is created in the symbolic execution, the concrete values are substituted into the constraints of both children, and the children that evaluate to false are placed in a deferred stash. Once execution reaches a terminal state, a deferred child is selected using some heuristic, and its constraints are used to generate a new concrete input. This is equivalent to negating a portion of the path constraint that you might typically see in literature on concolic execution. Our core implementation of concolic execution can be used outside of the cozy workflow as a standalone exploration technique. The relevant classes if you wish to do this can be found in the `cozy.concolic.exploration` module.

Since cozy is a comparative framework, we implement an additional strategy called joint concolic execution. In joint concolic execution, we alternate between the two programs when generating concrete values. After a concrete value is implemented, we run both programs on the same concrete values, which automatically leads to compatible state pairs being generated.

Note that like typical symbolic execution, concolic execution can be complete if so desired. The execution is complete if there are no deferred states in either program. However the primary benefit of concolic execution is that we can explore promising paths at the expense of an incomplete analysis. The promising paths in cozy are determined by heuristics. There are two different heuristics required for concolic execution:

1. A termination heuristic, which determines when to halt concolic execution.

2. A candidate heuristic, which determines which deferred state to explore next.

Some pre-made heuristics can be found in `cozy.concolic.heuristics`. Let’s walk through an example of using a joint concolic session to explore how to use `cozy.concolic.session.JointConcolicSession`.

Let’s assume that we already have a prepatched and postpatched cozy project set up:

    sess_prepatched = proj_prepatched.session('process_sensor_data')
    add_directives(sess_prepatched)
    initialize_state(sess_orig)

    sess_postpatched = proj_postpatched.session('process_sensor_data')
    add_directives(sess_postpatched)
    initialize_state(sess_postpatched)

We are now ready to create and run a joint concolic session. We must remember to pass a set of symbols used in the program to the `run()` method, as we need to assign concrete values to every symbolic value. We also construct the candidate and termination heuristics:

    joint_sess = JointConcolicSession(sess_prepatched, sess_postpatched,
                                      candidate_heuristic_left=BBTransitionCandidate(),
                                      candidate_heuristic_right=BBTransitionCandidate(),
                                      termination_heuristic_left=CyclomaticComplexityTermination.from_session(sess_prepatched),
                                      termination_heuristic_right=CyclomaticComplexityTermination.from_session(sess_postpatched))
    (prepatched_results, postpatched_results) = joint_sess.run([], [], symbols, cache_intermediate_states=True)

Here we are setting heuristics so that we do not explore every state. Instead, our candidate heuristic will pick states with the most unique basic block edge transitions in their history, and the exploration will be terminated once the number of terminal states exceeds the cyclomatic complexity of the session’s function. The return result from the `run()` method gives two `RunResult` objects, which can be directly be used by `cozy.analysis.Comparison`:

    comparison_results = analysis.Comparison(prepatched_results, postpatched_results)

We can of course visualize the results in the browser:

    # Here args is not the function arguments, but rather the contents of the memory
    # mutated by initialize_state
    execution_graph.visualize_comparison(proj_prepatched, proj_postpatched,
                                         prepatched_results, postpatched_results,
                                         comparison_results,
                                         concrete_arg_mapper=concrete_mapper, args=args,
                                         num_examples=2, open_browser=True)

The full implementation used in this guide can be found at https://github.com/draperlaboratory/cozy/blob/main/examples/cmp_weather_v5_concolic.py