CFI-C

Overview

CFI-C is a set of python programs that helps to inject control flow countermeasures into a C source code. These countermeasures enforce the flow of execution of the program and can defeat any jump attack of 2 C lines that would disrupt the normal flow of execution.

This project refers to the implementation of the papers:

News

Demonstration video

Contributors

Main developers

  • Jean-Francois Lalande
  • Pascal Berthomé
  • Karine Heydemann
  • Asmae El Farkh
  • Nicolas Audiot
  • Arnaud Schorr
  • Bénédicte Augustin
  • Morgan Follier

Prototypes and tests

  • Grégoire Poinsot
  • Pierre-Yves Robinet
  • Thomas Fécherolle
  • Nicolas Guilloteau
  • Pierre-Olivier Vauboin
  • Benoit Leriche
  • Thimothée Ravier
  • Fatima-Zahra Bouam
  • Victoire Dumand
  • Marion Ougier
  • Fatine Assoudi
  • Pierre Andrieux
  • Gregory Baudeau

Howto

Getting source code

Requirements

python 2.7
gcc
gcov
ploticus

Python requirements:

# Remove any pycparser distro package
sudo apt-get remove python-pycparser
# Install old version of pycparser
apt-get install python-pip
pip install pycparser==2.04

Getting sources

Clone the repository

git clone git+ssh://login@scm.gforge.inria.fr/gitroot/cfi-c/cfi-c.git
cd cfi-c
cd CParser

Environment

export PYTHONPATH=`pwd`:src/

Simulating physical jump attacks on AES

In this section, we show how to execute a campaign of physical attacks simulated by inserting and executing all possible jump attacks on each function of an implementation of AES. On the figure below, it coresponds to the three blue boxes.

principle_again2-01.png

Preparing input sources and output folders

If you use your own input source files you have to clean them. Each function should be indented carefully because we need to insert some new lines in the source code. Thus, a function that looks like:

type my_function(type p1, typep2, ...) { // Bracket should go on next line
   statement0; statement1; // One statement per line
   if (cond1) { // Bracket should go on next line
     statement3;
     while (cond2) { // Bracket should go on next line
        statement4;
     }
     if (cond3) // No conditional without brackets !
       statement5;
     } // Here several closing brackets:
   }
 }

should be cleaned to be of the form:

type my_function(type p1, typep2, ...)
{
   statement0;
   statement1;
   if (cond1)
   {
     statement3;
     while (cond2)
     {
        statement4;
     }
     if (cond3)
     {
       statement5;
     }
     int zzzz1; // artifact to have a statement between the two }
   }
   int zzzz2; // artifact to have a statement between the two }
 }

Note the artifacts that helps the parser to find the points between two closing brackets. If you look at the sources of cleaned_src/aes/ you will see that this work has already be done for AES. Typically, the code of AES looks like:

void aes_addRoundKey_cpy(uint8_t *buf, uint8_t *key, uint8_t *cpk)
{
  register uint8_t i = 16;

  while (i--)
  {
    buf[i] ^= key[i];
    cpk[i] = key[i];
    cpk[16+i] = key[16 + i];
  }
  int jfl = 1;
} /* aes_addRoundKey_cpy */

After getting the source code of CFI-C, go to the CParser directory and prepare the output folders that will receive the new .c file.

mkdir out
cd out
mkdir aes
cd aes
ln -s ../../cleaned_src/aes/aes256.h
cd ../..

Generating attack injection codes

By calling:

python2.7 src/analyzer/inject_attacks.py --filename cleaned_src/aes/aes256_jflaccolades.c --out aes

The sources of cleaned_src/aes/aes256_jflaccolades.c are processed and simulated attacks are injected. A set of new files out/aes/aes256_jflaccolades_c-FUNCTION.c are produced:

jf@noyal:~/git/cfi-c/CParser$ python2.7 src/analyzer/inject_attacks.py --filename cleaned_src/aes/aes256_jflaccolades.c --out aes
No file cleaned_src/aes/aes256_jflaccolades.h to copy.
GCOV: Compiling.
GCOV: Executing.
Original execution time: 0.00178289413452 and return code: 0
GCOV: Generating GCOV.
File 'aes256_jflaccolades.c'
Lines executed:100.00% of 243
Creating 'aes256_jflaccolades.c.gcov'

