Land #2915, @Meatballs1 improvements for LDAP post mixin

bug/bundler_fix
jvazquez-r7 2014-02-17 19:14:59 -06:00
commit f07efc91a8
5 changed files with 142 additions and 32 deletions

View File

@ -5,6 +5,64 @@ module Windows
module Accounts
GUID = [
['Data1',:DWORD],
['Data2',:WORD],
['Data3',:WORD],
['Data4','BYTE[8]']
]
DOMAIN_CONTROLLER_INFO = [
['DomainControllerName',:LPSTR],
['DomainControllerAddress',:LPSTR],
['DomainControllerAddressType',:ULONG],
['DomainGuid',GUID],
['DomainName',:LPSTR],
['DnsForestName',:LPSTR],
['Flags',:ULONG],
['DcSiteName',:LPSTR],
['ClientSiteName',:LPSTR]
]
##
# get_domain(server_name=nil)
#
# Summary:
# Retrieves the current DomainName the given server is
# a member of.
#
# Parameters
# server_name - DNS or NetBIOS name of the remote server
# Returns:
# The DomainName of the remote server or nil if windows
# could not retrieve the DomainControllerInfo or encountered
# an exception.
#
##
def get_domain(server_name=nil)
domain = nil
result = session.railgun.netapi32.DsGetDcNameA(
server_name,
nil,
nil,
nil,
0,
4)
begin
dc_info_addr = result['DomainControllerInfo']
unless dc_info_addr == 0
dc_info = session.railgun.util.read_data(DOMAIN_CONTROLLER_INFO, dc_info_addr)
pointer = session.railgun.util.unpack_pointer(dc_info['DomainName'])
domain = session.railgun.util.read_string(pointer)
end
ensure
session.railgun.netapi32.NetApiBufferFree(dc_info_addr)
end
domain
end
##
# delete_user(username, server_name = nil)
#

View File

@ -8,6 +8,7 @@ module LDAP
include Msf::Post::Windows::Error
include Msf::Post::Windows::ExtAPI
include Msf::Post::Windows::Accounts
LDAP_SIZELIMIT_EXCEEDED = 0x04
LDAP_OPT_SIZELIMIT = 0x03
@ -83,31 +84,49 @@ module LDAP
super
register_options(
[
OptString.new('DOMAIN', [false, 'The domain to query.', nil]),
OptInt.new('MAX_SEARCH', [true, 'Maximum values to retrieve, 0 for all.', 50]),
OptString.new('FIELDS', [true, 'FIELDS to retrieve.', nil]),
OptString.new('FILTER', [true, 'Search filter.', nil])
OptString.new('DOMAIN', [false, 'The domain to query or distinguished name (e.g. DC=test,DC=com)', nil]),
OptInt.new('MAX_SEARCH', [true, 'Maximum values to retrieve, 0 for all.', 500]),
], self.class)
end
# Converts a Distinguished Name to DNS name
#
# @param [String] Distinguished Name
# @return [String] DNS name
def dn_to_domain(dn)
if dn.include? "DC="
return dn.gsub(',','').split('DC=')[1..-1].join('.')
else
return dn
end
end
# Performs an ldap query
#
# @param [String] LDAP search filter
# @param [Integer] Maximum results
# @param [Array] String array containing attributes to retrieve
# @param [String] Optional domain or distinguished name
# @return [Hash] Entries found
def query(filter, max_results, fields)
default_naming_context = datastore['DOMAIN']
default_naming_context ||= get_default_naming_context
vprint_status("Default Naming Context #{default_naming_context}")
def query(filter, max_results, fields, domain=nil)
domain ||= datastore['DOMAIN']
domain ||= get_domain
if domain.blank?
raise RuntimeError, "Unable to find the domain to query."
end
if load_extapi
return session.extapi.adsi.domain_query(default_naming_context, filter, max_results, DEFAULT_PAGE_SIZE, fields)
return session.extapi.adsi.domain_query(domain, filter, max_results, DEFAULT_PAGE_SIZE, fields)
else
unless default_naming_context.include? "DC="
raise RuntimeError.new("DOMAIN must be specified as distinguished name e.g. DC=test,DC=com")
if domain and domain.include? "DC="
default_naming_context = domain
domain = dn_to_domain(domain)
else
default_naming_context = get_default_naming_context(domain)
end
bind_default_ldap_server(max_results) do |session_handle|
bind_default_ldap_server(max_results, domain) do |session_handle|
return query_ldap(session_handle, default_naming_context, 2, filter, fields)
end
end
@ -115,14 +134,15 @@ module LDAP
# Performs a query to retrieve the default naming context
#
def get_default_naming_context
bind_default_ldap_server(1) do |session_handle|
def get_default_naming_context(domain=nil)
bind_default_ldap_server(1, domain) do |session_handle|
print_status("Querying default naming context")
query_result = query_ldap(session_handle, "", 0, "(objectClass=computer)", ["defaultNamingContext"])
first_entry_fields = query_result[:results].first
# Value from First Attribute of First Entry
default_naming_context = first_entry_fields.first
vprint_status("Default naming context #{default_naming_context}")
return default_naming_context
end
end
@ -299,13 +319,14 @@ module LDAP
client.railgun.wldap32
end
# Binds to the default LDAP Server
# @param [int] the maximum number of results to return in a query
# @return [LDAP Session Handle]
def bind_default_ldap_server(size_limit)
def bind_default_ldap_server(size_limit, domain=nil)
vprint_status ("Initializing LDAP connection.")
init_result = wldap32.ldap_sslinitA("\x00\x00\x00\x00", 389, 0)
# If domain is still null the API may be able to handle it...
init_result = wldap32.ldap_sslinitA(domain, 389, 0)
session_handle = init_result['return']
if session_handle == 0
raise RuntimeError.new("Unable to initialize ldap server: #{init_result["ErrorMessage"]}")
@ -321,7 +342,6 @@ module LDAP
bind = bind_result['return']
unless bind == 0
vprint_status("Unbinding from LDAP service")
wldap32.ldap_unbind(session_handle)
raise RuntimeError.new("Unable to bind to ldap server: #{ERROR_CODE_TO_CONSTANT[bind]}")
end

