diff --git a/lib/msf/core/exploit/smb/client.rb b/lib/msf/core/exploit/smb/client.rb index e0d6ed23ef..676dac0af9 100644 --- a/lib/msf/core/exploit/smb/client.rb +++ b/lib/msf/core/exploit/smb/client.rb @@ -78,7 +78,7 @@ module Msf # # @param (see Exploit::Remote::Tcp#connect) # @return (see Exploit::Remote::Tcp#connect) - def connect(global=true) + def connect(global=true, versions: [1]) disconnect() if global @@ -92,7 +92,7 @@ module Msf direct = false end - c = SIMPLE.new(s, direct) + c = SIMPLE.new(s, direct, versions) # setup pipe evasion foo if datastore['SMB::pipe_evasion'] diff --git a/lib/rex/proto/smb/simpleclient.rb b/lib/rex/proto/smb/simpleclient.rb index 4abcaffcd6..381620de0e 100644 --- a/lib/rex/proto/smb/simpleclient.rb +++ b/lib/rex/proto/smb/simpleclient.rb @@ -28,26 +28,31 @@ EVADE = Rex::Proto::SMB::Evasions attr_accessor :last_error, :server_max_buffer_size # Private accessors -attr_accessor :socket, :client, :direct, :shares, :last_share +attr_accessor :socket, :client, :direct, :shares, :last_share, :versions # Pass the socket object and a boolean indicating whether the socket is netbios or cifs - def initialize(socket, direct = false) + def initialize(socket, direct = false, versions = [1]) self.socket = socket self.direct = direct - - self.client = RubySMB::Client.new(RubySMB::Dispatcher::Socket.new(self.socket, read_timeout: 60), - username: '', - password: '')#Rex::Proto::SMB::Client.new(socket) - self.client.evasion_opts = { - # Padding is performed between packet headers and data - 'pad_data' => EVADE::EVASION_NONE, - # File path padding is performed on all open/create calls - 'pad_file' => EVADE::EVASION_NONE, - # Modify the \PIPE\ string in trans_named_pipe calls - 'obscure_trans_pipe' => EVADE::EVASION_NONE, - } + self.versions = versions self.shares = {} self.server_max_buffer_size = 1024 # 4356 (workstation) or 16644 (server) expected + + if self.versions.include?(2) + self.client = RubySMB::Client.new(RubySMB::Dispatcher::Socket.new(self.socket, read_timeout: 60), + username: '', + password: '')#Rex::Proto::SMB::Client.new(socket) + self.client.evasion_opts = { + # Padding is performed between packet headers and data + 'pad_data' => EVADE::EVASION_NONE, + # File path padding is performed on all open/create calls + 'pad_file' => EVADE::EVASION_NONE, + # Modify the \PIPE\ string in trans_named_pipe calls + 'obscure_trans_pipe' => EVADE::EVASION_NONE, + } + else + self.client = Rex::Proto::SMB::Client.new(socket) + end end def login(name = '', user = '', pass = '', domain = '', @@ -70,7 +75,11 @@ attr_accessor :socket, :client, :direct, :shares, :last_share self.client.send_ntlm = send_ntlm ok = self.client.negotiate - self.server_max_buffer_size = self.client.server_max_buffer_size + if self.versions.include?(2) + self.server_max_buffer_size = self.client.server_max_buffer_size + else + self.server_max_buffer_size = ok['Payload'].v['MaxBuff'] + end # Disable NTLMv2 Session for Windows 2000 (breaks authentication on some systems) # XXX: This in turn breaks SMB auth for Windows 2000 configured to enforce NTLMv2 @@ -85,7 +94,11 @@ attr_accessor :socket, :client, :direct, :shares, :last_share # always a string pass ||= '' - ok = self.client.session_setup(user, pass, domain, true) + if self.versions.include?(2) + ok = self.client.session_setup(user, pass, domain, true) + else + ok = self.client.session_setup(user, pass, domain) + end rescue ::Interrupt raise $! rescue ::Exception => e @@ -149,7 +162,11 @@ attr_accessor :socket, :client, :direct, :shares, :last_share def connect(share) ok = self.client.tree_connect(share) - tree_id = ok.id + if self.versions.include?(2) + tree_id = ok.id + else + tree_id = ok['Payload']['SMB'].v['TreeID'] + end self.shares[share] = tree_id self.last_share = share @@ -164,27 +181,34 @@ attr_accessor :socket, :client, :direct, :shares, :last_share false end - def open(path, perm, chunk_size = 48000, read: true, write: false) - mode = 0 - perm.each_byte { |c| - case [c].pack('C').downcase - when 'x', 'c' - mode |= RubySMB::Dispositions::FILE_CREATE - when 'o' - mode |= RubySMB::Dispositions::FILE_OPEN - when 's' - mode |= RubySMB::Dispositions::FILE_SUPERSEDE - end - } + if self.versions.include?(2) + mode = 0 + perm.each_byte { |c| + case [c].pack('C').downcase + when 'x', 'c' + mode |= RubySMB::Dispositions::FILE_CREATE + when 'o' + mode |= RubySMB::Dispositions::FILE_OPEN + when 's' + mode |= RubySMB::Dispositions::FILE_SUPERSEDE + end + } - if write - ok = self.client.open(path, mode, read: true, write: true) + if write + file_id = self.client.open(path, mode, read: true, write: true) + else + file_id = self.client.open(path, mode, read: true) + end else - ok = self.client.open(path, mode, read: true) + mode = UTILS.open_mode_to_mode(perm) + access = UTILS.open_mode_to_access(perm) + + ok = self.client.open(path, mode, access) + file_id = ok['Payload'].v['FileID'] end - fh = OpenFile.new(self.client, path, self.client.last_tree_id, ok) + fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id, self.versions) fh.chunk_size = chunk_size fh end @@ -195,8 +219,15 @@ attr_accessor :socket, :client, :direct, :shares, :last_share def create_pipe(path, perm = 'c') disposition = UTILS.create_mode_to_disposition(perm) - file_id = self.client.create_pipe(path, disposition) - fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id) + ok = self.client.create_pipe(path, disposition) + + if self.versions.include?(2) + file_id = ok + else + file_id = ok['Payload'].v['FileID'] + end + + fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id, self.versions) end def trans_pipe(fid, data, no_response = nil) diff --git a/lib/rex/proto/smb/simpleclient/open_file.rb b/lib/rex/proto/smb/simpleclient/open_file.rb index 6c028ed1ab..cb9ef925cd 100644 --- a/lib/rex/proto/smb/simpleclient/open_file.rb +++ b/lib/rex/proto/smb/simpleclient/open_file.rb @@ -5,14 +5,15 @@ module SMB class SimpleClient class OpenFile - attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size + attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size, :versions - def initialize(client, name, tree_id, file_id) + def initialize(client, name, tree_id, file_id, versions) self.client = client self.name = name self.tree_id = tree_id self.file_id = file_id self.chunk_size = 48000 + self.versions = versions end def delete @@ -30,34 +31,73 @@ class OpenFile # Read data from the file def read(length = nil, offset = 0) - if (length == nil) - data = '' - max_size = self.client.open_files[self.client.last_file_id].size - fptr = offset + if self.versions.include?(2) + if (length == nil) + data = '' + max_size = self.client.open_files[self.client.last_file_id].size + fptr = offset - if max_size < self.chunk_size - chunk = max_size - else - chunk = self.chunk_size - end - - ok = self.client.read(self.file_id, fptr, chunk) - data << ok.pack('C*') - fptr = data.length - - while (ok && data.length < max_size) - if (max_size - data.length) < chunk - chunk = max_size - data.length + if max_size < self.chunk_size + chunk = max_size + else + chunk = self.chunk_size end + ok = self.client.read(self.file_id, fptr, chunk) data << ok.pack('C*') fptr = data.length + + while (ok && data.length < max_size) + if (max_size - data.length) < chunk + chunk = max_size - data.length + end + ok = self.client.read(self.file_id, fptr, chunk) + data << ok.pack('C*') + fptr = data.length + end + return data + else + ok = self.client.read(self.file_id, offset, length) + data = ok.pack('C*') + return data end - return data else - ok = self.client.read(self.file_id, offset, length) - data = ok.pack('C*') - return data + if (length == nil) + data = '' + fptr = offset + ok = self.client.read(self.file_id, fptr, self.chunk_size) + while (ok and ok['Payload'].v['DataLenLow'] > 0) + buff = ok.to_s.slice( + ok['Payload'].v['DataOffset'] + 4, + ok['Payload'].v['DataLenLow'] + ) + data << buff + if ok['Payload'].v['Remaining'] == 0 + break + end + fptr += ok['Payload'].v['DataLenLow'] + + begin + ok = self.client.read(self.file_id, fptr, self.chunk_size) + rescue XCEPT::ErrorCode => e + case e.error_code + when 0x00050001 + # Novell fires off an access denied error on EOF + ok = nil + else + raise e + end + end + end + return data + else + ok = self.client.read(self.file_id, offset, length) + data = ok.to_s.slice( + ok['Payload'].v['DataOffset'] + 4, + ok['Payload'].v['DataLenLow'] + ) + return data + end end end @@ -79,16 +119,15 @@ class OpenFile # Keep writing data until we run out while (chunk.length > 0) ok = self.client.write(self.file_id, fptr, chunk) - cl = ok + if self.versions.include?(2) + cl = ok + else + cl = ok['Payload'].v['CountLow'] + end # Partial write, push the failed data back into the queue if (cl != chunk.length) - begin - data = chunk.slice(cl - 1, chunk.length - cl) + data - rescue - self.write(data, offset) - return - end + data = chunk.slice(cl - 1, chunk.length - cl) + data end # Increment our painter and grab the next chunk diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index ef9e4f66b5..f105ad8382 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -40,7 +40,7 @@ class MetasploitModule < Msf::Auxiliary def smb_download vprint_status("Connecting...") - connect() + connect(versions: [1, 2]) smb_login() vprint_status("#{peer}: Mounting the remote share \\\\#{rhost}\\#{datastore['SMBSHARE']}'...") diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index 9cef6538e5..aca135b323 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -46,7 +46,7 @@ class MetasploitModule < Msf::Auxiliary def run_host(_ip) begin vprint_status("Connecting to the server...") - connect() + connect(versions: [1, 2]) smb_login() vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...")