A célunk most az, hogy egy olyan sniffert hozzunk létre, amely nem csak a TCP protokollt képes kezelni, hanem akár az UDP-t, illetve az ICMP-t is. A korábbi cikkünkben a socket elkapására a következő sort alkalmaztuk:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
A "socket.IPPROTO_TCP" paraméterrel állítottuk be azt, hogy a TCP protokollt figyeljük, és majd annak a tartalmát olvassuk ki. Igen ám, de mit csináljunk akkor, ha nem csak a TCP, hanem az UDP vagy az ICMP csomagok olvasását is el kívánjuk végezni? Ebben az esetben ez a paraméter nem alkalmazható. Nézzük meg Linux alatt egy szövegszerkesztővel (pl.: leafpad, nano, stb) az "it_ether.h" file tartalmát:
nano /usr/include/linux/it_ether.h
Ebben a file-ban találjuk meg a hálózati definíciókat. Mi most az Internet Protocol packet-et akarjuk alkalmazni, ezért választhatnánk paraméternek akár a 0x0800-at, de mi a 0x0003-at adtuk meg a kérés/válasz feldolgozása miatt.
1. ábra Az "it_ether.h" file tartalma (részlet)
Az Ethernet packet felépítése látható a következő ábrán. A cél és a forrás MAC címek után következik az Ether Type, amely azt árulja el, hogy milyen típusú a packet. Például a 0x0806 az ARP-ot takarja, a 0x0800 azt, ahol megtalálhatók a TCP, UDP, ICMP protokollok. A frame után találjuk az ábrán a "Data"-t, majd a CRC-t.
2. ábra Az Ethernet packet felépítése
A "Data"-ban lehet IP (amelyben UDP, TCP, ICMP is megtalálható), ARP, stb.. A következő ábra az UDP felépítését mutatja. Két-két byte tartalmazza a forrás és a cél portot, majd a méretét, illetve a checksum-ot találjuk a header-ben.
3. ábra UDP fejléc
A 4. ábrán az ICMP protokoll header-jének a felépítése látható. A számunkra talán a két legfontosabb a Type és a Code mezők, ahol konkréten megadásra kerülnek az ICMP részletei (pl.: ping).
4. ábra ICMP protokoll fejléce
Rövid elméleti bevezető után már tudjuk értelmezni a Python nyelven írt sniffer alkalmazásunkat. Nézzük meg a következő programunk működését.
A socket elkapásánál a "0x0003" került megadásra, azaz, mindent elkapunk. Amikor nem vagyunk kiváncsiak a TCP és az UDP adataira, akkor a "show" értékét 'n'-re kell állítani. Programunk legelején adtuk meg az Ethernet keret méretét is.
Az Ehernet keret felépítésénél látható volt, hogy ott a két MAC cím található meg, illetve a típus. Ezért alkalmaztuk a "6s6s2s" patternt az "unpack()" metódusnál.
Az Ethernet keret tartalmának kiolvasása után következik az IP keret feldolgozása. Ebben különböző protokollok találhatók, ezért nagyon fontos az, hogy az IP-ből kinyerjük a protokoll kódját (pl.: TCP: 6).
import socket, struct, binascii
try:
s = socket.socket( socket.PF_PACKET , socket.SOCK_RAW , socket.ntohs(0x0003))
except socket.error , msg:
print 'Sajnos gond van. Hiba: ' + str(msg[0]) + ' - ' + msg[1]
sys.exit()show = 'y'
ethLength = 14while True:
packet = s.recvfrom(65565)
ethHeader = packet[0][0:14]
ethUnp = struct.unpack("!6s6s2s", ethHeader)
print 'Ethernet Frame:'
print ' Source MAC: ' + binascii.hexlify(ethUnp[1]) + ', Dest MAC: ' + binascii.hexlify(ethUnp[0])
print ' ' + binascii.hexlify(ethUnp[2])#------ IP Header ----------
ipHeader = packet[0][14:34]
ipHeaderUnp = struct.unpack("!BBHHHBBH4s4s", ipHeader)version = ipHeaderUnp[0] >> 4
ihl = ipHeaderUnp[0] & 0xF
iphLength = ihl * 4totalLength = ipHeaderUnp[2]
identification = ipHeaderUnp[3]
ttl = ipHeaderUnp[5]
protocol = ipHeaderUnp[6]
headerChecksum = ipHeaderUnp[7]
sIP = socket.inet_ntoa(ipHeaderUnp[8])
dIP = socket.inet_ntoa(ipHeaderUnp[9])print 'IP Header:'
print ' Source IP: ' + sIP + ', Dest. IP: ' + dIP
print ' Version: ' + str(version) + ', IP Header length: ' + str(iphLength)
print ' TTL: ' + str(ttl) + ', Protocol: ' + str(protocol)
print ' Id: ' + str(identification) + ', TotalLength: ' + str(totalLength)
print ' Header Checksum: ' + str(headerChecksum)
if protocol == 6 :
tcpHeader = packet[0][34:54]
tcpHeaderUnp = struct.unpack("!HHLLBBHHH", tcpHeader)sPort = tcpHeaderUnp[0]
dPort = tcpHeaderUnp[1]
seqNumber = tcpHeaderUnp[2]
ackNumber = tcpHeaderUnp[3]
offsetReserved = tcpHeaderUnp[4]
tcpLength = offsetReserved >> 4tcpFlags = tcpHeaderUnp[5]
window = tcpHeaderUnp[6]
tcpChecksum = tcpHeaderUnp[7]
urgPointer = tcpHeaderUnp[8]print 'TCP Header:'
print ' Source Port: ' + str(sPort) + ', Dest. Port: ' + str(dPort)
print ' Sequence number: ' + str(seqNumber) + ', Ack Number: ' + str(ackNumber)
print ' TCP Length: ' + str(tcpLength)
print ' Window: ' + str(window) + ', TCP_Checksum: ' + str(tcpChecksum)
print ' Urgent Pointer: ' + str(urgPointer)if show == 'y' :
tcpHeaderLength = ethLength + iphLength + tcpLength*4
tcpData = packet[0][tcpHeaderLength:]
print ' Data: ' + str(tcpData)
elif protocol == 1 :
temp = ethLength + iphLength
icmpHeader = packet[0][temp:temp+4]
icmphUnp = struct.unpack('!BBH' , icmpHeader)
type = icmphUnp[0]
code = icmphUnp[1]
checksum = icmphUnp[2]print 'ICMP header:'
print ' type: ' + str(type) + ', code: ' + str(code)
print ' checksum: ' + str(checksum)elif protocol == 17 :
temp = ethLength + iphLength
udpHeader = packet[0][temp:temp+8]
udpHeaderUnp = struct.unpack('!HHHH' , udpHeader)
sPort = udpHeaderUnp[0]
dPort = udpHeaderUnp[1]
udpLength = udpHeaderUnp[2]
udpChecksum = udpHeaderUnp[3]print 'UDP Header:'
print ' Source Port: ' + str(sPort) + ', Dest. Port: ' + str(dPort)
print ' Length: ' + str(udpLength) + ', Checksum: ' + str(udpChecksum)if show == 'y' :
udpHeaderLength = ethLength + iphLength + udpLength
udpData = packet[0][udpHeaderLength:]
print ' Data: ' + str(udpData)print '---------------------------'
Indítsuk el a programunkat, és a hallgatózás a hálózaton elkezdődik. Egy eredményrészlet látható az 5. ábrán, ahol az ICMP protokoll adatai látthatók.
5. ábra Elkapott sniffer adatok
Ha tovább figyeljük a sniffer által mutatott adatokat, akkor olyan protokoll "adatai" is megjelennek, amelyet nem kezeltünk le az alkalmazásunkban. A "Protocol" értéke 55, amely az ARP-ot takarja. Megjegyezzük, hogy ha nem nézzük meg a "Protocol" értékét, akkor is rá tudunk jönni az ARP-ra, mert a MAC cím értéke FFFFFFFFFFFF (6. ábra).
6. ábra ICMP és ARP protokoll
Módosítsuk most úgy a sniffer alkalmazásunkat, hogy az ARP protokollt is kezelni tudjuk. Ehhez ismerni kell az ARP header felépítését, amelyet a 7. ábrán láthatunk.
7. ábra ARP fejléc felépítése
Módosítsuk tehát az alkalmazásunkat a következők szerint:
import socket, struct, binascii
try:
s = socket.socket( socket.PF_PACKET , socket.SOCK_RAW , socket.ntohs(0x0003))
except socket.error , msg:
print 'Sajnos gond van. Hiba: ' + str(msg[0]) + ' - ' + msg[1]
sys.exit()show = 'y'
ethLength = 14while True:
packet = s.recvfrom(65565)
ethHeader = packet[0][0:14]
ethUnp = struct.unpack("!6s6s2s", ethHeader)# 800:IP, 806:ARP
ethType = binascii.hexlify(ethUnp[2])print 'Ethernet Frame:'
print ' Source MAC: ' + binascii.hexlify(ethUnp[1]) + ', Dest MAC: ' + binascii.hexlify(ethUnp[0])# ARP
if ethType == '0806':
arpHeader = packet[0][14:42]
arpHeaderUnp = struct.unpack('!2s2s1s1s2s6s4s6s4s', arpHeader)
print 'ARP Header:'
print ' Hardware type: ' + binascii.hexlify(arpHeaderUnp[0]) + ', Hardware size: ' + binascii.hexlify(arpHeaderUnp[2])
print ' Protocol type: ' + binascii.hexlify(arpHeaderUnp[1]) + ', Protocol size: ' + binascii.hexlify(arpHeaderUnp[3])
print ' Source IP: ' + binascii.hexlify(arpHeaderUnp[6]) + ', Source MAC: ' + binascii.hexlify(arpHeaderUnp[5])
print ' Dest IP: ' + binascii.hexlify(arpHeaderUnp[8]) + ', Dest MAC: ' + binascii.hexlify(arpHeaderUnp[7])
print ' Opcode: ' + binascii.hexlify(arpHeaderUnp[4])# IP
elif ethType == '0800':
ipHeader = packet[0][14:34]
ipHeaderUnp = struct.unpack("!BBHHHBBH4s4s", ipHeader)version = ipHeaderUnp[0] >> 4
ihl = ipHeaderUnp[0] & 0xF
iphLength = ihl * 4totalLength = ipHeaderUnp[2]
identification = ipHeaderUnp[3]
ttl = ipHeaderUnp[5]
protocol = ipHeaderUnp[6]
headerChecksum = ipHeaderUnp[7]
sIP = socket.inet_ntoa(ipHeaderUnp[8])
dIP = socket.inet_ntoa(ipHeaderUnp[9])print 'IP Header:'
print ' Source IP: ' + sIP + ', Dest. IP: ' + dIP
print ' Version: ' + str(version) + ', IP Header length: ' + str(iphLength)
print ' TTL: ' + str(ttl) + ', Protocol: ' + str(protocol)
print ' Id: ' + str(identification) + ', TotalLength: ' + str(totalLength)
print ' Header Checksum: ' + str(headerChecksum)
if protocol == 6 :
tcpHeader = packet[0][34:54]
tcpHeaderUnp = struct.unpack("!HHLLBBHHH", tcpHeader)sPort = tcpHeaderUnp[0]
dPort = tcpHeaderUnp[1]
seqNumber = tcpHeaderUnp[2]
ackNumber = tcpHeaderUnp[3]
offsetReserved = tcpHeaderUnp[4]
tcpLength = offsetReserved >> 4tcpFlags = tcpHeaderUnp[5]
window = tcpHeaderUnp[6]
tcpChecksum = tcpHeaderUnp[7]
urgPointer = tcpHeaderUnp[8]print 'TCP Header:'
print ' Source Port: ' + str(sPort) + ', Dest. Port: ' + str(dPort)
print ' Sequence number: ' + str(seqNumber) + ', Ack Number: ' + str(ackNumber)
print ' TCP Length: ' + str(tcpLength)
print ' Window: ' + str(window) + ', TCP_Checksum: ' + str(tcpChecksum)
print ' Urgent Pointer: ' + str(urgPointer)if show == 'y' :
tcpHeaderLength = ethLength + iphLength + tcpLength*4
tcpData = packet[0][tcpHeaderLength:]
print ' Data: ' + str(tcpData)
elif protocol == 1 :
temp = ethLength + iphLength
icmpHeader = packet[0][temp:temp+4]
icmphUnp = struct.unpack('!BBH' , icmpHeader)
type = icmphUnp[0]
code = icmphUnp[1]
checksum = icmphUnp[2]print 'ICMP header:'
print ' type: ' + str(type) + ', code: ' + str(code)
print ' checksum: ' + str(checksum)elif protocol == 17 :
temp = ethLength + iphLength
udpHeader = packet[0][temp:temp+8]
udpHeaderUnp = struct.unpack('!HHHH' , udpHeader)
sPort = udpHeaderUnp[0]
dPort = udpHeaderUnp[1]
udpLength = udpHeaderUnp[2]
udpChecksum = udpHeaderUnp[3]print 'UDP Header:'
print ' Source Port: ' + str(sPort) + ', Dest. Port: ' + str(dPort)
print ' Length: ' + str(udpLength) + ', Checksum: ' + str(udpChecksum)if show == 'y' :
udpHeaderLength = ethLength + iphLength + udpLength
udpData = packet[0][udpHeaderLength:]
print ' Data: ' + str(udpData)print '---------------------------'
Futtassuk most a módosított sniffer alkalmazásunkat, ahol már az ARP csomagok is kiértékelésre kerülnek (8. ábra).
8.ábra Sniffer alkalmazásunk futás közben
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. . . .