Combining dynamic symbolic execution, code static analysis and fuzzing

This paper describes a new approach for dynamic code analysis. It combines dynamic symbolic execution and static code analysis with fuzzing to increase efficiency of each component. During fuzzing we recover indirect function calls and pass that information to the static analysis engine. This improves static path detection in the control flow graph of a program. Detected paths are used in dynamic symbolic execution to construct inputs which will cover new paths during execution. These inputs are used by the fuzzing tool to improve test-case generation and increase code coverage. The proposed approach can be used for classic fuzzing when the main goal is achieving high code coverage. As well it can be used for targeted analysis of paths and code fragments in the program. In this case the fuzzing tool accepts a set of programs addresses with potential defects and passes them to the static analysis engine. The engine constructs all paths connecting program entry point to the given addresses. Finally, dynamic symbolic execution is used to construct the set of inputs, which will cover these paths. Experimental results have shown that the proposed method can effectively detect different program defects.

Combining dynamic symbolic execution, code static analysis and fuzzing 1

Introduction
Dynamic program analysis has proven to be one of the most effective bugs finding techniques. It has a low false positive rate and most of the detected defects can be reproduced. There are several approaches for dynamic analysis. Fuzzing [1] is one of the most effective and widely used techniques, which detects defects and provides inputs to reproduce them. But it has some limitations. For example, fuzzing itself is not usable for analysis of the specific program fragments. The main reason is that inputs are randomly generated in an attempt to increase the code coverage. Dynamic symbolic execution [2] is used for systematic generation of program inputs to cover all possible execution paths. It is significantly slower than fuzzing and cannot be applied to analysis of large programs. One of the most widely used fuzzing tools is AFL (American Fuzzy Lop) [3,4,5,6]. It is a coverage guided fuzzing tool, which uses genetic algorithms for test case selection and mutation adoption. AFL can perform static instrumentation of the target program or dynamic binary code instrumentation based on QEMU [7] for coverage gathering. LibFuzzer [8] is an embedded fuzzing library in LLVM [9] compiler infrastructure, which provides the means to fuzz individual program function. Syzkaller [10] performs fuzzing of system functions calls for operating systems (OS) based on their descriptions. It generates and runs small programs containing system functions calls and monitors the OS state. If a crash is detected the corresponding input and generated program are stored for debugging purposes.
Peach [11] is used for network protocol fuzzing. It introduces the concept of pit files, which describe target protocols. Grammar-based fuzzing [12] is used for fuzzing of programs (compilers, interpreters, parsers, translators etc.) accepting BNF structured inputs. It has predefined specifications for more than 120 programming languages and data formats. Symbolic execution of a program typically refers to the process of traversing its execution tree while evaluating internal and external program data as abstract symbolic variables instead of concrete values. Program instructions applied to these variables form path constraints (typically represented as SMT -Satisfiability Modulo Theoryformulas). Working with these path constraints allows one to identify valuable information about multiple potential concrete execution paths at once. Dynamic symbolic execution (DSE) tools incorporate various techniques and improvements of basic symbolic execution to allow one to solve various practical program analysis tasks. They are widely used to perform automatic execution tree traversal by generating concrete input data. In turn, these data sets are used as test suites for defect detection and various coverage-related analyses for the target program. Avalanche [13,14], DySy [15], BINSEC/SE [16] are well known DSE tools.
There are advantages and limitations for both fuzzing and dynamic symbolic execution. Black-box and grey-box fuzzing tools can generate a lot of inputs in a limited time, but suffer from random nature of data generation algorithm and the Герасимов А.Ю., Саргсян С.С., Курмангалеев Ш.Ф., Акопян Д.А., Асрян С.А., Ермаков М.К. Комбинирование динамического символьного исполнения, статического анализа кода и фаззинга. Труды ИСП РАН, том 30, вып. 6, 2018 г., стр. [25][26][27][28][29][30][31][32][33][34][35][36][37][38] 27 only feedback which is used to support genetic algorithms is coverage data and crash/hang information for program under analysis. On the other hand, dynamic symbolic execution tools perform aggressive instrumentation of program under analysis to gather execution traces in terms of SMT formulas which drastically influences performance of program under analysis. Also, dynamic symbolic execution suffers from the path explosion problem [17]. Recent research focuses on combining different analysis methods to overcome limitations of methods applied separately. Amongst known solutions we want to mention jFuzz [18], Driller [19], a hybrid symbolic execution assisted fuzzing method [20] which combine fuzzing and symbolic execution to overcome known limitations of methods. In this paper we propose an approach for combining fuzzing, dynamic symbolic execution and static code analysis for program defects detection.

