Modüller | scapy

Oldukça önemli ve anlaması güç olabilen bir modülü anlatmaya çalışacağım. Ancak, anlattıklarımı anlayabilmeniz adına aşağıda linklerini verdiğim blogları okumak zorundasınız. Bu modül, daha çok Network ile alakalı olduğundan bu modülü anlamak için normal bir Python bilgisi yetmeyecektir.

IP (Internet Protocol)

MAC Adresleri

DNS (Domain Name System)

IP Bölüm 2 | Sayı Sistemleri Bölüm 2

OSI Katmanları

IP Bölüm 3 | TCP/IP

Ağ Çeşitleri

Ethernet

IP Bölüm 4 | Subnet Mask

DHCP

Ağ Cihazları

ping

ICMP

UDP

ARP

Sunucu (Server)

HTTP vs HTTPS

Yukarıdaki blogları okuduktan sonra okumanız gereken bloglar:

Kali'yi Tanıyalım

Wireless Adaptor ve Monitor Mode

Deauthentication (Yetkisizlendirme) Attack

DoS - DDoS Attack

Bu blog için Kali Linux üzerinde çalışacağım. Bu yüzden siz de UNIX bir sistem kullanırsanız uygulamanız daha mümkün olur. Windows üzerinde de Scapy kullanabilirsiniz ama bir ton kurulum gerekecek, bir sürü aksilik çıkacaktır.

Kali'yi açmadan önce VirtualBox veya VMWare üzerinden 'Bridged Adapter' seçeneğini seçerseniz daha sağlıklı olacaktır:

Scapy, Kali'de yüklü şekildedir. Ancak eğer yüklü değilse bir defa 'sudo apt-get update' komutunu verdikten sonra 'sudo pip3 install --pre scapy[complete]' komutunu verip Scapy'i yükleyebilirsiniz.

Şimdi, direkt olarak terminale girip 'sudo scapy' yazıyorum. Bu sayede Scapy'nin Shell'ine giriyorum. Nasıl ki python ya da python3 yazıp Python Shell'ine giriyorsak sudo scapy yazıp Scapy'nin Shell'ine giriyorum.

Sizi bu şekilde bir ekran karşılamalı. Dikkat ederseniz en sol alt köşede mavi renkli '>>>' ifadesi mevcut. İşte, Shell'e bağlandık ve artık hazırız. Bunların Python'a entegresini blogun sonunda göstereceğim. Zaten yapmanız gereken tek şey scapy.fonksiyon() olacaktır; diğer modüllerdeki gibidir.

Bir de, Wireshark programına ihtiyacımız var. Bu programı daha önce detaylı bir şekilde anlatmadım, sadece kesitler gösterdim ama aklınızda soru işareti kalmayacak şekilde anlatmaya çalışacağım. Kendisi bir ağ analiz aracıdır; ağ içerisinde kaçan göçen her şeyi yakalar. Bu program yüklü değilse aşağıdaki gibi yükleyebilirsiniz.

Ekranın en sağından başlayarak bakalım. sudo apt install wireshark deyip bunu Kali'ye yüklüyoruz. Sonrasında sol tarafta yeşil ok ile gösterdiğim kısımda Interface'inizin üzerine çift tıklamanız lazım. Bunu yaptığınız zaman o Interface üzerinden geçen ağ trafiği izlenecektir.

Programı açtığınız zaman sol üst köşede sarı ok ile belirttiğim buton, ağ taramasını başlatan butondur. Yanındaki mor ok ile gösterdiğim ise bunu durdurmaya yarar.

Bu program arka tarafta bu şekilde dursun, tekrar Scapy Shell'ine dönelim.

Scapy içerisinde kullanabileceğiniz fonksiyonları 'lsc()' fonksiyonunu çağırarak görebilirsiniz. 'List commands' kelimelerinin kısaltılmışıdır.

Eğer belli bir fonksiyon hakkında bilgi almak isterseniz 'help(<fonksiyon_adı>)' şeklinde bir komut verebilirsiniz. srp komutu ile ilgili bilgi almak istediğim için help(srp) dedim ve çıktımız aşağıdaki gibi geldi. Bu sayfadan çıkmak için q tuşuna basmanız yeterli olur.

İlk önce, ağdaki trafiği yakalamayı öğrenelim. Bu noktada göreceğimiz şey; hangi paketten kaç adet geçtiği bilgisidir.

"Sniff" kelimesi, "koklamak" anlamına gelir. Buradaki sniff'ten kastımız ise ağı koklamak, izlemek şeklindedir. Gördüğünüz üzere sağ tarafta bu işlem için sniff() fonksiyonunu çağırdım. Sonrasında tarayıcımı açıp ağ içerisinde biraz trafik yarattım. Son olarak bu fonksiyonun çalışmasını CTRL + C ile durdurdum ve bana bir sonuç döndürdü:

246 adet TCP, 1268 adet UDP ve 11 adet başka paketlerden (other) yakaladığını görüyoruz. Peki, bunu bir değişkene atayamaz mıyız?

sniffNetwork isminde bir değişken belirledim ve sniff() fonksiyonunu buna eşitledim. Bu işlemi yaptıktan sonra ağ analiz ediliyor olacaktır; CTRL + C ile bunu durdurabilirsiniz. Artık sniffNetwork isimli değişkenin içinde biraz önceki ağ taramamız duruyor. Dolayısıyla print(sniffNetwork) diyormuş gibi sniffNetwork diyerek bu değişkeni çağırırsanız, biraz önce tuttuğu trafiği size gösterecektir.

Şu kadar TCP, şu kadar UDP yakaladık ama kim kime yolladı, neler oldu bilmiyoruz; doğru mu? O zaman bir diğer fonksiyonumuza bakalım.

Gördüğünüz üzere summary() fonksiyonu sayesinde yakalanan her paketin içeriğini özet şeklinde görüyoruz. Zaten "summary" kelimesinin anlamı da "özet"tir.

Peki, yukarıdaki ekranda var olmayan üç paketi inceleyelim:

Ether / IP / TCP ne demek? Ether, OSI'nin ikinci katmanında bulunan bir yapıdır. IP ise üçüncü katmanda yer alan bir yapıdır. Bu noktada aralarda gördüğünüz / işareti ise, bunları birbirine eklemeyi temsil eder. Bir paket oluşturulurken OSI modeline göre nasıl ilerlediğini bildiğinizi düşünüyorum.

Dördüncü katmanda ne vardı peki? Elbette, TCP vardı. Hatırlatıcı (kırmızı kutucuk içerisindeki cümle):

Demek ki Ether/IP/TCP şeklindeki bir kullanım, bir paket oluşturmak için gerekli yapılardır ve birinci katmandan yukarı doğru çıkarken bunları iç içe koyup devam ediyoruz.

Peki, sonra ne oluyor? Mor kutuya baktığınızda göreceksiniz ki; hedef ve kaynak port bu kısımda pakete ekleniyor. Yeşil kutuya baktığınızda ise bu katmanın, yukarıdaki katmanlar arasında bir köprü görevi gördüğünü anlarsınız. Sonrasında beşinci katmanda ne oluyor?

Doğru tahmin; tarayıcımız için bir oturumun açıldığı kısım burası. Kısacası Ether/IP/TCP bu şekilde.

Şimdi, biraz önce aldığımız üç paketi tekrar koyalım, önümüzde dursun.

192.168.1.164:36612 > 216.58.206.195:http S

Bu ifade bize şunu söylüyor: "192.168.1.164 IP adresi, 36612 numaralı porttan (bu ben oluyorum), 216.58.206.195 IP adresinin (bu google.com oluyor) http (80) portuna bir SYN (S) isteği yolladı (>)."

Diğer iki pakette yazan IP adresleri ve başlıkları (header) buna göre yorumlayabilirsiniz. Peki, bu paket TCP ile taşınmadı mı? Peki, TCP'deki Handshake olayını hatırlıyor musunuz?

Ben bir SYN isteği yolladım; Google bana bir SYN-ACK (SA) paketi yolladı; ben ona bir ACK (A) paketi yolluyorum ve iletişim sağlanmış oluyor. Oradaki harflerin anlamı da bu şekildedir.

sniff() fonksiyonu sürekli olarak ağı kokluyordu. Peki, örneğin; "6 tane paket topla" diyebilir miyim?

pkt = sniff(count=6) ifadesi bize, ağ trafiğinden 6 adet paketi tutacaktır. Bunu, count=6 demeden de yapabilirsiniz; pkt=sniff(6) deseniz de olur. Ancak neyin ne olduğunu daha iyi görmek için parametre vermeniz daha uygun olur.

pkt.summary() diyebiliriz:

lambda kullansak nasıl olur?

pkt = sniff(count=6) dedikten sonra pkt veya pkt.summary() diyerek çağırmanıza hiç gerek yok; direkt olarak lambda kullanabilirsiniz. Bu noktada 'x', sırasıyla tutulan her paketi temsil edecektir. O zaman x.summary() dersek, tutulan her paketin özetini almaz mıyız? Elbette. İşte, lambda burada da işimize yaradı.

Oradaki prn= ifadesi zaten bir değişkendir. lambda kullanırken yaygın olarak kullanılan bir değişkendir ve 'pronoun' kelimesinden gelir; anlamı 'zamir' (ismin/paketin yerini tutan) şeklindedir. Bunu biraz daha geliştirelim.

Burada, herhangi bir paket sayısı belirtmeden direkt olarak lambda fonksiyonunu oluşturuyoruz. Bu sayede yakalanan her paketin özeti elimize geçecektir. Bu paket yakalama ve özet gösterme işlemi de kesintisiz bir şekilde olacaktır. Yani ne kadar trafik yaratırsanız o kadar fazla veri görürsünüz.

