From 78f546ce81880241fdd601778a53ee20763c307d Mon Sep 17 00:00:00 2001 From: Brent Cook Date: Mon, 7 May 2018 14:02:22 -0500 Subject: [PATCH] Land #9986, initial ruby_smb simple client integration --- .rubocop.yml | 12 + lib/msf/core/exploit/smb/client.rb | 13 +- lib/msf/core/exploit/smb/client/psexec.rb | 4 +- lib/rex/proto/dcerpc/client.rb | 2 +- lib/rex/proto/dcerpc/svcctl/packet.rb | 16 +- lib/rex/proto/ntlm/utils.rb | 1323 ++++++++--------- lib/rex/proto/smb/simpleclient.rb | 88 +- lib/rex/proto/smb/simpleclient/open_file.rb | 217 +-- modules/auxiliary/admin/smb/download_file.rb | 10 +- modules/auxiliary/admin/smb/upload_file.rb | 10 +- modules/auxiliary/scanner/smb/smb_version.rb | 6 - .../auxiliary/scanner/smb/smb_enumshares.json | 62 + .../auxiliary/scanner/smb/smb_enumusers.json | 62 + test/modules/exploits/windows/smb/psexec.json | 56 + tools/exploit/psexec.rb | 40 +- 15 files changed, 1072 insertions(+), 849 deletions(-) create mode 100644 test/modules/auxiliary/scanner/smb/smb_enumshares.json create mode 100644 test/modules/auxiliary/scanner/smb/smb_enumusers.json create mode 100644 test/modules/exploits/windows/smb/psexec.json diff --git a/.rubocop.yml b/.rubocop.yml index 95206045fa..53b629061d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,6 +17,10 @@ Metrics/ClassLength: Exclude: - 'modules/**/*' +Style/ClassAndModuleChildren: + Enabled: false + Description: 'Forced nesting is harmful for grepping and general code comprehension' + Metrics/AbcSize: Enabled: false 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' Enabled: false +Style/NumericPredicate: + Description: 'This adds no efficiency nor space saving' + Enabled: false + Style/Documentation: Enabled: true Description: 'Most Metasploit modules do not have class documentation.' @@ -109,6 +117,10 @@ Style/WordArray: Enabled: false 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: Exclude: # this pattern is very common and somewhat unavoidable diff --git a/lib/msf/core/exploit/smb/client.rb b/lib/msf/core/exploit/smb/client.rb index 09e9078b7e..ccbc8d0994 100644 --- a/lib/msf/core/exploit/smb/client.rb +++ b/lib/msf/core/exploit/smb/client.rb @@ -16,11 +16,6 @@ module Msf include Msf::Exploit::Remote::Tcp 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 DCERPCPacket = Rex::Proto::DCERPC::Packet DCERPCClient = Rex::Proto::DCERPC::Client @@ -78,7 +73,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 +87,7 @@ module Msf direct = false end - c = SIMPLE.new(s, direct) + c = Rex::Proto::SMB::SimpleClient.new(s, direct, versions) # setup pipe evasion foo if datastore['SMB::pipe_evasion'] @@ -218,8 +213,8 @@ module Msf # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] def smb_file_exist?(file) begin - fd = simple.open(file, 'ro') - rescue XCEPT::ErrorCode => e + fd = simple.open(file, 'o') + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e # If attempting to open the file results in a "*_NOT_FOUND" error, # then we can be sure the file is not there. # diff --git a/lib/msf/core/exploit/smb/client/psexec.rb b/lib/msf/core/exploit/smb/client/psexec.rb index 25aced14ba..9720f45ec3 100644 --- a/lib/msf/core/exploit/smb/client/psexec.rb +++ b/lib/msf/core/exploit/smb/client/psexec.rb @@ -75,7 +75,7 @@ module Exploit::Remote::SMB::Client::Psexec def smb_read_file(smbshare, host, file) begin simple.connect("\\\\#{host}\\#{smbshare}") - file = simple.open(file, 'ro') + file = simple.open(file, 'o') contents = file.read file.close simple.disconnect("\\\\#{host}\\#{smbshare}") @@ -267,7 +267,7 @@ module Exploit::Remote::SMB::Client::Psexec begin psexec(command) 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 diff --git a/lib/rex/proto/dcerpc/client.rb b/lib/rex/proto/dcerpc/client.rb index f8816e2d11..72500e53d4 100644 --- a/lib/rex/proto/dcerpc/client.rb +++ b/lib/rex/proto/dcerpc/client.rb @@ -158,7 +158,7 @@ require 'rex/proto/smb/exceptions' end end - data = self.socket.read( read_cnt, rand(1024)+1) + data = self.socket.read(read_cnt, rand(1024)+1) break if !(data and data.length > 0) raw_response += data diff --git a/lib/rex/proto/dcerpc/svcctl/packet.rb b/lib/rex/proto/dcerpc/svcctl/packet.rb index 0da50e9ac0..fa6fb53b9e 100644 --- a/lib/rex/proto/dcerpc/svcctl/packet.rb +++ b/lib/rex/proto/dcerpc/svcctl/packet.rb @@ -119,17 +119,17 @@ class Client NDR.long(default_opts[:password4]) begin response = dcerpc_client.call(CREATE_SERVICE_W, stubdata) - if response - svc_status = error_code(response[24,4]) - - if svc_status == ERROR_SUCCESS - svc_handle = response[4,20] - end - end rescue Rex::Proto::DCERPC::Exceptions::Fault => e print_error("Error creating service: #{e}") end + if response + svc_status = error_code(response[24,4]) + if svc_status == ERROR_SUCCESS + svc_handle = response[4,20] + end + end + return svc_handle, svc_status end @@ -169,7 +169,7 @@ class Client begin response = dcerpc_client.call(CLOSE_SERVICE_HANDLE, handle) if response - svc_status = error_code(response[20,4]) + svc_status = error_code(response) end rescue Rex::Proto::DCERPC::Exceptions::Fault => e print_error("Error closing service handle: #{e}") diff --git a/lib/rex/proto/ntlm/utils.rb b/lib/rex/proto/ntlm/utils.rb index d16c2f3c58..8c039cc3b9 100644 --- a/lib/rex/proto/ntlm/utils.rb +++ b/lib/rex/proto/ntlm/utils.rb @@ -3,531 +3,506 @@ require 'rex/proto/ntlm/constants' require 'rex/proto/ntlm/crypt' require 'rex/proto/ntlm/exceptions' -module Rex -module Proto -module NTLM -class Utils +module Rex::Proto::NTLM + 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 - # Convert a unix timestamp to a 64-bit signed server time - def self.time_unix_to_smb(unix_time) - t64 = (unix_time + 11644473600) * 10000000 - thi = (t64 & 0xffffffff00000000) >> 32 - tlo = (t64 & 0x00000000ffffffff) - return [thi, tlo] - end - - # Determine whether the password is a known hash format - def self.is_pass_ntlm_hash?(str) - str.downcase =~ /^[0-9a-f]{32}:[0-9a-f]{32}$/ - end - - # - # Prepends an ASN1 formatted length field to a piece of data - # - def self.asn1encode(str = '') - res = '' - - # If the high bit of the first byte is 1, it contains the number of - # length bytes that follow - - case str.length - when 0 .. 0x7F - res = [str.length].pack('C') + str - when 0x80 .. 0xFF - res = [0x81, str.length].pack('CC') + str - when 0x100 .. 0xFFFF - res = [0x82, str.length].pack('Cn') + str - when 0x10000 .. 0xffffff - res = [0x83, str.length >> 16, str.length & 0xFFFF].pack('CCn') + str - when 0x1000000 .. 0xffffffff - res = [0x84, str.length].pack('CN') + str - else - raise "ASN1 str too long" + # 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 + def self.time_unix_to_smb(unix_time) + t64 = (unix_time + 11644473600) * 10000000 + thi = (t64 & 0xffffffff00000000) >> 32 + tlo = (t64 & 0x00000000ffffffff) + return [thi, tlo] end - return res - end - # GSS functions + # Determine whether the password is a known hash format + def self.is_pass_ntlm_hash?(str) + str.downcase =~ /^[0-9a-f]{32}:[0-9a-f]{32}$/ + end - # GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message - # mechTypes: 2 items : - # -MechType: 1.3.6.1.4.1.311.2.2.30 (SNMPv2-SMI::enterprises.311.2.2.30) - # -MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider) - # - # this is the default on Win7 - def self.make_simple_negotiate_secblob_resp - blob = - "\x60" + self.asn1encode( - "\x06" + self.asn1encode( - "\x2b\x06\x01\x05\x05\x02" - ) + - "\xa0" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa0" + self.asn1encode( - "\x30" + self.asn1encode( - "\x06" + self.asn1encode( - "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" - ) - ) - ) - ) - ) - ) + # + # Prepends an ASN1 formatted length field to a piece of data + # + def self.asn1encode(str = '') + res = '' - return blob - end + # If the high bit of the first byte is 1, it contains the number of + # length bytes that follow - # GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message - # mechTypes: 4 items : - # MechType: 1.2.840.48018.1.2.2 (MS KRB5 - Microsoft Kerberos 5) - # MechType: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5) - # MechType: 1.2.840.113554.1.2.2.3 (KRB5 - Kerberos 5 - User to User) - # MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider) - # mechListMIC: - # principal: account@domain - def self.make_negotiate_secblob_resp(account, domain) - blob = - "\x60" + self.asn1encode( - "\x06" + self.asn1encode( - "\x2b\x06\x01\x05\x05\x02" - ) + - "\xa0" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa0" + self.asn1encode( - "\x30" + self.asn1encode( - "\x06" + self.asn1encode( - "\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" - ) + - "\x06" + self.asn1encode( - "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" - ) + - "\x06" + self.asn1encode( - "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" - ) + - "\x06" + self.asn1encode( - "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" - ) - ) - ) + - "\xa3" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa0" + self.asn1encode( - "\x1b" + self.asn1encode( - account + '@' + domain + case str.length + when 0 .. 0x7F + res = [str.length].pack('C') + str + when 0x80 .. 0xFF + res = [0x81, str.length].pack('CC') + str + when 0x100 .. 0xFFFF + res = [0x82, str.length].pack('Cn') + str + when 0x10000 .. 0xffffff + res = [0x83, str.length >> 16, str.length & 0xFFFF].pack('CCn') + str + when 0x1000000 .. 0xffffffff + res = [0x84, str.length].pack('CN') + str + else + raise "ASN1 str too long" + end + return res + end + + # GSS functions + + # GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message + # mechTypes: 2 items : + # -MechType: 1.3.6.1.4.1.311.2.2.30 (SNMPv2-SMI::enterprises.311.2.2.30) + # -MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider) + # + # this is the default on Win7 + def self.make_simple_negotiate_secblob_resp + blob = + "\x60" + self.asn1encode( + "\x06" + self.asn1encode( + "\x2b\x06\x01\x05\x05\x02" + ) + + "\xa0" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa0" + self.asn1encode( + "\x30" + self.asn1encode( + "\x06" + self.asn1encode( + "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" ) ) ) ) ) ) - ) - return blob - end + return blob + end - # BLOB without GSS usefull for ntlmssp type 1 message - def self.make_ntlmssp_blob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201) - blob = "NTLMSSP\x00" + - [1, flags].pack('VV') + + # GSS BLOB usefull for SMB_NEGOCIATE_RESPONSE message + # mechTypes: 4 items : + # MechType: 1.2.840.48018.1.2.2 (MS KRB5 - Microsoft Kerberos 5) + # MechType: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5) + # MechType: 1.2.840.113554.1.2.2.3 (KRB5 - Kerberos 5 - User to User) + # MechType: 1.3.6.1.4.1.311.2.2.10 (NTLMSSP - Microsoft NTLM Security Support Provider) + # mechListMIC: + # principal: account@domain + def self.make_negotiate_secblob_resp(account, domain) + blob = + "\x60" + self.asn1encode( + "\x06" + self.asn1encode( + "\x2b\x06\x01\x05\x05\x02" + ) + + "\xa0" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa0" + self.asn1encode( + "\x30" + self.asn1encode( + "\x06" + self.asn1encode( + "\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" + ) + + "\x06" + self.asn1encode( + "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" + ) + + "\x06" + self.asn1encode( + "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" + ) + + "\x06" + self.asn1encode( + "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" + ) + ) + ) + + "\xa3" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa0" + self.asn1encode( + "\x1b" + self.asn1encode( + account + '@' + domain + ) + ) + ) + ) + ) + ) + ) - [ - domain.length, #length - domain.length, #max length - 32 - ].pack('vvV') + + return blob + end - [ - name.length, #length - name.length, #max length - domain.length + 32 - ].pack('vvV') + + # BLOB without GSS usefull for ntlmssp type 1 message + def self.make_ntlmssp_blob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201) + blob = "NTLMSSP\x00" + + [1, flags].pack('VV') + - domain + name - return blob - end + [ + domain.length, #length + domain.length, #max length + 32 + ].pack('vvV') + - # GSS BLOB usefull for ntlmssp type 1 message - def self.make_ntlmssp_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201) - blob = - "\x60" + self.asn1encode( - "\x06" + self.asn1encode( - "\x2b\x06\x01\x05\x05\x02" - ) + - "\xa0" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa0" + self.asn1encode( - "\x30" + self.asn1encode( + [ + name.length, #length + name.length, #max length + domain.length + 32 + ].pack('vvV') + + + domain + name + return blob + end + + # GSS BLOB usefull for ntlmssp type 1 message + def self.make_ntlmssp_secblob_init(domain = 'WORKGROUP', name = 'WORKSTATION', flags=0x80201) + blob = + "\x60" + self.asn1encode( + "\x06" + self.asn1encode( + "\x2b\x06\x01\x05\x05\x02" + ) + + "\xa0" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa0" + self.asn1encode( + "\x30" + self.asn1encode( + "\x06" + self.asn1encode( + "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" + ) + ) + ) + + "\xa2" + self.asn1encode( + "\x04" + self.asn1encode( + make_ntlmssp_blob_init(domain, name, flags) + ) + ) + ) + ) + ) + + return blob + end + + + # BLOB without GSS usefull for ntlm type 2 message + def self.make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags) + + addr_list = '' + addr_list << [2, win_domain.length].pack('vv') + win_domain + addr_list << [1, win_name.length].pack('vv') + win_name + addr_list << [4, dns_domain.length].pack('vv') + dns_domain + addr_list << [3, dns_name.length].pack('vv') + dns_name + addr_list << [0, 0].pack('vv') + + ptr = 0 + blob = "NTLMSSP\x00" + + [2].pack('V') + + [ + win_domain.length, # length + win_domain.length, # max length + (ptr += 48) # offset + ].pack('vvV') + + [ flags ].pack('V') + + chall + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + [ + addr_list.length, # length + addr_list.length, # max length + (ptr += win_domain.length) + ].pack('vvV') + + win_domain + + addr_list + return blob + end + + # GSS BLOB usefull for ntlmssp type 2 message + def self.make_ntlmssp_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags) + + blob = + "\xa1" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa0" + self.asn1encode( + "\x0a" + self.asn1encode( + "\x01" + ) + ) + + "\xa1" + self.asn1encode( "\x06" + self.asn1encode( "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" ) - ) - ) + - "\xa2" + self.asn1encode( - "\x04" + self.asn1encode( - make_ntlmssp_blob_init(domain, name, flags) + ) + + "\xa2" + self.asn1encode( + "\x04" + self.asn1encode( + make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags) + ) ) ) ) - ) - ) - return blob - end + return blob + end + # BLOB without GSS Usefull for ntlmssp type 3 message + def self.make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201) + lm ||= "\x00" * 24 + ntlm ||= "\x00" * 24 - # BLOB without GSS usefull for ntlm type 2 message - def self.make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags) + domain_uni = Rex::Text.to_unicode(domain) + user_uni = Rex::Text.to_unicode(user) + name_uni = Rex::Text.to_unicode(name) + session = enc_session_key - addr_list = '' - addr_list << [2, win_domain.length].pack('vv') + win_domain - addr_list << [1, win_name.length].pack('vv') + win_name - addr_list << [4, dns_domain.length].pack('vv') + dns_domain - addr_list << [3, dns_name.length].pack('vv') + dns_name - addr_list << [0, 0].pack('vv') + ptr = 64 - ptr = 0 - blob = "NTLMSSP\x00" + - [2].pack('V') + - [ - win_domain.length, # length - win_domain.length, # max length - (ptr += 48) # offset + blob = "NTLMSSP\x00" + + [ 3 ].pack('V') + + + [ # Lan Manager Response + lm.length, + lm.length, + (ptr) ].pack('vvV') + + + [ # NTLM Manager Response + ntlm.length, + ntlm.length, + (ptr += lm.length) + ].pack('vvV') + + + [ # Domain Name + domain_uni.length, + domain_uni.length, + (ptr += ntlm.length) + ].pack('vvV') + + + [ # Username + user_uni.length, + user_uni.length, + (ptr += domain_uni.length) + ].pack('vvV') + + + [ # Hostname + name_uni.length, + name_uni.length, + (ptr += user_uni.length) + ].pack('vvV') + + + [ # Session Key (none) + session.length, + session.length, + (ptr += name_uni.length) + ].pack('vvV') + + [ flags ].pack('V') + - chall + - "\x00\x00\x00\x00\x00\x00\x00\x00" + - [ - addr_list.length, # length - addr_list.length, # max length - (ptr += win_domain.length) - ].pack('vvV') + - win_domain + - addr_list - return blob - end - # GSS BLOB usefull for ntlmssp type 2 message - def self.make_ntlmssp_secblob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags) + lm + + ntlm + + domain_uni + + user_uni + + name_uni + + session + "\x00" + return blob - blob = - "\xa1" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa0" + self.asn1encode( - "\x0a" + self.asn1encode( - "\x01" - ) - ) + - "\xa1" + self.asn1encode( - "\x06" + self.asn1encode( - "\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" - ) - ) + - "\xa2" + self.asn1encode( - "\x04" + self.asn1encode( - make_ntlmssp_blob_chall(win_domain, win_name, dns_domain, dns_name, chall, flags) + end + + # GSS BLOB Usefull for ntlmssp type 3 message + def self.make_ntlmssp_secblob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201) + + blob = + "\xa1" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa2" + self.asn1encode( + "\x04" + self.asn1encode( + make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags ) ) ) ) ) - - return blob - end - - # BLOB without GSS Usefull for ntlmssp type 3 message - def self.make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201) - lm ||= "\x00" * 24 - ntlm ||= "\x00" * 24 - - domain_uni = Rex::Text.to_unicode(domain) - user_uni = Rex::Text.to_unicode(user) - name_uni = Rex::Text.to_unicode(name) - session = enc_session_key - - ptr = 64 - - blob = "NTLMSSP\x00" + - [ 3 ].pack('V') + - - [ # Lan Manager Response - lm.length, - lm.length, - (ptr) - ].pack('vvV') + - - [ # NTLM Manager Response - ntlm.length, - ntlm.length, - (ptr += lm.length) - ].pack('vvV') + - - [ # Domain Name - domain_uni.length, - domain_uni.length, - (ptr += ntlm.length) - ].pack('vvV') + - - [ # Username - user_uni.length, - user_uni.length, - (ptr += domain_uni.length) - ].pack('vvV') + - - [ # Hostname - name_uni.length, - name_uni.length, - (ptr += user_uni.length) - ].pack('vvV') + - - [ # Session Key (none) - session.length, - session.length, - (ptr += name_uni.length) - ].pack('vvV') + - - [ flags ].pack('V') + - - lm + - ntlm + - domain_uni + - user_uni + - name_uni + - session + "\x00" - return blob - - end - - # GSS BLOB Usefull for ntlmssp type 3 message - def self.make_ntlmssp_secblob_auth(domain, name, user, lm, ntlm, enc_session_key, flags = 0x080201) - - blob = - "\xa1" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa2" + self.asn1encode( - "\x04" + self.asn1encode( - make_ntlmssp_blob_auth(domain, name, user, lm, ntlm, enc_session_key, flags ) - ) - ) - ) - ) - return blob - end + return blob + end - # GSS BLOB Usefull for SMB Success - def self.make_ntlmv2_secblob_success - blob = - "\xa1" + self.asn1encode( - "\x30" + self.asn1encode( - "\xa0" + self.asn1encode( - "\x0a" + self.asn1encode( - "\x00" + # GSS BLOB Usefull for SMB Success + def self.make_ntlmv2_secblob_success + blob = + "\xa1" + self.asn1encode( + "\x30" + self.asn1encode( + "\xa0" + self.asn1encode( + "\x0a" + self.asn1encode( + "\x00" + ) ) ) ) - ) - return blob - end - - # Return the correct ntlmflags upon the configuration - def self.make_ntlm_flags(opt = {}) - - signing = opt[:signing] != nil ? opt[:signing] : false - usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true - use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false - send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true - send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true - use_lanman_key = opt[:use_lanman_key] != nil ? opt[:use_lanman_key] : false - - if signing - ntlmssp_flags = 0xe2088215 - else - - ntlmssp_flags = 0xa2080205 + return blob end - if usentlm2_session - if use_ntlmv2 - #set Negotiate Target Info - ntlmssp_flags |= CONST::NEGOTIATE_TARGET_INFO + # Return the correct ntlmflags upon the configuration + def self.make_ntlm_flags(opt = {}) + + signing = opt[:signing] != nil ? opt[:signing] : false + usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true + use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false + send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true + send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true + use_lanman_key = opt[:use_lanman_key] != nil ? opt[:use_lanman_key] : false + + if signing + ntlmssp_flags = 0xe2088215 + else + + ntlmssp_flags = 0xa2080205 end - else - #remove the ntlm2_session flag - ntlmssp_flags &= 0xfff7ffff - #set lanmanflag only when lm and ntlm are sent - if send_lm - ntlmssp_flags |= CONST::NEGOTIATE_LMKEY if use_lanman_key - end - end - - #we can also downgrade ntlm2_session when we send only lmv1 - ntlmssp_flags &= 0xfff7ffff if usentlm2_session && (not use_ntlmv2) && (not send_ntlm) - - return ntlmssp_flags - end - - - # Parse an ntlm type 2 challenge blob and return usefull data - def self.parse_ntlm_type_2_blob(blob) - data = {} - # Extract the NTLM challenge key the lazy way - cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00") - - if not cidx - raise XCEPT::NTLMMissingChallenge - end - - data[:challenge_key] = blob[cidx + 24, 8] - - data[:server_ntlmssp_flags] = blob[cidx + 20, 4].unpack("V")[0] - - # Extract the address list from the blob - alist_len,alist_mlen,alist_off = blob[cidx + 40, 8].unpack("vvV") - alist_buf = blob[cidx + alist_off, alist_len] - - while(alist_buf.length > 0) - atype, alen = alist_buf.slice!(0,4).unpack('vv') - break if atype == 0x00 - addr = alist_buf.slice!(0, alen) - case atype - when 1 - #netbios name - temp_name = addr - temp_name.force_encoding("UTF-16LE") - data[:default_name] = temp_name.encode("UTF-8") - when 2 - #netbios domain - temp_domain = addr - temp_domain.force_encoding("UTF-16LE") - data[:default_domain] = temp_domain.encode("UTF-8") - when 3 - #dns name - temp_dns = addr - temp_dns.force_encoding("UTF-16LE") - data[:dns_host_name] = temp_dns.encode("UTF-8") - when 4 - #dns domain - temp_dns_domain = addr - temp_dns_domain.force_encoding("UTF-16LE") - data[:dns_domain_name] = temp_dns_domain.encode("UTF-8") - when 5 - #The FQDN of the forest. - when 6 - #A 32-bit value indicating server or client configuration - when 7 - #Client time - data[:chall_MsvAvTimestamp] = addr - when 8 - #A Restriction_Encoding structure - when 9 - #The SPN of the target server. - when 10 - #A channel bindings hash. - end - end - return data - end - - # This function return an ntlmv2 client challenge - # This is a partial implementation, full description is in [MS-NLMP].pdf around 3.1.5.2.1 :-/ - def self.make_ntlmv2_clientchallenge(win_domain, win_name, dns_domain, dns_name, - client_challenge = nil, chall_MsvAvTimestamp = nil, spnopt = {}) - - client_challenge ||= Rex::Text.rand_text(8) - # We have to set the timestamps here to the one in the challenge message from server if present - # If we don't do that, recent server like Seven/2008 will send a STATUS_INVALID_PARAMETER error packet - timestamp = chall_MsvAvTimestamp != '' ? chall_MsvAvTimestamp : self.time_unix_to_smb(Time.now.to_i).reverse.pack("VV") - # Make those values unicode as requested - win_domain = Rex::Text.to_unicode(win_domain) - win_name = Rex::Text.to_unicode(win_name) - dns_domain = Rex::Text.to_unicode(dns_domain) - dns_name = Rex::Text.to_unicode(dns_name) - # Make the AV_PAIRs - addr_list = '' - addr_list << [2, win_domain.length].pack('vv') + win_domain - addr_list << [1, win_name.length].pack('vv') + win_name - addr_list << [4, dns_domain.length].pack('vv') + dns_domain - addr_list << [3, dns_name.length].pack('vv') + dns_name - addr_list << [7, 8].pack('vv') + timestamp - - # Windows Seven / 2008r2 Request this type if in local security policies, - # Microsoft network server : Server SPN target name validation level is set to - # otherwise it send an STATUS_ACCESS_DENIED packet - if spnopt[:use_spn] - spn= Rex::Text.to_unicode("cifs/#{spnopt[:name] || 'unknown'}") - addr_list << [9, spn.length].pack('vv') + spn - end - - # MAY BE USEFUL FOR FUTURE - # Seven (client) add at least one more av that is of type MsAvRestrictions (8) - # maybe this will be usefull with future windows OSs but has no use at all for the moment afaik - # restriction_encoding = [48,0,0,0].pack("VVV") + # Size, Z4, IntegrityLevel, SubjectIntegrityLevel - # Rex::Text.rand_text(32) # MachineId generated on startup on win7 and above - # addr_list << [8, restriction_encoding.length].pack('vv') + restriction_encoding - - # Seven (client) and maybe others versions also add an av of type MsvChannelBindings (10) but the hash is "\x00" * 16 - # addr_list << [10, 16].pack('vv') + "\x00" * 16 - - - addr_list << [0, 0].pack('vv') - ntlm_clientchallenge = [1,1,0,0].pack("CCvV") + #RespType, HiRespType, Reserved1, Reserved2 - timestamp + #Timestamp - client_challenge + #clientchallenge - [0].pack("V") + #Reserved3 - addr_list + "\x00" * 4 - - end - - # create lm/ntlm responses - def self.create_lm_ntlm_responses(user, pass, challenge_key, domain = '', default_name = '', default_domain = '', - dns_host_name = '', dns_domain_name = '', chall_MsvAvTimestamp = nil, spnopt = {}, opt = {} ) - - usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true - use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false - send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true - send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true - - #calculate the lm/ntlm response - resp_lm = "\x00" * 24 - resp_ntlm = "\x00" * 24 - - client_challenge = Rex::Text.rand_text(8) - ntlm_cli_challenge = '' - if send_ntlm #should be default if usentlm2_session if use_ntlmv2 - ntlm_cli_challenge = self.make_ntlmv2_clientchallenge(default_domain, default_name, dns_domain_name, - dns_host_name,client_challenge , - chall_MsvAvTimestamp, spnopt) - if self.is_pass_ntlm_hash?(pass) - argntlm = { - :ntlmv2_hash => CRYPT::ntlmv2_hash( - user, - [ pass.upcase()[33,65] ].pack('H32'), - domain,{:pass_is_hash => true} - ), - :challenge => challenge_key - } - else - argntlm = { - :ntlmv2_hash => CRYPT::ntlmv2_hash(user, pass, domain), - :challenge => challenge_key - } - end + #set Negotiate Target Info + ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_TARGET_INFO + end - optntlm = { :nt_client_challenge => ntlm_cli_challenge} - ntlmv2_response = CRYPT::ntlmv2_response(argntlm,optntlm) - resp_ntlm = ntlmv2_response + else + #remove the ntlm2_session flag + ntlmssp_flags &= 0xfff7ffff + #set lanmanflag only when lm and ntlm are sent + if send_lm + ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_LMKEY if use_lanman_key + end + end + + #we can also downgrade ntlm2_session when we send only lmv1 + ntlmssp_flags &= 0xfff7ffff if usentlm2_session && (not use_ntlmv2) && (not send_ntlm) + + return ntlmssp_flags + end + + + # Parse an ntlm type 2 challenge blob and return usefull data + def self.parse_ntlm_type_2_blob(blob) + data = {} + # Extract the NTLM challenge key the lazy way + cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00") + + if not cidx + raise Rex::Proto::NTLM::Exceptions::NTLMMissingChallenge + end + + data[:challenge_key] = blob[cidx + 24, 8] + + data[:server_ntlmssp_flags] = blob[cidx + 20, 4].unpack("V")[0] + + # Extract the address list from the blob + alist_len,alist_mlen,alist_off = blob[cidx + 40, 8].unpack("vvV") + alist_buf = blob[cidx + alist_off, alist_len] + + while(alist_buf.length > 0) + atype, alen = alist_buf.slice!(0,4).unpack('vv') + break if atype == 0x00 + addr = alist_buf.slice!(0, alen) + case atype + when 1 + #netbios name + temp_name = addr + temp_name.force_encoding("UTF-16LE") + data[:default_name] = temp_name.encode("UTF-8") + when 2 + #netbios domain + temp_domain = addr + temp_domain.force_encoding("UTF-16LE") + data[:default_domain] = temp_domain.encode("UTF-8") + when 3 + #dns name + temp_dns = addr + temp_dns.force_encoding("UTF-16LE") + data[:dns_host_name] = temp_dns.encode("UTF-8") + when 4 + #dns domain + temp_dns_domain = addr + temp_dns_domain.force_encoding("UTF-16LE") + data[:dns_domain_name] = temp_dns_domain.encode("UTF-8") + when 5 + #The FQDN of the forest. + when 6 + #A 32-bit value indicating server or client configuration + when 7 + #Client time + data[:chall_MsvAvTimestamp] = addr + when 8 + #A Restriction_Encoding structure + when 9 + #The SPN of the target server. + when 10 + #A channel bindings hash. + end + end + return data + end + + # This function return an ntlmv2 client challenge + # This is a partial implementation, full description is in [MS-NLMP].pdf around 3.1.5.2.1 :-/ + def self.make_ntlmv2_clientchallenge(win_domain, win_name, dns_domain, dns_name, + client_challenge = nil, chall_MsvAvTimestamp = nil, spnopt = {}) + + client_challenge ||= Rex::Text.rand_text(8) + # We have to set the timestamps here to the one in the challenge message from server if present + # If we don't do that, recent server like Seven/2008 will send a STATUS_INVALID_PARAMETER error packet + timestamp = chall_MsvAvTimestamp != '' ? chall_MsvAvTimestamp : self.time_unix_to_smb(Time.now.to_i).reverse.pack("VV") + # Make those values unicode as requested + win_domain = Rex::Text.to_unicode(win_domain) + win_name = Rex::Text.to_unicode(win_name) + dns_domain = Rex::Text.to_unicode(dns_domain) + dns_name = Rex::Text.to_unicode(dns_name) + # Make the AV_PAIRs + addr_list = '' + addr_list << [2, win_domain.length].pack('vv') + win_domain + addr_list << [1, win_name.length].pack('vv') + win_name + addr_list << [4, dns_domain.length].pack('vv') + dns_domain + addr_list << [3, dns_name.length].pack('vv') + dns_name + addr_list << [7, 8].pack('vv') + timestamp + + # Windows Seven / 2008r2 Request this type if in local security policies, + # Microsoft network server : Server SPN target name validation level is set to + # otherwise it send an STATUS_ACCESS_DENIED packet + if spnopt[:use_spn] + spn= Rex::Text.to_unicode("cifs/#{spnopt[:name] || 'unknown'}") + addr_list << [9, spn.length].pack('vv') + spn + end + + # MAY BE USEFUL FOR FUTURE + # Seven (client) add at least one more av that is of type MsAvRestrictions (8) + # maybe this will be usefull with future windows OSs but has no use at all for the moment afaik + # restriction_encoding = [48,0,0,0].pack("VVV") + # Size, Z4, IntegrityLevel, SubjectIntegrityLevel + # Rex::Text.rand_text(32) # MachineId generated on startup on win7 and above + # addr_list << [8, restriction_encoding.length].pack('vv') + restriction_encoding + + # Seven (client) and maybe others versions also add an av of type MsvChannelBindings (10) but the hash is "\x00" * 16 + # addr_list << [10, 16].pack('vv') + "\x00" * 16 + + + addr_list << [0, 0].pack('vv') + ntlm_clientchallenge = [1,1,0,0].pack("CCvV") + #RespType, HiRespType, Reserved1, Reserved2 + timestamp + #Timestamp + client_challenge + #clientchallenge + [0].pack("V") + #Reserved3 + addr_list + "\x00" * 4 + + end + + # create lm/ntlm responses + def self.create_lm_ntlm_responses(user, pass, challenge_key, domain = '', default_name = '', default_domain = '', + dns_host_name = '', dns_domain_name = '', chall_MsvAvTimestamp = nil, spnopt = {}, opt = {} ) + + usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true + use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false + send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true + send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true + + #calculate the lm/ntlm response + resp_lm = "\x00" * 24 + resp_ntlm = "\x00" * 24 + + client_challenge = Rex::Text.rand_text(8) + ntlm_cli_challenge = '' + if send_ntlm #should be default + if usentlm2_session + if use_ntlmv2 + ntlm_cli_challenge = self.make_ntlmv2_clientchallenge( + default_domain, default_name, dns_domain_name, + dns_host_name,client_challenge, + chall_MsvAvTimestamp, spnopt) - if send_lm if self.is_pass_ntlm_hash?(pass) - arglm = { - :ntlmv2_hash => CRYPT::ntlmv2_hash( + argntlm = { + :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash( user, [ pass.upcase()[33,65] ].pack('H32'), domain,{:pass_is_hash => true} @@ -535,53 +510,113 @@ class Utils :challenge => challenge_key } else - arglm = { - :ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain), - :challenge => challenge_key + argntlm = { + :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(user, pass, domain), + :challenge => challenge_key } end - optlm = { :client_challenge => client_challenge } - resp_lm = CRYPT::lmv2_response(arglm, optlm) - else - resp_lm = "\x00" * 24 + optntlm = { :nt_client_challenge => ntlm_cli_challenge} + ntlmv2_response = Rex::Proto::NTLM::Crypt::ntlmv2_response(argntlm,optntlm) + resp_ntlm = ntlmv2_response + + if send_lm + if self.is_pass_ntlm_hash?(pass) + arglm = { + :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash( + user, + [ pass.upcase()[33,65] ].pack('H32'), + domain,{:pass_is_hash => true} + ), + :challenge => challenge_key + } + else + arglm = { + :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(user,pass, domain), + :challenge => challenge_key + } + end + + optlm = { :client_challenge => client_challenge } + resp_lm = Rex::Proto::NTLM::Crypt::lmv2_response(arglm, optlm) + else + resp_lm = "\x00" * 24 + end + + else # ntlm2_session + if self.is_pass_ntlm_hash?(pass) + argntlm = { + :ntlm_hash => [ pass.upcase()[33,65] ].pack('H32'), + :challenge => challenge_key + } + else + argntlm = { + :ntlm_hash => Rex::Proto::NTLM::Crypt::ntlm_hash(pass), + :challenge => challenge_key + } + end + + optntlm = { :client_challenge => client_challenge} + resp_ntlm = Rex::Proto::NTLM::Crypt::ntlm2_session(argntlm,optntlm).join[24,24] + + # Generate the fake LANMAN hash + resp_lm = client_challenge + ("\x00" * 16) end - else # ntlm2_session + else # we use lmv1/ntlmv1 if self.is_pass_ntlm_hash?(pass) argntlm = { :ntlm_hash => [ pass.upcase()[33,65] ].pack('H32'), - :challenge => challenge_key + :challenge => challenge_key } else argntlm = { - :ntlm_hash => CRYPT::ntlm_hash(pass), - :challenge => challenge_key + :ntlm_hash => Rex::Proto::NTLM::Crypt::ntlm_hash(pass), + :challenge => challenge_key } end - optntlm = { :client_challenge => client_challenge} - resp_ntlm = CRYPT::ntlm2_session(argntlm,optntlm).join[24,24] - - # Generate the fake LANMAN hash - resp_lm = client_challenge + ("\x00" * 16) + resp_ntlm = Rex::Proto::NTLM::Crypt::ntlm_response(argntlm) + if send_lm + if self.is_pass_ntlm_hash?(pass) + arglm = { + :lm_hash => [ pass.upcase()[0,32] ].pack('H32'), + :challenge => challenge_key + } + else + arglm = { + :lm_hash => Rex::Proto::NTLM::Crypt::lm_hash(pass), + :challenge => challenge_key + } + end + resp_lm = Rex::Proto::NTLM::Crypt::lm_response(arglm) + else + #when windows does not send lm in ntlmv1 type response, + # it gives lm response the same value as ntlm response + resp_lm = resp_ntlm + end end - - else # we use lmv1/ntlmv1 - if self.is_pass_ntlm_hash?(pass) - argntlm = { - :ntlm_hash => [ pass.upcase()[33,65] ].pack('H32'), - :challenge => challenge_key - } + else #send_ntlm = false + #lmv2 + if usentlm2_session && use_ntlmv2 + if self.is_pass_ntlm_hash?(pass) + arglm = { + :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash( + user, + [ pass.upcase()[33,65] ].pack('H32'), + domain,{:pass_is_hash => true} + ), + :challenge => challenge_key + } + else + arglm = { + :ntlmv2_hash => Rex::Proto::NTLM::Crypt::ntlmv2_hash(user,pass, domain), + :challenge => challenge_key + } + end + optlm = { :client_challenge => client_challenge } + resp_lm = Rex::Proto::NTLM::Crypt::lmv2_response(arglm, optlm) else - argntlm = { - :ntlm_hash => CRYPT::ntlm_hash(pass), - :challenge => challenge_key - } - end - - resp_ntlm = CRYPT::ntlm_response(argntlm) - if send_lm if self.is_pass_ntlm_hash?(pass) arglm = { :lm_hash => [ pass.upcase()[0,32] ].pack('H32'), @@ -589,185 +624,139 @@ class Utils } else arglm = { - :lm_hash => CRYPT::lm_hash(pass), + :lm_hash => Rex::Proto::NTLM::Crypt::lm_hash(pass), :challenge => challenge_key } end - resp_lm = CRYPT::lm_response(arglm) - else - #when windows does not send lm in ntlmv1 type response, - # it gives lm response the same value as ntlm response - resp_lm = resp_ntlm + resp_lm = Rex::Proto::NTLM::Crypt::lm_response(arglm) end + resp_ntlm = "" end - else #send_ntlm = false - #lmv2 - if usentlm2_session && use_ntlmv2 - if self.is_pass_ntlm_hash?(pass) - arglm = { - :ntlmv2_hash => CRYPT::ntlmv2_hash( - user, - [ pass.upcase()[33,65] ].pack('H32'), - domain,{:pass_is_hash => true} - ), - :challenge => challenge_key - } - else - arglm = { - :ntlmv2_hash => CRYPT::ntlmv2_hash(user,pass, domain), - :challenge => challenge_key - } - end - optlm = { :client_challenge => client_challenge } - resp_lm = CRYPT::lmv2_response(arglm, optlm) + return resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge + end + + # create the session key + def self.create_session_key(ntlmssp_flags, server_ntlmssp_flags, user, pass, domain, challenge_key, + client_challenge = '', ntlm_cli_challenge = '' , opt = {} ) + + usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true + use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false + send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true + send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true + use_lanman_key = opt[:use_lanman_key] != nil ? opt[:use_lanman_key] : false + + # Create the sessionkey (aka signing key, aka mackey) and encrypted session key + # Server will decide for key_size and key_exchange + enc_session_key = '' + signing_key = '' + + # Set default key size and key exchange values + key_size = 40 + key_exchange = false + # Remove ntlmssp.negotiate56 + ntlmssp_flags &= 0x7fffffff + # Remove ntlmssp.negotiatekeyexch + ntlmssp_flags &= 0xbfffffff + # Remove ntlmssp.negotiate128 + ntlmssp_flags &= 0xdfffffff + # Check the keyexchange + if server_ntlmssp_flags & Rex::Proto::NTLM::Constants::NEGOTIATE_KEY_EXCH != 0 then + key_exchange = true + ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_KEY_EXCH + end + # Check 128bits + if server_ntlmssp_flags & Rex::Proto::NTLM::Constants::NEGOTIATE_128 != 0 then + key_size = 128 + ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_128 + ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_56 + # Check 56bits else - if self.is_pass_ntlm_hash?(pass) - arglm = { - :lm_hash => [ pass.upcase()[0,32] ].pack('H32'), - :challenge => challenge_key - } - else - arglm = { - :lm_hash => CRYPT::lm_hash(pass), - :challenge => challenge_key - } + if server_ntlmssp_flags & Rex::Proto::NTLM::Constants::NEGOTIATE_56 != 0 then + key_size = 56 + ntlmssp_flags |= Rex::Proto::NTLM::Constants::NEGOTIATE_56 end - resp_lm = CRYPT::lm_response(arglm) end - resp_ntlm = "" - end - return resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge - end - - # create the session key - def self.create_session_key(ntlmssp_flags, server_ntlmssp_flags, user, pass, domain, challenge_key, - client_challenge = '', ntlm_cli_challenge = '' , opt = {} ) - - usentlm2_session = opt[:usentlm2_session] != nil ? opt[:usentlm2_session] : true - use_ntlmv2 = opt[:use_ntlmv2] != nil ? opt[:use_ntlmv2] : false - send_lm = opt[:send_lm] != nil ? opt[:send_lm] : true - send_ntlm = opt[:send_ntlm] != nil ? opt[:send_ntlm] : true - use_lanman_key = opt[:use_lanman_key] != nil ? opt[:use_lanman_key] : false - - # Create the sessionkey (aka signing key, aka mackey) and encrypted session key - # Server will decide for key_size and key_exchange - enc_session_key = '' - signing_key = '' - - # Set default key size and key exchange values - key_size = 40 - key_exchange = false - # Remove ntlmssp.negotiate56 - ntlmssp_flags &= 0x7fffffff - # Remove ntlmssp.negotiatekeyexch - ntlmssp_flags &= 0xbfffffff - # Remove ntlmssp.negotiate128 - ntlmssp_flags &= 0xdfffffff - # Check the keyexchange - if server_ntlmssp_flags & CONST::NEGOTIATE_KEY_EXCH != 0 then - key_exchange = true - ntlmssp_flags |= CONST::NEGOTIATE_KEY_EXCH - end - # Check 128bits - if server_ntlmssp_flags & CONST::NEGOTIATE_128 != 0 then - key_size = 128 - ntlmssp_flags |= CONST::NEGOTIATE_128 - ntlmssp_flags |= CONST::NEGOTIATE_56 - # Check 56bits - else - if server_ntlmssp_flags & CONST::NEGOTIATE_56 != 0 then - key_size = 56 - ntlmssp_flags |= CONST::NEGOTIATE_56 - end - end - # Generate the user session key - lanman_weak = false - if send_ntlm # Should be default - if usentlm2_session - if use_ntlmv2 - if self.is_pass_ntlm_hash?(pass) - user_session_key = CRYPT::ntlmv2_user_session_key(user, - [ pass.upcase()[33,65] ].pack('H32'), - domain, - challenge_key, ntlm_cli_challenge, - {:pass_is_hash => true}) + # Generate the user session key + lanman_weak = false + if send_ntlm # Should be default + if usentlm2_session + if use_ntlmv2 + if self.is_pass_ntlm_hash?(pass) + user_session_key = Rex::Proto::NTLM::Crypt::ntlmv2_user_session_key(user, + [ pass.upcase()[33,65] ].pack('H32'), + domain, + challenge_key, ntlm_cli_challenge, + {:pass_is_hash => true}) + else + user_session_key = Rex::Proto::NTLM::Crypt::ntlmv2_user_session_key(user, pass, domain, + challenge_key, ntlm_cli_challenge) + end else - user_session_key = CRYPT::ntlmv2_user_session_key(user, pass, domain, - challenge_key, ntlm_cli_challenge) + if self.is_pass_ntlm_hash?(pass) + user_session_key = Rex::Proto::NTLM::Crypt::ntlm2_session_user_session_key([ pass.upcase()[33,65] ].pack('H32'), + challenge_key, + client_challenge, + {:pass_is_hash => true}) + else + user_session_key = Rex::Proto::NTLM::Crypt::ntlm2_session_user_session_key(pass, challenge_key, + client_challenge) + end end - else - if self.is_pass_ntlm_hash?(pass) - user_session_key = CRYPT::ntlm2_session_user_session_key([ pass.upcase()[33,65] ].pack('H32'), - challenge_key, - client_challenge, + else # lmv1/ntlmv1 + # lanman_key may also be used without ntlm response but it is not so much used + # so we don't care about this feature + if send_lm && use_lanman_key + if self.is_pass_ntlm_hash?(pass) + user_session_key = Rex::Proto::NTLM::Crypt::lanman_session_key([ pass.upcase()[0,32] ].pack('H32'), + challenge_key, + {:pass_is_hash => true}) + else + user_session_key = Rex::Proto::NTLM::Crypt::lanman_session_key(pass, challenge_key) + end + lanman_weak = true + + + else + if self.is_pass_ntlm_hash?(pass) + user_session_key = Rex::Proto::NTLM::Crypt::ntlmv1_user_session_key([ pass.upcase()[33,65] ].pack('H32'), {:pass_is_hash => true}) - else - user_session_key = CRYPT::ntlm2_session_user_session_key(pass, challenge_key, - client_challenge) + else + user_session_key = Rex::Proto::NTLM::Crypt::ntlmv1_user_session_key(pass) + end end end - else # lmv1/ntlmv1 - # lanman_key may also be used without ntlm response but it is not so much used - # so we don't care about this feature - if send_lm && use_lanman_key - if self.is_pass_ntlm_hash?(pass) - user_session_key = CRYPT::lanman_session_key([ pass.upcase()[0,32] ].pack('H32'), - challenge_key, - {:pass_is_hash => true}) - else - user_session_key = CRYPT::lanman_session_key(pass, challenge_key) - end - lanman_weak = true - - - else - if self.is_pass_ntlm_hash?(pass) - user_session_key = CRYPT::ntlmv1_user_session_key([ pass.upcase()[33,65] ].pack('H32'), + else + if usentlm2_session && use_ntlmv2 + if self.is_pass_ntlm_hash?(pass) + user_session_key = Rex::Proto::NTLM::Crypt::lmv2_user_session_key(user, [ pass.upcase()[33,65] ].pack('H32'), + domain, + challenge_key, client_challenge, {:pass_is_hash => true}) + else + user_session_key = Rex::Proto::NTLM::Crypt::lmv2_user_session_key(user, pass, domain, + challenge_key, client_challenge) + end else - user_session_key = CRYPT::ntlmv1_user_session_key(pass) + if self.is_pass_ntlm_hash?(pass) + user_session_key = Rex::Proto::NTLM::Crypt::lmv1_user_session_key([ pass.upcase()[0,32] ].pack('H32'), + {:pass_is_hash => true}) + else + user_session_key = Rex::Proto::NTLM::Crypt::lmv1_user_session_key(pass) + end end - end end - else - if usentlm2_session && use_ntlmv2 - if self.is_pass_ntlm_hash?(pass) - user_session_key = CRYPT::lmv2_user_session_key(user, [ pass.upcase()[33,65] ].pack('H32'), - domain, - challenge_key, client_challenge, - {:pass_is_hash => true}) - else - user_session_key = CRYPT::lmv2_user_session_key(user, pass, domain, - challenge_key, client_challenge) - end - else - if self.is_pass_ntlm_hash?(pass) - user_session_key = CRYPT::lmv1_user_session_key([ pass.upcase()[0,32] ].pack('H32'), - {:pass_is_hash => true}) - else - user_session_key = CRYPT::lmv1_user_session_key(pass) - end - end + + user_session_key = Rex::Proto::NTLM::Crypt::make_weak_sessionkey(user_session_key,key_size, lanman_weak) + + # Sessionkey and encrypted session key + if key_exchange + signing_key = Rex::Text.rand_text(16) + enc_session_key = Rex::Proto::NTLM::Crypt::encrypt_sessionkey(signing_key, user_session_key) + else + signing_key = user_session_key + end + + return signing_key, enc_session_key, ntlmssp_flags end - - user_session_key = CRYPT::make_weak_sessionkey(user_session_key,key_size, lanman_weak) - - # Sessionkey and encrypted session key - if key_exchange - signing_key = Rex::Text.rand_text(16) - enc_session_key = CRYPT::encrypt_sessionkey(signing_key, user_session_key) - else - signing_key = user_session_key - end - - return signing_key, enc_session_key, ntlmssp_flags - - end - - - -end -end -end end diff --git a/lib/rex/proto/smb/simpleclient.rb b/lib/rex/proto/smb/simpleclient.rb index aa84ad8323..381620de0e 100644 --- a/lib/rex/proto/smb/simpleclient.rb +++ b/lib/rex/proto/smb/simpleclient.rb @@ -2,6 +2,7 @@ module Rex module Proto module SMB + class SimpleClient require 'rex/text' @@ -14,6 +15,7 @@ require 'rex/proto/smb/utils' require 'rex/proto/smb/client' require 'rex/proto/smb/simpleclient/open_file' require 'rex/proto/smb/simpleclient/open_pipe' +require 'ruby_smb' # Some short-hand class aliases CONST = Rex::Proto::SMB::Constants @@ -26,15 +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 = Rex::Proto::SMB::Client.new(socket) - self.shares = { } + 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 = '', @@ -57,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 = ok['Payload'].v['MaxBuff'] + 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 @@ -72,7 +94,11 @@ attr_accessor :socket, :client, :direct, :shares, :last_share # always a string pass ||= '' - ok = self.client.session_setup(user, pass, domain) + 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 @@ -135,7 +161,13 @@ attr_accessor :socket, :client, :direct, :shares, :last_share def connect(share) ok = self.client.tree_connect(share) - tree_id = ok['Payload']['SMB'].v['TreeID'] + + 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 end @@ -149,14 +181,34 @@ attr_accessor :socket, :client, :direct, :shares, :last_share false 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) - mode = UTILS.open_mode_to_mode(perm) - access = UTILS.open_mode_to_access(perm) + 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) + access = UTILS.open_mode_to_access(perm) - ok = self.client.open(path, mode, access) - file_id = ok['Payload'].v['FileID'] - fh = OpenFile.new(self.client, path, self.client.last_tree_id, file_id) + 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, file_id, self.versions) fh.chunk_size = chunk_size fh end @@ -168,8 +220,14 @@ attr_accessor :socket, :client, :direct, :shares, :last_share def create_pipe(path, perm = 'c') disposition = UTILS.create_mode_to_disposition(perm) ok = self.client.create_pipe(path, disposition) - file_id = ok['Payload'].v['FileID'] - fh = OpenPipe.new(self.client, path, self.client.last_tree_id, file_id) + + 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 6332b41572..69a46e99e7 100644 --- a/lib/rex/proto/smb/simpleclient/open_file.rb +++ b/lib/rex/proto/smb/simpleclient/open_file.rb @@ -1,106 +1,137 @@ # -*- coding: binary -*- -module Rex -module Proto -module SMB -class SimpleClient +module Rex::Proto::SMB + class SimpleClient + # + # This represents an open file, which can be read, written, or closed + # + class OpenFile + attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size, :versions -class OpenFile - attr_accessor :name, :tree_id, :file_id, :mode, :client, :chunk_size - - def initialize(client, name, tree_id, file_id) - self.client = client - self.name = name - self.tree_id = tree_id - self.file_id = file_id - self.chunk_size = 48000 - end - - def delete - begin - self.close - rescue - end - self.client.delete(self.name, self.tree_id) - end - - # Close this open file - def close - self.client.close(self.file_id, self.tree_id) - end - - # Read data from the file - def read(length = nil, offset = 0) - 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'] + 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 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 + close + rescue StandardError + end + client.delete(name, tree_id) + end + + # Close this open file + def close + client.close(file_id, tree_id) + end + + def read_ruby_smb(length, offset) + 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 = '' + fptr = offset + ok = client.read(file_id, fptr, chunk_size) + while ok && ok['Payload'].v['DataLenLow'] > 0 + buff = ok.to_s.slice( + ok['Payload'].v['DataOffset'] + 4, + ok['Payload'].v['DataLenLow'] + ) + data << buff + break if ok['Payload'].v['Remaining'] == 0 + fptr += ok['Payload'].v['DataLenLow'] + + begin + ok = client.read(file_id, fptr, 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 + else + ok = client.read(file_id, offset, length) + data = ok.to_s.slice( + ok['Payload'].v['DataOffset'] + 4, + ok['Payload'].v['DataLenLow'] + ) + 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 - 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 - - def << (data) - self.write(data) - end - - # Write data to the file - def write(data, offset = 0) - # Track our offset into the remote file - fptr = offset - - # Duplicate the data so we can use slice! - data = data.dup - - # Take our first chunk of bytes - chunk = data.slice!(0, self.chunk_size) - - # Keep writing data until we run out - while (chunk.length > 0) - ok = self.client.write(self.file_id, fptr, chunk) - cl = ok['Payload'].v['CountLow'] - - # Partial write, push the failed data back into the queue - if (cl != chunk.length) - data = chunk.slice(cl - 1, chunk.length - cl) + data + def <<(data) + write(data) end - # Increment our painter and grab the next chunk - fptr += cl - chunk = data.slice!(0, self.chunk_size) + # Write data to the file + def write(data, offset = 0) + # Track our offset into the remote file + fptr = offset + + # Duplicate the data so we can use slice! + data = data.dup + + # Take our first chunk of bytes + chunk = data.slice!(0, chunk_size) + + # Keep writing data until we run out + until chunk.empty? + ok = client.write(file_id, fptr, chunk) + if 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 + data = chunk.slice(cl - 1, chunk.length - cl) + data + end + + # Increment our painter and grab the next chunk + fptr += cl + chunk = data.slice!(0, chunk_size) + end + end end end end -end -end -end -end diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 6d7da1c769..04c81877da 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -12,12 +12,6 @@ class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report 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 super( 'Name' => 'SMB File Download Utility', @@ -40,7 +34,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']}'...") @@ -51,7 +45,7 @@ class MetasploitModule < Msf::Auxiliary vprint_status("Trying to download #{remote_path}...") data = '' - fd = simple.open("\\#{remote_path}", 'ro') + fd = simple.open("#{remote_path}", 'o') begin data = fd.read ensure diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index 81a4d11971..85b0b19b25 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -13,12 +13,6 @@ class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report 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 super( 'Name' => 'SMB File Upload Utility', @@ -46,7 +40,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']}'...") @@ -63,7 +57,7 @@ class MetasploitModule < Msf::Auxiliary begin 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'])) fd.write(data) fd.close diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 96cebed974..87ad18dc87 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -17,12 +17,6 @@ class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Scanner 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 super( 'Name' => 'SMB Version Detection', diff --git a/test/modules/auxiliary/scanner/smb/smb_enumshares.json b/test/modules/auxiliary/scanner/smb/smb_enumshares.json new file mode 100644 index 0000000000..3e2e363cbe --- /dev/null +++ b/test/modules/auxiliary/scanner/smb/smb_enumshares.json @@ -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" +} diff --git a/test/modules/auxiliary/scanner/smb/smb_enumusers.json b/test/modules/auxiliary/scanner/smb/smb_enumusers.json new file mode 100644 index 0000000000..2727ab7b2d --- /dev/null +++ b/test/modules/auxiliary/scanner/smb/smb_enumusers.json @@ -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" +} diff --git a/test/modules/exploits/windows/smb/psexec.json b/test/modules/exploits/windows/smb/psexec.json new file mode 100644 index 0000000000..c44300cbdc --- /dev/null +++ b/test/modules/exploits/windows/smb/psexec.json @@ -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" + } +} diff --git a/tools/exploit/psexec.rb b/tools/exploit/psexec.rb index 7533d38d9b..20413fbe68 100755 --- a/tools/exploit/psexec.rb +++ b/tools/exploit/psexec.rb @@ -30,20 +30,6 @@ require 'rex/encoder/ndr' 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) $stderr.puts "[-] #{msg}" end @@ -102,27 +88,20 @@ opt_pass = ARGV.shift() || "" opt_share = "ADMIN$" opt_domain = "." -socket = Rex::Socket.create_tcp({ 'PeerHost' => opt_host, 'PeerPort' => opt_port.to_i }) +begin + 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) +simple = Rex::Proto::SMB::SimpleClient.new(socket, opt_port.to_i == 445, versions = [1, 2]) simple.login( Rex::Text.rand_text_alpha(8), opt_user, opt_pass, 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$") @@ -138,16 +117,12 @@ if (not simple.client.auth_user) exit(1) end - - fname = Rex::Text.rand_text_alpha(8) + ".exe" sname = Rex::Text.rand_text_alpha(8) - # Upload the payload to the share print_status("Uploading payload...") - simple.connect(opt_share) fd = simple.open("\\#{fname}", 'rwct', 500) @@ -177,6 +152,7 @@ print_status("Bound to #{handle} ...") print_status("Obtaining a service manager handle...") scm_handle = nil +NDR = Rex::Encoder::NDR stubdata = NDR.uwstring("\\\\#{opt_host}") + NDR.long(0) +