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

o [NSE] Applied patch from Chris Woodbury that adds the following additional

information to the output of smb-os-discovery:
  + Forest name
  + FQDN
  + NetBIOS computer name
  + NetBIOS domain name
This commit is contained in:
patrik
2011-07-12 06:08:43 +00:00
parent 575c954a12
commit 23d2e0d31f
5 changed files with 229 additions and 30 deletions

View File

@@ -1,5 +1,12 @@
# Nmap Changelog ($Id$); -*-text-*- # Nmap Changelog ($Id$); -*-text-*-
o [NSE] Applied patch from Chris Woodbury that adds the following additional
information to the output of smb-os-discovery:
+ Forest name
+ FQDN
+ NetBIOS computer name
+ NetBIOS domain name
o [Ncat] Ncat now supports IPV6 addresses by default without the -6 flag. o [Ncat] Ncat now supports IPV6 addresses by default without the -6 flag.
Additionally ncat listens on both :: and localhost when passed Additionally ncat listens on both :: and localhost when passed
-l, or any other listening mode unless a specific listening address is -l, or any other listening mode unless a specific listening address is

View File

@@ -1,13 +1,16 @@
--- ---
-- Implements functionality related to Server Message Block (SMB, also known -- Implements functionality related to Server Message Block (SMB, an extension
-- as CIFS) traffic, which is a Windows protocol. -- of CIFS) traffic, which is a Windows protocol.
-- --
-- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems. Other systems -- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems. Other systems
-- implement SMB as well, including Samba and a lot of embedded devices. Some of them implement -- implement SMB as well, including Samba and a lot of embedded devices. Some of them implement
-- it properly and many of them not. Although the protocol has been documented decently -- it properly and many of them not. Although the protocol has been documented decently
-- well by Samba and others, many 3rd party implementations are broken or make assumptions. -- well by Samba and others, many 3rd party implementations are broken or make assumptions.
-- Even Samba's and Windows' implementations aren't completely compatiable. As a result, -- Even Samba's and Windows' implementations aren't completely compatiable. As a result,
-- creating an implementation that accepts everything is a bit of a minefield. -- creating an implementation that accepts everything is a bit of a minefield. Microsoft's
-- extensive documentation is available at the following URLs:
-- * SMB: http://msdn.microsoft.com/en-us/library/cc246231(v=prot.13).aspx
-- * CIFS: http://msdn.microsoft.com/en-us/library/ee442092(v=prot.13).aspx
-- --
-- Where possible, this implementation, since it's intended for scanning, will attempt to -- Where possible, this implementation, since it's intended for scanning, will attempt to
-- accept any invalid implementations it can, and fail gracefully if it can't. This has -- accept any invalid implementations it can, and fail gracefully if it can't. This has
@@ -554,7 +557,7 @@ function start_netbios(host, port, name)
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [1]" return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [1]"
end end
-- Check for a position session response (0x82) -- Check for a positive session response (0x82)
if result == 0x82 then if result == 0x82 then
stdnse.print_debug(3, "SMB: Successfully established NetBIOS session with server name %s", name) stdnse.print_debug(3, "SMB: Successfully established NetBIOS session with server name %s", name)
return true, socket return true, socket
@@ -932,7 +935,7 @@ end
-- * 'timezone' The server's timezone, in hours from UTC -- * 'timezone' The server's timezone, in hours from UTC
-- * 'timezone_str' The server's timezone, as a string -- * 'timezone_str' The server's timezone, as a string
-- * 'server_challenge' A random string used for challenge/response -- * 'server_challenge' A random string used for challenge/response
-- * 'domain' The server's primary domain -- * 'domain' The server's primary domain or workgroup
-- * 'server' The server's name -- * 'server' The server's name
function negotiate_protocol(smb, overrides) function negotiate_protocol(smb, overrides)
local header, parameters, data local header, parameters, data
@@ -1328,7 +1331,7 @@ local function start_session_extended(smb, log_errors, overrides)
status_name = get_status_name(status) status_name = get_status_name(status)
-- Only parse the parameters if it's ok or if we're going to keep going -- Only parse the parameters if it's ok or if we're going to keep going
if(status_name == "NT_STATUS_OK" or status_name == "NT_STATUS_MORE_PROCESSING_REQUIRED") then if(status_name == "NT_STATUS_SUCCESS" or status_name == "NT_STATUS_MORE_PROCESSING_REQUIRED") then
-- Parse the parameters -- Parse the parameters
pos, andx_command, andx_reserved, andx_offset, action, security_blob_length = bin.unpack("<CCSSS", parameters) pos, andx_command, andx_reserved, andx_offset, action, security_blob_length = bin.unpack("<CCSSS", parameters)
if(andx_command == nil or security_blob_length == nil) then if(andx_command == nil or security_blob_length == nil) then
@@ -1344,8 +1347,18 @@ local function start_session_extended(smb, log_errors, overrides)
smb['os'] = os smb['os'] = os
smb['lanmanager'] = lanmanager smb['lanmanager'] = lanmanager
local host_info = smbauth.get_host_info_from_security_blob(security_blob)
if ( host_info ) then
smb['fqdn'] = host_info['fqdn']
smb['domain_dns'] = host_info['dns_domain_name']
smb['forest_dns'] = host_info['dns_forest_name']
smb['server'] = host_info['netbios_computer_name']
smb['domain'] = host_info['netbios_domain_name']
end
-- If it's ok, do a cleanup and return true -- If it's ok, do a cleanup and return true
if(status_name == "NT_STATUS_OK") then if(status_name == "NT_STATUS_SUCCESS") then
-- Check if they're using an un-supported system -- Check if they're using an un-supported system
if(os == nil or lanmanager == nil) then if(os == nil or lanmanager == nil) then
stdnse.print_debug(1, "SMB: WARNING: the server is using a non-standard SMB implementation; your mileage may vary (%s)", smb['ip']) stdnse.print_debug(1, "SMB: WARNING: the server is using a non-standard SMB implementation; your mileage may vary (%s)", smb['ip'])
@@ -2920,17 +2933,42 @@ function get_os(host)
return false, "Server didn't return OS details" return false, "Server didn't return OS details"
end end
-- Convert blank values to something useful response['os'] = smbstate['os']
response['os'] = stdnse.string_or_blank(smbstate['os'], "Unknown") response['lanmanager'] = smbstate['lanmanager']
response['lanmanager'] = stdnse.string_or_blank(smbstate['lanmanager'], "Unknown") response['domain'] = smbstate['domain']
response['domain'] = stdnse.string_or_blank(smbstate['domain'], "Unknown") response['server'] = smbstate['server']
response['server'] = stdnse.string_or_blank(smbstate['server'], "Unknown") response['date'] = smbstate['date']
response['date'] = stdnse.string_or_blank(smbstate['date'], "Unknown") response['timezone_str'] = smbstate['timezone_str']
response['timezone_str'] = stdnse.string_or_blank(smbstate['timezone_str'], "Unknown")
-- Kill SMB -- Kill SMB
smb.stop(smbstate) smb.stop(smbstate)
-- Start another session with extended security. This will allow us to get
-- additional information about the target.
status, smbstate = smb.start_ex(host, true, true, nil, nil, false)
if(status == true) then
-- See if we actually got something
if (smbstate['fqdn'] or smbstate['domain_dns'] or smbstate['forest_dns']) then
response['fqdn'] = smbstate['fqdn']
response['domain_dns'] = smbstate['domain_dns']
response['forest_dns'] = smbstate['forest_dns']
-- After a non-extended security negotiation, smbstate['domain'] will
-- contain the NetBIOS domain name, or the workgroup name. However,
-- after an extended-security session setup, smbstate['domain'] will
-- contain the NetBIOS domain name. For hosts in a workgroup, Windows
-- uses the NetBIOS hostname as the NetBIOS domain name. Comparing the
-- two will reveal whether the target is in a domain or a workgroup.
if ( smbstate['domain'] ~= nil and response['domain'] ~= smbstate['domain'] ) then
response['workgroup'] = response['domain']
response['domain'] = nil
end
end
-- Kill SMB again
smb.stop(smbstate)
end
return true, response return true, response
end end
@@ -3162,13 +3200,13 @@ for i, v in pairs(command_codes) do
end end
-- see http://msdn.microsoft.com/en-us/library/cc231196(v=prot.10).aspx
status_codes = status_codes =
{ {
NT_STATUS_OK = 0x0000, NT_STATUS_SUCCESS = 0x00000000,
NT_STATUS_WERR_BADFILE = 0x00000002, NT_STATUS_WERR_BADFILE = 0x00000002,
NT_STATUS_WERR_ACCESS_DENIED = 0x00000005, NT_STATUS_WERR_ACCESS_DENIED = 0x00000005,
NT_STATUS_WERR_UNKNOWN_57 = 0x00000057, NT_STATUS_WERR_INVALID_PARAMETER = 0x00000057,
NT_STATUS_WERR_INVALID_NAME = 0x0000007b, NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c, NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c,
NT_STATUS_WERR_MORE_DATA = 0x000000ea, NT_STATUS_WERR_MORE_DATA = 0x000000ea,

View File

@@ -322,6 +322,17 @@ local function to_unicode(str)
return unicode return unicode
end end
local function from_unicode(unicode)
local str = ""
if ( unicode == nil ) then
return nil
end
for char_itr = 1, unicode:len(), 2 do
str = str .. unicode:sub(char_itr, char_itr)
end
return str
end
---Generate the Lanman v1 hash (LMv1). The generated hash is incredibly easy to reverse, because the input ---Generate the Lanman v1 hash (LMv1). The generated hash is incredibly easy to reverse, because the input
-- is padded or truncated to 14 characters, then split into two 7-character strings. Each of these strings -- is padded or truncated to 14 characters, then split into two 7-character strings. Each of these strings
-- are used as a key to encrypt the string, "KGS!@#$%" in DES. Because the keys are no longer than -- are used as a key to encrypt the string, "KGS!@#$%" in DES. Because the keys are no longer than
@@ -714,6 +725,86 @@ function get_security_blob(security_blob, ip, username, domain, password, passwo
end end
function get_host_info_from_security_blob(security_blob)
local ntlm_challenge = {}
--local pos, identifier, message_type, domain_length, domain_max, domain_offset, server_flags, challenge, reserved, target_info_length, target_info_max, target_info_offset = bin.unpack("<A8ISSIILLSSI", security_blob)
local pos, identifier, message_type, domain_length, domain_max, domain_offset, server_flags, challenge, reserved, target_info_length, target_info_max, target_info_offset = bin.unpack("<A8ISSIILLSSI", security_blob)
-- Do some validation on the NTLMSSP message
if ( identifier ~= "NTLMSSP\0" ) then
stdnse.print_debug( 1, "SMB: Invalid NTLM challenge message: unexpected signature." )
return false, "Invalid NTLM challenge message"
-- Per MS-NLMP, this field must be 2 for an NTLM challenge message
elseif ( message_type ~= 0x2 ) then
stdnse.print_debug( 1, "SMB: Invalid NTLM challenge message: unexpected message type: %d.", message_type )
return false, "Invalid message type in NTLM challenge message"
end
-- Parse the TargetName data (i.e. the server authentication realm)
if ( domain_length > 0 ) then
local length = domain_length
local pos = domain_offset + 1 -- +1 to convert to Lua's 1-based indexes
local target_realm
pos, target_realm = bin.unpack( string.format( "A%d", length ), security_blob, pos )
ntlm_challenge[ "target_realm" ] = from_unicode( target_realm )
end
-- Parse the TargetInfo data (Wireshark calls this the "Address List")
if ( target_info_length > 0 ) then
-- Definition of AvId values (IDs for AV_PAIR (attribute-value pair) structures),
-- as definied by the NTLM Authentication Protocol specification [MS-NLMP].
local NTLM_AV_ID_VALUES = {
MsvAvEOL = 0x0,
MsvAvNbComputerName = 0x1,
MsvAvNbDomainName = 0x2,
MsvAvDnsComputerName = 0x3,
MsvAvDnsDomainName = 0x4,
MsvAvDnsTreeName = 0x5,
MsvAvFlags = 0x6,
MsvAvTimestamp = 0x7,
MsvAvRestrictions = 0x8,
MsvAvTargetName = 0x9,
MsvAvChannelBindings = 0xA,
}
-- Friendlier names for AvId values, to be used as keys in the results table
-- e.g. ntlm_challenge[ "dns_computer_name" ] -> "host.test.local"
local NTLM_AV_ID_NAMES = {
[NTLM_AV_ID_VALUES.MsvAvNbComputerName] = "netbios_computer_name",
[NTLM_AV_ID_VALUES.MsvAvNbDomainName] = "netbios_domain_name",
[NTLM_AV_ID_VALUES.MsvAvDnsComputerName] = "fqdn",
[NTLM_AV_ID_VALUES.MsvAvDnsDomainName] = "dns_domain_name",
[NTLM_AV_ID_VALUES.MsvAvDnsTreeName] = "dns_forest_name",
[NTLM_AV_ID_VALUES.MsvAvTimestamp] = "timestamp",
}
local length = target_info_length
local pos = target_info_offset + 1 -- +1 to convert to Lua's 1-based indexes
local target_info
pos, target_info = bin.unpack( string.format( "A%d", length ), security_blob, pos )
pos = 1 -- reset pos to 1, since we'll be working out of just the target_info
repeat
local value, av_id, av_len
pos, av_id, av_len = bin.unpack( "<SS", target_info, pos )
pos, value = bin.unpack( string.format( "A%d", av_len ), target_info, pos )
local friendly_name = NTLM_AV_ID_NAMES[ av_id ]
if ( av_id == NTLM_AV_ID_VALUES.MsvAvEOL ) then
break
elseif ( av_id == NTLM_AV_ID_VALUES.MsvAvTimestamp ) then
-- this is a FILETIME value (see [MS-DTYP]), representing the time in 100-ns increments since 1/1/1601
ntlm_challenge[ friendly_name ] = bin.unpack( "<L", value )
elseif ( friendly_name ) then
ntlm_challenge[ friendly_name ] = from_unicode( value )
end
until ( pos >= #target_info )
end
return ntlm_challenge
end
---Create an 8-byte message signature that's sent with all SMB packets. ---Create an 8-byte message signature that's sent with all SMB packets.
-- --
--@param mac_key The key used for authentication. It's the concatination of the session key and the --@param mac_key The key used for authentication. It's the concatination of the session key and the

View File

@@ -542,7 +542,7 @@ local function initialize(host)
-- Get the OS (identifying windows versions tells us which hash to use) -- Get the OS (identifying windows versions tells us which hash to use)
result, os = smb.get_os(host) result, os = smb.get_os(host)
if(result == false) then if(result == false or os['os'] == nil) then
hostinfo['os'] = "<Unknown>" hostinfo['os'] = "<Unknown>"
else else
hostinfo['os'] = os['os'] hostinfo['os'] = os['os']

View File

@@ -1,11 +1,24 @@
description = [[ description = [[
Attempts to determine the operating system, computer name, domain, and current Attempts to determine the operating system, computer name, domain, workgroup, and current
time over the SMB protocol (ports 445 or 139). time over the SMB protocol (ports 445 or 139).
This is done by starting a session with the anonymous This is done by starting a session with the anonymous
account (or with a proper user account, if one is given; it likely doesn't make account (or with a proper user account, if one is given; it likely doesn't make
a difference); in response to a session starting, the server will send back all this a difference); in response to a session starting, the server will send back all this
information. information.
The following fields may be included in the output, depending on the
cirumstances (e.g. the workgroup name is mutually exclusive with domain and forest
names) and the information available:
* OS
* Computer name
* Domain name
* Forest name
* FQDN
* NetBIOS computer name
* NetBIOS domain name
* Workgroup
* System time
Some systems, like Samba, will blank out their name (and only send their domain). Some systems, like Samba, will blank out their name (and only send their domain).
Other systems (like embedded printers) will simply leave out the information. Other Other systems (like embedded printers) will simply leave out the information. Other
systems will blank out various pieces (some will send back 0 for the current systems will blank out various pieces (some will send back 0 for the current
@@ -18,7 +31,8 @@ servers that are being poorly maintained (for more information/random thoughts o
using the time, see http://www.skullsecurity.org/blog/?p=76. using the time, see http://www.skullsecurity.org/blog/?p=76.
Although the standard <code>smb*</code> script arguments can be used, Although the standard <code>smb*</code> script arguments can be used,
they likely won't change the outcome in any meaningful way. they likely won't change the outcome in any meaningful way. However, <code>smbnoguest</code>
will speed up the script on targets that do not allow guest access.
]] ]]
--- ---
@@ -29,9 +43,14 @@ they likely won't change the outcome in any meaningful way.
--@output --@output
-- Host script results: -- Host script results:
-- | smb-os-discovery: -- | smb-os-discovery:
-- | | OS: Windows 2000 (Windows 2000 LAN Manager) -- | OS: Windows Server (R) 2008 Standard 6001 Service Pack 1 (Windows Server (R) 2008 Standard 6.0)
-- | | Name: WORKGROUP\RON-WIN2K-TEST -- | Computer name: Sql2008
-- |_ |_ System time: 2009-11-09 14:33:39 UTC-6 -- | Domain name: lab.test.local
-- | Forest name: test.local
-- | FQDN: Sql2008.lab.test.local
-- | NetBIOS computer name: SQL2008
-- | NetBIOS domain name: LAB
-- |_ System time: 2011-04-20 13:34:06 UTC-5
----------------------------------------------------------------------- -----------------------------------------------------------------------
author = "Ron Bowes" author = "Ron Bowes"
@@ -62,6 +81,16 @@ function get_windows_version(os)
end end
function add_to_output(output_table, label, value, value_if_nil)
if (value == nil and value_if_nil ~= nil) then
value = value_if_nil
end
if (value ~= nil) then
table.insert(output_table, string.format("%s: %s", label, value) )
end
end
action = function(host) action = function(host)
local response = {} local response = {}
local status, result = smb.get_os(host) local status, result = smb.get_os(host)
@@ -70,9 +99,43 @@ action = function(host)
return stdnse.format_output(false, result) return stdnse.format_output(false, result)
end end
table.insert(response, string.format("OS: %s (%s)", get_windows_version(result['os']), result['lanmanager'])) local hostname_dns, is_domain_member, os_string, time_string
table.insert(response, string.format("Name: %s\\%s", result['domain'], result['server'])) if (result[ "fqdn" ]) then
table.insert(response, string.format("System time: %s %s", result['date'], result['timezone_str'])) -- Pull the first part of the FQDN as the computer name
hostname_dns = string.match( result[ "fqdn" ], "^([^.]+)%.?" )
if (result[ "domain_dns" ]) then
-- If the computer name doesn't match the domain name, the target is a domain member
is_domain_member = ( result[ "fqdn" ] ~= result[ "domain_dns" ] )
end
end
if (result['os'] and result['lanmanager']) then
os_string = string.format( "%s (%s)", get_windows_version( result['os'] ), result['lanmanager'] )
end
if (result['date'] and result['timezone_str']) then
time_string = string.format("%s %s", result['date'], result['timezone_str'])
end
add_to_output( response, "OS", os_string, "Unknown" )
add_to_output( response, "Computer name", hostname_dns )
if ( is_domain_member ) then
add_to_output( response, "Domain name", result[ "domain_dns" ] )
add_to_output( response, "Forest name", result[ "forest_dns" ] )
add_to_output( response, "FQDN", result[ "fqdn" ] )
end
add_to_output( response, "NetBIOS computer name", result[ "server" ] )
if ( is_domain_member ) then
add_to_output( response, "NetBIOS domain name", result[ "domain" ] )
else
add_to_output( response, "Workgroup", result[ "workgroup" ], result[ "domain" ] )
end
add_to_output( response, "System time", time_string, "Unknown" )
return stdnse.format_output(true, response) return stdnse.format_output(true, response)
end end