Eğer isterseniz Interface'inizi de spesifik olarak belirtebilirsiniz.

Gördüğünüz gibi sniff() fonksiyonu içerisine iface="eth0", diyerek spesifik bir oturum belirttim. Eğer birden fazla oturumunuz varsa bu sayede artık sadece eth0 koklanacaktır.

Diyelim ki aşağıdaki komut ile bir sürü paket yakaladınız:

pkt = sniff(iface="eth0", prn=lambda x : x.summary())

Bu noktada örneğin; 50. sıradaki paketin detaylarına nasıl bakardınız?

Gördüğünüz gibi; bu paketler aslında bir listede tutuluyor. Dolayısıyla ben, len(pkt) diyerek bu listenin uzunluğunu görebiliyorum. Sonrasında pkt[50] diyorum ve devamında .show() diyorum. show() fonksiyonu, ekranda gördüğünüz düzenli ve daha detaylı bir çıktı sağlar. Elbette summary() fonksiyonunu da kullanabilirsiniz ancak show() fonksiyonu daha detaylı ve daha düzenlidir.

Yukarıda, bir paketin içerisinde neler olabileceği hakkında bazı bilgiler ediniyoruz. Örneğin hedef ve kaynağın MAC adresleri (dst - src); IP adresinin IPv4 mü IPv6 mı olduğu bilgisi (type - verison); kullanılan protokol (proto); hedef ve kaynağın IP adresleri (dst - src); hedef ve kaynağın portları (sport - dport).

Peki, burası hakkında biraz daha açıklayıcı olalım; bu kısaltmalar ne anlama geliyor?

proto: Açılımı "Protocol" şeklindedir. Bu, paketin transferinde kullanılan protokolün ne olduğunu belirtir.

sport: Açılımı "Source Port" şeklindedir. Bu, kaynağın port numarasını ifade eder.

dport: Açılımı "Destination Port" şeklindedir. Bu, hedefin port numarasını ifade eder.

dst: Açılımı "Destinaton" şeklindedir. Bu bilgi eğer Ethernet içerisinde bulunuyorsa hedefin MAC adresini; IP içerisinde bulunuyorsa hedefin IP adresini temsil eder.

src: Açılımı "Source" şeklindedir. Bu bilgi eğer Ethernet içerisinde bulunuyorsa kaynağın MAC adresini; IP içerisinde bulunuyorsa kaynağın IP adresini temsil eder.

Dikkat ettiyseniz IP paketi içindeki src bilgisi, google.com'un IP adresi. O zaman incelediğimiz bu paket, bir 'Response' paketi değil midir? Biz isteğimizi attık, kendisi de bize bir yanıt döndürdü. Dolayısıyla burada 'kaynak' dediğimiz kişi google.com'un kendisidir.

Örneğin ilk paketimizi inceleyelim.

Kırmızı ok ile gösterdiğim kısım, ekrana sığmayan kısımdır.

Şimdi, ilk paketimizin kaynağı benim Local IP adresim (192.168.1.164). Hedef ise modemin IP adresi (192.168.1.1). Bu durum, internete nasıl çıkıldığının göstergesidir. Paket, internete çıkabilmek için modemden geçmek zorunda. Peki, bir başka pakete bakalım.

Bu sefer tam tersi oldu. O zaman bu pakette bana bir cevap döndü ve bu cevabı bana modem iletti.

sniff() ile spesifik bir MAC adresinin trafiğini de inceleme şansınız var. Burada sadece o MAC adresinin yarattığı trafiği size gösterecektir.

Elbette, öyle bir MAC adresi olmadığı için bir sonuç dönmüyor ama kullanımı ekranda gördüğünüz gibidir.

Dosya İşlemleri

Biraz önce ağı sürekli olarak kokladık ve aldığımız paketleri pkt isimli bir değişkende tuttuk. Ancak bu yaptığımız, daha kapsamlı işler için oldukça saçma olacaktır. Çünkü Scapy Shell'ini kapattıktan sonra pkt değişkeni diye bir şey kalmayacak ve bütün paketleri kaybedeceksiniz. Peki, bunları nasıl dosyalara atacağız; .txt dosyası olur mu?

Arka planda kalan Wireshark biraz ortaya çıksın bakalım; ağı analiz edeceğim.

Bunun için blogun başında gösterdiğim kısımda eth0 yazan yere çift tıklıyorum ve dinleme işlemi başlatılıyor. Bu sırada, internete girip bir trafik yaratıyorum. Yarattığım tüm trafiği de Wireshark topluyor. 3800 küsür paket yakalandığına göre sol üst köşedeki kırmızı butona tıklayıp koklama işlemini durduruyorum. Elbette siz daha fazla ya da daha az paket toplamak isteyebilirsiniz.

Şimdi, bu paketleri bir dosyada tutmak istiyorum. Bunun için Wireshark içerisinden File > Save As seçeneğini seçiyorum:

Bunu yaptıktan sonra karşınıza bir pencere gelecektir. Burada biraz dikkatli olalım. Önce görselimizi görelim.

