GitList
from display import *
from fileOps import *
from completer import *

import readline
import threading
import time

configDir = "./config/"

display = Display()
fileOps = FileOps(configDir)
completer = Completer()

class State():
	prevState = None
	currentState = None
	selection = None
	suppliedVal = None
	transMap = {}
	
	def transition(self, selection, suppliedVal = None):
	
		try:
			nextState = eval(self.transMap[selection])
		except KeyError:
			nextState = eval(self.currentState)

		nextState.selection = selection
		nextState.suppliedVal = suppliedVal
		nextState.prevState = self.__class__.__name__
		nextState.currentState = nextState().__class__.__name__

		return(nextState().run())

	def run(self):
		readline.set_completer(completer.check)
		readline.set_completer_delims("")
		readline.parse_and_bind("tab: complete")


class Intro(State):
	transMap = {"help": "Help", "exit": "Exit", "generateAll": "ConfigAllEdit"}

	def firstRun(self):
		self.currentState = "Intro" #seed currentState to return here if invalid selection is set, this is auto preformed in transistion() for future states 
		display.clear()
		display.init()
		input("{0}Enter any key to begin, \"help\", or \"exit\" at any time: {1}".format(display.GREEN, display.ENDC))
		self.run()

	def run(self):
		super().run()

		display.clear()
		display.prompt("Loaded modules from: {0}\n".format(fileOps.getConfigDir()))

		configs = fileOps.getConfigs()
		for i, f in enumerate(configs):
			display.prompt("{0}\t[{1}]{2}  ".format(display.GREEN, i, display.ENDC), '')
			display.prompt(f)
			self.transMap[f] = "ConfigEdit"

		display.prompt("\n\tor\n\n\t{0}generateAll{1}".format(display.GREEN, display.ENDC))
		completer.setCommands(list(self.transMap.keys()))

		#display.prompt("\nPlease select a module to use: ", '')

		selection = input("\nPlease select a module to use: ")
		if selection.isdigit():
			selection = configs[int(selection)]

		fileOps.loadConfig(selection)
		self.transition(selection)

