loader
Foto

Sniffer (TCP) készítése Python nyelven

A hálózati forgalom elemzés, figyelése elengedhetetlen az IT biztonsággal foglalkozók számára. Noha erre a feladatra számtalan program létezik, Python nyelven elkészítünk most mi is egyet, és eközben áttekintjük néhány header-nek a felépítését is. A sniffer alkalmazásunk a TCP protokollt tudja majd megfigyelni.

Több olyan ingyenes program létezik (pl.: Wiresharok, Tcpdump, stb.), amelyeknek a segítségével a hálózati forgalom megfigyelhető. Ennek ellenére mi úgy gondoltuk, hogy itt az ideje annak, hogy elkészítsünk egy saját sniffer programot. A választás a Python nyelvre esett, mert ennél a nyelvnél nincsenek olyan megkötések, mint például a C# nyelv esetén, ahol a .NET keretkörnyezet határt szab a hálózati forgalom monitorozásának.

Nézzük meg először is az IP header felépítését (1. ábra), hiszen ez fontos lesz számunkra akkor, amikor a kiolvasott hálózati forgalom eredményeit szeretnénk megjeleníteni. Az ábrán látható, hogy számunkra az első 20 byte lesz érdekes.

kep
1. ábra   Az IP header felépítése
 

Az IP header felépítése a következő részekből áll:

Version: Az IP verziószáma. Ha IPv4-et alkalmazunk, akkor ennek a mezőnek az értéke 4, ha IPv6-ot használunk, akkor az értéke 6
IHL: A header fejléchossza nem állandó. Ezért meg kell adni a méretét, amelyet ennél a mezőnél adnak meg. Ennek az "egysége" 4 byte. Tehát, ha ennek a mezőnek az értéke 5, akkor a header hossza 20 byte.
DS (Differentiated Services): 
     - DSCP: szolgáltatási minőség
     - ECN (Explicit Congestion Notification): Megjelöli a csomagot torlódás esetén, de nem dobja el. Torlódás jelzése
Total Length: A csomag teljes hossza
Identification: Ennek az azonosítónak a segítségével lehet a szétdarabolt csomagokat összerakni. Minden csomagnak egyedi azonosítója van.
Flags: Több flag található itt, például a DF beállításával megtiltható az üzenet darabolása. MF: az üzenet több részből áll.
Fragment Offset: Megadja, hogy a feldarabolt üzenet hol kezdődik a csomagban.
Time To Live: Élettartam, egy csomagnak mekkora az élettartama.
Protocol: Itt jelzik, hogy a csomagban milyen protokoll található. Bőbben itt olvashatunk erről. TCP-nél ennek a mezőnek az értéke 6. Az ICMP: 1, IGMP: 2, UDP: 17.
Header Checksum: Ellenőrző összeg, csak a fejlécre számolják (RFC 1071).
Source IP Address: Csomag küldőjének az IP címe.
Destination IP Address: A célállomás IP címe.

Ennyi (rövid) elméleti háttérrel a sniffer alkalmazásunk már meg is valósítható Python nyelven.

A sniffert a Kali Linux operációs rendszerben valósítottuk meg, amely alapból tartalmazza a Pythont. Ha Linux alatt készítjük el az alkalmazást, akkor hozzuk be a terminált, majd indítsuk el a "leafpad" szövegszerkesztőt, és gépeljük be a következő kódot:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)

while True:
          print s.recvfrom(65565)

 

És el is készítettük az első sniffer alkalmazásunkat. A gond "csak" az, hogy amikor futtatjuk az alkalmazásunkat, akkor az eredmény értelmezhetetlen:

kep
2. ábra   Snifferünk első változatának futtatása
 

Tekintettel arra, hogy az eredményünk "emberi fogyasztásra" alkalmatlan, változtatnunk kell az előző Python kódon. Módosítsuk a kódunkat a következő verziónak megfelelően:

import socket, sys
from struct import *

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)

while True:
    packet = s.recvfrom(65565)
    packet = packet[0]

    ipHeader = packet[0:20]

    iphUnp = unpack('!BBHHHBBH4s4s' , ipHeader)
    print str(iphUnp)

Létrehozzuk a "socket"-et, majd a végtelen ciklusban állandóan kiolvassuk a hálózati kártyáról a maximális mennyiséget. Az "ipHeader" -ben az első 20 karaktert tároljuk el. Ez az első 20 karakter az a hálózati forgalomban, amelyben megtalálható az IP header. Ahhoz, hogy ezt "látható" módon meg tudjuk jeleníteni, parsolnunk kell. Ehhez az "unpack" paraméterében meg kell adnunk egy "patternt". 

Ezután az "ipHeader"-t parsoljuk sztringgé (iphUnp), majd ezt az eredményt jelenítjük meg (3. ábra).:

kep
3. ábra   Futási eredmény
 

Ez már értelmezhetőbb eredmény, mint az első változat. Látható a soroknál, hogy a 4. szám egyesével növekszik. Ez az adott csomag azonosítója. Az azonosító után találjuk a 16384-et, amely binárisan a következő: 0001 0110 0011 1000 0100. Ha megnézzük az IP header felépítését, akkor látható, hogy a flag-ek (felső három bit) értéke 0, tehát nincs darabolás.
A TTL értéke 107. A header protokoll részénél a "6"-ot olvastuk ki,, amely a "TCP" protokoll értéke (pl.: 1: ICMP, 17: UDP, stb.), végül pedig az ellenőrző összeg következik az IP címek előtt.

