Uygulama 6 | DHCP Starvation - Network DDoS Attack

UYARI: "BU BLOG İÇERİSİNDE YER ALAN HER İFADE VE GÖSTERİLEN HER YÖNTEM YALNIZCA EĞİTİM VE FARKINDALIK YARATMA AMAÇLIDIR."

Bu yazıda sizlere, genel yapısı Scapy modülünden oluşan bir ağ saldırısı göstereceğim. Bu saldırı, Deauthentication saldırısına benzerdir. Ancak bu, sadece bağlı olduğunuz ağ içerisinde gerçekleştirebileceğiniz bir saldırı türüdür.

Bu saldırı çalıştığı süre boyunca modeminiz hizmet veremez hâle gelecektir; önlemlerinizi alınız. Saldırının temel aldığı yapı DHCP sunucusudur. Bu saldırı ile DHCP havuzunda bulunan bütün IP adreslerini meşgul edeceğiz ve havuzda, diğer gerçek cihazlara verilecek bir IP kalmayacak. Bunun sonucunda cihazlar ağa bağlanamayacak ve modem hizmet veremez hâle gelecektir.

Bu saldırı; yeri geldiğinde savunma, yeri geldiğinde ise (daha çok) saldırı olarak kullanılabilecek bir saldırıdır. Saldırıda kullanacağımız ifadeleri anlamanız için ilk önce Scapy modülünü ve içerisinde verdiğim blogları iyice okumanız, öğrenmeniz gerekir. Sonrasında optparse modülünü de okursanız bu projeyi tam anlamıyla kavrayacaksınızdır.

Başlayalım.

İlk önce DHCPstarvation.py isimli bir dosya oluşturuyorum ve içerisine scapy ile optparse modüllerini ekliyorum.

Sonrasında yapmamız gereken ilk şey, bir optparse objesi oluşturmak olmalıdır. Kodu buna göre şekillendireceğim.

Bunun için parser = opt.OptionParser() diyerek parser isimli bir obje oluşturdum. optparse modülünü anlattığım blogdan bileceğiniz üzere OptionParser() içerisine bir kullanım kılavuzu (usage) oluşturdum. Sonrasında bu kullanım kılavuzuna parser.usage şeklinde ulaşacağız.

Kılavuza baktığınızda RECOMMENDED python3 şeklinde bir ibare görüyorsunuz. Bu ifade, kullanıcıya python3 kullanmasını önerdiğimiz anlamına gelir. Biliyorsunuz ki Scapy python3 ile daha stabil çalışıyor.

Sonrasında usage1 ve usage2 şeklinde iki çalıştırma şekli belirdim. Bu noktada kullanıcının, bu kodu sudo anahtar kelimesini girerek çalıştırması gerektiğini; -i veya --interface yazdıktan sonra da hâlihazırda kullandığı interface'ini girmesi gerektiğini söyledik (sudo python3 <dosya_ismi.py> -i <interface>):

Sonrasında [*] başlıklı ifadelerde; bu saldırı sırasında kullanılacak varsayılan IP adresinin 1.2.3.4 olduğunu ve saldırıyı sonlandırmak için CTRL + C kombinasyonunu kullanabileceğini söyledik.

Son olarak; bunun yalnızca eğitim amaçlı olduğunu ve bu saldırının söz konusu ağı program çalıştığı süre boyunca yok edeceğini söyleyerek onu uyardık. Dediğim gibi; kodu buna göre şekillendireceğiz.

O zaman şimdi, -i ve --interface ifadeleri için bir opsiyon eklememiz gerekiyor.

parser objemizin add_option fonksiyonu ile bu opsiyonları ekledik. help menüsü için ise parser.usage kullanımı sayesinde OptionParser() ifadesine erdiğimiz kılavuzu çağırdık. Bu noktada kullanıcı eğer sudo python3 DHCPstarvation.py --help derse karşısına bu kılavuz gelecektir. Sonrasında dest="interface" diyerek belirttiğimiz bu opsiyonların interface isimli bir değişkende tutulacağını belirttik.

20. satıra geldiğimizde (option, args) şeklinde bir Tuple belirledik ve bunu, parser objemizin argümanlarına (opsiyonlarına) eşitledik. Bu noktada options, -i ve --interface ifadelerini tutacakken args, onlara verilen değeri tutacaktır.

