
Tecniche di exploitation per ChakraCore
CVE-2016-7190 [0] è un heap overflow nella funzione Array.map() di ChakraCore che consente di sovrascrivere la memoria adiacente. L'idea principale per ottenere accesso arbitrario in lettura/scrittura è prima allocare un numero di Array di interi JavaScript consecutivi e sfruttare l'overflow per manipolare la dimensione di un array. Quindi, sfruttiamo questo array per cambiare l'indirizzo base di un Uint8Array a qualsiasi indirizzo desideriamo leggere/scrivere. La ragione per questo passaggio aggiuntivo è che l'overflow è limitato a 2x la dimensione dell'array di interi overflowato.
Questa vulnerabilità è molto adatta per testare diverse strategie di exploitation perché può essere facilmente reintrodotta nelle versioni correnti di ChakraCore (vedi undo-cve-2016-7190.patch).
In questo esempio, dirottiamo il flusso di controllo sovrascrivendo il puntatore vtable di un oggetto C++ Uint8Array con un indirizzo di memoria controllato e invocando una funzione dell'oggetto Uint8Array che provoca l'invocazione di una funzione virtuale.
L'integrità del flusso di controllo (CFI) è una tecnica di difesa per mitigare gli attacchi di dirottamento del flusso di controllo. L'idea generale della CFI è calcolare un grafo del flusso di controllo (CFG) di un'applicazione durante la compilazione, e quindi strumentare l'applicazione con controlli a runtime per garantire che il flusso di controllo non si discosti dal CFG calcolato staticamente durante l'esecuzione. Tuttavia, se un'applicazione, come un browser web, supporta la generazione dinamica di codice, il CFG deve essere estendibile durante l'esecuzione. Questo impone una serie di sfide:
Durante il periodo in cui abbiamo condotto la nostra ricerca, ci siamo concentrati sull'ultima sfida. L'idea principale è manipolare l'input (dati) del compilatore just-in-time (JIT). Di conseguenza, il compilatore JIT genererebbe codice malizioso che verrebbe quindi integrato nel contesto corrente—e persino protetto con CFI. In parallelo alla nostra ricerca, theori [4] ha mostrato come bypassare CFI manipolando l'output del compilatore JIT. Questo attacco è mitigato verificando l'integrità dell'output tramite un checksum. Questo non può fermare il nostro attacco perché manipoliamo l'input del compilatore JIT. TUTTAVIA, esternalizzando la compilazione JIT in un altro processo (aka Arbitrary Code Guard [3]) entrambi gli attacchi sono mitigati.
Dimostriamo che un attaccante può sfruttare un attacco solo-dati contro il compilatore JIT per generare codice nativo arbitrario. In particolare, modifichiamo la rappresentazione intermedia (IR) del compilatore JIT per iniettare istruzioni controllate dall'attaccante. Quando il compilatore JIT genera quindi il codice nativo basato sull'IR modificato, genererà codice nativo controllato dall'attaccante.
Tra il momento in cui la ricerca è stata originariamente condotta e ora, l'IR di ChakraCore è stato modificato. Non abbiamo fatto lo sforzo di portare l'attacco, quindi, per giocare con questo attacco, si prega di utilizzare i seguenti binari.
Durante l'analisi di ACG [3] abbiamo notato, tra gli altri [1,2], che i dati globali di sola lettura, memorizzati nella sezione .mrdata, devono essere regolati per integrare il codice generato dinamicamente. Ciò viene fatto tramite la funzione LdrProtectMrdata() che utilizza un lock per essere thread-safe. Tuttavia, la sezione .mrdata viene prima rimappata come scrivibile prima che il lock venga acquisito. Interessantemente, la sezione .mrdata contiene l'indirizzo base e la dimensione della sezione .mrdata che viene successivamente utilizzata per rimappare nuovamente questa sezione come sola lettura.
Un attaccante può sfruttare ciò per creare una primitiva di attacco che consente di rimappare memoria di sola lettura come scrivibile. Pertanto, l'attaccante esegue i seguenti passaggi:
La conseguenza di questo attacco è che dati precedentemente fidati, cioè dati mappati come sola lettura, diventano non fidati. Nella nostra prova di concetto, sfruttiamo questa primitiva per bypassare Control-flow Guard (CFGuard) modificando il puntatore alla funzione di verifica di CFGuard salvato in _guard_dispatch_icall_fptr. L'effetto può essere osservato collegando il debugger e modificando la variabile bypass_cfguard.
[0] https://bugs.chromium.org/p/project-zero/issues/detail?id=923
[1] http://alex-ionescu.com/publications/euskalhack/euskalhack2017-cfg.pdf
[2] https://sites.google.com/site/bingsunsec/dataonlyattack
[3] https://blogs.windows.com/msedgedev/2017/02/23/mitigating-arbitrary-native-code-execution/
[4] http://theori.io/research/chakra-jit-cfg-bypass