diff --git a/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb b/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb index a85757ed92..b494ac44c6 100644 --- a/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb +++ b/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb @@ -14,10 +14,10 @@ module Msf pkt = CONST::SMB_CREATE_PKT.make_struct pkt.from_s(buff) - payload = (pkt['Payload'].v['Payload']) + payload = (pkt['Payload'].v['Payload']).downcase payload.gsub!(/^[\x00]*/, '') # delete padding - payload = Rex::Text.to_ascii(payload) - payload.gsub!(/[\x00]*$/, '') # delete padding + payload = Rex::Text.ascii_safe_hex(payload) + payload.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars if payload.nil? || payload.empty? payload = file_name @@ -28,7 +28,7 @@ module Msf attribs = 0x80 # File Attributes eof = exe_contents.length is_dir = 0 - elsif payload == path_name + elsif payload.eql?(path_name) fid = smb[:dir_id].to_i attribs = 0x10 # Ordinary Dir eof = 0 @@ -52,9 +52,9 @@ module Msf pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 pkt['Payload']['SMB'].v['WordCount'] = 42 pkt['Payload'].v['AndX'] = 0xff # no further commands - pkt['Payload'].v['OpLock'] = 0x3 # Grant Oplock on File + pkt['Payload'].v['OpLock'] = CONST::LEVEL_II_OPLOCK # Grant Oplock on File pkt['Payload'].v['FileID'] = fid - pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened + pkt['Payload'].v['Action'] = CONST::FILE_OPEN # The file existed and was opened pkt['Payload'].v['CreateTimeLow'] = lo pkt['Payload'].v['CreateTimeHigh'] = hi pkt['Payload'].v['AccessTimeLow'] = lo diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb index c6c72df8b6..dbbdf680da 100644 --- a/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb @@ -7,21 +7,32 @@ module Msf module Trans2 # This mixin provides methods to handle TRAN2_FIND_FIRST2 subcommands module FindFirst2 + def smb_cmd_trans2_find_first2(c, buff) + params = CONST::SMB_TRANS2_FIND_FIRST2_PARAMETERS.make_struct params.from_s(buff) loi = params.v['InformationLevel'] - file_name = Rex::Text.to_ascii(params.v['FileName']) - file_name.gsub!(/[\x00]*$/, '') #delete padding + search_path = Rex::Text.to_ascii(params.v['FileName']).downcase + search_path.gsub!(/[\x00]*/, '') #delete padding + search_path.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars + + # Do some dummy managing for wildcards + # TODO: improve + search_path.gsub!(/<\./, '*.') # manage wildcards + extension = File.extname(file_name) + if search_path == "#{path_name}*#{extension}" + search_path = "#{path_name}#{file_name}" + end case loi when CONST::SMB_FIND_FILE_NAMES_INFO - smb_cmd_find_file_names_info(c, file_name) + smb_cmd_find_file_names_info(c, search_path) when CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO - smb_cmd_find_file_both_directory_info(c, file_name) + smb_cmd_find_file_both_directory_info(c, search_path) when CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO - smb_cmd_find_file_full_directory_info(c, file_name) + smb_cmd_find_file_full_directory_info(c, search_path) else dprint("\t\tUnknown LOI [smb_cmd_trans2_find_first2] - #{loi}") # SEND success with the hope of going ahead... diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb index f7ef812332..fcd0890da5 100644 --- a/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb @@ -17,12 +17,10 @@ module Msf fid = params.v['FID'] case loi - when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS + when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS smb_cmd_trans_query_file_info_standard(c, fid) when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS smb_cmd_trans_query_file_info_basic(c, fid) - #when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO - #smb_cmd_trans_query_file_info_network(c, fid) else dprint("\t\tUnknown LOI [smb_cmd_trans2_query_file_information] - #{loi.to_s}") # SEND success with the hope of going ahead... diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb index 8243b2a572..5f36971ac2 100644 --- a/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb @@ -9,20 +9,22 @@ module Msf module QueryPathInformation def smb_cmd_trans2_query_path_information(c, buff) + params = CONST::SMB_TRANS2_QUERY_PATH_PARAMETERS.make_struct params.from_s(buff) loi = params.v['InformationLevel'] - file_name = params.v['FileName'] - file_name.gsub!(/[\x00]*$/, '') #delete padding + file_name = Rex::Text.to_ascii(params.v['FileName']).downcase + file_name.gsub!(/[\x00]*/, '') #delete padding + file_name.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars case loi + when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS + smb_cmd_trans_query_path_info_standard(c, file_name) when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS smb_cmd_trans_query_path_info_basic(c, file_name) - when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS - smb_cmd_trans_query_path_info_standard(c, file_name) - #when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO - #smb_cmd_trans_query_file_info_network(c, buff) + when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO + smb_cmd_trans_query_path_info_network(c, file_name) else dprint("\t\tUnknown LOI [smb_cmd_trans2_query_path_information] - #{loi.to_s}") # SEND success with the hope of going ahead... diff --git a/lib/msf/core/exploit/smb/server/share/information_level/query.rb b/lib/msf/core/exploit/smb/server/share/information_level/query.rb index d33198a158..dfb274f651 100644 --- a/lib/msf/core/exploit/smb/server/share/information_level/query.rb +++ b/lib/msf/core/exploit/smb/server/share/information_level/query.rb @@ -7,16 +7,59 @@ module Msf # This mixin provides methods to handle TRAN2_QUERY_PATH_INFORMATION subcommands module Query - # shortcut.. always send OBJECT_NAME_NOT_FOUND - def smb_cmd_trans_query_file_info_basic(c, buff) + # + # Responds to QUERY_PATH_INFO (Basic) requests + # + def smb_cmd_trans_query_file_info_basic(c, fid) + smb = @state[c] pkt = CONST::SMB_TRANS_RES_PKT.make_struct smb_set_defaults(c, pkt) + + if fid.eql?smb[:file_id].to_i + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL # File attributes => file + elsif fid.nil? || fid.empty? || fid == "\x00" # empty path + # QUERY_PATH_INFO_PARAMETERS doesn't include a file name, return a Directory answer + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY # File attributes => directory + else + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 + pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND # OBJECT_NAME_NOT_FOUND + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + c.put(pkt.to_s) + return + end + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + query_path_info.v['loCreationTime'] = lo + query_path_info.v['hiCreationTime'] = hi + query_path_info.v['loLastAccessTime'] = lo + query_path_info.v['hiLastAccessTime'] = hi + query_path_info.v['loLastWriteTime'] = lo + query_path_info.v['hiLastWriteTime'] = hi + query_path_info.v['loLastChangeTime'] = lo + query_path_info.v['hiLastChangeTime'] = hi + query_path_info.v['ExtFileAttributes'] = attrib + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND pkt['Payload']['SMB'].v['Flags1'] = FLAGS pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = 10 + pkt['Payload'].v['ParamCountTotal'] = trans2_params.to_s.length + pkt['Payload'].v['DataCountTotal'] = query_path_info.to_s.length + pkt['Payload'].v['ParamCount'] = trans2_params.to_s.length + pkt['Payload'].v['ParamOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + pkt['Payload'].v['DataCount'] = query_path_info.to_s.length + pkt['Payload'].v['DataOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + trans2_params.to_s.length + UNICODE_NULL_LENGTH + pkt['Payload'].v['Payload'] = + "\x00" + # Padding + trans2_params.to_s + + "\x00\x00" + # Padding + query_path_info.to_s + c.put(pkt.to_s) - return end # shortcut, we only have one file.... @@ -113,7 +156,6 @@ module Msf c.put(pkt.to_s) end - # # Responds to QUERY_PATH_INFO (Standard) requests # @@ -123,10 +165,8 @@ module Msf pkt = CONST::SMB_TRANS_RES_PKT.make_struct smb_set_defaults(c, pkt) - if path && path.ends_with?(file_name) #TODO: do it better + if path && path.include?(file_name) #TODO: do it better attrib = 0 # File attributes => file - elsif path && path.ends_with?(file_name + '.Local') - attrib = 1 # File attributes => file elsif path && path == path_name # QUERY_PATH_INFO_PARAMETERS doesn't include a file name, return a Directory answer attrib = 1 # File attributes => directory @@ -174,61 +214,68 @@ module Msf c.put(pkt.to_s) end -=begin # - # Responds to QUERY_FILE_INFO (Network) requests - # Is it needed? - def smb_cmd_trans_query_file_info_network(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) + # Responds to QUERY_PATH_INFO (Network Open) requests + # + # At the moment we just support '\\' path always send a SUCCESS... + def smb_cmd_trans_query_path_info_network(c, path) - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '').chomp.strip + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) - dprint("[smb_cmd_trans_query_file_info_network] Payload length: #{payload.length.to_s}") - dprint("[smb_cmd_trans_query_file_info_network] Payload is : #{payload.to_s}") - - if payload.length.to_s.eql?('4') - attrib = "\x10\x00\x00\x00" # File attributes => directory + if path && path.include?(file_name) #TODO: do it better + attrib = 0 # File attributes => file + elsif path && path == path_name + # QUERY_PATH_INFO_PARAMETERS doesn't include a file name, return a Directory answer + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY # File attributes => directory + elsif path.nil? || path.empty? || path == "\x00" # empty path + # QUERY_PATH_INFO_PARAMETERS doesn't include a file name, return a Directory answer + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY # File attributes => directory else - attrib = "\x80\x00\x00\x00" # File attributes => normal file + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 + pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND # OBJECT_NAME_NOT_FOUND + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + c.put(pkt.to_s) + return end + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct + query_path_info.v['loCreationTime'] = lo + query_path_info.v['hiCreationTime'] = hi + query_path_info.v['loLastAccessTime'] = lo + query_path_info.v['hiLastAccessTime'] = hi + query_path_info.v['loLastWriteTime'] = lo + query_path_info.v['hiLastWriteTime'] = hi + query_path_info.v['loLastChangeTime'] = lo + query_path_info.v['hiLastChangeTime'] = hi + query_path_info.v['AllocationSize'] = 1048576 + query_path_info.v['EndOfFile'] = exe_contents.length + query_path_info.v['ExtFileAttributes'] = attrib + pkt = CONST::SMB_TRANS_RES_PKT.make_struct smb_set_defaults(c, pkt) pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 + pkt['Payload']['SMB'].v['Flags1'] = FLAGS pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 56 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 56 - pkt['Payload'].v['DataOffset'] = 60 + pkt['Payload'].v['ParamCountTotal'] = trans2_params.to_s.length + pkt['Payload'].v['DataCountTotal'] = query_path_info.to_s.length + UNICODE_NULL_LENGTH + pkt['Payload'].v['ParamCount'] = trans2_params.to_s.length + pkt['Payload'].v['ParamOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + pkt['Payload'].v['DataCount'] = query_path_info.to_s.length + UNICODE_NULL_LENGTH + pkt['Payload'].v['DataOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + trans2_params.to_s.length + UNICODE_NULL_LENGTH pkt['Payload'].v['Payload'] = "\x00" + # Padding - # QUERY_PATH_INFO Parameters - "\x00\x00" + # EA Error Offset + trans2_params.to_s + "\x00\x00" + # Padding - # QUERY_PATH_INFO Data - [lo, hi].pack("VV") + # Created - [lo, hi].pack("VV") + # Last Access - [lo, hi].pack("VV") + # Last Write - [lo, hi].pack("VV") + # Change - "\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation Size = 1048576 || 1Mb - [exe_contents.length].pack("V") + "\x00\x00\x00\x00" + # End Of File - attrib + - "\x00\x00\x00\x00" # Unknown - - my_pkt = pkt.to_s - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + 24 - my_pkt[2, 2] = [original_length].pack("n") - new_length = my_pkt[2, 2].unpack("n").first + query_path_info.to_s c.put(pkt.to_s) end -=end end end end diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index 113e5b4446..dcaa4b512f 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -184,6 +184,19 @@ class Constants OPEN_SHARE_DENY_READEXEC = 0x30 OPEN_SHARE_DENY_NONE = 0x40 + # OpLock Levels + NO_OPLOCK = 0x00 + EXCLUSIVE_OPLOCK = 0x01 + BATCH_OPLOCK = 0x02 + LEVEL_II_OPLOCK = 0x03 + + # Dispositions, action to take if the file already exists or if the file is a new file and does not already exist + FILE_SUPERSEDE = 0x00000000 + FILE_OPEN = 0x00000001 + FILE_CREATE = 0x00000002 + FILE_OPEN_IF = 0x00000003 + FILE_OVERWRITE = 0x00000004 + FILE_OVERWRITE_IF = 0x00000005 # File Access OPEN_ACCESS_READ = 0x00 @@ -282,9 +295,10 @@ class Constants SMB_QUERY_FILE_BASIC_INFO_ALIAS = 0x3EC # alias for 0x101 SMB_SET_FILE_BASIC_INFO_ALIAS = 0x3EC # alias for 0x101 SMB_QUERY_FILE_STANDARD_INFO_ALIAS = 0x3ED # alias for 0x102 + SMB_QUERY_FILE_INTERNAL_INFO_ALIAS = 0x3EE # alias for 0x103 SMB_QUERY_FILE_EA_INFO_ALIAS = 0x3EF # alias for 0x103 SMB_QUERY_FILE_NAME_INFO_ALIAS = 0x3F1 # alias for 0x104 - SMB_QUERY_FILE_NETWORK_OPEN_INFO = 0x040a + SMB_QUERY_FILE_NETWORK_OPEN_INFO = 0x40A SMB_INFO_PASSTHROUGH = 0x1000 # SMB_COM_TRANSACTION2 MAX DATA COUNT information levels @@ -1282,6 +1296,24 @@ class Constants ['uint16v', 'EaErrorOffset', 0] ) + # A template for SMB_QUERY_FILE_NETWORK_INFO query path information level + SMB_QUERY_FILE_NETWORK_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'AllocationSize', 0], + ['uint64v', 'EndOfFile', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'Reserved', 0] + ) + + SMB_QUERY_FILE_NETWORK_INFO_HDR_LENGTH = 56 + # A template for SMB_QUERY_FILE_BASIC_INFO query path information level SMB_QUERY_FILE_BASIC_INFO_HDR = Rex::Struct2::CStructTemplate.new( ['uint32v', 'loCreationTime', 0],