Módosítsuk most ismét a programunkat úgy, hogy felhasználjuk az IP header felépítéséről szóló ismereteinket. Például az "ihlUnpVersion" változóban tároljuk el az IP verziót, ehhez az első byte  felső 4 bitjére lesz szükségünk. Ezért az "ipHeader" tömb 0. elemének a felső 4 bitjét betesszük a "version" változóba.

import socket, sys
from struct import *

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)

while True:
    packet = s.recvfrom(65565)
    packet = packet[0]

    ipHeader = packet[0:20]

    iphUnp = unpack('!BBHHHBBH4s4s' , ipHeader)
    
    ihlUnpVersion = iphUnp[0]
    version = ihlUnpVersion >> 4
    ihl = ihlUnpVersion & 0xF
    
    iphLength = ihl * 4
    
    ttl = iphUnp[5]
    protocol = iphUnp[6]
    sIP = socket.inet_ntoa(iphUnp[8]);
    dIP = socket.inet_ntoa(iphUnp[9]);
    
    print 'IP header'
    print 'Version: ' + str(version) + ', IP Header Length: ' + str(iphLength) + ', TTL: ' + str(ttl) + ', Protocol: ' + str(protocol)
    print 'Source IP: ' + str(sIP) + ', Destination IP: ' + str(dIP)
    print

 

Indítsuk el újra a programunkat, és a következő eredményt kapjuk.

kep
4. ábra   Futási eredmény
 

Látjuk a 4. ábrán, hogy az elkapott hálózati forgalom megjelenítése már alkalmas a különböző vizsgálatra. Kényelmesen tudjuk elolvasni a különböző adatokat, egyedül a protokoll fajtája (TCP) nem ismerhető fel, ehelyett a "6" került megjelenítésre.

Nézzük meg most a TCP header felépítését (5. ábra), mert szükségünk lesz ehhez, hogy a snifferünket tovább tudjuk fejleszteni.

kep
5. ábra   A TCP header felépítése
 

Egészítsük ki az alkalmazásunkat újabb részlettel (a # coding=utf-8 elhagyható), mert a célunk az, hogy a TCP header tartalmát is feldolgozzuk. Az IP header miatt beolvastunk korábban 20 karaktert. A TCP header teljes beolvasásához további 20 karakterre van szükségünk, amelyet szintén parsolunk.

# coding=utf-8

import socket, sys
from struct import *

s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)

while True:
    packet = s.recvfrom(65565)
    packet = packet[0]

    ipHeader = packet[0:20]

    iphUnp = unpack('!BBHHHBBH4s4s' , ipHeader)
    #print str(iphUnp)
    
    ihlUnpVersion = iphUnp[0]
    version = ihlUnpVersion >> 4
    ihl = ihlUnpVersion & 0xF
    
    iphLength = ihl * 4
    
    ttl = iphUnp[5]
    protocol = iphUnp[6]
    sIP = socket.inet_ntoa(iphUnp[8]);
    dIP = socket.inet_ntoa(iphUnp[9]);
    
    print 'IP header'
    print 'Version: ' + str(version) + ', IP Header Length: ' + str(iphLength) + ', TTL: ' + str(ttl) + ', Protocol: ' + str(protocol)
    print 'Source IP: ' + str(sIP) + ', Destination IP: ' + str(dIP)
    print


    #TCP Header
    tcpHeader = packet[iphLength:iphLength+20]
    tcphUnp = unpack('!HHLLBBHHH' , tcpHeader)
    
    sPort = tcphUnp[0]
    dPort = tcphUnp[1]
    sequence = tcphUnp[2]
    acknowledgement = tcphUnp[3]
    tcphLength = tcphUnp[4] >> 4

    print 'TCP header:'
    print 'Source port: ' + str(sPort) + ', Dest port: ' + str(dPort)
    print 'Seq. number: ' + str(sequence) + ', Ack.: ' + str(acknowledgement) + ', TCP header length: ' + str(tcphLength)
    
    hSize = iphLength + tcphLength * 4
    
    data = packet[hSize:]
    
    print 'Data: ' + data
    print

 

Indítsuk el ismét a módosított alkalmazásunkat. A futási eredmény a következő ábrán látható.

kep
6. ábra    Snifferünk futás közben
 

A következő cikkben megnézzük a sniffer megvalósítását az ICMP és az UDP protokollra  is.



Egyéb cikkek

További cikkeink ebben a témakörben

Régebbi cikkeink

Elkezdünk egy sorozatot, amelyhez a Kali Linux használata elengedhetetlen lesz, de természetesen folytatjuk a C# nyelv aszinkron használatának a bemutatását is. Az IT biztonság (amely elég széles területet foglal magába) egyik elengedhetetlen eszköze. . . .

Az nmap (grafikus megjelenítésnél a ZenMap) használata az IT biztonság, illetve az üzemeltetés területén dolgozó szakembereknél szinte elkerülhetetlen. Az ingyenes szoftver segítségével tesztelhetők a számítógépeink, a számítógéphálózatunk, vizsgálha. . . .

A saját informatikai rendszerünk tesztelésénél előfordulhat, hogy meg kell változtatni a MAC címünket. Hogyan tehetjük ezt meg a Kali Linux segítségével? Megnézzük most ezt ebben a cikkben.. . . .