diff --git a/CHANGELOG b/CHANGELOG index cabde3278..4f39303f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o Updated rpcinfo NSE script to use the new pack/unpack (binlib) + functions, use the new tab library, include better documentation, and + fix some bugs. [Sven Klemm] + o Fix a bug in the NSE http library which would cause some scripts to give the error: SCRIPT ENGINE: C:\Program Files\Nmap\nselib/http.lua:77: attempt to call field 'parse' (a nil @@ -21,7 +25,7 @@ o Added new addrow() function to NSE tab library. It allows o The NSE http library now supports chunked encoding. [Sven Klemm] -o Fix a number of NSE scripts which used print_debug() +o Fixed a number of NSE scripts which used print_debug() incorrectly. See http://seclists.org/nmap-dev/2008/q3/0470.html. [Sven Klemm]. diff --git a/scripts/rpcinfo.nse b/scripts/rpcinfo.nse index a0e8676bc..88388f182 100644 --- a/scripts/rpcinfo.nse +++ b/scripts/rpcinfo.nse @@ -1,94 +1,114 @@ -id = "rpcinfo" +--- +-- Connects to portmapper and fetches a list of all registered programs +-- +--@output +-- 111/tcp open rpcbind +-- | rpcinfo: +-- | 100000 2 111/udp rpcbind +-- | 100005 1,2,3 705/udp mountd +-- | 100003 2,3,4 2049/udp nfs +-- | 100024 1 32769/udp status +-- | 100021 1,3,4 32769/udp nlockmgr +-- | 100000 2 111/tcp rpcbind +-- | 100005 1,2,3 706/tcp mountd +-- | 100003 2,3,4 2049/tcp nfs +-- | 100024 1 50468/tcp status +-- |_ 100021 1,3,4 50468/tcp nlockmgr -description = "connects to portmapper and prints a list of all registered programs" + +require "shortport" +require "datafiles" +require "bin" +require "bit" +require "tab" + +id = "rpcinfo" +description = "connects to portmapper and fetches a list of all registered programs" author = "Sven Klemm " license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default","safe","discovery"} -require "comm" -require "shortport" -require "packet" -require "datafiles" - portrule = shortport.port_or_service(111, "rpcbind") +--- format a table of version for output +--@param version_table table containing the versions +--@return string with the formatted versions +local format_version = function( version_table ) + table.sort( version_table ) + return table.concat( version_table, ',' ) +end + action = function(host, port) - local try - local transaction_id = "nmap" - local result = " \n" - local rpc_numbers + local socket = nmap.new_socket() + socket:set_timeout(1000) + local catch = function() socket:close() end + local try = nmap.new_try(catch) + local rpc_numbers = try(datafiles.parse_rpc()) - try = nmap.new_try() - rpc_numbers = try(datafiles.parse_rpc()) + try(socket:connect(host.ip, port.number)) - local request = string.char(0x80,0,0,40) -- fragment header - request = request .. transaction_id -- transaction id - request = request .. "\0\0\0\0\0\0\0\2" -- message type: call (0) and rpc version 2 - request = request .. string.char(0,1,134,160) -- programm portmap (100000) - request = request .. "\0\0\0\2\0\0\0\4" -- programm version (2) procedure dump(4) - request = request .. "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"-- Credentials and verifier + -- build rpc dump call packet + local transaction_id = math.random(0x7FFFFFFF) + local request = bin.pack('>IIIIIIILL',0x80000028,transaction_id,0,2,100000,2,4,0,0) + try(socket:send(request)) - local answer = try(comm.exchange(host, port, request, {timeout=1000})) - local answer_part + local answer = try(socket:receive_bytes(1)) - local fragment_length = answer:byte(4) + answer:byte(3) * 256 + answer:byte(2) * 65536 - if answer:sub(5,8) == transaction_id and answer:byte(12) == 1 and answer:byte(16) == 0 and answer:byte(28) == 0 then - -- transaction_id matches, message type reply, reply state accepted and accept state executed successfully - answer_part = answer - answer = answer_part:sub( 28 + 1, fragment_length + 4 ) - answer_part = answer_part:sub( fragment_length + 4 + 1 ) + local _,offset,header,length,tx_id,msg_type,reply_state,accept_state,value,payload,last_fragment + last_fragment = false; offset = 1; payload = '' - while answer_part:len() > 0 do -- defragment packet - fragment_length = answer_part:byte(4) + answer_part:byte(3) * 256 + answer_part:byte(2) * 65536 - answer = answer .. answer_part:sub( 5, fragment_length + 4 ) - answer_part = answer_part:sub( fragment_length + 4 + 1 ) + -- extract payload from answer and try to receive more packets if header with + -- last_fragment set has not been received + while not last_fragment do + if offset > #answer then + answer = answer .. try(socket:receive_bytes(1)) end + offset,header = bin.unpack('>I',answer,offset) + last_fragment = bit.band( header, 0x80000000 ) ~= 0 + length = bit.band( header, 0x7FFFFFFF ) + payload = payload .. answer:sub( offset, offset + length - 1 ) + offset = offset + length + end + socket:close() + offset,tx_id,msg_type,reply_state,_,_,accept_state = bin.unpack( '>IIIIII', payload ) + + -- transaction_id matches, message type reply, reply state accepted and accept state executed successfully + if tx_id == transaction_id and msg_type == 1 and reply_state == 0 and accept_state == 0 then local dir = { udp = {}, tcp = {}} + local protocols = {[6]='tcp',[17]='udp'} local rpc_prog, rpc_vers, rpc_proto, rpc_port - while answer:byte(4) == 1 and answer:len() >= 20 do - rpc_prog = packet.u32( answer, 4 ) - rpc_vers = packet.u32( answer, 8 ) - rpc_proto = packet.u32( answer, 12 ) - rpc_port = packet.u32( answer, 16 ) - answer = answer:sub(21) - if rpc_proto == 6 then - rpc_proto = "tcp" - elseif rpc_proto == 17 then - rpc_proto = "udp" - end - if not dir[rpc_proto][rpc_port] then dir[rpc_proto][rpc_port] = {} end - if not dir[rpc_proto][rpc_port][rpc_prog] then dir[rpc_proto][rpc_port][rpc_prog] = {} end + offset, value = bin.unpack('>I',payload,offset) + while value == 1 and #payload - offset >= 19 do + offset,rpc_prog,rpc_vers,rpc_proto,rpc_port,value = bin.unpack('>IIIII',payload,offset) + rpc_proto = protocols[rpc_proto] or tostring( rpc_proto ) + -- collect data in a table + dir[rpc_proto] = dir[rpc_proto] or {} + dir[rpc_proto][rpc_port] = dir[rpc_proto][rpc_port] or {} + dir[rpc_proto][rpc_port][rpc_prog] = dir[rpc_proto][rpc_port][rpc_prog] or {} table.insert( dir[rpc_proto][rpc_port][rpc_prog], rpc_vers ) end - local format_version = function( version_table ) - if #version_table == 1 then return version_table[1] end - table.sort( version_table ) - for i=2,#version_table do - if version_table[i-1] ~= version_table[i] - 1 then - return table.concat( version_table, ',' ) - end - end - return string.format('%d-%d',version_table[1],version_table[#version_table]) - end - + -- format output + local output = tab.new(4) for rpc_proto, o in pairs(dir) do + -- get list of all used ports local ports = {} for rpc_port, i in pairs(o) do table.insert(ports, rpc_port) end table.sort(ports) + + -- iterate over ports to produce output for i, rpc_port in ipairs(ports) do i = o[rpc_port] for rpc_prog, versions in pairs(o[rpc_port]) do - versions = format_version( versions ) local name = rpc_numbers[rpc_prog] or '' - result = result .. string.format('%d %-5s %5d/%s %s\n',rpc_prog,versions,rpc_port,rpc_proto,name) + tab.addrow(output,rpc_prog,format_version(versions),('%5d/%s'):format(rpc_port,rpc_proto),name) end end end + return ' \n' .. tab.dump( output ) end - return result end