Land #9986, initial ruby_smb simple client integration

4.x
Brent Cook 2018-05-07 14:02:22 -05:00 committed by Metasploit
parent 0901f35f9c
commit 78f546ce81
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
15 changed files with 1072 additions and 849 deletions

View File

@ -17,6 +17,10 @@ Metrics/ClassLength:
Exclude: Exclude:
- 'modules/**/*' - 'modules/**/*'
Style/ClassAndModuleChildren:
Enabled: false
Description: 'Forced nesting is harmful for grepping and general code comprehension'
Metrics/AbcSize: Metrics/AbcSize:
Enabled: false Enabled: false
Description: 'This is often a red-herring' Description: 'This is often a red-herring'
@ -41,6 +45,10 @@ Style/RedundantReturn:
Description: 'This often looks weird when mixed with actual returns, and hurts nothing' Description: 'This often looks weird when mixed with actual returns, and hurts nothing'
Enabled: false Enabled: false
Style/NumericPredicate:
Description: 'This adds no efficiency nor space saving'
Enabled: false
Style/Documentation: Style/Documentation:
Enabled: true Enabled: true
Description: 'Most Metasploit modules do not have class documentation.' Description: 'Most Metasploit modules do not have class documentation.'
@ -109,6 +117,10 @@ Style/WordArray:
Enabled: false Enabled: false
Description: 'Metasploit prefers consistent use of []' Description: 'Metasploit prefers consistent use of []'
Style/IfUnlessModifier:
Enabled: false
Description: 'This style might save a couple of lines, but often makes code less clear'
Style/RedundantBegin: Style/RedundantBegin:
Exclude: Exclude:
# this pattern is very common and somewhat unavoidable # this pattern is very common and somewhat unavoidable

View File

@ -16,11 +16,6 @@ module Msf
include Msf::Exploit::Remote::Tcp include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::NTLM::Client include Msf::Exploit::Remote::NTLM::Client
SIMPLE = Rex::Proto::SMB::SimpleClient
XCEPT = Rex::Proto::SMB::Exceptions
CONST = Rex::Proto::SMB::Constants
# Alias over the Rex DCERPC protocol modules # Alias over the Rex DCERPC protocol modules
DCERPCPacket = Rex::Proto::DCERPC::Packet DCERPCPacket = Rex::Proto::DCERPC::Packet
DCERPCClient = Rex::Proto::DCERPC::Client DCERPCClient = Rex::Proto::DCERPC::Client
@ -78,7 +73,7 @@ module Msf
# #
# @param (see Exploit::Remote::Tcp#connect) # @param (see Exploit::Remote::Tcp#connect)
# @return (see Exploit::Remote::Tcp#connect) # @return (see Exploit::Remote::Tcp#connect)
def connect(global=true) def connect(global=true, versions: [1])
disconnect() if global disconnect() if global
@ -92,7 +87,7 @@ module Msf
direct = false direct = false
end end
c = SIMPLE.new(s, direct) c = Rex::Proto::SMB::SimpleClient.new(s, direct, versions)
# setup pipe evasion foo # setup pipe evasion foo
if datastore['SMB::pipe_evasion'] if datastore['SMB::pipe_evasion']
@ -218,8 +213,8 @@ module Msf
# @raise [Rex::Proto::SMB::Exceptions::ErrorCode] # @raise [Rex::Proto::SMB::Exceptions::ErrorCode]
def smb_file_exist?(file) def smb_file_exist?(file)
begin begin
fd = simple.open(file, 'ro') fd = simple.open(file, 'o')
rescue XCEPT::ErrorCode => e rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
# If attempting to open the file results in a "*_NOT_FOUND" error, # If attempting to open the file results in a "*_NOT_FOUND" error,
# then we can be sure the file is not there. # then we can be sure the file is not there.
# #

View File

@ -75,7 +75,7 @@ module Exploit::Remote::SMB::Client::Psexec
def smb_read_file(smbshare, host, file) def smb_read_file(smbshare, host, file)
begin begin
simple.connect("\\\\#{host}\\#{smbshare}") simple.connect("\\\\#{host}\\#{smbshare}")
file = simple.open(file, 'ro') file = simple.open(file, 'o')
contents = file.read contents = file.read
file.close file.close
simple.disconnect("\\\\#{host}\\#{smbshare}") simple.disconnect("\\\\#{host}\\#{smbshare}")
@ -267,7 +267,7 @@ module Exploit::Remote::SMB::Client::Psexec
begin begin
psexec(command) psexec(command)
rescue StandardError => exec_command_error rescue StandardError => exec_command_error
fail_with(Msf::Exploit::Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}") fail_with(Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}")
end end
end end

View File

@ -119,16 +119,16 @@ class Client
NDR.long(default_opts[:password4]) NDR.long(default_opts[:password4])
begin begin
response = dcerpc_client.call(CREATE_SERVICE_W, stubdata) response = dcerpc_client.call(CREATE_SERVICE_W, stubdata)
rescue Rex::Proto::DCERPC::Exceptions::Fault => e
print_error("Error creating service: #{e}")
end
if response if response
svc_status = error_code(response[24,4]) svc_status = error_code(response[24,4])
if svc_status == ERROR_SUCCESS if svc_status == ERROR_SUCCESS
svc_handle = response[4,20] svc_handle = response[4,20]
end end
end end
rescue Rex::Proto::DCERPC::Exceptions::Fault => e
print_error("Error creating service: #{e}")
end
return svc_handle, svc_status return svc_handle, svc_status
end end
@ -169,7 +169,7 @@ class Client
begin begin
response = dcerpc_client.call(CLOSE_SERVICE_HANDLE, handle) response = dcerpc_client.call(CLOSE_SERVICE_HANDLE, handle)
if response if response
svc_status = error_code(response[20,4]) svc_status = error_code(response)
end end
rescue Rex::Proto::DCERPC::Exceptions::Fault => e rescue Rex::Proto::DCERPC::Exceptions::Fault => e
print_error("Error closing service handle: #{e}") print_error("Error closing service handle: #{e}")

View File

