mirror of
https://github.com/nmap/nmap.git
synced 2025-12-10 09:49:05 +00:00
The script provides detection when version probes fail, but will default to the value provided the version probes if that value is more precise. The script also detects the server platform and database instance name. [Tom]
346 lines
13 KiB
Lua
346 lines
13 KiB
Lua
description = [[
|
|
Attempts to extract information from IBM DB2 Server instances. The script sends a
|
|
DB2 EXCSAT (exchange server attributes) command packet and parses the response.
|
|
]]
|
|
|
|
-- rev 1.2 (2010-11-11)
|
|
|
|
author = "Tom Sellers <nmap@fadedcode.net>"
|
|
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
|
|
categories = {"safe", "discovery", "version"}
|
|
|
|
require "stdnse"
|
|
require "shortport"
|
|
|
|
portrule = shortport.port_or_service({50000,60000},"ibm-db2", "tcp", {"open", "open|filtered"})
|
|
|
|
-- This function processes a section of the EXCSAT response packet
|
|
|
|
--@param response This is the data returned from the server as a result of the client query.
|
|
--@param position This is the position within the response that this function will start processing from.
|
|
--@param ebcdic2ascii This is a table containing a conversion chart for returning the ASCII value of EBCDIC encoded HEX value.
|
|
--@return section_length This is the length of the currect section. Will be used to move position for processing next section.
|
|
--@return data_string This string contains the data pulled from this section of the server response.
|
|
local function process_block(response, position, ebcdic2ascii)
|
|
|
|
-- This fuction assumes that the current position is the start of a section within
|
|
-- the DB2 EXCSAT response packet
|
|
|
|
-- Get the length of this section of the response packet
|
|
local section_length = string.format("%d",string.byte(response,position +1)) .. string.format("%d",string.byte(response,position + 2))
|
|
position = position + 2
|
|
|
|
-- locate the data string and convert it from EBCDIC to ASCII
|
|
local i = 0
|
|
local data_string = ""
|
|
for i = (position + 3),(position + section_length -2 ),1 do
|
|
-- stdnse.print_debug("%s","INFO: Current postion (i) = " .. i)
|
|
-- stdnse.print_debug("%s","INFO: Hex value = " .. string.format("%x",string.byte(response,i)))
|
|
-- stdnse.print_debug("%s","INFO: Current data_string = " .. data_string)
|
|
if string.format("%x",string.byte(response,i)) == "0" then
|
|
break
|
|
end
|
|
data_string = data_string .. ebcdic2ascii[string.format("%x",string.byte(response,i))]
|
|
end
|
|
|
|
return section_length, data_string
|
|
|
|
end -- fuction process_block
|
|
|
|
|
|
|
|
action = function(host, port)
|
|
|
|
local ebcdic2ascii = {
|
|
-- The following reference was used for this table: http://www.simotime.com/asc2ebc1.htm
|
|
["00"] = string.format("%c", 00),
|
|
["40"] = " ",
|
|
["81"] = "a",
|
|
["82"] = "b",
|
|
["83"] = "c",
|
|
["84"] = "d",
|
|
["85"] = "e",
|
|
["86"] = "f",
|
|
["87"] = "g",
|
|
["88"] = "h",
|
|
["89"] = "i",
|
|
["91"] = "j",
|
|
["92"] = "k",
|
|
["93"] = "l",
|
|
["94"] = "m",
|
|
["95"] = "n",
|
|
["96"] = "o",
|
|
["97"] = "p",
|
|
["98"] = "q",
|
|
["99"] = "r",
|
|
["a2"] = "s",
|
|
["a3"] = "t",
|
|
["a4"] = "u",
|
|
["a5"] = "v",
|
|
["a6"] = "w",
|
|
["a7"] = "x",
|
|
["a8"] = "y",
|
|
["a9"] = "z",
|
|
["c1"] = "A",
|
|
["c2"] = "B",
|
|
["c3"] = "C",
|
|
["c4"] = "D",
|
|
["c5"] = "E",
|
|
["c6"] = "F",
|
|
["c7"] = "G",
|
|
["c8"] = "H",
|
|
["c9"] = "I",
|
|
["d1"] = "J",
|
|
["d2"] = "K",
|
|
["d3"] = "L",
|
|
["d4"] = "M",
|
|
["d5"] = "N",
|
|
["d6"] = "O",
|
|
["d7"] = "P",
|
|
["d8"] = "Q",
|
|
["d9"] = "R",
|
|
["e2"] = "S",
|
|
["e3"] = "T",
|
|
["e4"] = "U",
|
|
["e5"] = "V",
|
|
["e6"] = "W",
|
|
["e7"] = "X",
|
|
["e8"] = "Y",
|
|
["e9"] = "Z",
|
|
["f0"] = 0,
|
|
["f1"] = 1,
|
|
["f2"] = 2,
|
|
["f3"] = 3,
|
|
["f4"] = 4,
|
|
["f5"] = 5,
|
|
["f6"] = 6,
|
|
["f7"] = 7,
|
|
["f8"] = 8,
|
|
["f9"] = 9,
|
|
["4b"] = ".",
|
|
["4c"] = "<",
|
|
["4d"] = "(",
|
|
["4e"] = "+",
|
|
["4f"] = "|",
|
|
["5a"] = "!",
|
|
["5b"] = "$",
|
|
["5c"] = "*",
|
|
["5d"] = ")",
|
|
["5e"] = ";",
|
|
["60"] = "-",
|
|
["61"] = "/",
|
|
["6b"] = ",",
|
|
["6c"] = "%",
|
|
["6d"] = "_",
|
|
["6e"] = ">",
|
|
["6f"] = "?",
|
|
["79"] = "`",
|
|
["7a"] = ":",
|
|
["7b"] = "#",
|
|
["7c"] = "@",
|
|
["7d"] = "'",
|
|
["7e"] = "=",
|
|
["7f"] = "\"",
|
|
["a1"] = "~",
|
|
["ba"] = "[",
|
|
["bb"] = "]",
|
|
["c0"] = "{",
|
|
["d0"] = "}",
|
|
["e0"] = "\\" -- escape the \ character
|
|
}
|
|
|
|
-- ebcdic2ascii does not contain all value, set a default value
|
|
-- to improve stability.
|
|
setmetatable(ebcdic2ascii, { __index = function() return " " end })
|
|
|
|
-- create the socket used for our connection
|
|
local socket = nmap.new_socket()
|
|
|
|
-- set a reasonable timeout value
|
|
socket:set_timeout(10000)
|
|
|
|
-- do some exception handling / cleanup
|
|
local catch = function()
|
|
stdnse.print_debug("%s", "ERROR communicating with " .. host.ip .. " on port " .. port.number)
|
|
socket:close()
|
|
end
|
|
|
|
local try = nmap.new_try(catch)
|
|
|
|
try(socket:connect(host.ip, port.number, "tcp"))
|
|
|
|
-- Build DB2 EXCSAT (exchange server attributes) command packet
|
|
|
|
local query = string.char(0x00, 0x98, 0xd0, 0x41, 0x00, 0x01, 0x00, 0x92, 0x10, 0x41) -- Header
|
|
|
|
-- NOTE: The server's response packet is in the same format at the client query packet being built
|
|
-- in the section below.
|
|
|
|
-- External Name section: first two bytes (00,48) are section length in HEX, next bytes (11,5e) are section identifier for External Name
|
|
-- In this packet the external name is 'db2jcc_application JCC03570300' encoded in EBCDIC
|
|
query = query .. string.char(0x00, 0x48, 0x11, 0x5e, 0x84, 0x82, 0xf2, 0x91, 0x83, 0x83, 0x6d, 0x81, 0x97, 0x97, 0x93, 0x89)
|
|
query = query .. string.char(0x83, 0x81, 0xa3, 0x89, 0x96, 0x95, 0x40, 0x40, 0xd1, 0xc3, 0xc3, 0xf0, 0xf3, 0xf5, 0xf7, 0xf0)
|
|
query = query .. string.char(0xf3, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
query = query .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
|
query = query .. string.char(0x00, 0x00, 0x00, 0x60, 0xf0, 0xf0, 0xf0, 0xf1)
|
|
|
|
-- Client Name section: first two bytes (00,16) are section length in HEX, next two bytes (11,6d) are section identifier for Server Name
|
|
-- In the request packet Server Name = client name. The value here is all spaces, encoded in EBCDIC
|
|
query = query .. string.char(0x00, 0x16, 0x11, 0x6d, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40)
|
|
query = query .. string.char(0x40, 0x40, 0x40, 0x40, 0x40, 0x40)
|
|
|
|
-- Product Release Level section: This section is not as important in the client query as it is in the server response.
|
|
-- The first two bytes (00,0c) are section length in HEX, next two bytes (11,5a) are section identifier for Product Release Level
|
|
-- The value here is 'JCC03570' encoded in EBCDIC
|
|
query = query .. string.char(0x00, 0x0c, 0x11, 0x5a, 0xd1, 0xc3, 0xc3, 0xf0, 0xf3, 0xf5, 0xf7, 0xf0)
|
|
|
|
-- Manager level section: first two bytes (00,18) are section length in HEX, next two bytes (14,04) are section identifier for Manager-Level List
|
|
query = query .. string.char(0x00, 0x18, 0x14, 0x04, 0x14, 0x03, 0x00, 0x07, 0x24, 0x07, 0x00, 0x0a, 0x24, 0x0f, 0x00, 0x08)
|
|
query = query .. string.char(0x14, 0x40, 0x00, 0x09, 0x14, 0x74, 0x00, 0x08)
|
|
|
|
-- Server Class section: first two bytes (00,0c) are section length in HEX, next two bytes (11,47) are section identifier for Server Class Name
|
|
-- This section is essentially platform software information. The value here is 'QDB2/JBM' encoded in EBCDIC)
|
|
query = query .. string.char(0x00, 0x0c, 0x11, 0x47, 0xd8, 0xc4, 0xc2, 0xf2, 0x61, 0xd1, 0xe5, 0xd4)
|
|
|
|
-- Access Security section
|
|
query = query .. string.char(0x00, 0x26, 0xd0, 0x01, 0x00, 0x02, 0x00, 0x20, 0x10, 0x6d, 0x00, 0x06, 0x11, 0xa2, 0x00, 0x03)
|
|
|
|
|
|
-- Database name section: This is the client's query for a specific database. A DB2 default database name, 'db2insta1', was chosen.
|
|
-- It is encoded below in EBCDIC. The first two bytes (00,16) are section length in HEX, next two bytes (21,10) are section identifier
|
|
-- for Relational Database Name
|
|
query = query .. string.char(0x00, 0x16, 0x21, 0x10, 0x84, 0x82, 0xf2, 0x89, 0x95, 0xa2, 0xa3, 0xf1, 0x40, 0x40, 0x40, 0x40)
|
|
query = query .. string.char(0x40, 0x40, 0x40, 0x40, 0x40, 0x40)
|
|
|
|
try(socket:send(query))
|
|
|
|
local status
|
|
local response
|
|
|
|
-- read in any response we might get
|
|
status, response = socket:receive()
|
|
|
|
socket:close()
|
|
|
|
if (not status) or (response == "TIMEOUT") or (response == nil) then
|
|
stdnse.print_debug("%s","ERROR: No data, ending communications with " .. host.ip .. ":" .. port.number)
|
|
return
|
|
end
|
|
|
|
local position = 0
|
|
|
|
-- Check to see if the data is actually a DB2 DDM EXCSAT response.
|
|
-- 0d in the 3rd byte of the data section seems to be a reliable test.
|
|
if string.format("%x",string.byte(response,position + 3)) ~= "d0" then
|
|
return
|
|
end
|
|
|
|
local bytes = " "
|
|
local len_response = string.len(response) - 2
|
|
|
|
-- Parse response until the EXCSAT identifier is found. From here we should
|
|
-- be able to find everything else.
|
|
while (bytes ~= "1443") and (position <= len_response) do
|
|
bytes = string.format("%x",string.byte(response,position +1)) .. string.format("%x",string.byte(response,position + 2))
|
|
if bytes == nil then
|
|
return
|
|
end
|
|
position = position + 2
|
|
end
|
|
|
|
if position >= len_response then
|
|
-- If this section is true then this either not a valid response or
|
|
-- it is in a format that we have not seen. Exit cleanly.
|
|
return
|
|
end
|
|
|
|
-- ****************************************************************************
|
|
-- Process the Server class section of the response packet
|
|
-- ****************************************************************************
|
|
local len_external_name, external_name = process_block(response, position, ebcdic2ascii)
|
|
|
|
-- ****************************************************************************
|
|
-- Process the Manager Level section of the response packet
|
|
-- ****************************************************************************
|
|
-- Move the position to the beginning of the current section
|
|
position = position + len_external_name
|
|
|
|
-- Get the length of the next block, Wireshark calls this "Manager-Level list"
|
|
-- We are going to skip over this section
|
|
local len_manager_level = string.format("%d",string.byte(response, position +1)) .. string.format("%d",string.byte(response,position + 2))
|
|
|
|
|
|
-- ****************************************************************************
|
|
-- Process the Server class section of the response packet
|
|
-- ****************************************************************************
|
|
-- Move the position to the beginning of the current section
|
|
position = position + len_manager_level
|
|
local len_server_class, server_class = process_block(response, position, ebcdic2ascii)
|
|
|
|
|
|
-- ****************************************************************************
|
|
-- Process the Server name section of the response packet
|
|
-- ****************************************************************************
|
|
-- Move the position to the beginning of the current section
|
|
position = position + len_server_class
|
|
local len_server_name, server_name = process_block(response, position, ebcdic2ascii)
|
|
|
|
-- ****************************************************************************
|
|
-- Process the Server version section of the response packet
|
|
-- ****************************************************************************
|
|
-- Move the position to the beginning of the current section
|
|
position = position + len_server_name
|
|
|
|
local len_server_version, server_version = process_block(response, position, ebcdic2ascii)
|
|
|
|
if string.sub(server_version,1,3) == "SQL" then
|
|
local major_version = string.sub(server_version,4,5)
|
|
|
|
-- strip the leading 0 from the major version, for consistency with
|
|
-- nmap-service-probes results
|
|
if string.sub(major_version,1,1) == "0" then
|
|
major_version = string.sub(major_version,2)
|
|
end
|
|
local minor_version = string.sub(server_version,6,7)
|
|
local hotfix = string.sub(server_version,8)
|
|
server_version = major_version .. "." .. minor_version .. "." .. hotfix
|
|
end
|
|
|
|
-- Try to determine which of the two values (probe version vs script) has more
|
|
-- precision. A couple DB2 versions send DB2 UDB 7.1 vs SQL090204 (9.02.04)
|
|
local _
|
|
local current_count = 0
|
|
if port.version.version ~= nil then
|
|
_, current_count = string.gsub(port.version.version, "%.", "%.")
|
|
end
|
|
|
|
local new_count = 0
|
|
if server_version ~= nil then
|
|
_, new_count = string.gsub(server_version, "%.", "%.")
|
|
end
|
|
|
|
if current_count < new_count then
|
|
port.version.version = server_version
|
|
end
|
|
|
|
-- Set port information
|
|
port.version.name = "ibm-db2"
|
|
port.version.name_confidence = 100
|
|
if server_class ~= nil then port.version.extrainfo = server_class end
|
|
|
|
nmap.set_port_version(host, port, "hardmatched")
|
|
|
|
-- Generate results
|
|
local results = "DB2 Version: " .. server_version .. "\n"
|
|
results = results .. "Server Platform: " .. server_class .. "\n"
|
|
results = results .. "Instance Name: " .. server_name .. "\n"
|
|
results = results .. "External Name: " .. external_name
|
|
|
|
return results
|
|
|
|
end
|
|
|
|
|