#!/usr/bin/python

import os
import threading
import subprocess
import re
import argparse
import netaddr
import random
import sys
import socket
import time
import datetime
import traceback
from argparse import RawTextHelpFormatter


__version__ = '1.6.7'

print"""
      (`-.     ('-.      .-')
    _(OO  )_ _(  OO)    ( OO )
,--(_/   ,. (,------,--./ ,-- ,--. ,--.,--.   ,--.  .. --. ,--.  ,--,
\   \   /(__/|  .---|   \ |  ||  .'   /|   `.'   | |  -.  \|   \ |  |
 \   \ /   / |  |   |    \|  ||      / |         . ' '  |  |    \|  |
  \   '   /,(|  '--.|  .     ||     '  |  |'.'|  | | |_.'  |  .     |
   \     /__)|  .--'|  |\    ||  .   \ |  |   |  | |  .-.  |  |\    |
    \   /    |  `---|  | \   ||  |\   \|  |   |  | |  | |  |  | \   |
     `-'     `------`--'  `--'`--' '--'`--'   `--' `--' `--`--'  `--'

Cause We be hunting ghosts!

Used to hunt accounts that could be used for privilege escalation. It will check
services on remote hosts and the current and recently logged in users for
accounts of interest.

Author: Darryl Lane
Twitters: @darryllane101
Github: https://github.com/darryllane/venkman
Version: {}
""".format(__version__)
parser = argparse.ArgumentParser(formatter_class=RawTextHelpFormatter)

group = parser.add_mutually_exclusive_group(required=True)
group2 = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-sl', help='Used to supply a file containing target hosts',  required=False)
group.add_argument('-s', help='Used to supply one target host inline',  required=False)
group.add_argument('-r', help='Used to supply a CIDR range of potential targets',  required=False)
parser.add_argument('-b', help='Debugging',  required=False, action='store_true')
parser.add_argument('-a', help='This will print the details of each identified service',  required=False, action='store_true')
parser.add_argument('-u', help='The username to authenticate to targets Example: DOMAIN\username',  required=True)
parser.add_argument('-p', help='The password to authenticate to targets',  required=True)
group2.add_argument('-nl', help='Supply a file containing target accounts',  required=False)
group2.add_argument('-n', help='Used to supply one target name',  required=False)
args = vars(parser.parse_args())


def percentage(part, whole):
	return int(round(100 * float(part)/float(whole)))


def are_you_up(ip):
	rangei = ['0.1', '0.2', '0.3', '0.4', '0.5']
	try:
		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		socketTimeout = random.choice(rangei)
		s.settimeout(float(socketTimeout))
		s.connect((ip, 135))
		sys.stdout.write("\r")
		return True
	except socket.error, e:
		items = ['/', '\\', '|']
		sys.stdout.write("\r{}".format(random.choice(items)))
		sys.stdout.flush()


def get_ups(serverList):
	ips = []
	print '\nChecking Who\'s Alive\n'
	for ip in serverList:
		if are_you_up(ip):
			ips.append(ip)
	print 'The Following Hosts Are Up.\n'
	for ip in ips:
		print ip
	return ips


def unique(seq):
	seen = set()
	seen_add = seen.add
	return [ x for x in seq if x not in seen and not seen_add(x)]


def name_list(filename):
	try:
		name_list = [line.rstrip('\n') for line in open(filename)]
	except Exception, e:
		print traceback.print_exc()
	return name_list


def server_list(filename):
	if os.path.exists(filename):
		try:
			server_list = [line.rstrip('\n') for line in open(filename)]
		except Exception, e:
			traceback.print_exc()
	else:
		print 'ERROR!\n\tThe Filename: {} in \'-sl\' does not exist.\n\tPlease check and try again.'.format(filename)
		sys.exit()
	return server_list


def action_range(ip_range):
	ip_range_expanded = []
	for ip_addy in netaddr.IPNetwork(ip_range):
		ip_range_expanded.append(str(ip_addy))
	return unique(ip_range_expanded)