22. satırda bulunan interface = option.interface kodu, opsiyonlarımızın ve biraz önce dest="" içerisine verdiğimiz değişkenin bağlandıklarını ifade eder. Yani artık bunlar bir bütün.

Sonrasında main() isminde bir fonksiyon oluşturdum. Asıl saldırıyı bu fonksiyonda yapmak istiyorum. Fonksiyonun içerisine ilk olarak sc.conf.checkIPaddr = False ifadesini veriyorum. En başta bulunan sc. ifadesinin, zaten Scapy'nin kullanımıyla alakalı olduğunu biliyorsunuz.

Önceki bloglardan bileceğiniz üzere; normalde yolladığımız isteklerin hangi IP adresinden geldiği otomatik olarak belirlenir ve ona göre bir yanıt verilir. İşte bu durum, conf.checkIPaddr ile kontrol edilir ve bu, varsayılan olarak True değerindedir. Yani her zaman çalışır durumdadır; zaten öyle olması gerekir. Bu noktada biz bunu, ona False değerini vererek kapatıyoruz. Bu sayede yollayacağımız paketlerin nereden geldiği sorgulanmayacak, yanıt vermek için kontrol edilmeyecektir.

Şimdi, paketimizi hazırlamamız lazım. Amacımız, DHCP havuzundaki IP adreslerin hepsini boşa çıkarmak. Aşağıdaki görsel için kafanızın karışmasını istemiyorum; hepsini tek tek anlatacağım.

dhcpDiscover yani "DHCP Yayını" isimli bir değişken belirledim ve aynı satırda Ether() paketimin içeriğini belirliyorum. sc.Ether() dedikten sonra parantezler arasına dst="" ifadesini veriyorum. Bu, bu paketin hedefini gösterecektir. İçerisinde bulunan ve MAC adresine benzeyen "ff:ff:ff:ff:ff:ff" ifadesindeki her 'ff', hexadecimal olarak bakıldığında 255 sayısına tekabül eder. Bir IP aralığının ise maksimum değerinin 255.255.255.255 olduğunu Scapy içerisinde verdiğim bloglardan biliyorsunuzdur. Her IP adresine sahip olan cihazın da bir MAC adresi olacağına göre bu kod sayesinde bütün MAC adreslerini hedef alıyoruz. O zaman biz bunu kullanırsak bir broadcast yayını yapmış oluruz yani ağdaki her cihaza istek atmış oluruz.

Attığımız bu istekler karşılığında her istek için rastgele bir MAC adresi belirlememiz gerekir. Hemen yanında bulunan src=sc.RandMAC() bunu ifade eder. Ether() paketinin içinde dst= yani hedefi broadcast olarak belirledik. Sonrasında bir kaynak da belirtmemiz lazım ve bunu src= ifadesindeki rastgele MAC adresleriyle yapıyoruz. Satırın sonunda bulunan ters slash (\) ise, sonraki satırları altına yazacağımız için gerekli bir ifadedir. Elbette, isterseniz hepsini yan yana yazabilirsiniz. Ancak bu şekildeki kullanım daha düzenli olacaktır. 

Görseli tekrar gözümüzün önüne koyup bir sonraki satırı inceleyelim.

29. satıra geldiğimizde bu sefer sc.IP() şeklinde bir kullanım ile IP() header'ını düzenliyoruz. Bu noktada src="1.2.3.4" ifadesi, atacağımız bu istekler sırasında paketin içinde var olacak IP adresimiz olacaktır; kendisi sahte bir IP adresidir. Zaten 24. satırdaki yeşil yazıda, bu değerin değiştirilebileceğini söyledik. dst= ile belirlediğimiz IP adresleri ise, biraz önce söylediğim maksimum aralığı hedef alıyor. Bu sayede biraz önce ff:ff:ff:ff:ff:ff ile bütün MAC adreslerini nasıl hedef aldıysak; 255.255.255.255 ifadesi ile ağdaki bütün IP adreslerini hedef aldık.

