First Fit

Reading time: 7 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE) Azure Hacking'i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin

First Fit

Bir programda glibc kullanarak bellek serbest bıraktığınızda, bellek parçalarını yönetmek için farklı "kutular" kullanılır. İşte iki yaygın senaryonun basitleştirilmiş bir açıklaması: sıralanmamış kutular ve hızlı kutular.

Sıralanmamış Kutular

Hızlı bir parça olmayan bir bellek parçasını serbest bıraktığınızda, bu sıralanmamış kutuya gider. Bu kutu, yeni serbest bırakılan parçaların ön tarafa (baş) eklendiği bir liste gibi davranır. Yeni bir bellek parçası talep ettiğinizde, allocator sıralanmamış kutuya arka taraftan (kuyruk) bakarak yeterince büyük bir parça bulur. Eğer sıralanmamış kutudan bir parça, ihtiyacınız olandan büyükse, bu parça bölünür; ön kısmı geri döner ve kalan kısım kutuda kalır.

Örnek:

  • 300 bayt (a) ayırırsınız, sonra 250 bayt (b), ardından a'yı serbest bırakır ve tekrar 250 bayt (c) talep edersiniz.
  • a'yı serbest bıraktığınızda, bu sıralanmamış kutuya gider.
  • Eğer sonra tekrar 250 bayt talep ederseniz, allocator a'yı kuyrukta bulur ve onu böler, talebinize uyan kısmı geri döner ve geri kalanını kutuda tutar.
  • c, önceki a'ya işaret edecek ve a'nın içeriği ile doldurulacaktır.
c
char *a = malloc(300);
char *b = malloc(250);
free(a);
char *c = malloc(250);

Fastbins

Fastbins, küçük bellek parçaları için kullanılır. Sıralanmamış kutuların aksine, fastbins yeni parçaları başa ekler ve son giren ilk çıkar (LIFO) davranışı oluşturur. Küçük bir bellek parçası talep ederseniz, ayırıcı fastbin'in başından alır.

Örnek:

c
char *a = malloc(20);
char *b = malloc(20);
char *c = malloc(20);
char *d = malloc(20);
free(a);
free(b);
free(c);
free(d);
a = malloc(20);   // d
b = malloc(20);   // c
c = malloc(20);   // b
d = malloc(20);   // a

🔥 Modern glibc dikkate alındığında (tcache ≥ 2.26)

glibc 2.26'dan itibaren her thread kendi tcache'ini unsorted bin'den önce sorgular. Bu nedenle bir first-fit senaryosu yalnızca şu durumlarda ulaşılabilir:

  1. İstenen boyut tcache_max'dan daha büyükse (64-bit'te varsayılan olarak 0x420), veya
  2. İlgili tcache bin zaten dolu veya manuel olarak boşaltılmışsa (7 eleman tahsis edilip kullanılırken tutulduğunda).

Gerçek istismarlarınızda genellikle şu şekilde bir yardımcı rutin ekleyeceksiniz:

c
// Drain the tcache for a given size
for(int i = 0; i < 7; i++) pool[i] = malloc(0x100);
for(int i = 0; i < 7; i++) free(pool[i]);

Bir kez tcache tükenirse, sonraki serbest bırakmalar sıralanmamış kutuya gider ve klasik ilk uygun davranış (kuyruk araması, başa ekleme) tekrar tetiklenebilir.


🚩 İlk uygun ile örtüşen parça UAF oluşturma

Aşağıdaki parça (glibc 2.38 üzerinde test edilmiştir) sıralanmamış kutudaki bölücünün nasıl kötüye kullanılabileceğini gösterir ve 2 örtüşen işaretçi oluşturur - tek bir serbest bırakmayı yazma-sonrası serbest bırakmaya dönüştüren güçlü bir ilke.

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){
setbuf(stdout, NULL);

/* 1. prepare 2 adjacent chunks and free the first one */
char *A = malloc(0x420);   // big enough to bypass tcache
char *B = malloc(0x420);
strcpy(A, "AAAA\n");
free(A);                   // A → unsorted

/* 2. request a *smaller* size to force a split of A */
char *C = malloc(0x400);   // returns lower half of former A

/* 3. The remainder of A is still in the unsorted bin.
Another 0x400-byte malloc will now return the *same*
region pointed to by B – creating a UAF/overlap. */
char *C2 = malloc(0x400);

printf("B  = %p\nC2 = %p (overlaps B)\n", B, C2);

// Arbitrary write in B is immediately visible via C2
memset(B, 'X', 0x10);
fwrite(C2, 1, 0x10, stdout);  // prints Xs
}

Exploitation recipe (common in recent CTFs):

  1. Hedef boyut için tcache'i boşaltın.
  2. Bir parça serbest bırakın böylece sıralanmamış kutuya düşer.
  3. Biraz daha küçük bir boyut ayırın – ayırıcı sıralanmamış parçayı böler.
  4. Tekrar ayırın – kalan kısım mevcut bir kullanımda olan parça ile örtüşür → UAF.
  5. Hassas alanları (fonksiyon işaretçileri, FILE vtable, vb.) üzerine yazın.

Pratik bir uygulama, bu tam ilkenin __free_hook üzerinde tam kontrol sağlamak için UAF'den geçiş yapmak için kullanıldığı 2024 HITCON Quals Setjmp zorluğunda bulunabilir.


🛡️ Önlemler & Sertleştirme

  • Güvenli bağlantı (glibc ≥ 2.32) yalnızca tek bağlı tcache/fastbin listelerini korur. Sıralanmamış/küçük/büyük kutular hala ham işaretçileri saklar, bu nedenle bir heap leak elde edebilirseniz ilk uygun tabanlı örtüşmeler geçerliliğini korur.
  • Heap işaretçi şifreleme & MTE (ARM64) henüz x86-64 glibc'yi etkilemiyor, ancak GLIBC_TUNABLES=glibc.malloc.check=3 gibi dağıtım sertleştirme bayrakları tutarsız meta verilerde abort yapar ve naif PoC'leri bozabilir.
  • Serbest bırakıldığında tcache doldurma (2024'te glibc 2.41 için önerildi) sıralanmamış kullanımı daha da azaltır; genel istismarlar geliştirirken gelecekteki sürümleri izleyin.

Diğer Referanslar & Örnekler

  • https://heap-exploitation.dhavalkapil.com/attacks/first_fit
  • https://8ksec.io/arm64-reversing-and-exploitation-part-2-use-after-free/
  • ARM64. Kullanımdan sonra: Bir kullanıcı nesnesi oluşturun, serbest bırakın, serbest bırakılan parçayı alan bir nesne oluşturun ve üzerine yazılmasına izin verin, öncekinden user->password konumunu üzerine yazın. Kullanıcıyı şifre kontrolünü atlamak için yeniden kullanın.
  • https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/use_after_free/#example
  • Program notlar oluşturulmasına izin verir. Bir not, bir işlevi çağırabilecek bir işlev işaretçisine sahip bir malloc(8) içinde not bilgilerini ve notun içeriği ile başka bir malloc() işaretçisini içerecektir.
  • Saldırı, not bilgisi boyutundan daha büyük malloc içeriklerine sahip 2 not (note0 ve note1) oluşturmak ve ardından bunları serbest bırakmak olacaktır, böylece hızlı kutuya (veya tcache'e) gireceklerdir.
  • Ardından, içerik boyutu 8 olan başka bir not (note2) oluşturun. İçerik, parça yeniden kullanılacağı için note1'de olacak, burada işlev işaretçisini kazanan işlevi gösterecek şekilde değiştirebiliriz ve ardından note1'i Kullanımdan Sonra Serbest bırakıp yeni işlev işaretçisini çağırabiliriz.
  • https://guyinatuxedo.github.io/26-heap_grooming/pico_areyouroot/index.html
  • Bazı bellek ayırmak, istenen değeri yazmak, serbest bırakmak, yeniden ayırmak ve önceki veriler hala orada olduğu için, parçadaki yeni beklenen yapı ile işlenmesi mümkün hale gelir, böylece değeri ayarlamak veya bayrağı almak mümkün olur.
  • https://guyinatuxedo.github.io/26-heap_grooming/swamp19_heapgolf/index.html
  • Bu durumda, belirli bir parçanın içine 4 yazmak gereklidir, bu da tahsis edilen ilk parçadır (hepsini zorla serbest bıraktıktan sonra bile). Her yeni tahsis edilen parçanın dizideki numarası saklanır. Ardından, 4 parça (+ başlangıçta tahsis edilen) ayırın, sonuncusu içinde 4 olacak, bunları serbest bırakın ve ilk parçanın yeniden tahsis edilmesini zorlayın, bu da son serbest bırakılan parçayı kullanacaktır, bu da içinde 4 olan parçadır.
  • 2024 HITCON Quals Setjmp yazısı (Quarkslab) – pratik ilk uygun / sıralanmamış-bölme örtüşme saldırısı: https://ctftime.org/writeup/39355
  • Angstrom CTF 2024 heapify yazısı – sıralanmamış-kutu bölme istismar ederek libc'yi sızdırma ve örtüşme sağlama: https://hackmd.io/@aneii11/H1S2snV40

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE) Azure Hacking'i öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks'i Destekleyin