Problématique Internet Explorer est, depuis longtemps, une des cibles préférés des pirates dans le cadre de l’écriture d’exploits. Tout simplement il s’agit du navigateur de référence sous Windows, qui représente la très grande majorité du parc. Malheureusement, plus particulièrement depuis Windows XP SP2, l’écriture d’exploits de type heap overflow devient de plus en plus difficile, de par la mise en place d’un certain nombre de protections. C’est bien souvent dans la heap que vont se produire la plupart des bugs qui peuvent mener à une exploitation réussie. Ce que l’on appelle communément la « heap » ou encore le « tas » est une zone de l’espace d’adressage qui est réservé pour les allocations dynamiques faites pendant l’exécution du programme au travers d’appels à un allocateur mémoire. L’allocateur maintient donc plusieurs listes de blocs de mémoires, et, sans entrer dans les détails, il faut savoir que la structure de chacun de ces blocs est la suivante : Avant le SP2 La partie header permet à l’allocateur de garder, d’organiser et de gérer les différents blocs mémoire. La partie donnée, elle, est la mémoire qui a été demandée par le programme. Depuis Windows XP SP2, un nouveau champ est apparu dans le header, qui a pour but de renforcer le contrôle d’intégrité de la heap, rendant l’exploitation plus difficile. De plus, il effectue des vérifications supplémentaires quand à la cohérence des différents blocs entre eux. Si jamais Windows détecte la corruption d’un des blocs, il va lancer une exception, mettant fin à l’exécution du programme, réduisant à néant nos chances de réussir l’exploitation, et donnant l’alerte. Ces exploitations sont toujours possibles, mais demandent un contrôle de l’état de la heap. La problématique se déplace donc vers le contrôle de l’état de la heap au moment de l’exploitation, facteur crucial pour la réussite d’un exploit. Méthodes Existantes Actuellement, les méthodes efficaces d’exploitation de heap overflow se tournent de plus en plus vers l’écrasement de données du programme lui-même que vers l’exploitation des mécanismes internes de l’allocateur. Au vu des différentes protections, citées ci-dessus, qui ont été mises en place, l’exploitation des mécanismes internes de l’allocateur devient en effet plus compliqué que de tirer partie des données déjà présentes sur la heap et utilisées par le programme ciblé, comme des pointeurs sur fonction ou des structures de données particulières. Pour que l’exploit réussisse, il faut que l’attaquant arrive à détourner le flux de l'exécution, de sorte à exécuter son propre code. Pour ce faire, il va détourner le pointeur d'exécution (EIP) pour l'amener à pointer vers des sections de données qu'il contrôle, plus précisément vers un shellcode, qui est en fait du code exécutable qu’il aura injecté dans la mémoire du programme. Pour maximiser les chances de réussite, une technique assez répandue dénommée « heap spraying » consiste à se répandre au maximum dans la heap du programme exploité, en tirant partie de certaines instruction « inoffensives » (dites ‘NOP’) qui auront pour seul but de maximiser nos chances Précisons la difficulté de l’opération : si le pointeur d’exécution (EIP) pointe dans une zone de données non contrôlée, il y a très fort à parier qu’il s’agisse soit d’instructions invalides ou que le programme plante tout simplement en arrivant ici, car le code assembleur sera incohérent. Par contre si notre pointeur d’exécution pointe dans la série de NOP, alors ces instructions inoffensives seront exécutées et l’EIP finira par arriver à notre shellcode, qui lui aura l’effet escompté. De même si notre pointeur d’exécution tombe au milieu de notre shellcode, alors le programme crashera : il est nécessaire d’exécuter e shellcode depuis sa première instruction. Pour illustrer cela, considérons la situation suivante : Nous avons ici une faible marge d’erreur de 10 instructions maximum pour sauter dans notre shellcode : il existe dix adresses possibles pour le pointeur EIP afin que l’exploitation réussisse. Le risque que le pointeur EIP soit dans une zone invalide (non contrôlée ou au milieu du shellcode) est important. Mais imaginons plutôt la situation suivante, ou le nombre d’instructions NOP est significativement augmenté: Vous réalisez bien que la situation n’est plus la même, nous avons ici une marge d’erreur de 10 Mo (ce qui représente plusieurs millions d’instructions) pour atterrir dans notre la zone de NOP afin d’exécuter le shellcode. Autant dire que les chances statistiques de réussir l’attaque on très sérieusement augmenté. Même si cette technique augmente considérablement les chances de réussite de l’exploit, elle présente un certain nombre de limitations : - On ne peut prétendre remplir tout l’espace mémoire disponible, les chances de tomber sur de la mémoire sous contrôle restent donc limitées. Compromis chances de réussite / Mémoire consommée. Consommation excessive de mémoire signifie perte de performances, ce qui peut alerter la victime. Vers un contrôle affiné de l’état de la mémoire La méthode présentée par Alexander Sotirov à la black hat 2007 (à Amsterdam) propose de tirer un trait sur ces limitations en affinant le contrôle de l’état de la mémoire, rendant donc inutile ce remplissage excessif. Il propose d’utiliser les mécanismes internes du moteur JavaScript de Internet Explorer pour prédire et contrôler l’état de la mémoire. Le concept est le suivant : <script language=’javascript’> Var str1=’AAAAAAAAAAAAAAAAA’ ; // n’alloue pas de mémoire Var str2 = str1.substr(0, 10) ; // alloue une chaîne de 10 caractères </script> Un des fondements de ce schéma d'attaque repose sur le fait que JavaScript, qui utilise (normalement) une heap séparée, va en fait utiliser la heap de Internet Explorer lorsqu'il s'agit d'allouer des chaînes de caractères. En analysant plus profondément l’implémentation du moteur javascript on se rend compte qu’il est même possible d’implémenter un équivalent des routines d’allocation et de libération de mémoire en javascript. Ce qui veut dire qu’au lieu d’envahir la mémoire pour maximiser nos chances, il est possible de prévoir très précisément ou nôtre bloc sera alloué. En tenant compte du mode de fonctionnement du moteur JavaScript et d’un certain nombre de facteurs comme : - Le garbage collector de JavaScript - Les problématiques de fragmentation dans le cadre de l’exploitation de heap overflow - Les différentes listes maintenues par l’allocateur (lookaside buffers) Alexander Sotirov en arrive à présenter une API tout à fait similaire à celle du système luimême, ce qui facilite considérablement la tâche. C’est un peu comme si l’attaquant pouvait participer de manière intrusive au déroulement du programme. Ce niveau de contrôle de l'état de la heap au travers d'un langage de si haut niveau est possible car l'algorithme de l'allocateur est tel qu'il est facilement possible de créer une routine qui va défragmenter la heap (donc remplir tous les trous qui auraient pu être crées au cours de l'exécution du programme au travers de diverses allocations/libérations de mémoire), et de mettre les listes internes maintenues par l'allocateur dans un état tel que l'on s'assure que chaque nouvel appel à l'allocateur allouera de la mémoire 'fraîche' (on entend par la qu'il n'ira pas chercher des blocs déjà utilises et libérés). <script language=’javascript’> Heap.alloc(0x2000) ; Heap.alloc(0x2020, ‘to_be_freed’); Heap.alloc(0x2020); Heap.free(‘to_be_freed’); </script> Ce qui rends ce schéma d’attaque très réaliste est le fait que Internet Explorer laisse JavaScript (donc un langage de script venant de l’extérieur en lequel on ne devrait pas avoir confiance) impacter la heap du système lui-même. En partant de ce principe, il est possible, d’augmenter très fortement les chances de réussite, puisque en connaissant l’adresse « de base » de la heap il est possible de prédire très précisément l’adresse des blocs que nous contrôlons, ce qui rends inutile la méthode de heap spraying. De plus cette méthode est très probablement extensible à la plupart des navigateurs et des moteurs JavaScript, ce qui va faciliter l’écriture d’exploits pour ce type de cibles.