Stack Overflow Nedir, Nasıl İstismar Edilir?

Merhaba,

Bugün stack tabanlı taşma zafiyetlerinden olan stack overflow zafiyetinden ve nasıl istismar edilebileceğinden bahsedeceğiz. İşe aşina olanların kolaylıkla hatırlayacaklar PcMan ftp server yazılımında var olan bu zafiyeti istismar edip, pythonda exploitini geliştireceğiz.

Bu zafiyeti istismar etmeden önce bilmemiz gereken kavramlar var. Bundan da daha öncesinde, sizlere tavsiye edeceğim konu, bir programın ram bellek üzerindeki davranışlarının neler olduğunu anlamanız. Bir programı çalıştırdığınızda bu process (işlem ya da program) ram belleğe yükleniyor ve bu programın çalışmasından, programın kapanıp solanmasına kadar ram bellekte neler oluyor? bunu bilmek sizi bir sıfır önde tutacaktır. Diğer yandan bugün gene üzerinde duracağımız x86 registerlar konusuna az çok hakim olmanız sizi gene bir sıfır önde başlatacaktır.

Şimdi ilk olarak heap ve stack kavramlarını ve last in first out –lifo- kavramını görelim.  

Stack nedir?

Ram bellekte stack, programın çalışması sırasında geçici verilerin saklandığı bir alan olarak düşünülebilir. Stack, LIFO yani last in first out (son giren ilk çıkar) prensibiyle çalışır. Yani stack’e eklenen verilerden en son giren ilk önce çıkar. Bunu bulaşık yıkarken tabakları köpürtüp, üst üste koyup, durulama aşamasına geçerken de en üste koyduğumuz tabaktan durulamaya başlamaya benzetebilirsiniz. Kısaca stack, geçici verilerin belirli bir düzende tutulduğu ve yönetildiği bir yapının adıdır.

Heap nedir?

Ram bellekte dinamik bellek yönetimi için kullanılan bir alandır. Stackden farklı olarak heap alanı, programın çalışma süresi boyunca ihtiyaç duyulan değişkenlerin dinamik olarak bellek alanı tahsisi edip bırakılmasına olanak tanır. Programın çalışma zamanında ihtiyaç duyulup tahsis edilen bu bellek alanları heap alanından tahsis edilir.

Stack Overflow Nedir?

Her programın ram bellekte saklandığını, bu programın gerek duyduğunda geçici olarak bellek alanlarını kullandığını, tahsisat yaptığını ve tahsis ettikleri bellek alanlarını geri bıraktıklarını biliyoruz.

Stack overflow bir programın stack belleğinde tahsis edilen alanın dolması ve taşması durumudur. Bu durumda program hata verebilir, çökebilir (crash olabilir).

Basit bir C programı yazalım ve bu C programı overflow zafiyetine sahip olsun, bizde bu zafiyeti bunun üzerinden anlamaya çalışalım.

#include <stdio.h>
#include <string.h>

int main(){
Char mesaj[10];
Char input[100];
Printf(“Bir mesaj girin: ”);
Fgets(input, sizeof(input), stdin);
Strcpy(buffer,input);
Return 0;
}

Burada bazı buffer tanımları mevcuttur. Char türden mesaj dizisi tanımlanmış ve kapasitesinin 10 karakter olacağı belirtilmiştir. Gene char türden input dizisi tanımlanmış ve kapasitesinin 100 olduğu belirtilmiştir.

Fgets fonksiyonu kullanılarak programı çalıştıran kullanıcıdan bir mesaj girmesi gerektiğini söylüyor ve bunu 100 karakter boyutundaki input dizisine yazıyor. Input dizisine yazılan bu mesaj strcpy fonksiyonu kullanılarak input dizisinden mesaj dizisine strcpy(mesaj, input)  şeklinde kopyalanıyor.

