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.
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:
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).:
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)
Indítsuk el újra a programunkat, és a következő eredményt kapjuk.
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.
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)
#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] >> 4print '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
Indítsuk el ismét a módosított alkalmazásunkat. A futási eredmény a következő ábrán látható.
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.
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 Python programozási nyelv nagyon elterjedt a fejlesztők körében. Használják beágyazott rendszereknél, webes alkalmazásoknál, IT biztonság különböző területein, stb. Látható, hogy nagyon széles a felhasználási területe ennek a nyelvnek, ideje volt m. . . .
Elkezdjük most részletesebben megismerni az nmap használatát. Az nmap nagyon fontos eszköz az IT biztonsággal, illetve az üzemeltetéssel foglalkozó szakembereknél. De hogyan működik? Hogyan lehet és érdemes a scannelési tulajdonságokat beállítani? M. . . .