GCOV: extra compiling without GCOV.
Helps with blowfish that seems impacted with the introduction of gcov. Restore an clean .exe
WARNING: Rupture de sequence, on est MAL !<pycparser.c_ast.Break object at 0x7fbf3e2dab50>
Coordinates : cleaned_src/aes/aes256_jflaccolades.c:141
Generating file for function DUMP
Generating file for function gf_alog
Generating file for function gf_log
Generating file for function gf_mulinv
Generating file for function rj_sbox
Generating file for function rj_sbox_inv
Generating file for function rj_xtime
Generating file for function aes_subBytes
Generating file for function aes_subBytes_inv
Generating file for function aes_addRoundKey
Generating file for function aes_addRoundKey_cpy
Generating file for function aes_shiftRows
Generating file for function aes_shiftRows_inv
Generating file for function aes_mixColumns
Generating file for function aes_mixColumns_inv
Generating file for function aes_expandEncKey
Generating file for function aes_expandDecKey
Generating file for function aes256_init
Generating file for function aes256_done
Generating file for function aes256_encrypt_ecb
Generating file for function aes256_decrypt_ecb
Generating file for function main
The code contains 22 functions.
Generated file: cleaned_src/aes/aes256_jflaccolades.c
Finished.

23 C files should have been generated.

For example, for the function aes_addRoundKey_cpy, the corresponding file looks like:

