Finding Substitutable Binary Code By Synthesizing Adapters

Finding Substitutable Binary Code By Synthesizing Adapters

Vaibhav Sharma Department of Computer Science and
University of Minnesota
Minneapolis, MN 55455
   Kesha Hietala Department of Computer Science
University of Maryland
College Park, MD 20742
   Stephen McCamant Department of Computer Science and
University of Minnesota
Minneapolis, MN 55455

Independently developed codebases typically contain many segments of code that perform same or closely related operations (semantic clones). Finding functionally equivalent segments enables applications like replacing a segment by a more efficient or more secure alternative. Such related segments often have different interfaces, so some glue code (an adapter) is needed to replace one with the other. We present an algorithm that searches for replaceable code segments at the function level by attempting to synthesize an adapter between them from some family of adapters; it terminates if it finds no possible adapter. We implement our technique using (1) concrete adapter enumeration based on Intel’s Pin framework (2) binary symbolic execution, and explore the relation between size of adapter search space and total search time. We present examples of applying adapter synthesis for improving security and efficiency of binary functions, deobfuscating binary functions, and switching between binary implementations of RC4. We present two large-scale evaluations, (1) we run adapter synthesis on more than 13,000 function pairs from the Linux C library, (2) using more than 61,000 fragments of binary code extracted from a ARM image built for the iPod Nano 2g device and known functions from the VLC media player, we evaluate our adapter synthesis implementation on more than a million synthesis tasks . Our results confirm that several instances of adaptably equivalent binary functions exist in real-world code, and suggest that adapter synthesis can be applied for reverse engineering and for constructing cleaner, less buggy, more efficient programs.


I Introduction

When required to write an implementation for matrix multiplication, the average programmer will come up with a naive implementation in a matter of minutes. However, much research effort has been invested into creating more efficient matrix multiplication algorithms [56, 8, 16]. On attempting to replace the naive implementation with an implementation of a more efficient matrix multiplication algorithm, the programmer is likely to encounter interface differences, such as taking its arguments in a different order. In this paper we present a technique that automates the process of finding functions that match the behavior specified by an existing function, while also discovering the necessary wrapper needed to handle interface differences between the original and discovered functions. Other use cases for our technique include replacing insecure dependencies of off-the-shelf libraries with bug-free variants, deobfuscating binary-level functions by comparing their behavior to known implementations, and locating multiple versions of a function to be run in parallel to provide security through diversity [4], and reverse engineering a fragment of code to its intended semantic functionality.

Our technique works by searching for a wrapper that can be added around one function’s interface to make it equivalent to another function. We consider wrappers that transform function arguments and return values. Listing 1 shows implementations in two commonly-used libraries of the isalpha predicate, which checks if a character is a letter. Both implementations follow the description of the isalpha function as specified in the ISO C standard, but the glibc implementation signifies the input is a letter by returning 1024, while the musl implementation returns 1 in that case.