30. satırda bir UDP başlığı (header) hazırladığımızı görüyorsunuz. Bu noktada sport ve dport sırasıyla kaynak ve hedef portları ifade ederken sport=68 ifadesi 'kullanıcıyı yani bizi' ifade eder; dport=67 ifadesi ise 'sunucuyu ifade eder' ifade eder. Yani bu işlemler yapılırken kullanacağımız port, 68 numaralı port olacaktır ve paketin gideceği port ise sunucunun portu yani 67 numaralı port olacaktır; bildiğiniz client-server yapısının basit hâlidir.

Görseli tekrar önümüze koyalım ve devam edelim.

Bu seride daha önce görmediğiniz bir yapı mevcut; 31. satırda bulunan "BOOTP". BOOTP dediğimiz yapı, bir önyükleme protokolüdür (Bootstrap Protocol). BOOTP, cihazları sunucularla ilişkilendirmeye yarar ve dinamik bir yapısı vardır. BOOTP, programları kaynaklarına atama işlemini yapar ama IP adresi ataması da yapabilir. Bu noktada ilk başta DHCP ile BOOTP yapısını birbirine karıştırmanız doğaldır. O zaman kısa bir paragrafla bu ikisini birbirinden ayıralım.

DHCP yapısını, BOOTP'nin bir gelişmiş versiyonu olarak kabul edebiliriz. DHCP, ana bilgisayar yapılandırma protokolü iken BOOTP, önyükleme protokolüdür. DHCP, cihazlara geçici bir IP adresi sağlarken BOOTP, geçici IP adresi sağlamaz. DHCP'de otomatik bir yapılandırma söz konusu iken BOOTP'de bu, el ile yapılır. Bu yüzden DHCP'deki hata oranı, BOOTP'ye göre oldukça azdır. Ayrıca BOOTP telefonları desteklemez ama DHCP destekler. BOOTP yapısında istemciler (client), MAC adresleriyle tanımlanır. 

O zaman buradaki sc.BOOTP(chaddr=sc.RandMAC) ifadesi, önyükleme sırasında MAC adresinin rastgele bir şekilde atanmasını sağlayacaktır.

Görseli tekrar alalım.

32. satırdaki sc.DHCP() ifadesinde aslında DHCP paketinin konfigürasyonunu sağlıyoruz. Bu noktada options=[] ile verdiğimiz opsiyonlarda bu mesajın bir yayın mesajı olduğunu ve bunun, işlemleri bitirdikten sonra biteceğini söylüyoruz.

Şimdi, paketimiz hazır olduğuna göre bunu ağa yollamamız gerekiyor.

Dikkat edelim; paketimizde Ether() kullandığımız için bu paketi yollarken send() değil sendp() fonksiyonunu kullanıyoruz. sc.sendp(dhcpDiscover) derken, dhcpDiscover değişkeninin tuttuğu paketi yollayacağımızı belirtiyoruz. Sonrasında gelen iface=interface ifadesi ise, parser objesinin opsiyonlarına verilecek interface değerini ifade eder. Buradaki iface, Scapy içerisindeki değişken olurken interface, biraz önce bizim tanımladığımız değişkendir.

Bu paketi sürekli yollamak için while veya for döngüsü kullanmak yerine loop=1 ifadesini veriyoruz. Bu ifade sayesinde programı durdurmadığımız sürece bu paket yollanacaktır. Sonrasında gelen verbose=1 ifadesinin anlamı; "paketler detaylı olsun" şeklindedir. Bu paketleri yollarken aynı zamanda Wireshark gibi programlardan yararlanarak bunları yakalayabiliriz. Yakaladığımız paketlerin içeriğinin detaylı olması için bu ifadeyi veriyoruz; vermeseniz de olur. Ben de zaten görmeniz için koydum.

Evet, main() fonksiyonumuz artık hazır. Kodun nasıl çalışacağını belirledik ama bunları kontrol etmedik. İlk olarak; başka bir kod sayfasıyla etkileşimi olmaması adına klasik if __name__ == "__main__": ifademizi koymak istiyorum.

"Eğer çalıştırılan dosya bu ise main() fonksiyonundaki işlemleri yerine getir" dedim. Peki, kullanıcı eksik veya hatalı bir çalıştırma sağlarsa ne yapacağız?

OptionParser() ifadesine verdiğimiz kullanım kılavuzu ile aslında kullanıcı odaklı olduk. Ancak bu kesinlikle yetmez; kullanıcının kafasında hiçbir soru işareti kalmaması gerekiyor.

