1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-10 09:49:05 +00:00
Files
nmap/scripts/db2-info.nse
tomsellers b5444fa390 Add a new script, db2-info.nse, that enhances DB2 database instance detection.
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]
2009-11-12 11:44:02 +00:00

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