File name: kısmında dosyanıza bir isim vermeniz gerekiyor. Hemen altındaki Save as: kısmında ise pcap dosyasını seçmeniz gerekiyor. Yüksek olasılıkla sizde, pcapng şeklinde gelecektir. Ancak onun üzerine tıklayıp açılan menüden pcap seçeneğini seçmeniz gerekiyor.

Bunları yaptıktan sonra nereye kaydedilmesini isterseniz orayı seçiyorsunuz ve Save butonuna tıklıyorsunuz. Artık bir pcap dosyamız mevcut.

Dosya elde etmenin ikinci yolu

Bunun için ilk önce 'Wireless Adaptor ve Monitor Mode' isimli blogda bahsettiğim alete sahip olmanız ve yine o blog içerisinde anlattıklarımı yapmanız gerek. Sonrasında 'Deauth' isimli bloga girip ağları taramanız, kendi ağınızı bulmanız gerek. Bunu yaptıktan sonra aşağıdaki komutu takip edebilirsiniz:

Bu noktada modemin channel ve bssid bilgisi gerekiyor. Sonrasında --write argümanı ile kaydedilecek dosyaya bir isim veriyorsunuz (--write cyberworm). Bunu yaptıktan sonra ağ taranacaktır ve siz bu işlemi durdurduktan sonra .cap uzantılı bir dosyanız oluşacaktır.

Eğer dosyanın nerede olduğunu bir türlü bulamıyorsanız aşağıdaki komutu verebilirsiniz:

Dosyayı oradan almak isterseniz de aşağıdaki komutu verebilirsiniz.

Detaya girmediğim komutları, "Kali'yi Tanıyalım" isimli blog içerisinde gösterdim.

Bu noktada bu .pcap ve .cap dosyalarını Scapy ile okuyabiliriz. Bir şeyleri otomatize etmek için gerekli olacak bir konudur. 

rdpcap() fonksiyonunun açılımı "read pcap" şeklindedir ve anlamı "pcap dosyasını oku" demektir. rdpcap() fonksiyonu, içerisine bir adet parametre alır ve bu parametre, pcap veya cap dosyasının yoludur (path). Bu noktada bunu bir değişkene atadım (fileP) ve değişkeni çağırdığımda kaç paketin tutulduğunu gördüm.

Peki, len(fileP) diyebilir miyiz?

Kaydettiğimiz bu dosyanın içinde 5884 adet paket varmış. Ben de bu noktada fileP içerisinde tuttuğum pcap dosyasının 1453. paketini görmek istiyorum (pkt = fileP[1453]).

Bunu az önce sniff() ile de yaptık; ne farkı var? sniff() ile yaptıklarınız anlık olarak kaydediliyordu. Yani o an bir değişkende paketleri tutuyordunuz. Ancak bir yerin ağ analiz sonuçlarını bir dosyada tutmak demek, o dosyaya istediğiniz zaman erişebilir ve inceleme yapabilir olmanız demektir. Buradaki fileP ve pkt değişkenleri ben terminali kapattığımda yok olacak ama dosyam olduğu yerde kalacaktır.

Tabii ki, daha güzel bir gürünüm mevcut.

SORU: Sizce OSI ya da TCP/IP Modeli kesin, değiştirilemez, manipüle edilemez birer model midir?

Kendi paketimizi oluşturalım, ister misiniz?

IP() dediğim zaman bir IP Header döndürülüyor. Aynı şey diğer yapılar için de söz konusu. Eğer böyle bir şey varsa yüksek olasılıkla bu header'ların içini doldurabilir hatta bunları, blogun başlarında gördüğünüz / işareti ile birleştirebilirim.

Ben, IP() fonksiyonunu bir değişkene atarsam içerisindeki özel bilgilere erişebilir miyim? Örneğin

ipInfo = IP()

ipInfo.src

dersem ne olur?

127.0.0.1 adresi döndü. Bu, loopback IP address isimli, rezerve bir IP adresidir (localhost). Ama amacımız 127.0.0.1'in ne olduğunu öğrenmek değil; önceki bloglarda anlattım. Bu, varsayılan olarak eklenen bilgidir. Amacımız, spesifik bilgilere erişebildiğimizi göstermektir. Evet, o zaman bu bilgiyi değiştirebilir miyiz? Örneğin ben, bu IP header'ın 1.2.3.4 şeklinde bir IP adresi koymak istiyorum.

Eğer ipInfo.src ifadesinin değeri 127.0.0.1 ise ipInfo.src = "1.2.3.4" diyebilir ve bu header içindeki src bilgisini manipüle edebilirim. Yukarıda gördüğünüz üzere bunu yaptıktan sonra ipInfo.show() dedim ve src bilgisinin, ayarladığım şekilde değiştiğini gördüm.

O zaman, dst bilgisiyle de oynayabilmem gerekir.

