SSTIC 2013 - Mise à plat de graphes de flots de contrôle
par Eloi Vanderbeken (@elvanderb)
(edit: article et slides)
Plusieurs techniques de brouillage de code existent pour le rendre difficile à reverser. Bas niveau, on ajoute du code mort, on remplace des instructions par d’autres etc… Coté haut niveau, on joue sur des variables globales, on transforme la façon d’exécuter le code, etc…
Le flot de contrôle une fois obfusqué ne ressemble plus au graphe habituel, mais à un alignement de pleins de basic block avec un gestionnaire pleins de conditions et de tests pour rediriger le flot de contrôle vers les bon basic blocks.
Pour simplifier ce graph, plusieurs techniques existent. On peut par exemple tracer l’exécution de la fonction et enregistrer l’enchaînement des basic blocs. C’est rapide mais si on trace trop longtemps on retombe sur la structure initiale. On peut aussi travailler avec une logique de flot de données, on s’abstrait alors totalement du code, ce qui n’est pas idéal sur les fonctions qui effectuent beaucoup de calculs mathématiques. Il est possible de travailler par analyse statique en considérant l’ensemble des données possibles.
L’auteur à choisi une approche statique par exécution symbolique du code pour identifier les blocs de base. Lorsque le gestionnaire est identifié, on trace de la sortie du gestionnaire à la re-entrée dans le gestionnaire, ce qui permet d’identifier chaque bloc de base. Pour ne pas trop tracer, on utilise l’exécution symbolique pour déterminer les plages de valeur des variables utilisés dans les blocs de base. On se débarrasse ensuite des gestionnaires et on obtiens un joli graph.
Sauf qu’en pratique ça ne marche pas, parce qu’il y a plein de problèmes concrets à résoudre: identification des gestionnaires, identification des autres basic bloc en fonction du contexte, etc…
L’extraction des blocs de base s’est fait avec IDA.
L’implémentation de l’exécution symbolique c’est faite avec Z3, un solveur de contraintes de chez Microsoft. à l’aide de plusieurs heuristiques, l’auteur élimine les valeurs symboliques peu probables, et en exécutant les traces symboliquement, il simplifie les valeurs possibles qui peuvent être prise par les variables.
Au final ça marchouille (dixit l’orateur)