danielbohannon / Invoke-Obfuscation

PowerShell Obfuscator
Apache License 2.0
3.62k stars 764 forks source link

Out-ObfuscatedTokenCommand fails on long ParameterBinding options #1

Closed cobbr closed 7 years ago

cobbr commented 7 years ago

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

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.

function Enable-Privilege
{
    Param(
        [Parameter()]
        [ValidateSet("SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege",
            "SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege",
            "SeDebugPrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege",
            "SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege", "SeMachineAccountPrivilege",
            "SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege",
            "SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege",
            "SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege",
            "SeUndockPrivilege", "SeUnsolicitedInputPrivilege")]
        [String]
        $Privilege
    )
    Write-Verbose "Enabled privilege: $Privilege"
}

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:

$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.

danielbohannon commented 7 years ago

Issue fixed in d419d0b4a0592c0cd48d7a22d462477f4b976970 release.