Evet, gayet başarılı bir şekilde yaptık. Demek ki biz, kendi oluşturduğumuz paketlerle çok kolay bir şekilde oynayabiliyoruz ve bu durum sadece IP() için değil, diğer Header'lar için de geçerli olmalıdır.

Şimdi, örneğin IP() ve TCP() paketlerimizin hazır olduğunu varsayalım. Bunları birleştirmeyi deneyelim.

Gördüğünüz gibi bu iki yapıyı birleştirebildik. Şu ana kadar herhangi bir sorunla karşılaşmadık.

Katman sırasına bakacak olursanız doğru bir sıralama ve birleştirme yaptığımızı görüyorsunuz.

load bilgisi de istediğimiz şekilde ayarlandı. Bu, yapılmasını istediğimiz şeyi ifade eder; GET isteği atıyoruz. Peki, biraz daha pratik yapalım.

Bence IP Header gayet güzel oldu. Şimdi, bir paketi nasıl yollarız? Mesela ben bu paketi oluşturdum ve yollamak istiyorum.

Her şeyden önce Wireshark'a girip bir filtreleme yapıyorum:

ip.addr == <ip_adresi> yaptığınız zaman yalnızca oradaki IP adresi ile alakalı paketler döndürülecektir. Elbette, arka planda bütün paketler yine tutulacaktır. Ancak bu filtrelemeyi yapıp Enter'a bastığınızda sadece o IP adresine ait paketleri görürsünüz. Filtreyi kaldırdığınız zaman bütün paketleri görebilirsiniz. Bunu yapmamın sebebi, diğer paketler arasında kaybolmamak, istediğimi hemen bulmaktır. O zaman devam edelim.

Kodumu hazırladım, gerekli parametreleri girdim ve send(package) fonksiyonunu kullandım. Ben bu paketi send() ile yollar yollamaz ekranıma paketin kendisi düştü. Dilerseniz siz de bu şekilde uygulayıp Wireshark'ta aşağıdaki detayları inceleyebilirsiniz.

send() fonksiyonu üçüncü katmanda çalışır ve bunu kullandığınızda herhangi bir düzenleme yapmanıza gerek kalmaz. Bu bilgiyi aklınızda tutun.

Burada neden MAC adresi bilgileri yok? Örneğin biraz önceki Wireshark ekran görüntüsünde detaylar kısmında Ethernet II yazan yerde MAC adresleri vardı, paketimizde neden yok?

Çünkü Ether() Header'ı eksik. MAC adreslerinin bulunduğu yer üçüncü katman değil, ikinci (IP()) katmandır. O zaman bir de Ether() ekleyip öyle gönderelim ve sonuca bakalım.

Neden böyle oldu? Normalde IP(src="1.2.3.4") deyip IP adresimizi manipüle ediyorduk; MAC adresini manipüle edemiyor muyuz?

Elbette, edebiliyoruz. Buradaki sorun MAC adresi değil, kullandığımız fonksiyondu. Bakalım.

Bu sefer başarılı olduk. Sağ tarafa baktığınız zaman send() yerine sendp() fonksiyonunu kullandık. Biraz önce send() fonksiyonunun üçüncü katmanda çalıştığını söylemiştim. Biz, Ether() eklediğimiz için aslında bu pakete ikinci katmanı da elimizle ekledik. send() üçüncü katmanda çalışırken sendp() ikinci katmanda çalışır. Bu yüzden eğer Ether() ifadesini veriyorsanız send() yerine sendp() kullanmak zorundasınız. Wireshark içerisinde detaylar kısmında manipüle edilmiş MAC adresini görüyorsunuz; benim MAC adresim o değil.

Peki, yolladığımız paketlerden sonra Wireshark üzerinde 'Protocol' kısmını değiştirmeyi deneyelim.

Gördüğünüz gibi bunu da manipüle edebiliyoruz.

Eğer tek bir paket yollayıp karşılığında bir yanıt almak isterseniz sr1() fonksiyonunu kullanabilirsiniz.

Açılımı, "Send and Receive 1 Packet" şeklindedir. Bu noktada kodun kendisinde /ICMP()/"ICMP pack." dediğimi gördüğünüzü düşünüyorum. Bu yapı sayesinde ICMP içerisine mesaj gömebilirsiniz.

Şimdi, UDP kullanarak bir DNS sorgusu yapmayı öğrenelim. UDP blogundan hatırlatıcı:

Önce kodumuzu inceleyelim. sr1, IP(), dst="" kısımlarını artık biliyorsunuz. Kodun en sonunda qname="www.google.com" ifadesi yer alıyor. Bu, zaten anlayacağınız üzere "query name" anlamına gelir ve sorgulamak istediğiniz domain adresini buraya yazarsınız.

Biraz sol tarafa kaydığımız zaman qd=DNSQR ifadesini görüyoruz. Bu, "query data" anlamına gelir ve sorgunun ne olacağını bildirmemize olanak tanır. Buradaki "query data" değerimiz "DNSQR" şeklinde. DNS Query anlamına gelen bu ifade, DNS sorgusu yapacağımızı ifade eder. Dikkat ederseniz içine aldığı parametre, DNS sorgusu yapacağımız domain adresi oldu. Yani temel yapımız şu şekilde:

