
Exploitation d'une vulnérabilité corrigée dans JavaScriptCore
Ceci est un exploit pour une vulnérabilité WebKit qui a été découverte à l'origine par Fluoroacetate lors de la compétition pwn2own à Vancouver. Bien que je n'aie pas découvert ce bogue, j'ai écrit cet exploit pour pratiquer mes compétences en développement d'exploits. L'article original sur cet exploit est ici de Zero Day Initiative. Bien que cet article soit très bon et ait été déterminant pour m'aider à comprendre la vulnérabilité, il est du point de vue de quelqu'un qui vérifie la vulnérabilité. J'ai trouvé que certains détails clés manquaient lors de la tentative d'ingénierie de cet exploit à partir de zéro et j'espère combler certaines lacunes que l'article ZDI a omises et acquérir des compétences pratiques sur la façon d'ingénieriser un exploit complexe à partir de zéro.
Ces étapes servent de plan pour obtenir une exécution de code arbitraire dans JavaScriptCore (JSC), le moteur JavaScript de WebKit
La vulnérabilité qui sera exploitée est un dépassement d'entier qui se produit dans le code produit par le compilateur just in time (JIT) DFG pour WebKit. Cela se produit spécifiquement dans la fonction compileNewArrayWithSpread. Cette fonction sera appelée lorsque du code utilisant la syntaxe de décomposition JavaScript pour créer un nouveau tableau est compilé à la volée (JITé) par DFG.

Dans le code JITé, il calcule d'abord la taille du tableau. Il le fait en ajoutant la longueur de chaque argument passé au constructeur du tableau. Lorsqu'il calcule la taille pour chaque addition, il vérifie un dépassement de la taille. Après cela, il appelle la fonction compileAllocateNewArray en passant la longueur qui a été calculée dans cette fonction.

compileAllocateNewArray passe ensuite la longueur calculée précédemment à emitAllocateButterfly.

emitAllocateButterfly décale ensuite la taille de 3 bits vers la gauche, ce qui équivaut à la multiplier par 8. Cependant, il n'y a pas de vérification de dépassement et par conséquent un nombre comme 0x20000001 peut déborder à 0x8
Ce programme C illustre cette vulnérabilité :


Nous pouvons utiliser cette vulnérabilité pour tromper le moteur JavaScript en lui faisant croire que nous avons alloué un tableau de taille 0x20000001 alors qu'en réalité seulement assez d'espace pour 1 JSValue (8 octets) a été alloué. Cela entraînera une primitive de lecture et d'écriture hors limites (OOB) qui pourra ensuite être exploitée pour obtenir une lecture/écriture arbitraire et finalement une exécution de code à distance (RCE).
Afin de confirmer que nous avons une lecture OOB, nous allons essayer de déclencher cette vulnérabilité sur une version de JSC avec address sanitizer (ASAN).
Pour ce faire, depuis le répertoire WebKit, nous pouvons exécuter les commandes :```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug
Cela construira une version de débogage de JSC avec ASAN activé, ce qui nous permettra de vérifier si nous avons ou non déclenché la vulnérabilité avec succès.
Voici la première itération de exploit.js```javascript
function jitMe(array){
return [...array]
}
let dummy = [1.1]
for(let i = 0; i < 200; i++){
jitMe(dummy);
}
let a = []
let len = 0x20000001
for(let i = 0; i < len; i++){
a[i] = 1.1
}
jitMe(a)
Lors de l'exécution, j'obtiens l'erreur suivante :
Program terminated with signal SIGKILL, Killed. The program no longer exists.
Mon intuition était que trop de mémoire était consommée en essayant d'allouer un tableau aussi grand. Pour le confirmer, j'ai ajouté un point d'arrêt dans le code JITé en appelant m_jit.breakpoint() à l'intérieur de compileNewArrayWithSpread, ce qui ajoute une instruction int3 au code JITé.
Après avoir ajouté le point d'arrêt, je me suis aperçu qu'il n'était pas atteint, et j'ai alors décidé de tester une longueur de 0x20001. J'ai ensuite réalisé que le code n'était même pas compilé, j'ai donc ajouté plus d'itérations pour activer le compilateur DFG.```javascript function jitMe(array){ for(let i = 0; i < 0x4000; i++){ let x = 1 + 1 } return [...array] }
let dummy = [1.1] for(let i = 0; i < 60; i++){ print(i) jitMe(dummy); }
let a = []
let len = 0x20000001
for(let i = 0; i < len; i++){ a[i] = 1.1 }
jitMe(a)
Tester le programme tel quel mène toujours au SIGKILL cependant, lors des tests avec une longueur plus petite, le point d'arrêt est atteint. À ce stade, il me semble toujours que JSC manque de mémoire lors du traitement de ce tableau énorme.
Pour résoudre ce problème, j'ai décidé d'allouer un tableau `a` plus petit, puis d'utiliser la syntaxe de décomposition pour l'employer plusieurs fois lors de la création du tableau corrompu, ce qui donne le fichier exploit.js suivant```
function jitMe(array){
for(let i = 0; i < 0x4000; i++){
let x = 1 + 1
}
return [...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array, ...array]
}
let dummy = [1.1]
for(let i = 0; i < 100; i++){
print(i)
jitMe(dummy);
}
let a = []
let len = 0x20000010 / 0x10
for(let i = 0; i < len; i++){
a[i] = 1.1
}
jitMe(a)
En utilisant ce code, nous avons pu atteindre le point d'arrêt sans SIGKILL ! Comme c'est souvent le cas, la résolution d'un problème en a révélé un autre, et nous avons reçu un SIGABORT à la place... En utilisant la commande bt de gdb, nous voyons que operationNewArrayWithSize a été appelée, qui elle-même a appelé create.
Il semble étrange que notre code JITé appelle operationNewArrayWithSize ; cela signifie que le code JITé a dû emprunter un chemin lent vers le moteur JavaScript pour une raison quelconque.

Nous pouvons voir dans compileAllocateNewArrayWithSize qu'il y a effectivement un basculement vers operationNewArrayWithSize. Nous devons alors trouver pourquoi exactement nous basculons vers le cas lent.