diff --git a/CHANGELOG b/CHANGELOG
index 60465b434..5730c37e8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
# Nmap Changelog ($Id$); -*-text-*-
+o Added the broadcast-ping script which sends icmp packets to broadcast
+ addresses on the selected network interface, or all ethernet interfaces if
+ none is selected. It has the option to add the discovered hosts as targets.
+
o [NSE] Applied patch from Chris Woodbury that adds the following additional
information to the output of smb-os-discovery:
+ Forest name
diff --git a/scripts/broadcast-ping.nse b/scripts/broadcast-ping.nse
new file mode 100755
index 000000000..37dfa4251
--- /dev/null
+++ b/scripts/broadcast-ping.nse
@@ -0,0 +1,274 @@
+description = [[
+Sends broadcast pings on a selected interface using raw ethernet packets and
+outputs the responder hosts' IP and MAC addresses. r00t permissions are a
+prerequisite. Most operating systems don't respond to broadcast-ping probes,
+but they can be configured to do so.
+
+The interface on which is broadcasted can be specified using the -e Nmap option
+or the broadcast-ping.interface script-arg. If no interface is
+specified this script broadcasts on all ethernet interfaces which have an IPv4
+address defined.
+
+The newtarget script-arg can be used so the script adds the
+discovered IPs as targets.
+
+The timeout of the ICMP probes can be specified using the timeout
+script-arg. The default timeout is 3000 ms. A higher number might be necesary
+when scanning across larger networks.
+
+The number of sent probes can be specified using the num-probes
+script-arg. The default number is 1. A higher value might get more results on
+larger networks.
+
+The ICMP probes sent comply with the --ttl and --data-length Nmap options, so
+you can use those to control the TTL(time to live) and ICMP payload length
+respectively. The default value for TTL is 64, and the length of the payload
+is 0. The payload is consisted of random bytes.
+]]
+
+---
+-- @usage
+-- nmap -e [--ttl ] [--data-length ]
+-- --script broadcast-ping [--script-args [broadcast-ping.timeout=],[num-probes=]]
+--
+-- @arg interface string specifying which interface to use for this script
+-- @arg num_probes number specifying how many ICMP probes should be sent
+-- @arg timeout number specifying how long to wait for response in miliseconds
+--
+-- @output
+-- | broadcast-ping:
+-- | IP: 192.168.1.1 MAC: 00:23:69:2a:b1:25
+-- | IP: 192.168.1.106 MAC: 1c:65:9d:88:d8:36
+-- |_ Use the newtargets script-arg to add the results as targets
+--
+--
+
+author = "Gorjan Petrovski"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"discovery","safe"}
+
+require "nmap"
+require "stdnse"
+require "packet"
+require "openssl"
+require "bin"
+require "target"
+
+prerule = function()
+ if not nmap.is_privileged() then
+ nmap.registry[SCRIPT_NAME] = nmap.registry[SCRIPT_NAME] or {}
+ if not nmap.registry[SCRIPT_NAME].rootfail then
+ stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
+ end
+ nmap.registry[SCRIPT_NAME].rootfail = true
+ return nil
+ end
+
+ if nmap.address_family() ~= 'inet' then
+ stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
+ return false
+ end
+
+ return true
+end
+
+
+--- ICMP packet crafting
+--
+-- @param srcIP string containing the source IP, IPv4 format
+-- @param dstIP string containing the destination IP, IPv4 format
+-- @param ttl number containing value for the TTL (time to live) field in IP header
+-- @param data_length number value of ICMP payload length
+local icmp_packet = function(srcIP, dstIP, ttl, data_length, mtu, seqNo, icmp_id)
+ -- A couple of checks first
+ assert((seqNo and seqNo>0 and seqNo<=0xffff),"ICMP Sequence number: Value out of range(1-65535).")
+ assert((ttl and ttl>0 and ttl<0xff),"TTL(time-to-live): Value out of range(1-256).")
+ -- MTU values should be considered here!
+ assert((data_length and data_length>=0 and data_length0 then
+ icmp_payload = openssl.rand_bytes(data_length)
+ else
+ icmp_payload = ""
+ end
+
+ local seqNo_hex = stdnse.tohex(seqNo)
+ local icmp_seqNo = bin.pack(">H", string.rep("0",(4-seqNo_hex))..seqNo_hex)
+
+ -- Type=08; Code=00; Chksum=0000; ID=icmp_id; SeqNo=icmp_seqNo; Payload=icmp_payload(hex string);
+ local icmp_tmp = bin.pack(">HAAA", "0800 0000", icmp_id, icmp_seqNo, icmp_payload)
+
+ local icmp_checksum = packet.in_cksum(icmp_tmp)
+
+ local icmp_msg = bin.pack(">HHAAA", "0800", stdnse.tohex(icmp_checksum), icmp_id, icmp_seqNo, icmp_payload)
+
+
+ --IP Total Length
+ local length_hex = stdnse.tohex(20 + #icmp_msg)
+ local ip_length = bin.pack(">H", string.rep("0",(4-#length_hex))..length_hex)
+
+ --TTL
+ local ttl_hex = stdnse.tohex(ttl)
+ local ip_ttl = bin.pack(">H", string.rep("0",(2-ttl_hex))..ttl_hex)
+
+ --IP header
+ local ip_bin = bin.pack(">HAHAH","4500",ip_length, "0000 4000", ip_ttl,
+ "01 0000 0000 0000 0000 0000")
+
+ -- IP+ICMP; Addresses and checksum need to be filled
+ local icmp_bin = bin.pack(">AA",ip_bin, icmp_msg)
+
+ --Packet
+ icmp = packet.Packet:new(icmp_bin,#icmp_bin)
+ assert(icmp,"Mistake during ICMP packet parsing")
+
+ icmp:ip_set_bin_src(packet.iptobin(srcIP))
+ icmp:ip_set_bin_dst(packet.iptobin(dstIP))
+ icmp:ip_count_checksum()
+
+ return icmp
+end
+
+local broadcast_if = function(if_table,icmp_responders)
+ local condvar = nmap.condvar(icmp_responders)
+
+ local num_probes = stdnse.get_script_args(SCRIPT_NAME .. ".num-probes")
+ if not num_probes then num_probes = 1 end
+
+ local timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout")
+ if not timeout then timeout = 3000 end
+
+ local ttl = nmap.get_ttl()
+
+ local data_length = nmap.get_payload_length()
+ local sequence_number = 1
+ local destination_IP = "255.255.255.255"
+
+ -- raw IPv4 socket
+ local dnet = nmap.new_dnet()
+ try = nmap.new_try()
+ try = nmap.new_try(function() dnet:ethernet_close() end)
+
+ -- raw sniffing socket (icmp echoreply style)
+ local pcap = nmap.new_socket()
+ pcap:set_timeout(timeout)
+
+ local mtu = if_table.mtu or 256 -- 256 is minimal mtu
+
+ pcap:pcap_open(if_table.device, 104, false, "dst host ".. if_table.address ..
+ " and icmp[icmptype]==icmp-echoreply")
+ try(dnet:ethernet_open(if_table.device))
+
+ local source_IP = if_table.address
+
+ local icmp_ids = {}
+
+ for i = 1, num_probes do
+ -- ICMP packet
+ local icmp_id = openssl.rand_bytes(2)
+ icmp_ids[icmp_id]=true
+ local icmp = icmp_packet( source_IP, destination_IP, ttl,
+ data_length, mtu, sequence_number, icmp_id)
+
+ local ethernet_icmp = bin.pack("HAHA", "FF FF FF FF FF FF", if_table.mac, "08 00", icmp.buf)
+
+ try( dnet:ethernet_send(ethernet_icmp) )
+ end
+
+ while true do
+ local status, plen, l2, l3data, _ = pcap:pcap_receive()
+ if not status then break end
+
+ -- Do stuff with packet
+ local icmpreply = packet.Packet:new(l3data,plen,false)
+ -- We check whether the packet is parsed ok, and whether the ICMP ID of the sent packet
+ -- is the same with the ICMP ID of the received packet. We don't want ping probes interfering
+ local icmp_id = icmpreply:raw(icmpreply.icmp_offset+4,2)
+ if icmpreply:ip_parse() and icmp_ids[icmp_id] then
+ if not icmp_responders[icmpreply.ip_src] then
+ -- [key = IP]=MAC
+ local mac_pretty = string.format("%02x:%02x:%02x:%02x:%02x:%02x",l2:byte(7),
+ l2:byte(8),l2:byte(9),l2:byte(10),l2:byte(11),l2:byte(12))
+ icmp_responders[icmpreply.ip_src] = mac_pretty
+ end
+ else
+ stdnse.print_debug("Erroneous ICMP packet received; Cannot parse IP header.")
+ end
+ end
+
+ pcap:close()
+ dnet:ethernet_close()
+
+ condvar "signal"
+end
+
+
+action = function()
+
+ --get interface script-args, if any
+ local interface_arg = stdnse.get_script_args(SCRIPT_NAME .. ".interface")
+ local interface_opt = nmap.get_interface()
+
+ -- interfaces list (decide which interfaces to broadcast on)
+ local interfaces ={}
+ if interface_opt or interface_arg then
+ -- single interface defined
+ local interface = interface_opt or interface_arg
+ local if_table = nmap.get_interface_info(interface)
+ if not if_table or not if_table.address or not if_table.link=="ethernet" then
+ stdnse.print_debug("Interface not supported or not properly configured.")
+ return false
+ end
+ table.insert(interfaces, if_table)
+ else
+ local tmp_ifaces = nmap.list_interfaces()
+ for _, if_table in ipairs(tmp_ifaces) do
+ if if_table.address and
+ if_table.link=="ethernet" and
+ if_table.address:match("%d+%.%d+%.%d+%.%d+") then
+ table.insert(interfaces, if_table)
+ end
+ end
+ end
+
+ if #interfaces == 0 then
+ stdnse.print_debug("No interfaces found.")
+ return
+ end
+
+ local icmp_responders={}
+ local threads ={}
+ local condvar = nmap.condvar(icmp_responders)
+
+ -- party time
+ for _, if_table in ipairs(interfaces) do
+ -- create a thread for each interface
+ local co = stdnse.new_thread(broadcast_if, if_table, icmp_responders)
+ threads[co]=true
+ end
+
+ repeat
+ condvar "wait"
+ for thread in pairs(threads) do
+ if coroutine.status(thread) == "dead" then threads[thread] = nil end
+ end
+ until next(threads) == nil
+
+ -- generate output
+ local output = {}
+ if target.ALLOW_NEW_TARGETS then
+ for ip_addr, mac_addr in pairs(icmp_responders) do
+ target.add(ip_addr)
+ table.insert(output,ip_addr.." "..mac_addr)
+ end
+ else
+ for ip_addr, mac_addr in pairs(icmp_responders) do
+ table.insert(output,"IP: "..ip_addr..string.rep(" ",15-#ip_addr).." MAC: "..mac_addr)
+ end
+ table.insert(output,"Use the newtargets script-arg to add the results as targets")
+ end
+
+ return stdnse.format_output( (#output>0), output )
+end
diff --git a/scripts/script.db b/scripts/script.db
index b8964b67d..2f4801167 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -16,6 +16,7 @@ Entry { filename = "broadcast-dropbox-listener.nse", categories = { "broadcast",
Entry { filename = "broadcast-ms-sql-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-netbios-master-browser.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-novell-locate.nse", categories = { "broadcast", "safe", } }
+Entry { filename = "broadcast-ping.nse", categories = { "discovery", "safe", } }
Entry { filename = "broadcast-upnp-info.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }