description = [[ SLAAC-based host discovery. Do a very fast host discovery on link-local IPv6 network. ]] --- -- @usage -- ./nmap -6 --script=slaac_host_discovery.nse --script-args 'newtargets,interface=eth0,ipv6="2001:da8:215:3320:223:aeff:fe5d:3b10"' -sP author = "David and Weilin" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery"} require 'nmap' require 'target' require 'packet' require "os" require "math" prerule = function() return nmap.is_privileged() and (stdnse.get_script_args("interface") or nmap.get_interface()) end catch = function() dnet:ethernet_close() pcap:pcap_close() end try = nmap.new_try(catch) --- Get a Unique-local Address with random global ID. -- @param local_scope The scope of the address, local or reserved. -- @return A 16-byte string of IPv6 address, and the length of the prefix. local function get_radom_ula_prefix(local_scope) local ula_prefix math.randomseed(os.time()) local global_id = string.char(math.random(256)-1,math.random(256)-1,math.random(256)-1,math.random(256)-1,math.random(256)-1) if local_scope then ula_prefix = packet.ipv6tobin("fd00::") else ula_prefix = packet.ipv6tobin("fc00::") end ula_prefix = string.sub(ula_prefix,1,1) .. global_id .. string.sub(ula_prefix,7,-1) return ula_prefix,64 end local function get_ipv6_interface_info_by_name(if_name) local ifaces = nmap.list_interfaces() local iface local if_nfo for _,iface in pairs(ifaces) do if if_name == iface.device and (#iface.address>15 or string.find(iface.address, "::")) then if_nfo = iface return if_nfo end end return nil end action = function() local if_name = stdnse.get_script_args("interface") or nmap.get_interface() local dnet = nmap.new_dnet() local pcap = nmap.new_socket() try(dnet:ethernet_open(if_name)) pcap:pcap_open(if_name, 128, true, "src ::0/128 and dst net ff02::1:0:0/96 and icmp6 and ip6[6:1] = 58 and ip6[40:1] = 135") local if_nfo = get_ipv6_interface_info_by_name(if_name) local if_in6 = stdnse.get_script_args("ipv6.src") or if_nfo.address local actual_prefix = string.sub(packet.ipv6tobin(if_in6),1,8) local src_mac = if_nfo.mac local ula_prefix, prefix_len = get_radom_ula_prefix() -- preferred_lifetime <= valid_lifetime. -- Nmap will get the whole IPv6 addresses of each host if the two parameters are both longer than 5 seconds. -- Sometimes it makes sense to regard the several addresses of a host as different hosts, as the host's administrator may apply different firewall configurations on them. local valid_lifetime = 6 local preferred_lifetime = 6 local probe = packet.Frame:new() probe:build_router_advert(src_mac,ula_prefix,prefix_len,valid_lifetime,preferred_lifetime) probe:build_icmpv6_header() probe:build_ipv6_packet() probe:build_ether_frame() try(dnet:ethernet_send(probe.frame_buf)) local expected_mac_dst_prefix = packet.mactobin("33:33:ff:00:00:00") local expected_ip6_src = packet.ipv6tobin("::") local expected_ip6_dst_prefix = packet.ipv6tobin("ff02::1:0:0") local id_set = {} pcap:set_timeout(1000) local pcap_timeout_count = 0 local nse_timeout = 5 local start_time = nmap:clock() local cur_time = nmap:clock() repeat local status, length, layer2, layer3 = pcap:pcap_receive() cur_time = nmap:clock() if not status then pcap_timeout_count = pcap_timeout_count + 1 else local reply = packet.Frame:new(layer2) if string.sub(reply.mac_dst, 1, 3) == string.sub(expected_mac_dst_prefix, 1, 3) then reply = packet.Packet:new(layer3) if reply.ip6_src == expected_ip6_src and string.sub(expected_ip6_dst_prefix,1,12) == string.sub(reply.ip6_dst,1,12) then local ula_target_addr_str = packet.toipv6(reply.ns_target) stdnse.print_debug(0,"Discovery ULA addr: " .. ula_target_addr_str) local identifier = string.sub(reply.ns_target,9,-1) --Filter out the reduplicative identifiers. --A host will send several NS packets with the same interface identifier if it receives several RA packets with different prefix during the discovery phase. if not id_set[identifier] then id_set[identifier] = true local actual_addr_str = packet.toipv6(actual_prefix .. identifier) target.add(actual_addr_str) end end end end until pcap_timeout_count >= 2 or cur_time - start_time >= nse_timeout dnet:ethernet_close() pcap:pcap_close() return true end