def wmi_check(serverList, nameList, user, password):

	alldata = []
	print '\nGathering Data'
	for server in serverList:
		print '\n' + str(server)
		sid_list = get_sid(server, user, password)
		if sid_list[0]:
			luser_name = get_user(server, user, password, sid_list)

			k = 0
			for name in nameList:
				if '\\' in name:
					name = str(name).split('\\')[1]
				elif name == '':
					pass
				else:
					if luser_name:
						for ided_user in luser_name:
							if name.lower() in ided_user.lower():
								print 'Logged In: {}'.format(ided_user)
							else:
								if args['b']:
									if k < 1:
										print '\nVerbose Output:'
									else:
										print '\n\tLogged In: {}'.format(ided_user)
					else:
						luser_name = ['none']
		for name in nameList:
			try:
				cmd='wmic -U \'{u}%{p}\' //{s} "Select DisplayName, PathName, ProcessId, Description, startname from win32_service where startname Like \'%{n}%\'"'.format(s=server,u=user,p=password,n=name)
				if args['b']:
					print '\nVerbose Output\n\tWMIC Command:'
					print '\t\t', cmd
				if args['b']:
					print '\nVerbose Output\n\tService Enumeration:'
					print '\t\tCompleted'
				process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
				output = process.communicate()[0]
				lines = output.splitlines()
				if 'ERROR' in lines[2]:
					pass
				else :
					if lines[2]:
						alldata.append((server, lines[2], luser_name))

			except IndexError:
				if 'ERROR' in lines:
					pass
				else :
					alldata.append((server, lines, luser_name))
					continue
			except Exception, e:
				print 'ERROR:'
				print server
				print traceback.print_exc()
				continue
	print '\n'
	alldata = sorted(alldata)
	return alldata


