local bit = require "bit" local datafiles = require "datafiles" local nmap = require "nmap" local stdnse = require "stdnse" local string = require "string" description = [[ Shows extra information about IPv6 addresses, such as embedded MAC or IPv4 addresses when available. Some IP address formats encode extra information; for example some IPv6 addresses encode an IPv4 address or MAC address. This script can decode these address formats: * IPv4-compatible IPv6 addresses, * IPv4-mapped IPv6 addresses, * Teredo IPv6 addresses, * 6to4 IPv6 addresses, * IPv6 addresses using an EUI-64 interface ID, * IPv4-embedded IPv6 addresses, * IPv4-translated IPv6 addresses and * ISATAP Modified EUI-64 IPv6 addresses. See RFC 4291 for general IPv6 addressing architecture and the definitions of some terms. ]] --- -- @output -- Nmap scan report for ::1.2.3.4 -- Host script results: -- |_address-info: IPv4-compatible; IPv4 address: 1.2.3.4 -- -- Nmap scan report for ::ffff:1.2.3.4 -- Host script results: -- |_address-info: IPv4-mapped; IPv4 address: 1.2.3.4 -- -- Nmap scan report for 2001:0:506:708:282a:3d75:fefd:fcfb -- Host script results: -- | address-info: Teredo; -- | Server IPv4 address: 5.6.7.8 -- | Client IPv4 address: 1.2.3.4 -- |_ UDP port: 49802 -- -- Nmap scan report for 2002:102:304::1 -- Host script results: -- |_address-info: 6to4; IPv4 address: 1.2.3.4 -- -- Nmap scan report for fe80::a8bb:ccff:fedd:eeff -- Host script results: -- |_address-info: IPv6 EUI-64; MAC address: aa:bb:cc:dd:ee:ff (Unknown) -- -- Nmap scan report for 64:ff9b::c000:221 -- Host script results: -- |_address-info: IPv4-embedded IPv6 address; IPv4 address: 192.0.2.33 -- -- Nmap scan report for ::ffff:0:c0a8:101 -- Host script results: -- |_address-info: IPv4-translated IPv6 address; IPv4 address: 192.168.1.1 -- -- * ISATAP. RFC 5214. -- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d author = "David Fifield" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "safe"} hostrule = function(host) return true end -- Match an address (array of bytes) against a hex-encoded pattern. "XX" in the -- pattern is a wildcard. local function matches(addr, pattern) local octet_patterns octet_patterns = {} for op in pattern:gmatch("([%xX][%xX])") do octet_patterns[#octet_patterns + 1] = op end if #addr ~= #octet_patterns then return false end for i = 1, #addr do local a, op a = addr[i] op = octet_patterns[i] if not (op == "XX" or a == tonumber(op, 16)) then return false end end return true end local function get_manuf(mac) if not nmap.registry.mac then local catch = function() return end local try = nmap.new_try(catch) -- Create the table in the registry so we can share between scripts nmap.registry.mac = {} nmap.registry.mac.prefixes = try(datafiles.parse_mac_prefixes()) end local prefix = string.upper(string.format("%02x%02x%02x", mac[1], mac[2], mac[3])) local manuf = nmap.registry.mac.prefixes[prefix] or "Unknown" return manuf end local function format_mac(mac) local octets octets = {} for _, v in ipairs(mac) do octets[#octets + 1] = string.format("%02x", v) end return stdnse.strjoin(":", octets) .. " (" .. get_manuf(mac) .. ")" end local function format_ipv4(ipv4) local octets octets = {} for _, v in ipairs(ipv4) do octets[#octets + 1] = string.format("%d", v) end return stdnse.strjoin(".", octets) end local function do_ipv4(addr) end -- EUI-64 from MAC, RFC 4291. local function decode_eui_64(eui_64) if eui_64[4] == 0xff and eui_64[5] == 0xfe then return { bit.bxor(eui_64[1], 0x02), eui_64[2], eui_64[3], eui_64[6], eui_64[7], eui_64[8] } end end local function format_output(label, lines) local output output = "" if #lines == 0 then return label elseif #lines == 1 then if label then output = output .. label .. "; " end return output .. lines[1] else if label then output = output .. label .. ";" end return output .. "\n " .. stdnse.strjoin("\n ", lines) end end local function do_ipv6(addr) local label local output output = {} if matches(addr, "0000:0000:0000:0000:0000:0000:XXXX:XXXX") and not matches(addr, "0000:0000:0000:0000:0000:0000:0000:0001") then -- RFC 4291 2.5.5.1. Specifically exclude ::1 for localhost. local ipv4 = { addr[13], addr[14], addr[15], addr[16] } return format_output("IPv4-compatible", { "IPv4 address: " .. format_ipv4(ipv4) }) elseif matches(addr, "0000:0000:0000:0000:0000:ffff:XXXX:XXXX") then -- RFC 4291 2.5.5.2. local ipv4 = { addr[13], addr[14], addr[15], addr[16] } return format_output("IPv4-mapped", { "IPv4 address: " .. format_ipv4(ipv4) }) elseif matches(addr, "2001:0000:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then -- Teredo, RFC 4380. local server_ipv4 = { addr[5], addr[6], addr[7], addr[8] } -- RFC 5991 makes the flags mostly meaningless. local flags = addr[9] * 256 + addr[10] local obs_port = addr[11] * 256 + addr[12] local obs_client_ipv4 = { addr[13], addr[14], addr[15], addr[16] } local port, client_ipv4 -- Invert obs_port. port = bit.bxor(obs_port, 0xffff) -- Invert obs_client_ipv4. client_ipv4 = {} for _, octet in ipairs(obs_client_ipv4) do client_ipv4[#client_ipv4 + 1] = bit.bxor(octet, 0xff) end output[#output + 1] = "Server IPv4 address: " .. format_ipv4(server_ipv4) output[#output + 1] = "Client IPv4 address: " .. format_ipv4(client_ipv4) output[#output + 1] = "UDP port: " .. tostring(port) return format_output("Teredo", output) elseif matches(addr, "0064:ff9b:XXXX:XXXX:00XX:XXXX:XXXX:XXXX") then --IPv4-embedded IPv6 addresses. RFC 6052, Section 2 --skip addr[9] if matches(addr,"0064:ff9b:0000:0000:0000:0000:XXXX:XXXX") then local ipv4 = {addr[13], addr[14], addr[15], addr[16]} return format_output("IPv4-embedded IPv6 address",{"IPv4 address: "..format_ipv4(ipv4)}) elseif addr[5] ~= 0x01 then local ipv4 = {addr[5], addr[6], addr[7], addr[8]} return format_output("IPv4-embedded IPv6 address",{"IPv4 address: "..format_ipv4(ipv4)}) elseif addr[6] ~= 0x22 then local ipv4 = {addr[6], addr[7], addr[8], addr[10]} return format_output("IPv4-embedded IPv6 address",{"IPv4 address: "..format_ipv4(ipv4)}) elseif addr[7] ~= 0x03 then local ipv4 = {addr[7], addr[8], addr[10], addr[11]} return format_output("IPv4-embedded IPv6 address",{"IPv4 address: "..format_ipv4(ipv4)}) elseif addr[8] ~= 0x44 then local ipv4 = {addr[8], addr[10], addr[11], addr[12]} return format_output("IPv4-embedded IPv6 address",{"IPv4 address: "..format_ipv4(ipv4)}) elseif addr[10] == 0x00 and addr[11] == 0x00 and addr[12] == 0x00 then local ipv4 = {addr[13], addr[14], addr[15], addr[16]} return format_output("IPv4-embedded IPv6 address",{"IPv4 address: "..format_ipv4(ipv4)}) end elseif matches(addr, "0000:0000:0000:0000:ffff:0000:XXXX:XXXX") then -- IPv4-translated IPv6 addresses. RFC 2765, Section 2.1 return format_output("IPv4-translated IPv6 address", {"IPv4 address: "..format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}) elseif matches(addr, "XXXX:XXXX:XXXX:XX00:0000:5efe:XXXX:XXXX") then -- ISATAP. RFC 5214, Appendix A -- XXXX:XXXX:XXXX:XX00:0000:5EFE:a.b.c.d return format_output("ISATAP Modified EUI-64 IPv6 Address", {"IPv4 address: "..format_ipv4( {addr[13], addr[14], addr[15], addr[16]})}) end -- These following use common handling for the Interface ID part -- (last 64 bits). if matches(addr, "2002:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX") then -- 6to4, RFC 3056. local ipv4 = { addr[3], addr[4], addr[5], addr[6] } local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15], addr[16] }) label = "6to4" output[#output + 1] = "IPv4 address: " .. format_ipv4(ipv4) end local mac = decode_eui_64({ addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15], addr[16] }) if mac then output[#output + 1] = "MAC address: " .. format_mac(mac) if not label then label = "IPv6 EUI-64" end end return format_output(label, output) end action = function(host) local addr_s, addr_t addr_s = host.bin_ip addr_t = { string.byte(addr_s, 1, #addr_s) } if #addr_t == 4 then return do_ipv4(addr_t) elseif #addr_t == 16 then return do_ipv6(addr_t) end end