Buffer overflow tekniği tamamen yazılan program’ın hafızada yaptığı işlemler baz alınarak kullanılır. Bunun için bir programın ram bölümünde kullandığı stack ve heap ismindeki bölümlerin iyi bilinmesi gerekiyor. Üst seviye bir dil kullanan yazılım geliştiricileri bu bölümleri nasıl kullanılacağına karar vermezler. Bu yüzden, genel olarak bu işlemleri onlar için framework yapar. Eğer programı geliştirdiğiniz framework böyle bir açığa sahipse, bu durumda yazdığınız program hiç bir zaman güvenli olamaz. Örneğin, Kaspersky 2015 verilerine göre java, toplam vulnerabilities’ in %15 üne sahip. Burada javada yazılan bir programı kastetmiyorum. JDK ve JRE yi kastediyorum tabiki. Yani siz bir bina kuruyorsunuz, ama binayı kurduğunuz zemin çok kaygan. Bu yüzden ne kadar sağlam bir bina yaparsanız yapın, zemindeki açıktan dolayı bina çöküyor. Bununla alakalı bilgilere ulaşmak için bu linke göz atabilirsiniz.

Bir program bu şekilde bir hafıza yapısı kullanır. User stack bölümü programdaki fonksiyonların içerisinde tanımlanan ve foksiyonlardan çıkıldıktan sonra işletim sistemi tarafından otomatik olarak silinen değişkenlerin tutulduğu bölümdür. Runtime heap bölümünde ise fonksiyonlarda malloc, new ve alloc gibi c fonksiyonları kullanarak ramden tahsil edilen alanlar tutulur. Heap bölümü ram’in kullanabileceği kadar fazla alanını kullanabilir. Fakat işletim sistemi her program için stack bölümüne sabit bir büyüklükte yer ayırır. Shared Libraries bölümünde yazılan programa dahil edilen kütüphanelerin içerisindeki değişkenler ve argümanlar tutulur. Ve en önemlisi, bir de yazılan programın’ makine dilindeki hali, ram’deki exec bölümünde tutulur. Bu yüzden kod içerisine yazılan hiç bir şifre ve gizli bilgi güvenli değildir. Herhangi bir debugger program kullanılarak ramdaki bu hafıza alanları okunabilir.

Stack frame’in kaba taslak şu şekilde bir yapısı vardır;

Func ismindeki bir fonksiyon, şu şekilde main içerisinden çağrılıyor olsun;

void func(char *str) 
{

  char buf[128];

  strcpy(buf, str);

}



int main(int argc, char* argv[])
{
   char * str= argv[1];
   func(str);
   return 0;
}

İşlemci, program counter ismi verilen bir register vasıtası ile program komutlarını sıra sıra çalıştırmaktadır. Doğal olarak program counter main fonksiyonunun ilk satırına geldiğinde stack bölümüne yukarıdaki programda tanımladığımız “str” pointer’ini ve içeriğini yazar. Daha sonra func isimli fonksiyonunun içerisine girmeden önce, program counter register’ının olduğu adresi stack bölümüne yazar. Bunun sebebi, fonksiyondan çıktıktan sonra program counter’ın nereden devam edeceğini hatırlaması içindir. Stack içerisinde iki çeşit işaretleyici kaydedici yani pointer vardır. Bunlar EBS(Frame Pointer) ve ESP(Stack Pointer) olarak isimlendirilir. Stack Pointer(ESP), stack’in genişlediği son adresi işaretler. Bu sayede stack’e kaydedilen herhangi bir veri okunmak istendiğinde Stack Pointer(ESP) okunmak istenen byte sayısı kadar geriye çekilerek okunmak istenen adres ya da değişken bulunur. Frame Pointer(EBS) ise son yazılan return adresin’in nerede olduğunu işaretler. Doğal olarak EBS ve ESP arasındaki bölümün girilen son fonksiyonun değişkenlerini tuttuğu bilinir. Eğer birden fazla iç içe fonksiyon varsa, bu fonksiyonlar sıra sıra bu frame şekilde arka arkaya yazılırlar. Son fonksiyon işlemini tamamladığında Stack Pointer(ESP) , Frame Pointer’ın(EBS) gösterdiği yere geri çekilir. Frame Pointer(EBS) ise işaretlediği yere daha önce kaydettiği bir önceki return adrese geri çekilir.

Şimdi gelelim bu hafıza yapısını nasıl manüpüle edeceğimize. Buffer overflow tekniği, stack bölümüne yazılan return adresi overflow ederek çalıştırmak istediğimiz ve komutların adresine döndürmeyi amaçlamaktadır. Yukarıda yazdığımız programda gördüğünüz gibi, strcpy fonksiyonu kullanılırken herhangi bir veri boyutu belirtilmemiş. Yani aslında 128 byte’lık bir alan ayrılmış olsa da ben bu alana 128 byte dan büyük veri yazabiliyorum. Bu açık sayesinde, 128 byte verinin sonunda return adresi de overwrite edebiliyorum. Yani eğer 136 byte veri yazarsam ve bu 136 byte verinin son 8 byte’ı benim belirlediğim bir adres olursa, bu sayede kendi programımı çalıştırabilirim.

Buradaki en büyük soru şu, çağıracağım ya da bu fonksiyondaki açık vasıtası ile ram’e yazdığım kodların adreslerini nasıl bileceğim? Aslında adresini bilemeyiz. Fakat tahmin edebiliriz. Ve yazdığım kodun büyüklüğü stackde ne kadar çok yer kaplarsa tahminlerimin yazdığım koda isabet etme olasılığı okadar yüksek olacaktır. Bu yüzden nope slide, dediğimiz yöntem kullanılır. Yani büyük bir alana, nope gibi yani aslında işlemciye hiç bir işlem yaptırmayacak gereksiz kodlar girilir. Ve bu nope slide’ın sonuna da çalıştırmak istenilen kodlar yerleştirlir. Bu sayede tahminlerden herhangi bir tanesi bu nope, işlemine denk geldiğinde işlemci bu nope, işlemini takip ederek sonunda, kötü amaçlı olarak yazılan koda ulaşır ve o kodu çalıştırır. Bu sayede istenilen program çalıştırılabilir..

Bu açıktan dolayı işletim sistemleri stack’ üzerinde kodların çalıştırılmasına izin vermeme kararı almış. Yani stack bölümüne çalıştırılabilir kodları koyamıyorsunuz artık. Fakat, bu hackerları durduramamış tabiki. Bu sefer, Heap bölümünü overflow ederek oradaki adresleri, heap bölümüne yerleştirdikleri kodlara yönlendirmişler. Heap over flow atak olayını ise bir sonraki yazımda anlatacağım. Şimdilik hoşçakalın.

Referans: https://crypto.stanford.edu/cs155/lectures/02-ctrl-hijacking.pdf

Yusuf

Yusuf

Bir Mühendis.

Önerilen makaleler

2 Yorum

  1. Avatar

    Stackoverflow sitesinin isminin buradan geldiğini hiç tahmin etmezdim.

    1. Yusuf

      Evet aslında çoğu kişi bunu bilmiyor. 🙂

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Translate »