qd=DNSQR(qname="www.google.com")

Burada dediğim şey şu: "Benim query data değerim DNSQR ve DNSQR sayesinde qname değeri www.google.com olan adrese sorgu yapacağım."

Biraz daha sola kaydığımızda rd=1 ifadesini görüyoruz. 'rd', "recursive query" ifadesinin kısaltılmışıdır ve anlamı "özyinelemeli/tekrarlı sorgu" şeklindedir. Bunun ne anlama geldiğini bilmiyorsanız DNS blogunu okuyabilirsiniz.

Son olarak dikkat ederseniz; biraz önce anlattığım bütün ifadeler, DNS() paketinin aldığı parametrelerdi. DNS()'in, bu şekilde parametreler aldığını bilelim. Temel yapımız bu şekilde:

DNS(rd=1, qd=DNSQR(qname="www.google.com"))

Şimdi sıra sol tarafta. Sarı kutucuk içerisine aldığım mesaja bakın: "Malmorfed Packet" yani "Kusurlu Paket".

Bu paketin kusurlu olmasının temel sebebi, UDP protokolünü kullanmadığımızdandır. Peki, madem UDP() fonksiyonunu kullanmadık; o zaman neden Wireshark bu paketi yakalayabildi? Çünkü:

Paketin yollanması da yakalanması da yukarıdaki hatırlatıcıda yazanlar yüzündendir. Yani paketin gidişinde sorun yok, sorun; bu paketin kusurlu olmasıdır. Peki, şimdi kodumuzda UDP() kullanalım ama bunu nereye yazacağımızı önce iyice düşünün; ondan sonra devam edin.

Önce sağ tarafa, kodumuza bakalım. Biraz önceki hatırlatıcılardan bileceğiniz üzere eğer DNS sorgusu, UDP ile gerçekleşiyorsa bahsi geçen bu DNS sorgusunu, UDP()'nin içerisine koymamız gerekir. Dolayısıyla orada UDP()/DNS(...) şeklinde bir yapı oluşmak zorundadır. Zaten aradaki / işaretinin bu ikisini birleştirdiğini söylememe gerek yok.

Wireshark tarafına baktığımızda bu sefer "Malmorfed" tarzında bir uyarı veya mesaj da görmedik. Hatta onun yerine direkt olarak "User Datagram Protocol - UDP" ifadesini görüyoruz. Yukarıdaki Protocol kısmına baktığınızda DNS sorgumuzun başarıyla gerçekleştiğini göreceksinizdir.

Scapy ile port taraması da yapabiliriz.

sr() fonksiyonu, sr1() fonksiyonunun yapısına benzer. Ancak sr1() yalnızca 1 paket yollarken, sr() fonksiyonu verilen parametrelere göre paket yollar; 5 parametre verirseniz 5 paket atacaktır. Sonrasında gelen IP() ve TCP() ifadelerini artık biliyorsunuz. Ancak şimdi, TCP() fonksiyonunun içine bakacağız.

TCP(dport=[22,80]) ifadesinde biz, 'dport' derken hedef portları ifade ederiz; biliyorsunuz. Eşitliğin sağında kalan bir liste görüyorsunuz; [22,80]. Bunlar, port numaralarını temsil eder. 22 numaralı port SSH'ı temsil ederken 80 numaralı port HTTP portunu temsil eder.

Kodun tamamına baktığımızda dediğimiz şey şudur: "192.168.1.1 IP adresinin 22 ve 80 numaralı portlarını kontrol et". Bunu dedik, kontrol edildi; ee?

Genel ve temel olarak ağ yapılarında Request-Response yani İstek-Yanıt yapısı vardır. Biz bu kod ile oradaki portlara bir Request atmış oluyoruz. Dolayısıyla eğer bu portlar mevcutsa ve çalışır durumdaysa ya da çalışmıyor durumdaysa sr() fonksiyonunun 'r' yani 'receive' kısmı bize sonuç döndürecektir. En aşağıya baktığınızda "Received 2 packets, got 2 answers, remaining 0 packets" şeklinde bir ifade görürsünüz. Bu ifadenin anlamı şu şekildedir: "2 paket alındı, 2 cevap alındı, 0 paket kaldı". Bu noktada sr() fonksiyonu, belirttiğimiz IP adresinin 22 ve 80 portuna bir istek yolladı ve gelen cevabı bize döndürdü. Ayrıca mesajın hemen üzerindeki '**' ifadesi, 2 cevabın alındığına işarettir. Eğer '**.' tarzında bir ifade görüyorsanız yani o yapı içerisinde nokta varsa o nokta sayısı kadar olumsuz cevap alındığı anlamına gelir.

Peki, şimdi control değişkeninin içini kontrol edelim.

