1local datafiles = require "datafiles"
2local nmap = require "nmap"
3local stdnse = require "stdnse"
4local string = require "string"
5local table = require "table"
6
7description = [[
8Shows extra information about IPv6 addresses, such as embedded MAC or IPv4 addresses when available.
9
10Some IP address formats encode extra information; for example some IPv6
11addresses encode an IPv4 address or MAC address. This script can decode
12these address formats:
13* IPv4-compatible IPv6 addresses,
14* IPv4-mapped IPv6 addresses,
15* Teredo IPv6 addresses,
16* 6to4 IPv6 addresses,
17* IPv6 addresses using an EUI-64 interface ID,
18* IPv4-embedded IPv6 addresses,
19* IPv4-translated IPv6 addresses and
20* ISATAP Modified EUI-64 IPv6 addresses.
21
22See RFC 4291 for general IPv6 addressing architecture and the
23definitions of some terms.
24]]
25
26---
27-- @output
28-- Nmap scan report for ::1.2.3.4
29-- Host script results:
30-- | address-info:
31-- |   IPv4-compatible:
32-- |_    IPv4 address: 1.2.3.4
33--
34-- Nmap scan report for ::ffff:1.2.3.4
35-- Host script results:
36-- | address-info:
37-- |   IPv4-mapped:
38-- |_    IPv4 address: 1.2.3.4
39--
40-- Nmap scan report for 2001:0:506:708:282a:3d75:fefd:fcfb
41-- Host script results:
42-- | address-info:
43-- |   Teredo:
44-- |     Server IPv4 address: 5.6.7.8
45-- |     Client IPv4 address: 1.2.3.4
46-- |_    UDP port: 49802
47--
48-- Nmap scan report for 2002:102:304::1
49-- Host script results:
50-- | address-info:
51-- |   6to4:
52-- |_    IPv4 address: 1.2.3.4
53--
54-- Nmap scan report for fe80::a8bb:ccff:fedd:eeff
55-- Host script results:
56-- | address-info:
57-- |   IPv6 EUI-64:
58-- |     MAC address:
59-- |       address: aa:bb:cc:dd:ee:ff
60-- |_      manuf: Unknown
61--
62-- Nmap scan report for 64:ff9b::c000:221
63-- Host script results:
64-- | address-info:
65-- |   IPv4-embedded IPv6 address:
66-- |_    IPv4 address: 192.0.2.33
67--
68-- Nmap scan report for ::ffff:0:c0a8:101
69-- Host script results:
70-- | address-info:
71-- |   IPv4-translated IPv6 address:
72-- |_    IPv4 address: 192.168.1.1
73
74-- * ISATAP. RFC 5214.
75--   XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d
76
77---
78--@xmloutput
79-- <table key="IPv4-mapped">
80--   <elem key="IPv4 address">1.2.3.4</elem>
81-- </table>
82--
83-- <table key="IPv4-compatible">
84--   <elem key="IPv4 address">1.2.3.4</elem>
85-- </table>
86--
87-- <table key="Teredo">
88--   <elem key="Server IPv4 address">5.6.7.8</elem>
89--   <elem key="Client IPv4 address">1.2.3.4</elem>
90--   <elem key="UDP port">49802</elem>
91-- </table>
92--
93-- <table key="6to4">
94--   <elem key="IPv4 address">1.2.3.4</elem>
95-- </table>
96--
97-- <table key="IPv6 EUI-64">
98--   <table key="MAC address">
99--     <elem key="address">aa:bb:cc:dd:ee:ff</elem>
100--     <elem key="manuf">Unknown</elem>
101--   </table>
102-- </table>
103--
104-- <table key="IPv4-embedded IPv6 address">
105--   <elem key="IPv4 address">192.0.2.33</elem>
106-- </table>
107--
108-- <table key="IPv4-translated IPv6 address">
109--   <elem key="IPv4 address">192.168.1.1</elem>
110-- </table>
111
112author = "David Fifield"
113
114license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
115
116categories = {"default", "safe"}
117
118
119hostrule = function(host)
120  return true
121end
122
123-- Match an address (array of bytes) against a hex-encoded pattern. "XX" in the
124-- pattern is a wildcard.
125local function matches(addr, pattern)
126  local octet_patterns
127
128  octet_patterns = {}
129  for op in pattern:gmatch("([%xX][%xX])") do
130    octet_patterns[#octet_patterns + 1] = op
131  end
132
133  if #addr ~= #octet_patterns then
134    return false
135  end
136
137  for i = 1, #addr do
138    local a, op
139
140    a = addr[i]
141    op = octet_patterns[i]
142    if not (op == "XX" or a == tonumber(op, 16)) then
143      return false
144    end
145  end
146
147  return true
148end
149
150local function get_manuf(mac)
151  local catch = function() return "Unknown" end
152  local try = nmap.new_try(catch)
153  local mac_prefixes = try(datafiles.parse_mac_prefixes())
154  local prefix = string.upper(string.format("%02x%02x%02x", mac[1], mac[2], mac[3]))
155  return mac_prefixes[prefix] or "Unknown"
156end
157
158local function format_mac(mac)
159  local out = stdnse.output_table()
160  out.address = stdnse.format_mac(string.char(table.unpack(mac)))
161  out.manuf = get_manuf(mac)
162  return out
163end
164
165local function format_ipv4(ipv4)
166  local octets
167
168  octets = {}
169  for _, v in ipairs(ipv4) do
170    octets[#octets + 1] = string.format("%d", v)
171  end
172
173  return table.concat(octets, ".")
174end
175
176local function do_ipv4(addr)
177end
178
179-- EUI-64 from MAC, RFC 4291.
180local function decode_eui_64(eui_64)
181  if eui_64[4] == 0xff and eui_64[5] == 0xfe then
182    return { (eui_64[1] ~ 0x02),
183      eui_64[2], eui_64[3], eui_64[6], eui_64[7], eui_64[8] }
184  end
185end
186
187local function do_ipv6(addr)
188  local label
189  local output
190
191  output = stdnse.output_table()
192
193  if matches(addr, "0000:0000:0000:0000:0000:0000:0000:0001") then
194    -- ::1 is localhost. Not much to report.
195    return nil
196  elseif matches(addr, "0000:0000:0000:0000:0000:0000:XXXX:XXXX") then
197    -- RFC 4291 2.5.5.1.
198    local ipv4 = { addr[13], addr[14], addr[15], addr[16] }
199    return {["IPv4-compatible"]= { ["IPv4 address"] = format_ipv4(ipv4) } }
200  elseif matches(addr, "0000:0000:0000:0000:0000:ffff:XXXX:XXXX") then
201    -- RFC 4291 2.5.5.2.
202    local ipv4 = { addr[13], addr[14], addr[15], addr[16] }
203    return {["IPv4-mapped"]= { ["IPv4 address"] = format_ipv4(ipv4) } }
204  elseif matches(addr, "2001:0000:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then
205    -- Teredo, RFC 4380.
206    local server_ipv4 = { addr[5], addr[6], addr[7], addr[8] }
207    -- RFC 5991 makes the flags mostly meaningless.
208    local flags = addr[9] * 256 + addr[10]
209    local obs_port = addr[11] * 256 + addr[12]
210    local obs_client_ipv4 = { addr[13], addr[14], addr[15], addr[16] }
211    local port, client_ipv4
212
213    -- Invert obs_port.
214    port = obs_port ~ 0xffff
215
216    -- Invert obs_client_ipv4.
217    client_ipv4 = {}
218    for _, octet in ipairs(obs_client_ipv4) do
219      client_ipv4[#client_ipv4 + 1] = octet ~ 0xff
220    end
221
222    output["Server IPv4 address"] = format_ipv4(server_ipv4)
223    output["Client IPv4 address"] = format_ipv4(client_ipv4)
224    output["UDP port"] = tostring(port)
225
226    return {["Teredo"] = output}
227  elseif matches(addr, "0064:ff9b:XXXX:XXXX:00XX:XXXX:XXXX:XXXX") then
228    --IPv4-embedded IPv6 addresses. RFC 6052, Section 2
229
230    --skip addr[9]
231    if matches(addr,"0064:ff9b:0000:0000:0000:0000:XXXX:XXXX") then
232      local ipv4 = {addr[13], addr[14], addr[15], addr[16]}
233      return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
234    elseif addr[5] ~= 0x01 then
235      local ipv4 = {addr[5], addr[6], addr[7], addr[8]}
236      return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
237    elseif addr[6] ~= 0x22 then
238      local ipv4 = {addr[6], addr[7], addr[8], addr[10]}
239      return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
240    elseif addr[7] ~= 0x03 then
241      local ipv4 = {addr[7], addr[8], addr[10], addr[11]}
242      return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
243    elseif addr[8] ~= 0x44 then
244      local ipv4 = {addr[8], addr[10], addr[11], addr[12]}
245      return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
246    elseif addr[10] == 0x00 and addr[11] == 0x00 and addr[12] == 0x00 then
247      local ipv4 = {addr[13], addr[14], addr[15], addr[16]}
248      return {["IPv4-embedded IPv6 address"]= {["IPv4 address"] = format_ipv4(ipv4)}}
249    end
250  elseif matches(addr, "0000:0000:0000:0000:ffff:0000:XXXX:XXXX") then
251    -- IPv4-translated IPv6 addresses. RFC 2765, Section 2.1
252    return {["IPv4-translated IPv6 address"]=
253      {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}}
254  elseif matches(addr, "XXXX:XXXX:XXXX:XX00:0000:5efe:XXXX:XXXX") then
255    -- ISATAP. RFC 5214, Appendix A
256    -- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d
257    return {["ISATAP Modified EUI-64 IPv6 Address"]=
258      {["IPv4 address"] = format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}}
259  end
260
261  -- These following use common handling for the Interface ID part
262  -- (last 64 bits).
263
264  if matches(addr, "2002:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then
265    -- 6to4, RFC 3056.
266    local ipv4 = { addr[3], addr[4], addr[5], addr[6] }
267
268    label = "6to4"
269    output["IPv4 address"] = format_ipv4(ipv4)
270  end
271
272  local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12],
273    addr[13], addr[14], addr[15], addr[16] })
274  if mac then
275    output["MAC address"] = format_mac(mac)
276    if not label then
277      label = "IPv6 EUI-64"
278    end
279  end
280
281  if label then
282    return {[label]= output}
283  end
284  -- else no match
285end
286
287action = function(host)
288  local addr_s, addr_t
289
290  addr_s = host.bin_ip
291  addr_t = { string.byte(addr_s, 1, #addr_s) }
292
293  if #addr_t == 4 then
294    return do_ipv4(addr_t)
295  elseif #addr_t == 16 then
296    return do_ipv6(addr_t)
297  end
298end
299