La ROP, return-oriented programming, est une technique d'exploitation avancée de type dépassement de pile (stack overflow) permettant l'exécution de code par un attaquant et ce en s'affranchissant plus ou moins efficacement des mécanismes de protection tels que l'utilisation de zones mémoires non-exécutables (cf. bit NX pour Data Execution Prevention, DEP), l'utilisation d'un espace d'adressage aléatoire (Address Space Layout Randomization, ASLR) ou encore la signature de code[1].
Cette technique permet à un attaquant d'obtenir le contrôle de la pile d'exécution (call stack) d'un programme, d'en rediriger les flux et, in fine, exécuter une suite de courtes instructions situées en zone mémoire exécutable, appelées « gadgets »[2].
Chaque gadget est typiquement suivi d'une instruction ret et localisé dans une routine d'un autre programme ou dans le code d'une bibliothèque dynamique chargée en mémoire. Une fois assemblés, ces gadgets forment une chaîne généralement appelée ROP chain[3] et permettent à un attaquant d'effectuer des opérations arbitraires sur une machine employant pourtant des mécanismes de protection, là où une attaque simple aurait été déjouée.
Contexte
Généralement, ce type d'attaque est issu d'une manipulation de la pile d'exécution (call stack) en exploitant un défaut dans la conception d'un programme, notamment un dépassement de tampon (buffer overrun). Dans une attaque par dépassement de tampon, une fonction ne procédant pas aux contrôles relatifs à la taille des données fournies par l'utilisateur avant de les stocker en mémoire est susceptible d'accepter plus de données qu'elle n'est dûment capable de stocker. Les données étant écrites sur la pile d'exécution, le surplus de données dépassant la zone allouée est ainsi susceptible d'écraser la zone allouée aux variables de la fonction (e. g. « Variables locales » sur le diagramme de la pile d'exécution à droite), mais aussi d'écraser l'adresse de retour de la fonction.
Une fonction pouvant être appelée plusieurs fois et depuis plusieurs emplacements, y-compris depuis d'autres fonctions, durant la phase d'exécution d'un programme (on parle de fonction active), il est nécessaire d'enregistrer dans la pile d'exécution, l'adresse de l'instruction d'appel (caller). Cette adresse stockée sur la pile d'exécution est dynamique et réécrite à chaque appel de la fonction avant redirection du flux de contrôle et permet d'indiquer où chaque fonction active doit retourner à la fin de son cycle d'exécution. Une fois le cycle d'exécution terminé, la fonction récupère l'adresse de retour sur la pile d'exécution et redirige le flux de contrôle vers la fonction d'appel. Ainsi, si cette adresse est écrasée, le flux de contrôle peut être détourné vers une instruction arbitraire et définie par la nouvelle adresse de retour.
Dans une attaque standard par dépassement de tampon, l'attaquant aurait simplement procédé à l'injection d'une charge utile (payload) sur la pile d'exécution et réécrit l'adresse de retour avec l'adresse de ces nouvelles instructions. Jusqu'à la fin des années 1990, la majorité des systèmes d'exploitation n'offraient aucune protection contre ce type d'attaque ; notamment Microsoft Windows jusqu'en 2004[4]. À terme, les systèmes d'exploitation commencèrent à combattre l'exploitation de dépassements de tampon en marquant des zones mémoires comme non-exécutables, technique connue sous le nom de prévention de l'exécution des données (Data Execution Prevention, DEP). Une fois ce dispositif activé, l'ordinateur refuse l'exécution de code situé dans une zone mémoire inscriptible par l'utilisateur, empêchant l'attaquant de placer sa charge utile sur la pile d'exécution et d'y sauter (jump) au moyen d'un écrasement de l'adresse de retour. Le support matériel pour la prévention de l'exécution des données fut par la suite implémenté afin de renforcer cette protection.
Technique par retour en bibliothèque
L'implémentation généralisée de la prévention de l'exécution des données a rendu l'exploitation standard du dépassement de tampon difficile, voire impossible tel qu'expliqué ci-dessus. Ainsi un attaquant se voit limité à l'utilisation d'instructions déjà en mémoire et marquées comme exécutables, telles que le code du programme lui-même et des bibliothèques dynamiques liées. Étant donné que les bibliothèques dynamiques, telles que LIBC, contiennent généralement des appels systèmes et autres fonctions susceptibles de susciter l'intérêt des attaquants, elles sont les plus utilisées pour la recherche d'instructions afin d'assembler la ROP chain.
Dans une attaque par retour en bibliothèque, l'attaquant redirige le flux de contrôle en exploitant une vulnérabilité par dépassement de tampon, tel qu'expliqué ci-dessus. Au lieu d'essayer d'écrire une charge utile sur la pile d'exécution, l'attaquant utilise les fonctions des bibliothèques disponibles et écrase l'adresse de retour avec celle de son point d'entrée. D'autres emplacements de la pile d'exécution sont ensuite écrasés, en respectant la convention d'appel de fonction, de telle sorte que les arguments propres à chaque fonction soient passés afin d'effectuer les opérations souhaitées par l'attaquant. Cette technique a été présentée tout d'abord par Solar Designer en 1997[5] et a ensuite évolué afin de permettre la construction d'une ROP chain illimitée[6].
Réutilisation de code
Avec la montée des processeurs 64-bit x86, une modification de la convention d'appel de fonction a été faite afin d'exiger que le premier argument passé à une fonction soit stocké dans un registre de processeur et non plus sur la pile d'exécution. Ceci impliquait qu'un attaquant ne pouvait plus mettre en place un appel de fonction avec les paramètres souhaités, simplement en manipulant la pile d'exécution par exploitation d'un dépassement de tampon. Les développeurs de bibliothèques commencèrent également à retirer ou restreindre les fonctions intéressantes pour un attaquant, telles que les appels systèmes. Par conséquent il était de plus en plus difficile de réussir une attaque par retour en bibliothèque.
Une nouvelle évolution à cette technique s'est ainsi développée en n'utilisant qu'une partie des instructions composant ces fonctions, au lieu de fonctions complètes, permettant à nouveau l'exploitation de vulnérabilités par dépassements de tampon[7]. Cette technique recherche des fonctions contenant des séquences d'instructions permettant le déplacement de valeurs situées sur la pile d'exécution dans un registre de processeur (pop). Une sélection minutieuse de ces séquences d'instruction permet à un attaquant de placer les valeurs souhaitées dans les registres de processeur appropriés afin d'effectuer un appel de fonction arbitraire sous cette nouvelle convention d'appel. Le reste de l'attaque se déroule comme une attaque standard par retour en bibliothèque.
Attaques
La programmation orientée retour repose sur l'approche par fragmentation de code et l'étend afin de fournir une approche Turing-complet à l'attaquant, y-compris les boucles et les branchements conditionnels[8],[9].
Autrement dit, la programmation orientée retour fournit un « langage » entièrement fonctionnel qu'un attaquant peut exploiter pour faire exécuter par une machine compromise, n'importe quelle opération. Hovav Shacham publia la technique en 2007[10] et démontra comment les œuvres les plus importantes issues de la programmation standard peuvent être simulées en utilisant le concept de programmation orientée retour contre une application liée à une bibliothèque standard de C et contenant une vulnérabilité exploitable de type dépassement de tampon.
Une attaque utilisant la programmation orientée retour est supérieure à tout autre type d'attaque tant pour son expressivité que pour sa résistance aux mesures défensives. Aucune technique de contre-exploitation mentionnée ci-dessus, ni même la suppression de fonctions dangereuses au sein des bibliothèques dynamiques ne semblent efficaces contre cette attaque.
Architecture x86
Bien que les attaques utilisant la programmation orientée retour peuvent être lancées sur une multitude d'architectures matérielles[10], l'article de Shacham et la plupart des suivants se concentrent sur l'architecture x86 d'Intel. L'architecture x86 est un microprocesseur à jeu d'instructions étendu (cf. CISC). La programmation orientée retour sur x86 tire profit du fait que les jeux d'instructions soient très « denses », ainsi une séquence aléatoire d'octets est interprétable comme un jeu d'instruction x86 valide.
Il est ainsi possible de rechercher un code machine (opcode) permettant la modification du flux de contrôle, notamment l'instruction return (valeur hexadécimale0xC3), puis rechercher des instructions potentiellement utiles dans les octets précédant cette instruction. Ces jeux d'instruction appelés gadgets peuvent ainsi être chaînés par écrasement de l'adresse de retour, au moyen de l'exploitation de dépassement de tampon, avec l'adresse de la première instruction du premier gadget. La première adresse des gadgets suivant composant la ROP chain est écrite successivement sur la pile d'exécution et enchaînés un-à-un. À l'issue de l'exécution du premier gadget, une instruction de retour (return) est exécutée et permettra d'extraire l'adresse du prochain gadget de la pile d'exécution et sauter jusqu'à lui. À la fin de ce gadget, la chaîne continue avec le troisième et ainsi de suite. En chaînant ces petits jeux d'instructions, un attaquant est en mesure de créer un comportement arbitraire au programme en utilisant des codes de bibliothèques dynamiques pré-chargées en mémoire. Shacham affirme qu'étant donné le nombre important de codes (incluant, mais ne se limitant pas aux bibliothèque dynamique de C), suffisamment de gadgets existent pour permettre d'offrir des fonctionnalités Turing-complet[10].
Un outil automatique a été développé afin d'offrir une automatisation du processus de recherche de gadgets et de préparation d'une attaque ROP contre un programme[11]. Cet outil, connu sous le nom de ROPgadget, recherche dans un programme, des gadgets potentiellement exploitables et tente de les assembler en créant une ROP chain en permettant l'ouverture d'un shell interactif et l'exécution de commandes arbitraires par l'attaquant.
Contre-mesures / contre-exploitations
Un certain nombre de techniques ont été proposées pour contrer les attaques basées sur la programmation orientée retour[12]. La plupart se contentent d'une randomisation de l'emplacement du programme et des bibliothèques dynamiques sur la pile d'exécution, de telle sorte que l'attaquant ne soit pas en mesure de prédire avec précision la localisation d'une instruction pouvant être utile dans un gadget et ne puisse pas monter de ROP chain valide. Une des implémentations majeures de cette technique, Address Space Layout Randomization (ASLR), charge les bibliothèques dynamiques dans un emplacement mémoire différent à chaque exécution d'un programme. Bien que largement déployé sur les systèmes d'exploitation modernes, ASLR est vulnérable aux attaques par fuite de données et d'autres approches permettant de déterminer l'adresse de n'importe quelle fonction de bibliothèque dynamique en mémoire. Si un attaquant parvient à déterminer l'emplacement d'une instruction connue, la position de toutes les autres peut être déduite et une attaque ROP construite.
Cette approche de randomisation peut aller plus loin en déplaçant toutes les instructions du programme séparément[13]. Cela nécessite un support d'exécution approfondie, tel qu'un traducteur dynamique de programme, afin de ré-assembler les instructions randomisées au démarrage. Cette technique aboutit à une difficulté supplémentaire pour la recherche et l'utilisation de gadgets, mais apporte une charge supplémentaire significative.
Une autre approche, adoptée par kBouncer, consiste à modifier le système d'exploitation de telle sorte qu'il vérifie si les instructions de retour (return) détournent de manière effective le flux de contrôle vers un emplacement situé immédiatement après l'instruction d'appel (caller). Ceci permet d'empêcher le chaînage de gadgets, mais apporte également une lourde charge supplémentaire dans le processus d'exécution d'un programme et n'est pas efficace contre les attaques basées sur la programmation orientée saut, modifiant les sauts (jump) et autres instructions de redirection du flux de contrôle plutôt que les retours[14].
↑(en) Hovav Shacham, Erik Buchanan, Ryan Roemer et Stefan Savage, Proceedings of the 15th ACM conference on Computer and communications security - CCS '08, , 576 p. (ISBN978-1-59593-810-7, DOI10.1145/1455770.1455776, lire en ligne), « When Good Instructions Go Bad: Generalizing Return-Oriented Programming to RISC », p. 27–38
↑(en) Nemo, « Modern Objective-C Exploitation Techniques », Phrack, no 69, (lire en ligne)
↑(en) Nergal, « The advanced return-into-lib(c) exploits », Phrack, no 58, (lire en ligne)
↑(en) Sebastian Krahmer, X86-64 buffer overflow exploits and the borrowed code chunks exploitation technique, (lire en ligne)
↑(en) M. N. Abadi, M. Budiu, Ú. Erlingsson et J. Ligatti, Proceedings of the 12th ACM conference on Computer and communications security - CCS '05, (ISBN978-1-59593-226-6 et 1-59593-226-7, DOI10.1145/1102120.1102165), « Control-Flow Integrity: Principles, Implementations, and Applications », p. 340–353
↑(en) M. N. Abadi, M. Budiu, Ú. Erlingsson et J. Ligatti, Control-Flow Integrity : Principles, Implementations, and Applications, vol. 13, ACM Transactions on Information and System Security, (DOI10.1145/1609956.1609960), p. 1
↑ ab et c(en) H. Shacham, Proceedings of the 14th ACM conference on Computer and communications security - CCS '07, (ISBN978-1-59593-703-2, DOI10.1145/1315245.1315313), « The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86) », p. 552–561
↑(en) R. Skowyra, K. Casteel, H. Okhravi, N. Zeldovich et W. Streilein, Research in Attacks, Intrusions, and Defenses, vol. 8145, (ISBN978-3-642-41283-7, DOI10.1007/978-3-642-41284-4_5, lire en ligne), « Systematic Analysis of Defenses against Return-Oriented Programming », p. 82–102
↑(en) J. Hiser, A. Nguyen-Tuong, M. Co, M. Hall et J.W. Davidson, 2012 IEEE Symposium on Security and Privacy, (ISBN978-1-4673-1244-8, DOI10.1109/SP.2012.39), « ILR: Where'd My Gadgets Go? », p. 571–585