Her zamanki gibi control.show() dedim ama neden böyle oldu? Hata mesajına baktığımızda bunun bir Tuple olduğunu görüyoruz ve Tuple'lar değiştirilemez yapılardı. Ayrıca Tuple'ın show isminde bir özelliği yokmuş. Bu durumda bir Python geliştirici olarak siz olsanız ne yapardınız?

Dikkat edelim; control.show() diyemiyoruz ama control diyerek söz konusu paketleri görebiliyoruz. Bu paketler, Results ve Unanswered olarak gruplanmış şekilde duruyor. Result dediğimiz kısımda, yanıt alınabilen sonuçlar yer alırken Unanswered kısmında yanıt alınamayan sonuçlar yer alıyor.

O zaman ben, bu ikisini bir değişkene atarsam ve Python'daki özel bir ifadeyi Results kısmına atarsam bu sorunu aşabilir miyim?

O yüzden results, unans = _ ifadesini veriyorum. Buradaki _ ifadesi, Results içerisindeki her detayı results değişkeni içerisine atacaktır. Dolayısıyla results.show() dersem bu işi çözerim. Çünkü control değişkeninde bir Tuple tutuluyor ama kendi oluşturduğum results değişkeninin içindeki veriler Tuple değil.

Gördüğünüz gibi detayları alabildik ve sorunumuz çözüldü. Az önce kurduğum yapıyı bu seride henüz göstermedim. Alt tire işareti Python'da önemli bir ifadedir ve yer tutucu gibidir. İlerleyen zamanlarda bunu göreceğiz.

Bir ARP() paketi de yollayabiliriz. İlk önce bir ARP() header'ının nasıl göründüğüne bakalım.

İçerisinde bulunan 4 ifadeye yer vereceğim ve bitireceğim. Çünkü ileriki zamanlarda yapacağımız projelerde bunları kullanacağız.

hwsrc: Açılımı "Hardware Source" yani "Kaynak Donanımı" şeklindedir. Buradaki 'Kaynak Donanım'dan kastımız, Kaynağın MAC adresidir.

hwdst: Açılımı "Hardware Destination" yani "Hedef Donanımı" şeklindedir. Buradaki 'Kaynak Donanım'dan kastımız, Hedefin MAC adresidir.

psrc: Açılımı "Packet Source" yani "Paket Kaynağı" şeklindedir. Buradaki kaynaktan kasıt, paketi yollayan cihazın IP adresidir.

pdst: Açılımı "Packet Destionatin" yani "Paket Hedefi" şeklindedir. Buradaki kaynaktan kasıt, paketi alan cihazın IP adresidir.

İsterseniz bir de bunu modifiye edelim.

Gördüğünüz gibi, modifiye ettik ve bu paketin artık bir hedefi de var. İlk ekran görüntüsünde sadece kaynak ile alakalı bilgiler vardı çünkü kaynak biziz. Ancak şimdi, hedefin de bir adresi var, biz yaptık.

Peki, genel olarak Scapy modülünü tanıdık ve anladık. Şimdi biraz daha başka şeyler yapalım ve yeni şeyler öğrenelim.

[***] AŞAĞIDA YER ALAN HER İFADE, GÖSTERİLEN HER YÖNTEM YALNIZCA EĞİTİM AMAÇLIDIR. [***]

Örneğin bir SYN Flood Saldırısı yapalım. Ne olduğunu bilmiyorsanız blogun başında verdiğim DoS-DDoS blogunu okuyunuz.

Kodumuzda srloop() fonksiyonunu kullandık. Bu fonksiyon, sr() fonksiyonunun döngü işlevli olan tarzıdır. Yani bu fonksiyon sürekli olarak -siz durdurana kadar- paket yollayacaktır.

flags="S" şeklindeki ifade ise, SYN bayrağı taşımayı ifade eder. Daha sade bir deyişle, belirttiğimiz hedefe sürekli olarak bir SYN isteği yollanacaktır. Geri kalan kısımları biliyorsunuz.

Blog boyunca bazı yerlerde kendi paketimizi oluşturduk, bunları sıraya dizdik ve ağa yolladık. Ayrıca size blogun ortalarında şöyle bir soru sordum; umarım üzerine düşünmüşsünüzdür:

İsterseniz bu istikrarlı modellerin (!) istikrarını biraz test edelim.

l2 isminde bir değişken oluşturdum. Bu, "Layer 2" yani "İkinci Katman" anlamına geliyor ve bu değişkene, Ether() paketini yerleştirdim. İçerisine de MAC adresini manipüle edecek bir ifade yazım; benim MAC adresim o değil.

Aynı şeyi, l3 isimli değişken için de yaptım ve ona IP() paketini yerleştirdim. IP() paketi içerisine modemimin IP adresini verdim.

Sonrasında sendPakcet = sendp(l2/l3) şeklinde bir kullanım yaptım. Bu, sendp(Ether()/IP()) ile aynı şeydir; ben sadece değişken kullandım. Bunu yaparak ağa, bir paket yollamış oldum ve Wireshark ekranına bakarsanız paketimin doğru bir şekilde geldiğini görürsünüz.

