| 5 min read
Table of contents
In 2003 the Defense Advanced Research Projects Agency, DARPA
, announced the Cyber Grand Challenge, a two-year competition seeking to create automatic systems for vulnerability detection, exploitation, and patching in near real-time which brought quite a big and complex task to the table. With this task symbolic execution techniques, which have been around since the mid '70s
, came to the attention of a bigger and broader audience. Difficulty is intriguing for hackers and security researchers, it makes things interesting but when we are just learning about something complicated it can be overwhelming and we need to take a step back and tackle it from a different angle.
This article is meant to give you a different view or angle from which to tackle this topic, to help you understand the basic concepts of symbolic execution and how it works in order to enable you to do some more in depth research on the topic. Common problems and how to solve them will not be covered here but once you are finished with this article you should have all the concepts to be able to understand the why to the problems behind symbolic execution.
Symbolic Execution
Symbolic execution is a popular program analysis technique introduced in the mid ’70s
in the context of software testing to check whether a certain property can be violated by a program. Today it conserves the same principles and there is no doubt that it has played a big role in the detection of security vulnerabilities in modern software (Baldoni et al, 2016).
Symbolic execution can simultaneously explore multiple paths that a program could take under different inputs that don’t necessarily have to be defined. The analysis is done at either a source or binary code level. You might wonder how a program can be executed with different inputs without actually knowing the exact value of that input. Well, think of it as a mathematical expression, if you don’t know the exact value to plug into the expression, you just put x
or y
which is just a variable or better yet a symbol to represent some unknown value. Same thing here, if we give a program symbolic, rather than concrete input values it can run with those values and give me an expression at the end.
How it works
The execution of all possible paths is done by a symbolic execution engine. To keep track of all executions, the engine always maintains a state (stmt
, σ
, π
).
-
stmt
: Statement. The next statement to evaluate. -
σ
: Symbolic Store. Maps program variables to concrete values or symbolsαi
. -
π
: Path Constraints. A set of all conditions the symbols must meet in order to reach the stmt in the execution branch. Always true at the beginning.
To better understand how symbolic execution works we are going to look at an example that illustrates the steps taken by the symbolic execution engine.
Consider the following code:
simexec.c.
void myFunct(int a, int b){
int x = 1, y = 0;
if (a != 5){
y = 3 * x;
if (b == 1){
x = 5 * (a + b);
}
}
assert(x - y != 0);
}
Let’s try to find the values that make the assert fail.
Symbolic execution tree of function myFunct().
The symbolic execution tree in the image represents the states the symbolic execution engine goes through during the analysis of the program, in this case during the analysis of myFunct()
.
-
The function receives two parameters,
a
andb
. The symbolic execution engine assigns symbols for each parameter:a → αa
,b → αb
. Since the program hasn’t really began executing, we don’t yet have any paths and therefore no constraints, thusπ
is always true at the beginning. The next statement to execute isint x = 1, y = 0;
leaving our engine in stateA: σ = {a → αa , b→ αb} π = true
. -
The next statement is executed and two values are assigned. This adds
x →1
,y → 0
to ourσ
. No conditions have been established soπ
is stilltrue
. The next statement to execute isif (a != 5)
leaving our engine in stateB: σ = {a → αa , b → αb, x = 1, y = 0} π = true
. -
Here a decision is made based on the value of the first parameter,
a
. Since symbolic execution doesn’t assign concrete values to input the initial symbols remain and a will still be equal toαa
. Symbolic execution analyses multiple paths, so the execution tree must continue whether the condition is met or not. The symbolic execution engine can take two states. If the condition is met,αa!= 5
, that implies a constraint which is added toπ
leaving our engine in stateC: σ = {a → αa , b → αb, x = 1, y = 0} π = αa != 5
where the next statement to execute would bey = 3 * x
. If the condition is not met,αa = 5
, that also implies a constraint and it must also be added to ourπ
leaving our engine in stateD: σ = {a → αa , b → αb, x= 1, y = 0} π = αa = 5
and the next statement to execute would be the assert which when executed would pass all checks meaning the end of that branch. -
The value of
y
is assigned to be three times that ofx
. The only thing this changes is the value ofy
in ourσ
and the next statement to execute is a condition leaving our engine in stateE: σ = {a → αa , b →αb, x = 1, y = 3} π = αa != 5
. -
A decision is made based on the value of the second parameter,
b
. Both possibilities are evaluated. If the condition is met,αb = 1
, that is another constraint and it is added to ourπ
. The next statement to execute would be ``x= 5 * ab;+ leaving our engine in state
F: σ = {a → αa , b → αb, x = 1, y= 3} π = αa != 5 ∧ αb = 1. If the condition is not met,
αb != 1, that constraint must also be added to our
πleaving our engine in state
G: σ = {a → αa , b → αb, x = 1, y = 3} π = αa!= 5 ∧ αb != 1` where the next statement to execute would be the assert which when executed would pass all checks meaning the end of that branch. -
The value of
x
is assigned to be five times the sum ofa
andb
. The only thing this changes is the value ofx
in ourσ
leaving our engine in stateH: σ = {a → αa , b → αb, x = 5(αa αb), y = 3} π = αa != 5 ∧ αb = 1+
where the next statement to execute would be the assert. When the assert,x -y != 0
is evaluated the values are replaced for what our engine has.5(αa αb) - 3 = 0 ∧ αa != 5 ∧ αb = 1+
this is the expression that tells me what values will make my assert fail. To satisfy the previous expressionαa
has to be equal to0.6
andαb
has to be1
.
There it is, we found a property that can be violated and the conditions that must be met in order to violate it.
References
Table of contents
Share
Recommended blog posts
You might be interested in the following related posts.
Introduction to cybersecurity in the aviation sector
Why measure cybersecurity risk with our CVSSF metric?
Our new testing architecture for software development
Protecting your PoS systems from cyber threats
Top seven successful cyberattacks against this industry
Challenges, threats, and best practices for retailers
Be more secure by increasing trust in your software