Nmap Development mailing list archives
[NSE] Script to enumerate network interfaces
From: Thomas Buchanan <tbuchanan () thecompassgrp net>
Date: Thu, 04 Mar 2010 18:45:41 -0600
Hello. Using Patrik Karlsson's excellent SNMP scripts [1] as a starting point, I've put together a script that attempts to enumerate network interfaces. It's working for me now against several SNMP implementations, including net-snmp, OpenBSD's new snmpd engine, Microsoft Windows, and Cisco IOS. I'd love for other folks to try it and let me know if it works, and what you think of the output. It uses information from standard SNMP Management Information Bases (IF-MIB and IP-MIB) which should give it a good chance of working on most network devices that support the protocol.
When run as a default script (without the verbose flag), it only lists devices that are marked as up and ignores any software loopback interfaces. It shows only the interface description and any IP address that is associated with it. For example:
161/udp open snmp | snmp-sysdescr: Powered by OpenBSD |_ System uptime: 0 days, 0:27:21.32 (164132 timeticks) | snmp-interfaces: | vic0 |_ IP address: 192.168.221.126 161/udp open snmp | snmp-interfaces: | eth0 | IP address: 192.168.221.128 | tun0 |_ IP address: 192.168.129.54When the script is run with the verbose flag (or called directly) it prints out information about all network interfaces, active or not, and provides additional details:
161/udp open snmp | snmp-interfaces: | lo | IP address: 127.0.0.1/255.0.0.0 | Type: softwareLoopback (10 Mbps) | Status: up | Traffic stats: 2.79 Mb sent, 2.79 Mb received | eth0 | IP address: 192.168.221.128/255.255.255.0 | MAC address: 00:0c:29:01:e2:74 (VMware) | Type: ethernetCsmacd (1 Gbps) | Status: up | Traffic stats: 883.96 Kb sent, 2.26 Mb received | sit0 | Type: tunnel (0 Kbps) | Status: down | Traffic stats: 0.00 Kb sent, 0.00 Kb received | tun0 | IP address: 192.168.129.54/255.255.255.255 | Type: other (0 Kbps) | Status: up |_ Traffic stats: 114.80 Kb sent, 1.41 Mb receivedObviously this can get pretty lengthy, especially when run against a managed switch (or a Windows 7 system). I'd be open to ideas as to how to manage the output better, if it seems like too much.
Thanks to Patrik for a great starting point, and to the developers of the SNMP library that make the encoding and decoding so transparent.
As always, questions and comments are encouraged. Thomas [1] http://seclists.org/nmap-dev/2010/q1/178
description = [[ Attempts to enumerate network interfaces through SNMP ]] --- -- @output -- | snmp-interfaces: -- | eth0 -- |_ IP address: 192.168.128.15 -- -- author = "Thomas Buchanan" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} dependencies = {"snmp-brute"} -- code borrowed heavily from Patrik Karlsson's excellent snmp scripts -- Created 03/03/2010 - v0.1 - created by Thomas Buchanan <tbuchanan () thecompassgrp net> require "shortport" require "snmp" require "datafiles" portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) --- Walks the MIB Tree -- -- @param socket socket already connected to the server -- @base_oid string containing the base object ID to walk -- @return table containing <code>oid</code> and <code>value</code> function snmp_walk( socket, base_oid ) local catch = function() socket:close() end local try = nmap.new_try(catch) local snmp_table = {} local oid = base_oid while ( true ) do local value, response, snmpdata, options, item = nil, nil, nil, {}, {} options.reqId = 28428 -- unnecessary? payload = snmp.encode( snmp.buildPacket( snmp.buildGetNextRequest(options, oid) ) ) try(socket:send(payload)) response = try( socket:receive_bytes(1) ) snmpdata = snmp.fetchResponseValues( response ) value = snmpdata[1][1] oid = snmpdata[1][2] if not oid:match( base_oid ) or base_oid == oid then break end item.oid = oid item.value = value table.insert( snmp_table, item ) end socket:close() snmp_table.baseoid = base_oid return snmp_table end --- Gets a value for the specified oid -- -- @param tbl table containing <code>oid</code> and <code>value</code> -- @param oid string containing the object id for which the value should be extracted -- @return value of relevant type or nil if oid was not found function get_value_from_table( tbl, oid ) for _, v in ipairs( tbl ) do if v.oid == oid then return v.value end end return nil end --- Gets the network interface type from a list of IANA approved types -- Taken from IANAifType-MIB -- Available at http://www.iana.org/assignments/ianaiftype-mib -- REVISION "201002110000Z" -- -- @param iana integer interface type returned from snmp result -- @return string description of interface type, or "Unknown" if type not found function get_iana_type( iana ) -- 254 types are currently defined -- if the requested type falls outside that range, reset to "other" if iana > 254 or iana < 1 then iana = 1 end local iana_types = { "other", "regular1822", "hdh1822", "ddnX25", "rfc877x25", "ethernetCsmacd", "iso88023Csmacd", "iso88024TokenBus", "iso88025TokenRing", "iso88026Man", "starLan", "proteon10Mbit", "proteon80Mbit", "hyperchannel", "fddi", "lapb", "sdlc", "ds1", "e1", "basicISDN", "primaryISDN", "propPointToPointSerial", "ppp", "softwareLoopback", "eon", "ethernet3Mbit", "nsip", "slip", "ultra", "ds3", "sip", "frameRelay", "rs232", "para", "arcnet", "arcnetPlus", "atm", "miox25", "sonet", "x25ple", "iso88022llc", "localTalk", "smdsDxi", "frameRelayService", "v35", "hssi", "hippi", "modem", "aal5", "sonetPath", "sonetVT", "smdsIcip", "propVirtual", "propMultiplexor", "ieee80212", "fibreChannel", "hippiInterface", "frameRelayInterconnect", "aflane8023", "aflane8025", "cctEmul", "fastEther", "isdn", "v11", "v36", "g703at64k", "g703at2mb", "qllc", "fastEtherFX", "channel", "ieee80211", "ibm370parChan", "escon", "dlsw", "isdns", "isdnu", "lapd", "ipSwitch", "rsrb", "atmLogical", "ds0", "ds0Bundle", "bsc", "async", "cnr", "iso88025Dtr", "eplrs", "arap", "propCnls", "hostPad", "termPad", "frameRelayMPI", "x213", "adsl", "radsl", "sdsl", "vdsl", "iso88025CRFPInt", "myrinet", "voiceEM", "voiceFXO", "voiceFXS", "voiceEncap", "voiceOverIp", "atmDxi", "atmFuni", "atmIma", "pppMultilinkBundle", "ipOverCdlc", "ipOverClaw", "stackToStack", "virtualIpAddress", "mpc", "ipOverAtm", "iso88025Fiber", "tdlc", "gigabitEthernet", "hdlc", "lapf", "v37", "x25mlp", "x25huntGroup", "trasnpHdlc", "interleave", "fast", "ip", "docsCableMaclayer", "docsCableDownstream", "docsCableUpstream", "a12MppSwitch", "tunnel", "coffee", "ces", "atmSubInterface", "l2vlan", "l3ipvlan", "l3ipxvlan", "digitalPowerlinev", "mediaMailOverIp", "dtm", "dcn", "ipForward", "msdsl", "ieee1394", "if-gsn", "dvbRccMacLayer", "dvbRccDownstream", "dvbRccUpstream", "atmVirtual", "mplsTunnel", "srp", "voiceOverAtm", "voiceOverFrameRelay", "idsl", "compositeLink", "ss7SigLink", "propWirelessP2P", "frForward", "rfc1483", "usb", "ieee8023adLag", "bgppolicyaccounting", "frf16MfrBundle", "h323Gatekeeper", "h323Proxy", "mpls", "mfSigLink", "hdsl2", "shdsl", "ds1FDL", "pos", "dvbAsiIn", "dvbAsiOut", "plc", "nfas", "tr008", "gr303RDT", "gr303IDT", "isup", "propDocsWirelessMaclayer", "propDocsWirelessDownstream", "propDocsWirelessUpstream", "hiperlan2", "propBWAp2Mp", "sonetOverheadChannel", "digitalWrapperOverheadChannel", "aal2", "radioMAC", "atmRadio", "imt", "mvl", "reachDSL", "frDlciEndPt", "atmVciEndPt", "opticalChannel", "opticalTransport", "propAtm", "voiceOverCable", "infiniband", "teLink", "q2931", "virtualTg", "sipTg", "sipSig", "docsCableUpstreamChannel", "econet", "pon155", "pon622", "bridge", "linegroup", "voiceEMFGD", "voiceFGDEANA", "voiceDID", "mpegTransport", "sixToFour", "gtp", "pdnEtherLoop1", "pdnEtherLoop2", "opticalChannelGroup", "homepna", "gfp", "ciscoISLvlan", "actelisMetaLOOP", "fcipLink", "rpr", "qam", "lmp", "cblVectaStar", "docsCableMCmtsDownstream", "adsl2", "macSecControlledIF", "macSecUncontrolledIF", "aviciOpticalEther", "atmbond", "voiceFGDOS", "mocaVersion1", "ieee80216WMAN", "adsl2plus", "dvbRcsMacLayer", "dvbTdm", "dvbRcsTdma", "x86Laps", "wwanPP", "wwanPP2", "voiceEBS", "ifPwType", "ilan", "pip", "aluELP", "gpon", "vdsl2", "capwapDot11Profile", "capwapDot11Bss", "capwapWtpVirtualRadio" } return iana_types[iana] end --- Calculates the speed of the interface based on the snmp value -- -- @param speed value from IF-MIB::ifSpeed -- @return string description of speed function get_if_speed( speed ) local result -- GigE or 10GigE speeds if speed >= 1000000000 then result = string.format( "%d Gbps", speed / 1000000000) -- Common for 10 or 100 Mbit ethernet elseif speed >= 1000000 then result = string.format( "%d Mbps", speed / 1000000) -- Anything slower report in Kbps else result = string.format( "%d Kbps", speed / 1000) end return result end --- Calculates the amount of traffic passed through an interface based on the snmp value -- -- @param amount value from IF-MIB::ifInOctets or IF-MIB::ifOutOctets -- @return string description of traffic amount function get_traffic( amount ) local result -- Gigabytes if amount >= 1000000000 then result = string.format( "%.2f Gb", amount / 1000000000) -- Megabytes elseif amount >= 1000000 then result = string.format( "%.2f Mb", amount / 1000000) -- Anything lower report in kb else result = string.format( "%.2f Kb", amount / 1000) end return result end --- Converts a 6 byte string into the familiar MAC address formatting -- -- @param mac string containing the MAC address -- @return formatted string suitable for printing function get_mac_addr( mac ) local catch = function() return end local try = nmap.new_try(catch) -- Build the MAC prefix lookup table if not nmap.registry.snmp_interfaces then -- Create the table in the registry so we can share between script instances nmap.registry.snmp_interfaces = {} nmap.registry.snmp_interfaces.mac_prefixes = try(datafiles.parse_mac_prefixes()) end if mac:len() ~= 6 then return "Unknown" else local prefix = string.upper(string.format("%02x%02x%02x", mac:byte(1), mac:byte(2), mac:byte(3))) local manuf = nmap.registry.snmp_interfaces.mac_prefixes[prefix] or "Unknown" return string.format("%02x:%02x:%02x:%02x:%02x:%02x (%s)", mac:byte(1), mac:byte(2), mac:byte(3), mac:byte(4), mac:byte(5), mac:byte(6), manuf ) end end --- Processes the list of network interfaces -- -- @param tbl table containing <code>oid</code> and <code>value</code> -- @return table with network interfaces described in key / value pairs function process_interfaces( tbl ) -- Add the %. escape character to prevent matching the index on e.g. "1.3.6.1.2.1.2.2.1.10." local if_index = "1.3.6.1.2.1.2.2.1.1%." local if_descr = "1.3.6.1.2.1.2.2.1.2%." local if_type = "1.3.6.1.2.1.2.2.1.3%." local if_speed = "1.3.6.1.2.1.2.2.1.5%." local if_phys_addr = "1.3.6.1.2.1.2.2.1.6%." local if_status = "1.3.6.1.2.1.2.2.1.8%." local if_in_octets = "1.3.6.1.2.1.2.2.1.10%." local if_out_octets = "1.3.6.1.2.1.2.2.1.16%." local new_tbl = {} -- Some operating systems (such as MS Windows) don't list interfaces with consecutive indexes -- Therefore we keep an index list so we can iterate over the indexes later on new_tbl.index_list = {} for _, v in ipairs( tbl ) do if ( v.oid:match("^" .. if_index) ) then local item = {} item.index = get_value_from_table( tbl, v.oid ) local objid = v.oid:gsub( "^" .. if_index, if_descr) local value = get_value_from_table( tbl, objid ) if value and value:len() > 0 then item.descr = value end objid = v.oid:gsub( "^" .. if_index, if_type ) value = get_value_from_table( tbl, objid ) if value then item.type = get_iana_type(value) end objid = v.oid:gsub( "^" .. if_index, if_speed ) value = get_value_from_table( tbl, objid ) if value then item.speed = get_if_speed( value ) end objid = v.oid:gsub( "^" .. if_index, if_phys_addr ) value = get_value_from_table( tbl, objid ) if value and value:len() > 0 then item.phys_addr = get_mac_addr( value ) end objid = v.oid:gsub( "^" .. if_index, if_status ) value = get_value_from_table( tbl, objid ) if value == 1 then item.status = "up" elseif value == 2 then item.status = "down" end objid = v.oid:gsub( "^" .. if_index, if_in_octets ) value = get_value_from_table( tbl, objid ) if value then item.received = get_traffic( value ) end objid = v.oid:gsub( "^" .. if_index, if_out_octets ) value = get_value_from_table( tbl, objid ) if value then item.sent = get_traffic( value ) end new_tbl[item.index] = item -- Add this interface index to our master list table.insert( new_tbl.index_list, item.index ) end end return new_tbl end --- Processes the list of network interfaces and finds associated IP addresses -- -- @param if_tbl table containing network interfaces -- @param ip_tbl table containing <code>oid</code> and <code>value</code> pairs from IP::MIB -- @return table with network interfaces described in key / value pairs function process_ips( if_tbl, ip_tbl ) local ip_index = "1.3.6.1.2.1.4.20.1.2." local ip_addr = "1.3.6.1.2.1.4.20.1.1." local ip_netmask = "1.3.6.1.2.1.4.20.1.3." local index local item for _, v in ipairs( ip_tbl ) do if ( v.oid:match("^" .. ip_index) ) then index = get_value_from_table( ip_tbl, v.oid ) item = if_tbl[index] local objid = v.oid:gsub( "^" .. ip_index, ip_addr ) local value = get_value_from_table( ip_tbl, objid ) if value then item.ip_addr = value end objid = v.oid:gsub( "^" .. ip_index, ip_netmask ) value = get_value_from_table( ip_tbl, objid ) if value then item.netmask = value end end end return if_tbl end --- Process the table of network interfaces for reporting -- -- @param tbl table containing network interfaces -- @return table suitable for <code>stdnse.format_output</code> function build_results( tbl ) local new_tbl = {} local verbose = nmap.verbosity() -- For each interface index previously discovered, format the relevant information for output for _, index in ipairs( tbl.index_list ) do local interface = tbl[index] local item = {} local status = interface.status local if_type = interface.type -- If no verbose flags are present, only print interfaces that are active, and don't show too many details -- Also, ignore software loopback interfaces if (verbose < 1) and (status == "up") and ( if_type ~= "softwareLoopback") then if interface.descr then item.name = interface.descr else item.name = string.format("Interface %d", item.index) end if interface.ip_addr then table.insert( item, ("IP address: %s"):format( interface.ip_addr ) ) end elseif verbose > 0 then if interface.descr then item.name = interface.descr else item.name = string.format("Interface %d", item.index) end if interface.ip_addr and interface.netmask then table.insert( item, ("IP address: %s/%s"):format( interface.ip_addr, interface.netmask ) ) end if interface.phys_addr then table.insert( item, ("MAC address: %s"):format( interface.phys_addr ) ) end if interface.type and interface.speed then table.insert( item, ("Type: %s (%s)"):format( interface.type, interface.speed ) ) end if interface.status then table.insert( item, ("Status: %s"):format( interface.status ) ) end if interface.sent and interface.received then table.insert( item, ("Traffic stats: %s sent, %s received"):format( interface.sent, interface.received ) ) end end table.insert( new_tbl, item ) end return new_tbl end action = function(host, port) local socket = nmap.new_socket() local catch = function() socket:close() end local try = nmap.new_try(catch) -- IF-MIB - used to look up network interfaces local if_oid = "1.3.6.1.2.1.2.2.1" -- IP-MIB - used to determine IP address information local ip_oid = "1.3.6.1.2.1.4.20" local interfaces = {} local ips = {} socket:set_timeout(5000) try(socket:connect(host.ip, port.number, "udp")) -- retreive network interface information from IF-MIB interfaces = snmp_walk( socket, if_oid ) if ( interfaces == nil ) or ( #interfaces == 0 ) then return end stdnse.print_debug("SNMP walk of IF-MIB returned %d lines", #interfaces) -- build a table of network interfaces from the IF-MIB table interfaces = process_interfaces( interfaces ) -- retreive IP address information from IP-MIB try(socket:connect(host.ip, port.number, "udp")) ips = snmp_walk( socket, ip_oid ) -- associate that IP address information with the correct interface if ( ips ~= nil ) and ( #ips ~= 0 ) then interfaces = process_ips( interfaces, ips ) end nmap.set_port_state(host, port, "open") interfaces = build_results( interfaces ) return stdnse.format_output( true, interfaces ) end
_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://seclists.org/nmap-dev/
Current thread:
- [NSE] Script to enumerate network interfaces Thomas Buchanan (Mar 04)
- Re: [NSE] Script to enumerate network interfaces David Fifield (Mar 05)
- Re: [NSE] Script to enumerate network interfaces Thomas Buchanan (Mar 05)
- Re: [NSE] Script to enumerate network interfaces Patrik Karlsson (Mar 06)
- Re: [NSE] Script to enumerate network interfaces David Fifield (Mar 06)
- Re: [NSE] Script to enumerate network interfaces Thomas Buchanan (Mar 05)
- Re: [NSE] Script to enumerate network interfaces David Fifield (Mar 05)