Burada kullanıcıdan alınacak mesaj, 100 karakter sınırındaki input dizisine yazdırılıyor. Yani 100 karakter yerine daha fazlası girilirse bu kabul edilmez. Şurada dikkat edilmesi gereken şey input dizisinin 100, mesaj dizisinin 10 karakter sınırına sahip olması. Yani mesaj dizisine 10 dan fazla karakter giremeyiz, girersek ne olur? Şimdi burada overflow zafiyetine sebebiyet veren durumlardan en önemlisi fazla veriyi alıp, o veriyi kaldıramayacak küçüklükte bir alana kopyalamak. Kopyalama sırasında kullandığımız strcpy fonksiyonu overflow tarzı zafiyetlerin oluşmasına sebebiyet verebilir.  Strcpy fonksiyonu güvenli bir fonksiyon değildir. Eğer strcpy fonksiyonu kullanılarak 10 elemanlı bir diziye farklı bir dizide bulunan 10dan fazla karaktere, elemana sahip bir değeri kopyalamaya çalışırsak burada overflow zafiyeti tetiklenir. Mantık basit, 50 araç kapasiteli otoparka 60 araç alamazsınız.  Eğer fazla karakter girer ve karakter yüklerseniz program çöker.

Peki 10 byte kapasitesi olan bir diziye 100 byte veriyi kopyalarsak ne olur? Program taşar demiştik, bu geri kalan 90 byte ne olur? Programın akışını yanlış yönde etkiler ve çeşitli farklı bellek alanlarına yazılarak programın doğru akışını bozar. Burada virgül koyup, register’lardan çok kısa bahsetmemiz gerekir. (blogu okumadan öncesinde registerların neye yaradıklarını bildiğinizi varsayıyorum).

Registerlar nedir? Ne yaparlar?

Registerlar işlemcinin hızlı bir şekilde veri işleyebilmesi için kullanılan küçük, yüksek hızlı bellek alanlarıdır. Genellikle veri üzerinde işlem yapmadan önce bu veriler registerlara yüklenir. Mimarisine göre değişkenlik gösterebilse de x86 mimarisinde birkaç temel register türü vardır:

EAX, EBX, ECX, EDX, EIP, ESP, EBP registerlarıdır.

Bu registerlar çeşitli işlemler yapmaktadırlar. Genel amaçlı registerlar mevcuttur ve bu registerlar EAX, EBX, ECX, EDX registerlardır. Bunlar genel amaçlıdır ve farklı veri türlerini saklamak için kullanılabilirler.

EIP (Instruction Pointer), o an yürütülen komutun adresini tutar.

EBP (Base Pointer), stack üzerinde en üstteki adresi tutar.

ESP (Stack Pointer), stack çerçevesinde başlangıç adresini tutar. Stack de LIFO (Last In Firs Out) son giren yani ilk çıkacak elemanı gösterir.

Overflow zafiyetlerini sömürebilmemiz için registerların ne işe yaradıklarını iyi bilmek gerekiyor. Çünkü exploit edeceğimiz overflow zafiyetinin mantığında programın akışını, saldırganın zararlı kötü amaçlı kodlarının barındığı bellek adresine yönlendirmek var. Bu yönlendirme sonrasında kötü amaçlı kodların sistem üzerinde çalıştırılması ve zafiyetin istismarı var.

Saldırının nihai hedefine ulaşabilmesi için aşağıdaki adımları tek tek sırayla işleyip göreceğiz.

  1. Programın stackini doldurarak programı crash ediyoruz.
  2. Doldurulan stack alanı kaç byte veri gönderildiğinde crash oluyor?
  3. Prorgam kaçıncı byte veride crash oluyor?
  4. EIP register üzerine yazmak.
  5. Programın çalışmasını engelleyen kötücül karakterlerin tespit edilmesi (bad chars)
  6. EIP registerdan ESP register’a zıplayabilmek için doğru modülün tespit edilmesi
  7. ShellCode geliştirme
  8. Ve istismar.

Kullanacağımız araçları başta söylemiştik buna ek olarak immunity debugger, bizim tercih edeceğimiz debugger olacaktır.

Tercih edilecek işletim sistemi olarak windows’un herhangi bir sürümünü kullanabilirsiniz fakat pcman ftp server uygulaması eski bir uygulama olduğu için genellikle windows xp amcamızın zamanında kullanılıyordu. Bu yüzden exploiting aşamalarını xp makineden yapabilirsiniz. Ben windows 11 kullanacağım.

PCMan Ftp server yazılımını immunity debuggerda açıp run ediyoruz. Server running haldeyken, küçük bir python scripti yazıp programın crash oluşunu gözlemleyelim.

