Modern işletim sistemlerinde bir program çalıştırıldığında, işletim sistemi program için belirli bir bellek alanı ayırır. Bu bellek alanı, memory layout (bellek yerleşimi) adı verilen yapıya sahiptir. Bellek yerleşimi, programın farklı ihtiyaçlarına göre ayrılmış bölümlerden oluşur. Bu yazıda, bu bölümleri detaylarıyla inceleyecek ve özellikle stack yapısı ve stack operasyonlarına (PUSH, POP) odaklanacağız.
1. Bellek Yerleşiminin Bölümleri
Tipik bir programın bellek yerleşimi aşağıdaki segmentlerden oluşur:
1.1 Stack (Yığın)
- Geçici verileri depolamak için kullanılır.
- Özellikle fonksiyon çağrıları sırasında oluşturulan parametreler, yerel değişkenler ve geri dönüş adresi burada tutulur.
- LIFO (Last-In First-Out) prensibiyle çalışır: en son giren veri ilk çıkar.
- Program çalışırken dinamik olarak büyüyüp küçülebilir.
- Stack, yüksek bellek adreslerinden düşük bellek adreslerine doğru büyür. (Aşağıya doğru ilerler)
- Her fonksiyon çağrısı için yeni bir stack frame (yığın çerçevesi) oluşturulur.
1.2 Heap
- Dinamik olarak ayarlanan veri (örneğin
malloc,new) burada tutulur. - Programcı tarafından manuel olarak yönetilir.
- Stack’in tersine, düşük bellek adreslerinden yukarıya doğru büyür.
- Heap bellek uygun şekilde yönetilmezse memory leak (bellek sızıntısı) meydana gelebilir.
1.3 Uninitialized Data Segment (.bss)
- Başlangıç değeri atanmamış global ve statik değişkenler burada yer alır.
- Program başlatıldığında bu bölgedeki veriler 0 ile sıfırlanır.
1.4 Initialized Data Segment (.data)
- Başlangıç değeri atanmış global ve statik değişkenler bu bölgede yer alır.
- Program çalışmadan önce işletim sistemi tarafından hazırlanır.
1.5 Text Segment (.text veya Code Segment)
- Programın makine diliyle yazılmış kodları (talimatlar) burada bulunur.
- Salt okunur bir bellek alanıdır. Kodun kazara değiştirilmesini engeller.
- Exploit’leri önlemek için genellikle bu segment executable (çalıştırılabilir) ama writable (yazılabilir) değildir.
2. Stack Yapısı
Stack, fonksiyon çağrıları için ayrılmış bir bloktur ve aşağıdaki elemanlardan oluşur:
- Return Address (Geri dönüş adresi): Fonksiyon tamamlandıktan sonra hangi noktaya geri dönüleceğini belirtir.
- Parameters (Parametreler): Fonksiyona gönderilen veriler.
- Local Variables (Yerel Değişkenler): Fonksiyon içinde tanımlanan değişkenler.
- Saved Base Pointer (ebp): Önceki fonksiyonun yığın çerçevesine dönüşü sağlar.
Fonksiyon çağrıldığında:
ebpdeğeri yığına kaydedilir.espdeğeri, yeni yığın çerçevesi için ayarlanır.- Parametreler ve yerel değişkenler için boşluk ayrılır.
- Fonksiyon tamamlandığında
rettalimatı ile geri dönüş yapılır.
3. Stack Operasyonları
Stack’e veri eklemek veya çıkarmak için özel talimatlar kullanılır:
PUSH (Yığına Ekleme)
- Yığının üstüne bir veri ekler.
esp(stack pointer) kaydı önce bir miktar azalır, ardından değer yeni konuma yazılır.- Örnek:
push eax ; eax kaydındaki değeri yığına ekler.
POP (Yığından Çekme)
- Yığının en üstündeki veriyi alır ve bir kayda aktarır.
espkaydı bir miktar artar (yani o alan artık kullanılmaz).- Örnek:
pop ebx ; yığının en üstündeki değeri ebx kaydına alır
Stack Yapısında Dikkat Edilmesi Gerekenler
- Stack overflow: Çok fazla veri eklenirse stack belirlenmiş sınırları aşar. Bu durumda program çöker.
- Buffer overflow: Stack üzerinde bulunan bir değişkenin sınırlarının aşılması, geri dönüş adresinin üzerine yazılmasına neden olabilir.
- Stack genellikle işletim sistemi tarafından koruma altına alınır (
stack canary,DEP,ASLRgibi mekanizmalar).
Kaynakça:
Exploit Geliştirme 101 (2. Baskı) – M. ALPARSLAN AKYILDIZ
Hacking: The Art of Exploitation, 2nd Edition