June 28, 2026
Básico Exploits Linux — Parte 1
Este texto é uma complemento de um dos meus estudos. O objetivo deste blog é registrar minhas ideias. Aqui, explicarei minha jornada de…

By Matheus Oliveira
4 min read
Este texto é uma complemento de um dos meus estudos. O objetivo deste blog é registrar minhas ideias. Aqui, explicarei minha jornada de desenvolvimento de exploits em x32. Decidi que não faria mal compartilhar algumas partes do meu aprendizado com todos. Espero que aprendam bastante, tentei deixar o meu jeito de entender as coisas. Bom estudo a todos! 😁
Por que estudar exploits? Hackers éticos devem estudar exploits para entender se vulnerabilidades são exploráveis. Às vezes, profissionais de segurança acreditam erroneamente, e afirmam publicamente que uma determinada vulnerabilidade não é explorável, mas hackers black hat sabem que isso não é verdade. O fato de uma pessoa não conseguir encontrar um exploit para uma vulnerabilidade não significa que outra pessoa também não consiga. É uma questão de tempo e nível de habilidade. Portanto, hackers éticos precisam entender como explorar vulnerabilidades e verificar isso por conta própria. Nesse processo, eles podem precisar criar um código de prova de conceito (proof-of-concept ou POC para os mais íntimos😜) para demonstrar ao fornecedor que uma vulnerabilidade é explorável e precisa ser corrigida. Neste capítulo , focaremos na exploração de um stack overflows em Linux de 32 bits, na desativação de técnicas de mitigação de exploits em tempo de compilação e na randomização do layout do espaço de endereçamento (Address Space Layout Randomization ou ASLR).
Stack e Processo de Chamada de Função
O conceito de Stack na computação pode ser melhor explicado quando comparado a uma pilha de pratos de uma lanchonete. Quando nós colocamos o prato no topo de uma pilha de pratos, ele precisa ser o primeiro a sair para que possamos pegar o prato que há embaixo. Mais formalmente nos termos da ciência da computação, o Stack é uma estrutura de dado que possui a qualidade de FIRST IN, LAST OUT (FILO) ou O primeiro que entra é o último que sai.
A partir daqui chamarei a Pilha de Stack para melhor entendimento e diferenciar caso precise falar pilha de outra coisa kkkkkkkkk.
O processo de colocar itens na Stack é chamada de Push e é chamado em assembly pelo comando PUSH. Da mesma maneira o processo de pegar itens da Stack é chamado Pop e chamado em assembly como POP.
A relação de EBP e ESP em uma Stack é melhor representado do jeito que montei o diagrama abaixo.
DIREÇÃO DO CRESCIMENTO DA PILHA (Endereços Diminuem)
<---------------------------------------------------------------
[ Memória Baixa ] [ Memória Alta ]
(Ex: 0x11111111) (Ex: 0x7FFFFFFF)
| |
v v
+--------------+-------------------------------+---------------+
| | STACK FRAME | |
| Outros Dados | (Quadro da Função Atual) | Dados Antigos |
| da Pilha | - Variáveis Locais | da Pilha |
| | - Endereço de Retorno | |
+--------------+-------------------------------+---------------+
^ ^
| |
[ ESP ] [ EBP ]
(Ponteiro do Topo) (Ponteiro da Base)
Move-se a cada PUSH/POP Fica fixo na função DIREÇÃO DO CRESCIMENTO DA PILHA (Endereços Diminuem)
<---------------------------------------------------------------
[ Memória Baixa ] [ Memória Alta ]
(Ex: 0x11111111) (Ex: 0x7FFFFFFF)
| |
v v
+--------------+-------------------------------+---------------+
| | STACK FRAME | |
| Outros Dados | (Quadro da Função Atual) | Dados Antigos |
| da Pilha | - Variáveis Locais | da Pilha |
| | - Endereço de Retorno | |
+--------------+-------------------------------+---------------+
^ ^
| |
[ ESP ] [ EBP ]
(Ponteiro do Topo) (Ponteiro da Base)
Move-se a cada PUSH/POP Fica fixo na funçãoComo puderam ver, a Stack sempre cresce para baixo. Note que a seta aponta para a esqueda em direção a memória mais baixa (Low Memory). Na arquitetura de computadores x86, conforme novos dados são adicionados na Stack, os endereços de memória vão diminuindo.
Endereços Altos vs. Baixos: O topo fisico da memória fica na direita e a base da Stack começa lá e vai descendo
Quando um programa chama uma função ele reserva um espaço da Stack exclusivamente para ela. Esse pedaço é chamdo de Stack Frame. Ela armazena Variaveis locais, Parametros passados pela função e o Endereço de retorno (Para onde o programa volta quando a função termina).
Uma função é um módulo de código que pode ser chamado por outra funções, incluindo a função main(). Quando uma função é chamado ela provoca um desvio no fluxo do programa. Ao chamar uma função em assembly, três ações ocorrem:
- Por convenção, o programa que realiza a chamada prepara a invocação da função colocando primeiramente os parâmetros da função na pilha em ordem inversa.
- Em seguida, o Extended Instruction Pointer (EIP) é salvo na pilha para que o programa possa continuar de onde parou quando a função retornar. Esse valor é chamado de endereço de retorno.
- Por fim, o comando de chamada (call) é executado, e o endereço da função é carregado no EIP para sua execução.
Registradores EBP e ESP
Para que esse pedaço do código não se perca, a CPU usa duas "âncoras", EBP e ESP.
EBP (Extended Base Pointer ou Ponteiro de base) aponta sempre para a base da Stack Frame atual. Como ele fica fixo durante a execução de uma função, o programa o utiliza como ponteiro de referência para encontrar váriaveis locais e argumentos (ex.: "procure a váriavel X a 4 bytes de distancia de EBP" ou "EBP — 4").
ESP (Extended Stack Pointer ou Ponteiro de Pilha) aponta diferente de EBP, ele aponta sempre para o topo atual da Stack (que no caso está mais perto da baixa memória). Conforme usa os comandos PUSH ou POP, o ESP se move para esquerda ou para direita.
Nota: O assembly produzido nessa parte é utilizando o compilador gcc com a opção -fno-stack-protector para desabilitar a proteção Stack Canary que posso trazer em outro momento se desejarem.
No código assembly, essa chamada é parecida com isso:
0x5655621b <+38>: mov edx,DWORD PTR [eax]
0x5655621d <+40>: mov eax,DWORD PTR [ebx+0x4]
0x56556220 <+43>: add eax,0x4
0x56556223 <+46>: mov eax,DWORD PTR [eax]
0x56556225 <+48>: sub esp,0x8
0x56556228 <+51>: push edx
0x56556229 <+52>: push eax
0x5655622a <+53>: call 0x565561a9 <greeting>0x5655621b <+38>: mov edx,DWORD PTR [eax]
0x5655621d <+40>: mov eax,DWORD PTR [ebx+0x4]
0x56556220 <+43>: add eax,0x4
0x56556223 <+46>: mov eax,DWORD PTR [eax]
0x56556225 <+48>: sub esp,0x8
0x56556228 <+51>: push edx
0x56556229 <+52>: push eax
0x5655622a <+53>: call 0x565561a9 <greeting>Quando um programa chama uma função, ela precisa criar o seu próprio espaço na memória (Stack Frame) sem destruir o que a função anterior estava fazendo
push ebp ; 1. Salva o EBP antigo na pilha
mov ebp, esp ; 2. Define a base do novo Stack Frame
push ebx ; 3. (Opcional) Salva outros registradores que serão usados
sub esp, 0x194 ; 4. Reserva espaço para variáveis locaispush ebp ; 1. Salva o EBP antigo na pilha
mov ebp, esp ; 2. Define a base do novo Stack Frame
push ebx ; 3. (Opcional) Salva outros registradores que serão usados
sub esp, 0x194 ; 4. Reserva espaço para variáveis locaisA funçao "push ebp" "anota" no papel (stack) onde o EBP da funçao anterior estava apontando. Isso é essencial para que quando a funçao terminar, a anterior possa continuar de onde parou.
Na "mov ebp, esp" o EBP agora se move para onde o ESP está, assim se forma uma nova Stack Frame.
Já na "push ebx" nós apenas salvamos o valor do EBX para não corromper dados importantes.
Como a Stack crece para baixo usamos "sub esp, 0x194" para subtrair um valor de esp, arrastando o topo da Stack para longe. No exemplo nós reservamos 0x194 bytes (404 bytes em decimal) de espaço na memória para guardar variáveis locais que você declarar nessa função.
Quando uma função termina de executar suas tarefas, ela precisa desfazer toda a bagunça e devolver o controle para quem chamou. Se ela não fizer isso, o programa trava ou acontece uma falha de buffer overflow.
O código para isso é incrívelmente curto:
leave ; 1. Limpa as variáveis locais e restaura o EBP antigo
ret ; 2. Retorna para a função que a chamouleave ; 1. Limpa as variáveis locais e restaura o EBP antigo
ret ; 2. Retorna para a função que a chamouO "leave" é uma função que faz duas coisas ao mesmo tempo. Ela move ESP de volta para onde EBP está (destruindo todo o espaço reservado para as váriaveis locais), mas ela também faz um "pop ebp", trazendo de volta o EBP da função anterios
A "ret" retira da Stack o endereço de retorno e salta de volta pra la, colocando esse endereço no EIP. E o progrma principal continua como se nada tivesse acontecido.
Você verá esses pequenos trechos de código assembly várias vezes ao procurar por buffer overflows.
Nos vemos na Parte 2…
Básico Exploits Linux — Parte 2 Este texto é uma continuação da Parte 1, você até consegue acompanhar sem ter visto, mas para melhor compreensão…