1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
  
     | 
    
      local coroutine = require "coroutine"
local ipOps = require "ipOps"
local nmap = require "nmap"
local packet = require "packet"
local stdnse = require "stdnse"
local string = require "string"
local tab = require "tab"
local table = require "table"
local target = require "target"
description = [[
Sends an ICMPv6 packet with an invalid extension header to the
all-nodes link-local multicast address (<code>ff02::1</code>) to
discover (some) available hosts on the LAN. This works because some
hosts will respond to this probe with an ICMPv6 Parameter Problem
packet.
]]
---
-- @usage
-- ./nmap -6 --script=targets-ipv6-multicast-invalid-dst.nse --script-args 'newtargets,interface=eth0' -sP
-- @output
-- Pre-scan script results:
-- | targets-ipv6-multicast-invalid-dst:
-- |   IP: 2001:0db8:0000:0000:0000:0000:0000:0001  MAC: 11:22:33:44:55:66  IFACE: eth0
-- |_  Use --script-args=newtargets to add the results as targets
-- @args newtargets  If true, add discovered targets to the scan queue.
-- @args targets-ipv6-multicast-invalid-dst.interface  The interface to use for host discovery.
author = {"David Fifield", "Xu Weilin"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery","broadcast"}
prerule = function()
  return nmap.is_privileged()
end
--- Build an IPv6 invalid extension header.
-- @param nxt_hdr integer that stands for next header's type
local function build_invalid_extension_header(nxt_hdr)
  -- RFC 2640, section 4.2 defines the TLV format of options headers.
  -- It is important that the first byte have 10 in the most significant
  -- bits; that instructs the receiver to send a Parameter Problem.
  -- Option type 0x80 is unallocated; see
  -- http://www.iana.org/assignments/ipv6-parameters/.
  return string.char(nxt_hdr, 0) .. --next header, length 8
  "\x80\x01\x00\x00\x00\x00"
end
local function get_interfaces()
  local interface_name = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
    or nmap.get_interface()
  -- interfaces list (decide which interfaces to broadcast on)
  local interfaces = {}
  if interface_name then
    -- single interface defined
    local if_table = nmap.get_interface_info(interface_name)
    if if_table and ipOps.ip_to_str(if_table.address) and if_table.link == "ethernet" then
      interfaces[#interfaces + 1] = if_table
    else
      stdnse.debug1("Interface not supported or not properly configured.")
    end
  else
    for _, if_table in ipairs(nmap.list_interfaces()) do
      if ipOps.ip_to_str(if_table.address) and if_table.link == "ethernet" then
        table.insert(interfaces, if_table)
      end
    end
  end
  return interfaces
end
local function single_interface_broadcast(if_nfo, results)
  stdnse.debug1("Starting " .. SCRIPT_NAME .. " on " .. if_nfo.device)
  local condvar = nmap.condvar(results)
  local src_mac = if_nfo.mac
  local src_ip6 = ipOps.ip_to_str(if_nfo.address)
  local dst_mac = packet.mactobin("33:33:00:00:00:01")
  local dst_ip6 = ipOps.ip_to_str("ff02::1")
  ----------------------------------------------------------------------------
  --Multicast invalid destination exheader probe
  local dnet = nmap.new_dnet()
  local pcap = nmap.new_socket()
  local function catch ()
    dnet:ethernet_close()
    pcap:pcap_close()
  end
  local try = nmap.new_try(catch)
  try(dnet:ethernet_open(if_nfo.device))
  pcap:pcap_open(if_nfo.device, 128, false, "icmp6 and ip6[6:1] = 58 and ip6[40:1] = 4")
  local probe = packet.Frame:new()
  probe.mac_src = src_mac
  probe.mac_dst = dst_mac
  probe.ip_bin_src = src_ip6
  probe.ip_bin_dst = dst_ip6
  -- In addition to setting an invalid option in
  -- build_invalid_extension_header, we set an unknown ICMPv6 type of
  -- 254. (See http://www.iana.org/assignments/icmpv6-parameters for
  -- allocations.) Mac OS X 10.6 appears to send a Parameter Problem
  -- response only if both of these conditions are met. In this we differ
  -- from the alive6 tool, which sends a proper echo request.
  probe.icmpv6_type = 254
  probe.icmpv6_code = 0
  -- Add a non-empty payload too.
  probe.icmpv6_payload = "\x00\x00\x00\x00"
  probe:build_icmpv6_header()
  probe.exheader = build_invalid_extension_header(packet.IPPROTO_ICMPV6)
  probe.ip6_nhdr = packet.IPPROTO_DSTOPTS
  probe:build_ipv6_packet()
  probe:build_ether_frame()
  try(dnet:ethernet_send(probe.frame_buf))
  pcap:set_timeout(1000)
  local pcap_timeout_count = 0
  local nse_timeout = 5
  local start_time = nmap:clock()
  local cur_time = nmap:clock()
  local addrs = {}
  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 l2reply = packet.Frame:new(layer2)
      if l2reply.mac_dst == src_mac then
        local reply = packet.Packet:new(layer3)
        local target_str = reply.ip_src
        if not results[target_str] then
          if target.ALLOW_NEW_TARGETS then
            target.add(target_str)
          end
          results[#results + 1] = { address = target_str, mac = stdnse.format_mac(l2reply.mac_src), iface = if_nfo.device }
          results[target_str] = true
        end
      end
    end
  until pcap_timeout_count >= 2 or cur_time - start_time >= nse_timeout
  dnet:ethernet_close()
  pcap:pcap_close()
  condvar("signal")
end
local function format_output(results)
  local output = tab.new()
  for _, record in ipairs(results) do
    tab.addrow(output, "IP: " .. record.address, "MAC: " .. record.mac, "IFACE: " .. record.iface)
  end
  if #results > 0 then
    output = { tab.dump(output) }
    if not target.ALLOW_NEW_TARGETS then
      output[#output + 1] = "Use --script-args=newtargets to add the results as targets"
    end
    return stdnse.format_output(true, output)
  end
end
action = function()
  local threads = {}
  local results = {}
  local condvar = nmap.condvar(results)
  for _, if_nfo in ipairs(get_interfaces()) do
    -- create a thread for each interface
    local co = stdnse.new_thread(single_interface_broadcast, if_nfo, results)
    threads[co] = true
  end
  repeat
    for thread in pairs(threads) do
      if coroutine.status(thread) == "dead" then threads[thread] = nil end
    end
    if ( next(threads) ) then
      condvar "wait"
    end
  until next(threads) == nil
  return format_output(results)
end
 
     |