@ -3,15 +3,9 @@ require 'rex/proto/ntlm/constants'
require 'rex/proto/ntlm/crypt' require 'rex/proto/ntlm/crypt'
require 'rex/proto/ntlm/exceptions' require 'rex/proto/ntlm/exceptions'
module Rex module Rex::Proto::NTLM
module Proto
module NTLM
class Utils class Utils
CONST = Rex::Proto::NTLM::Constants
CRYPT = Rex::Proto::NTLM::Crypt
XCEPT = Rex::Proto::NTLM::Exceptions
# duplicate from lib/rex/proto/smb/utils cause we only need this fonction from Rex::Proto::SMB::Utils # duplicate from lib/rex/proto/smb/utils cause we only need this fonction from Rex::Proto::SMB::Utils
# Convert a unix timestamp to a 64-bit signed server time # Convert a unix timestamp to a 64-bit signed server time
def self.time_unix_to_smb(unix_time) def self.time_unix_to_smb(unix_time)
@ -353,7 +347,7 @@ class Utils
if usentlm2_session if usentlm2_session
if use_ntlmv2 if use_ntlmv2
#set Negotiate Target Info #set Negotiate Target Info
ntlmssp_flags |= CONST::NEGOTIATE_TARGET_INFO ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_TARGET_INFO
end end
else else
@ -361,7 +355,7 @@ class Utils
ntlmssp_flags &= 0xfff7ffff ntlmssp_flags &= 0xfff7ffff
#set lanmanflag only when lm and ntlm are sent #set lanmanflag only when lm and ntlm are sent
if send_lm if send_lm
ntlmssp_flags |= CONST::NEGOTIATE_LMKEY if use_lanman_key ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_LMKEY if use_lanman_key
end end
end end
@ -379,7 +373,7 @@ class Utils
cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00") cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00")
if not cidx if not cidx
raise XCEPT::NTLMMissingChallenge raise Rex::Proto::NTLM::Exceptions::NTLMMissingChallenge
end end
data[:challenge_key] = blob[cidx + 24, 8] data[:challenge_key] = blob[cidx + 24, 8]
@ -501,12 +495,14 @@ class Utils
if send_ntlm #should be default if send_ntlm #should be default
if usentlm2_session if usentlm2_session
if use_ntlmv2 if use_ntlmv2
ntlm_cli_challenge = self.make_ntlmv2_clientchallenge(default_domain, default_name, dns_domain_name, ntlm_cli_challenge = self.make_ntlmv2_clientchallenge(
default_domain, default_name, dns_domain_name,
dns_host_name,client_challenge, dns_host_name,client_challenge,
chall_MsvAvTimestamp, spnopt) chall_MsvAvTimestamp, spnopt)
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
argntlm = { argntlm = {
:ntlmv2_hash => CRYPT::ntlmv2_hash( :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(
user, user,
[ pass.upcase()[33,65] ].pack('H32'), [ pass.upcase()[33,65] ].pack('H32'),
domain,{:pass_is_hash => true} domain,{:pass_is_hash => true}
@ -515,19 +511,19 @@ class Utils
} }
else else
argntlm = { argntlm = {
:ntlmv2_hash => CRYPT::ntlmv2_hash(user, pass, domain), :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(user, pass, domain),
:challenge => challenge_key :challenge => challenge_key
} }
end end
optntlm = { :nt_client_challenge => ntlm_cli_challenge} optntlm = { :nt_client_challenge => ntlm_cli_challenge}
ntlmv2_response = CRYPT::ntlmv2_response(argntlm,optntlm) ntlmv2_response = Rex::Proto::NTLM::Crypt::ntlmv2_response(argntlm,optntlm)
resp_ntlm = ntlmv2_response resp_ntlm = ntlmv2_response
if send_lm if send_lm
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
arglm = { arglm = {
:ntlmv2_hash => CRYPT::ntlmv2_hash( :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(
user, user,
[ pass.upcase()[33,65] ].pack('H32'), [ pass.upcase()[33,65] ].pack('H32'),
domain,{:pass_is_hash => true} domain,{:pass_is_hash => true}
@ -536,13 +532,13 @@ class Utils
} }
else else
arglm = { arglm = {
:ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain), :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(user,pass, domain),
:challenge => challenge_key :challenge => challenge_key
} }
end end
optlm = { :client_challenge => client_challenge } optlm = { :client_challenge => client_challenge }
resp_lm = CRYPT::lmv2_response(arglm, optlm) resp_lm = Rex::Proto::NTLM::Crypt::lmv2_response(arglm, optlm)
else else
resp_lm = "\x00" * 24 resp_lm = "\x00" * 24
end end
@ -555,13 +551,13 @@ class Utils
} }
else else
argntlm = { argntlm = {
:ntlm_hash => CRYPT::ntlm_hash(pass), :ntlm_hash => Rex::Proto::NTLM::Crypt::ntlm_hash(pass),
:challenge => challenge_key :challenge => challenge_key
} }
end end
optntlm = { :client_challenge => client_challenge} optntlm = { :client_challenge => client_challenge}
resp_ntlm = CRYPT::ntlm2_session(argntlm,optntlm).join[24,24] resp_ntlm = Rex::Proto::NTLM::Crypt::ntlm2_session(argntlm,optntlm).join[24,24]
# Generate the fake LANMAN hash # Generate the fake LANMAN hash
resp_lm = client_challenge + ("\x00" * 16) resp_lm = client_challenge + ("\x00" * 16)
@ -575,12 +571,12 @@ class Utils
} }
else else
argntlm = { argntlm = {
:ntlm_hash => CRYPT::ntlm_hash(pass), :ntlm_hash => Rex::Proto::NTLM::Crypt::ntlm_hash(pass),
:challenge => challenge_key :challenge => challenge_key
} }
end end
resp_ntlm = CRYPT::ntlm_response(argntlm) resp_ntlm = Rex::Proto::NTLM::Crypt::ntlm_response(argntlm)
if send_lm if send_lm
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
arglm = { arglm = {
@ -589,11 +585,11 @@ class Utils
} }
else else
arglm = { arglm = {
:lm_hash => CRYPT::lm_hash(pass), :lm_hash => Rex::Proto::NTLM::Crypt::lm_hash(pass),
:challenge => challenge_key :challenge => challenge_key
} }
end end
resp_lm = CRYPT::lm_response(arglm) resp_lm = Rex::Proto::NTLM::Crypt::lm_response(arglm)
else else
#when windows does not send lm in ntlmv1 type response, #when windows does not send lm in ntlmv1 type response,
# it gives lm response the same value as ntlm response # it gives lm response the same value as ntlm response
@ -605,7 +601,7 @@ class Utils
if usentlm2_session && use_ntlmv2 if usentlm2_session && use_ntlmv2
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
arglm = { arglm = {
:ntlmv2_hash => CRYPT::ntlmv2_hash( :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(
user, user,
[ pass.upcase()[33,65] ].pack('H32'), [ pass.upcase()[33,65] ].pack('H32'),
domain,{:pass_is_hash => true} domain,{:pass_is_hash => true}
@ -614,12 +610,12 @@ class Utils
} }
else else
arglm = { arglm = {
:ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain), :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(user,pass, domain),
:challenge => challenge_key :challenge => challenge_key
} }
end end
optlm = { :client_challenge => client_challenge } optlm = { :client_challenge => client_challenge }
resp_lm = CRYPT::lmv2_response(arglm, optlm) resp_lm = Rex::Proto::NTLM::Crypt::lmv2_response(arglm, optlm)
else else
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
arglm = { arglm = {
@ -628,11 +624,11 @@ class Utils
} }
else else
arglm = { arglm = {
:lm_hash => CRYPT::lm_hash(pass), :lm_hash => Rex::Proto::NTLM::Crypt::lm_hash(pass),
:challenge => challenge_key :challenge => challenge_key
} }
end end
resp_lm = CRYPT::lm_response(arglm) resp_lm = Rex::Proto::NTLM::Crypt::lm_response(arglm)
end end
resp_ntlm = "" resp_ntlm = ""
end end
@ -664,20 +660,20 @@ class Utils
# Remove ntlmssp.negotiate128 # Remove ntlmssp.negotiate128
ntlmssp_flags &= 0xdfffffff ntlmssp_flags &= 0xdfffffff
# Check the keyexchange # Check the keyexchange
if server_ntlmssp_flags & CONST::NEGOTIATE_KEY_EXCH != 0 then if server_ntlmssp_flags & Rex::Proto::NTLM::Constants::NEGOTIATE_KEY_EXCH != 0 then
key_exchange = true key_exchange = true
ntlmssp_flags |= CONST::NEGOTIATE_KEY_EXCH ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_KEY_EXCH
end end
# Check 128bits # Check 128bits
if server_ntlmssp_flags & CONST::NEGOTIATE_128 != 0 then if server_ntlmssp_flags & Rex::Proto::NTLM::Constants::NEGOTIATE_128 != 0 then
key_size = 128 key_size = 128
ntlmssp_flags |= CONST::NEGOTIATE_128 ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_128
ntlmssp_flags |= CONST::NEGOTIATE_56 ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_56
# Check 56bits # Check 56bits
else else
if server_ntlmssp_flags & CONST::NEGOTIATE_56 != 0 then if server_ntlmssp_flags & Rex::Proto::NTLM::Constants::NEGOTIATE_56 != 0 then
key_size = 56 key_size = 56
ntlmssp_flags |= CONST::NEGOTIATE_56 ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_56
end end
end end
# Generate the user session key # Generate the user session key
@ -686,23 +682,23 @@ class Utils
if usentlm2_session if usentlm2_session
if use_ntlmv2 if use_ntlmv2
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
user_session_key = CRYPT::ntlmv2_user_session_key(user, user_session_key = Rex::Proto::NTLM::Crypt::ntlmv2_user_session_key(user,
[ pass.upcase()[33,65] ].pack('H32'), [ pass.upcase()[33,65] ].pack('H32'),
domain, domain,
challenge_key, ntlm_cli_challenge, challenge_key, ntlm_cli_challenge,
{:pass_is_hash => true}) {:pass_is_hash => true})
else else
user_session_key = CRYPT::ntlmv2_user_session_key(user, pass, domain, user_session_key = Rex::Proto::NTLM::Crypt::ntlmv2_user_session_key(user, pass, domain,
challenge_key, ntlm_cli_challenge) challenge_key, ntlm_cli_challenge)
end end
else else
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
user_session_key = CRYPT::ntlm2_session_user_session_key([ pass.upcase()[33,65] ].pack('H32'), user_session_key = Rex::Proto::NTLM::Crypt::ntlm2_session_user_session_key([ pass.upcase()[33,65] ].pack('H32'),
challenge_key, challenge_key,
client_challenge, client_challenge,
{:pass_is_hash => true}) {:pass_is_hash => true})
else else
user_session_key = CRYPT::ntlm2_session_user_session_key(pass, challenge_key, user_session_key = Rex::Proto::NTLM::Crypt::ntlm2_session_user_session_key(pass, challenge_key,
client_challenge) client_challenge)
end end
end end
@ -711,63 +707,56 @@ class Utils
# so we don't care about this feature # so we don't care about this feature
if send_lm && use_lanman_key if send_lm && use_lanman_key
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
user_session_key = CRYPT::lanman_session_key([ pass.upcase()[0,32] ].pack('H32'), user_session_key = Rex::Proto::NTLM::Crypt::lanman_session_key([ pass.upcase()[0,32] ].pack('H32'),
challenge_key, challenge_key,
{:pass_is_hash => true}) {:pass_is_hash => true})
else else
user_session_key = CRYPT::lanman_session_key(pass, challenge_key) user_session_key = Rex::Proto::NTLM::Crypt::lanman_session_key(pass, challenge_key)
end end
lanman_weak = true lanman_weak = true
else else
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
user_session_key = CRYPT::ntlmv1_user_session_key([ pass.upcase()[33,65] ].pack('H32'), user_session_key = Rex::Proto::NTLM::Crypt::ntlmv1_user_session_key([ pass.upcase()[33,65] ].pack('H32'),
{:pass_is_hash => true}) {:pass_is_hash => true})
else else
user_session_key = CRYPT::ntlmv1_user_session_key(pass) user_session_key = Rex::Proto::NTLM::Crypt::ntlmv1_user_session_key(pass)
end end
end end
end end
else else
if usentlm2_session && use_ntlmv2 if usentlm2_session && use_ntlmv2
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
user_session_key = CRYPT::lmv2_user_session_key(user, [ pass.upcase()[33,65] ].pack('H32'), user_session_key = Rex::Proto::NTLM::Crypt::lmv2_user_session_key(user, [ pass.upcase()[33,65] ].pack('H32'),
domain, domain,
challenge_key, client_challenge, challenge_key, client_challenge,
{:pass_is_hash => true}) {:pass_is_hash => true})
else else
user_session_key = CRYPT::lmv2_user_session_key(user, pass, domain, user_session_key = Rex::Proto::NTLM::Crypt::lmv2_user_session_key(user, pass, domain,
challenge_key, client_challenge) challenge_key, client_challenge)
end end
else else
if self.is_pass_ntlm_hash?(pass) if self.is_pass_ntlm_hash?(pass)
user_session_key = CRYPT::lmv1_user_session_key([ pass.upcase()[0,32] ].pack('H32'), user_session_key = Rex::Proto::NTLM::Crypt::lmv1_user_session_key([ pass.upcase()[0,32] ].pack('H32'),
{:pass_is_hash => true}) {:pass_is_hash => true})
else else
user_session_key = CRYPT::lmv1_user_session_key(pass) user_session_key = Rex::Proto::NTLM::Crypt::lmv1_user_session_key(pass)
end end
end end
end end
user_session_key = CRYPT::make_weak_sessionkey(user_session_key,key_size, lanman_weak) user_session_key = Rex::Proto::NTLM::Crypt::make_weak_sessionkey(user_session_key,key_size, lanman_weak)
# Sessionkey and encrypted session key # Sessionkey and encrypted session key
if key_exchange if key_exchange
signing_key = Rex::Text.rand_text(16) signing_key = Rex::Text.rand_text(16)
enc_session_key = CRYPT::encrypt_sessionkey(signing_key, user_session_key) enc_session_key = Rex::Proto::NTLM::Crypt::encrypt_sessionkey(signing_key, user_session_key)
else else
signing_key = user_session_key signing_key = user_session_key
end end
return signing_key, enc_session_key, ntlmssp_flags return signing_key, enc_session_key, ntlmssp_flags
end
end
end end
end end
end end

View File

@ -2,6 +2,7 @@
module Rex module Rex
module Proto module Proto
module SMB module SMB
class SimpleClient class SimpleClient
require 'rex/text' require 'rex/text'
@ -14,6 +15,7 @@ require 'rex/proto/smb/utils'
require 'rex/proto/smb/client' require 'rex/proto/smb/client'
require 'rex/proto/smb/simpleclient/open_file' require 'rex/proto/smb/simpleclient/open_file'
require 'rex/proto/smb/simpleclient/open_pipe' require 'rex/proto/smb/simpleclient/open_pipe'
require 'ruby_smb'
# Some short-hand class aliases # Some short-hand class aliases
CONST = Rex::Proto::SMB::Constants CONST = Rex::Proto::SMB::Constants
@ -26,15 +28,31 @@ EVADE = Rex::Proto::SMB::Evasions
attr_accessor :last_error, :server_max_buffer_size attr_accessor :last_error, :server_max_buffer_size
# Private accessors # 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 # 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.socket = socket
self.direct = direct self.direct = direct
self.client = Rex::Proto::SMB::Client.new(socket) self.versions = versions
self.shares = {} self.shares = {}
self.server_max_buffer_size = 1024 # 4356 (workstation) or 16644 (server) expected 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 end
def login(name = '', user = '', pass = '', domain = '', def login(name = '', user = '', pass = '', domain = '',
@ -57,7 +75,11 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
self.client.send_ntlm = send_ntlm self.client.send_ntlm = send_ntlm
ok = self.client.negotiate ok = self.client.negotiate
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'] self.server_max_buffer_size = ok['Payload'].v['MaxBuff']
end
# Disable NTLMv2 Session for Windows 2000 (breaks authentication on some systems) # 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 # XXX: This in turn breaks SMB auth for Windows 2000 configured to enforce NTLMv2
@ -72,7 +94,11 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
# always a string # always a string
pass ||= '' pass ||= ''
if self.versions.include?(2)
ok = self.client.session_setup(user, pass, domain, true)
else
ok = self.client.session_setup(user, pass, domain) ok = self.client.session_setup(user, pass, domain)
end
rescue ::Interrupt rescue ::Interrupt
raise $! raise $!
rescue ::Exception => e rescue ::Exception => e
@ -135,7 +161,13 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
def connect(share) def connect(share)
ok = self.client.tree_connect(share) ok = self.client.tree_connect(share)
if self.versions.include?(2)
tree_id = ok.id
else
tree_id = ok['Payload']['SMB'].v['TreeID'] tree_id = ok['Payload']['SMB'].v['TreeID']
end
self.shares[share] = tree_id self.shares[share] = tree_id
self.last_share = share self.last_share = share
end end
@ -149,14 +181,34 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
false false
end end
def open(path, perm, chunk_size = 48000, read: true, write: false)
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
}
def open(path, perm, chunk_size = 48000) 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
mode = UTILS.open_mode_to_mode(perm) mode = UTILS.open_mode_to_mode(perm)
access = UTILS.open_mode_to_access(perm) access = UTILS.open_mode_to_access(perm)
ok = self.client.open(path, mode, access) ok = self.client.open(path, mode, access)
file_id = ok['Payload'].v['FileID'] file_id = ok['Payload'].v['FileID']
fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id) end
fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id, self.versions)
fh.chunk_size = chunk_size fh.chunk_size = chunk_size
fh fh
end end
@ -168,8 +220,14 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
def create_pipe(path, perm = 'c') def create_pipe(path, perm = 'c')
disposition = UTILS.create_mode_to_disposition(perm) disposition = UTILS.create_mode_to_disposition(perm)
ok = self.client.create_pipe(path, disposition) ok = self.client.create_pipe(path, disposition)
if self.versions.include?(2)
file_id = ok
else
file_id = ok['Payload'].v['FileID'] file_id = ok['Payload'].v['FileID']
fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id) end
fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id, self.versions)
end end
def trans_pipe(fid, data, no_response = nil) def trans_pipe(fid, data, no_response = nil)

View File

@ -1,52 +1,74 @@
# -*- coding: binary -*- # -*- coding: binary -*-
module Rex module Rex::Proto::SMB
module Proto
module SMB
class SimpleClient class SimpleClient
#
# This represents an open file, which can be read, written, or closed
#
class OpenFile 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.client = client
self.name = name self.name = name
self.tree_id = tree_id self.tree_id = tree_id
self.file_id = file_id self.file_id = file_id
self.chunk_size = 48000 self.chunk_size = 48000
self.versions = versions
end end
def delete def delete
begin begin
self.close close
rescue rescue StandardError
end end
self.client.delete(self.name, self.tree_id) client.delete(name, tree_id)
end end
# Close this open file # Close this open file
def close def close
self.client.close(self.file_id, self.tree_id) client.close(file_id, tree_id)
end end
# Read data from the file def read_ruby_smb(length, offset)
def read(length = nil, offset = 0) if length.nil?
if (length == nil) max_size = client.open_files[client.last_file_id].size
fptr = offset
chunk = [max_size, chunk_size].min
data = client.read(file_id, fptr, chunk).pack('C*')
fptr = data.length
while data.length < max_size
if (max_size - data.length) < chunk
chunk = max_size - data.length
end
data << client.read(file_id, fptr, chunk).pack('C*')
fptr = data.length
end
else
data = client.read(file_id, offset, length).pack('C*')
end
data
end
def read_rex_smb(length, offset)
if length.nil?
data = '' data = ''
fptr = offset fptr = offset
ok = self.client.read(self.file_id, fptr, self.chunk_size) ok = client.read(file_id, fptr, chunk_size)
while (ok and ok['Payload'].v['DataLenLow'] > 0) while ok && ok['Payload'].v['DataLenLow'] > 0
buff = ok.to_s.slice( buff = ok.to_s.slice(
ok['Payload'].v['DataOffset'] + 4, ok['Payload'].v['DataOffset'] + 4,
ok['Payload'].v['DataLenLow'] ok['Payload'].v['DataLenLow']
) )
data << buff data << buff
if ok['Payload'].v['Remaining'] == 0 break if ok['Payload'].v['Remaining'] == 0
break
end
fptr += ok['Payload'].v['DataLenLow'] fptr += ok['Payload'].v['DataLenLow']
begin begin
ok = self.client.read(self.file_id, fptr, self.chunk_size) ok = client.read(file_id, fptr, chunk_size)
rescue XCEPT::ErrorCode => e rescue XCEPT::ErrorCode => e
case e.error_code case e.error_code
when 0x00050001 when 0x00050001
@ -57,20 +79,27 @@ class OpenFile
end end
end end
end end
return data
else else
ok = self.client.read(self.file_id, offset, length) ok = client.read(file_id, offset, length)
data = ok.to_s.slice( data = ok.to_s.slice(
ok['Payload'].v['DataOffset'] + 4, ok['Payload'].v['DataOffset'] + 4,
ok['Payload'].v['DataLenLow'] ok['Payload'].v['DataLenLow']
) )
return data end
data
end
# Read data from the file
def read(length = nil, offset = 0)
if versions.include?(2)
read_ruby_smb(length, offset)
else
read_rex_smb(length, offset)
end end
end end
def <<(data) def <<(data)
self.write(data) write(data)
end end
# Write data to the file # Write data to the file
@ -82,23 +111,25 @@ class OpenFile
data = data.dup data = data.dup
# Take our first chunk of bytes # Take our first chunk of bytes
chunk = data.slice!(0, self.chunk_size) chunk = data.slice!(0, chunk_size)
# Keep writing data until we run out # Keep writing data until we run out
while (chunk.length > 0) until chunk.empty?
ok = self.client.write(self.file_id, fptr, chunk) ok = client.write(file_id, fptr, chunk)
if versions.include?(2)
cl = ok
else
cl = ok['Payload'].v['CountLow'] cl = ok['Payload'].v['CountLow']
end
# Partial write, push the failed data back into the queue # Partial write, push the failed data back into the queue
if (cl != chunk.length) if cl != chunk.length
data = chunk.slice(cl - 1, chunk.length - cl) + data data = chunk.slice(cl - 1, chunk.length - cl) + data
end end
# Increment our painter and grab the next chunk # Increment our painter and grab the next chunk
fptr += cl fptr += cl
chunk = data.slice!(0, self.chunk_size) chunk = data.slice!(0, chunk_size)
end
end
end end
end end
end end

