Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
28e570c
feat: reading IPv6 header successfully!
Oct 23, 2020
17d5c76
feat: reading src/dst ipv6 address
Oct 23, 2020
d5157d9
feat: barebones protocols recognition
Oct 23, 2020
76e3c2c
feat: hop-by-hop options
Oct 23, 2020
16d6b33
feat: hop by hop FIXED calculation of data
Oct 23, 2020
497a281
feat: destination options
Oct 23, 2020
cdf9947
feat: routing header
Oct 23, 2020
b362dfa
feat: fragment header
Oct 23, 2020
c9119a6
feat: auth header
Oct 23, 2020
abe523c
feat: encapsuling header + no next header
Oct 23, 2020
a34fe95
feat: ICMPv6 and ICMPv4 fixed
Oct 23, 2020
a4ccf2a
refactor: TCP to have a template method
Oct 23, 2020
163378c
feat: TCP in ipv6
Oct 23, 2020
f1471d6
refactor: UDP to have a template method
Oct 23, 2020
96a45a9
feat: UDP in ipv6
Oct 23, 2020
0ace140
refactor: code cleanup
Oct 23, 2020
afae271
refactor: IPv6 header separated into function
Oct 23, 2020
38849c7
comment: RFC for IPv6 ext header order
Oct 23, 2020
3d7af5a
comment: yes commenting yes
Oct 23, 2020
ce00968
feat: making stuff being printable-friendly
Nov 6, 2020
8b4fa3f
feat: fixed some protocols and added ARP because why not
Nov 6, 2020
78e54a6
feat: refactoring structure of the module
11808s8 Nov 7, 2020
ef109fc
feat: add textwrap module
11808s8 Dec 9, 2020
af64a1d
feat: Facelift on the README.md doc
11808s8 Dec 9, 2020
d4f9c79
refactor: I'm not a pro on .md, excuse me
11808s8 Dec 9, 2020
b2b7a98
feat: simple .gitignore file
11808s8 Dec 9, 2020
3a1fe74
refactor: exclude __pycache__ generated files
11808s8 Dec 9, 2020
7b710cb
refactor: actually removing the pycache folder :)
11808s8 Dec 9, 2020
24bf27f
comment: doc linking to WHY a hardcoded number is read for IPv6 packe…
11808s8 Dec 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
161 changes: 57 additions & 104 deletions Packet-Sniffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,138 +3,91 @@
import socket
import struct
import textwrap

TAB_1 = '\t - '
TAB_2 = '\t\t - '
TAB_3 = '\t\t\t - '
TAB_4 = '\t\t\t\t - '

DATA_TAB_1 = '\t '
DATA_TAB_2 = '\t\t '
DATA_TAB_3 = '\t\t\t '
DATA_TAB_4 = '\t\t\t\t '
import ipv4_packets
import ipv6_packets
import multiple_protocol_methods
from format_packets import *

def main():
conn = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))
while True:
raw_data, addr = conn.recvfrom(65536)
dest_mac, src_mac, eth_proto, data = ethernet_frame(raw_data)
dest_mac, src_mac, eth_proto, data = multiple_protocol_methods.ethernet_frame(raw_data)

print('\n Ethernet Frame: ')
print(TAB_1 + 'Destination: {}, Source: {}, Protocol: {}'.format(dest_mac, src_mac, eth_proto))
print(TAB_1 + '\n Ethernet Frame: ')
print(TAB_2 + 'Destination: {}, Source: {}, Protocol: {}'.format(dest_mac, src_mac, eth_proto))

if eth_proto == 8:
(version, header_length, ttl, proto, src, target, data) = ipv4_Packet(data)
if eth_proto == 8: # IPv4

(version, header_length, ttl, proto, src, target, data) = ipv4_packets.ipv4_Packet(data)
print(TAB_1 + "IPV4 Packet:")
print(TAB_2 + 'Version: {}, Header Length: {}, TTL: {}'.format(version, header_length, ttl))
print(TAB_3 + 'protocol: {}, Source: {}, Target: {}'.format(proto, src, target))

# ICMP
if proto == 1:
icmp_type, code, checksum, data = icmp_packet(data)
print(TAB_1 + 'ICMP Packet:')
print(TAB_2 + 'Type: {}, Code: {}, Checksum: {},'.format(icmp_type, code, checksum))
print(TAB_2 + 'ICMP Data:')
print(format_output_line(DATA_TAB_3, data))
multiple_protocol_methods.icmp_packet_template_method(data)

# TCP
elif proto == 6:
src_port, dest_port, sequence, acknowledgment, flag_urg, flag_ack, flag_psh, flag_rst, flag_syn, flag_fin = struct.unpack(
'! H H L L H H H H H H', raw_data[:24])
print(TAB_1 + 'TCP Segment:')
print(TAB_2 + 'Source Port: {}, Destination Port: {}'.format(src_port, dest_port))
print(TAB_2 + 'Sequence: {}, Acknowledgment: {}'.format(sequence, acknowledgment))
print(TAB_2 + 'Flags:')
print(TAB_3 + 'URG: {}, ACK: {}, PSH: {}'.format(flag_urg, flag_ack, flag_psh))
print(TAB_3 + 'RST: {}, SYN: {}, FIN:{}'.format(flag_rst, flag_syn, flag_fin))

if len(data) > 0:
# HTTP
if src_port == 80 or dest_port == 80:
print(TAB_2 + 'HTTP Data:')
try:
http = HTTP(data)
http_info = str(http.data).split('\n')
for line in http_info:
print(DATA_TAB_3 + str(line))
except:
print(format_output_line(DATA_TAB_3, data))
else:
print(TAB_2 + 'TCP Data:')
print(format_output_line(DATA_TAB_3, data))
multiple_protocol_methods.tcp_template_method(raw_data, data)
# UDP
elif proto == 17:
src_port, dest_port, length, data = udp_seg(data)
print(TAB_1 + 'UDP Segment:')
print(TAB_2 + 'Source Port: {}, Destination Port: {}, Length: {}'.format(src_port, dest_port, length))
multiple_protocol_methods.udp_template_method(data)

# Other IPv4
else:
print(TAB_1 + 'Other IPv4 Data:')
print(format_output_line(DATA_TAB_2, data))
elif(eth_proto == 56710): # IPv6 - It is 56710 as htons converts it FROM 34525
# (the original value read - which is 86DD in HEX and indicates the packet being IPv6
# - more info on https://tools.ietf.org/html/rfc2464#section-3)

next_header, data = ipv6_packets.ipv6_header(data)

# ORDER DEFINED ON RFC8200 - https://tools.ietf.org/html/rfc8200
#Hop-by-Hop Options
if(next_header == 0 ):
next_header, data = ipv6_packets.hop_by_hop_options(data)
# pass
#Destination Options
if(next_header == 60 ):
next_header, data = ipv6_packets.destination_options(data)
#Routing
if(next_header == 43 ):
next_header, data = ipv6_packets.routing_header(data)

#Fragment
if(next_header == 44 ):
next_header, data = ipv6_packets.fragment_header(data)
#Authentication
if(next_header == 51 ):
next_header, data = ipv6_packets.authentication_header(data)

#Encapsulating Security Payload
if(next_header == 50 ):
next_header, data = ipv6_packets.encapsuling_header(data)
if(next_header == 59) :
print("No next header")
#ICMPv6
if(next_header == 58 ):
# Defined on https://tools.ietf.org/html/rfc4443#page-3 <--- The same as ICMPv4 :)
multiple_protocol_methods.icmp_packet_template_method(data)

# TCP
if(next_header == 6):
multiple_protocol_methods.tcp_template_method(raw_data, data)

# UDP
if(next_header == 17):
multiple_protocol_methods.udp_template_method(data)

elif(eth_proto==1544): # ARP
print(TAB_1 + " --- ARP Protocol ---")
else:
print('Ethernet Data:')
print(format_output_line(DATA_TAB_1, data))



# Unpack Ethernet Frame
def ethernet_frame(data):
dest_mac, src_mac, proto = struct.unpack('! 6s 6s H', data[:14])
return get_mac_addr(dest_mac), get_mac_addr(src_mac), socket.htons(proto), data[14:]

# Format MAC Address
def get_mac_addr(bytes_addr):
bytes_str = map('{:02x}'.format, bytes_addr)
mac_addr = ':'.join(bytes_str).upper()
return mac_addr

# Unpack IPv4 Packets Recieved
def ipv4_Packet(data):
version_header_len = data[0]
version = version_header_len >> 4
header_len = (version_header_len & 15) * 4
ttl, proto, src, target = struct.unpack('! 8x B B 2x 4s 4s', data[:20])
return version, header_len, ttl, proto, ipv4(src), ipv4(target), data[header_len:]

# Returns Formatted IP Address
def ipv4(addr):
return '.'.join(map(str, addr))


# Unpacks for any ICMP Packet
def icmp_packet(data):
icmp_type, code, checksum = struct.unpack('! B B H', data[:4])
return icmp_type, code, checksum, data[4:]

# Unpacks for any TCP Packet
def tcp_seg(data):
(src_port, destination_port, sequence, acknowledgenment, offset_reserv_flag) = struct.unpack('! H H L L H', data[:14])
offset = (offset_reserv_flag >> 12) * 4
flag_urg = (offset_reserved_flag & 32) >> 5
flag_ack = (offset_reserved_flag & 32) >>4
flag_psh = (offset_reserved_flag & 32) >> 3
flag_rst = (offset_reserved_flag & 32) >> 2
flag_syn = (offset_reserved_flag & 32) >> 1
flag_fin = (offset_reserved_flag & 32) >> 1

return src_port, dest_port, sequence, acknowledgement, flag_urg, flag_ack, flag_psh, flag_rst, flag_syn, flag_fin, data[offset:]


# Unpacks for any UDP Packet
def udp_seg(data):
src_port, dest_port, size = struct.unpack('! H H 2x H', data[:8])
return src_port, dest_port, size, data[8:]

# Formats the output line
def format_output_line(prefix, string, size=80):
size -= len(prefix)
if isinstance(string, bytes):
string = ''.join(r'\x{:02x}'.format(byte) for byte in string)
if size % 2:
size-= 1
return '\n'.join([prefix + line for line in textwrap.wrap(string, size)])


main()
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,40 @@

## Overview

Packet Sniffer created in Python 3. Allows you to monitor traffic running through local network. Allows the user to be able to view Source of the packets, Target host and the type of protocol used e.g. UDP/TCP.
Packet Sniffer created in Python 3.
It allows you to monitor traffic running through local network. Allows the user to be able to view Source of the packets, Target host and the type of protocol used e.g. UDP,TCP, ICMP. More on the details subsection

## Requirement
- Python 3.x
- Python 3.6.9 (untested with others, be my guest :) )
- Privileged/Administrative Rights
- Linux or Windows Operating System

## Usage

On linux:
```bash
sudo python3 Packet-Sniffer.py
```

On Windows:
To be defined

## Details

Protocols recognized: IPv4, IPv6, ARP <br>
Within IPv4: TCP, UDP, ICMP. <br>
Within IPv6: TCP, UDP, ICMPv6, Hop-by-Hop Options, Destination options, Routing, Fragment, Authentication, Encapsuling Header ( This one is just a dummy recognition as of now :) )

The original package has been broken down into a more structured approach, separating responsibilities from IPv4/IPv6 packets into their own files, as well as shared protocols into a file to be used by both.
The Packet-Sniffer.py could use a few more cleanups since we have some outputs there still.

The output formatting is a bit on the nose and needs refactoring, since it's not optimal the way it's done now.

IPv6 next header order is based on RFC8200 [1]. Other RFCs are referenced throughout the code.


* The Python 3 sockets library, for capturing the packets, is using AF_PACKET for capturing both internet protocols. Beware of that, since there are AF_INET/AF_INET6 for specific protocols (IPv4 only, IPv6 only) and your use case might not fit. More on: [2]

[1] https://tools.ietf.org/html/rfc8200

[2] https://docs.python.org/3/library/socket.html
20 changes: 20 additions & 0 deletions format_packets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import textwrap

TAB_1 = '\t - '
TAB_2 = '\t\t - '
TAB_3 = '\t\t\t - '
TAB_4 = '\t\t\t\t - '

DATA_TAB_1 = '\t '
DATA_TAB_2 = '\t\t '
DATA_TAB_3 = '\t\t\t '
DATA_TAB_4 = '\t\t\t\t '

# Formats the output line
def format_output_line(prefix, string, size=80):
size -= len(prefix)
if isinstance(string, bytes):
string = ''.join(r'\x{:02x}'.format(byte) for byte in string)
if size % 2:
size-= 1
return '\n'.join([prefix + line for line in textwrap.wrap(string, size)])
13 changes: 13 additions & 0 deletions ipv4_packets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import struct

# Unpack IPv4 Packets Recieved
def ipv4_Packet(data):
version_header_len = data[0]
version = version_header_len >> 4
header_len = (version_header_len & 15) * 4
ttl, proto, src, target = struct.unpack('! 8x B B 2x 4s 4s', data[:20])
return version, header_len, ttl, proto, ipv4(src), ipv4(target), data[header_len:]

