
Techniques d'exploitation de ChakraCore
CVE-2016-7190 [0] est un débordement de tas dans la fonction Array.map() de ChakraCore qui permet d'écraser la mémoire adjacente. L'idée principale pour obtenir un accès en lecture-écriture arbitraire est d'abord d'allouer plusieurs tableaux d'entiers JavaScript consécutifs, puis d'exploiter le débordement pour manipuler la taille d'un tableau. Ensuite, on utilise ce tableau pour changer l'adresse de base d'un Uint8Array vers n'importe quelle adresse que l'on souhaite lire/écrire. La raison de cette étape supplémentaire est que le débordement est limité à 2 fois la taille du tableau d'entiers débordé.
Cette vulnérabilité est très adaptée pour tester différentes stratégies d'exploitation car elle peut être facilement réintroduite dans les versions actuelles de ChakraCore (voir undo-cve-2016-7190.patch).
Dans cet exemple, nous détournons le flux de contrôle en écrasant le pointeur vtable d'un objet C++ Uint8Array vers une adresse mémoire contrôlée, et en invoquant une fonction des objets Uint8Array qui entraîne l'invocation d'une fonction virtuelle.
L'intégrité du flux de contrôle (CFI) est une technique de défense pour atténuer les attaques de détournement du flux de contrôle. L'idée générale du CFI est de calculer un graphe de flux de contrôle (CFG) d'une application lors de la compilation, puis d'instrumenter l'application avec des vérifications à l'exécution pour s'assurer que le flux de contrôle ne s'écarte pas du CFG calculé statiquement. Cependant, si une application, comme un navigateur web, prend en charge la génération dynamique de code, le CFG doit pouvoir être étendu à l'exécution. Cela impose un certain nombre de défis :
Pendant la période où nous avons mené nos recherches, nous nous sommes concentrés sur le dernier défi. L'idée principale est de manipuler l'entrée (données) du compilateur just-in-time (JIT). En conséquence, le compilateur JIT générerait du code malveillant qui serait alors intégré dans le contexte actuel—et même renforcé avec CFI. Parallèlement à nos recherches, theori [4] a montré comment contourner CFI en manipulant la sortie du compilateur JIT. Cette attaque est atténuée en vérifiant l'intégrité de la sortie via une somme de contrôle. Cela ne peut pas arrêter notre attaque car nous manipulons l'entrée du compilateur JIT. CEPENDANT, en externalisant la compilation JIT vers un autre processus (alias Arbitrary Code Guard [3]) les deux attaques sont atténuées.
Nous démontrons que un attaquant peut exploiter une attaque par données uniquement contre le compilateur JIT pour générer du code natif arbitraire. En particulier, nous modifions la représentation intermédiaire (IR) du compilateur JIT pour injecter des instructions contrôlées par l'attaquant. Lorsque le compilateur JIT génère ensuite le code natif basé sur l'IR modifié, il générera du code natif contrôlé par l'attaquant.
Entre le moment où la recherche a été initialement menée et aujourd'hui, l'IR de ChakraCore a été modifié. Nous n'avons pas fait l'effort de porter l'attaque, donc, pour expérimenter avec cette attaque, veuillez utiliser les binaires suivants.
Lors de l'analyse de l'ACG [3], nous avons remarqué, entre autres [1,2], que les données globales en lecture seule, stockées dans la section .mrdata, doivent être ajustées pour intégrer le code généré dynamiquement. Cela se fait via la fonction LdrProtectMrdata() qui utilise un verrou pour assurer la sécurité des threads. Cependant, la section .mrdata est d'abord remappée en écriture avant que le verrou soit acquis. Fait intéressant, la section .mrdata contient l'adresse de base et la taille de la section .mrdata qui sont ensuite utilisées pour remapper cette section à nouveau en lecture seule.
Un attaquant peut exploiter cela pour créer une primitive d'attaque qui permet de remapper de la mémoire en lecture seule en écriture. Par conséquent, l'attaquant exécute les étapes suivantes :
La conséquence de cette attaque est que des données auparavant de confiance, c'est-à-dire des données mappées en lecture seule, deviennent non fiables. Dans notre preuve de concept, nous exploitons cette primitive pour contourner Control-flow Guard (CFGuard) en modifiant le pointeur vers la fonction de vérification de CFGuard qui est sauvegardé dans _guard_dispatch_icall_fptr. L'effet peut être observé en attachant le débogueur et en modifiant la variable bypass_cfguard.
[0] https://bugs.chromium.org/p/project-zero/issues/detail?id=923
[1] http://alex-ionescu.com/publications/euskalhack/euskalhack2017-cfg.pdf
[2] https://sites.google.com/site/bingsunsec/dataonlyattack
[3] https://blogs.windows.com/msedgedev/2017/02/23/mitigating-arbitrary-native-code-execution/
[4] http://theori.io/research/chakra-jit-cfg-bypass