mirror of
https://github.com/fortra/impacket
synced 2026-06-08 14:15:13 +00:00
531 lines
19 KiB
Python
531 lines
19 KiB
Python
# Impacket - Collection of Python classes for working with network protocols.
|
|
#
|
|
# Copyright Fortra, LLC and its affiliated companies
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# This software is provided under a slightly modified version
|
|
# of the Apache Software License. See the accompanying LICENSE file
|
|
# for more information.
|
|
#
|
|
|
|
import array
|
|
import struct
|
|
|
|
from impacket.ImpactPacket import Header, Data, array_tobytes
|
|
from impacket.IP6_Address import IP6_Address
|
|
|
|
|
|
class ICMP6(Header):
|
|
#IP Protocol number for ICMP6
|
|
IP_PROTOCOL_NUMBER = 58
|
|
protocol = IP_PROTOCOL_NUMBER #ImpactDecoder uses the constant "protocol" as the IP Protocol Number
|
|
|
|
#Size of ICMP6 header (excluding payload)
|
|
HEADER_SIZE = 4
|
|
|
|
#ICMP6 Message Type numbers
|
|
DESTINATION_UNREACHABLE = 1
|
|
PACKET_TOO_BIG = 2
|
|
TIME_EXCEEDED = 3
|
|
PARAMETER_PROBLEM = 4
|
|
ECHO_REQUEST = 128
|
|
ECHO_REPLY = 129
|
|
ROUTER_SOLICITATION = 133
|
|
ROUTER_ADVERTISEMENT = 134
|
|
NEIGHBOR_SOLICITATION = 135
|
|
NEIGHBOR_ADVERTISEMENT = 136
|
|
REDIRECT_MESSAGE = 137
|
|
NODE_INFORMATION_QUERY = 139
|
|
NODE_INFORMATION_REPLY = 140
|
|
|
|
#Destination Unreachable codes
|
|
NO_ROUTE_TO_DESTINATION = 0
|
|
ADMINISTRATIVELY_PROHIBITED = 1
|
|
BEYOND_SCOPE_OF_SOURCE_ADDRESS = 2
|
|
ADDRESS_UNREACHABLE = 3
|
|
PORT_UNREACHABLE = 4
|
|
SOURCE_ADDRESS_FAILED_INGRESS_EGRESS_POLICY = 5
|
|
REJECT_ROUTE_TO_DESTINATION = 6
|
|
|
|
#Time Exceeded codes
|
|
HOP_LIMIT_EXCEEDED_IN_TRANSIT = 0
|
|
FRAGMENT_REASSEMBLY_TIME_EXCEEDED = 1
|
|
|
|
#Parameter problem codes
|
|
ERRONEOUS_HEADER_FIELD_ENCOUNTERED = 0
|
|
UNRECOGNIZED_NEXT_HEADER_TYPE_ENCOUNTERED = 1
|
|
UNRECOGNIZED_IPV6_OPTION_ENCOUNTERED = 2
|
|
|
|
#Node Information codes
|
|
NODE_INFORMATION_QUERY_IPV6 = 0
|
|
NODE_INFORMATION_QUERY_NAME_OR_EMPTY = 1
|
|
NODE_INFORMATION_QUERY_IPV4 = 2
|
|
NODE_INFORMATION_REPLY_SUCCESS = 0
|
|
NODE_INFORMATION_REPLY_REFUSED = 1
|
|
NODE_INFORMATION_REPLY_UNKNOWN_QTYPE = 2
|
|
|
|
#Node Information qtypes
|
|
NODE_INFORMATION_QTYPE_NOOP = 0
|
|
NODE_INFORMATION_QTYPE_UNUSED = 1
|
|
NODE_INFORMATION_QTYPE_NODENAME = 2
|
|
NODE_INFORMATION_QTYPE_NODEADDRS = 3
|
|
NODE_INFORMATION_QTYPE_IPv4ADDRS = 4
|
|
|
|
#ICMP Message semantic types (error or informational)
|
|
ERROR_MESSAGE = 0
|
|
INFORMATIONAL_MESSAGE = 1
|
|
|
|
#ICMP message dictionary - specifying text descriptions and valid message codes
|
|
#Key: ICMP message number
|
|
#Data: Tuple ( Message Type (error/informational), Text description, Codes dictionary (can be None) )
|
|
#Codes dictionary
|
|
#Key: Code number
|
|
#Data: Text description
|
|
|
|
#ICMP message dictionary tuple indexes
|
|
MSG_TYPE_INDEX = 0
|
|
DESCRIPTION_INDEX = 1
|
|
CODES_INDEX = 2
|
|
|
|
icmp_messages = {
|
|
DESTINATION_UNREACHABLE : (ERROR_MESSAGE, "Destination unreachable",
|
|
{ NO_ROUTE_TO_DESTINATION : "No route to destination",
|
|
ADMINISTRATIVELY_PROHIBITED : "Administratively prohibited",
|
|
BEYOND_SCOPE_OF_SOURCE_ADDRESS : "Beyond scope of source address",
|
|
ADDRESS_UNREACHABLE : "Address unreachable",
|
|
PORT_UNREACHABLE : "Port unreachable",
|
|
SOURCE_ADDRESS_FAILED_INGRESS_EGRESS_POLICY : "Source address failed ingress/egress policy",
|
|
REJECT_ROUTE_TO_DESTINATION : "Reject route to destination"
|
|
}),
|
|
PACKET_TOO_BIG : (ERROR_MESSAGE, "Packet too big", None),
|
|
TIME_EXCEEDED : (ERROR_MESSAGE, "Time exceeded",
|
|
{HOP_LIMIT_EXCEEDED_IN_TRANSIT : "Hop limit exceeded in transit",
|
|
FRAGMENT_REASSEMBLY_TIME_EXCEEDED : "Fragment reassembly time exceeded"
|
|
}),
|
|
PARAMETER_PROBLEM : (ERROR_MESSAGE, "Parameter problem",
|
|
{
|
|
ERRONEOUS_HEADER_FIELD_ENCOUNTERED : "Erroneous header field encountered",
|
|
UNRECOGNIZED_NEXT_HEADER_TYPE_ENCOUNTERED : "Unrecognized Next Header type encountered",
|
|
UNRECOGNIZED_IPV6_OPTION_ENCOUNTERED : "Unrecognized IPv6 Option Encountered"
|
|
}),
|
|
ECHO_REQUEST : (INFORMATIONAL_MESSAGE, "Echo request", None),
|
|
ECHO_REPLY : (INFORMATIONAL_MESSAGE, "Echo reply", None),
|
|
ROUTER_SOLICITATION : (INFORMATIONAL_MESSAGE, "Router Solicitation", None),
|
|
ROUTER_ADVERTISEMENT : (INFORMATIONAL_MESSAGE, "Router Advertisement", None),
|
|
NEIGHBOR_SOLICITATION : (INFORMATIONAL_MESSAGE, "Neighbor Solicitation", None),
|
|
NEIGHBOR_ADVERTISEMENT : (INFORMATIONAL_MESSAGE, "Neighbor Advertisement", None),
|
|
REDIRECT_MESSAGE : (INFORMATIONAL_MESSAGE, "Redirect Message", None),
|
|
NODE_INFORMATION_QUERY: (INFORMATIONAL_MESSAGE, "Node Information Query", None),
|
|
NODE_INFORMATION_REPLY: (INFORMATIONAL_MESSAGE, "Node Information Reply", None),
|
|
}
|
|
|
|
|
|
|
|
|
|
############################################################################
|
|
def __init__(self, buffer = None):
|
|
Header.__init__(self, self.HEADER_SIZE)
|
|
if (buffer):
|
|
self.load_header(buffer)
|
|
|
|
def get_header_size(self):
|
|
return self.HEADER_SIZE
|
|
|
|
def get_ip_protocol_number(self):
|
|
return self.IP_PROTOCOL_NUMBER
|
|
|
|
def __str__(self):
|
|
type = self.get_type()
|
|
code = self.get_code()
|
|
checksum = self.get_checksum()
|
|
|
|
s = "ICMP6 - Type: " + str(type) + " - " + self.__get_message_description() + "\n"
|
|
s += "Code: " + str(code)
|
|
if (self.__get_code_description() != ""):
|
|
s += " - " + self.__get_code_description()
|
|
s += "\n"
|
|
s += "Checksum: " + str(checksum) + "\n"
|
|
return s
|
|
|
|
def __get_message_description(self):
|
|
return self.icmp_messages[self.get_type()][self.DESCRIPTION_INDEX]
|
|
|
|
def __get_code_description(self):
|
|
code_dictionary = self.icmp_messages[self.get_type()][self.CODES_INDEX]
|
|
if (code_dictionary is None):
|
|
return ""
|
|
else:
|
|
return code_dictionary[self.get_code()]
|
|
|
|
############################################################################
|
|
def get_type(self):
|
|
return (self.get_byte(0))
|
|
|
|
def get_code(self):
|
|
return (self.get_byte(1))
|
|
|
|
def get_checksum(self):
|
|
return (self.get_word(2))
|
|
|
|
############################################################################
|
|
def set_type(self, type):
|
|
self.set_byte(0, type)
|
|
|
|
def set_code(self, code):
|
|
self.set_byte(1, code)
|
|
|
|
def set_checksum(self, checksum):
|
|
self.set_word(2, checksum)
|
|
|
|
############################################################################
|
|
def calculate_checksum(self):
|
|
#Initialize the checksum value to 0 to yield a correct calculation
|
|
self.set_checksum(0)
|
|
#Fetch the pseudo header from the IP6 parent packet
|
|
pseudo_header = self.parent().get_pseudo_header()
|
|
#Fetch the ICMP data
|
|
icmp_header = self.get_bytes()
|
|
#Build an array of bytes concatenating the pseudo_header, the ICMP header and the ICMP data (if present)
|
|
checksum_array = array.array('B')
|
|
checksum_array.extend(pseudo_header)
|
|
checksum_array.extend(icmp_header)
|
|
if (self.child()):
|
|
checksum_array.extend(self.child().get_bytes())
|
|
|
|
#Compute the checksum over that array
|
|
self.set_checksum(self.compute_checksum(checksum_array))
|
|
|
|
def is_informational_message(self):
|
|
return self.icmp_messages[self.get_type()][self.MSG_TYPE_INDEX] == self.INFORMATIONAL_MESSAGE
|
|
|
|
def is_error_message(self):
|
|
return self.icmp_messages[self.get_type()][self.MSG_TYPE_INDEX] == self.ERROR_MESSAGE
|
|
|
|
def is_well_formed(self):
|
|
well_formed = True
|
|
|
|
#Check that the message type is known
|
|
well_formed &= self.get_type() in self.icmp_messages.keys()
|
|
|
|
#Check that the code is known (zero, if there are no codes defined)
|
|
code_dictionary = self.icmp_messages[self.get_type()][self.CODES_INDEX]
|
|
if (code_dictionary is None):
|
|
well_formed &= self.get_code() == 0
|
|
else:
|
|
well_formed &= self.get_code() in code_dictionary.keys()
|
|
|
|
return well_formed
|
|
|
|
############################################################################
|
|
|
|
@classmethod
|
|
def Echo_Request(class_object, id, sequence_number, arbitrary_data = None):
|
|
return class_object.__build_echo_message(ICMP6.ECHO_REQUEST, id, sequence_number, arbitrary_data)
|
|
|
|
@classmethod
|
|
def Echo_Reply(class_object, id, sequence_number, arbitrary_data = None):
|
|
return class_object.__build_echo_message(ICMP6.ECHO_REPLY, id, sequence_number, arbitrary_data)
|
|
|
|
@classmethod
|
|
def __build_echo_message(class_object, type, id, sequence_number, arbitrary_data):
|
|
#Build ICMP6 header
|
|
icmp_packet = ICMP6()
|
|
icmp_packet.set_type(type)
|
|
icmp_packet.set_code(0)
|
|
|
|
#Pack ICMP payload
|
|
icmp_bytes = struct.pack('>H', id)
|
|
icmp_bytes += struct.pack('>H', sequence_number)
|
|
if (arbitrary_data is not None):
|
|
icmp_bytes += array_tobytes(array.array('B', arbitrary_data))
|
|
icmp_payload = Data()
|
|
icmp_payload.set_data(icmp_bytes)
|
|
|
|
#Link payload to header
|
|
icmp_packet.contains(icmp_payload)
|
|
|
|
return icmp_packet
|
|
|
|
|
|
############################################################################
|
|
@classmethod
|
|
def Destination_Unreachable(class_object, code, originating_packet_data = None):
|
|
unused_bytes = [0x00, 0x00, 0x00, 0x00]
|
|
return class_object.__build_error_message(ICMP6.DESTINATION_UNREACHABLE, code, unused_bytes, originating_packet_data)
|
|
|
|
@classmethod
|
|
def Packet_Too_Big(class_object, MTU, originating_packet_data = None):
|
|
MTU_bytes = struct.pack('!L', MTU)
|
|
return class_object.__build_error_message(ICMP6.PACKET_TOO_BIG, 0, MTU_bytes, originating_packet_data)
|
|
|
|
@classmethod
|
|
def Time_Exceeded(class_object, code, originating_packet_data = None):
|
|
unused_bytes = [0x00, 0x00, 0x00, 0x00]
|
|
return class_object.__build_error_message(ICMP6.TIME_EXCEEDED, code, unused_bytes, originating_packet_data)
|
|
|
|
@classmethod
|
|
def Parameter_Problem(class_object, code, pointer, originating_packet_data = None):
|
|
pointer_bytes = struct.pack('!L', pointer)
|
|
return class_object.__build_error_message(ICMP6.PARAMETER_PROBLEM, code, pointer_bytes, originating_packet_data)
|
|
|
|
@classmethod
|
|
def __build_error_message(class_object, type, code, data, originating_packet_data):
|
|
#Build ICMP6 header
|
|
icmp_packet = ICMP6()
|
|
icmp_packet.set_type(type)
|
|
icmp_packet.set_code(code)
|
|
|
|
#Pack ICMP payload
|
|
icmp_bytes = array_tobytes(array.array('B', data))
|
|
if (originating_packet_data is not None):
|
|
icmp_bytes += array_tobytes(array.array('B', originating_packet_data))
|
|
icmp_payload = Data()
|
|
icmp_payload.set_data(icmp_bytes)
|
|
|
|
#Link payload to header
|
|
icmp_packet.contains(icmp_payload)
|
|
|
|
return icmp_packet
|
|
|
|
############################################################################
|
|
|
|
@classmethod
|
|
def Neighbor_Solicitation(class_object, target_address):
|
|
return class_object.__build_neighbor_message(ICMP6.NEIGHBOR_SOLICITATION, target_address)
|
|
|
|
@classmethod
|
|
def Neighbor_Advertisement(class_object, target_address):
|
|
return class_object.__build_neighbor_message(ICMP6.NEIGHBOR_ADVERTISEMENT, target_address)
|
|
|
|
@classmethod
|
|
def __build_neighbor_message(class_object, msg_type, target_address):
|
|
#Build ICMP6 header
|
|
icmp_packet = ICMP6()
|
|
icmp_packet.set_type(msg_type)
|
|
icmp_packet.set_code(0)
|
|
|
|
# Flags + Reserved
|
|
icmp_bytes = array_tobytes(array.array('B', [0x00] * 4))
|
|
|
|
# Target Address: The IP address of the target of the solicitation.
|
|
# It MUST NOT be a multicast address.
|
|
icmp_bytes += array_tobytes(array.array('B', IP6_Address(target_address).as_bytes()))
|
|
|
|
icmp_payload = Data()
|
|
icmp_payload.set_data(icmp_bytes)
|
|
|
|
#Link payload to header
|
|
icmp_packet.contains(icmp_payload)
|
|
|
|
return icmp_packet
|
|
|
|
############################################################################
|
|
|
|
def get_target_address(self):
|
|
return IP6_Address(self.child().get_bytes()[4:20])
|
|
|
|
def set_target_address(self, target_address):
|
|
address = IP6_Address(target_address)
|
|
payload_bytes = self.child().get_bytes()
|
|
payload_bytes[4:20] = address.get_bytes()
|
|
self.child().set_bytes(payload_bytes)
|
|
|
|
# 0 1 2 3 4 5 6 7
|
|
# +-+-+-+-+-+-+-+-+
|
|
# |R|S|O|reserved |
|
|
# +-+-+-+-+-+-+-+-+
|
|
|
|
def get_neighbor_advertisement_flags(self):
|
|
return self.child().get_byte(0)
|
|
|
|
def set_neighbor_advertisement_flags(self, flags):
|
|
self.child().set_byte(0, flags)
|
|
|
|
def get_router_flag(self):
|
|
return (self.get_neighbor_advertisement_flags() & 0x80) != 0
|
|
|
|
def set_router_flag(self, flag_value):
|
|
curr_flags = self.get_neighbor_advertisement_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x80
|
|
else:
|
|
curr_flags &= ~0x80
|
|
self.set_neighbor_advertisement_flags(curr_flags)
|
|
|
|
def get_solicited_flag(self):
|
|
return (self.get_neighbor_advertisement_flags() & 0x40) != 0
|
|
|
|
def set_solicited_flag(self, flag_value):
|
|
curr_flags = self.get_neighbor_advertisement_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x40
|
|
else:
|
|
curr_flags &= ~0x40
|
|
self.set_neighbor_advertisement_flags(curr_flags)
|
|
|
|
def get_override_flag(self):
|
|
return (self.get_neighbor_advertisement_flags() & 0x20) != 0
|
|
|
|
def set_override_flag(self, flag_value):
|
|
curr_flags = self.get_neighbor_advertisement_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x20
|
|
else:
|
|
curr_flags &= ~0x20
|
|
self.set_neighbor_advertisement_flags(curr_flags)
|
|
|
|
############################################################################
|
|
@classmethod
|
|
def Node_Information_Query(class_object, code, payload = None):
|
|
return class_object.__build_node_information_message(ICMP6.NODE_INFORMATION_QUERY, code, payload)
|
|
|
|
@classmethod
|
|
def Node_Information_Reply(class_object, code, payload = None):
|
|
return class_object.__build_node_information_message(ICMP6.NODE_INFORMATION_REPLY, code, payload)
|
|
|
|
@classmethod
|
|
def __build_node_information_message(class_object, type, code, payload = None):
|
|
#Build ICMP6 header
|
|
icmp_packet = ICMP6()
|
|
icmp_packet.set_type(type)
|
|
icmp_packet.set_code(code)
|
|
|
|
#Pack ICMP payload
|
|
qtype = 0
|
|
flags = 0
|
|
nonce = [0x00] * 8
|
|
|
|
icmp_bytes = struct.pack('>H', qtype)
|
|
icmp_bytes += struct.pack('>H', flags)
|
|
icmp_bytes += array_tobytes(array.array('B', nonce))
|
|
|
|
if payload is not None:
|
|
icmp_bytes += array_tobytes(array.array('B', payload))
|
|
|
|
icmp_payload = Data()
|
|
icmp_payload.set_data(icmp_bytes)
|
|
|
|
#Link payload to header
|
|
icmp_packet.contains(icmp_payload)
|
|
|
|
return icmp_packet
|
|
|
|
def get_qtype(self):
|
|
return self.child().get_word(0)
|
|
|
|
def set_qtype(self, qtype):
|
|
self.child().set_word(0, qtype)
|
|
|
|
def get_nonce(self):
|
|
return self.child().get_bytes()[4:12]
|
|
|
|
def set_nonce(self, nonce):
|
|
payload_bytes = self.child().get_bytes()
|
|
payload_bytes[4:12] = array.array('B', nonce)
|
|
self.child().set_bytes(payload_bytes)
|
|
|
|
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
# | unused |G|S|L|C|A|T|
|
|
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
def get_flags(self):
|
|
return self.child().get_word(2)
|
|
|
|
def set_flags(self, flags):
|
|
self.child().set_word(2, flags)
|
|
|
|
def get_flag_T(self):
|
|
return (self.get_flags() & 0x0001) != 0
|
|
|
|
def set_flag_T(self, flag_value):
|
|
curr_flags = self.get_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x0001
|
|
else:
|
|
curr_flags &= ~0x0001
|
|
self.set_flags(curr_flags)
|
|
|
|
def get_flag_A(self):
|
|
return (self.get_flags() & 0x0002) != 0
|
|
|
|
def set_flag_A(self, flag_value):
|
|
curr_flags = self.get_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x0002
|
|
else:
|
|
curr_flags &= ~0x0002
|
|
self.set_flags(curr_flags)
|
|
|
|
def get_flag_C(self):
|
|
return (self.get_flags() & 0x0004) != 0
|
|
|
|
def set_flag_C(self, flag_value):
|
|
curr_flags = self.get_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x0004
|
|
else:
|
|
curr_flags &= ~0x0004
|
|
self.set_flags(curr_flags)
|
|
|
|
def get_flag_L(self):
|
|
return (self.get_flags() & 0x0008) != 0
|
|
|
|
def set_flag_L(self, flag_value):
|
|
curr_flags = self.get_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x0008
|
|
else:
|
|
curr_flags &= ~0x0008
|
|
self.set_flags(curr_flags)
|
|
|
|
def get_flag_S(self):
|
|
return (self.get_flags() & 0x0010) != 0
|
|
|
|
def set_flag_S(self, flag_value):
|
|
curr_flags = self.get_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x0010
|
|
else:
|
|
curr_flags &= ~0x0010
|
|
self.set_flags(curr_flags)
|
|
|
|
def get_flag_G(self):
|
|
return (self.get_flags() & 0x0020) != 0
|
|
|
|
def set_flag_G(self, flag_value):
|
|
curr_flags = self.get_flags()
|
|
if flag_value:
|
|
curr_flags |= 0x0020
|
|
else:
|
|
curr_flags &= ~0x0020
|
|
self.set_flags(curr_flags)
|
|
|
|
def set_node_information_data(self, data):
|
|
payload_bytes = self.child().get_bytes()
|
|
payload_bytes[12:] = array.array('B', data)
|
|
self.child().set_bytes(payload_bytes)
|
|
|
|
def get_note_information_data(self):
|
|
return self.child().get_bytes()[12:]
|
|
|
|
############################################################################
|
|
def get_echo_id(self):
|
|
return self.child().get_word(0)
|
|
|
|
def get_echo_sequence_number(self):
|
|
return self.child().get_word(2)
|
|
|
|
def get_echo_arbitrary_data(self):
|
|
return self.child().get_bytes()[4:]
|
|
|
|
def get_mtu(self):
|
|
return self.child().get_long(0)
|
|
|
|
def get_parm_problem_pointer(self):
|
|
return self.child().get_long(0)
|
|
|
|
def get_originating_packet_data(self):
|
|
return self.child().get_bytes()[4:]
|