# Returns Formatted IP Address
def ipv4(addr):
return '.'.join(map(str, addr))
113 changes: 113 additions & 0 deletions ipv6_packets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from format_packets import *
import struct
import socket

def ipv6_header(data):

first_32_bits, \
payload_length,\
next_header, \
hop_limit = struct.unpack('! IHBB', data[:8])

version = first_32_bits >> 28
traffic_class = (first_32_bits >> 20) & 255
flow_label = first_32_bits & 1048575

src_address = socket.inet_ntop(socket.AF_INET6, data[8:24])
dst_address = socket.inet_ntop(socket.AF_INET6, data[24:40])

print(TAB_1 + "IPV6 Packet:")
print(TAB_2 + "Version: {}, Traffic Class: {}, Flow Label: {}".format(version, traffic_class, flow_label))
print(TAB_2 + "Payload Length: {}, Next Header: {}, Hop Limit: {}".format(payload_length, next_header, hop_limit))
print(TAB_3 + "Source Address: {}".format(src_address))
print(TAB_3 + "Destination Address: {}".format(dst_address))

data = data[40:]
return (next_header, data)

def hop_by_hop_options(data):
next_header, header_length = struct.unpack('! B B', data[:2])

'''
BY DEFINITION ON https://tools.ietf.org/html/rfc8200#section-4.3
Hdr Ext Len 8-bit unsigned integer. Length of the
Hop-by-Hop Options header in 8-octet units,
not including the first 8 octets.


That is: 1 octet = 8 bits (1 byte)
as it uses 8 octets by default for the number in Hdr Ext len,
from that logic we have:
Hdr Ext Len * 8
As it does not include the first 8 octets, we have
to add to it
Hdr Ext Len * 8 + 8
'''

print(TAB_1 + "Hop-by-hop options:")
print(TAB_2 + "Next Header: {}, Header Length: {}".format(next_header, header_length))

data = data[:hdr_ext_len_converter(header_length)]
return (next_header, data)

def hdr_ext_len_converter(octets):
return hdr_ext_len_converter_raw(octets, 8)

def hdr_ext_len_converter_4_octets(octets):
return hdr_ext_len_converter_raw(octets, 4)

def hdr_ext_len_converter_raw(octets, default_octet_number=8):
return int(octets*default_octet_number+8)

def destination_options(data):
next_header, header_length = struct.unpack('! B B', data[:2])

print(TAB_1 + "Destination options:")
print(TAB_2 + "Next Header: {}, Header Length: {}".format(next_header, header_length))

# header length uses the same definition as HOP BY HOP options

data = data[:hdr_ext_len_converter(header_length)]
return (next_header, data)

def routing_header(data):
next_header, header_length, routing_type, segments_left = struct.unpack('! B B B B', data[:4])

# header length uses the same definition as HOP BY HOP options

print(TAB_1 + "Routing Header:")
print(TAB_2 + "Header Length: {}, Routing Type: {}, Segments Left: {}".format(next_header, routing_type,segments_left))

data = data[:hdr_ext_len_converter(header_length)]
return (next_header, data)

def fragment_header(data):
next_header, reserved, offset_res_flag_word, identification = struct.unpack('! B B H I', data[:8])

fragment_offset = offset_res_flag_word >> 3
res = offset_res_flag_word & 6
m_flag = offset_res_flag_word & 1


print(TAB_1 + "Fragment Header:")
print(TAB_2 + 'Next Header: {}, Reserved: {}, Fragment Offset: {}'.format(next_header, reserved, fragment_offset))
print(TAB_3 + 'Res: {}, M Flag: {}, Identification: {}'.format(res, m_flag, identification))

data = data[8:]
return (next_header,data)


# Defined on https://tools.ietf.org/html/rfc4302
def authentication_header(data):
next_header, payload_length, reserved, spi, sequence_number = struct.unpack('! B B H I I', data[:12])
print(TAB_1 + "Authentication Header:")
print(TAB_2 + 'Next Header: {}, Payload Length: {}, Reserved: {}'.format(next_header, payload_length, reserved))
print(TAB_3 + 'Security Parameters Index: {}, Sequence Number Field: {}'.format(spi, sequence_number))

data = data[:hdr_ext_len_converter_4_octets(payload_length)]

return (next_header, data)

def encapsuling_header(data):
print("No next header")
return (59, data) # returns a no next header, as this one is hard as heck to calculate :). More info on: https://tools.ietf.org/html/rfc4303
Loading