class ConfigAllEdit(State):
	#This code intentionally obfuscated to prevent IP theft... no really, why are you laughing...
	#luckily this can be added withouut changing any other code
	#once there's time an actually designed class with the same inputs/outputs can be swapped in

	transMap = {"exit": "Exit", "menu": "Intro", "help": "Help", "generate": "GenerationPrompt"}
	optionsMap = {}
	multipleApplicable = {}
	configMap = []
	configsLoaded = False
	multipleSelection = False
	multipleOption = ''
	multipleIndex = []
	setValue = ''
	genInProgress = False
	generationIndex = []

	def run(self):
		super().run()
		display.clear()
		completer.setCommands(list(self.transMap.keys()))
		
		if ConfigAllEdit.genInProgress:
			self.generateAll()

		display.prompt("Configure all supported payloads for network detection testing\n")

		optionNum = 0
		if not self.configsLoaded:
			for config in fileOps.getConfigs():
				curConfig = fileOps.loadConfig(config)
				display.prompt("{0}\n{1}\n".format(config, curConfig.get("Type", "info")))
				optionNum = self.parse(curConfig, optionNum)
				self.configMap.append({curConfig.get("Type", "name"): config, "config": curConfig}) #sometimes you write a line and it's hard to keep a straight face
			ConfigAllEdit.configsLoaded = True

		elif self.configsLoaded:
			name = ''
			for entry in self.configMap:
				for i in list(entry.keys()):
					if i != "config":
						name = entry[i]
				curConfig = entry["config"]
				display.prompt("{0}\n{1}\n".format(name, curConfig.get("Type", "info")))
				optionNum = self.parse(curConfig, optionNum)

		display.prompt("Or\nSet for all applicable payloads\n")


		tempDict = {}
		for option in self.multipleApplicable:
			numSpaces = 40
			numSpaces = numSpaces - (len(str(optionNum))+len(option))
			display.prompt("\t{0}[{1}] {2}:{3}{4}{5}".format(display.GREEN, optionNum, option, 
									  display.ENDC, ' '*numSpaces, self.multipleApplicable[option]))
			tempDict[str(optionNum)] = option
			optionNum = optionNum+1
		self.multipleApplicable = {**self.multipleApplicable, **tempDict} #...I have nothing to say for myself
	
		

		if self.multipleSelection:
			self.editMultipleEntries()
	
		selection = input("\nSelect an option to edit, {0}generate{1}, or {2}exit{3}: ".format(display.GREEN, display.ENDC, display.GREEN, display.ENDC))
	
		singleSelection = False
	
		if selection.startswith("set "):
			option = selection.split(" ")[1]
			self.suppliedVal = selection.split(option+" ", 1)[-1]
	
			if self.suppliedVal == "":
				selection = "invalid"

			selection = option
		
		if selection.isdigit():
			try:
				selection = self.optionsMap[selection]
				singleSelection = True
				for i in self.configMap:
					try:
						if i[selection["config"]]:
							fileOps.setCurrentConfig(i["config"]) #Donald Knuth forgive me
					except KeyError:
						continue
				selection = selection["option"]
			except KeyError:
				try:
					selection = self.multipleApplicable[selection]
					ConfigAllEdit.multipleSelection = True
					ConfigAllEdit.multipleOption = selection
					ConfigAllEdit.setValue = self.suppliedVal
				except KeyError:
					selection = "invalid"

		else: 
			if selection == "generate":
				ConfigAllEdit.genInProgress = True
				self.generateAll()

			#TODO: burn this with fire also it exits if you enter a bad option name
			elif selection == "exit" or selection == "help" or selection == "menu" or selection == "":
				self.transition(selection)
				
			elif selection in list(self.multipleApplicable.keys()):
				ConfigAllEdit.multipleSelection = True
				ConfigAllEdit.multipleOption = selection
				ConfigAllEdit.setValue = self.suppliedVal

			else:
				for i in list(self.optionsMap.keys()):
					name = selection[:selection.find('.')]
					option = selection[selection.find('.')+1:]
					if self.optionsMap[i]["config"] == name and self.optionsMap[i]["option"] == option:
						for j in self.configMap:
							try:
								if j[name]:
									fileOps.setCurrentConfig(j["config"])
									selection = option
									singleSelection = True
							except KeyError:
								continue
			
					


		if singleSelection:
			self.transition(selection, self.suppliedVal)
		
		if self.multipleSelection:
			self.editMultipleEntries()


	def editMultipleEntries(self): #Marcus did a bad thing
			if ConfigAllEdit.setValue == '' or ConfigAllEdit.setValue == None:
				ConfigAllEdit.setValue = input("Please enter a value for "+ConfigAllEdit.multipleOption+": ")

			for option in self.optionsMap:
				#input(self.optionsMap[option]["option"]+" "+self.optionsMap[option]["config"]+" "+ConfigAllEdit.multipleOption)
				if self.optionsMap[option]["option"] == ConfigAllEdit.multipleOption:
					for j in self.configMap:
						try:
							if j[self.optionsMap[option]["config"]] not in ConfigAllEdit.multipleIndex:
								fileOps.setCurrentConfig(j["config"])
								ConfigAllEdit.multipleIndex.append(j[self.optionsMap[option]["config"]])
								self.transition(self.multipleOption, ConfigAllEdit.setValue)
						except KeyError:
							continue
			ConfigAllEdit.multipleSelection = False
			ConfigAllEdit.multipleIndex = []
			ConfigAllEdit.multipleOption = ''
			ConfigAllEdit.setValue = ''
			
	def parse(self, config, curOptionNum):

		cfgName = ''
		for section_name in config:

			section = config[section_name]
			if section_name == "DEFAULT":
				continue

			if section_name == "Type":
				cfgName = section["name"]
				continue

			if section_name == "Output":
				continue
			

			#TODO: this dies if one of the cfgs has a .swp file in the same dir
			numTabs = 50
			numTabs = numTabs - (len(cfgName)+len(section_name)+len(str(curOptionNum)))

			
			display.prompt("{0}\t[{1}] {2}.{3}:{4}{5}{6}".format(display.GREEN, curOptionNum, cfgName, section_name, display.ENDC, '-'*numTabs, section["var"]))
		
			
			self.transMap[section_name] = "OptionEdit"
			self.optionsMap[str(curOptionNum)] = {"config": cfgName, "option": section_name}
			completer.addCommand(cfgName+'.'+section_name)
			completer.addCommand("set " + cfgName+'.'+section_name)
			completer.addCommand("set " + str(curOptionNum))
			curOptionNum += 1
			
			#if list(self.optionsMap.values()).count(section_name) > 1 and section_name not in self.multipleApplicable.values():
			#	self.multipleApplicable[section_name] = section["var"]

			if section_name not in self.multipleApplicable.values():
				count = 0
				for i in list(self.optionsMap.values()):
					if i["option"] == section_name:
						count = count+1
					if count > 1:
						self.multipleApplicable[section_name] = section["var"]

		display.prompt("")


		return(curOptionNum)

	def generateAll(self):
		for i in self.configMap:
			if i["config"] not in ConfigAllEdit.generationIndex:
				fileOps.setCurrentConfig(i["config"])
				i["config"]["Output"]["var"] = "./GenerateAll/" + i["config"]["Output"]["var"]
				ConfigAllEdit.generationIndex.append(i["config"])
				self.transition("generate")
		ConfigAllEdit.genInProgress = False
		ConfigAllEdit.generationIndex = []
		self.transition("exit")

class ConfigEdit(State):
	transMap = {"exit": "Exit", "menu": "Intro", "help": "Help", "generate": "GenerationPrompt"}	
	optionsMap = {}	#will become a dict of {"0": "optionA" "1": "optionB"}
			#allows number input since the 0th actual content of config
			#will likely be DEFAULT, help or type data	
	
	def run(self):
		super().run()

		display.clear()
		display.prompt("Payload Editor\n")
		
		config = fileOps.getCurrentConfig()

		completer.setCommands(list(self.transMap.keys()))
		self.parse(config)

		selection = input("Select an option to edit, {0}generate{1}, or {2}exit{3}: ".format(display.GREEN, display.ENDC, display.GREEN, display.ENDC))
	
		#Not sure if these checks are needed	
		if selection.startswith("set "):
			option = selection.split(" ")[1]
			self.suppliedVal = selection.split(option+" ", 1)[-1]
	
			if self.suppliedVal == "":
				selection = "invalid"

			selection = option
		
		if selection.isdigit():
			try:
				selection = self.optionsMap[selection]
			except KeyError:
				selection = "invalid"

		self.transition(selection, self.suppliedVal)
			

	def parse(self, config):

		optionNum = 0
		for section_name in config:

			if section_name == "DEFAULT":
				continue

			section = config[section_name]
			if section_name == "Type":
				display.prompt("Selected Payload: {0}\n".format(section["info"]))

			else:
				numTabs = 1
				if len(section_name) < 12: numTabs = 2

				display.prompt("{0}\t[{1}] {2}:{3}{4}{5}".format(display.GREEN, optionNum, section_name, display.ENDC, '\t'*numTabs, section["var"]))
				
				self.transMap[section_name] = "OptionEdit"
				self.optionsMap[chr(optionNum+48)] = section_name
				completer.addCommand(section_name)
				completer.addCommand("set " + section_name)
				completer.addCommand("set " + chr(optionNum+48))
				optionNum += 1
			
			section = config[section_name]
		display.prompt("")


class OptionEdit(State):
	transMap = {"exit": "Exit", "ConfigEdit": "ConfigEdit", "ConfigAllEdit": "ConfigAllEdit"}

	validParams = []
	
	def run(self):
		config = fileOps.getCurrentConfig()
		option = config[self.selection]
		self.validParams = self.parseOptions(option)

		if self.suppliedVal == None:
			outstring = ''
			outstring = outstring + "Enter a value for [{0}]: (Valid options are: [".format(self.selection)
			for param in self.validParams:
				outstring = outstring + "\'{0}{1}{2}\'".format(display.GREEN, param, display.ENDC)
				
				if param != self.validParams[-1]:
					outstring = outstring + ', '

			outstring = outstring + "]): "			

			self.suppliedVal = input(outstring)

		if self.suppliedVal in self.validParams or "allowWilds" in self.validParams:
			fileOps.updateCurrentConfig(self.selection, self.suppliedVal)

		self.transition(self.prevState)

		
		#TODO: Move valid param checking to fileops	
	def parseOptions(self, option):
		validParams = []

		#TODO: just found this var, I don't know why it's here	
		optionString = 	""
		for validParam in option:

			if validParam == "var" or validParam == "help":
				continue

			validParams.append(validParam)

		return validParams				



class GenerationPrompt(State):

	def run(self):
		config = fileOps.getCurrentConfig()
		t1 = threading.Thread(target = fileOps.generate, args = [config])

		t1.start()

		i = 1
		end = ['/', '-', '\\', '|']
		while t1.is_alive():
			display.prompt("Generating: "+"="*i+end[i%4], '\r')
			time.sleep(0.3)
			i = i+1

		t1.join()
		display.prompt("{0}Generating: ||{1}||{2}\n".format(display.GREEN, '='*i, display.ENDC))

		info = config["Type"]["runInfo"]
		display.prompt("{0}Execute with: {1}".format(display.GREEN, display.ENDC), '')
		display.prompt(info, '\n\n')
		

class Help(State):
	transMap = {"Help": "Help", "Intro": "Intro", "ConfigEdit": "ConfigEdit", "ConfigAllEdit": "ConfigAllEdit", "GenerationPrompt": "GenerationPrompt"}
	
	def run(self):
		display.clear()
			
		if self.prevState == "Intro":
			display.prompt("Select a payload module by index ['#'] or name\n\tValid options are:\n")
		
			#TODO this is used in multiple states, move it to super	
			configs = fileOps.getConfigs()
			for i, f in enumerate(configs):
				conf = fileOps.loadConfig(f)
				helpStr = ''
				numTabs = 1
				if len(f) < 19: numTabs = 2

				try:
					helpStr = conf.get("Type", "info")	
				except Exception:
					helpStr = ''

				display.prompt("{0}\t[{1}]  {2}{3}{4}{5}".format(display.GREEN, i, f, display.ENDC, '\t'*numTabs, helpStr))

			display.prompt("\n{0}\tgenerateAll{1}\t\t\tGenerate all payloads for network testing".format(display.GREEN, display.ENDC))
			

		elif self.prevState == "ConfigEdit":
			config = fileOps.getCurrentConfig()
			optionNum = 0
			for section_name in config:

				if section_name == "DEFAULT":
					continue

				section = config[section_name]
				if section_name == "Type":
					display.prompt("The selected payload: {0}{1}{2}\nhas the following options:\n".format(display.GREEN, section["info"], display.ENDC))
					display.prompt("\tOptionName\t\tDefault Value\t\tAllowed Values")

				else:
					allowedValSpaces = 24
					allowedValSpaces = allowedValSpaces-len(str(section["var"]))
					validParams = []
				
					for validParam in section:

						if validParam == "var" or validParam == "help":
							continue

						validParams.append(validParam)
					
					helpStrSpaces = 43-len(str(validParams))

					numTabs = 1
					if len(section_name) < 12: numTabs = 2

					helpStr = ""
					try:
						helpStr = section["help"]
					except KeyError:
						helpStr = ""

					display.prompt("{0}\t[{1}] {2}:{3}{4}{5}{6}{7}{8}{9}".format(display.GREEN, optionNum, section_name, display.ENDC, 
												'\t'*numTabs, section["var"], ' '*allowedValSpaces, str(validParams),
												' '*helpStrSpaces, helpStr))
					optionNum += 1
				
				section = config[section_name]

			#TODO: just realized we can reuse color vars {0} and {1}, need to apply throughout
			display.prompt("\nValues may be changed by entering: {0}#{1}, {0}OptionName{1}, {0}set # value{1}, or {0}set OptionName value{1}".format(display.GREEN, display.ENDC))


			
		
		elif self.prevState == "ConfigAllEdit":
			display.prompt("The Generate All editor is used to generate all payloads, and an accompanying execution script.")
			display.prompt("It's primary use is to test the application whitlisting solution on your network against the")
			display.prompt("newest application whitelisting bypasses.\n")

			display.prompt("After setting each payload for a testing C2 server, {0}generate{1} can be entered.".format(display.GREEN, display.ENDC))
			display.prompt("The payloads, and an accompanying powershell test script, will be output in ./GenerateAll/")
			display.prompt("Copy this folder to a testing Windows box and execute the script to see which bypasses you are vulnerable to.")
			
	
			display.prompt("\n\nFor detailed information on each payload's options enter {0}menu{1}, select the payload, then enter {0}help{1}".format(display.GREEN, display.ENDC))

		elif self.prevState == "GenerationPrompt":
			display.prompt("Shouldn't be able to get here")

		elif self.prevState == "Help":
			display.prompt("Shouldn't be able to get here")

		display.prompt("\nYou may enter {0}menu{1} at any time to return to the inital payload selection.\n".format(display.GREEN, display.ENDC))

		#hacky
		selection = input("Enter any key to return to the previous menu: ")
		if selection == "menu":
			self.prevState = "Intro"

		self.transition(self.prevState)



class Exit(State):

	def run(self):
		exit(0)
	

def main():
	intro = Intro()
	intro.firstRun()
	


if __name__ == '__main__':
	try:
		main()

	except KeyboardInterrupt:
		print('')
		exit(0)
	
	except EOFError:
		print('')
		exit(0)