<summary><strong>Nauka hakowania AWS od zera do bohatera z</strong><ahref="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Inne sposoby wsparcia HackTricks:
* Jeśli chcesz zobaczyć swoją **firmę reklamowaną w HackTricks** lub **pobrać HackTricks w formacie PDF**, sprawdź [**PLANY SUBSKRYPCYJNE**](https://github.com/sponsors/carlospolop)!
* **Dołącz do** 💬 [**grupy Discord**](https://discord.gg/hRep4RUj7f) lub [**grupy telegramowej**](https://t.me/peass) lub **śledź** nas na **Twitterze** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Podziel się swoimi sztuczkami hakowania, przesyłając PR-y do** [**HackTricks**](https://github.com/carlospolop/hacktricks) i [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
Aby poprawić efektywność przechowywania kawałków, każdy kawałek nie jest przechowywany tylko w jednym związku, ale istnieje kilka rodzajów. Są to tzw. bins i istnieje 5 rodzajów binsów: [62](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=blob;f=malloc/malloc.c;h=6e766d11bc85b6480fa5c9f2a76559f8acf9deb5;hb=HEAD#l1407) małych binsów, 63 dużych binsów, 1 bins nieuporządkowany, 10 szybkich binsów i 64 binsy tcache na wątek.
Początkowy adres do każdego binsa nieuporządkowanego, małego i dużego znajduje się w tej samej tablicy. Indeks 0 jest nieużywany, 1 to bins nieuporządkowany, binsy 2-64 to małe binsy, a binsy 65-127 to duże binsy.
Mimo że wątki starają się mieć własny sterta (patrz [Areny](bins-and-memory-allocations.md#arenas) i [Podsterty](bins-and-memory-allocations.md#subheaps)), istnieje możliwość, że proces z wieloma wątkami (np. serwer internetowy) **będzie dzielił stertę z innymi wątkami**. W takim przypadku głównym rozwiązaniem jest użycie **blokad**, które mogą **znacząco spowolnić wątki**.
Dlatego tcache jest podobny do szybkiego binsa na wątek w taki sposób, że jest to **jednokierunkowa lista** nie łącząca kawałków. Każdy wątek ma **64 jednokierunkowe binsy tcache**. Każdy bins może zawierać maksymalnie [7 kawałków o tej samej wielkości](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l323) o rozmiarze [24 do 1032B na systemach 64-bitowych i 12 do 516B na systemach 32-bitowych](https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=2527e2504761744df2bdb1abdc02d936ff907ad2;hb=d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc#l315).
Gdy wątek zwalnia kawałek, jeśli nie jest zbyt duży, aby być przydzielony do tcache, a odpowiedni bins tcache **nie jest pełny** (już 7 kawałków), **zostanie tam przydzielony**. Jeśli nie może trafić do tcache, będzie musiał poczekać na blokadę sterty, aby móc wykonać operację zwolnienia globalnie.
Gdy **kawałek jest przydzielany**, jeśli istnieje wolny kawałek wymaganego rozmiaru w **Tcache, zostanie użyty**, jeśli nie, będzie musiał poczekać na blokadę sterty, aby znaleźć go w globalnych binsach lub utworzyć nowy.\
Istnieje także optymalizacja, w tym przypadku, podczas posiadania blokady sterty, wątek **wypełni swoje Tcache kawałkami sterty (7) o żądanym rozmiarze**, więc jeśli potrzebuje więcej, znajdzie je w Tcache.
W poniższym kodzie można zobaczyć **maksymalne pojemniki** i **kawałki na indeks**, strukturę **`tcache_entry`** stworzoną w celu uniknięcia podwójnych zwolnień oraz **`tcache_perthread_struct`**, strukturę, którą każdy wątek używa do przechowywania adresów do każdego indeksu pojemnika.
Szybkie pojemniki są zaprojektowane w celu **przyspieszenia alokacji pamięci dla małych fragmentów**, trzymając niedawno zwolnione fragmenty w strukturze o szybkim dostępie. Te pojemniki korzystają z podejścia Last-In, First-Out (LIFO), co oznacza, że **najbardziej niedawno zwolniony fragment jest pierwszy** do ponownego użycia, gdy występuje nowe żądanie alokacji. To zachowanie jest korzystne dla szybkości, ponieważ szybciej jest wstawiać i usuwać z góry stosu (LIFO) w porównaniu do kolejki (FIFO).
Dodatkowo, **szybkie pojemniki używają list jednokierunkowych**, a nie dwukierunkowych, co dodatkowo poprawia szybkość. Ponieważ fragmenty w szybkich pojemnikach nie są łączone z sąsiadującymi, nie ma potrzeby skomplikowanej struktury, która pozwala na usuwanie z środka. Lista jednokierunkowa jest prostsza i szybsza dla tych operacji.
W zasadzie, to co się dzieje tutaj, to że nagłówek (wskaźnik do pierwszego fragmentu do sprawdzenia) zawsze wskazuje na najnowszy zwolniony fragment tego rozmiaru. Więc:
* Gdy nowy fragment jest alokowany tego rozmiaru, nagłówek wskazuje na wolny fragment do użycia. Ponieważ ten wolny fragment wskazuje na następny do użycia, ten adres jest przechowywany w nagłówku, więc następna alokacja wie, gdzie uzyskać dostępny fragment.
* Gdy fragment jest zwalniany, wolny fragment zapisze adres do aktualnie dostępnego fragmentu, a adres tego nowo zwolnionego fragmentu zostanie umieszczony w nagłówku.
Maksymalny rozmiar listy jednokierunkowej to `0x80`, są one zorganizowane tak, że fragment o rozmiarze `0x20-0x2f` będzie w indeksie `0`, fragment o rozmiarze `0x30-0x3f` będzie w `idx``1`...
Fragmenty w szybkich pojemnikach nie są ustawione jako dostępne, więc są trzymane jako fragmenty szybkich pojemników przez pewien czas, zamiast móc łączyć się z innymi wolnymi fragmentami otaczającymi je.
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i <8;i++){
free(chunks[i]);
}
return 0;
}
```
Zauważ, jak alokujemy i zwalniamy 8 bloków tego samego rozmiaru, aby wypełnić tcache, a ósmy jest przechowywany w szybkim bloku.
Skompiluj to i debuguj z punktem przerwania w operacji powrotu z funkcji main. Następnie za pomocą gef możesz zobaczyć wypełnienie kubełka tcache oraz jeden blok w szybkim kubełku:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Nieposortowany blok to **pamięć podręczna** używana przez menedżera sterty do przyspieszenia alokacji pamięci. Oto jak to działa: Gdy program zwalnia kawałek pamięci, i jeśli ten kawałek nie może być zaalokowany w tcache lub fast bin i nie koliduje z kawałkiem najwyższym, menedżer sterty nie umieszcza go od razu w określonym małym lub dużym pojemniku. Zamiast tego najpierw próbuje **połączyć go z sąsiednimi wolnymi kawałkami**, aby utworzyć większy blok wolnej pamięci. Następnie umieszcza ten nowy kawałek w ogólnym pojemniku o nazwie "nieposortowany blok".
Gdy program **prosi o pamięć**, menedżer sterty **sprawdza nieposortowany blok**, aby zobaczyć, czy jest tam wystarczająco duży kawałek. Jeśli znajdzie taki, od razu go używa. Jeśli nie znajdzie odpowiedniego kawałka w nieposortowanym bloku, przenosi wszystkie kawałki z tej listy do odpowiadających im pojemników, małych lub dużych, w zależności od ich rozmiaru.
Zauważ, że jeśli większy kawałek zostanie podzielony na dwie połowy i reszta będzie większa niż MINSIZE, zostanie on umieszczony z powrotem w nieposortowanym bloku. 
Tak więc, nieposortowany blok to sposób przyspieszenia alokacji pamięci poprzez szybkie ponowne wykorzystanie niedawno zwolnionej pamięci i zmniejszenie potrzeby czasochłonnych wyszukiwań i łączenia.
Zauważ, że nawet jeśli kawałki należą do różnych kategorii, jeśli dostępny kawałek koliduje z innym dostępnym kawałkiem (nawet jeśli początkowo należą do różnych pojemników), zostaną one połączone.
<summary>Dodaj przykład nieposortowanego kawałka</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunks[9];
int i;
// Loop to allocate memory 8 times
for (i = 0; i <9;i++){
chunks[i] = malloc(0x100);
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i <8;i++){
free(chunks[i]);
}
return 0;
}
```
Zauważ, jak alokujemy i zwalniamy 9 bloków tego samego rozmiaru, aby **wypełnić tcache**, a ósmy jest przechowywany w nieuporządkowanym bloku, ponieważ jest **za duży dla fastbin**, a dziewiąty nie jest zwolniony, więc dziewiąty i ósmy **nie są scalane z głównym blokiem**.
Skompiluj to i debuguj z punktem przerwania w opcode ret z funkcji main. Następnie za pomocą gef możesz zobaczyć wypełnienie tcache bin i jeden blok w nieuporządkowanym bloku:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
Małe pojemniki są szybsze niż duże pojemniki, ale wolniejsze niż szybkie pojemniki.
Każdy z 62 pojemników będzie zawierał **kawałki tej samej wielkości**: 16, 24, ... (z maksymalną wielkością 504 bajtów w 32 bitach i 1024 w 64 bitach). Pomaga to w szybkości znajdowania pojemnika, w którym powinno być przydzielone miejsce oraz w wstawianiu i usuwaniu wpisów na tych listach.
Tak jest obliczana wielkość małego pojemnika zgodnie z indeksem pojemnika:
* Najmniejsza wielkość: 2\*4\*indeks (np. indeks 5 -> 40)
* Największa wielkość: 2\*8\*indeks (np. indeks 5 -> 80)
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
if (chunks[i] == NULL) { // Check if malloc failed
fprintf(stderr, "Memory allocation failed at iteration %d\n", i);
return 1;
}
printf("Address of chunk %d: %p\n", i, (void *)chunks[i]);
}
// Loop to free the allocated memory
for (i = 0; i <8;i++){
free(chunks[i]);
}
chunks[9] = malloc(0x110);
return 0;
}
```
Zauważ, jak alokujemy i zwalniamy 9 bloków tego samego rozmiaru, aby **wypełnić tcache**, a ósmy jest przechowywany w nieuporządkowanym bloku, ponieważ jest **za duży dla fastbin**, a dziewiąty nie jest zwolniony, więc dziewiąty i ósmy **nie są scalane z głównym blokiem**. Następnie alokujemy większy blok o rozmiarze 0x110, co powoduje, że **blok w nieuporządkowanym bloku przechodzi do małego bloku**.
Skompiluj to i debuguj z punktem przerwania w operacji powrotu z funkcji main. Następnie za pomocą gef możesz zobaczyć wypełnienie bloku tcache i jeden blok w małym bloku:
```bash
gef➤ heap bins
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
W przeciwieństwie do małych pojemników, które zarządzają kawałkami o stałych rozmiarach, **każdy duży pojemnik obsługuje zakres rozmiarów kawałków**. Jest to bardziej elastyczne, pozwalając systemowi na dostosowanie się do **różnych rozmiarów** bez konieczności posiadania osobnego pojemnika dla każdego rozmiaru.
W alokatorze pamięci duże pojemniki zaczynają się tam, gdzie kończą się małe pojemniki. Zakresy dla dużych pojemników rosną stopniowo, co oznacza, że pierwszy pojemnik może obejmować kawałki od 512 do 576 bajtów, podczas gdy następny obejmuje 576 do 640 bajtów. Ten wzorzec się kontynuuje, a największy pojemnik zawiera wszystkie kawałki powyżej 1 MB.
Duże pojemniki są wolniejsze w działaniu w porównaniu do małych pojemników, ponieważ muszą **sortować i przeszukiwać listę różnych rozmiarów kawałków, aby znaleźć najlepsze dopasowanie** dla alokacji. Gdy kawałek jest wstawiany do dużego pojemnika, musi być posortowany, a gdy pamięć jest alokowana, system musi znaleźć odpowiedni kawałek. Dodatkowa praca sprawia, że są **wolniejsze**, ale ponieważ duże alokacje są mniej powszechne niż małe, jest to akceptowalny kompromis.
Istnieją:
* 32 pojemniki o zakresie 64B (kolidują z małymi pojemnikami)
* 16 pojemników o zakresie 512B (kolidują z małymi pojemnikami)
* 8 pojemników o zakresie 4096B (częściowo kolidują z małymi pojemnikami)
* 4 pojemniki o zakresie 32768B
* 2 pojemniki o zakresie 262144B
* 1 pojemnik na pozostałe rozmiary
<details>
<summary>Kody rozmiarów dużych pojemników</summary>
```c
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
2 duże alokacje są wykonywane, następnie jedna jest zwalniana (umieszczając ją w nieuporządkowanym pojemniku) i dokonywana jest większa alokacja (przenosząc zwolnioną do pojemnika nieuporządkowanego do pojemnika dużego).
Skompiluj to i debuguj z punktem przerwania w opcode ret z funkcji main. Następnie za pomocą gef możesz zobaczyć wypełnienie pojemnika tcache i jeden kawałek w pojemniku dużym:
```bash
gef➤ heap bin
──────────────────────────────────────────────────────────────────────────────── Tcachebins for thread 1 ────────────────────────────────────────────────────────────────────────────────
All tcachebins are empty
───────────────────────────────────────────────────────────────────────── Fastbins for arena at 0xfffff7f90b00 ─────────────────────────────────────────────────────────────────────────
Fastbins[idx=0, size=0x20] 0x00
Fastbins[idx=1, size=0x30] 0x00
Fastbins[idx=2, size=0x40] 0x00
Fastbins[idx=3, size=0x50] 0x00
Fastbins[idx=4, size=0x60] 0x00
Fastbins[idx=5, size=0x70] 0x00
Fastbins[idx=6, size=0x80] 0x00
─────────────────────────────────────────────────────────────────────── Unsorted Bin for arena at 0xfffff7f90b00 ───────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in unsorted bin.
──────────────────────────────────────────────────────────────────────── Small Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
[+] Found 0 chunks in 0 small non-empty bins.
──────────────────────────────────────────────────────────────────────── Large Bins for arena at 0xfffff7f90b00 ────────────────────────────────────────────────────────────────────────
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1711
/*
Top
The top-most available chunk (i.e., the one bordering the end of
available memory) is treated specially. It is never included in
any bin, is used only if no other chunk is available, and is
released back to the system if it is very large (see
M_TRIM_THRESHOLD). Because top initially
points to its own bin with initial zero size, thus forcing
extension on the first malloc request, we avoid having any special
code in malloc to check whether it even exists yet. But we still
need to do so when getting memory from system, so we make
initial_top treat the bin as a legal but unusable chunk during the
interval between initialization and the first call to
sysmalloc. (This is somewhat delicate, since it relies on
the 2 preceding words to be zero during this interval as well.)
*/
/* Conveniently, the unsorted bin can be used as dummy top on first call */
#define initial_top(M) (unsorted_chunks (M))
```
Podstawowo, to kawałek zawiera całą obecnie dostępną stertę. Gdy wykonywane jest malloc, jeśli nie ma dostępnego wolnego kawałka do użycia, ten kawałek wierzchołkowy będzie zmniejszał swoją wielkość, zapewniając niezbędną przestrzeń. Wskaźnik do kawałka wierzchołkowego jest przechowywany w strukturze `malloc_state`.
Co więcej, na początku możliwe jest użycie kawałka niesortowanego jako kawałka wierzchołkowego.
<summary>Zobacz przykład kawałka wierzchołkowego</summary>
```c
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
char *chunk;
chunk = malloc(24);
printf("Address of the chunk: %p\n", (void *)chunk);
gets(chunk);
return 0;
}
```
Po skompilowaniu i zdebugowaniu go z punktem przerwania w operacji powrotu (ret) w funkcji main zauważyłem, że funkcja malloc zwróciła adres: `0xaaaaaaac12a0`, a to są fragmenty:
Chunk(addr=0xaaaaaaac1ae0, size=0x20530, flags=PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA) ← top chunk
```
Gdzie można zobaczyć, że najwyższy kawałek znajduje się pod adresem `0xaaaaaaac1ae0`. To żadna niespodzianka, ponieważ ostatnio zaalokowany kawałek był pod adresem `0xaaaaaaac12a0` o wielkości `0x410`, a `0xaaaaaaac12a0 + 0x410 = 0xaaaaaaac1ae0`.\
Można również zobaczyć długość najwyższego kawałka na jego nagłówku kawałka:
Kiedy używane jest malloc i kawałek jest podzielony (na przykład z listy niepowiązanych lub z kawałka górnego), kawałek utworzony z reszty podzielonego kawałka nazywany jest Ostatnim Przypomnieniem, a jego wskaźnik jest przechowywany w strukturze `malloc_state`.
<summary><strong>Naucz się hakować AWS od zera do bohatera z</strong><ahref="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
Inne sposoby wsparcia HackTricks:
* Jeśli chcesz zobaczyć swoją **firmę reklamowaną w HackTricks** lub **pobrać HackTricks w formacie PDF**, sprawdź [**PLANY SUBSKRYPCYJNE**](https://github.com/sponsors/carlospolop)!
* **Dołącz do** 💬 [**grupy Discord**](https://discord.gg/hRep4RUj7f) lub [**grupy telegramowej**](https://t.me/peass) lub **śledź** nas na **Twitterze** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.**
* **Podziel się swoimi sztuczkami hakerskimi, przesyłając PR-y do** [**HackTricks**](https://github.com/carlospolop/hacktricks) i [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.