Nmap Development mailing list archives

Re: [NSE] Simple Banner Grabbing, Banner Grabber and Grabber of Banners - banner.nse


From: jah <jah () zadkiel plus com>
Date: Sat, 01 Nov 2008 14:08:37 +0000

On 01/11/2008 05:04, Kris Katterjohn wrote:
On 10/31/2008 11:41 PM, jah wrote:
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.


I guess my idea wasn't so bad after all[1] :)
Hi Kris,

Certainly not a bad idea.  Even with --version-intensity 0 it's not
possible to do pure banner grabbing without sending any probes and so
using a script such as this is a good way to get maximum information
without going as far as version scanning.  And it's often helpful to
know when a service blurts something out, without having to trawl
through a --version-trace (which can be large with many targets or high
intensity).
The odd thing is that I did a search of nmap-dev after I saw banner
grabbing mentioned in the TODO list to make sure the script wasn't
already floating around.  I saw a thread from 2006 where it was
suggested to use Amap and some stuff from 2002 which I didn't even look
at.  Where was your cleverly titled post [1] in these results?  I guess
my search term should have been "banner grabber" rather than "banner
grabbing"...
It doesn't do anything clever, just connects a socket and returns
anything presented to it

- From a quick glance, I see that you use a grab_banner() function of
your own
for this.  I wrote banner-grabbing functionality into the Comm
library; does
it not suffice for you here?  If not, maybe it can be updated a little to
support your use somehow.
Good point.  The Comm library already does what the script needs so
attached is an updated version of the script.

Cheers for the input,

jah
[1] http://seclists.org/nmap-dev/2007/q2/0372.html
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 comm   = require "comm"
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 opts = {}
  opts.timeout = get_timeout()
  opts.protocol = port.protocol

  local status, response = comm.get_banner(host.ip, port.number, opts)

  if not status then
    stdnse.print_debug(((response == "EOF" or response == "TIMEOUT") and 3) or nil,
      "%s No Banner from %s on %s %s: %s", id, host.ip, port.number, port.protocol, response or "No Error." )
    return nil
  end

  return response

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: