PICOCTF PicoCTF 2024 - Heap 3: Use After Free Exploitation

Navigation

Computer Science

Mathematics

Security

PicoCTF

HackTheBox

TryHackMe

Geometry

Cheatsheet

Sosyal Medya

Güncel Makaleler

Yükleniyor...
Erciyes Uni.
Bilgisayar Muh.
CBFRPRO CBTEAMER CNPEN eMAPT

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:

  1. Program başlıyor, x için heap’te yer ayrılıyor, flag = "bico" oluyor
  2. Menüden seçenek 5’i seçerek x‘i free ediyoruz
  3. x hala aynı adresi gösteriyor ama o bellek artık “serbest”
  4. alloc_object() ile yeni bir allocation yapıyoruz
  5. malloc büyük ihtimalle az önce free ettiğimiz yeri geri verecek (glibc malloc optimizasyonu)
  6. Yeni allocation’a yazdığımız veri, aslında x‘in gösterdiği yere yazılacak!

Use After Free Attack Flow Ş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!

Exploit Logic Diagram Ş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:

Struct Memory Layout Ş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ı:

Exploit Payload Visualization Ş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:

  • x pointer’ı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 x pointer’ı 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:

  1. Bir pointer için bellek ayrılır
  2. Bellek free edilir ama pointer NULL yapılmaz (dangling pointer)
  3. Aynı bellek başka bir allocation’a verilir
  4. Eski pointer üzerinden erişim yapılır → kontrol edilen veriye erişilmiş olur

Bu challenge’da tam olarak bunu yaptık:

  • x free 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’ı NULL yapı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
Paylaş

Yorumlar

🔔
Yeni yazılardan haberdar ol! Bildirim al, hiçbir yazıyı kaçırma.