#!/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.6'

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 reacently 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 e
	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)
		for name in nameList:
			if '\\' in name:
				name = str(name).split('\\')[1]
			sid_list = get_sid(server, user, password)
			if sid_list:
				luser_name = get_user(server, user, password, sid_list)
				k = 0
				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'

			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\tService Enumeration:'
					print '\t\tCompleted'
				process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
				output = process.communicate()[0]
				lines = output.splitlines()
				if lines[2]:
					alldata.append((server, lines[2], luser_name))

			except IndexError:
				alldata.append((server, lines, luser_name))
				continue
			except Exception, e:
				print 'ERROR:'
				print server
				print e
				continue
	print '\n'
	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.STDOUT)
	output_check = process_check.communicate()[0]
	line = output_check.splitlines()

	j = 0
	for i in line:
		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 "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 e

	return identified_users


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)
	for item in alldata:
		try:

			if not item[1][0]:
				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]:
						if not item[1]:
							if item[2]:
								final_set.append((item[0], 'No Service Data', item[2]))
		except Exception,e:
			print e
			if args['b']:
				print e
			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 e

	k = 0
	for item in tiab:

		if k < 1:
			print 'Analysis Completed\n'

		if item[0] == 'NoPid':
			print 'Host: ' + value[0]
			print 'Users Logged In: '
			for name in value[2]:
				print '\t' + name
		if args['a']:
			if item[0] == 'NoPid':
				pass
			else:
				print 'Server: ' + 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':
				pass
			else:
				print 'Server: ' + item[1]
				print 'Logged On: ' + item[2]
				print 'Account: ' + item[7]
				print '\n'
