La protection des programmes contre le reverse engineering

La fonction d'obfuscation permet de lutter contre les tentatives de rétro-ingénierie. Elle repose sur un jeu de transformations appliquées aux sources, de l'insertion de code mort à la virtualisation.

Un logiciel révèle beaucoup d'informations, en particulier sur la manière dont il fonctionne. Toute l'intelligence et l'investissement nécessaires à sa réalisation, aussi bien des algorithmes critiques que des données névralgiques, sont exposés à ceux qui les analysent. Il est néanmoins possible de ralentir ces tentatives d'analyse, l'idéal étant même de rendre le coût de cette dernière rédhibitoire pour un éventuel attaquant.

En termes de protection, un processus, ou fonction d'obfuscation, peut se définir comme étant une transformation appliquée au code en vue de le rendre inintelligible tout en préservant sa sémantique. Vue sous forme de boîte noire, une fonction f et la même fonction obfusquée O(f), sont parfaitement équivalentes. La fonction d'obfuscation repose sur un jeu de transformations appliquées au code, en voici quelques unes :

  Insertion de code mort : Il s'agit d'insérer dans le binaire, du code qui ne sera jamais exécuté, c'est la raison pour laquelle on parle de code mort. Pour aboutir à ce résultat, des prédicats biaisés peuvent être utilisés : une condition qui ne sera jamais satisfaite à l'exécution du programme.
 Insertion de patterns non-impactant : chaque pattern a un effet nul, par exemple l'ajout puis la soustraction d'une même constante. La force de cette technique réside d'une part dans la qualité des patterns, mais aussi et surtout dans leur nombre ; l'idée étant de noyer le code réel sous du code inutile.
 Utilisation de machine virtuelle : consiste à virtualiser le code, en le convertissant dans le code machine d'un processeur virtuel implémenté de manière logicielle.

La plupart de ces techniques nécessitent d'être appliquées massivement afin d'être efficaces. Ce point peut être critique sur des systèmes disposant de peu de ressources processeur et mémoires, cas typique des systèmes embarqués. De plus certaines normes interdisent par exemple la présence de code mort.

Les packers : une enveloppe destinée à protéger l'intelligence mise dans le logiciel sans en empêcher en fonctionnement

Enfin nous pouvons parler des packers. Il s'agit d'une sorte d'enveloppe destinée à protéger l'intelligence mise dans le logiciel sans en empêcher en fonctionnement. D'une manière simple, ils tentent d'apporter une contre-mesure à chacune des techniques de reverse-engineering :

 Compression/chiffrement contre l'analyse statique.

 Obfuscation, fractionnement du code, morcellement du flot d'exécution pour dégrader la lisibilité du code.

 Techniques anti-debugging (détection des outils les plus fréquemment utilisés) s'opposent à l'analyse dynamique.

Tout comme on protège un réseau à l'aide de différents composants (routeurs, pare-feux, et autres), l'architecture des packers est cruciale. En effet, chaque mesure prise séparément est relativement inopérante, alors qu'une combinaison astucieuse se révèle beaucoup plus pertinente. Toutefois, un packer est un logiciel "comme les autres", ils ont, eux aussi, été étudiés et, pour la plupart il est possible de trouver sur Internet des moyens de les contourner, voire de les retirer complètement.

Pour résumer la protection parfaite est une utopie, une protection logicielle est un équilibre précaire entre l'environnement du programme à protéger, le coût de la protection, et la criticité de l'information à protéger.

Tribune réalisée par Edouard Jeanson.

Edouard Jeanson est Directeur de l'Agence Sogeti/ESEC.