View File

@ -3668,11 +3668,11 @@ class Def_kernel32
# ])
dll.add_function( 'lstrlenA', 'DWORD',[
["PCHAR","lpString","in"],
["LPVOID","lpString","in"],
])
dll.add_function( 'lstrlenW', 'DWORD',[
["PWCHAR","lpString","in"],
["LPVOID","lpString","in"],
])

View File

@ -12,6 +12,19 @@ class Def_netapi32
def self.create_dll(dll_path = 'netapi32')
dll = DLL.new(dll_path, ApiConstants.manager)
dll.add_function('NetApiBufferFree','DWORD',[
["LPVOID","Buffer","in"]
])
dll.add_function('DsGetDcNameA', 'DWORD',[
["PWCHAR","ComputerName","in"],
["PWCHAR","DomainName","in"],
["PBLOB","DomainGuid","in"],
["PWCHAR","SiteName","in"],
["DWORD","Flags","in"],
["PDWORD","DomainControllerInfo","out"]
])
dll.add_function('NetUserDel', 'DWORD',[
["PWCHAR","servername","in"],
["PWCHAR","username","in"],

View File

@ -341,7 +341,7 @@ class Util
# See #unpack_pointer
#
def is_null_pointer(pointer)
if pointer.class == String
if pointer.kind_of? String
pointer = unpack_pointer(pointer)
end
@ -375,6 +375,26 @@ class Util
return str
end
#
# Reads null-terminated ASCII strings from memory.
#
# Given a pointer to a null terminated array of CHARs, return a ruby
# String. If +pointer+ is NULL (see #is_null_pointer) returns an empty
# string.
#
def read_string(pointer, length=nil)
if is_null_pointer(pointer)
return ''
end
unless length
length = railgun.kernel32.lstrlenA(pointer)['return']
end
chars = read_array(:CHAR, length, pointer)
return chars.join('')
end
#
# Read a given number of bytes from memory or from a provided buffer.
#
@ -437,7 +457,7 @@ class Util
return raw.unpack('l').first
end
#If nothing worked thus far, return it raw
#If nothing worked thus far, return it raw
return raw
end
@ -498,7 +518,7 @@ class Util
# Returns true if the type passed describes a data structure, false otherwise
def is_struct_type?(type)
return type.class == Array
return type.kind_of? Array
end
@ -513,10 +533,13 @@ class Util
return pointer_size
end
if is_array_type?(type)
element_type, length = split_array_type(type)
return length * sizeof_type(element_type)
if type.kind_of? String
if is_array_type?(type)
element_type, length = split_array_type(type)
return length * sizeof_type(element_type)
else
return sizeof_type(type.to_sym)
end
end
if is_struct_type?(type)
@ -559,10 +582,8 @@ class Util
def struct_offsets(definition, offset)
padding = 0
offsets = []
definition.each do |mapping|
key, data_type = mapping
if sizeof_type(data_type) > padding
offset = offset + padding
end
@ -570,7 +591,6 @@ class Util
offsets.push(offset)
offset = offset + sizeof_type(data_type)
padding = calc_padding(offset)
end
@ -606,12 +626,11 @@ class Util
if type =~ /^(\w+)\[(\w+)\]$/
element_type = $1
length = $2
unless length =~ /^\d+$/
length = railgun.const(length)
end
return element_type, length
return element_type.to_sym, length.to_i
else
raise "Can not split non-array type #{type}"
end