Şimdi PCMan Ftp Server yazılımında daha önceden keşfedilmiş bir overflow zafiyeti var. Bunu google, exploit-db gibi web sitelerinden araştırıp bulabilirsiniz. FTP bağlantısı yapmak için ftp serverın 21 portuna bağlandığımızda USER parametresi ile kullanıcı adını aldığı input alanında bir overflow zafiyeti daha önceden tespit edilmiş. Bizde çalışmamızı bu parametre üzerine yoğunlaştıracağız.

Bunun için aşağıdaki kodu yazıyorum.

Bu python scripti ile 192.168.1.113 makinesinin 21 ftp portuna bağlanıyor ve 2200 tane “A” harfini USER parametresine gönderiyor. Var olan overflow zafiyeti sebebiyle program çöküyor.

Gönderdiğimiz 2200 A harfi taşma zafiyetine sebep olmuş ve ESP, EIP registerlarına kadar yazmıştır. EIP registerı programın nereden çalışmaya devam edeceğinin adresini tutan register ve burada 41414141 değerini görüyoruz. A karakterinin hexadecimal karşılığı 41’dir.

Burada EIP registerına istediğimiz adresi yazıp programı zararlı kodumuzdan çalışmaya zorlamak için ilk önce programın tam olarak kaç tane A gönderdiğimizde çöktüğünü bulmamız lazım. Bunun için metasploit de pattern_create.rb dosyasını kullanıp birbirinden farklı 2200 tane karakter oluşturuyoruz.

Burada oluşturulan 2200 adet karakteri kopyalayıp python scriptimizde shellcode değişkenine değer olarak atıyoruz.

Ardından debugger da programı baştan başlatıp python scriptimizi tekrar çalıştırıyoruz.

EIP registerına yazan değeri kopyalıyoruz. Bu bize programın kaçıncı karakterden sonra çöktüğünü gösterecek. Bunun için metasploit de pattern_offset.rb aracını kullanıp EIP registerına yazan değeri –q parametresi ile yapıştırıyoruz.

Offset değerimiz 2003’müş. 2003 karakterden sonra EIP registerına yazabiliyormuşuz. Şimdi tekrar scriptimize dönüp 2003 tane A karakteri yazıp sonrasında 4 adet B karakteri ekliyoruz. Bu işin sonunda EIP de 42424242 değerini görmemiz lazım. B karakterinin hexadecimal karşılığı 42’dir.

EIP registerınıa 42424242 yazdırabildik.

Şimdi shellcode’umuzu msfvenom kullanarak oluşturmadan önce, bu shellcode içerisinde bulunmaması gereken ve program tarafından engellenen karakterlerin tespitini yapıp shellcode da badcharsların bulunmamasını sağlamamız gerekiyor.

İmmunity debugger da kullanabileceğimiz mona.py yardımcı scripti ile !mona bytearray yazarak karakter listelerine ulaşabiliriz. Bu karakter listesini kullanarak engellenen karakterleri tespit edip, scriptden bu karakterleri tek tek çıkartarak programı debuggerda her defasında yeniden başlatıp son karakter olan FF değerine kadar tüm karakterlerin (kötü karakterlerden arındırılmış şekilde) görüntülenmesini sağlamalıyız.

Programı tekrar çalıştırıp karakterleri gönderdiğimizde ESP registerına sağ tıklayıp Follow in Dump diyoruz. Sol alt da bizi gönderdiği yerde 00 00 00 değerini görüyoruz. Bu da x00 karakterinin badchar yani kötü karakter olduğunu bize gösteriyor. Ayrıca x00 değeri null-byte olarak da bilinmektedir. Scriptimizden \x00 değerini çıkartıp programı tekrar debuggerda açıyoruz ve scriptimizi tekrar çalıştırıyoruz.

X09 dan sonra x0a gelmesi gerekirken bu da engellenmiş. X0a değeri de bad char olduğunu anlıyoruz. Bunu silip tekrar programı çalıştırıp gönderiyoruz.

Bu işlemi son karaktere kadar devam ettiriyoruz. En sonunda tüm badcharlarımız şunlar oluyor:

x00 – x0a – x0e – x0d

Badcharslarımızı tespit ettik ve artık shellcode’umuzu hazırlayabiliriz. Bunun için msfvenom kullanacağım.

Msfvenom –f python –p windows/exec EXEC=calc –bad-chars=’\x0a\x0d\x00\x0e’

Bu komut ile hedef makinemizde çalıştıracağımız shellcode’umuzu oluşturuyoruz. Shellcode oluştururken shellcode içerisinde, tespit ettiğimiz badcharlar bulunmayacak ki programın çalışması engellenmesin.