/* -------------------------------------------------------------------------- */
void aes_addRoundKey_cpy(uint8_t *buf, uint8_t *key, uint8_t *cpk)
{
int local_transient = 0;
#ifdef ATTACK_DESTINATION239
attack239: ; // GCOV=1
#endif
#ifdef ATTACK_SOURCE239
if (!glob_transient && local_transient == ATTACK_TRANSIENT_ROUND) { glob_transient = -1;
fprintf(stderr,"Jumping to: %i\n",239);
goto ATTACK_SOURCE239;
} else { if (!glob_transient) local_transient++; }
#endif
    register uint8_t i = 16;
...

A first execution of the program have been monitored using GCOV. Thus, at this stage it sould be possible to execute the produced binary:

cd out/aes
./aes256_jflaccolades.exe
txt: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff
key: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
     10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
---
enc: 8e a2 b7 ca 51 67 45 bf ea fc 49 90 4b 49 60 89
tst: 8e a2 b7 ca 51 67 45 bf ea fc 49 90 4b 49 60 89
dec: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff

The output will be used to distinguish a Wrong Answer from an Effect Less attack.

Running an attack campaign

Running an attack campaign consists in compiling and executing each file and triggering attacks for each possible moment. Because of the number of files, a campaign runs in parallel multiple files. This can be adjusted by editing the file perform_attacks_against_all_files.py and by changing X in the line range(X). For each run, the output of the run is compared to the reference output.

The parameter of this script is the name of the subdirectory of out where to find the files. For AES it should be:

python2.7 src/analyzer/perform_attacks_against_all_files.py --out aes
Launching: aes_expandEncKey

Launching: aes_expandDecKey
Launching: main

Launching: aes256_encrypt_ecb
...
Finished: aes256_done
Finished: gf_log
Finished: gf_alog
 ALL DONE !
1109.32707
Simulation time: 0:18:29.327070

Errors could appear on the screen: because of the modification done to the source code, the program can trigger signals or end with errors.

Exploiting results

A script enables to produce attack graphs for all output logs. The script requires to have ploticus installed.

cd out/aes
ln -s ../../ploticus/prepare.sh
bash prepare.sh

For example, for the aes_addRoundKey_cpy we obtain:

out-aes_addRoundKey_cpy.datu_heatmap.png

Securizing AES with software countermeasures

In this section we show how to add countermeasures to the source code of AES. Then, we can perform again the test campaign for simulating physical attacks. On the figure below, the injection of countermeasures corresponds to the three blue boxes.

principle_again2-02.png

Preparing output folders

The generated source code containing countermeasures should go to the subdirectory out-cfi/aes. Prepare this subfolder:

mkdir out-cfi
cd out-cfi
mkdir aes
cd ..

Injecting countermeasures in AES

python2.7 src/cfi/inject_cfi.py --filename cleaned_src/aes/aes256_jflaccolades.c --out aes
Checking inside Basic Blocks (option --check_in_BB): False
WARNING: Rupture de sequence, on est MAL !<pycparser.c_ast.Break object at 0x7f41056c1b50>
Coordinates : cleaned_src/aes/aes256_jflaccolades.c:141
Creating counter for a For CNT_1___DUMP
Creating artefact CNT_1___DUMPNBEND0
Creating artefact *CNT_0___DUMPNBENDFUNC with value: 56
...

If you have a warning Rupture de sequence, on est MAL ! this means that you have a break in your source code. As this statement is not handled, you should remove it manually. Counters CNT_X___DUMP... are created and inserted in the original source code.

If you want to activate checks inside basic blocks, you can use the option --check_in_BB:

python2.7 src/cfi/inject_cfi.py --filename cleaned_src/aes/aes256_jflaccolades.c --out aes  --check_in_BB
Checking inside Basic Blocks (option --check_in_BB): True
WARNING: Rupture de sequence, on est MAL !<pycparser.c_ast.Break object at 0x7f41056c1b50>
Coordinates : cleaned_src/aes/aes256_jflaccolades.c:141
Creating counter for a For CNT_1___DUMP
Creating artefact CNT_1___DUMPNBEND0
Creating artefact *CNT_0___DUMPNBENDFUNC with value: 56

3 files should have been generated in out-cfi/aes:

  • aes256_jflaccolades.c: the original source file
  • aes256_jflaccolades_CFI.c: the new source file including countermeasures
  • aes256_jflaccolades_CFI.h: the definition of the initial values of counters

As done previously, we need the needed complementary .h of this program in order to have all files to compile this new source code.

cd out-cfi/aes/
cp ../../cleaned_src/aes/aes256.h .

Edit the .h and patch each prototype by adding a parameter of type "unsigned short *": vi aes256.h

For example:

void aes256_init(aes256_context *, uint8_t * /* key */);
void aes256_done(aes256_context *);
void aes256_encrypt_ecb(aes256_context *, uint8_t * /* plaintext */);
void aes256_decrypt_ecb(aes256_context *, uint8_t * /* cipertext */);

becomes:

void aes256_init(aes256_context *, uint8_t * /* key */, unsigned short *);
void aes256_done(aes256_context *, unsigned short *);
void aes256_encrypt_ecb(aes256_context *, uint8_t * /* plaintext */, unsigned short *);
void aes256_decrypt_ecb(aes256_context *, uint8_t * /* cipertext */, unsigned short *);

You should be able to compile aes256_jflaccolades_CFI.c:

gcc -o prog aes256_jflaccolades_CFI.c ../../inclusion_err_hack/err_hack.c

./prog
txt: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff
key: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
     10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
---
enc: 8e a2 b7 ca 51 67 45 bf ea fc 49 90 4b 49 60 89
tst: 8e a2 b7 ca 51 67 45 bf ea fc 49 90 4b 49 60 89
dec: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff

Simulating physical jump attacks on the securized AES

Now, we can redo the simulation of attack campaign on the securized version of AES. This corresponds to the blue boxes below:

principle_again2-03.png

Generating attack injection codes

First, we should prepare again the output folder for the source code where injected attack will be included:

mkdir out/aes-CFI
cd out/aes-CFI/
ln -s ../../out-cfi/aes/aes256.h
cd ../..

Then, simulation source code should be generated:

python2.7 src/analyzer/inject_attacks.py --filename out-cfi/aes/aes256_jflaccolades_CFI.c --out aes-CFI
Copying out-cfi/aes/aes256_jflaccolades_CFI.h to out/aes-CFI
GCOV: Compiling.
out/aes-CFI/aes256_jflaccolades_CFI.c: In function ‘DUMP’:
out/aes-CFI/aes256_jflaccolades_CFI.c:68:104: warning: implicit declaration of function ‘killcard’ [-Wimplicit-function-declaration]
         CNT_1___DUMPfor = !(CNT_1___DUMPfor == 0 || CNT_1___DUMPfor == CNT_1___DUMPNBEND0) ? killcard()
                                                                                              ^
GCOV: Executing.
Original execution time: 0.00292086601257 and return code: 0
GCOV: Generating GCOV.
File 'aes256_jflaccolades_CFI.c'
Lines executed:100.00% of 846
Creating 'aes256_jflaccolades_CFI.c.gcov'

GCOV: extra compiling without GCOV.
Helps with blowfish that seems impacted with the introduction of gcov. Restore an clean .exe
out/aes-CFI/aes256_jflaccolades_CFI.c: In function ‘DUMP’:
out/aes-CFI/aes256_jflaccolades_CFI.c:68:104: warning: implicit declaration of function ‘killcard’ [-Wimplicit-function-declaration]
         CNT_1___DUMPfor = !(CNT_1___DUMPfor == 0 || CNT_1___DUMPfor == CNT_1___DUMPNBEND0) ? killcard()
                                                                                              ^
WARNING: GOTO DETECTED (probably countermeasure will not work) ! (out-cfi/aes/aes256_jflaccolades_CFI.c:69
WARNING: GOTO DETECTED (probably countermeasure will not work) ! (out-cfi/aes/aes256_jflaccolades_CFI.c:77
...
Generating file for function DUMP
Generating file for function gf_alog
Generating file for function gf_log
Generating file for function gf_mulinv
Generating file for function rj_sbox
Generating file for function rj_sbox_inv
Generating file for function rj_xtime
Generating file for function aes_subBytes
Generating file for function aes_subBytes_inv
Generating file for function aes_addRoundKey
Generating file for function aes_addRoundKey_cpy
Generating file for function aes_shiftRows
Generating file for function aes_shiftRows_inv
Generating file for function aes_mixColumns
Generating file for function aes_mixColumns_inv
Generating file for function aes_expandEncKey
Generating file for function aes_expandDecKey
Generating file for function aes256_init
Generating file for function aes256_done
Generating file for function aes256_encrypt_ecb
Generating file for function aes256_decrypt_ecb
Generating file for function main
The code contains 22 functions.
Generated file: out-cfi/aes/aes256_jflaccolades_CFI.c
Finished.

Warning about GOTO are normal: the countermeasures have transformed while construct using goto statements.

At this stage it sould be possible to execute the reference execution file (that includes countermeasures):

cd out/aes-CFI/
./aes256_jflaccolades_CFI.exe
txt: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff
key: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
     10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f
---
enc: 8e a2 b7 ca 51 67 45 bf ea fc 49 90 4b 49 60 89
tst: 8e a2 b7 ca 51 67 45 bf ea fc 49 90 4b 49 60 89
dec: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff

23 C files should have been generated.

Running the attack campaign

As done previously for the non secured version of the code, the attack campaign is launched by calling this script with the folder containing the source of the code to consider:

python2.7 src/analyzer/perform_attacks_against_all_files.py --out aes-CFI
Launching: main
Launching: aes_expandEncKey
Launching: aes_expandDecKey
Launching: aes256_encrypt_ecb
...
Finished: rj_xtime
Finished: aes_addRoundKey
Finished: gf_log
Finished: gf_alogALL DONE !

18838.7490039
Simulation time: 5:13:58.749004

If you need to unlog from the server because it is too long:

screen
python2.7 src/analyzer/perform_attacks_against_all_files.py --out aes-CFI
ctrl+a+d (detach terminal from screen)
...
screen -r

Exploiting results

cd out/aes-CFI
ln -s ../../ploticus/prepare.sh
bash prepare.sh
out-aes_addRoundKey_cpy.datu_heatmap-CFI.png

Benchmarking AES and Securized AES

A modified version of AES is available in cleaned_src/aes: it loops 10000 times over the main program of AES in order to have an accurate evaluation of the time consumed by AES. Thus, to evaluate AES:

cd cleaned_src/aes
gcc -o prog aes256_jflaccolades-BENCHMARK.c
time ./prog > /dev/null

real  0m7.701s
user  0m7.696s
sys   0m0.004s

For the securized version of aes256_jflaccolades-BENCHMARK.c, you need to generate the securized version of this source code and then run the benchmark:

# With CHECKS INSIDE BB: +CM
python2.7 src/cfi/inject_cfi.py --filename cleaned_src/aes/aes256_jflaccolades-BENCHMARK.c --out aes --check_in_BB
cd out-cfi/aes/
gcc -o prog aes256_jflaccolades-BENCHMARK_CFI.c ../../inclusion_err_hack/err_hack.c
time ./prog > /dev/null

real  0m18.349s
user  0m18.336s
sys   0m0.000s

# Without CHECKS INSIDE BB: + CMBB
python2.7 src/cfi/inject_cfi.py --filename cleaned_src/aes/aes256_jflaccolades-BENCHMARK.c --out aes
real  0m13.914s
user  0m13.900s
sys   0m0.004s

Thus, we have the folllowing execution times:

  x86 Overhead
AES 0.77 ms  
AES + CM 1.83 ms +138%
AES + CMBB 1.39 ms +81%

Using the GUI for browsing results

A GUI has been developed to browse the results. It shows the source code and enables to select an attack and see the code that is jumped (backward or foward). To launche the GUI, use:

python2.7 src/gui/viewer.py --help
with arguments:
--dir dir    the directory where the files are
--cfile out  the original c file

For example, for browsing the results for aes:

python2.7 src/gui/viewer.py --dir out/aes --cfile aes256_jflaccolades.c
gui01.png

The upper left window lists all functions. Below, each attack is listed (WA, EL, Good, Error, Killcard). Each class of attack can be filtered with the radio button. On the heatmap on the bottom left window, each square can be clicked, which displays the lines that are jumped in the source code on the right window.

Results for the secured version of AES can be browsed either. The black colors indicates that the killcard function has detected an attack:

python2.7 src/gui/viewer.py --dir out/aes-CFI --cfile aes256_jflaccolades_CFI.c
gui02.png