danielbohannon / Invoke-Obfuscation

PowerShell Obfuscator
Apache License 2.0
3.59k stars 759 forks source link

Error with imbalanced parentheses during Token obfuscation #15

Closed cobbr closed 6 years ago

cobbr commented 7 years ago

Problem

There seems to be an issue that can arise when doing string token obfuscation when using one of the special characters that use the string "replace" obfuscation. I encountered this issue when obfuscating the Get-SQLColumnSampleData module in Empire.

Steps to reproduce

PS > Invoke-Obfuscation -ScriptPath 'https://raw.githubusercontent.com/EmpireProject/Empire/2.0_beta/data/module_source/collection/Get-SQLColumnSampleData.ps1' -Command 'Token\All\1' -Quiet
Exception calling "Create" with "1" argument(s): "At line:843 char:23
+         HelpMessage = {("{3}{4}{1}{0}{2}"-f 'tput anyt','ou','hing.', ...
+                       ~
Missing closing '}' in statement block or type definition.
At line:843 char:85
+ ... ("{3}{4}{1}{0}{2}"-f 'tput anyt','ou','hing.','DonCK','Et ')).REplacE ...
+                                                                  ~
Missing ] at end of attribute or type literal.
At line:843 char:85
+ ... ("{3}{4}{1}{0}{2}"-f 'tput anyt','ou','hing.','DonCK','Et ')).REplacE ...
+                                                                  ~
Parameter declarations are a comma-separated list of variable names with optional initializer expressions.
At line:843 char:85
+ ... ("{3}{4}{1}{0}{2}"-f 'tput anyt','ou','hing.','DonCK','Et ')).REplacE ...
+                                                                  ~
Missing ')' in function parameter list."

The problem can be minimized with this script as input:

PS> cat .\test.ps1
Function Get-DomainObject {
    [CmdletBinding()]
    Param(
        [Parameter(HelpMessage = 'domain\user')]
        [string]$Username
    )
}
PS > Invoke-Obfuscation -ScriptPath .\test.ps1 -Command 'Token\All\1' -Quiet
Exception calling "Create" with "1" argument(s): "At line:4 char:34
+         [Parameter(HelpMessage = {("{2}{0}{4}{1}{3}" -f'om','P','d',' ...
+                                  ~
Missing closing '}' in statement block or type definition.
At line:4 char:85
+ ... Message = {("{2}{0}{4}{1}{3}" -f'om','P','d','Huser','ain6')).rEPLAce ...
+                                                                  ~
Missing ] at end of attribute or type literal.
At line:4 char:85
+ ... Message = {("{2}{0}{4}{1}{3}" -f'om','P','d','Huser','ain6')).rEPLAce ...
+                                                                  ~
Parameter declarations are a comma-separated list of variable names with optional initializer expressions.
At line:4 char:85
+ ... Message = {("{2}{0}{4}{1}{3}" -f'om','P','d','Huser','ain6')).rEPLAce ...

It may take a few tries to reproduce the error, as the error only occurs when the ".replace()" method of replacement is randomly selected.

Solution

The issue appears to stem from this section of code in Out-ObfuscatedTokenCommand.ps1:

# Evenly trim leading/trailing parentheses.
While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')'))
{
    $ObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim()
}

When the ".replace()" technique of replacement is used it results in an $ObfuscatedToken of this form: (something).replace(something). You can see why trimming the leading and trailing parentheses wouldn't work in this case.

This can be fixed by checking if the parantheses are properly balanced post-trim before permanently removing the parentheses:

# Evenly trim leading/trailing parentheses.
While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')'))
{
    $TrimmedObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim()
    # Check if the parentheses are balanced before permenantly trimming
    $Balanced = $True
    $Counter = 0
    ForEach($char in $TrimmedObfuscatedToken.ToCharArray()) {
        If($char -eq '(') {
            $Counter = $Counter + 1
        }
        ElseIf($char -eq ')') {
            If($Counter -eq 0) {
                $Balanced = $False
                break
            }
            Else {
                $Counter = $Counter - 1
            }
        }
    }
    # If parantheses are balanced, we can safely trim the parentheses
    If($Balanced -and $Counter -eq 0) {
        $ObfuscatedToken = $TrimmedObfuscatedToken
    }
    # If parentheses cannot be trimmed, break out of loop
    Else {
        break
    }
}

There might be other ways of solving it, but this was the only one I could think of. There is another suspiciously similar looking block of code in Out-ObfuscatedTokenCommand.ps1:

# Evenly trim leading/trailing parentheses.
While($ObfuscatedSubToken.StartsWith('(') -AND $ObfuscatedSubToken.EndsWith(')'))
{
    $ObfuscatedSubToken = ($ObfuscatedSubToken.SubString(1,$ObfuscatedSubToken.Length-2)).Trim()
}

I haven't run into any problems from this block of code, but it may be worth investigating as well.

cobbr commented 6 years ago

950785e5f846fb0e8d3be8484d2ba466c2340469