sponding case can be found in the first part ls1 of an appended
list ls1+ls2, then finding a case in the appended list amounts to
finding it in the second part ls2 of the appended list.
Lemma select_switch_app_right: forall ls1 ls2 pc,
ls1[pc]=[] -> (ls1++ls2)[pc]=ls2[pc].
7. Implementation and Experiments
Our formal development consists of about 2100 lines of specifica-
tions and 6000 lines of proofs. It is integrated into the latest version
(i.e. version 2.5) of the CompCert compiler [18]. Our proof relies
on the Clight semantics of CompCert and reuses many auxiliary
lemmata already present in CompCert and not mentioned in this
paper.
Our CFG flattening transformation does not exactly produce
the same result as in Figure 1. Indeed, as explained previously
(see Section 5.1), some extra switch cases that only increase the
program counter were introduced in order to facilitate the proof.
Another advantage of this approach is that it differentiates our
obfuscation from similar ones, and thus makes it more difficult to
detect during reverse engineering (i.e. we generate a more complex
CFG). A drawback is that it increases the size of the generated code.
Furthermore, instead of numbering the cases of the switch
statement from 1 to n as demonstrated by the examples, our trans-
formation is actually parametrized by a numbering function ϕ over
32-bit machine integers (0 to 2
32
−1) and a proof that ϕ is injective.
So the cases are numbered by ϕ(1), . . . , ϕ (n), thus improving the
obfuscation of the generated code. Simple examples of such func-
tions are the identity function, shift functions (n 7→ n + k mod 2
32
)
and the reflection function (n 7→ 2
32
− 1 − n).
To evaluate our CFG flattening transformation, we applied it
on the set of programs of the CompCert test suite. The size of
its larger program is 2233 lines of C code. We also tested our
transformation on two common programs that could benefit from
program obfuscation: 6pack.c which is the example compressor
program from the FastLZ library, and an implementation of the
LZW algorithm [21] that is widely used in many programs such
as FFmpeg or LibTIFF.
Table 1 records the computation times necessary to execute
programs before and after the CFG flattening transformation. We
ran our experiments on a MacBook Pro with a 2,5GHz i5 processor
and 8GB RAM. As always with non-trivial obfuscations, our CFG
flattening increases the size of the program and thus slows down
its execution. Table 1 shows also the slowdown ratio between both
programs.
Moreover, we compared our CFG flattening with the CFG flat-
tening implemented in Obfuscator-LLVM, an obfuscator integrated
into the LLVM compiler and freely available [11]. Obfuscator-
LLVM is not formally verified and operates over the LLVM inter-
mediate representation, that is a lower-level language than Clight.
There is a noticeable slowdown in the execution time after the
obfuscation with Obfuscator-LLVM as well. Our slowdown ra-
tio ranges from 1.2 to 13.1, and it ranges from 1.1 to 24.6 for
Obfuscator-LLVM.
When running our first experiments, we realized that the per-
formance of our obfuscator was not as good as expected, due to
a number of skip statements that are generated by CompCert
before our obfuscation pass. Indeed, the first pass of CompCert
translates CompCert C programs into Clight programs by mak-
ing them deterministic: it operates over non-deterministic programs
and pulls side-effects out of expressions and fixes an evaluation
order. The correctness proof of this pass is the most challenging
one among CompCert proofs. A trick used to facilitate this proof is
the use of skip statements to materialize evaluation steps of non-
deterministic C expressions.
As a consequence, this pass generates at least one skip state-
ment for each expression of the initial program. These skip state-
ments do not change the performance of the generated assembly
code, as they do not correspond to any assembly instruction. How-
ever, they are transformed by our obfuscator, which slowdowns the
performance of the assembly generated code. To overcome this
problem, we added and formally verified in Coq a pass that re-
moves these skip statements before our obfuscation pass. More
precisely, it transforms all skip;s sequences of statements into s.
Its correctness proof also relies on a simulation diagram involving
a matching relation between program states that is much simpler
that the match_states relation we defined in this paper.
In Table 1, the two columns numbered (4) and (5) correspond
to the execution of the skip-elimination pass followed by our ob-
fuscation. The last column in Table 1 shows the slowdown ratio be-
tween Obfuscator-LLVM and our obfuscator. The results are very
encouraging as Obfuscator-LLVM is not formally verified. Our ob-
fuscator is better when this ratio is greater than one. This is the
case for 14 of our 24 test programs. Among the 8 remaining pro-
grams, our obfuscator has similar execution times on 3 programs,
and is slower on 5 programs. The slowest program is only about
four times slower. We think that these discrepancies are mainly
due to the fact that the obfuscations of Obfuscator-LLVM are per-
formed over a lower level language than Clight, and thus after the
optimization passes of the LLVM compiler.
8. Related Work
CFG flattening first appears in Chenxi Wang’s Ph.D. thesis [20].
It has since become a widely used obfuscation technique, that
is implemented in several advanced obfuscators, including com-
mercially available obfuscators operating over C programs (e.g.
[7, 8, 11, 16]). None of these tools is formally verified.
The idea of proving the correctness of obfuscation transforma-
tions was first mentioned by Drape et al. in [10], where the authors
present a framework to specify and prove the correctness of imper-
ative data obfuscations (mainly variable renaming, variable encod-
ing and array splitting). This work formalizes basic obfuscations
that do not change the control flow of programs, and operate over
a toy imperative language defined by a big-step semantics. More-
over, the correctness proof is a refinement proof, and it is only a
paper-and-pencil proof.
To the best of our knowledge, the only work that presents a
mechanized proof of code obfuscation transformations is [4]. The
authors define and prove correct a few basic obfuscations using the
Coq proof assistant. The obfuscations operate over a toy imperative
language defined by a big-step semantics. The proof of correctness
relies on non-standard semantics called distorted semantics. The
main idea of this work is to show that it is possible to devise from
the correctness proofs a qualitative measure the potency of these
obfuscations.
Our work is integrated into the CompCert formally verified
compiler [12] and operates over the Clight language of Comp-
Cert. It is a realistic compiler that relies on different intermediate
languages and program transformations. The first formally veri-
fied transformation operating over Clight was the first the compiler
pass (of the very early CompCert compiler) generating Cminor pro-
grams [6]. This pass was redesigned since and split into 2 passes re-
lying on a new intermediate language between Clight and Cminor.
Since the publication of this work 9 years ago, the Clight language
and its semantics became more complex [14].