BROP - Blind Return Oriented Programming

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

Informações Básicas

O objetivo deste ataque é ser capaz de abusar de um ROP por meio de um estouro de buffer sem nenhuma informação sobre o binário vulnerável. Este ataque é baseado no seguinte cenário:

  • Uma vulnerabilidade de pilha e conhecimento de como ativá-la.

  • Uma aplicação de servidor que reinicia após uma falha.

Ataque

1. Encontrar o deslocamento vulnerável enviando um caractere a mais até que uma falha no servidor seja detectada

2. Forçar o canário para vazá-lo

3. Forçar os endereços RBP e RIP armazenados na pilha para vazá-los

Você pode encontrar mais informações sobre esses processos aqui (BF Forked & Threaded Stack Canaries) e aqui (BF Addresses in the Stack).

4. Encontrar o gadget de parada

Este gadget basicamente permite confirmar que algo interessante foi executado pelo gadget ROP porque a execução não travou. Geralmente, esse gadget será algo que interrompe a execução e é posicionado no final da cadeia ROP ao procurar gadgets ROP específicos que foram executados.

5. Encontrar o gadget BROP

Esta técnica usa o gadget ret2csu. E isso ocorre porque se você acessar esse 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

Observe como com esses gadgets é possível controlar 2 argumentos de uma função a ser chamada.

Além disso, observe que o gadget ret2csu tem uma assinatura muito única porque ele vai desempilhar 6 registradores da pilha. Então, enviando uma cadeia como:

'A' * deslocamento + canário + rbp + ADDR + 0xdead * 6 + STOP

Se o STOP for executado, isso basicamente significa que um endereço que está desempilhando 6 registradores da pilha foi usado. Ou que o endereço usado também foi 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 desempilhou 6 registradores:

'A' * deslocamento + canário + rbp + ADDR

Sabendo o endereço do gadget ret2csu, é possível inferir o endereço dos gadgets para controlar rsi e rdi.

6. Encontrar PLT

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 trava mesmo se os argumentos não estiverem corretos. Além disso, verificar o endereço de uma entrada na PLT + 6B também não trava pois é o primeiro código executado.

Portanto, é possível encontrar a tabela PLT verificando os seguintes comportamentos:

  • 'A' * deslocamento + canário + rbp + ADDR + STOP -> sem travamento

  • 'A' * deslocamento + canário + rbp + (ADDR + 0x6) + STOP -> sem travamento

  • 'A' * deslocamento + canário + rbp + (ADDR + 0x10) + STOP -> sem travamento

7. Encontrar strcmp

A função strcmp define o registrador rdx como o comprimento da string sendo comparada. Observe que rdx é o terceiro argumento e precisamos que ele seja maior que 0 para posteriormente usar write para vazar o programa.

É possível encontrar a localização do 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>) -> travamento

  • strcmp(<endereço não lido>, <endereço lido>) -> travamento

  • strcmp(<endereço lido>, <endereço não lido>) -> travamento

  • strcmp(<endereço lido>, <endereço lido>) -> sem travamento

É 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 para dlresolve) seguido na pilha pelo número de entrada que se deseja sondar (começando em zero) para escanear todas as entradas da PLT a partir da primeira:

  • strcmp(<endereço não lido>, <endereço lido>) -> travamento

  • b'A' * deslocamento + canário + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP -> Vai travar

  • strcmp(<endereço lido>, <endereço não lido>) -> travamento

  • b'A' * deslocamento + canário + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP

  • strcmp(<endereço lido>, <endereço lido>) -> sem travamento

  • b'A' * deslocamento + canário + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP

Lembre-se de 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 com um valor maior que 0.

Observe que geralmente rdx já terá um valor maior que 0, então esta etapa pode não ser necessária.

### 8. Encontrando Write ou equivalente

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 o write, então vamos falar sobre ele:

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 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

Exploração Automática

Referências

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

Last updated