--- -- 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("