diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba0430d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/Packet-Sniffer.py b/Packet-Sniffer.py index 82918fe..19089c7 100644 --- a/Packet-Sniffer.py +++ b/Packet-Sniffer.py @@ -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() diff --git a/README.md b/README.md index b274a63..bf760f5 100644 --- a/README.md +++ b/README.md @@ -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
+Within IPv4: TCP, UDP, ICMP.
+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 diff --git a/format_packets.py b/format_packets.py new file mode 100644 index 0000000..58182fd --- /dev/null +++ b/format_packets.py @@ -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)]) diff --git a/ipv4_packets.py b/ipv4_packets.py new file mode 100644 index 0000000..0f0c9c0 --- /dev/null +++ b/ipv4_packets.py @@ -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)) \ No newline at end of file diff --git a/ipv6_packets.py b/ipv6_packets.py new file mode 100644 index 0000000..3af381a --- /dev/null +++ b/ipv6_packets.py @@ -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 diff --git a/multiple_protocol_methods.py b/multiple_protocol_methods.py new file mode 100644 index 0000000..8c18b2e --- /dev/null +++ b/multiple_protocol_methods.py @@ -0,0 +1,77 @@ +import struct +import socket +from format_packets import * + +# 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 + + + +def icmp_packet_template_method(data): + 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)) + +# 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:] + + +def tcp_template_method(raw_data, data): + 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)) + +def udp_template_method(data): + 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)) + +# Unpacks for any TCP Packet +def tcp_seg(data): + (src_port, destination_port, sequence, acknowledgement, offset_reserved_flag) = struct.unpack('! H H L L H', data[:14]) + offset = (offset_reserved_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, destination_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:] \ No newline at end of file