Nmap Development mailing list archives

[NSE] Simple Banner Grabbingbanner.nse


From: jah <jah () zadkiel plus com>
Date: Sat, 01 Nov 2008 04:41:22 +0000

Hi folks,

I was looking at Nmap's TODO list and saw mention of a banner grabbing
script, so for some light relief I tidied-up a script I had for just
such a purpose.

It doesn't do anything clever, just connects a socket and returns
anything presented to it.  The main work is to tidy-up the banner for
outputting.  In order to keep things pretty it pre-empts nmap's hex
encoding of unprintable characters so that output which is truncated to
a lines width remains so.  This is something I think showHTMLTitle could
benefit from too - long titles are truncated to 65 chars, but this is
ruined if there are unprintable chars in the title.

I've set the character width of a line to 75 chars and the pretty output
will look horrible if your command window is less than this, but this
value "line_len" is easily changed.  I'd love to hear what width
everybody uses - I use 80 chars.

Timeouts may well need some tweaking.

If you envisioned bigger things or have other ideas for such a script
then please holler.

I find port numbers in the 20's often give-up the goods without being
asked.  One of my favourites is this:

PORT   STATE SERVICE
23/tcp open  telnet
|  Banner: \xFF\xFB\x01\xFF\xFE"\x0A\x0D\x09NDS1060HUE-K-TSA Copyright by
|_ ARESCOM 2003\x0A\x0D\x0A\x0D\x0A\x0DLogin Success!\x0A\x0D*NetDSL>*

Ah, if everything was so easy...

Regards,

jah

id          = "Banner"
description = [[
A simple banner grabber.
Connects to an open or open|filtered port and prints out anything issued by a listening service.

The banner will be truncated to fit into a single line, but an extra line may be printed for every
increase in the level of verbosity requested on the command line.
]]

author      = "jah <jah at zadkiel.plus.com>"
license     = "See Nmap License: http://nmap.org/book/man-legal.html";
runlevel    = 1
categories  = {"discovery", "safe"}



local nmap   = require "nmap"
local stdnse = require "stdnse"



-- The width of your screen. You must choose, but choose wisely.
-- For as the true number will bring you pretty output,
-- the false number will take it from you.
local line_len = 75



---
-- Always returns true.
portrule = function( host, port )
  return true
end


---
-- Grabs a banner and outputs it nicely formatted.
action = function( host, port )
  local out = grab_banner(host, port)
  return output( out )
end



---
-- Connects to the target on the given port and returns any data issued by a listening service.
-- @param host  Host Table.
-- @param port  Port Table.
-- @return      String or nil if data was not recieved.
function grab_banner(host, port)

  local socket = nmap.new_socket()
  local catch = function()
    stdnse.print_debug( "%s Connection to %s failed or was aborted! No Output for this Target.", id, host.ip )
    socket:close()
  end

  local result, status, line = {}
  local try = nmap.new_try( catch )

  socket:set_timeout( get_timeout() )
  try( socket:connect( host.ip, port.number ) )

  while true do
    local status, lines = socket:receive_lines(1)
    if not status then
      break
    else
      result[#result+1] = lines
    end
  end

  socket:close()

  if #result == 0 then
    return nil
  end

  return table.concat( result )

end



---
-- Returns a number of milliseconds for use as a socket timeout value.  The number is based on nmap's timing level:
-- * T0 = 15000 ms
-- * T1 = 10000 ms
-- * T2 =  7000 ms
-- * T3 =  5000 ms
-- * T4 =  5000 ms
-- * T5 =  5000 ms
-- @return Number of milliseconds.
function get_timeout()

  local timeout = {[0] = 15000, 10000, 7000}
  return timeout[nmap.timing_level()] or 5000

end



---
-- Formats the banner for printing to the port script result.  Non-printable characters are hex encoded and the banner 
is
-- then truncated to fit into the number of lines of output desired.
-- @param out  String banner issued by a listening service.
-- @return     String formatted for output.
function output( out )

  if type(out) ~= "string" or out == "" then return nil end

  local fline_offset = 5 -- number of chars excluding script id not available to the script on the first line
  local fline_len = line_len -1 -id:len() -fline_offset -- number of chars allowed on first line
  local sline_len = line_len -1 -(fline_offset-2)       -- number of chars allowed on subsequent lines
  local total_out_chars = fline_len + ( extra_output()*sline_len )

  -- replace non-printable ascii chars - no need to do the whole string
  out = replace_nonprint(out, 1+total_out_chars) -- 1 extra char so we can truncate below.

  -- truncate banner to total_out_chars ensuring we remove whole hex encoded chars
  if out:len() > total_out_chars then
    while out:len() > total_out_chars do
      if (out:sub(-4,-1)):match("\\x%x%x") then
        out = out:sub(1,-1-4)
      else
        out = out:sub(1,-1-1)
      end
    end
    out = ("%s..."):format(out:sub(1,total_out_chars-3)) -- -3 for ellipsis
  end

  -- break into lines - this will look shit if line_len is more than the actual space available on a line...
  local ptr = fline_len
  local t = {}
  while true do
    if out:len() >= ptr then
      t[#t+1] = out:sub(1,ptr)
      out = out:sub(ptr+1,-1)
      ptr = sline_len
    else
      t[#t+1] = out
      break
    end
  end

  return table.concat(t,"\n")

end



---
-- Replaces characters with ASCII values outside of the range of standard printable
-- characters (decimal 32 to 126 inclusive) with hex encoded equivalents.
-- The second paramater dictates the number of characters to return, however, if the
-- last character before the number is reached is one that needs replacing then up to
-- three characters more than this number may be returned.
-- If the second parameter is nil, no limit is applied to the number of characters
-- that may be returned.
-- @param s    String on which to perform substitutions.
-- @param len  Number of characters to return.
-- @return     String.
function replace_nonprint( s, len )

  local t = {}
  local count = 0

  for c in s:gmatch(".") do
    if c:byte() < 32 or c:byte() > 126 then
      t[#t+1] = ("\\x%s"):format( ("0%s"):format( ( (stdnse.tohex( c:byte() )):upper() ) ):sub(-2,-1) ) -- capiche
      count = count+4
    else
      t[#t+1] = c
      count = count+1
    end
    if type(len) == "number" and count >= len then break end
  end

  return table.concat(t)

end



---
-- Returns a number for each level of verbosity specified on the command line.
-- Ignores level increases resulting from debugging level.
-- @return Number
function extra_output()
  return (nmap.verbosity()-nmap.debugging()>0 and nmap.verbosity()-nmap.debugging()) or 0
end

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

Current thread: