PicoCTF
PicoCTF 2024 - Heap 3: Use After Free Exploitation
December 26, 2025
Giriş
picoCTF 2024’ün Binary Exploitation kategorisindeki “heap 3” problemi, Use-After-Free (UAF) zafiyetlerini anlamak için klasik bir örnektir. Challenge açıklaması “This program mishandles memory” diyerek bize ipucu veriyor, hint kısmında da açıkça “use after free” yazıyor.
Challenge Bilgileri:
- Zorluk: Medium
- Kategori: Binary Exploitation
- Flag:
picoCTF{now_thats_free_real_estate_79173b73}
Challenge Analizi
Kaynak Kod İncelemesi
Programda tanımlanan struct yapısı şu şekilde:
typedef struct {
char a[10];
char b[10];
char c[10];
char flag[5];
} object;
object *x;
Burada a, b, c dizileri var ve en sonda 5 byte’lık bir flag alanı bulunuyor. Global bir x pointer’ı bu struct’a işaret ediyor.
Zafiyet Nerede?
Programdaki kritik hata free_memory() fonksiyonunda görülüyor:
void free_memory() {
free(x);
}
Burada x free ediliyor ama sonra x = NULL yapılmıyor. Bu, x pointer’ının hala eski (artık geçersiz olan) adrese işaret ettiği anlamına geliyor. Buna dangling pointer (sarkaç pointer) diyoruz.
Free’den sonra program x‘i kullanmaya devam ederse ne olur? İşte UAF tam olarak bu: Free edilmiş bellek alanını kullanmaya devam etmek.
Win Koşulu
Programda check_win() fonksiyonu var:
void check_win() {
if(!strcmp(x->flag, "pico")) {
// flag'i yazdır
}
}
Eğer x->flag değeri "pico" olursa kazanıyoruz. Başlangıçta init() fonksiyonunda bu değer "bico" (b harfiyle) olarak set ediliyor.
Kazanmak için x->flag alanının değerini "bico"‘dan "pico"‘ya değiştirmemiz gerekiyor.
Exploit Stratejisi
UAF zafiyetini kullanarak bu değişikliği yapabilir miyiz? Adım adım düşünelim:
- Program başlıyor,
xiçin heap’te yer ayrılıyor,flag = "bico"oluyor - Menüden seçenek 5’i seçerek
x‘i free ediyoruz xhala aynı adresi gösteriyor ama o bellek artık “serbest”alloc_object()ile yeni bir allocation yapıyoruz- malloc büyük ihtimalle az önce free ettiğimiz yeri geri verecek (glibc malloc optimizasyonu)
- Yeni allocation’a yazdığımız veri, aslında
x‘in gösterdiği yere yazılacak!
Şekil 1: Use-After-Free saldırısının 3 aşaması
Bu çizimde UAF zafiyetinin özünü görüyorsunuz. Aşama 1‘de x pointer’ı heap’teki bir chunk’ı gösteriyor, içinde “bico” verisi var. Aşama 2‘de bu chunk free() ile serbest bırakılıyor ama x pointer’ı hâlâ aynı adresi gösteriyor (dangling pointer). Bellek artık “serbest” ama pointer habersiz. Aşama 3‘te yeni bir malloc() çağrısı yapılınca, malloc allocator’ı “aa dur, bu chunk daha yeni free edilmişti, al sana aynısı!” diyor ve aynı adresi veriyor. Sonuç: İki farklı pointer (x ve new_alloc) aynı belleğe işaret ediyor. Biri oraya “pico” yazdığında, diğeri okuduğunda “pico” görüyor. İşte UAF’ın gizemi bu!
Şekil 2: Exploit akış şeması
Bu flow chart, saldırıyı adım adım gösteriyor. Program başladığında x için malloc yapılıyor ve flag alanına “bico” yazılıyor. Biz menüden “Free x” seçeneğini seçiyoruz, bu noktada dangling pointer oluşuyor (kırmızı uyarı!). Sonra “Alloc 35 bytes” diyoruz, malloc bize aynı chunk’ı veriyor (çünkü freelist’te hazır duruyordu). Oraya "AAA...pico" yazdığımızda, aslında x‘in gösterdiği yere yazmış oluyoruz. Son adımda check_win() çağrılıyor ve x->flag kontrolü yapılıyor. Artık orada “pico” yazıyor, WIN!
Bellek Layout’u Analizi
Struct’ın bellekteki yapısını anlayalım:
Şekil 3: Bellekteki struct yapısı ve offset değerleri
Struct’ın belleğe nasıl yerleştiğini gösteren bu diyagramı ilk gördüğümde “aa, işte bu yüzden struct padding önemli!” dedim. C’de bir struct tanımladığınızda, compiler alanları sırayla bellek bloklarına yerleştirir. Burada a, b, c dizileri her biri 10 byte. Yani a 0-9 arasında, b 10-19’da, c 20-29’da. En sonda 5 byte’lık flag alanı 30. byte’tan başlıyor. Exploit yazarken bu offset bilgisi altından değerli! Çünkü biz flag alanını değiştirmek istiyoruz. Anladık ki 30 byte’lık bir dolgu (padding) ile a, b, c‘yi geçip tam flag‘ın üstene düşebiliriz.
flag alanı 30. byte’tan başlıyor. O halde payload’ımız şöyle olmalı:
Şekil 4: Payload yapısı ve belleğe yerleşimi
İşte payload’ın görsel hali! Üstteki kısımda bizim hazırladığımız veriyi görüyorsunuz: 30 adet ‘A’ harfi (padding) ve arkasından “pico”. Alttaki kısım ise bu verinin belleğe nasıl yerleştiğini gösteriyor. ‘A’ harfleri a[10], b[10], c[10] alanlarını tamamen dolduruyor (toplam 30 byte). Sonrasında gelen “pico” stringi ise tıpkı bir ok gibi doğrudan flag alanına oturuyor. Bu precision (kesinlik) exploit yazımında çok önemli. 1 byte fazla veya eksik yazsanız, ya flag‘ı kaçırırsınız ya da paşa paşa segmentation fault yersiniz!
İlk 30 byte ile a, b, c alanlarını dolduruyoruz. Sonraki 4-5 byte ise flag alanına "pico" yazıyor.
Exploit Kodu
from pwn import *
conn = remote('tethys.picoctf.net', 51037)
conn.recvuntil(b'Enter your choice: ')
# Adım 1: x'i free et
conn.sendline(b'5')
conn.recvuntil(b'Enter your choice: ')
# Adım 2: Aynı boyutta yeni allocation yap
conn.sendline(b'2')
conn.recvuntil(b'Size of object allocation: ')
conn.sendline(b'35')
conn.recvuntil(b'Data for flag: ')
# Adım 3: Payload gönder (+ b'pico'
conn.sendline(payload)
conn.recvuntil(b'Enter your choice: ')
# Adım 4: Win kontrolünü tetikle
conn.sendline(b'4')
conn.interactive()
Peki Neden Çalışıyor?
glibc’nin malloc implementasyonu, free edilen chunk’ları bir listede (freelist) tutuyor. Aynı boyutta yeni bir allocation istediğinizde, önce bu listeye bakıyor. Eğer uygun boyutta free edilmiş chunk varsa, yeni bellek ayırmak yerine onu veriyor.
Bizim durumumuzda:
xpointer’ını free ettik (sizeof(object)kadar)- Sonra 35 byte istedik
- malloc: “Aa dur, ben bunu az önce geri almıştım. Al sana aynısı!”
- Ama
xpointer’ı hala o adresi gösteriyordu
Biz yeni allocation’a "AAA...pico" yazdık. Bu aslında x‘in gösterdiği yere yazıldı.
Sonuç: x->flag artık "pico" oldu, check_win() geçti, flag ortaya çıktı!
Özet ve Öğrendiklerimiz
Use-After-Free (UAF) zafiyetleri şu tipik pattern’de oluşuyor:
- Bir pointer için bellek ayrılır
- Bellek free edilir ama pointer NULL yapılmaz (dangling pointer)
- Aynı bellek başka bir allocation’a verilir
- Eski pointer üzerinden erişim yapılır → kontrol edilen veriye erişilmiş olur
Bu challenge’da tam olarak bunu yaptık:
xfree edildi ama kullanılmaya devam etti- Biz de o belleğe kendi verimizi yazdık
- Program eski pointer üzerinden okuduğunda bizim verilerimizi gördü
Gerçek Dünya Etkisi:
UAF zafiyetleri modern sistemlerde ciddi güvenlik açıklarına yol açar. Tarayıcılarda (Chrome, Firefox), işletim sistemlerinde ve kritik uygulamalarda UAF zafiyetleri yüzünden code execution mümkün olabiliyor.
Nasıl Önlenir:
- Free’den sonra pointer’ı
NULLyapın - Smart pointer’lar kullanın (C++’ta
unique_ptr,shared_ptr) - Memory sanitizer araçları kullanın (AddressSanitizer, Valgrind) 30 byte padding + “pico”) payload = b’A’ * 30
Yorumlar