Nmap Development mailing list archives

Re: autonomous system numbers NSE script


From: jah <jah () zadkiel plus com>
Date: Thu, 19 Jun 2008 01:56:26 +0100

On 18/06/2008 01:40, Michael Pattrick wrote:
Hey everyone,

I wrote this script to find autonomous system numbers using the method
described here [1].

But there are two problems:
 - A query will return the same ASN if its in the same BGP netblock.
My script also discovers the BGP netblock, is there any way to cache
results in this case - ie check if the IP fits into a netblock that
we've already scanned?
 - The website states that the the best way is the DNS method, and
hosts making too many whois queries will be blocked, is there a way to
forge DNS query packets in NSE or would there need to be some kind of
DNS API? I tried making the raw packet in a lua script and ssending it
out but wireshark claimed that the packet was malformed :(
<snip>
[1] http://www.team-cymru.org/Services/ip-to-asn.html
  
Hi Michael,

As Kris said, I've had the same requirement for whois.nse.  In that
script, it deals with quite a lot of information and so the goal was to
have a single host result showing the whois record and other hosts in
the same range to return "See the results for x.x.x.x" to avoid
repeating the same results over and over.  On the basis that your script
doesn't show a lot of data, you might get a way with repeating the same
ASN info for each target in that block and this is quite easy to do
using mutex and the registry.

I've attached a very rough script that will
a) do the dns method.  This required some reading of RFC 1035 (and
specifically Section 4.1.2. - QNAME) to get a non-malformed query (a
good exercise though, I enjoyed it) and
b) cache results to prevent multiple queries for the same netblock using
the registry and a mutex
and which you might find useful.

There are two caveats:
1) I haven't worked out how to get information about which dns server to
use and so   --script-args dns=some.dns.server   is required to get this
script to run.
2) The method by which the script reads the dns response is very shaky
indeed.  It finds end of the original query in the answer and fetches
everything after 13 bytes further on - it works, but I can't imagine it
will always work...

Does anyone have any idea how to get ones dns server for a use in a script?

Finally, it occurs to me that ASN numbers might be integrated into
whois.nse since it ought to be possible to get route information from
the Regional Internet Registries.  Ripe certainly serves-up route info
as part of an IP address lookup, but at the moment I don't know if any
of the others do.  It might be worth looking into this possibility in
either case.

Regards,

jah
id = "ASN"
description = "nmap <target> --script asn --script-args dns=<recursion_enabled_dns_server>"

require "comm"
require "ipOps"

hostrule = function( host )
  return true
end

if not nmap.registry.asn then
  nmap.registry.asn = {}
  nmap.registry.asn.cache = {}
end

local mutex = nmap.mutex( id )

action = function( host )

  -- get args or die
  local dns_server
  if nmap.registry.args.dns then
    dns_server = nmap.registry.args.dns
  else
    return
  end

  -- wait
  mutex "lock"

  -- check for cached data
  for _, cache in ipairs( nmap.registry.asn.cache ) do
    if ip_in_net( host.ip, cache.bgp) then
      mutex "done"
      return " \nBGP Prefix: " .. cache.bgp ..  "\nAS number: " .. cache.asn ..  "\nCountry Code: " .. cache.co_id
    end
  end

  -- format data
  local t = {}
  t[4], t[3], t[2], t[1] = host.ip:match( "([^\.]*)\.([^\.]*)\.([^\.]*)\.([^\.]*)" )
  local tsoh = labels( t )
  local z = { "origin", "asn", "cymru", "com" }
  local zone = labels( z )

  local t_id = string.char( tonumber( t[2] ), tonumber( t[3] ) ) -- not at all random...
  local dns_std = string.char( 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 )
  local null_char = string.char( 0x00 )
  local qtype = string.char( 0x00, 0x10 )
  local qclass = string.char( 0x00, 0x01 )
  local query = tsoh .. zone .. null_char .. qtype .. qclass

  local data = t_id .. dns_std .. query

  -- send data
  local options = {}
  options.proto = "udp"
  options.lines = 1
  options.timeout = 1000
  local status, result = comm.exchange( dns_server, 53, data, options )
  if not status then
    mutex "done"
    return
  end

  -- read result - this method is tenuous!!
  local _, offset = string.find( result, query )
  local line = string.sub( result, offset + 13 )
  fields = {line:match( ("([^|]*)|"):rep(3) )}

  -- cache result
  local blob = {}
  blob.bgp = fields[2]:gsub( "^%s*(.-)%s*$", "%1" )
  blob.asn = fields[1]:gsub( "^%s*[^0](.-)%s*$", "%1" )
  blob.co_id = fields[3]:gsub( "^%s*(.-)%s*$", "%1" )
  table.insert( nmap.registry.asn.cache, blob )
  mutex "done"

  -- return result
  return " \nBGP Prefix: " .. blob.bgp .. "\nAS number: " .. blob.asn .. "\nCountry Code: " .. blob.co_id

end


-- labels
-- given a table of strings, return a string made up of concateneted labels
-- where each label consists of a length value (cast as char) followed by that number of characters.
function labels( t )
  local ret = ""
  for _, v in ipairs(t) do
      ret = ret .. string.char( string.len(v) ) .. v
  end
  return ret
end

-- ip_in_net
-- returns true if the supplied ip address falls inside the supplied range
function ip_in_net(ip, net)
  local i, j, net_lo, net_hi, dw_ip
  local m_dotted = "(%d+%.%d+%.%d+%.%d+)[%s]*[-][%s]*(%d+%.%d+%.%d+%.%d+)"
  local m_cidr = "(%d+)[.]*(%d*)[.]*(%d*)[.]*(%d*)[/]+(%d+)"

  if net:match(m_dotted) then
    net_lo, net_hi = net:match(m_dotted)
    net_lo = ipOps.todword(net_lo)
    net_hi = ipOps.todword(net_hi)
  elseif net:match(m_cidr) then
    net_lo, net_hi = two_dwords(net, m_cidr)
  end

  dw_ip = ipOps.todword(ip)
  if net_lo <= dw_ip  and dw_ip <= net_hi then return true end
  return false
end

-- two_dwords
-- returns the two ip addresses at either end of a cidr range, as dwords
function two_dwords(str, patt)
  local a, b, c, d, e, lo_net, host
  a, b, c, d, e = str:match(patt)
  local ipt = {b, c, d}
  local strip = ""
  for _, cap in ipairs(ipt) do
    if cap == "" then cap = "0" end
    strip = strip .. "." .. cap
  end
  lo_net = a .. strip
  if e ~= "" then e = tonumber(e)
    if e and e <=32 then
      host = 32 - e end
  end
  return ipOps.todword(lo_net), ipOps.todword(lo_net) + 2^host - 1
end


_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: