Introduction

IPv6 solves the address resolution problem by introducing ICMP6 Solicitation message (a type of Neighbor Discovery message) and a type of Neighbor Advertisement message. This tutorial serves two purposes.

  • We are to understand IPv6 Neighbor Disovery and Advertisement for address resolution via hands-on packet crafting experiments
  • We need this to realize part of TCP protocol via packet crafting

There are two methods to add (or to spoof) a host.

  • Add a static entry to a host’s ARP (or neighbor) table
  • Reply to ICMPv6 Neighbor Discovery message

Adding Static ARP Entry

We can add a static IPv6 neighbor via the ip neighbor command . Below is an example of using the command.

sudo ip -6 neighbor add fc00::1:1:3 lladdr 08:00:27:08:0d:a1 dev enp0s9

Replying to IPv6 Neighbor Discovery Message

The following program does just that,

#
# ipv6phantom.py
#
# don't run this on the host the IPv6 neighbor to be updated with
# run it on any other host on the same network
#
import signal
import sys
import time

from scapy.data import IP_PROTOS
from scapy.sendrecv import sendp
from scapy.sendrecv import sniff
from scapy.layers.l2 import Ether
from scapy.layers.inet6 import IPv6
from scapy.layers.inet6 import ICMPv6ND_NS
from scapy.layers.inet6 import ICMPv6ND_NA
from scapy.layers.inet6 import ICMPv6NDOptSrcLLAddr
from scapy.layers.inet6 import ICMPv6NDOptDstLLAddr

def gracefully_exit(sig, frame):
    print("Ctrl-C pressed. Exiting ...")
    sys.exit(0)

def usage(prog):
    # python ipv6phantom.py fc00::1:1:3 08:00:27:08:0d:a1 enp0s9
    print("Usage: prog PHANTOM_HOST_IPv6 PHANTOM_HW_ADDR NIC")

def main(argv):
    signal.signal(signal.SIGINT, gracefully_exit)
    if len(argv) < 4: 
        usage(argv[0])
        sys.exit(1)
    # host = 'fc00::1:1:3'
    # nic = 'enp0s9'
    # hwaddr = '08:00:27:08:0d:a1'
    host = argv[1]
    hwaddr = argv[2]
    nic = argv[3]
    filter = "icmp6 and ( ip6[40] == 135 or ip6[40] == 136 )"
    while True: 
        packets = sniff(filter=filter, count=1, iface=nic)
        if not packets[0].tgt == host:
            continue
        if not packets is None \
                and not packets[0][ICMPv6ND_NS][ICMPv6NDOptSrcLLAddr] is None \
                and packets[0][ICMPv6ND_NS][ICMPv6NDOptSrcLLAddr].type == 1:
            # https://tools.ietf.org/html/rfc2461#section-4.6.1
            ndopthdr = ICMPv6NDOptDstLLAddr()
            ndopthdr.lladdr = hwaddr
            # https://tools.ietf.org/html/rfc2461#section-4.4
            ndnahdr = ICMPv6ND_NA(R=0,S=1,O=1) 
            ndnahdr.tgt = host
            # https://tools.ietf.org/html/rfc8200#section-3
            ipv6hdr = IPv6(nh=IP_PROTOS.ipv6_icmp, hlim=255)
            ipv6hdr.src = host
            ipv6hdr.dst = packets[0][IPv6].src
            # https://tools.ietf.org/html/rfc2464
            ethhdr = Ether()
            ethhdr.dst = packets[0][ICMPv6ND_NS][ICMPv6NDOptSrcLLAddr].lladdr
            ethhdr.src = hwaddr
            # now the packet
            ndpkt = ethhdr / ipv6hdr / ndnahdr / ndopthdr
            # now transmit ...
            sendp(ndpkt, iface=nic)
            ndpkt.display()
            #print(ndpkt.summary())
            time.sleep(5)
            
if __name__ == "__main__":
    main(sys.argv)