BROP - Blind Return Oriented Programming
Last updated
Last updated
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
O objetivo deste ataque é ser capaz de abusar de um ROP via um estouro de buffer sem qualquer informação sobre o binário vulnerável. Este ataque é baseado no seguinte cenário:
Uma vulnerabilidade na pilha e conhecimento de como acioná-la.
Uma aplicação de servidor que reinicia após uma falha.
Você pode encontrar mais informações sobre esses processos aqui (BF Forked & Threaded Stack Canaries) e aqui (BF Addresses in the Stack).
Este gadget basicamente permite confirmar que algo interessante foi executado pelo gadget ROP porque a execução não falhou. Normalmente, este gadget será algo que para a execução e está posicionado no final da cadeia ROP ao procurar gadgets ROP para confirmar que um gadget ROP específico foi executado.
Esta técnica usa o gadget ret2csu. E isso ocorre porque, se você acessar este gadget no meio de algumas instruções, você obtém gadgets para controlar rsi
e rdi
:
Esses seriam os gadgets:
pop rsi; pop r15; ret
pop rdi; ret
Note como, com esses gadgets, é possível controlar 2 argumentos de uma função a ser chamada.
Além disso, note que o gadget ret2csu tem uma assinatura muito única porque vai estar popando 6 registradores da pilha. Portanto, enviando uma cadeia como:
'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
Se o STOP for executado, isso basicamente significa que um endereço que está popando 6 registradores da pilha foi usado. Ou que o endereço usado também era um endereço STOP.
Para remover esta última opção, uma nova cadeia como a seguinte é executada e não deve executar o gadget STOP para confirmar que o anterior popou 6 registradores:
'A' * offset + canary + rbp + ADDR
Sabendo o endereço do gadget ret2csu, é possível inferir o endereço dos gadgets para controlar rsi
e rdi
.
A tabela PLT pode ser pesquisada a partir de 0x400000 ou do endereço RIP vazado da pilha (se PIE estiver sendo usado). As entradas da tabela são separadas por 16B (0x10B), e quando uma função é chamada, o servidor não falha mesmo que os argumentos não estejam corretos. Além disso, verificar o endereço de uma entrada na PLT + 6B também não falha pois é o primeiro código executado.
Portanto, é possível encontrar a tabela PLT verificando os seguintes comportamentos:
'A' * offset + canary + rbp + ADDR + STOP
-> sem falha
'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
-> sem falha
'A' * offset + canary + rbp + (ADDR + 0x10) + STOP
-> sem falha
A função strcmp
define o registrador rdx
para o comprimento da string sendo comparada. Note que rdx
é o terceiro argumento e precisamos que ele seja maior que 0 para depois usar write
para vazar o programa.
É possível encontrar a localização de strcmp
na PLT com base em seu comportamento usando o fato de que agora podemos controlar os 2 primeiros argumentos das funções:
strcmp(<endereço não lido>, <endereço não lido>) -> falha
strcmp(<endereço não lido>, <endereço lido>) -> falha
strcmp(<endereço lido>, <endereço não lido>) -> falha
strcmp(<endereço lido>, <endereço lido>) -> sem falha
É possível verificar isso chamando cada entrada da tabela PLT ou usando o caminho lento da PLT, que basicamente consiste em chamar uma entrada na tabela PLT + 0xb (que chama dlresolve
) seguido na pilha pelo número da entrada que se deseja sondar (começando em zero) para escanear todas as entradas PLT a partir da primeira:
strcmp(<endereço não lido>, <endereço lido>) -> falha
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
-> Vai falhar
strcmp(<endereço lido>, <endereço não lido>) -> falha
b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
strcmp(<endereço lido>, <endereço lido>) -> sem falha
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
Lembre-se que:
BROP + 0x7 aponta para pop RSI; pop R15; ret;
BROP + 0x9 aponta para pop RDI; ret;
PLT + 0xb aponta para uma chamada para dl_resolve.
Tendo encontrado strcmp
, é possível definir rdx
para um valor maior que 0.
Note que geralmente rdx
já terá um valor maior que 0, então este passo pode não ser necessário.
Finalmente, é necessário um gadget que exfiltra dados para exfiltrar o binário. E neste momento é possível controlar 2 argumentos e definir rdx
maior que 0.
Existem 3 funções comuns que poderiam ser abusadas para isso:
puts(data)
dprintf(fd, data)
write(fd, data, len(data)
No entanto, o artigo original menciona apenas a write
, então vamos falar sobre isso:
O problema atual é que não sabemos onde a função write está dentro da PLT e não sabemos um número de fd para enviar os dados para nosso socket.
No entanto, sabemos onde está a tabela PLT e é possível encontrar write com base em seu comportamento. E podemos criar várias conexões com o servidor e usar um FD alto esperando que ele corresponda a algumas de nossas conexões.
Assinaturas de comportamento para encontrar essas funções:
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Se houver dados impressos, então puts foi encontrado
'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Se houver dados impressos, então dprintf foi encontrado
'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
-> Se houver dados impressos, então write foi encontrado
Artigo original: https://www.scs.stanford.edu/brop/bittau-brop.pdf
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)