View File

@ -12,12 +12,6 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner include Msf::Auxiliary::Scanner
# Aliases for common classes
SIMPLE = Rex::Proto::SMB::SimpleClient
XCEPT = Rex::Proto::SMB::Exceptions
CONST = Rex::Proto::SMB::Constants
def initialize def initialize
super( super(
'Name' => 'SMB File Download Utility', 'Name' => 'SMB File Download Utility',
@ -40,7 +34,7 @@ class MetasploitModule < Msf::Auxiliary
def smb_download def smb_download
vprint_status("Connecting...") vprint_status("Connecting...")
connect() connect(versions: [1, 2])
smb_login() smb_login()
vprint_status("#{peer}: Mounting the remote share \\\\#{rhost}\\#{datastore['SMBSHARE']}'...") vprint_status("#{peer}: Mounting the remote share \\\\#{rhost}\\#{datastore['SMBSHARE']}'...")
@ -51,7 +45,7 @@ class MetasploitModule < Msf::Auxiliary
vprint_status("Trying to download #{remote_path}...") vprint_status("Trying to download #{remote_path}...")
data = '' data = ''
fd = simple.open("\\#{remote_path}", 'ro') fd = simple.open("#{remote_path}", 'o')
begin begin
data = fd.read data = fd.read
ensure ensure

View File

@ -13,12 +13,6 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner include Msf::Auxiliary::Scanner
# Aliases for common classes
SIMPLE = Rex::Proto::SMB::SimpleClient
XCEPT = Rex::Proto::SMB::Exceptions
CONST = Rex::Proto::SMB::Constants
def initialize def initialize
super( super(
'Name' => 'SMB File Upload Utility', 'Name' => 'SMB File Upload Utility',
@ -46,7 +40,7 @@ class MetasploitModule < Msf::Auxiliary
def run_host(_ip) def run_host(_ip)
begin begin
vprint_status("Connecting to the server...") vprint_status("Connecting to the server...")
connect() connect(versions: [1, 2])
smb_login() smb_login()
vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...") vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...")
@ -63,7 +57,7 @@ class MetasploitModule < Msf::Auxiliary
begin begin
vprint_status("Trying to upload #{local_path} to #{remote_path}...") vprint_status("Trying to upload #{local_path} to #{remote_path}...")
fd = simple.open("\\#{remote_path}", 'rwct') fd = simple.open("#{remote_path}", 's', write: true)
data = ::File.read(datastore['LPATH'], ::File.size(datastore['LPATH'])) data = ::File.read(datastore['LPATH'], ::File.size(datastore['LPATH']))
fd.write(data) fd.write(data)
fd.close fd.close

View File

@ -17,12 +17,6 @@ class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report include Msf::Auxiliary::Report
# Aliases for common classes
SIMPLE = Rex::Proto::SMB::SimpleClient
XCEPT = Rex::Proto::SMB::Exceptions
CONST = Rex::Proto::SMB::Constants
def initialize def initialize
super( super(
'Name' => 'SMB Version Detection', 'Name' => 'SMB Version Detection',

View File

@ -0,0 +1,62 @@
{
"COMMAND_LIST": [],
"CREDS_FILE": "../JSON/creds.json",
"FRAMEWORK_BRANCH": "upstream/master",
"HTTP_PORT": 5309,
"MSF_HOSTS": [
{
"CPE": "cpe:/a:rapid7:metasploit:::",
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
"METHOD": "VM_TOOLS_UPLOAD",
"MSF_ARTIFACT_PATH": "/home/msfuser/rapid7/test_artifacts",
"MSF_PATH": "/home/msfuser/rapid7/metasploit-framework",
"TYPE": "VIRTUAL"
}
],
"REPORT_PREFIX": "SmbEnumSharesTest",
"STARTING_LISTENER": 30000,
"SUCCESS_LIST": [
"IPC$"
],
"TARGETS": [
{
"CPE": "cpe:/o:microsoft:windows_server_2008:r2:sp1:x64",
"METHOD": "EXPLOIT",
"MODULES": [
{
"NAME": "auxiliary/scanner/smb/smb_enumshares",
"SETTINGS": [
"smbuser=vagrant",
"smbpass=vagrant"
]
}
],
"TYPE": "VIRTUAL"
},
{
"CPE": "cpe:/o:microsoft:windows_server_2016:::x64",
"METHOD": "EXPLOIT",
"MODULES": [
{
"NAME": "auxiliary/scanner/smb/smb_enumshares",
"SETTINGS": [
"smbuser=vagrant",
"smbpass=vagrant"
]
}
],
"TYPE": "VIRTUAL"
}
],
"TARGET_GLOBALS": {
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
"METERPRETER_JAVA": "C:\\software\\x86\\java\\bin\\java.exe",
"METERPRETER_PYTHON": "C:\\software\\x86\\python27\\python.exe",
"METHOD": "VM_TOOLS_UPLOAD",
"PAYLOAD_DIRECTORY": "C:\\payload_test",
"PYTHON": "C:\\software\\x86\\python27\\python.exe",
"TESTING_SNAPSHOT": "TESTING_BASE",
"TYPE": "VIRTUAL"
},
"TEST_NAME": "smb_enumshares test"
}

View File

@ -0,0 +1,62 @@
{
"COMMAND_LIST": [],
"CREDS_FILE": "../JSON/creds.json",
"FRAMEWORK_BRANCH": "upstream/master",
"HTTP_PORT": 5309,
"MSF_HOSTS": [
{
"CPE": "cpe:/a:rapid7:metasploit:::",
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
"METHOD": "VM_TOOLS_UPLOAD",
"MSF_ARTIFACT_PATH": "/home/msfuser/rapid7/test_artifacts",
"MSF_PATH": "/home/msfuser/rapid7/metasploit-framework",
"TYPE": "VIRTUAL"
}
],
"REPORT_PREFIX": "SmbEnumUsersTest",
"STARTING_LISTENER": 30000,
"SUCCESS_LIST": [
"Administrator"
],
"TARGETS": [
{
"CPE": "cpe:/o:microsoft:windows_server_2008:r2:sp1:x64",
"METHOD": "EXPLOIT",
"MODULES": [
{
"NAME": "auxiliary/scanner/smb/smb_enumusers",
"SETTINGS": [
"smbuser=vagrant",
"smbpass=vagrant"
]
}
],
"TYPE": "VIRTUAL"
},
{
"CPE": "cpe:/o:microsoft:windows_server_2016:::x64",
"METHOD": "EXPLOIT",
"MODULES": [
{
"NAME": "auxiliary/scanner/smb/smb_enumusers",
"SETTINGS": [
"smbuser=vagrant",
"smbpass=vagrant"
]
}
],
"TYPE": "VIRTUAL"
}
],
"TARGET_GLOBALS": {
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
"METERPRETER_JAVA": "C:\\software\\x86\\java\\bin\\java.exe",
"METERPRETER_PYTHON": "C:\\software\\x86\\python27\\python.exe",
"METHOD": "VM_TOOLS_UPLOAD",
"PAYLOAD_DIRECTORY": "C:\\payload_test",
"PYTHON": "C:\\software\\x86\\python27\\python.exe",
"TESTING_SNAPSHOT": "TESTING_BASE",
"TYPE": "VIRTUAL"
},
"TEST_NAME": "smb_enumusers test"
}

View File

@ -0,0 +1,56 @@
{
"COMMAND_LIST": [
"sessions -C sessions -l",
"sessions -C sysinfo",
"sessions -C exit"
],
"CREDS_FILE": "../JSON/creds.json",
"FRAMEWORK_BRANCH": "upstream/master",
"HTTP_PORT": 5309,
"MODULES": [
{
"NAME": "exploit/windows/smb/psexec",
"SETTINGS": [
"SMBUser=vagrant",
"SMBPass=vagrant"
]
}
],
"MSF_HOSTS": [
{
"CPE": "cpe:/a:rapid7:metasploit:::",
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
"METHOD": "VM_TOOLS_UPLOAD",
"MSF_ARTIFACT_PATH": "/home/msfuser/rapid7/test_artifacts",
"MSF_PATH": "/home/msfuser/rapid7/metasploit-framework",
"TYPE": "VIRTUAL"
}
],
"PAYLOADS": [
{
"NAME": "windows/x64/meterpreter/reverse_tcp",
"SETTINGS": []
}
],
"STARTING_LISTENER": 30000,
"SUCCESS_LIST": [
"Session 1 created in the background"
],
"TARGETS": [
{
"CPE": "cpe:/o:microsoft:windows_7:::x64"
},
{
"CPE": "cpe:/o:microsoft:windows_10:::x64"
}
],
"TARGET_GLOBALS": {
"HYPERVISOR_CONFIG": "../JSON/esxi_config.json",
"METERPRETER_JAVA": "C:\\software\\x86\\java\\bin\\java.exe",
"METERPRETER_PYTHON": "C:\\software\\x86\\python27\\python.exe",
"METHOD": "EXPLOIT",
"PAYLOAD_DIRECTORY": "C:\\payload_test",
"PYTHON": "C:\\software\\x86\\python27\\python.exe",
"TYPE": "VIRTUAL"
}
}

View File

@ -30,20 +30,6 @@ require 'rex/encoder/ndr'
require 'rex/proto/smb/simpleclient' require 'rex/proto/smb/simpleclient'
# SMB constants from Rex
SIMPLE = Rex::Proto::SMB::SimpleClient
XCEPT = Rex::Proto::SMB::Exceptions
CONST = Rex::Proto::SMB::Constants
# Alias over the Rex DCERPC protocol modules
DCERPCPacket = Rex::Proto::DCERPC::Packet
DCERPCClient = Rex::Proto::DCERPC::Client
DCERPCResponse = Rex::Proto::DCERPC::Response
DCERPCUUID = Rex::Proto::DCERPC::UUID
NDR = Rex::Encoder::NDR
def print_error(msg) def print_error(msg)
$stderr.puts "[-] #{msg}" $stderr.puts "[-] #{msg}"
end end
@ -102,27 +88,20 @@ opt_pass = ARGV.shift() || ""
opt_share = "ADMIN$" opt_share = "ADMIN$"
opt_domain = "." opt_domain = "."
begin
socket = Rex::Socket.create_tcp({ 'PeerHost' => opt_host, 'PeerPort' => opt_port.to_i }) socket = Rex::Socket.create_tcp({ 'PeerHost' => opt_host, 'PeerPort' => opt_port.to_i })
rescue Rex::ConnectionRefused, Rex::HostUnreachable => e
print_error("Could not connect: #{e}")
exit(1)
end
simple = Rex::Proto::SMB::SimpleClient.new(socket, opt_port.to_i == 445, versions = [1, 2])
simple = Rex::Proto::SMB::SimpleClient.new(socket, opt_port.to_i == 445)
simple.login( simple.login(
Rex::Text.rand_text_alpha(8), Rex::Text.rand_text_alpha(8),
opt_user, opt_user,
opt_pass, opt_pass,
opt_domain opt_domain
#datastore['SMB::VerifySignature'],
#datastore['NTLM::UseNTLMv2'],
#datastore['NTLM::UseNTLM2_session'],
#datastore['NTLM::SendLM'],
#datastore['NTLM::UseLMKey'],
#datastore['NTLM::SendNTLM'],
#datastore['SMB::Native_OS'],
#datastore['SMB::Native_LM'],
#{:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
) )
simple.connect("\\\\#{opt_host}\\IPC$") simple.connect("\\\\#{opt_host}\\IPC$")
@ -138,16 +117,12 @@ if (not simple.client.auth_user)
exit(1) exit(1)
end end
fname = Rex::Text.rand_text_alpha(8) + ".exe" fname = Rex::Text.rand_text_alpha(8) + ".exe"
sname = Rex::Text.rand_text_alpha(8) sname = Rex::Text.rand_text_alpha(8)
# Upload the payload to the share # Upload the payload to the share
print_status("Uploading payload...") print_status("Uploading payload...")
simple.connect(opt_share) simple.connect(opt_share)
fd = simple.open("\\#{fname}", 'rwct', 500) fd = simple.open("\\#{fname}", 'rwct', 500)
@ -177,6 +152,7 @@ print_status("Bound to #{handle} ...")
print_status("Obtaining a service manager handle...") print_status("Obtaining a service manager handle...")
scm_handle = nil scm_handle = nil
NDR = Rex::Encoder::NDR
stubdata = stubdata =
NDR.uwstring("\\\\#{opt_host}") + NDR.uwstring("\\\\#{opt_host}") +
NDR.long(0) + NDR.long(0) +