The Architecture of the tool
The tool consists of four basic components ( fig. 1). The first component is a fuzzing tool, which provides a set of mutations and basic infrastructure. The second component is a DynamoRIO [21] based client library for code coverage gathering. The third component is the dynamic symbolic execution tool Anxiety [22]. The fourth component is a program binary code static analysis engine. The proposed tool is able to perform classic fuzzing, where the main goal is to increase code coverage as much as possible. Additionally, it can perform directed analysis of the target programinstead of trying to increase code coverage the tool tries to generate input data to cover specified fragments of the target program. For directed fuzzing the tool accepts a set of addresses which should be executed during analysis. At first, classic fuzzing is performed until coverage stops to increase for some time (controlled by user). This typically means that there are certain fragments of code which are completely inaccessible during execution (i.e. dead code) or can only be reached with an input data set with internal dependencies that are too complex for the semi-random input mutation algorithms. In order to generate these input data sets we employ dynamic symbolic execution guided by static analysis.

Guided dynamic symbolic execution
Anxiety, the dynamic symbolic execution tool used within the system, implements «offline» concolic execution:  it continuously performs concrete executions along with symbolic execution of the target program using initial input data sets and input data sets generated by the tool;  thus, a concrete execution for an input data set produces a symbolic path constraint for this specific data set;  this path constraint includes a number of branch points explicitly influenced by the input data set;  for every branch point in the path constraint an attempt is made to invert the corresponding comparison and check whether the modified path constraint is satisfiable;  the process of checking for satisfiability automatically produces a different input data set which is presumed to force the execution of the program onto a different path at the corresponding branch point;  upper and lower depth limits are used to avoid processing the same branch points (producing input data sets processed previously) and creating path constraints too large to check in a limited time. The number of branch points is a critical factor of the analysis complexity. During guided symbolic execution certain branch points are processed in a different manner based on fuzzing goals:  «black» lists are used to skip certain branch points which were already covered during normal fuzzing (meaning that fuzzing produced at least two different input data sets which force the program execution differently for every branch point among given);  «white» lists are used to augment path constraints with external informationwhich direction at the branch point must be taken for all generated paths. Classic fuzzing, where code coverage increase is the main goal may also be improved via DSE integration. The only difference is in the list of basic blocks passed to DSE. Static analysis detects the list of basic blocks, whose both branches Герасимов А.Ю., Саргсян С.С., Курмангалеев Ш.Ф., Акопян Д.А., Асрян С.А., Ермаков М.К. Комбинирование динамического символьного исполнения, статического анализа кода и фаззинга. Труды ИСП РАН, том 30, вып. 6, 2018 г., стр. [25][26][27][28][29][30][31][32][33][34][35][36][37][38] 29 were executed and pass them to DSE as a «black» list (since no new information we will be gained by inverting such blocks). In both cases, traces of the target program execution are stored in order to perform indirect call recovery (function pointers, virtual functions). This information is used to improve static analysis which in its turn improves the results of other components. Static analysis is periodically invoked during fuzzing to keep the data base of the target program updated using recovered indirect call addresses. This enables mutual improvement for static and dynamic analysis. Experimental results prove the effectiveness of this approach.

Static analysis engine
The static analysis engine has two basic functionalities: detecting paths in a control flow graph and program trace analysis. In the first case the tool identifies a number of paths between two program addresses. The number of limitations are applied for optimization: path's maximum length, maximum number of usages for each basic block or a function during path construction etc. These limitations are necessary to overcome the path explosion problem. Path construction consists of two basic stages ( fig. 2). The first stage filters some functions based on call graph. It uses forward and backward BFS (Breadth-First Search) algorithm for entry and destination addresses of a target program to determine all functions which should be included in the path detection process. In the second stage we use modified DFS (Depth-First Search) for path detection. Then we construct a «white» list for DSE. It contains all basic blocks from detected paths which have branch instructions. The «white» list is used by DSE to generate data which will cover both branches of each basic block. In the second case, static analysis loads the set of traces generated by fuzzing tool and tries to find all basic blocks whose both branches were executed. Then it creates a «black» list based on these blocks to be used by DSE for optimization.

Switching metric
To switch between the fuzzing tool and DSE (static analysis included) we use a variable parameter N. DSE is invoked if below formula is satisfied:

total_execs -last_effective_exec > 10000 * N
where total_execsnumber of executions in the moment when we try to invoke DSE, last_effective_execnumber of executions when the fuzzing last time was able to detect new execution path, Nis specified by user. If the fuzzing tool was not able to open new execution traces for some time , then we invoke DSE.

DSE run time metric
We use special metric for calculating the maximum amount of time to allow for the DSE stage. This amount (in seconds) is calculated according to below formula: runtime = 30 + total_execs / 50000 where runtimetime limit for a DSE run, total_execsnumber of executions at the moment when we try to invoke DSE. The running time for DSE is at least 30 seconds (the number is determined according to experimental results). Our experiments show that less than 30 second for DSE is not enough to achieve valuable results for an average program. We increase DSE run time limit in one second after each 50.000 executions, which enables it to run longer during fuzzing.

Mutual improvement of static analysis results
While the target program is processed, static analysis engine precision is continuously improving due to indirect call address recovery. DynamoRIO [18] based coverage library has trace generation support, which allows us to recover actual addresses for indirect call instructions. During fuzzing process, unique traces are generated for the target program. Then they are analyzed for indirect call address recovery. The process is simple, for each executed block we store information about previously executed block. Then based on that information the actual address is recovered: if there is block in trace which belongs to some function f and previously executed block belongs to some function g, then there is an edge between g and f functions in the call graph. The newly detected edges are added to the target program data base. Improved static analysis has positive impact on DSE results. It allows to construct more inputs which are covering different execution paths between program entry Герасимов А.Ю., Саргсян С.С., Курмангалеев Ш.Ф., Акопян Д.А., Асрян С.А., Ермаков М.К. Комбинирование динамического символьного исполнения, статического анализа кода и фаззинга. Труды ИСП РАН, том 30, вып. 6, 2018 г., стр. 25-38 31 point and destination addresses (a direct fuzzing case). These inputs improve the coverage of the fuzzing tool and improve its effectiveness. Proposed scheme of interaction between these three tools allows iteratively improve the results of each other and overall fuzzing results.

Results of fuzzing integrated with DSE
In the table below (Tab. 1) you will find experimental results of classic fuzzing (with aim of code coverage increase) integrated with DSE. In this case we try to increase code coverage as much as possible. All detected crashes were verified manually.

Operating system
Test name

Results of directed fuzzing
Results of the directed fuzzing for programs from Linux distribution and DARPA [23] Cyber Grand Challenge are presented in Table 2. Static analysis has detected potential program addresses which may have defects. We run fuzzing in directed mode to generate data, which will cover specified addresses in an attempt to crash them. The last column shows the number of hits for detected address list. The fist value is the number of addresses for which the fuzzing tool was able to generate input data to cover them during execution. The second value is the number of potential buggy addresses detected by static analysis. For example, for the test FableReport static analysis has detected 15 potential defect addresses, but fuzzing tool managed to cover only 7 of them. The number of crashes is not synchronous with hit addresses due to several reasons:  program can crash in the same address with different execution paths and fuzzing will consider it as different crashes  if fuzzing managed to generate data which will cover specified address, it is not necessary that program should crash; the address may be false positive from static analysis or generated data do not crash it.
All results were verified manually.

Discussion
A similar approach is used in Badger [24] tool. It combines fuzzing and dynamic symbolic execution in the following way: when the input is passed to symbolic execution it tries to update this input until it reaches new coverage or find a path with lower cost of analysis in terms of computational resources. This approach uses trie-based [25] symbolic execution to predict and reduce the complexity of dynamic Герасимов А.Ю., Саргсян С.С., Курмангалеев Ш.Ф., Акопян Д.А., Асрян С.А., Ермаков М.К. Комбинирование динамического символьного исполнения, статического анализа кода и фаззинга. Труды ИСП РАН, том 30, вып. 6, 2018 г., стр. [25][26][27][28][29][30][31][32][33][34][35][36][37][38] 33 symbolic execution by saving a trie-like structure for path constraints gathered during path exploration until new part of path detected to execute it in symbolic manner. Qsym is another analysis tool [26] which combines symbolic and fuzzing. It uses optimistic solving of relaxed path constraints trying to find new paths with small cost of computations in solver and pruning conditions gathered from repetitive basic blocks from symbolic formulae to simplify constraints relying on fuzzing tool as an efficient validator of generated input. Our approach differs from the proposed solutions. It uses static analysis to guide fuzzing and dynamic symbolic execution through continuously updated program call graph to reach destination address with the help of dynamic symbolic execution.