def get_sid(server,user,password):
	users = []
	connected = False
	command_check = "net rpc registry enumerate 'HKEY_USERS' -S {s} -U '{u}%{p}'".format(s=server,u=user,p=password)
	process_check = subprocess.Popen(command_check, shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
	output_check = process_check.communicate()[0]
	line = output_check.splitlines()

	j = 0
	for i in line:
		if '0x800706ba' in i:
			print 'RPC Unavailable'
		if 'NT_STATUS_UNSUCCESSFUL' in i:
			print 'Unsuccessfull Connection Attempt: {}'.format(server)

		if 'Classes' not in i:
			if args['b']:
				if j < 1:
					print '\nVerbose Output\n\tHKEY_USERS Enumeration:\n'

				j += 1
				print '\t\t' + i
			pattern = r'(.*?)(S\-\d{1,3}\-\d{1,3}\-\d{1,3}\-)(.*)'
			matches = re.match(pattern,i)

			if matches:
				sid = matches.group(2) + matches.group(3)
				connected = True
				users.append((connected, sid))

	if not connected:
		sid = 'Unknown'
		return (connected, sid)
	return (users)



def get_user(server, user, password, sid_list):
	identified_users = []
	for sid_user in sid_list:
		try:
			if sid_user == False:
				raise Exception('NoUserError')
			command_check = 'rpcclient -U "{u}%{p}" {s} -c "lookupsids {sid}"'.format(s=server,u=user,p=password, sid=sid_user[1])
			process_check = subprocess.Popen(command_check, shell=True, stdout=subprocess.PIPE,stderr=subprocess.STDOUT)
			output_check = process_check.communicate()[0]
			line = output_check.splitlines()
			j = 0
			for i in line:
				if 'INVALID' in i:
					user_name = 'None'
				else:
					if "Can't load" not in i:
						pattern = r'(.*?)\s(.*)\s'
						matches = re.match(pattern,i)
						if args['b']:
							if j < 1:
								print '\nVerbose Output\n\tSID Lookups:\n'
							j += 1
							print '\t\tSID: ' + i.split(' ')[0]
						if matches:
							user_name = matches.group(2)
							if args['b']:
								if j < 1:
									print '\nVerbose Output\n\tIdentified Users:\n'
								j += 1
								print '\t\tUser: ' + user_name
							identified_users.append(user_name)
		except Exception('NoUserError'):
			continue
		except Exception,e:
			if 'NoUserError' in e:
				continue
			else:
				print traceback.print_exc()

	return identified_users

def clean(data_list):
	results = []
	for item in data_list:
		if item not in results:
			results.append(item)

	return results


if __name__ == "__main__":

	user = args['u']
	password = args['p']
	if args['sl']:
		serverList = server_list(args['sl'])
		serverList = get_ups(serverList)
	if args['s']:
		serverList = [args['s'], ]
	if args['r']:
		start_time_scope = time.time()
		for item in args['r']:
			if ',' in item:
				args['r'].append(item.strip(','))
				args['r'].remove(item)
		serverList = action_range(args['r'])
		serverList = get_ups(serverList)

		time_spent_total = time.time() - start_time_scope
		timetaken = str(datetime.timedelta(seconds=(time_spent_total))).split('.')[0]
		print '\nComplete: {}'.format(timetaken)
	if args['nl']:
		nameList = name_list(args['nl'])
	if args['n']:
		nameList = [args['n'], ]
	services = []
	final_set = []
	tiab = []
	alldata = wmi_check(serverList, nameList, user, password)
	alldata = clean(alldata)
	if args['b']:
		print 'Verbose Output:'
		print '\t', alldata
	for item in alldata:
		if args['b']:
			print 'Verbose Output:'
			print '\t', item
		try:
			if isinstance(item[1][0], list):
				if 'error' in str(item[1][0]).lower():
					pass
				else:
					final_set.append((item[0], str(item[1][0]).split('|'), item[2]))
			else:
				if 'error' in str(item[1]).lower():
					pass
				else:
					final_set.append((item[0], item[1].split('|'), item[2]))
				continue

		except IndexError:
			for name in nameList:
				if not item[2][0]:
					pass
				else:
					if name in item[2][0]:
						final_set.append((item[0], 'No Service Data', item[2]))
		except Exception,e:
			print traceback.print_exc()
			if args['b']:
				print traceback.print_exc()
			continue

	for value in final_set:
		if value[1] == 'No Service Data':
			tiab.append(('NoPid', value[0], value[2], 'NoState', 'NoDisplay', 'NoDescription', 'NoPath', 'NoIdAccount'))
		else:
			try:
				Description = value[1][0]
				DisplayName = value[1][1]
				IdAccount = value[1][5]
				PathName = value[1][3]
				pid = value[1][4]
				if pid == '0':
					state = 'stopped'
				else:
					state = 'running'
				tiab.append((pid, value[0], value[2], state, DisplayName, Description, PathName, IdAccount))
			except IndexError:
				continue
			except Exception,e:
				print traceback.print_exc()

	k = 0
	for item in tiab:
		if k < 1:
			print '\nService Analysis Completed\n'

		if args['a']:
			if item[0] == 'NoPid':
				print 'Host: ' + item[1]
				print 'Users Logged In: '
				for name in item[2]:
					print '\t' + name
			else:
				print '\nServer: ' + item[1]
				print 'Logged On:'
				if item[2]:
					for name in item[2]:
						print '\t' + name
				print 'Account: ' + item[7]
				print 'Service Name: ' + item[4]
				print 'Description: ' + item[5]
				print 'ServicePath: ' + item[6]
				print 'PID: ' + item[0]
				print 'State: ' + item[3]
				print '\n'
		else:
			if item[0] == 'NoPid':
				print 'Host: ' + item[1]
				print 'Users Logged In: '
				for name in item[2]:
					print '\t' + name
			else:
				print '\nServer: ' + item[1]
				print 'Logged On: '
				for name in item[2]:
					print '\t' + name
				print 'Service Account: ' + item[7]
				print '\n'
		k += 1