---
-- Implements functionality related to Server Message Block (SMB, an extension
-- of CIFS) version 2 traffic, which is a Windows protocol.
--
-- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems. Microsoft's
-- extensive documentation is available at the following URL:
-- * SMB2: https://msdn.microsoft.com/en-us/library/cc246482.aspx
-----------------------------------------------------------------------
local asn1 = require "asn1"
local bin = require "bin"
local bit = require "bit"
local coroutine = require "coroutine"
local io = require "io"
local math = require "math"
local match = require "match"
local netbios = require "netbios"
local nmap = require "nmap"
local os = require "os"
local smbauth = require "smbauth"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local unicode = require "unicode"
local smb = require "smb"
_ENV = stdnse.module("smb2", stdnse.seeall)
-- These arrays are filled in with constants at the bottom of this file
command_codes = {}
command_names = {}
status_codes = {}
status_names = {}
filetype_codes = {}
filetype_names = {}
file_attributes = {}
smb2_flags = {}
smb2_OplockLevel = {}
smb2_ImpersonationLevel = {}
file_share_access = {}
create_disposition = {}
create_options = {}
local TIMEOUT = 10000
---Wrapper around smbauth.add_account
.
function add_account(host, username, domain, password, password_hash, hash_type, is_admin)
smbauth.add_account(host, username, domain, password, password_hash, hash_type, is_admin)
end
---Wrapper around smbauth.get_account
.
function get_account(host)
return smbauth.get_account(host)
end
---Get an 'overrides' table for the anonymous user
--
--@param overrides [optional] A base table of overrides. The appropriate fields will be added.
function get_overrides_anonymous(overrides)
if(not(overrides)) then
return {username='', domain='', password='', password_hash=nil, hash_type='none'}
else
overrides['username'] = ''
overrides['domain'] = ''
overrides['password'] = ''
overrides['password_hash'] = ''
overrides['hash_type'] = 'none'
end
end
---Convert a status number from the SMB header into a status name, returning an error message (not nil) if
-- it wasn't found.
--
--@param status The numerical status.
--@return A string representing the error. Never nil.
function get_status_name(status)
stdnse.debug(2, "SMB2: Got status '%s'", status)
if(status_names[status] == nil) then
-- If the name wasn't found in the array, do a linear search on it
for i, v in pairs(status_names) do
if(v == status) then
return i
end
end
return string.format("NT_STATUS_UNKNOWN (0x%08x)", status)
else
return status_names[status]
end
end
--- Begins a SMB session, automatically determining the best way to connect.
--
-- @param host The host object
-- @return (status, smb) if the status is true, result is the newly crated smb object;
-- otherwise, socket is the error message.
function start(host)
local port = smb.get_port(host)
local status, result
local state = {}
state['uid'] = 0
state['tid'] = 0
state['mid'] = 1
state['pid'] = math.random(32766) + 1
state['host'] = host
state['ip'] = host.ip
state['sequence'] = -1
state['MessageId'] = -1
-- Store the name of the server
local nbcache_mutex = nmap.mutex("Netbios lookup mutex")
nbcache_mutex "lock"
if ( not(host.registry['netbios_name']) ) then
status, result = netbios.get_server_name(host.ip)
if(status == true) then
host.registry['netbios_name'] = result
state['name'] = result
end
else
stdnse.debug2("SMB: Resolved netbios name from cache")
state['name'] = host.registry['netbios_name']
end
nbcache_mutex "done"
stdnse.debug2("SMB: Starting SMB2 session for %s (%s)", host.name, host.ip)
if(port == nil) then
return false, "SMB: Couldn't find a valid port to check"
end
-- Initialize the accounts for logging on
smbauth.init_account(host)
if(port ~= 139) then
status, state['socket'] = start_raw(host, port)
state['port'] = port
if(status == false) then
return false, state['socket']
end
return true, state
else
status, state['socket'] = start_netbios(host, port)
state['port'] = port
if(status == false) then
return false, state['socket']
end
return true, state
end
return false, "SMB: Couldn't find a valid port to check"
end
--- Kills the SMB connection and closes the socket.
--
-- In addition to killing the connection, this function will log off the user and disconnect
-- the connected tree, if possible.
--
--@param smb The SMB object associated with the connection
--@return (status, result) If status is false, result is an error message. Otherwise, result
-- is undefined.
function stop(smb)
if(smb['TreeId'] ~= 0) then
tree_disconnect(smb)
end
if(smb['MessageId'] ~= -1) then
logoff(smb)
end
stdnse.debug2("SMB: Closing socket")
if(smb['socket'] ~= nil) then
local status, err = smb['socket']:close()
if(status == false) then
return false, "SMB: Failed to close socket: " .. err
end
end
return true
end
--- Begins a raw SMB session, likely over port 445. Since nothing extra is required, this
-- function simply makes a connection and returns the socket.
--
--@param host The host object to check.
--@param port The port to use (most likely 445).
--@return (status, socket) if status is true, result is the newly created socket.
-- Otherwise, socket is the error message.
function start_raw(host, port)
local status, err
local socket = nmap.new_socket()
socket:set_timeout(TIMEOUT)
status, err = socket:connect(host, port, "tcp")
if(status == false) then
return false, "SMB: Failed to connect to host: " .. err
end
return true, socket
end
--- Begins a SMB session over NetBIOS.
--
-- This requires a NetBIOS Session Start message to be sent first, which in
-- turn requires the NetBIOS name. The name can be provided as a parameter, or
-- it can be automatically determined.
--
-- Automatically determining the name is interesting, to say the least. Here
-- are the names it tries, and the order it tries them in:
-- * The name the user provided, if present
-- * The name pulled from NetBIOS (udp/137), if possible
-- * The generic name "*SMBSERVER"
-- * Each subset of the domain name (for example, scanme.insecure.org would
-- attempt "scanme", "scanme.insecure", and "scanme.insecure.org")
--
-- This whole sequence is a little hackish, but it's the standard way of doing
-- it.
--
--@param host The host object to check.
--@param port The port to use (most likely 139).
--@param name [optional] The NetBIOS name of the host. Will attempt to
-- automatically determine if it isn't given.
--@return (status, socket) if status is true, result is the port
-- Otherwise, socket is the error message.
function start_netbios(host, port, name)
local i
local status, err
local pos, result, flags, length
local socket = nmap.new_socket()
-- First, populate the name array with all possible names, in order of significance
local names = {}
-- Use the name parameter
if(name ~= nil) then
names[#names + 1] = name
end
-- Get the name of the server from NetBIOS
status, name = netbios.get_server_name(host.ip)
if(status == true) then
names[#names + 1] = name
end
-- "*SMBSERVER" is a special name that any server should respond to
names[#names + 1] = "*SMBSERVER"
-- If all else fails, use each substring of the DNS name (this is a HUGE hack, but is actually
-- a recommended way of doing this!)
if(host.name ~= nil and host.name ~= "") then
local new_names = get_subnames(host.name)
for i = 1, #new_names, 1 do
names[#names + 1] = new_names[i]
end
end
-- This loop will try all the NetBIOS names we've collected, hoping one of them will work. Yes,
-- this is a hackish way, but it's actually the recommended way.
i = 1
repeat
-- Use the current name
name = names[i]
-- Some debug information
stdnse.debug1("SMB: Trying to start NetBIOS session with name = '%s'", name)
-- Request a NetBIOS session
local session_request = bin.pack(">CCSzz",
0x81, -- session request
0x00, -- flags
0x44, -- length
netbios.name_encode(name), -- server name
netbios.name_encode("NMAP") -- client name
);
stdnse.debug3("SMB: Connecting to %s", host.ip)
socket:set_timeout(TIMEOUT)
status, err = socket:connect(host, port, "tcp")
if(status == false) then
socket:close()
return false, "SMB: Failed to connect: " .. err
end
-- Send the session request
stdnse.debug3("SMB: Sending NetBIOS session request with name %s", name)
status, err = socket:send(session_request)
if(status == false) then
socket:close()
return false, "SMB: Failed to send: " .. err
end
socket:set_timeout(TIMEOUT)
-- Receive the session response
stdnse.debug3("SMB: Receiving NetBIOS session response")
status, result = socket:receive_buf(match.numbytes(4), true);
if(status == false) then
socket:close()
return false, "SMB: Failed to close socket: " .. result
end
pos, result, flags, length = bin.unpack(">CCS", result)
if(result == nil or length == nil) then
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [1]"
end
-- Check for a positive session response (0x82)
if result == 0x82 then
stdnse.debug3("SMB: Successfully established NetBIOS session with server name %s", name)
return true, socket
end
-- If the session failed, close the socket and try the next name
stdnse.debug1("SMB: Session request failed, trying next name")
socket:close()
-- Try the next name
i = i + 1
until i > #names
-- We reached the end of our names list
stdnse.debug1("SMB: None of the NetBIOS names worked!")
return false, "SMB: Couldn't find a NetBIOS name that works for the server. Sorry!"
end
--- Creates a string containing a SMB packet header - async. The header looks like this:
--
--
-- --------------------------------------------------------------------------------------------------
-- | 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
-- --------------------------------------------------------------------------------------------------
-- | 0xFE | 'S' | 'M' | 'B' |
-- --------------------------------------------------------------------------------------------------
-- | StructureSize | CreditCharge |
-- --------------------------------------------------------------------------------------------------
-- | (ChannelSequence/Reserved)/Status |
-- --------------------------------------------------------------------------------------------------
-- | Command | CreditRequest/CreditResponse |
-- --------------------------------------------------------------------------------------------------
-- | Flags |
-- --------------------------------------------------------------------------------------------------
-- | NextCommand |
-- --------------------------------------------------------------------------------------------------
-- | MessageId |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | AsyncId |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | MessageId |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | SessionId |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | Signature |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
--
--
-- All fields are, incidentally, encoded in little endian byte order.
--
-- For the purposes here, the program doesn't care about most of the fields so they're given default
-- values. The "command" field is the only one we ever have to set manually, in my experience. The TID
-- and UID need to be set, but those are stored in the smb state and don't require user intervention.
--
--@param smb The smb state table.
--@param command The command to use.
--@param overrides The overrides table. Keep in mind that overriding things like flags is generally a very bad idea, unless you know what you're doing.
--@return A binary string containing the packed packet header.
function smb_encode_header_async(smb, command, overrides)
-- TODO
end
---Turn off extended security negotiations for this connection.
--
-- There are a few reasons you might want to do that, the main ones being that
-- extended security is going to be marginally slower and it's not going to
-- give the same level of information in some cases (namely, it doesn't present
-- the server's name).
--@param smb The SMB state table.
function disable_extended(smb)
smb['extended_security'] = false
end
---
-- Starts SMB2 connection
--
--
function start_ex(host, bool_negotiate_protocol, bool_start_session, str_tree_connect, str_create_file, bool_disable_extended, overrides)
local smbstate
local status, err
-- Make sure we have overrides
overrides = overrides or {}
-- Begin the SMB session
status, smbstate = start(host)
if(status == false) then
return false, smbstate
end
-- Disable extended security if it was requested
if(bool_disable_extended == true) then
disable_extended(smbstate)
end
if(bool_negotiate_protocol == true) then
-- Negotiate the protocol
status, err = negotiate_protocol(smbstate, overrides)
if(status == false) then
stop(smbstate)
return false, err
end
if(bool_start_session == true) then
-- Start up a session
status, err = start_session(smbstate, overrides)
if(status == false) then
stop(smbstate)
return false, err
end
if(str_tree_connect ~= nil) then
-- Connect to share
status, err = tree_connect(smbstate, str_tree_connect, overrides)
if(status == false) then
stop(smbstate)
return false, err
end
if(str_create_file ~= nil) then
-- Try to connect to requested pipe
status, err = create_file(smbstate, str_create_file, overrides)
if(status == false) then
stop(smbstate)
return false, err
end
end
end
end
end
-- Return everything
return true, smbstate
end
--- Creates a string containing a SMB packet header - sync. The header looks like this:
--
--
-- --------------------------------------------------------------------------------------------------
-- | 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
-- --------------------------------------------------------------------------------------------------
-- | 0xFE | 'S' | 'M' | 'B' |
-- --------------------------------------------------------------------------------------------------
-- | StructureSize | CreditCharge |
-- --------------------------------------------------------------------------------------------------
-- | (ChannelSequence/Reserved)/Status |
-- --------------------------------------------------------------------------------------------------
-- | Command | CreditRequest/CreditResponse |
-- --------------------------------------------------------------------------------------------------
-- | Flags |
-- --------------------------------------------------------------------------------------------------
-- | NextCommand |
-- --------------------------------------------------------------------------------------------------
-- | MessageId |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | Reserved |
-- --------------------------------------------------------------------------------------------------
-- | TreeId |
-- --------------------------------------------------------------------------------------------------
-- | SessionId |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | Signature |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
-- | ... |
-- --------------------------------------------------------------------------------------------------
--
--
-- All fields are, incidentally, encoded in little endian byte order.
--
-- For the purposes here, the program doesn't care about most of the fields so they're given default
-- values. The "command" field is the only one we ever have to set manually, in my experience. The TID
-- and UID need to be set, but those are stored in the smb state and don't require user intervention.
--
--@param smb The smb state table.
--@param command The command to use.
--@param overrides The overrides table. Keep in mind that overriding things like flags is generally a very bad idea, unless you know what you're doing.
--@return A binary string containing the packed packet header.
function smb_encode_header_sync(smb, command, overrides)
-- Make sure we have an overrides array
overrides = overrides or {}
-- Used for the ProtocolId
local sig = "\xFESMB"
local structureSize = 64
local flags = 0
if smb['MessageId'] then
smb['MessageId'] = smb['MessageId'] + 1
end
local header = bin.pack("smb_get_header.
--@param data The data.
--@param overrides Overrides table.
--@return (result, err) If result is false, err is the error message. Otherwise, err is
-- undefined
function smb_send(smb, header, data, overrides)
overrides = overrides or {}
local body = header .. data
local attempts = 5
local status, err
local out = bin.pack(">II", netbios_data)
if(netbios_length == nil) then
return false, "SMB2: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [2]"
end
-- Make the length 24 bits
netbios_length = bit.band(netbios_length, 0x00FFFFFF)
-- The total length is the netbios_length, plus 4 (for the length itself)
length = netbios_length + 4
local attempts = 5
local smb_data
repeat
attempts = attempts - 1
status, smb_data = smb['socket']:receive_buf(match.numbytes(netbios_length), true)
until(status or (attempts == 0))
-- Make sure the connection is still alive
if(status ~= true) then
return false, "SMB2: Failed to receive bytes after 5 attempts: " .. smb_data
end
local result = netbios_data .. smb_data
if(#result ~= length) then
stdnse.debug1("SMB2: ERROR: Received wrong number of bytes, there will likely be issues (received %d, expected %d)", #result, length)
return false, string.format("SMB2: ERROR: Didn't receive the expected number of bytes; received %d, expected %d. This will almost certainly cause some errors.", #result, length)
end
-- The header is 64 bytes.
pos, header = bin.unpack("SMB2 NEGOTIATE, which is typically the first SMB packet sent out.
function negotiate_protocol(smb, overrides)
stdnse.debug(1, "SMB2:Negotiating connection")
local header, data
-- Make sure we have overrides
overrides = overrides or {}
header = smb_encode_header_sync(smb, command_codes['SMB2_COM_NEGOTIATE'], overrides)
local StructureSize = 36
local DialectCount
-- Data is a list of strings, terminated by a blank one.
if(overrides['Dialects'] == nil) then
DialectCount = 2
else
DialectCount = #overrides['Dialects']
end
local SecurityMode = overrides["SecurityMode"] or 0x01
local Reserved = 0
local Capabilities = overrides["SecurityMode"] or 0
local GUID1 = overrides["GUID1"] or 1
local GUID2 = overrides["GUID2"] or 1
local ClientStartTime = overrides["ClientStartTime"] or 0x010
local Dialects1 = 0x0202 --Dialect 2.0.2
local Dialects2 = 0x0210 --Dialect 2.1.0
local Dialects3 = 0x0300 --Dialect 3.0
local Dialects3_0_2 = 0x0302 --Dialect 3.0.2
local Dialects3_1_1 = 0x0311 --Dialect 3.1.1
local Dialects = {0x0202, 0x0210}
local data = bin.pack(" 0 ) then
pos, smb['SecurityBlob'] = bin.unpack(" 11 ) then
local pos, oid = bin.unpack(">A6", smb['SecurityBlob'], 5)
sp_nego = ( oid == "\x2b\x06\x01\x05\x05\x02" ) -- check for SPNEGO OID 1.3.6.1.5.5.2
end
while result ~= false do
-- These are loop variables
local security_blob = nil
local security_blob_length = 0
repeat
-- Get the new security blob, passing the old security blob as a parameter. If there was no previous security blob, then nil is passed, which creates a new one
if ( not(security_blob) ) then
status, security_blob, smb['mac_key'] = smbauth.get_security_blob(security_blob, smb['ip'], username, domain, password, password_hash, hash_type, (sp_nego and 0x00088215))
if ( sp_nego ) then
local enc = asn1.ASN1Encoder:new()
local mechtype = enc:encode( { type = 'A0', value = enc:encode( { type = '30', value = enc:encode( { type = '06', value = bin.pack("H", "2b06010401823702020a") } ) } ) } )
local oid = enc:encode( { type = '06', value = bin.pack("H", "2b0601050502") } )
security_blob = enc:encode(security_blob)
security_blob = enc:encode( { type = 'A2', value = security_blob } )
security_blob = mechtype .. security_blob
security_blob = enc:encode( { type = '30', value = security_blob } )
security_blob = enc:encode( { type = 'A0', value = security_blob } )
security_blob = oid .. security_blob
security_blob = enc:encode( { type = '60', value = security_blob } )
end
else
if ( sp_nego ) then
if ( smb['domain'] or smb['server'] and ( not(domain) or #domain == 0 ) ) then
domain = smb['domain'] or smb['server']
end
hash_type = "ntlm"
end
status, security_blob, smb['mac_key'] = smbauth.get_security_blob(security_blob, smb['ip'], username, domain, password, password_hash, hash_type, (sp_nego and 0x00088215))
if ( sp_nego ) then
local enc = asn1.ASN1Encoder:new()
security_blob = enc:encode(security_blob)
security_blob = enc:encode( { type = 'A2', value = security_blob } )
security_blob = enc:encode( { type = '30', value = security_blob } )
security_blob = enc:encode( { type = 'A1', value = security_blob } )
end
end
-- There was an error processing the security blob
if(status == false) then
return false, string.format("SMB: ERROR: Security blob: %s", security_blob)
end
local data = bin.pack(" 9) then
return false, "SMB: ERROR: Server has too many active connections; giving up."
end
local backoff = math.random() * 10
stdnse.debug1("SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
stdnse.sleep(backoff)
else
-- Display a message to the user, and try the next account
if(log_errors == nil or log_errors == true) then
stdnse.debug1("SMB: Extended login to %s as %s\\%s failed (%s)", smb['ip'], domain, stdnse.string_or_blank(username), status_name)
end
--Go to the next account
if(overrides == nil or overrides['username'] == nil) then
smbauth.next_account(smb['host'])
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
if(not(result)) then
return false, username
end
else
result = false
end
result = false
end
end -- Loop over the accounts
if(log_errors == nil or log_errors == true) then
stdnse.debug1("SMB: ERROR: All logins failed, sorry it didn't work out!")
end
return false, status_name
end
---
-- SMB2_COM_TREE_CONNECT
---
function tree_connect(smb, path, overrides)
overrides = overrides or {}
local buffer = ""
for i = 1, #path do
buffer = buffer .. bin.pack("share_host_returns_proper_error
-- has been called and returns true
.
--
--@param host The host object
--@param share The share to test
--@return (status, result) If status is false, result is an error message. Otherwise, result is a boolean value:
-- true if anonymous access is permitted, false otherwise.
function share_user_can_read(host, share)
local status, smbstate, err
local overrides = {}
-- Begin the SMB session
status, smbstate = start(host)
if(status == false) then
return false, smbstate
end
-- Negotiate the protocol
status, err = negotiate_protocol(smbstate, overrides)
if(status == false) then
stop(smbstate)
return false, err
end
-- Start up a null session
status, err = start_session(smbstate, overrides)
if(status == false) then
stop(smbstate)
return false, err
end
-- Attempt a connection to the share
status, err = tree_connect(smbstate, share, overrides)
if(status == false) then
-- Stop the session
stop(smbstate)
-- ACCESS_DENIED is the expected error: it tells us that the connection failed
if(err == 0xc0000022 or err == 'NT_STATUS_ACCESS_DENIED') then
return true, false
else
return false, err
end
end
stop(smbstate)
return true, true
end
---Get all the details we can about the share. These details are stored in a table and returned.
--
--@param host The host object.
--@param share An array of shares to check.
--@return (status, result) If status is false, result is an error message. Otherwise, result is a boolean value:
-- true if the file was successfully written, false if it was not.
function share_get_details(host, share)
local msrpc2 = require "msrpc2" -- avoid require cycle
local smbstate, status, result
local i
local details = {}
-- Save the name
details['name'] = share
-- Check if the current user can read the share
stdnse.debug1("SMB2: Checking if share %s can be read by the current user", share)
status, result = share_user_can_read(host, share)
if(status == false) then
return false, result
end
details['user_can_read'] = result
-- Check if the anonymous reader can read the share
stdnse.debug1("SMB2: Checking if share %s can be read by the anonymous user", share)
status, result = share_anonymous_can_read(host, share)
if(status == true) then
details['anonymous_can_read'] = result
end
-- Check if the current user can write to the share
stdnse.debug1("SMB2: Checking if share %s can be written by the current user", share)
status, result = share_user_can_write(host, share)
if(status == false) then
if(result == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
details['user_can_write'] = "NT_STATUS_OBJECT_NAME_NOT_FOUND"
else
return false, result
end
end
details['user_can_write'] = result
-- Check if the anonymous user can write to the share
stdnse.debug1("SMB: Checking if share %s can be written by the anonymous user", share)
status, result = share_anonymous_can_write(host, share)
status, result = share_anonymous_can_write(host, share)
if(status == false and result == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
details['anonymous_can_write'] = "NT_STATUS_OBJECT_NAME_NOT_FOUND"
elseif( status == true ) then
details['anonymous_can_write'] = result
end
-- Try and get full details about the share
status, result = msrpc2.get_share_info(host, share)
if(status == false) then
-- We don't stop for this error (it's pretty common since administrative privileges are required here)
stdnse.debug1("SMB: Failed to get share info for %s: %s", share, result)
details['details'] = result
else
-- Process the result a bit
result = result['info']
if(result['max_users'] == 0xFFFFFFFF) then
result['max_users'] = ""
end
details['details'] = result
end
return true, details
end
function share_get_list(host)
local msrpc2 = require "msrpc2" -- avoid require cycle
local status, result
local enum_status
local extra = ""
local shares = {}
local share_details = {}
-- Try and do this the good way, make a MSRPC call to get the shares
stdnse.debug1("SMB2: Attempting to log into the system to enumerate shares")
enum_status, shares = msrpc2.enum_shares(host)
print(enum_status, shares)
-- If that failed, try doing it with brute force. This almost certainly won't find everything, but it's the
-- best we can do.
if(enum_status == false) then
stdnse.debug1("SMB2: Enumerating shares failed, guessing at common ones (%s)", shares)
extra = string.format("ERROR: Enumerating shares failed, guessing at common ones (%s)", shares)
-- Take some common share names I've seen (thanks to Brandon Enright for most of these, except the last few)
shares = {"ADMIN", "BACKUP", "DATA", "DESKTOP", "DOCS", "FILES", "GROUPS", "HD", "HOME", "INFO", "IPC", "MEDIA", "MY DOCUMENTS", "NETLOGON", "PICTURES", "PORN", "PR0N", "PRINT", "PROGRAMS", "PRON", "PUBLIC", "SHARE", "SHARED", "SOFTWARE", "STMP", "TEMP", "TEST", "TMP", "USERS", "WEB DOCUMENTS","WEBSERVER", "WWW", "XSERVE" }
-- Try every alphabetic share
for i = string.byte("A", 1), string.byte("Z", 1), 1 do
shares[#shares + 1] = string.char(i)
end
-- For each share, add one with the same name and a trailing '$'
local sharesLength = #shares
for shareItr = 1, sharesLength, 1 do
shares[ sharesLength + shareItr ] = shares[ shareItr ] .. '$'
end
else
stdnse.debug1("SMB: Found %d shares, will attempt to find more information", #shares)
end
-- Sort the shares
table.sort(shares)
-- Ensure that the server returns the proper error message
-- first try anonymously, then using a user account (in case anonymous connections are not supported)
for _, anon in ipairs({true, false}) do
status, result = share_host_returns_proper_error(host)
if(status == true and result == false) then
return false, "Server doesn't return proper value for non-existent shares; can't enumerate shares"
end
end
if(status == false) then
return false, result
end
-- Get more information on each share
for i = 1, #shares, 1 do
local status, result
stdnse.debug(3,"SMB2: Getting information for share: %s", shares[i])
status, result = share_get_details(host, shares[i])
if(status == false and result == 'NT_STATUS_BAD_NETWORK_NAME') then
stdnse.debug1("SMB2: Share doesn't exist: %s", shares[i])
elseif(status == false) then
stdnse.debug1("SMB2: Error while getting share details: %s", result)
return false, result
else
-- Save the share details
table.insert(share_details, result)
end
end
return true, share_details, extra
end
function close_file(smb, overrides)
overrides = overrides or {}
local header = smb_encode_header_sync(smb, command_codes['SMB2_COM_CLOSE'], overrides)
local StructureSize = 24
local Flags = 0x0000
local Reserved = 0
local response = {}
local data = bin.pack("