Normalde Ether()/IP()/TCP şeklinde gidiyorduk. Ancak şimdi, l2/l3/l2 dersem iki tane Ether() paketi yollamış olmaz mıyım? Peki bunu yaparsam birileri bana kızar mı?

Kusurlu paket uyarısı geldi. Ancak sonuçta ben, bu paketi ağa sızdırmayı başardım. Yani Ether()/IP()/Ether() yapmış oldum ve bu, modellerimize aykırıdır. Peki, birden fazla IP paketi yollayamaz mıyız? Neden olmasın ki?

Herhangi bir hata almadık ve Wireshark'ta paketin iki tane IP() header'ına sahip olduğunu görüyorsunuz. Manipüle ettik, başarılı.

Şimdi başka bir şey göstermek istiyorum.

load_contrib() fonksiyonu ile mümkün olan bütün paketleri yükleyebilirsiniz. MPLS dediğimiz bu paketin açılımı "Multi Protocol Label Switching - Çoklu Protokol Etiketi Değiştirme" şeklindedir. Kendisi, ağ üzerinden iletimleri yönetmek için ağ adresleri yerine etiketlere dayalı en kısa yolu kullanarak trafiği yönlendiren bir ağ teknolojisidir. OSI'nin 2. katmanındaki anahtarlama (switching) işlemi ve 3. katmanındaki yönlendirme (routing) işlemlerini entegre eder. Detayları için kendi araştırmanızı yapabilirsiniz.

Bu paketi aldık. Peki, bu paketi de ağa enjekte edebilir miyiz? Bence yapabiliriz.

myMPLS = MPLS() diyerek diğer işlemlerde olduğu gibi bu paketi değişkene atadım. Sonrasında l4 = TCP() dedim. Artık l2, l3, myMPLS ve l4 olmak üzere 4 paketim var.

Yukarıdaki enjeksiyonda bir sorunla karşılaşmadık. Zaten olması gereken yerde durduğu için sıkıntı olmadı. Ancak biz, ağın kafasını karıştırmak istiyoruz.

Aynı paketi l3 ile l4 arasına koydum. Wireshark'ta detaylar kısmında kırmızı kutu içerisinde yer alan Ethernet doğru bir şekilde orada duruyor yani zaten orada olması gereken bir paket. Sonrasında yeşil kutuya bakarsanız görürsünüz; bir Ethernet daha var. Biz böyle bir şey yapmadık; ne oldu?

MPLS, ikinci ve üçüncü katmanlardaki işlemlerin entegresinden sorumluydu. İlk kısımda l2/myMPLS/l3 dedik yani Ether()/myMPLS/IP() oldu. Devamında l3/myMPLS/l4 dedik ve bu, IP()/myMPLS/TCP() ifadesine tekabül ediyor olmalıydı. Ancak MPLS, sol tarafta l3 yani IP() paketini gördüğü için sağ tarafında da Ether()'in olabileceğini düşündü ve l4 yani TCP() paketini Ether() paketi sanarak bunu aldı. İşte, ağı yine manipüle etmeyi başardık. Dikkat ederseniz Wireshark'ta Protocol kısmında TCP yerine 0x5002 yazıyor.

Python ve Scapy

Peki, bütün bu yaptıklarımızı Python'a nasıl entegre edeceğiz?

Yapmanız gereken tek şey, import scapy.all demek. Eğer isterseniz başka bir isimle de bunu kullanabilirsiniz. Örneğin import scapy.all as sc şeklinde kullandım. Bu kullanımla beraber Scapy'den kullanacağınız her fonksiyon, her yapı için başına sc. koymanız gerekir. Örneğin; sc.sniff(), fileP = sc.rdpcap() gibi.

Bir kod yazdığınız zaman bunu çalıştırmak için tercih etmeniz gereken çalıştırma stiline bakalım.

Scapy içeren bir kod çalıştıracaksanız ilk olarak root kullanıcı konumunda olmanız ya da sudo anahtar kelimesini kullanmanız gerekir. Bu noktada Scapy, python2 sürümünde bazı aksilikler çıkarabildiği için python3 kullanmanızı önerebilirim. Genel olarak çalıştırma stili: sudo python3 <projeismi.py>

Yani sudo python _scapyTutorial.py derken aslında python2 ile çalıştırmış oluruz.

Scapy blogunun sonuna geldik. Çok yakında Scapy ile bir uygulama paylaşacağım. Uygulama hazır ancak biraz rahatsız olduğum için ertelemem gerekiyor. Bu uygulamada, kullanıcı odaklı olmanın getirdiği avantajlardan ve Scapy'nin yeni, başka özelliklerinden bahsedeceğim. Sevgiler.


Yayınlanma Tarihi: 2022-12-04 13:53:06

Son Düzenleme Tarihi: 2022-12-11 17:24:53