Shellcode, hedef makine üzerinde calc.exe yani hesap makinesini açacak. Siz msfvenom ile reverse shell için bir shellcode oluşturabilir ve makine de session edinebilirsiniz.

Son olarak, ilk başlarda EIP registerına BBBB gibi karakterler yazdırmıştık. Programın bizim shellcode’umuzdan çalışmaya devam etmesi için EIP register’a ESP alanından çalışmaya devam etmesini sağlayacak JMP ESP komutunun bulunduğu adresi tespit etmemiz gerekmektedir. EIP register’ına yazacağımız JMP ESP komutunun adresi, programın stackden devam edip bizim shellcode’umuzu çalıştırmasına yarayacaktır.

Bu adresi tespit etmek için debuggerda View -> Executable Modules tıklayarak overflow zafiyeti barındıran PCMan FTPD programının kullandığı DLL dosyalarını görüntüleyebiliriz. Ayrıca “!mona modules”  ile bu görüntülemeyi sağlayabiliriz.

Burada ASLR ve SEH korumasının olmadığı bir dll dosyası tespit edip bu dosya içerisinde JMP ESP komutunu arayacağız. ASLR korumasına değinmek gerekirse, ASLR (Address Space Layout Randomization) eğer aktif ise (True) her program çalıştırıldığında bu DLL’in bellekte farklı bir adreste yüklenmesi sağlayabilir. ASLR, bir istismarın hedeflediği adreslerin rastgele olmasını sağlayarak exploit’i zorlaştırır.

PCMANFTPD2.exe ve Blowfish.dll dosyalarında SEH ve ASLR’in false olduğunu görüntüleyebilmekteyiz.

Bu dosyalar istismar için tespit etmemiz gereken JMP ESP komutunun adresini barındırıyorlarsa, JMP ESP adresini programın her çalışmasında tekrar tekrar kullanabileceğiz. Ben USER32.dll dosyasını kendime hedef seçtim ve bu dosya da JMP ESP komutunun adresini arayacağım.

View – Executable Modules sekmesini takip edip aşağıdaki ekrana ulaşıyorum ve USER32.dll dosyasına çift tıklıyorum.

Açılan ekranda  ctrl+f ile jmp esp komutunu aratıyorum.

75F60307 adresinde JMP ESP komutunu bulabildik. ESP registerına bu değeri yazacağız. Fakat bu adresi ters yazacağız.

Exploit geliştirmede jmp esp adresini ters yazmamızın sebebi, endianness (bayt sıralaması) kavramıdır. Çoğu modern işlemci mimarisinde, bellek adresleri küçük endian (little-endian) formatında saklanır.

Little-Endian ve Big-Endian Nedir?

Little-Endian: En düşük anlamlı bayt (least significant byte, LSB) önce, en yüksek anlamlı bayt (most significant byte, MSB) ise sonda saklanır.

Big-Endian: En yüksek anlamlı bayt önce, en düşük anlamlı bayt ise sonda saklanır.

Örneğin:

Eğer bir jmp esp adresiniz 0x0043410d ise, bellek, bu adresi little-endian formatında saklayacaktır. Yani, adres exploite şu şekilde yazılır:

0x0d 0x41 0x43 0x00

Bu yüzden exploit’imiz, bu adresi hedef belleğe doğru yerleştirebilmek için ters sıralamada yazmak zorundadır: \x0d\x41\x43\x00.

Neden Ters Yazıyoruz?

Çünkü işlemcinin bellekten adresi nasıl okuduğu, kullanılan endianness’e göre belirlenir. Intel ve x86/x64 mimarileri little-endian’dır, bu nedenle exploit kodları yazarken adresleri ters formatta yerleştiririz.

Exploitimizin son hali aşağıdaki gibidir.

Exploiti çalıştırdıktan sonra calc.exe’inin makinede çalıştığını görebilmekteyiz. Exploit içerisinde 10 adet no operation (NOPs) kodu kullandık. No operation komutu hiç bir şey yapmaması ve çoğu kez bellekte 1 byte yer kaplamasıdır. İşlemci shellcode’u çalıştırmadan önce bağlangıcını NOP’larla doldurarak shellcode’u güvene alır.

Kendinize iyi bakın görüşmek üzere.