When Out-ObfuscatedTokenCommand.ps1 attempts to find $ParameterValidationAttributesToTreatAsScriptblock, it misses some of the longer validation options.
I discovered this issue while attempting to obfuscate modules in the Empire project.
PS> git clone https://github.com/danielbohannon/Invoke-Obfuscation.git
PS> wget https://github.com/adaptivethreat/Empire/raw/master/data/module_source/credentials/Invoke-TokenManipulation.ps1
PS> Import-Module .\Invoke-Obfuscation\Invoke-Obfuscation.psm1
[*] Validating necessary commands are loaded into current PowerShell session.
[*] Function Loaded :: Out-ObfuscatedTokenCommand
[*] Function Loaded :: Out-ObfuscatedStringCommand
[*] Function Loaded :: Out-EncodedAsciiCommand
[*] Function Loaded :: Out-EncodedHexCommand
[*] Function Loaded :: Out-EncodedOctalCommand
[*] Function Loaded :: Out-EncodedBinaryCommand
[*] Function Loaded :: Out-SecureStringCommand
[*] Function Loaded :: Out-EncodedBXORCommand
[*] Function Loaded :: Out-PowerShellLauncher
[*] Function Loaded :: Invoke-Obfuscation
[*] All modules loaded and ready to run Invoke-Obfuscation
PS> Out-ObfuscatedTokenCommand -Path .\Invoke-TokenManipulation.ps1 | Out-File out
Exception calling "Create" with "1" argument(s): "At line:657 char:17
+ ... ("{2}{5}{6}{1}{4}{3}{0}"-f 'ege','eP','Se','efilePrivil', ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block.
At line:658 char:17
+ ... ("{3}{0}{1}{2}" -f 'DebugPr','ivi','lege','Se'), {"{6}{ ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block.
At line:659 char:17
+ ... ("{3}{2}{5}{4}{0}{6}{1}"-f 'i','ege','seQ','SeIncrea','ta ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block.
At line:660 char:17
+ ... ("{2}{1}{3}{4}{0}" -f'ilege','Vol','SeManage','umePri','v ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block.
At line:661 char:17
+ ... ("{4}{5}{1}{0}{2}{3}"-f'vi','i','l','ege','SeS','ecurityP ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block.
At line:662 char:17
+ ... ("{2}{6}{3}{1}{4}{0}{5}" -f 'P','im','Se','ystemt','e','r ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block.
At line:663 char:17
+ ... ("{1}{2}{0}{3}" -f'rivil','SeU','ndockP','ege'), {"{2}{ ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Attribute argument must be a constant or a script block."
(errors continue)
Upon further inspection, I was able to reduce the Invoke-TokenManipulation.ps1 file to a minified version that still reproduces the error.
It seems the long ValidateSet is not being caught as a 'ParameterValidationAttributesToTreatAsScriptblock', even though it is in the list of attributes.
This portion of the code in Out-ObfuscatedTokenCommand.ps1 seems to be the culprit:
$EncapsulateAsScriptBlockInsteadOfParentheses = $FALSE
# If dealing with ObfuscationLevel -gt 1 (e.g. -f format operator), perform check to see if we're dealing with a string that is part of a Parameter Binding.
If(($ObfuscationLevel -gt 1) -AND ($Token.Start -gt 5) -AND ($ScriptString.SubString($Token.Start-5,5).Contains('(') -OR $ScriptString.SubString($Token.Start-5,5).Contains(',')) -AND $ScriptString.SubString(0,$Token.Start).Contains('[') -AND $ScriptString.SubString(0,$Token.Start).Contains('('))
{
# Gather substring preceding the current String token to see if we need to treat the obfuscated string as a scriptblock.
$ParameterBindingName = $ScriptString.SubString(0,$Token.Start)
$ParameterBindingName = $ParameterBindingName.SubString(0,$ParameterBindingName.LastIndexOf('('))
$ParameterBindingName = $ParameterBindingName.SubString($ParameterBindingName.LastIndexOf('[')+1).Trim()
# Filter out values that are not Parameter Binding due to contain whitespace, some special characters, etc.
If(!$ParameterBindingName.Contains(' ') -AND !$ParameterBindingName.Contains('.') -AND !$ParameterBindingName.Contains(']') -AND !($ParameterBindingName.Length -eq 0))
{
# If we have a match then set boolean to True so result will be encapsulated with curly braces at the end of this function.
If($ParameterValidationAttributesToTreatStringAsScriptblock -Contains $ParameterBindingName.ToLower())
{
$EncapsulateAsScriptBlockInsteadOfParentheses = $TRUE
}
}
}
Replacing the previous code with this modified version seems to fix the issue:
$EncapsulateAsScriptBlockInsteadOfParentheses = $FALSE
$LastEndBracketIndex = $ScriptString.LastIndexOf(']', $Token.Start)
$LastBeginBracketIndex = $ScriptString.LastIndexOf('[', $Token.Start)
If($LastBeginBracketIndex -gt $LastEndBracketIndex) {
$ScriptSubString = $ScriptString.SubString($LastBeginBracketIndex, $Token.Start-$LastBeginBracketIndex)
# If dealing with ObfuscationLevel -gt 1 (e.g. -f format operator), perform check to see if we're dealing with a string that is part of a Parameter Binding.
If(($ObfuscationLevel -gt 1) -AND ($Token.Start -gt 5) -AND ($ScriptSubString.Contains('(')) )
{
# Gather substring preceding the current String token to see if we need to treat the obfuscated string as a scriptblock.
$ParameterBindingName = $ScriptSubString.SubString(0,$ScriptSubString.LastIndexOf('('))
$ParameterBindingName = $ParameterBindingName.SubString($ParameterBindingName.LastIndexOf('[')+1).Trim()
# Filter out values that are not Parameter Binding due to contain whitespace, some special characters, etc.
If(!$ParameterBindingName.Contains(' ') -AND !$ParameterBindingName.Contains('.') -AND !$ParameterBindingName.Contains(']') -AND !($ParameterBindingName.Length -eq 0))
{
# If we have a match then set boolean to True so result will be encapsulated with curly braces at the end of this function.
If($ParameterValidationAttributesToTreatStringAsScriptblock -Contains $ParameterBindingName.ToLower())
{
$EncapsulateAsScriptBlockInsteadOfParentheses = $TRUE
}
}
}
}
This is a more systematic way of grabbing the ValidationAttribute's name, as opposed to assuming a length of the attribute itself.
Problem
When Out-ObfuscatedTokenCommand.ps1 attempts to find $ParameterValidationAttributesToTreatAsScriptblock, it misses some of the longer validation options.
I discovered this issue while attempting to obfuscate modules in the Empire project.
This error specifically was found while attempting to obfuscate Invoke-TokenManipulation.ps1
Steps to reproduce
Upon further inspection, I was able to reduce the Invoke-TokenManipulation.ps1 file to a minified version that still reproduces the error.
Solution
It seems the long ValidateSet is not being caught as a 'ParameterValidationAttributesToTreatAsScriptblock', even though it is in the list of attributes.
This portion of the code in Out-ObfuscatedTokenCommand.ps1 seems to be the culprit:
Replacing the previous code with this modified version seems to fix the issue:
This is a more systematic way of grabbing the ValidationAttribute's name, as opposed to assuming a length of the attribute itself.