summaryrefslogtreecommitdiffstats
path: root/scripts/targets-ipv6-multicast-echo.nse
blob: adf34599ed4fea188e10897c30ede3c186e428c0 (plain)
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
local coroutine = require "coroutine"
local ipOps = require "ipOps"
local nmap = require "nmap"
local packet = require "packet"
local stdnse = require "stdnse"
local tab = require "tab"
local table = require "table"
local target = require "target"

description = [[
Sends an ICMPv6 echo request packet to the all-nodes link-local
multicast address (<code>ff02::1</code>) to discover responsive hosts
on a LAN without needing to individually ping each IPv6 address.
]]

---
-- @usage
-- ./nmap -6 --script=targets-ipv6-multicast-echo.nse --script-args 'newtargets,interface=eth0' -sL
-- @output
-- Pre-scan script results:
-- | targets-ipv6-multicast-echo:
-- |   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-echo.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

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 echo ping 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] = 129")

  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
  probe.echo_id = 5
  probe.echo_seq = 6
  probe.echo_data = "Nmap host discovery."
  probe:build_icmpv6_echo_request()
  probe:build_icmpv6_header()
  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()

  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..layer3)
      if reply.mac_dst == src_mac then
        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(reply.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