1int musl_isalpha(int c) {
2  return ((unsigned)c|32)-’a’ < 26;
4int glibc_isalpha(int c) {
5  return table[c] & 1024;
7int adapted_isalpha(int c) {
8  return (glibc_isalpha(c) != 0) ? 1 : 0;
Listing 1: musl and glibc implementations of the isalpha predicate and a wrapper around the glibc implementation that is equivalent to the musl implementation

The glibc implementation can be adapted to make it equivalent to the musl implementation by replacing its return value, if non-zero, by 1 as shown by the adapted_isalpha function. This illustrates the driving idea of our approach: to check whether two functions and are different interfaces to the same functionality, we can search for a wrapper that allows to be replaced by .

We refer to the function being wrapped around as the inner function and the function being emulated as the target function. In the example above, the inner function is glibc_isalpha and the target function is musl_isalpha. We refer to the wrapper code automatically synthesized by our tool as an adapter. Our adapter synthesis tool searches in the space of all possible adapters allowed by a specified adapter family for an adapter that makes the behavior of the inner function equivalent to that of the target function . We represent that such an adapter exists by the notation . Note that this adaptability relationship may not be symmetric: does not imply . To efficiently search for an adapter, we use counterexample guided inductive synthesis (CEGIS) [52]. An adapter family is represented as a formula for transforming values controlled by parameters: each setting of these parameters represents a possible adapter. Each step of CEGIS allows us to conclude that either a counterexample exists for the previously hypothesized adapter, or that an adapter exists that will work for all previously found tests. We use binary symbolic execution both to generate counterexamples and to find new candidate adapters; the symbolic execution engine internally uses a satisfiability modulo theories (SMT) solver. We contrast the performance of binary symbolic execution for adapter search with an alternate approach that uses a randomly-ordered enumeration of all possible adapters. We always restrict our search to a specified finite family of adapters, and also bound the size of function inputs.

We show that adapter synthesis is useful for a variety of software engineering tasks. One of our automatically synthesized adapters creates adaptable equivalence between a naive implementation of matrix multiplication and an implementation of Strassen’s matrix multiplication algorithm. We also demonstrate the application of adapter synthesis to deobfuscation by deobfuscating a heavily obfuscated implementation of CRC-32 checksum computation. We find adaptable equivalence modulo a security bug caused by undefined behavior. Two other pairs of our automatically synthesized adapters create adaptable equivalence between RC4 setup and encryption functions in mbedTLS (formerly PolarSSL) and OpenSSL. We can use binary symbolic execution both to generate counterexamples and to find new candidate adapters. Our notion of adapter correctness only considers code’s behavior, so we can detect substitutability between functions that have no syntactic similarity. We explore the trade-off between using concrete enumeration and binary symbolic execution for adapter search. Guided by this experiment, we show that binary symbolic execution-based adapter synthesis can be used for reverse engineering at scale. We use the Rockbox project [49] to create an a ARM-based 3rd party firmware image for the iPod Nano 2g device and identify more than 61,000 target code fragments from this image. We extract reference functions from the VLC media player [57]. Using these target code fragments and reference functions, our evaluation completes more than 1.17 million synthesis tasks. Each synthesis task navigates an adapter search space of more than 1.353 x adapters, enumerating these concretely would take an infeasible amount of time ( years). We find our adapter synthesis implementation finds several instances of reference functions in the firmware image. Using the most interesting reference functions from this evaluation, we then compare adapter families to explore different parameter settings for adapter synthesis. To test adapter synthesis within the C library, we evaluate two of our adapter families on more than 13,000 pairs of functions from the C library and present synthesized adapters for some of them. The rest of this paper is organized as follows. Section II presents our algorithm for adapter synthesis and describes our adapter families. Section III describes our implementation, and Section IV presents examples of application of adapter synthesis, large-scale evaluations, and a comparison of two adapter search implementations. Section V discusses limitations and future work, Section VI describes related work, and Section VII concludes.

Ii adapter Synthesis

Ii-a An Algorithm for adapter Synthesis

The idea of counterexample-guided synthesis is to alternate between synthesizing candidate adapter expressions, and checking whether those expressions meet the desired specification. When a candidate adapter expression fails to meet the specification, a counterexample is produced to guide the next synthesis attempt. We are interested in synthesizing adapters that map the arguments of the target function to the arguments of the inner function, and map the return value of the inner function to that of the target function, in such a way that the behavior of the two functions match. Our specification for synthesis is provided by the behavior of the target function, and we define counterexamples to be inputs on which the behavior of the target and inner functions differ for a given adapter. Our adapter synthesis algorithm is presented in Algorithm 1 and is explained in a corresponding figure in Figure 1.

Fig. 1: Counterexample-guided adapter synthesis

Algorithm 1 will terminate with either an adapter that produces equivalence between the target and inner functions for all side-effects, or an indication that the functions cannot be made equivalent using the adapter family we specify.

Input : Pointers to the target function T and inner function I
Output : (argument adapter A, return value adapter R) or null
1 A default-function-args-adapter;
2 R default-return-value-adapter;
3 test-list empty-list;
4 while true do
5        counterexample CheckAdapter (A, R, T, I);
6        if counterexample == null then
7               return (A, R);
9       else
10               test-list.append(counterexample);
12        end if
13        (A, R) SynthesizeAdapter (test-list, T, I);
14        if A == null then
15               return null;
17        end if
19 end while
Algorithm 1 Counterexample-guided adapter synthesis
Input : Concrete adapter A for function arguments and R for return value, target function pointer T, inner function pointer I
Output : Counterexample to the given adapter or null
1 args symbolic;
2 while execution path available do
3        T-return-value, T-side-effects T(args);
4        I-return-value, I-side-effects I(adapt(A, args));
5        if ! (equivalent(T-side-effects, I-side-effects) and equivalent(T-return-value, adapt(R, I-return-value))) then
6               return concretize(args);
8        end if
10 end while
11 return null;
Algorithm 2 CheckAdapter used by Algorithm 1

Algorithm 1 first initializes the current adapter to a default adapter. In our implementation, we often use an ‘identity adapter’ which sets every argument of the inner function to be its corresponding argument to the target function. Next, the list of current tests is set to be the empty list. At every iteration (until a correct adapter is found) a new counterexample is added to this list, and any subsequently generated candidate adapter must satisfy all tests in the list. This provides the intuition for why the adapter search process will always terminate: with every iteration the adapters found become more ‘correct’ in the sense that they produce the desired behavior for more known tests than any previous candidate adapter. Algorithm 1 terminates if the space of candidate adapters allowed by the adapter family is finite. In practice, we found the number of iterations to be small.

Input : List of previously generated counterexamples test-list, target function pointer T, inner function pointer I
Output : (argument adapter A, return value adapter R) or null
1 A symbolic function args adapter;
2 R symbolic return value adapter;
3 while execution path available do
4        eq-counter 0;
5        while eq-counter length(test-list) do
6               T-return-value, T-side-effects T(test);
7               I-return-value, I-side-effects I(adapt(A, test));
8               if equivalent(T-side-effects, I-side-effects) and equivalent(T-return-value, adapt(R, I-return-value)) then
9                      eq-counter eq-counter + 1;
11              else
12                     break;
13               end if
15        end while
16        if eq-counter == length(test-list) then
17               return (concretize(A), concretize(R));
19        end if
21 end while
22 return null;
Algorithm 3 SynthesizeAdapter used by Algorithm 1

The CheckAdapter procedure (described in Algorithm 2) first executes the target function with symbolic arguments and saves its return value and side-effects. It then plugs in the symbolic function arguments to the concrete adapter given as input to produce adapted symbolic arguments for the inner function by calling the adapt method. Algorithm 2 executes the adapted inner function, saves its return value and a list of its side-effects. Algorithm 2 is successful if it finds inequivalence between (1) side-effects of the target and inner functions, or (2) the target function’s return value and adapted return value of the inner function created by calling the adapt method with the input return value adapter. On success, it selects concrete function arguments which create this inequivalence and returns them as a counter-example to the given input adapter. On failure, it concludes no such inequivalence can be found on any execution path, and returns null.

The SynthesizeAdapter procedure described in Algorithm 3 first concretely executes the target function with a test case from the input test list, and saves the return value side-effects. It then plugs in the concrete test case into the symbolic argument adapter to create symbolic arguments for the inner function by calling the adapt method. It then executes the inner function, saving its return value and side-effects. If Algorithm 3 finds equivalence between (1) side-effects of the target and inner functions, and (2) the target function’s return value and the inner function’s adapted return value, it considers this test case satisfied. Finally, on line 15 of Algorithm 3, if it finds all tests to be satisifed, it concretizes the function argument and return value adapters and returns them. The overall time it takes for Algorithm 3 to find an adapter strongly depends on the space of operations permitted by the adapter family it operates on. We describe the design of the adapter families, found useful in our evaluation, in the next subsection.

Ii-B Adapter Families

Ii-B1 Argument Substitution:

This family of adapters allows replacement of any inner function argument by one of the target function arguments or a constant. This simple family is useful, for instance, when synthesizing adapters between the cluster of functions in the C library that wrap around the wait system call as shown in Section IV.

Ii-B2 Argument Substitution with Type Conversion:

This family extends the argument substitution adapter family by allowing inner function arguments to be the result of a type conversion applied to a target function argument. Since type information is not available at the binary level, this adapter tries all possible combinations of type conversion on function arguments. Applying a type conversion at the 64-bit binary level means that each target function argument itself may have been a char, short or a int, thereby using only the low 8, 16, or 32 bytes of the argument register. The correct corresponding inner function argument could be produced by either a sign extension or zero extension on the low 8, 16, or 32 bits of the argument register. This adapter family also allows for converting target function arguments to boolean values by comparing those arguments to zero.

Ii-B3 Arithmetic adapter:

This family allows inner function arguments to be arithmetic combinations of target function arguments. To ensure that the space of adapters is finite, our implementation only allows for arithmetic expressions of a specified bounded depth. Arithmetic adapters allow our tool to reproduce other kinds of synthesis. In the description of the capabilities of the synthesis tool SKETCH, Solar-Lezama et. al. [52] present the synthesis of an efficient bit expression that creates a mask isolating the rightmost 0-bit of an integer. We can synthesize the same bit expression by synthesizing an arithmetic adapter that adapts the identity function to a less-efficient implementation of the operation.

Ii-B4 Memory Substitution:

This family of adapters allows a field of an inner function structure argument to be adapted to a field of a target function structure argument. Each field is treated as an array with n entries (where n cannot be less than 1), with each entry of size 1, 2, 4, or 8 bytes. Corresponding array entries used by the target and inner functions need not be at the same address and may also have different sizes, in which case both sign-extension and zero-extension are valid options to explore for synthesizing the correct adapter as shown in Figure 2. This makes our adapter synthesis more powerful because it can be used in combination with other rules that allow argument substitution. This adapter family synthesizes correct adapters between RC4 implementations in the mbedTLS and OpenSSL libraries in Section IV-D.

Fig. 2: Memory substitution adapter to make struct i adaptably equivalent to struct t

Ii-B5 Return Value Substitution:

The argument substitution families described above can be applied on the return values as well. An example of different return values having the same semantic meaning is the return value of the C library function isalpha as shown in Listing 1.

Ii-C Example

1int clamp_target(int x) {
2    if ((unsigned)x > 255) x = x < 0 ? 0 : 255;
3    return x;
5#include <boost/algorithm/clamp.hpp>
6extern "C" int clamp_reference(int x, int lo, int hi)
8    return boost::algorithm::clamp(x, lo, hi);
Listing 2: Rockbox iPod Nano implementation of the clamp function followed by a standard C++ implementation of the clamp function.

To illustrate our approach, we walk through a representative run of our adapter synthesis algorithm using a target function, that represents binary code from a Rockbox firmware image built for the iPod Nano 2g device, and the clamp function in the Boost library as the reference function. Both the target code region (represented as a function) and reference function are shown in Listing 2. Although our adapter synthesis implementation can use any binary code region as the target region, in this example we define the target code regions to be a C function, and let inputs correspond to function arguments and output correspond to the function return value. Here we will focus only on synthesis of the input adapter, although the general algorithm also produces an adapter that acts the output of the reference function. A correct input adapter should set the first argument of clamp_reference to the integer argument of clamp_target and set the second and third arguments of clamp_reference to 0 and 255 respectively. We write this adapter as .

Step 0: adapter synthesis begins with an empty counterexample list and a default adapter that maps every argument to the constant 0 (i.e. ). During counterexample generation (CheckAdapter in Figure 1), we use symbolic execution to search for an input such that the output of clamp_target(x) is not equivalent to the output of clamp_reference() = clamp_reference(0,0,0). From CheckAdapter, we learn that is one such counterexample.

Step 1: Next, during adapter synthesis (adapterSynthesis in Figure 1), we use symbolic execution to search for a new adapter that will make clamp_target(x) equivalent to clamp_reference() for every input in the list [1]. From SynthesizeAdapter, we learn that is a suitable adapter, and this becomes our new candidate.

Step 2: At the beginning of this step, the candidate adapter is and the counterexample list is [1]. First, we use CheckAdapter to search for a counterexample to the current candidate adapter. We find that is a counterexample.

Step 3: Next, we use SynthesizeAdapter to search for an adapter for which the output of clamp_target(x) will be equivalent to the output of clamp_reference() for both and . SynthesizeAdapter identifies as the new candidate.

Step 4: At the beginning of this step, the candidate adapter is and the counterexample list is [1, 509]. As before, first we use CheckAdapter to search for a counterexample to the current candidate adapter. We find that is a counterexample.

Step 5: Next, we use SynthesizeAdapter to search for an adapter for which the output of clamp_target(x) will be equivalent to the output of clamp_reference() for every [1, 509, -2147483393]. SynthesizeAdapter identifies as the new candidate.

Step 6: In this step, counterexample generation fails to find a counterexample for the current adapter, indicating that the current adapter is correct for all explored paths. Therefore, adapter synthesis terminates with the final adapter . Alternatively, adapter synthesis could have terminated with the decision that the target function is not substitutable by the reference function with any allowed adapter. In our evaluations, adapter synthesis may also terminate with a timeout, indicating the total runtime has exceeded a predefined threshold.

Ii-D Extensibility

The adapter synthesis algorithm presented in this section is not tied to any particular source programming language or family of adapters. In our implementation (Section III) we target binary x86 and ARM code, and we use adapters that allow for common argument structure changes in C code. In Section IV we present two different interpretations of “target code regions.” The first is the function interpretation discussed earlier, where inputs correspond to function arguments and outputs correspond to function return values and side effects. The second interpretation, enabled by our focus on binary code, defines code regions as “code fragments.” We define a code fragment to be a sequence of instructions consisting of atleast one instruction. Inputs to code fragments are all the general-purpose registers available on the architecture of the code fragment and outputs are registers written to within the code fragment. We could also allow reference functions to be more general code regions, but we restricted ourselves to the function-level for now with the idea that a function is the most natural unit of code in which a reverse engineer can express a known behavior.

Iii Implementation

We implement adapter synthesis for Linux/x86-64 binaries using the symbolic execution tool FuzzBALL [36], which is freely available [15]. FuzzBALL allows us to explore execution paths through the target and adapted inner functions to (1) find counterexamples that invalidate previous candidate adapters and (2) find candidate adapters that create behavioral equivalence for the current set of tests. As FuzzBALL symbolically executes a program, it constructs and maintains Vine IR expressions using the BitBlaze [53] Vine library [54] and interfaces with the STP [17] decision procedure to solve path conditions. We replace the symbolic execution-based implementation of adapter search with a concrete implementation that searches the adapter space in a random order.

Iii-a Test Harness

To compare code for equivalence we use a test harness similar to the one used by Ramos et al. [47] to compare C functions for direct equivalence using symbolic execution. The test harness exercises every execution path that passes first through the target code region, and then through the adapted reference function. As FuzzBALL executes a path through the target code region, it maintains a path condition that reflects the branches that were taken. As execution proceeds through the adapted reference function on an execution path, FuzzBALL will only take branches that are do not contradict the path condition. Thus, symbolic execution through the target and reference code consistently satisfies the same path condition over the input. Listing 3 provides a representative test harness. If the target code region is a code fragment, it’s inputs , …, need to be written into the first n general purpose registers available on the architecture. Since the target code fragment may write into the stack pointer register (sp on ARM), the value of the stack pointer also needs to be saved before executing the target code fragment and restored after the target code fragment has finished execution. These operations are represented on lines 2, 3, and 5 of Listing 3. On line 4 the test harness executes the target code region with inputs , …, and captures its output in r1. If the target code region is a code fragment, its output needs to be determined in a preprocessing phase. One heuristic for choosing a code fragment’s output is to choose the last register that was written into by the code fragment. On line 9, it calls the adapted reference function REF with inputs , …, , which are derived from , …, using an adapter. It adapts REF’s return value using the return adapter R and saves the adapted return value in r2. On line 10 the test harness branches on whether the results of the calls to the target and adapted reference code match.

1void compare(x1, ..., xn) {
2  r1 = T(x1, ..., xn);
3  y1 = adapt(A, x1, ..., xn);
4  ...
5  ym = adapt(A, x1, ..., xn);
6  r2 = adapt(R, I(y1, ..., ym));
7  if (r1 == r2) printf("Match\n");
8  else printf("Mismatch\n");
Listing 3: Test harness

We use the same test harness for both counterexample search and adapter search. During counterexample search, the inputs , …, are marked as symbolic and the adapter is concrete. FuzzBALL first executes the target code region using the symbolic , …, . It then creates reference function arguments , …, using the concrete adapter and executes the reference function. During adapter search, the adapter is marked as symbolic, and for each set of test inputs , …, , FuzzBALL first executes the target code region concretely. FuzzBALL then applies symbolic adapter formulas (described in III-B) to the concrete test inputs and passes these symbolic formulas as the adapted reference function arguments , …, , before finally executing the reference function. During counterexample search we are interested in paths that execute the “Mismatch” side, and during adapter search we are interested in paths that execute the “Match” side of the branches on line 7 of Listing 3. For simplicity, Listing 3 shows only the return values and as being used for equivalence checking.

Iii-B Adapters as Symbolic Formulae

1y_1_type:reg8_t == 1:reg8_t ? y_1_val:reg64_t :
2  ( y_1_val:reg64_t == 0:reg64_t ? x1:reg64_t :
3    ( y_1_val:reg64_t == 1:reg64_t ? x2:reg64_t : x3:reg64_t ))
Listing 4: Argument Substitution adapter
1y_1_type:reg8_t == 1:reg8_t ? y_1_val:reg32_t :
2 ( y_1_type:reg8_t == 0:reg32_t ?
3   ( y_1_val:reg32_t == 0:reg32_t ? x1:reg32_t :
4    ( y_1_val:reg32_t == 1:reg32_t ? x2:reg32_t : x3:reg32_t ))
5  :
6   cast( cast(
7     ( y_1_val:reg32_t == 0:reg32_t ? x1:reg32_t :
8      ( y_1_val:reg32_t == 1:reg32_t ? x2:reg32_t : x3:reg32_t ))
9     L:reg16_t ) S:reg32_t )
Listing 5: Vine IR formula for one type conversion operation and argument substitution

We represent adapters in FuzzBALL using Vine IR expressions involving symbolic variables. As an example, an argument substitution adapter for the adapted inner function argument is represented by a Vine IR expression that indicates whether should be replaced by a constant value (and if so, what constant value) or an argument from the target function (and if so, which argument) This symbolic expression uses two symbolic variables, y_i_type and y_i_val. We show an example of an adapter from the argument substitution family represented as a symbolic formula in Vine IR in Listing 4. This listing assumes the target function takes three arguments, x1, x2, x3. This adapter substitutes the first adapted inner function argument with either a constant or with one of the three target function arguments. A value of 1 in y_1_type indicates the first adapted inner function argument is to be substituted by a constant value given by y_1_val. If y_1_type is set to a value other than 1, the first adapted inner function argument is to be substituted by the target function argument at position present in y_1_val. We constrain the range of values y_1_val can take by adding side conditions. In the example shown in Listing 4, when y_1_type equals a value other than 1, y_1_val can only equal 0, 1, or 2 since the target function takes 3 arguments. Symbolic formulae for argument substitution can be extended naturally to more complex adapter families by adding additional symbolic variables. For example, consider the Vine IR formula shown in Listing 5 which extends the formula in Listing 4 to allow sign extension from the low 16 bits of a value. Listing 5 begins in the same way as Listing 4 on line 1. But, this time, if y_1_type is 0, it performs argument substitution based on the value in y_1_val on lines 3, 4. If y_1_type is any value other than 0, it performs sign extension of the low 16 bits in a value. This value is chosen based on the position set in y_1_val on lines 8, 9. Notice lines 8, 9 are the same as lines 3, 4, which means the value, whose low 16 bits are sign-extended, is chosen in exactly the same way as argument substitution.

During the adapter search step of our algorithm, Vine IR representations of adapted inner function arguments are placed into argument registers of the adapted inner function before it begins execution, and placed into the return value register when the inner function returns to the test harness. When doing adapter synthesis using memory substitution, Vine IR formulas allowing memory substitutions are written into memory pointed to by inner function arguments. We use the registers %rdi, %rsi, %rdx, %rcx, %r8, and %r9 for function arguments and the register %rax for function return value, as specified by the x86-64 ABI calling convention [38]. We do not currently support synthesizing adapters between functions that use arguments passed on the stack, use variable number of arguments, or specify return values in registers other than %rax.

Iii-C Equivalence checking of side-effects

We record the side-effects of executing the target and adapted inner functions and compare them for equivalence on every execution path. For equivalence checking of side-effects via system calls, we check the sequence of system calls and their arguments, made by both functions, for equality. For equivalence checking of side-effects on concretely-addressed memory, we record write operations through both functions and compare the pairs of (address, value) for equivalence. For equivalence checking of side-effects on memory addressed by symbolic values, we use a FuzzBALL feature called symbolic regions. Symbolic address expressions encountered during adapted inner function execution are checked for equivalence with those seen during target function execution and mapped to the same symbolic region, if equivalent.

Iii-D Concrete adapter search

Given an adapter family, the space of possible adapters is finite. Instead of using symbolic execution for adapter search, we can concretely check if an adapter produces equal side-effects and return values for all previously-found tests. We implement concrete enumeration-based adapter search in C for all the adapter families described in Section II. We use the Pin [34] framework for checking side-effects on memory and system calls for equality. To prevent adapter search time from depending on the order of enumeration, we randomize the sequence in which adapters are generated.

Iv Evaluation

Iv-a Example: Security

1int l1(int *table, int len, int c) {
2  if(abs(c) > (len/2)) // bug if c = -2147483648
3    return -1;
4  return table[c+(len/2)+1];
6int l2(int c, int *table, int len) {
7  if( !(-(len/2) <= c && c <= (len/2)) )
8    return -1;
9  return table[c+(len/2)+1];
Listing 6: two implementations for mapping ordered keys,negative or positive, to values using a C array

Consider a table implementing a function of a signed input. For example, keys ranging from -127 to 127 can be mapped to a 255-element array. Any key k will then be mapped to the element at position k+127 in this array. We present two implementations of such lookup functions in Listing 6. Both functions, l1 and l2, assume keys ranging from -len/2 to +len/2 are mapped in the table parameter. However, l1 contains a bug caused due to undefined behavior. The return value of abs for the most negative 32-bit integer (-2147483648) is not defined [19]. The eglibc-2.19 implementation of abs returns the absolute value of the most negative 32-bit integer as this same 32-bit integer. This causes the check on line 2 of Listing 6 to not be satisfied allowing execution to continue to line 4 and cause a segmentation fault. Even worse, passing a carefully-chosen value for len can allow a sensitive value to be read and allow this bug to be exploited by an attacker. l2 in Listing 6 performs a check, semantically-equivalent to the one on line 2, but does not contain this bug. Our adapter synthesis implementations were able to synthesize correct argument substitution adapters in the l1 l2 direction. adapter synthesis with concrete enumeration-based adapter search takes 5 seconds, and with FuzzBALL-based adapter search takes 41 seconds. This adapter synthesis requires adaptation modulo the potential segmentation fault in l1. This example shows adapter synthesis provides replacement of buggy functions with their bug-free siblings by adapting the interface of the bug-free function to the buggy one.

Iv-B Example: Deobfuscation

A new family of banking malware named Shifu was reported in 2015 [14][21]. Shifu was found to be targeting banking entities across the UK and Japan. It continues to be updated [41]. Shifu is heavily obfuscated, and one computation used frequently in Shifu is the computation of CRC-32 checksums. We did not have access to the real malware binary, but we were able to simulate its obfuscated checksum computation binary function using freely-available obfuscation tools.

Given a reference implementation of CRC-32 checksum computation, adapter synthesis can be used to check if an obfuscated implementation is adaptably equivalent to the reference implementation. We used the implementation of CRC-32 checksum computation available on the adjunct website [58] of Hacker’s Delight [59] (modified so that we could provide the length of the input string) as our reference function. We obfuscated this function at the source code and Intermediate Representation (IR) levels to create three obfuscated clones. For the first clone, we used a tool named Tigress [7] to apply the following source-level obfuscating transformations:

  1. Function virtualization: This transformation turns the reference function into an interpreter with its own bytecode language.

  2. Just-in-time compilation/dynamic unpacking: This transformation translates each function f into function f’ consisting of intermediate code so that, when f’ is executed, f is dynamically compiled to machine code.

  3. reordering the function arguments randomly, inserting bogus arguments, adding bogus non-trivial functions and loops, and allowing superoperators [45].

These transformations led to a 250% increase in the number of source code lines. For the second clone, we applied the following obfuscating transformations at the LLVM IR level using Obfuscator-LLVM [28]:

  1. Instruction Substitution: This transformation replaces standard binary operators like addition, subtraction, and boolean operators with functionally equivalent, but more complicated, instruction sequences.

  2. Bogus Control Flow: This transformation modifies the function call graph by injecting basic blocks for bogus control flow and modifying existing basic blocks by adding junk instructions chosen at random.

  3. Control flow flattening: This transformation flattens the control flow graph of the clone in a way similar to László et al [32].

These transformations caused the number of instruction bytes to increase from 126 to 2944 bytes. Finally, we compiled the obfuscated C code (obtained using Tigress) with the LLVM obfuscator tool to create a third clone. We then ran our adapter synthesis tool with the reference function as the target function and all three clones as inner functions. We used the CRC-32 checksum of a symbolic 1 byte input string as the return value of each clone. Our adapter synthesis tool, using FuzzBALL-based adapter search, correctly concluded that all three clones were adaptably equivalent to the reference function in less than 3 minutes using argument substitution. A correct adapter for one obfuscated clone is shown in Figure 3. It maps the string and length arguments correctly, while ignoring the four bogus arguments (the mappings to bogus arguments are irrelevant). While performing adapter synthesis on heavily-obfuscated binary code is challenging, adaptation in this example is complicated further by an increase in the number of inner function arguments causing the adapter search space to increase to 43.68 million adapters.

Fig. 3: Argument substitution adapter to make one obfuscated CRC-32 checksum function adaptably equivalent to the reference function

Iv-C Example: Efficiency

1#define N 4
2void naive_mm(int** C, int** A, int** B) {
3  int i, j, k;
4  for ( i = 0; i < N; i++)
5    for ( j = 0; j < N; j++) {
6      C[i][j] = 0;
7      for ( k = 0; k < N; k++)
8        C[i][j] += A[i][k]*B[k][j];
9    }
Listing 7: Naive implementation of matrix multiplication

Adapter synthesis can also be applied to find more efficient versions of a function, even when those versions have a different interface. Matrix multiplication is one of the most frequently-used operations in mathematics and computer science. It can be used for other crucial matrix operations (for example, gaussian elimination, LU decomposition [1]) and as a subroutine in other fast algorithms (for example, for graph transitive closure). Adapting faster binary implementations of matrix multiplication to the naive one’s interface improves the runtime of such other operations relying on matrix multiplication. Hence, as our target function, we use the naive implementation of matrix multiplication shown in Listing 7. As our inner function we use an implementation of Strassen’s algorithm [56] from Intel’s website [22], which takes the input matrices A and B as the 1st and 2nd arguments respectively and places the product matrix in its 3rd argument. We modified their implementation so that it used Strassen’s algorithm for all matrix sizes. Our adapter synthesis tool, using FuzzBALL-based adapter search, finds the correct argument substitution adapter for making the implementation using Strassen’s algorithm adaptably equivalent to the naive implementation in 17.7 minutes for matrices of size 4x4. When using concrete enumeration-based adapter search, the adapter search finds the correct adapter in less than 4.5 minutes.

This example shows that adapter synthesis can be used for finding adaptably equivalent clones of a function that have different algorithmic space and time complexity. Program analysis techniques for checking space and time usage of different implementations are being actively researched [9]. Symbolic execution can also be used for finding inputs that cause functions to exhibit worst-case computational complexity [6]. adapter synthesis can be used as a pre-processing step before applying other techniques for detecting algorithmic complexity of semantic clones.

Iv-D Example: RC4 encryption

To show that adapter synthesis can be applied to replace one library with another, we chose to adapt functions implementing RC4 functionality in mbedTLS and OpenSSL.

Iv-D1 RC4 key structure initialization:

The RC4 algorithm uses a variable length input key to initialize a table with 256 entries within the key structure argument. Both cryptography libraries in our example, mbedTLS and OpenSSL, have their own implementation of this initialization routine. Both function signatures are shown in Figure 4.

Fig. 4: Argument substitution adapter to make RC4_set_key adaptably equivalent to mbedtls_arc4_setup

Executing each of these initialization routines requires 256 rounds of mixing bytes from the key string into the key structure. The two initialization routines require the key length argument at different positions, so making RC4_set_key adaptably equivalent to mbedtls_arc4_setup requires not only mapping the mbedtls_arc4_context object to a RC4_KEY object, but also figuring out the correct mapping of the key length argument. This combination of argument substitution and memory substitution adapter families creates a search space of 421.823 million adapters.

Our adapter synthesis tool correctly figures out both mappings and finds adaptable equivalence by creating equivalence between side-effects on the structure objects (ctx for mbedtls_arc4_setup, RC4_KEY for RC4_set_key). To setup adapter synthesis between these two function pairs (we synthesized adapters in both directions), we used a symbolic key string of length 1, and hence the synthesis tool correctly sets the key length argument to 1. Our tool, when using FuzzBALL-based adapter search, figures out the correct memory and argument substitution adapters in the mbedTLS OpenSSL direction for initialization routines in 60 minutes and in the OpenSSL mbedTLS direction in 49 minutes. Thus, we combined the memory substitution adapter with the argument substitution adapter family to synthesize adaptable equivalence between the RC4 setup pair of functions.

Iv-D2 RC4 encryption:

RC4 encryption functions in mbedTLS and OpenSSL take 4 arguments each, one of which is the RC4 key structure argument. The RC4 key structures (RC4 in OpenSSL, mbedtls_arc4_context in mbedTLS) contain three fields as shown in Figure 5.

Fig. 5: Memory substitution adapter to make RC4_KEY adaptably equivalent to mbedtls_arc4_context

The first two 4-byte fields are used to index into the third field, which is an array with 256 entries. Each entry is 4 bytes long in OpenSSL and 1 byte long in mbedTLS. In order to present an example of a memory substitution adapter synthesized in isolation, we created wrappers for both RC4 encryption functions so that only the key structure argument was exposed and used a fixed value for the input string. This allowed us to direct the adapter search to search for all possible mappings between the mbedTLS and OpenSSL RC4 key structure fields. Allowing arbitrary numbers of 1, 2, 4, or 8 byte entries in each field of the 264 (2*4+256*1) byte mbedTLS key structure and 1032 (2*4+256*4) byte OpenSSL key structure made the search space of memory mappings very large, so we instead only explored adapters where the number of entries in each array was a power of 2. While this reduction is useful in practice, it still gives us a search space of about 4.7 million adapters in both directions of adaptation.

The correct adapter that adapts the OpenSSL key structure to the mbedTLS key structure (mbedTLS OpenSSL) performs 2 mapping operations: (1) it maps the first 2 mbedTLS key structure fields directly to the first 2 OpenSSL key structure fields and (2) it zero extends each 1 byte entry in the 3rd field of the mbedTLS key structure to the corresponding 4 byte entry in the 3rd field of the OpenSSL key structure. The correct adapter for adapting in the reverse direction (OpenSSL mbedTLS) changes the second mapping operation to map the least significant byte of each 4 byte entry in the 3rd field to the 1 byte entry in its corresponding position. Our adapter synthesis tool, when using FuzzBALL-based adapter search, found the correct memory substitution adapter in the mbedTLS OpenSSL direction in 2.4 hours and in the OpenSSL mbedTLS direction in 2.6 hours. When using concrete enumeration-based adapter search, we found the correct adapter in 1.8 hours in the mbedTLS OpenSSL direction, of which only 6 minutes were spent on adapter search. In the OpenSSL mbedTLS direction, we found the correct adapter, with concrete enumeration-based adapter search, in 65 minutes, of which only 1.5 minutes were spent on adapter search. The correct adapter for making RC4_KEY adaptably equivalent to mbedtls_arc4_context is shown in Figure 5. We verified the correctness of our adapted key structures by using self-tests present in mbedTLS and OpenSSL.

Iv-D3 RC4 adapter verification using nmap:

We verified our RC4 memory substitution adapter using nmap, as shown in Figure 6.

Fig. 6: nmap using RC4 encryption in mbedTLS instead of OpenSSL

We created adapted versions of the OpenSSL RC4 setup and encryption functions that internally use the mbedTLS key structure adapted to the OpenSSL key structure. On a 64-bit virtual machine running Ubuntu 14.04, we compiled the adapted setup and encryption functions into a shared library and setup a local webserver on the virtual machine, which communicated over port 443 using the RC4+RSA cipher. We used the stock nmap binary to scan our localhost and injected our specially created shared library using the LD_PRELOAD environment variable. The preloading caused the RC4 functions in our shared library to be executed instead of the ones inside OpenSSL. The output of nmap, run with preloading our specially created shared library which used the OpenSSL mbedTLS structure adapter, was the same as the output of nmap which used the system OpenSSL library.

Iv-E Evaluation with C library

Iv-E1 Setup

We evaluated our adapter synthesis tool on the system C library available on Ubuntu 14.04 (eglibc 2.19). The C library uses a significant amount of inlined assembly, for instance, the ffs, ffsl, ffsll functions, which motivates automated adapter synthesis at the binary level. We enumerated 1316 exported functions in the library in the order they appear, which caused functions that are defined in the same source files to appear close to each other. Considering every function in this list as the target function, we chose five functions that appear above and below it as 10 potential inner functions. These steps gave us a list of 13130 (101316 - 2 ) pairs of target and inner functions. We used the argument substitution and type conversion adapter families combined with the return value adapter family because these families scale well and are widely applicable. We ran our adapter synthesis with a 2 minute timeout on a machine running CentOS 6.8 with 64-bit Linux kernel version 2.6.32 using 64 GB RAM and a Intel Xeon E5-2680v3 processor. To keep the running time of the entire adapter synthesis process within practical limits, we configured FuzzBALL to use a 5 second SMT solver timeout and to consider any queries that trigger this timeout as unsatisfiable. We limited the maximum number of times any instruction can be executed to 4000 because this allowed execution of code which loaded library dependencies. We limited regions to be symbolic up to a 936 byte offset limit (the size of the largest structure in the library interface) and any offset outside this range was considered to contain zero.

Iv-E2 Results

Table I summarizes the results of searching for argument substitution and type conversion adapters with a return value adapter within the 13130 function pairs described above. The similarity in the results for the type conversion adapter family and argument substitution adapter family arises from the similarity of these two families. The most common causes of crashing during execution of the target function were missing system call support in FuzzBALL, and incorrect null dereferences (caused due to lack of proper initialization of pointer arguments). The timeout column includes all function pairs for which we had a solver timeout (5 seconds), hit the iteration limit (4000), or reached a hard timeout (2 minutes). The search terminated without a timeout for 70% of the function pairs, which reflects a complete exploration of the space of symbolic inputs to a function, or of adapters.

arg. sub.
8887 382 3014 847
type conv. 8909 383 2989 849
TABLE I: adapter Synthesis over 13130 function pairs without memory-based equivalence checking
or adapter
abs(1) labs(1)
abs(1) llabs(1)
32-to-64S(#0) and
32-to-64Z(return value)
labs(1) llabs(1)
ldiv(1) lldiv(1)
ffs(1) ffsl(1)
ffs(1) ffsll(1)
ffsl(1) ffsll(1)
setpgrp(0) setpgid(2) 0, 0
wait(1) waitpid(3) -1, #0, 0
wait(1) wait4(4) -1, #0, 0, 0
waitpid(3) wait4(4) #0, #1, #2, 0
wait(1) wait3(3) #0, 0, 0
wait3(3) wait4(4) -1, #0, #1, #2
umount(1) umount2(2) #0, 0
putchar(1) putchar_unlocked
putwchar(1) putwchar_unlocked(1)
recv(4) recvfrom(6)
send(4) sendto(6)
32-to-64S(#0), #1, #2,
32-to-64S(#3), 0, 0
atol(1) atoll(1)
atol(1) strtol(3)
atoi(1) strtol(3)
atoll(1) strtoll(3)
#0, 0, 10
isupper(1) islower(1) #0 + 32
islower(1) isupper(1) #0 - 32
killpg(1) kill(1) -#0, #1
TABLE II: adapters found within eglibc-2.19

Since there is no ground truth, we manually corroborated the results of our evaluation by checking the C library documentation and source code. Our adapter synthesis evaluation on the C library reported 30 interesting true positives shown in Table II. (The remaining adapters found are correct but trivial.) The first column shows the function pair between which an adapter was found (with the number of arguments) and the second column shows the adapters. We use the following notation to describe adapters in a compact way. means and . # followed by a number indicates inner argument substitution by a target argument, while other numbers indicate constants. X-to-YS represents taking the low X bits and sign extending them to Y bits, X-to-YZ represents a similar operation using zero extension. The last three rows shown in Table II shows three arithmetic adapters found within the C library using partial automation. We synthesized the correct adapters by writing wrappers containing preconditions around the isupper, islower, kill functions.

Iv-F Comparison with Concrete Enumeration-based Adapter Search

The adapter search step in our CEGIS approach need not use binary symbolic execution. We swapped out our FuzzBALL-based adapter search step with a concrete enumeration-based adapter search. We ensured that our concrete enumeration generated adapters, such that every adapter had the same probability of being chosen. We synthesized every adapter, presented so far, using both adapter search implementations and captured the total adapter search time. We also counted the size of the adapter search space for every adaptation. In some cases, the adapter search space was too large to be concretely enumerated. For example, the adapter search space for the killpg kill adapter consists of 98.1 million arithmetic adapters. In such cases, we reduced the size of the search space by using smaller constant bounds. Based on the size of adapter search space, we compared the total adapter search times for both adapter search strategies. We present the results from this comparison in Figure 7.

Fig. 7: Comparing concrete enumeration-based adapter search with binary symbolic execution-based adapter search

For concrete enumeration-based adapter search, Figure 7 shows the time required to find an adapter has a consistent exponential increase with an increase in the size of adapter search space. But, we cannot derive any such conclusion for binary symbolic execution-based adapter search. This is because symbolic execution is more sensitive to variations in difficulty of adapter search than concrete enumeration. We further explored this comparison between concrete enumeration and binary symbolic execution-based adapter search using an example which would allow us to control adapter search difficulty.

1unsigned short popCntNaive(unsigned short v) {
2  unsigned short c;
3  for (c = 0; v; v >>= 1) { c += v & 1; }
4  return c;
6unsigned short popCntSketch(unsigned short x) {
7  x= (x & 0x5555)+ ((x>> 1) & 0x5555);
8  x= (x & 0x3333)+ ((x>> 2) & 0x3333);
9  x= (x & 0x0077)+ ((x>> 8) & 0x0077);
10  x= (x & 0xf)+ ((x>> 4) & 0xf);
11  return x;
Listing 8: naive and SKETCH-based implementations of popCnt

The popCnt function synthesized by SKETCH [52] allows us to control the difficulty of adapter search. The popCnt function counts the number of bits set in a 16-bit value. We present the target (popCntNaive) function and one variant of the inner function (popCntSketch) in Listing 8. The popCntSketch function uses 8 constants (1, 2, 4, 8, 0xf, 0x77, 0x3333, 0x5555), which can be passed as arguments instead of being hardcoded. The argument substitution adapter family allows constant bounds to be specified to make the adapter search space finite. By varying the constant bounds and the number of arguments (which were replaced by appropriate constants by the correct adapter) to popCntSketch, we varied the size of the adapter search space while keeping the difficulty of adapter search uniform. We created 24 variants of popCountSketch. Using each popCountSketch variant as the inner function, and popCntNaive as the target function, we synthesized adapters using concrete enumeration and binary symbolic execution-based adapter search. Figure 8 shows the result of comparing total adapter search times across sizes of adapter search space when using concrete enumeration and binary symbolic execution-based adapter search.

Fig. 8: Comparing concrete enumeration-based adapter search with binary symbolic execution-based adapter search using variants of popCnt

Figure 8 shows concrete enumeration-based adapter search is faster than binary symbolic execution-based adapter search upto search spaces of size . But this gain quickly drops off as the size of search space approaches . We also created a variant of popCntSketch that takes 6 arguments and uses them for its largest constants. Synthesizing an adapter using this variant as the inner function creates a search space of size 3.517x (not including return value substitution adapters). Using only binary symbolic execution-based adapter search, our tool synthesized the correct adpator in 168 seconds, with 154 seconds spent in adapter search. Enumerating this search space concretely would take 11.15 million years.

Iv-G Reverse engineering using reference functions

Iv-G1 Code fragment selection

: Rockbox [49] is a free replacement 3rd party firmware for digital music players. We used a Rockbox image compiled for the iPod Nano (2g) device, based on the 32-bit ARM architecture, and disassembled it. We dissected the firmware image into code fragments using the following rules: (1) no code fragment could use memory, stack, floating-point, coprocessor, and supervisor call instructions, (2) no code fragment could branch to an address outside itself, (3) the first instruction of a code fragment could not be conditionally executed.

The first rule disallowed code fragments from having any inputs from and outputs to memory, thereby allowing us to use the 13 general purpose registers on ARM as inputs. The second rule prevented a branch to an invalid address. ARM instructions can be executed based on a condition code specified in the instruction, if the condition is not satisfied, the instruction is turned into a noop. The third rule disallowed the possibility of having code fragments that begin with a noop instruction, or whose behavior depended on a condition. The outputs of every code fragment were the last (up to) three registers written to by the code fragment. This caused each code fragment to be used as the target code region up to three times, once for each output register. This procedure gave us a total of 183,653 code regions, with 61,680 of them consisting of between 3 and 20 ARM instructions.

To evaluate which code fragments can be synthesized just with our adapter family without a contribution from a reference function, we checked which of these 61,680 code fragments can be adaptably substituted by a reference function that simply returns one of its arguments. Intuitively, any code fragment that can be adaptably substituted by an uninteresting reference function must be uninteresting itself, and so need not be considered further. We found 46,831 of the 61,680 code fragments could not be adaptably substituted by our simple reference function, and so we focused our further evaluation on these 46,831 code fragments that were between 3 and 20 ARM instructions long.

Iv-G2 Reference functions

: Since our code fragments consisted of between 3 and 20 ARM instructions, we focused on using reference functions that can be expressed in a similar number of ARM instructions. We used the source code of version 2.2.6 of the VLC media player [57] as the codebase for our reference functions. We performed an automated search for functions that were up to 20 lines of source code. This step gave us a total of 1647 functions. Similar to the three rules for code fragment selection, we discarded functions that accessed memory, called other VLC-specific functions, or made system calls to find 24 reference functions. Other than coming from a broadly similar problem domain (media players), our selection of reference functions was independent of the Rockbox codebase, so we would not expect that every reference function would be found in Rockbox.

Iv-G3 Results

We used the type conversion adapter family along with the return value substitution family, disallowing return value substitution adapters from setting the return value to be a type-converted argument of the reference function (which would lead to uninteresting adapters). But we allowed the reference function arguments to be replaced by unrestricted 32-bit constants, and we assumed each code segment takes up to 13 arguments. The size of this adapter search space can be calculated using the following formula:

The first multiplicative factor of 8 is due to the 8 possible return value substitution adapters. The permutation and combination operators occur due to the choices of arguments for the target code fragment and reference functions (we assumed both have 13 arguments). The final represents the 8 possible type conversion operators that a type conversion adapter can apply. The dominant factor for the size of the adapter search space comes from size of the set of possible constants. Our adapter family used unrestricted 32-bit constants, leading to a constants set of size .

With this adapter family set up, we ran adapter synthesis trying to adaptably substitute each of the 46,831 code fragments by each reference function . This gave us a total of 1,123,944 (46831*24) adapter synthesis tasks, with each adapter synthesis search space consisting of 1.353 x adapters, too large for concrete enumeration. We set a 5 minute hard time limit and a total memory limit of 2 GB per adapter synthesis task. We split the adapter synthesis tasks with each reference function into 32 parallel jobs creating a total of 768 (32*24) parallel jobs. We ran our jobs on a machine cluster running CentOS 6.8 with 64-bit Linux kernel version 2.6.32 and Intel Xeon E5-2680v3 processors. We present our results in Table III. The full set of results is presented in Section A-A of the Appendix.

adapter not substitutable timeout
fn_name #(full) #cluster steps
# steps
# steps
clamp 683(177) 110 12.9 99.3(12.1) 82.2(32.5) 40553 7.7 63.0(6.4) 5595 16.5 44.3 5416/179
prev_pow_2 32(0) 6 4.7 6.1(0.3) 1.8(0.9) 46767 4.3 7.5(0.5) 32 1 289.4 0/32
abs_diff 575(5) 75 10.5 20.0(1.3) 7.0(1.8) 46250 8.2 18.7(1.4) 6 5.7 286.5 0/6
bswap32 115(8) 19 8.7 16.6(1.2) 4.3(1.2) 46708 4.7 8.2(0.5) 8 2.8 293.5 0/8
integer_cmp 93(5) 15 9.6 21.4(2.2) 12.6(4.7) 46467 5.2 15.3(1.8) 271 3.1 247.2 3/268
even 3(2) 3 5.7 11.3(0.6) 4.3(2.3) 46823 4.2 12.7(0.9) 5 1.8 116.5 0/5
div255 4(0) 2 5 6.5(0.3) 2.5(1.5) 46823 4.4 7.6(0.5) 4 2.5 294.2 0/4
reverse_bits 276(0) 11 9 25.3(2.9) 9.1(1.9) 46541 12.5 50.9(5.6) 14 3 292.3 0/14
binary_log 48(0) 5 6.7 23.6(5.9) 12.6(8.8) 46528 4 25.6(6.4) 255 1.2 207.3 19/236
median 332(42) 60 13.7 119.2(26.7) 101.4(33.9) 32171 6.5 89.8(15.1) 14328 13.6 65.4 14184/144
hex_value 0 0 0 0) 0 46354 3.2 9.2(2.1) 477 1 268.8 2/475
get_descriptor_len 22(9) 2 9 16.7(0.6) 4.8(1.6) 46625 5.4 11.7(0.7) 184 1.4 233.4 0/184
tile_pos   5617(407) 909 10.9 53.5(23.1) 42.5(18.1) 24696 8 67.6(27.1) 16518 7.6 280.5 16441/77
diract_pic_n_bef_m 330(2) 18 13.2 25.7 (3.0) 12.7(2.1) 46393 6.6 15.3(1.3) 108 51.5 138 74/34
ps_id_to_tk 0 0 0 0 0 46721 4.4 15.8(2.4) 110 1.1 258.8 3/107
clz 41(0) 7 18.6 39.0(4.5) 16.2(2.5) 46727 7.8 16.7(2.1) 63 5.1 143.6 1/62
ctz 46(0) 4 5.9 16.2(3.8) 7.1(3.5) 46701 3.4 19.5(6.2) 84 1.5 283.7 4/80
popcnt_32 0 0 0 0 0 46802 5.6 11.5(0.8) 29 1 295.4 0/29
parity 0 0 0 0 0 46821 5 10.0(0.6) 10 1.2 266.3 0/10
dv_audio_12_to_16 0 0 0 0 0 46637 3.9 17.7(2.8) 194 1 290.3 1/193
is_power_2 0 0 0 0 0 46801 3.8 9.1(1.4) 30 3.7 291.1 0/30
RenderRGB 763(2) 64 10.8 27.5(1.5) 10.4(2.8) 46061 5.7 17(0.9) 7 4.4 115.7 0/7
decode_BCD 0 0 0 0 0 46824 4.7 8.8(1.1) 7 1.9 126.1 0/7
22(15) 4 5 7.9(0.9) 2.6(1.8) 46235 3.4 9.3(2.1) 574 1 289.5 4/570
TABLE III: Reverse engineering results using 46831 target code fragments from a Rockbox firmware image and 24 reference functions from VLC media player grouped by the three overall possible terminations of adapter synthesis. The #(full) column reports how many code fragments were found to be adaptably substitutable, and how many of those exploited the full generality of the reference function.

The first column shows the reference functions chosen from the VLC media player source code. The #(full) column reports how many code fragments were found to be adaptably substitutable (represented by the value for #), and how many of those exploited the full generality of the reference function (represented by the value of full). We report average number of steps, average total running time (average solver time), average total time spent in adapter search steps (average time during the last adapter search step) in columns steps, total time (solver), AS time (last) respectively. In case of timeouts, only average solver time is reported since the average total running time was 5 minutes.

Iv-G4 Clustering using random tests:

For every reference function, we can either have a conclusion that finds an adapter, or finds the fragment to not be adaptably substitutable, or runs out of time. Our adapter synthesis tool finds adaptable substitution using 18 out of the 24 reference functions. For every reference function, we cluster its adapted versions using 100000 random tests: all adapted versions of a reference function that report the same output for all inputs are placed in the same cluster. The number of clusters is reported in the #clusters column. For each reference function, we then manually examine these clusters to judge which adapted versions use the complete functionality of that reference function; these are the cases where describing the functionality of the target fragment in terms of the reference function is mostly likely to be concise and helpful. This took us less than a minute of manual effort for each reference function because we understood the intended semantic functionality of every reference function (we had its source code). We found several instances of adapters using the full generality of the reference function for 11 reference functions.

Fig. 9: Subset of partial order relationship among adapted clamp instances

We found that a majority of our found adapters exploit specific functionality of the reference functions. We explored this observation further by manually summarizing the semantics of the 683 adapters reported for clamp. We found that these 683 adapters have a partial order between them created by our adapter families of type conversion and return value substitution. We present a subset of this partial order as a lattice-like diagram in Figure 9. To explain one unexpected example, the invert-low-bit operation on a value v can be implemented in terms of val < N by setting val to the low bit of v zero-extended to 32 bits and N to 1, and zero-extending the low 1 bit of the return value of val < N to 32 bits. Some such functionalities owe more to the flexibility of the adapter family than they do to the reference function. These results suggest it would be worthwhile in the future to prune them earlier by searching for instances of the simplest reference functions first, and then excluding these from future searches.

Timeouts were the third conclusion of each adapter synthesis task as reported in Table III. We report a histogram of the total running time used to find adapters in Figure 12 for the tile_pos reference function, which had the most timeouts. Similar histograms for clamp and median reference function are reported in Figures 1011.

Fig. 10: Running times for synthesized adapters using clamp reference function
Fig. 11: Running times for synthesized adapters using median reference function

The number of adapters found after 300 seconds decreases rapidly, consistent with the mean total running time (subcolumn total time (solver) under column adapter in Table III) of 53.5 seconds for the tile_pos reference function.

Fig. 12: Running times for synthesized adapters using tile_pos reference function

Table III also shows that the total running time, when our tool concludes with finding an adapter, is significantly lesser than 300 seconds for all reference functions that reported adapters. Though setting any finite timeout can cause some instances to be lost, these results suggest that a 300-second timeout was appropriate for this experiment, and that most timeouts would not have led to adapters.

Iv-H Comparing adapter families

We also explored the tradeoff between adapter search space size and effectiveness of the adapter family. We ran all 46,831 target code fragments with clamp as the reference function using two additional adapter families beyond the combination of type conversion family with return value substitution described above. The first adapter family allowed only argument permutation and the second allowed argument permututation along with substitution with unrestricted 32-bit constants. We ran the first adapter family setup (argument permutation + return value substitution) with a 2.5 minute hard time limit, the second adapter family setup (argument substitution + return value substitution) with a 5 minute hard time limit, and the third adapter family setup (argument substitution + return value substitution) was the same as the previous subsection with also a 5 minute hard time limit. We present our results in Table IV.

size #-ad #-inequiv #-timeout
4.98E+10 9 46803 19
1.3538427E+126 705 45782 344
1.3538430E+126 683 40553 5595
TABLE IV: Comparing adapter families with 46,831 target code fragments and clamp reference function

As expected, the number of timeouts increases with an increase in the size of adapter search space. Table IV also shows that, for clamp, a simpler adapter family is better at finding adapters than a more expressive family, because more searches can complete within the timeout. But, this may not be true for all reference functions. Table IV suggests that, when computationally feasible, adapter families should be tried in increasing order of expressiveness to have the fewest timeouts overall. We plan to explore this tradeoff between expressiveness and effectiveness of adapter families in the future.

V Limitations and Future Work

We represented our synthesized adapters by an assignment of concrete values to symbolic variables and manually checked them for correctness. adapters can be automatically translated into binary code which can replace the original function with the adapter function. We plan to automate generation of such adapter code in the future.

During every adapter search step, symbolic execution explores all feasible paths, including paths terminated on a previous adapter search step because they did not lead to a correct adapter. Once an adapter is found, the next adapter search can be accelerated by saving the state of adapter search, and picking up symbolic execution from the last path that led to a correct adapter in the next adapter search.

Our tool currently presumes that all behaviors of the target function must be matched, modulo failures such as null dereferences. Using a tool like Daikon [13] to infer the preconditions of a function from its uses could help our tool find adapters that are correct for correct uses of functions, such as isupper and islower.

adapter synthesis requires us to find if there exists an adapter such that for all inputs to the target function, the output of the target function and the output of the adapted inner function are equal. Thus the synthesis problem can be posed as a single query whose variables have this pattern of quantification (whereas CEGIS uses only quantifier-free queries). We plan to explore using solvers for this fragment of first-order bitvector logic, such as Yices [12].

Symbolic execution can only check equivalence over inputs of bounded size, though improvements such as path merging [31, 3] can improve scaling. Our approach could also integrate with any other equivalence checking approach that produces counterexamples, including ones that synthesize inductive invariants to cover unbounded inputs [55], though we are not aware of any existing binary-level implementations that would be suitable.

Vi Related Work

Vi-a Detecting Equivalent Code

The majority of previous work in this area has focused on detecting syntactically equivalent code, or ‘clones,’ which are, for instance, the result of copy-and-paste [29, 33, 25]. Jiang et al. [26] propose an algorithm for automatically detecting functionally equivalent code fragments using random testing and allow for limited types of adapter functions over code inputs — specifically permutations and combinations of multiple inputs into a single struct. Ramos et al. [47] present a tool that checks for equivalence between arbitrary C functions using symbolic execution. While our definition of functional equivalence is similar to that used by Jiang et al. and Ramos et al., our adapter families capture a larger set of allowed transformations during adapter synthesis than both.

Amidon et al. [2] describe a technique for fracturing a program into pieces which can be replaced by more optimized code from multiple applications. They mention the need for automatic generation of adapters which enable replacement of pieces of code which are not immediately compatible. While Amidon et al. describe a parameter reordering adapter, they do not mention how automation of synthesis of such adapters can be achieved. David et al. [10] decompose binary code into smaller pieces, find semantic similarity between pieces, and use statistical reasoning to compose similarity between procedures. Since this approach relies on pieces of binary code, they cannot examine binary code pieces that make function calls and check for semantic similarity across wrappers around function calls. Goffi et al. [20] synthesize a sequence of functions that are equivalent to another function w.r.t a set of execution scenarios. Their implementation is similar to our concrete enumeration-based adapter search which produces equivalence w.r.t. a set of tests. In the hardware domain, adapter synthesis has been applied to low-level combinatorial circuits by Gascón et al [18]. They apply equivalence checking to functional descriptions of a low-level combinatorial circuit and reference implementations while synthesizing a correct mapping of the input and output signals and setting of control signals. They convert this mapping problem into a exists/forall problem which is solved using the Yices SMT solver [12].

Vi-B Component Retrieval

Type-based component retrieval was an active area of research in the past. Many papers in this area [48][50][51] focused on the problem of finding a function, whose polymorphic type is known to the user, within a library of software components. Type-based hot swapping [11] and signature matching [61] were also active areas of related research in the past. These techniques relied on adapter-like operations such as currying or uncurrying functions, reordering tuples, and type conversion. Reordering, insertion, deletion, and type conversion are only some of the many operations supported by our adapters. These techniques can only be applied at the source level, whereas our adapter synthesis technique can be applied at source and binary levels

Vi-C Component Adaptation

Component adaptation was another related active area of research in the past. This includes techniques for adapter specification [46], for component adaptation using formal specifications of components [39][42][43][60][5]. Component adaptation has also been performed at the Java bytecode level [30], as well as the C bitcode level [40]. Behavior sampling [44] is a similar area of research for finding equivalence over a small set of input samples. However, these techniques either relied on having a formal specification of the behavior of all components in the library to be searched, or provided techniques for translating a formally specified adapter [46].

Vi-D Program Synthesis

Program synthesis is an active area of research that has many applications including generating optimal instruction sequences [37, 27], automating repetitive programming, filling in low-level program details after programmer intent has been expressed [52], and even binary diversification [23]. Programs can be synthesized from formal specifications [35], simpler (likely less efficient) programs that have the desired behavior [37, 52, 27], or input/output oracles [24]. We take the second approach to specification, treating existing functions as specifications when synthesizing adapter functions.

Vii Conclusion

We presented a new technique to search for semantically-equivalent pieces of code which can be substituted while adapting differences in their interfaces. This approach is implemented at the binary level, thereby enabling wider applications and consideration of exact run-time behavior. We implemented adapter synthesis for x86-64 and ARM binary code. We presented examples demonstrating applications towards security, deobfuscation, efficiency, and library replacement, and an evaluation using the C library. Our adapter families can be combined to find sophisticated adapters as shown by adaptation of RC4 implementations. While finding thousands of functions to not be equivalent, our tool reported many instances of semantic equivalence, including C library functions such as ffs and ffsl, which have assembly language implementations. Our comparison of concrete enumeration-based adapter search with binary symbolic execution-based adapter search allows users of adapter synthesis to choose between the two approaches based on size of adapter search space. We selected more than 61,000 target code fragments from a 3rd party firmware image for the iPod Nano 2g and 24 reference functions from the VLC media player. Given a adapter search space of 1.353 x adapters, we used binary symbolic execution-based adapter search to run more than a million adapter synthesis tasks. Our tool finds dozens of instances of several reference functions in the firmware image, and confirms that the process of understanding the semantics of binary code fragments can be automated using adapter synthesis. Our results show that the CEGIS approach for adapter synthesis of binary code is feasible and sheds new light on potential applications such as searching for efficient clones, deobfuscation, program understanding, and security through diversity.


  • [1] A. V. Aho and J. E. Hopcroft. The design and analysis of computer algorithms. Pearson Education India, 1974.
  • [2] P. Amidon, E. Davis, S. Sidiroglou-Douskos, and M. Rinard. Program fracture and recombination for efficient automatic code reuse. In High Performance Extreme Computing Conference (HPEC), 2015 IEEE, pages 1–6, Sept 2015.
  • [3] T. Avgerinos, A. Rebert, S. K. Cha, and D. Brumley. Enhancing symbolic execution with veritesting. In 36th International Conference on Software Engineering (ICSE), pages 1083–1094, June 2014.
  • [4] H. Borck, M. Boddy, I. J. D. Silva, S. Harp, K. Hoyme, S. Johnston, A. Schwerdfeger, and M. Southern. Frankencode: Creating diverse programs using code clones. In 2016 IEEE 23nd International Conference on Software Analysis, Evolution and Reengineering (SANER), pages 604–608. IEEE, 2016.
  • [5] A. Bracciali, A. Brogi, and C. Canal. A formal approach to component adaptation. Journal of Systems and Software, 74(1):45–54, 2005.
  • [6] J. Burnim, S. Juvekar, and K. Sen. Wise: Automated test generation for worst-case complexity. In Proceedings of the 31st International Conference on Software Engineering, pages 463–473. IEEE Computer Society, 2009.
  • [7] C. Collberg, S. Martin, J. Myers, and J. Nagra. Distributed application tamper detection via continuous software updates. In Proceedings of the 28th Annual Computer Security Applications Conference, ACSAC ’12, pages 319–328, New York, NY, USA, 2012. ACM.
  • [8] D. Coppersmith and S. Winograd. Matrix multiplication via arithmetic progressions. Journal of Symbolic Computation, 9(3):251 – 280, 1990.
  • [9] DARPA. Space/Time Analysis for Cybersecurity (STAC)., 2015.
  • [10] Y. David, N. Partush, and E. Yahav. Statistical similarity of binaries. In Proceedings of the 37th ACM SIGPLAN Conference on Programming Language Design and Implementation, PLDI ’16, pages 266–280, New York, NY, USA, 2016. ACM.
  • [11] D. Duggan. Type-based hot swapping of running modules. Acta Inf., 41(4):181–220, Mar. 2005.
  • [12] B. Dutertre. Yices 2.2. In A. Biere and R. Bloem, editors, Computer Aided Verification: 26th International Conference, CAV 2014, Held as Part of the Vienna Summer of Logic, VSL 2014, Vienna, Austria, July 18-22, 2014. Proceedings, pages 737–744, Cham, 2014. Springer International Publishing.
  • [13] M. D. Ernst, J. H. Perkins, P. J. Guo, S. McCamant, C. Pacheco, M. S. Tschantz, and C. Xiao. The daikon system for dynamic detection of likely invariants. Science of Computer Programming, 69(1):35–45, 2007.
  • [14] FireEye Inc. Shifu Malware Analyzed: Behavior, Capabilities and Communications., 2015.
  • [15] FuzzBALL: Vine-based Binary Symbolic Execution., 2013–2016.
  • [16] F. L. Gall. Powers of tensors and fast matrix multiplication. CoRR, abs/1401.7714, 2014.
  • [17] V. Ganesh and D. L. Dill. A decision procedure for bit-vectors and arrays. In W. Damm and H. Hermanns, editors, Computer Aided Verification: 19th International Conference, CAV 2007, Berlin, Germany, July 3-7, 2007. Proceedings, pages 519–531, Berlin, Heidelberg, 2007. Springer.
  • [18] A. Gascón, P. Subramanyan, B. Dutertre, A. Tiwari, D. Jovanović, and S. Malik. Template-based circuit understanding. In Proceedings of the 14th Conference on Formal Methods in Computer-Aided Design, FMCAD ’14, pages 17:83–17:90, Austin, TX, 2014. FMCAD Inc.
  • [19] GNU. The gnu c library : Absolute value., 2017.
  • [20] A. Goffi, A. Gorla, A. Mattavelli, M. Pezzè, and P. Tonella. Search-based synthesis of equivalent method sequences. In Proceedings of the 22nd ACM SIGSOFT International Symposium on Foundations of Software Engineering, pages 366–376. ACM, 2014.
  • [21] Shifu: ’Masterful’ New Banking Trojan Is Attacking 14 Japanese Banks., 2015.
  • [22] Implementing Strassen’s Algorithm For Matrix-Matrix Multiplication With OpenMP-3.0 Tasks (Intel)., 2010.
  • [23] M. Jacob, M. H. Jakubowski, P. Naldurg, C. W. N. Saw, and R. Venkatesan. The superdiversifier: Peephole individualization for software protection. In K. Matsuura and E. Fujisaki, editors, Advances in Information and Computer Security: Third International Workshop on Security, IWSEC 2008, Kagawa, Japan, November 25-27, 2008. Proceedings, pages 100–120, Berlin, Heidelberg, 2008. Springer.
  • [24] S. Jha, S. Gulwani, S. A. Seshia, and A. Tiwari. Oracle-guided component-based program synthesis. In Proceedings of the 32nd ACM/IEEE International Conference on Software Engineering - Volume 1, ICSE ’10, pages 215–224, New York, NY, USA, 2010. ACM.
  • [25] L. Jiang, G. Misherghi, Z. Su, and S. Glondu. Deckard: Scalable and accurate tree-based detection of code clones. In Proceedings of the 29th International Conference on Software Engineering, ICSE ’07, pages 96–105, Washington, DC, USA, 2007. IEEE Computer Society.
  • [26] L. Jiang and Z. Su. Automatic mining of functionally equivalent code fragments via random testing. In Proceedings of the Eighteenth International Symposium on Software Testing and Analysis, ISSTA ’09, pages 81–92, New York, NY, USA, 2009. ACM.
  • [27] R. Joshi, G. Nelson, and K. Randall. Denali: A goal-directed superoptimizer. In Proceedings of the ACM SIGPLAN 2002 Conference on Programming Language Design and Implementation, PLDI ’02, pages 304–314, New York, NY, USA, 2002. ACM.
  • [28] P. Junod, J. Rinaldini, J. Wehrli, and J. Michielin. Obfuscator-LLVM – software protection for the masses. In B. Wyseur, editor, Proceedings of the IEEE/ACM 1st International Workshop on Software Protection, SPRO’15, Firenze, Italy, May 19th, 2015, pages 3–9. IEEE, 2015.
  • [29] T. Kamiya, S. Kusumoto, and K. Inoue. Ccfinder: A multilinguistic token-based code clone detection system for large scale source code. IEEE Trans. Softw. Eng., 28(7):654–670, July 2002.
  • [30] R. Keller and U. Hölzle. Binary component adaptation. In European Conference on Object-Oriented Programming, pages 307–329. Springer, 1998.
  • [31] V. Kuznetsov, J. Kinder, S. Bucur, and G. Candea. Efficient state merging in symbolic execution. In ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), pages 193–204, June 2012.
  • [32] T. László and Á. Kiss. Obfuscating C++ programs via control flow flattening. Annales Universitatis Scientarum Budapestinensis de Rolando Eötvös Nominatae, Sectio Computatorica, 30:3–19, 2009.
  • [33] Z. Li, S. Lu, S. Myagmar, and Y. Zhou. Cp-miner: A tool for finding copy-paste and related bugs in operating system code. In Proceedings of the 6th Conference on Symposium on Opearting Systems Design & Implementation - Volume 6, OSDI’04, pages 20–20, Berkeley, CA, USA, 2004. USENIX Association.
  • [34] C.-K. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser, G. Lowney, S. Wallace, V. J. Reddi, and K. Hazelwood. Pin: building customized program analysis tools with dynamic instrumentation. ACM SIGPLAN Notices, 40(6):190–200, 2005.
  • [35] Z. Manna and R. Waldinger. A deductive approach to program synthesis. ACM Trans. Program. Lang. Syst., 2(1):90–121, Jan. 1980.
  • [36] L. Martignoni, S. McCamant, P. Poosankam, D. Song, and P. Maniatis. Path-exploration lifting: Hi-fi tests for lo-fi emulators. In Proceedings of the 17th International Conference on Architectural Support for Programming Languages and Operating Systems, ASPLOS XVII, pages 337–348, New York, NY, USA, 2012. ACM.
  • [37] H. Massalin. Superoptimizer: A look at the smallest program. In Proceedings of the Second International Conference on Architectual Support for Programming Languages and Operating Systems, ASPLOS II, pages 122–126, Los Alamitos, CA, USA, 1987. IEEE Computer Society Press.
  • [38] M. Matz, J. Hubicka, A. Jaeger, and M. Mitchell. System V Application Binary Interface., 2013.
  • [39] B. Morel and P. Alexander. Spartacas: automating component reuse and adaptation. IEEE Transactions on Software Engineering, 30(9):587–600, 2004.
  • [40] M. Nita and D. Grossman. Automatic transformation of bit-level c code to support multiple equivalent data layouts. In L. Hendren, editor, Compiler Construction: 17th International Conference, CC 2008, Held as Part of the Joint European Conferences on Theory and Practice of Software, ETAPS 2008, Budapest, Hungary, March 29 - April 6, 2008. Proceedings, pages 85–99, Berlin, Heidelberg, 2008. Springer Berlin Heidelberg.
  • [41] Palo Alto Networks. 2016 Updates to Shifu Banking Trojan., 2017.
  • [42] J. Penix and P. Alexander. Toward automated component adaptation. In Proceedings of the Ninth International Conference on Software Engineering and Knowledge Engineering, pages 535–542, 1997.
  • [43] J. Penix, P. Baraona, and P. Alexander. Classification and retrieval of reusable components using semantic features. In Knowledge-Based Software Engineering Conference, 1995. Proceedings., 10th, pages 131–138. IEEE, 1995.
  • [44] A. Podgurski and L. Pierce. Behavior sampling: a technique for automated retrieval of reusable components. In Proceedings of the 14th international conference on Software engineering, pages 349–361. ACM, 1992.
  • [45] T. A. Proebsting. Optimizing an ansi c interpreter with superoperators. In Proceedings of the 22nd ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL ’95, pages 322–332, New York, NY, USA, 1995. ACM.
  • [46] J. M. Purtilo and J. M. Atlee. Module reuse by interface adaptation. Software: Practice and Experience, 21(6):539–556, 1991.
  • [47] D. A. Ramos and D. R. Engler. Practical, low-effort equivalence verification of real code. In Proceedings of the 23rd International Conference on Computer Aided Verification, CAV’11, pages 669–685, Berlin, Heidelberg, 2011. Springer-Verlag.
  • [48] M. Rittri. Using types as search keys in function libraries. In Proceedings of the Fourth International Conference on Functional Programming Languages and Computer Architecture, FPCA ’89, pages 174–183, New York, NY, USA, 1989. ACM.
  • [49] Rockbox - Free Music Player Firmware., 2002–2017.
  • [50] C. Runciman and I. Toyn. Retrieving re-usable software components by polymorphic type. In Proceedings of the fourth international conference on Functional programming languages and computer architecture, pages 166–173. ACM, 1989.
  • [51] C. Runciman and I. Toyn. Retrieving reusable software components by polymorphic type. Journal of Functional Programming, 1(2):191–211, Apr 1991.
  • [52] A. Solar-Lezama, L. Tancau, R. Bodík, S. A. Seshia, and V. A. Saraswat. Combinatorial sketching for finite programs. In Proceedings of the 12th International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), pages 404–415, Oct. 2006.
  • [53] D. Song. Bitblaze: Binary analysis for computer security., 2017.
  • [54] D. Song, D. Brumley, H. Yin, J. Caballero, I. Jager, M. G. Kang, Z. Liang, J. Newsome, P. Poosankam, and P. Saxena. BitBlaze: A new approach to computer security via binary analysis. In Proceedings of the 4th International Conference on Information Systems Security. Keynote invited paper., Hyderabad, India, Dec. 2008.
  • [55] S. Srivastava and S. Gulwani. Program verification using templates over predicate abstraction. In Proceedings of the 2009 ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), pages 223–234, June 2009.
  • [56] V. Strassen. Gaussian elimination is not optimal. Numerische Mathematik, 13(4):354–356, 1969.
  • [57] VLC - Official Site - VideoLAN., 2001–2017.
  • [58] H. S. Warren. Collection of programs to compute CRC-32 checksum., 2013.
  • [59] H. S. Warren. Hacker’s delight. Pearson Education, 2013.
  • [60] D. M. Yellin and R. E. Strom. Protocol specifications and component adaptors. ACM Transactions on Programming Languages and Systems (TOPLAS), 19(2):292–333, 1997.
  • [61] A. M. Zaremski and J. M. Wing. Signature matching: a tool for using software libraries. ACM Transactions on Software Engineering and Methodology (TOSEM), 4(2):146–170, 1995.

Appendix A Appendix

A-a Reverse engineering expanded tables

For the results reported in Section IV-G, we report detailed metrics for the three possible conclusions, adapter found, not substitutable, timed out, in the Tables VVIVII respectively. The AS-stops/CE-stops column in Table VII reports the number of times a timeout resulted in an adapter search step or counter-example search step to be halted. In the first column, after each reference function’s name, the #N within parenthesis reports the number of arguments taken by the reference function.

fn_name # #full #clusters steps
total time
CE total time
CE last time
AS total time
AS last time
clamp 683 177 110 12.903 99.272 (12.099) 17.110 (0.941) 1.880 (0.282) 82.163 (11.158) 32.490 (4.253)
prev_pow_2(#1) 32 0 6 4.688 6.125 (0.266) 4.312 (0.144) 0.875 (0.053) 1.812 (0.122) 0.938 (0.063)
abs_diff(#2) 575 5 75 10.517 19.981 (1.331) 12.944 (0.487) 1.120 (0.095) 7.037 (0.844) 1.843 (0.276)
bswap32(#1) 115 8 19 8.67 16.565 (1.235) 12.313 (0.984) 1.000 (0.227) 4.252 (0.251) 1.226 (0.089)
integer_cmp(#2) 93 5 15 9.645 21.419 (2.246) 8.839 (0.598) 1.280 (0.275) 12.581 (1.648) 4.742 (0.630)
even(#1) 3 2 3 5.667 11.333 (0.558) 7.000 (0.312) 2.333 (0.218) 4.333 (0.246) 2.333 (0.154)
div255(#1) 4 0 2 5 6.500 (0.262) 4.000 (0.143) 0.750 (0.051) 2.500 (0.119) 1.500 (0.068)
reverse_bits(#1) 276 0 11 8.978 25.264 (2.926) 16.192 (0.678) 1.978 (0.112) 9.072 (2.248) 1.895 (0.454)
binary_log(#1) 48 0 5 6.708 23.562 (5.870) 10.938 (2.191) 2.125 (0.728) 12.625 (3.679) 8.750 (3.235)
median(#3) 332 42 60 13.669 119.226 (26.739) 17.789 (1.323) 2.250 (0.454) 101.437 (25.416) 33.931 (8.548)
hex_value(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
22 9 2 9 16.682 (0.583) 11.909 (0.328) 1.136 (0.091) 4.773 (0.255) 1.591 (0.098)
tile_pos(#4) 5617 407 909 10.902 53.478 (23.124) 10.968 (1.767) 2.836 (1.409) 42.510 (21.357) 18.090 (10.019)
330 2 18 13.224 25.736 (2.974) 13.048 (0.638) 0.855 (0.084) 12.688 (2.335) 2.124 (0.386)
ps_id_to_tk(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
leading_zero_count(#1) 41 0 7 18.561 39.000 (4.529) 22.780 (1.174) 1.000 (0.146) 16.220 (3.355) 2.488 (0.721)
trailing_zero_count(#1) 46 0 4 5.87 16.196 (3.832) 9.109 (1.097) 2.065 (0.738) 7.087 (2.735) 3.478 (1.322)
popcnt_32(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
parity(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
dv_audio_12_to_16(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
is_power_2(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
RenderRGB(#3) 763 2 64 10.814 27.469 (1.518) 17.021 (0.814) 1.046 (0.143) 10.448 (0.704) 2.819 (0.221)
decode_BCD(#1) 0 0 0 0 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000) 0.000 (0.000)
22 15 4 5 7.909 (0.887) 5.273 (0.505) 1.182 (0.361) 2.636 (0.381) 1.773 (0.345)
TABLE V: Metrics for adapters for all reference functions
fn_name # steps
total time
CE total time
CE last time
AS total time
AS last time
clamp 40553 7.711 63.015 (6.361) 8.171 (0.375) 1.703 (0.112) 54.844 (5.986) 38.464 (4.032)
prev_pow_2(#1) 46767 4.258 7.521 (0.492) 4.833 (0.225) 2.008 (0.154) 2.687 (0.267) 1.502 (0.201)
abs_diff(#2) 46250 8.205 18.735 (1.384) 11.281 (0.411) 2.281 (0.124) 7.453 (0.973) 3.268 (0.562)
bswap32(#1) 46708 4.682 8.184 (0.493) 5.136 (0.196) 1.764 (0.102) 3.048 (0.297) 1.620 (0.217)
integer_cmp(#2) 46467 5.249 15.324 (1.772) 7.850 (0.404) 2.816 (0.177) 7.474 (1.369) 4.640 (0.999)
even(#1) 46823 4.218 12.699 (0.859) 7.088 (0.229) 2.883 (0.149) 5.611 (0.630) 3.881 (0.529)
div255(#1) 46823 4.381 7.568 (0.463) 4.849 (0.206) 1.824 (0.117) 2.719 (0.257) 1.499 (0.196)
reverse_bits(#1) 46541 12.536 50.866 (5.645) 22.051 (0.784) 2.359 (0.103) 28.815 (4.861) 12.573 (1.454)
binary_log(#1) 46528 4.024 25.631 (6.368) 4.848 (0.551) 2.004 (0.136) 20.783 (5.817) 15.253 (4.314)
median(#3) 32171 6.484 89.779 (15.126) 6.598 (0.312) 1.723 (0.097) 83.181 (14.815) 75.092 (13.180)
hex_value(#1) 46354 3.157 9.233 (2.092) 4.412 (0.370) 2.333 (0.128) 4.821 (1.722) 3.894 (1.471)
transform_from_basic_ops(#10) 40169 10.253 115.732 (8.667) 9.020 (0.452) 1.552 (0.079) 106.712 (8.215) 75.875 (5.514)
get_descriptor_length_24b(#1) 46625 5.442 11.687 (0.718) 7.791 (0.329) 2.384 (0.104) 3.896 (0.388) 1.988 (0.301)
tile_pos(#4) 24696 8.031 67.636 (27.126) 7.045 (0.397) 1.756 (0.091) 60.591 (26.728) 46.309 (20.400)
diract_picture_n_before_m(#2) 46393 6.615 15.315 (1.327) 6.968 (0.315) 2.226 (0.116) 8.347 (1.012) 3.746 (0.337)
ps_id_to_tk(#1) 46721 4.41 15.811 (2.370) 7.414 (1.090) 2.579 (0.190) 8.397 (1.280) 6.504 (1.127)
leading_zero_count(#1) 46727 7.838 16.737 (2.105) 8.462 (0.598) 2.090 (0.136) 8.275 (1.507) 3.473 (0.609)
trailing_zero_count(#1) 46701 3.392 19.508 (6.189) 4.161 (0.706) 1.881 (0.135) 15.347 (5.483) 13.786 (5.088)
popcnt_32(#1) 46802 5.602 11.500 (0.818) 7.296 (0.313) 2.471 (0.155) 4.204 (0.504) 2.076 (0.335)
parity(#1) 46821 4.988 9.968 (0.644) 6.447 (0.292) 2.584 (0.179) 3.521 (0.352) 1.813 (0.244)
dv_audio_12_to_16(#1) 46637 3.884 17.708 (2.780) 8.279 (0.598) 3.607 (0.155) 9.429 (2.182) 7.004 (1.673)
is_power_2(#1) 46801 3.791 9.130 (1.357) 5.420 (0.316) 2.819 (0.225) 3.710 (1.042) 2.218 (0.659)
RenderRGB(#3) 46061 5.663 17.038 (0.901) 9.718 (0.366) 2.670 (0.172) 7.320 (0.535) 4.023 (0.330)
decode_BCD(#1) 46824 4.706 8.751 (1.124) 5.516 (0.356) 1.890 (0.202) 3.235 (0.768) 1.903 (0.618)
mpga_get_frame_samples(#1) 46235 3.366 9.288 (2.057) 4.887 (0.497) 2.580 (0.148) 4.401 (1.560) 3.595 (1.454)
TABLE VI: Metrics for the insubstitutable conclusion for all reference functions
fn_name # steps
total time
CE total time
CE last time
AS total time
AS last time
clamp 5595 16.505 300.000 (44.278) 27.856 (8.112) 9.392 (6.966) 272.144 (36.167) 140.702 (17.457) 5416/179
prev_pow_2(#1) 32 1 300.000 (289.445) 300.000 (289.445) 300.000 (289.445) 0.000 (0.000) 0.000 (0.000) 0/32
abs_diff(#2) 6 5.667 300.000 (286.525) 297.333 (286.318) 288.167 (285.378) 2.667 (0.206) 1.167 (0.112) 0/6
bswap32(#1) 8 2.75 300.000 (293.526) 299.125 (293.479) 296.250 (293.329) 0.875 (0.047) 0.875 (0.047) 0/8
integer_cmp(#2) 271 3.085 300.000 (247.247) 296.347 (246.627) 288.122 (243.312) 3.653 (0.620) 1.063 (0.209) 3/268
even(#1) 5 1.8 300.000 (116.452) 299.600 (116.434) 297.400 (116.320) 0.400 (0.019) 0.400 (0.019) 0/5
div255(#1) 4 2.5 300.000 (294.241) 299.500 (294.203) 297.500 (294.115) 0.500 (0.037) 0.500 (0.037) 0/4
reverse_bits(#1) 14 3 300.000 (292.294) 298.714 (292.182) 294.786 (291.965) 1.286 (0.112) 1.286 (0.112) 0/14
binary_log(#1) 255 1.239 300.000 (207.291) 298.824 (206.920) 277.769 (203.879) 1.176 (0.371) 0.949 (0.336) 19/236
median(#3) 14328 13.634 300.000 (65.444) 15.655 (2.144) 3.266 (1.319) 284.345 (63.300) 167.910 (35.663) 14184/144
hex_value(#1) 477 1.013 300.000 (268.765) 299.964 (268.754) 298.753 (268.165) 0.036 (0.010) 0.027 (0.007) 2/475
6409 18.381 300.000 (27.949) 22.098 (3.092) 4.510 (2.408) 277.902 (24.857) 172.895 (14.278) 6319/90
184 1.391 300.000 (233.380) 299.832 (233.373) 298.853 (233.277) 0.168 (0.006) 0.168 (0.006) 0/184
tile_pos(#4) 16518 7.634 300.000 (280.532) 8.118 (1.326) 2.782 (0.988) 291.882 (279.206) 256.574 (249.372) 16441/77
108 51.481 300.000 (137.988) 132.556 (87.679) 89.917 (85.144) 167.444 (50.309) 25.954 (3.204) 74/34
ps_id_to_tk(#1) 110 1.118 300.000 (258.764) 299.755 (258.748) 291.764 (250.903) 0.245 (0.015) 0.218 (0.014) 3/107
63 5.079 300.000 (143.608) 297.254 (143.230) 171.063 (111.259) 2.746 (0.379) 0.841 (0.100) 1/62
84 1.476 300.000 (283.679) 299.155 (283.545) 285.417 (270.053) 0.845 (0.134) 0.643 (0.111) 4/80
popcnt_32(#1) 29 1 300.000 (295.366) 300.000 (295.366) 300.000 (295.366) 0.000 (0.000) 0.000 (0.000) 0/29
parity(#1) 10 1.2 300.000 (266.296) 299.900 (266.293) 275.100 (266.280) 0.100 (0.003) 0.100 (0.003) 0/10
194 1.026 300.000 (290.336) 299.979 (290.334) 296.928 (288.827) 0.021 (0.002) 0.021 (0.002) 1/193
is_power_2(#1) 30 3.667 300.000 (291.082) 297.833 (290.309) 293.867 (290.012) 2.167 (0.773) 1.133 (0.375) 0/30
RenderRGB(#3) 7 4.429 300.000 (115.721) 297.000 (115.538) 290.714 (115.275) 3.000 (0.184) 1.714 (0.099) 0/7
decode_BCD(#1) 7 1.857 300.000 (126.084) 299.714 (126.040) 298.429 (125.986) 0.286 (0.044) 0.286 (0.044) 0/7
574 1.024 300.000 (289.464) 299.963 (289.460) 297.423 (288.201) 0.037 (0.003) 0.035 (0.003) 4/570
TABLE VII: Metrics for the timeout conclusion for all reference functions
Comments 0
Request Comment
You are adding the first comment!
How to quickly get a good reply:
  • Give credit where it’s due by listing out the positive aspects of a paper before getting into which changes should be made.
  • Be specific in your critique, and provide supporting evidence with appropriate references to substantiate general statements.
  • Your comment should inspire ideas to flow and help the author improves the paper.

The better we are at sharing our knowledge with each other, the faster we move forward.
The feedback must be of minimum 40 characters and the title a minimum of 5 characters
Add comment
Loading ...
This is a comment super asjknd jkasnjk adsnkj
The feedback must be of minumum 40 characters
The feedback must be of minumum 40 characters

You are asking your first question!
How to quickly get a good answer:
  • Keep your question short and to the point
  • Check for grammar or spelling errors.
  • Phrase it like a question
Test description