Örneğin kullanıcı sudo anahtar kelimesini girmediğinde Kali bunu çalıştırmıyor çünkü yetkili bir çalıştırma bekliyor. Bu ve bu tarz sorunların önüne geçmek için bazı kontroller sağlamalı, kullanıcı hata yaptığında onu uyarmalı ve ona doğru yolu göstermeliyiz.

Örneğin kullanıcı interface bilgisini girmeyebilir. Bu yüzden if options.interface == None yani "kullanıcı, interface bilgisi vermezse" diyorum. Sonrasında altına print(parser.usage) diyorum ve exit() ile programı sonlandırıyorum.

Peki, kullanıcı eğer interface bilgisini verirse ne olsun? Bu durumda bir else: koşulu açıyorum ve kullanıcının, interface bilgisini vermesi dahilinde olacakları göz önüne alıyorum.

Öncelikle kullanıcı bize interface bilgisini verse bile sudo kalıbı olmadan program çalışmayacaktır. Az önceki ekran görüntüsünde PermissionError: şeklinde bir hata aldık. İşte, bunu kontrol edeceğiz. Bunu, os modülü ile kontrol edebilirsiniz ama ben, seride daha önce görmediğiniz bir yapıyı kullanacağım; try-except blokları.

Bu yapıda ilk önce try: bloğunu açıyoruz ve "bu kodları dene" diyoruz. Sonrasında except bloğu açıyoruz ve denenen bu kodlarda oluşan hataları yakalayıp hata koduna göre kullanıcıya bir mesaj veriyoruz. Örneğin;

else: bloğu içerisine bir try: bloğu açıyorum ve oraya main() ifadesini yerleştiriyorum. Bu noktada şunu demiş oluyoruz: "main() fonksiyonu içerisindeki kodları çalıştırmayı dene". Çalıştırma esnasında eğer PermissionError alırsak diye except PermissionError: şeklinde bir blog açıyoruz ve içerisine, OptionParser() içinde belirlediğimiz kılavuzu yazdırma işlemini yapıyoruz. Bu noktada söylediğimiz şey ise şu: "Eğer PermissionError kodlu bir hata alırsan kullanım kılavuzunu yazdır". İsterseniz deneyelim.

Gördüğünüz gibi az önce kodu çalıştırırken sudo koymadığım için hata mesajı aldık. Ancak şimdi, hata mesajı yerine kullanım kılavuzumuz ekrana yazdırıldı. İşte, hata yakalamak bu şekilde gerçekleşiyor. İlerleyen zamanlarda bu konuyu daha detaylı bir şekilde ele alacağız.

Örneğin kullanıcı, var olmayan bir interface bilgisi verdi, o zaman ne olacak?

Gördüğünüz gibi; şu an benim sadece eth0 isimli bir interface'im var ama oraya wlan0 yazdım. Aldığım hata ise OSError. O zaman hadi, bunu da kontrol edelim.

Tekrar bir except bloğu açtım ve except OSError: diyerek "OSError hatasını yakala" dedim. Sonrasında bloğun içerisine girdim ve bu hatanın yakalanması durumunda kullanıcıyı uyardım. İsterseniz ek olarak kullanım kılavuzunu da yazdırabilirsiniz.

Kodumuz tamamlandı, şimdi çalıştıralım ve neler olduğunu görelim.

Kodu doğru bir şekilde çalıştırdık. Bağlantımın kopmaması için kısa süre sonra bunu CTRL + C ile durdurdum ancak siz biraz daha bekleyerek sonuçlarını görebilirsiniz. İnternetiniz gidecektir ama kodu durdurduktan bir süre sonra modem tekrar eski hâline dönecektir. Ağın işlevini yitirmesi bir dakika içinde gerçekleşecektir.

Bu kodu GitHub gibi online platformlarda ya da ödevlerinizde kullanacaksanız lütfen kaynak bildirin. Sevgiler.

Kodun Kesintisiz Hâli

UYARI: "BU BLOG İÇERİSİNDE YER ALAN HER İFADE VE GÖSTERİLEN HER YÖNTEM YALNIZCA EĞİTİM VE FARKINDALIK YARATMA AMAÇLIDIR."


Yayınlanma Tarihi: 2022-12-06 15:20:03

Son Düzenleme Tarihi: 2022-12-11 17:25:01