1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-12 10:49:02 +00:00

o [NSE] Added the a Vuze library, port probe and the script vuze-dht-info. The

script connects to a Vuze node and gets protocol, vendor and network
  information. [Patrik]
This commit is contained in:
patrik
2011-12-03 09:18:58 +00:00
parent e0ca50b976
commit 171c917b37
5 changed files with 642 additions and 2 deletions

View File

@@ -1,6 +1,11 @@
# Nmap Changelog ($Id$); -*-text-*-
o [Nping] The --safe-payloads option is now default. Added --include-payloads for special situations. [Colin Rice]
o [NSE] Added the a Vuze library, port probe and the script vuze-dht-info. The
script connects to a Vuze node and gets protocol, vendor and network
information. [Patrik]
o [Nping] The --safe-payloads option is now default. Added --include-payloads
for special situations. [Colin Rice]
o [NSE] Added the script reverse-index that creates creates a reverse index
showing which hosts run a particular service rather than the services for

View File

@@ -10117,3 +10117,11 @@ Probe UDP sybaseanywhere q|\x1b\0\0\x3d\0\0\0\0\x12CONNECTIONLESS_TDS\0\0\0\x01\
rarity 7
ports 2638
match sybaseanywhere m|^\x1b\0\0.\0\0\0\0\x12CONNECTIONLESS_TDS\0\0\0\x01\x01\0\x04\0\x05\0\x05\0.(.*)\0\x01\x02..\x03\x01\x02\x04\x08\0\0\0\0\0\0\0\0\x07\x02\x04\xb1|s p/Sybase SQL Anywhere/ i/Instance name: $1/
##############################NEXT PROBE##############################
# Vuze DHT PING probe
# See http://wiki.vuze.com/w/Distributed_hash_table#PING
Probe UDP vuze-dht q|\xff\xf0\x97\x0d\x2e\x60\xd1\x6f\0\0\x04\0\0\x55\xab\xec\x32\0\0\0\0\0\x32\x04\x0a\0\xc8\x75\xf8\x16\0\x5c\xb9\x65\0\0\0\0\x4e\xd1\xf5\x28|
rarity 8
ports 17555,49152-49156
match vuze-dht m|^\0\0\x04\x01\0U\xab\xec\xff\xf0\x97\r\.`\xd1o..........|s p/Vuze/

546
nselib/vuzedht.lua Normal file
View File

@@ -0,0 +1,546 @@
---
-- A Vuze DHT protocol implementation based on the following documentation:
-- o http://wiki.vuze.com/w/Distributed_hash_table
--
-- It currently supports the PING and FIND_NODE requests and parses the
-- responses. The following main classes are used by the library:
--
-- o Request - the request class containing all of the request classes. It
-- currently contains the Header, PING and FIND_NODE classes.
--
-- o Response - the response class containing all of the response classes. It
-- currently contains the Header, PING, FIND_NODE and ERROR
-- classs.
--
-- o Session - a class containing "session state" such as the transaction- and
-- instance ID's.
--
-- o Helper - The helper class that serves as the main interface between
-- scripts and the library.
--
-- @author "Patrik Karlsson <patrik@cqure.net>"
--
module(... or "vuzedht",package.seeall)
require 'ipOps'
stdnse.silent_require('openssl')
Request = {
Actions = {
ACTION_PING = 1024,
FIND_NODE = 1028,
},
-- The request Header class shared by all Requests classes
Header = {
-- Creates a new Header instance
-- @param action number containing the request action
-- @param session instance of Session
-- @return o new instance of Header
new = function(self, action, session)
local o = {
conn_id = string.char(255) .. openssl.rand_pseudo_bytes(7),
action = action,
trans_id = session:getTransactionId(),
proto_version = 0x32,
vendor_id = 0,
network_id = 0,
local_proto_version = 0x32,
address = session:getAddress(),
port = session:getPort(),
instance_id = session:getInstanceId(),
time = os.time(),
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts the header to a string
__tostring = function(self)
local lhost = ipOps.todword(self.address)
return bin.pack( ">AIICCICCISIL", self.conn_id, self.action, self.trans_id,
self.proto_version, self.vendor_id, self.network_id, self.local_proto_version,
4, lhost, self.port, self.instance_id, self.time )
end,
},
-- The PING Request class
Ping = {
-- Creates a new Ping instance
-- @param session instance of Session
-- @return o new instance of Ping
new = function(self, session)
local o = {
header = Request.Header:new(Request.Actions.ACTION_PING, session)
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts a Ping Request to a string
__tostring = function(self)
return tostring(self.header)
end,
},
-- The FIND_NODES Request class
FindNode = {
-- Creates a new FindNode instance
-- @param session instance of Session
-- @return o new instance of FindNode
new = function(self, session)
local o = {
header = Request.Header:new(Request.Actions.FIND_NODE, session),
id_length = 20,
node_id = '\xA7' .. openssl.rand_pseudo_bytes(19),
status = 0xFFFFFFFF,
dht_size = 0,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Converts a FindNode Request to a string
__tostring = function(self)
local data = tostring(self.header)
data = data .. bin.pack(">CAII", self.id_length, self.node_id, self.status, self.dht_size)
return data
end,
}
}
Response = {
-- A table of currently supported Actions (Responses)
-- It's used in the fromString method to determine which class to create.
Actions = {
ACTION_PING = 1025,
FIND_NODE = 1029,
ERROR = 1032,
},
-- Creates an address record based on received data
-- @param data containing an address record [C][I|H][S] where
-- [C] is the length of the address (4 or 16)
-- [I|H] is the address as a dword or hex string
-- [S] is the port number as a short
-- @return o Address instance on success, nil on failure
Address = {
new = function(self, data)
local o = { data = data }
setmetatable(o, self)
self.__index = self
if ( o:parse() ) then
return o
end
end,
-- Parses the received data
-- @return true on success, false on failure
parse = function(self)
local pos, addr_len = bin.unpack("C", self.data)
if ( addr_len == 4 ) then
self.length = 4 + 2 + 1
pos, self.ip = bin.unpack("<I", self.data, pos)
self.ip = ipOps.fromdword(self.ip)
elseif( addr_len == 16 ) then
self.length = 16 + 2 + 1
pos, self.ip = bin.unpack("H16", self.data, pos)
else
stdnse.print_debug("Unknown address type (length: %d)", addr_len)
return false, "Unknown address type"
end
pos, self.port = bin.unpack(">S", self.data, pos)
return true
end
},
-- The reponse header, present in all packets
Header = {
Vendors = {
[0] = "Azureus",
[1] = "ShareNet",
[255] = "Unknown", -- to be honest, we report all except 0 and 1 as unknown
},
Networks = {
[0] = "Stable",
[1] = "CVS"
},
-- Creates a new Header instance
-- @param data string containing the received data
-- @return o instance of Header
new = function(self, data)
local o = { data = data }
setmetatable(o, self)
self.__index = self
o:parse()
return o
end,
-- parses the header
parse = function(self)
local pos
pos, self.action, self.trans_id, self.conn_id,
self.proto_version, self.vendor_id, self.network_id,
self.instance_id = bin.unpack(">IIH8CCII", self.data)
end,
-- Converts the header to a suitable string representation
__tostring = function(self)
local result = {}
table.insert(result, ("Transaction id: %d"):format(self.trans_id))
table.insert(result, ("Connection id: 0x%s"):format(self.conn_id))
table.insert(result, ("Protocol version: %d"):format(self.proto_version))
table.insert(result, ("Vendor id: %s (%d)"):format(
Response.Header.Vendors[self.vendor_id] or "Unknown", self.vendor_id))
table.insert(result, ("Network id: %s (%d)"):format(
Response.Header.Networks[self.network_id] or "Unknown", self.network_id))
table.insert(result, ("Instance id: %d"):format(self.instance_id))
return stdnse.format_output(true, result)
end,
},
-- The PING response
PING = {
-- Creates a new instance of PING
-- @param data string containing the received data
-- @return o new PING instance
new = function(self, data)
local o = {
header = Response.Header:new(data)
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Creates a new PING instance based on received data
-- @param data string containing received data
-- @return status true on success, false on failure
-- @return new instance of PING on success, error message on failure
fromString = function(data)
local ping = Response.PING:new(data)
if ( ping ) then
return true, ping
end
return false, "Failed to parse PING response"
end,
-- Converts the PING response to a response suitable for script output
-- @return result formatted script output
__tostring = function(self)
return tostring(self.header)
end,
},
-- A class to process the response from a FIND_NODE query
FIND_NODE = {
-- Creates a new FIND_NODE instance
-- @param data string containing the received data
-- @return o new instance of FIND_NODE
new = function(self, data)
local o = {
header = Response.Header:new(data),
data = data:sub(27)
}
setmetatable(o, self)
self.__index = self
o:parse()
return o
end,
-- Parses the FIND_NODE response
parse = function(self)
local pos
pos, self.spoof_id, self.node_type, self.dht_size,
self.network_coords = bin.unpack(">IIIH20", self.data)
local contact_count
pos, contact_count = bin.unpack("C", self.data, pos)
self.contacts = {}
for i=1, contact_count do
local contact, addr_len, address = {}
pos, contact.type, contact.proto_version, addr_len = bin.unpack("CCC", self.data, pos)
if ( addr_len == 4 ) then
pos, address = bin.unpack("<I", self.data, pos)
contact.address = ipOps.fromdword(address)
elseif ( addr_len == 16 ) then
pos, contact.address = bin.unpack("H16", self.data, pos)
end
pos, contact.port = bin.unpack(">S", self.data, pos)
table.insert(self.contacts, contact)
end
end,
-- Creates a new instance of FIND_NODE based on received data
-- @param data string containing received data
-- @return status true on success, false on failure
-- @return new instance of FIND_NODE on success, error message on failure
fromString = function(data)
local find = Response.FIND_NODE:new(data)
if ( find.header.proto_version < 13 ) then
stdnse.print_debug("ERROR: Unsupported version %d", self.header.proto_version)
return false
end
return true, find
end,
-- Convert the FIND_NODE response to formatted string data, suitable
-- for script output.
-- @return string with formatted FIND_NODE data
__tostring = function(self)
if ( not(self.contacts) ) then
return ""
end
local result = {}
for _, contact in ipairs(self.contacts) do
table.insert(result, ("%s:%d"):format(contact.address, contact.port))
end
return stdnse.format_output(true, result)
end
},
-- The ERROR action
ERROR = {
-- Creates a new ERROR instance based on received socket data
-- @return o new ERROR instance on success, nil on failure
new = function(self, data)
local o = {
header = Response.Header:new(data),
data = data:sub(27)
}
setmetatable(o, self)
self.__index = self
if ( o:parse() ) then
return o
end
end,
-- parses the received data and attempts to create an ERROR response
-- @return true on success, false on failure
parse = function(self)
local pos, err_type = bin.unpack(">I", self.data)
if ( 1 == err_type ) then
self.addr = Response.Address:new(self.data:sub(5))
return true
end
return false
end,
-- creates a new ERROR instance based on the received data
-- @return true on success, false on failure
fromString = function(data)
local err = Response.ERROR:new(data)
if ( err ) then
return true, err
end
return false
end,
-- Converts the ERROR action to a formatted response
-- @return string containing the formatted response
__tostring = function(self)
return ("Wrong address, expected: %s"):format(self.addr.ip)
end,
},
-- creates a suitable Response class based on the Action received
-- @return true on success, false on failure
-- @return response instance of suitable Response class on success,
-- err string error message if status is false
fromString = function(data)
local pos, action = bin.unpack(">I", data)
if ( action == Response.Actions.ACTION_PING ) then
return Response.PING.fromString(data)
elseif ( action == Response.Actions.FIND_NODE ) then
return Response.FIND_NODE.fromString(data)
elseif ( action == Response.Actions.ERROR ) then
return Response.ERROR.fromString(data)
end
stdnse.print_debug("ERROR: Unknown response received from server")
return false, "Failed to parse response"
end,
}
-- The Session
Session = {
-- Creates a new Session instance to keep track on some of the protocol
-- stuff, such as transaction- and instance- identities.
-- @param address the local address to pass in the requests to the server
-- this could be either the local address or the IP of the router
-- depending on if NAT is used or not.
-- @param port the local port to pass in the requests to the server
-- @return o new instance of Session
new = function(self, address, port)
local o = {
trans_id = math.random(12345678),
instance_id = math.random(12345678),
address = address,
port = port,
}
setmetatable(o, self)
self.__index = self
return o
end,
-- Gets the next transaction ID
-- @return trans_id number
getTransactionId = function(self)
self.trans_id = self.trans_id + 1
return self.trans_id
end,
-- Gets the next instance ID
-- @return instance_id number
getInstanceId = function(self)
self.instance_id = self.instance_id + 1
return self.instance_id
end,
-- Gets the stored local address used to create the session
-- @return string containing the IP passed to the session
getAddress = function(self)
return self.address
end,
-- Get the stored local port used to create the session
-- @return number containing the local port
getPort = function(self)
return self.port
end
}
-- The Helper class, used as main interface between the scripts and the library
Helper = {
-- Creates a new instance of the Helper class
-- @param host table as passed to the action method
-- @param port table as passed to the action method
-- @param lhost [optional] used if an alternate local address is to be
-- passed in the requests to the remote node (ie. NAT is in play).
-- @param lport [optional] used if an alternate port is to be passed in
-- the requests to the remote node.
-- @return o new instance of Helper
new = function(self, host, port, lhost, lport)
local o = {
host = host,
port = port,
lhost = lhost,
lport = lport
}
setmetatable(o, self)
self.__index = self
math.randomseed(os.time())
return o
end,
-- Connects to the remote Vuze Node
-- @return true on success, false on failure
-- @return err string error message if status is false
connect = function(self)
local lhost = self.lhost or stdnse.get_script_args('vuzedht.lhost')
local lport = self.lport or stdnse.get_script_args('vuzedht.lport')
self.socket = nmap.new_socket()
if ( lport ) then
self.socket:bind(nil, lport)
end
local status, err = self.socket:connect(self.host, self.port)
if ( not(status) ) then
return false, "Failed to connect to server"
end
if ( not(lhost) or not(lport) ) then
local status, lh, lp, _, _ = self.socket:get_info()
if ( not(status) ) then
return false, "Failed to get socket information"
end
lhost = lhost or lh
lport = lport or lp
end
self.session = Session:new(lhost, lport)
return true
end,
-- Sends a Vuze PING request to the server and parses the response
-- @return status true on succes, false on failure
-- @return response PING response instance on success,
-- err string containing the error message on failure
ping = function(self)
local ping = Request.Ping:new(self.session)
local status, err = self.socket:send(tostring(ping))
if ( not(status) ) then
return false, "Failed to send PING request to server"
end
local data
status, data = self.socket:receive()
if ( not(status) ) then
return false, "Failed to receive PING response from server"
end
local response
status, response = Response.fromString(data)
if ( not(status) ) then
return false, "Failed to parse PING response from server"
end
return true, response
end,
-- Requests a list of known nodes by sending the FIND_NODES request
-- to the remote node and parses the response.
-- @return status true on success, false on failure
-- @return response FIND_NODE response instance on success
-- err string containing the error message on failure
findNodes = function(self)
local find = Request.FindNode:new(self.session)
local status, err = self.socket:send(tostring(find))
if ( not(status) ) then
return false, "Failed to send FIND_NODE request to server"
end
local data
status, data = self.socket:receive()
local response
status, response = Response.fromString(data)
if ( not(status) ) then
return false, "Failed to parse FIND_NODE response from server"
end
return true, response
end,
-- Closes the socket connect to the remote node
close = function(self)
self.socket:close()
end,
}

View File

@@ -276,6 +276,7 @@ Entry { filename = "unusual-port.nse", categories = { "safe", } }
Entry { filename = "upnp-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "vnc-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "vnc-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "vuze-dht-info.nse", categories = { "discovery", "safe", } }
Entry { filename = "wdb-version.nse", categories = { "default", "discovery", "version", "vuln", } }
Entry { filename = "whois.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "wsdd-discover.nse", categories = { "default", "discovery", "safe", } }

80
scripts/vuze-dht-info.nse Normal file
View File

@@ -0,0 +1,80 @@
description = [[
Retrieves some basic information, including protocol version from a Vuze node.
]]
---
-- @usage
-- nmap -sU -p <port> <ip> --script vuze-dht-info -sV
--
-- @output
-- PORT STATE SERVICE VERSION
-- 17555/udp open vuze-dht Vuze
-- | vuze-dht-info:
-- | Transaction id: 9438865
-- | Connection id: 0xFF79A77B4592BDB0
-- | Protocol version: 50
-- | Vendor id: Azureus (0)
-- | Network id: Stable (0)
-- |_ Instance id: 2260473691
--
-- As Vuze doesn't have a default port for it's DHT service, this script has
-- some difficulties in determining when to run. Most scripts are triggered by
-- either a default port or a fingerprinted service. To get around this, there
-- are two options:
-- 1. Always run a version scan, to identify the vuze-dht service in order to
-- trigger the script.
-- 2. Force the script to run against each port by setting the argument
-- vuze-dht-info.allports
--
-- @args vuze-dht-info.allports if set runs this script against every open port
categories = {"discovery", "safe"}
require 'shortport'
require 'ipOps'
require 'vuzedht'
portrule = function(host, port)
local allports = stdnse.get_script_args('vuze-dht-info.allports')
if ( tonumber(allports) == 1 or allports == 'true' ) then
return true
else
local f = shortport.port_or_service({17555, 49160, 49161, 49162}, "vuze-dht", "udp", {"open", "open|filtered"})
return f(host, port)
end
end
local function getDHTInfo(host, port, lhost)
local helper = vuzedht.Helper:new(host, port, lhost)
local status = helper:connect()
if ( not(status) ) then
return false, "\n ERROR: Failed to connect to server"
end
local response
status, response = helper:ping()
if ( not(status) ) then
return false, "\n ERROR: Failed to ping vuze node"
end
helper:close()
return true, response
end
action = function(host, port)
local status, response = getDHTInfo(host, port)
-- check whether we have an error due to an incorrect address
-- ie. we're on a NAT:ed network and we're announcing our private ip
if ( status and response.header.action == vuzedht.Response.Actions.ERROR ) then
status, response = getDHTInfo(host, port, response.addr.ip)
end
if ( status ) then
nmap.set_port_state(host, port, "open")
return tostring(response)
end
end