PowerShellProObfuscator.psm1
|
################################################################################ # # PowerShell Pro Obfuscator — PowerShell Gallery Web API client # # Version : v1.0.0 # Language : PowerShell # Author : Bartosz Wójcik # Web page : https://www.pelock.com # ################################################################################ Set-StrictMode -Version Latest $script:PpoApiUrl = 'https://www.pelock.com/api/powershell-pro-obfuscator/v1' $script:PpoUserAgent = 'PELock PowerShell Pro Obfuscator' $script:PpoTimeoutSec = 3600 $script:PpoErrorMessages = @{ 0 = 'Success' 1 = 'In demo mode you can only obfuscate source code that is 1000 chars max.' 2 = 'Please enter valid PowerShell source code sample' 3 = 'An error occurred while parsing the input source code' 4 = 'An error occurred while applying obfuscation to the parsed code' 5 = 'An error occurred while trying to generate output code' } # PascalCase switch name -> API form field $script:PpoStrategyMap = [ordered]@{ SelfDefending = 'self_defending' DetectDebugger = 'detect_debugger' ProtectionLinker = 'protection_linker' RenameVariables = 'rename_variables' RenameParameters = 'rename_parameters' RenameFunctions = 'rename_functions' ShuffleFunctions = 'shuffle_functions' ControlFlowFlatten = 'control_flow_flatten' StateMachine = 'state_machine' VmStrategy = 'vm_strategy' EncryptIntegers = 'encrypt_integers' EncryptFloating = 'encrypt_floating' IntegersToFloating = 'integers_to_floating' IntegersToArrays = 'integers_to_arrays' FloatsToArrays = 'floats_to_arrays' AffineIntegerMask = 'affine_integer_mask' EncryptStrings = 'encrypt_strings' SplitStrings = 'split_strings' StringCharArrayVault = 'string_char_array_vault' ReflectInvokeCommands = 'reflect_invoke_commands' InsertDeadCode = 'insert_dead_code' DecoyFunctions = 'decoy_functions' OpaqueBranches = 'opaque_branches' ScriptblockDecoys = 'scriptblock_decoys' LiteralPadding = 'literal_padding' TryFinallyNoise = 'try_finally_noise' EventStub = 'event_stub' FakeDotSourceMarkers = 'fake_dot_source_markers' ComplexifyBooleans = 'complexify_booleans' RemoveComments = 'remove_comments' } function Compress-PpoSource { param([Parameter(Mandatory)][string]$Source) $bytes = [System.Text.Encoding]::UTF8.GetBytes($Source) $ms = New-Object System.IO.MemoryStream try { $gzip = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Compress, $true) try { $gzip.Write($bytes, 0, $bytes.Length) } finally { $gzip.Dispose() } return [Convert]::ToBase64String($ms.ToArray()) } finally { $ms.Dispose() } } function Expand-PpoSource { param([Parameter(Mandatory)][string]$CompressedBase64) $bytes = [Convert]::FromBase64String($CompressedBase64) $ms = New-Object System.IO.MemoryStream(, $bytes) try { $gzip = New-Object System.IO.Compression.GZipStream($ms, [System.IO.Compression.CompressionMode]::Decompress) try { $reader = New-Object System.IO.StreamReader($gzip, [System.Text.Encoding]::UTF8) try { return $reader.ReadToEnd() } finally { $reader.Dispose() } } finally { $gzip.Dispose() } } finally { $ms.Dispose() } } function ConvertTo-PpoFormBody { param([Parameter(Mandatory)][hashtable]$Fields) $parts = foreach ($key in $Fields.Keys) { $name = [uri]::EscapeDataString([string]$key) $value = [uri]::EscapeDataString([string]$Fields[$key]) '{0}={1}' -f $name, $value } return ($parts -join '&') } function Invoke-PpoApiRequest { [CmdletBinding()] param( [Parameter(Mandatory)][hashtable]$Fields ) $body = ConvertTo-PpoFormBody -Fields $Fields $headers = @{ 'User-Agent' = $script:PpoUserAgent } try { return Invoke-RestMethod -Uri $script:PpoApiUrl -Method Post -Body $body ` -ContentType 'application/x-www-form-urlencoded' -Headers $headers ` -TimeoutSec $script:PpoTimeoutSec } catch { throw ("Cannot connect to the Web API interface, check your network connection. {0}" -f $_.Exception.Message) } } function Resolve-PpoStrategies { [CmdletBinding()] param( [string]$ActivationCode, [hashtable]$StrategySwitches ) $enabled = @{} foreach ($name in $script:PpoStrategyMap.Keys) { $enabled[$name] = $false } $hasKey = -not [string]::IsNullOrWhiteSpace($ActivationCode) if (-not $hasKey) { $enabled['RenameVariables'] = $true return $enabled } $any = $false foreach ($name in $script:PpoStrategyMap.Keys) { if ($StrategySwitches.ContainsKey($name) -and [bool]$StrategySwitches[$name]) { $enabled[$name] = $true $any = $true } } if (-not $any) { foreach ($name in $script:PpoStrategyMap.Keys) { $enabled[$name] = $true } } return $enabled } function Add-PpoStrategyFields { param( [Parameter(Mandatory)][hashtable]$Fields, [Parameter(Mandatory)][hashtable]$Enabled ) foreach ($name in $script:PpoStrategyMap.Keys) { if ($Enabled[$name]) { $Fields[$script:PpoStrategyMap[$name]] = '1' } } } function Get-PpoApiErrorMessage { param([int]$ErrorCode) if ($script:PpoErrorMessages.ContainsKey($ErrorCode)) { return $script:PpoErrorMessages[$ErrorCode] } return ("Unknown API error code: {0}" -f $ErrorCode) } function Get-PpoResultProperty { param( [Parameter(Mandatory)]$Result, [Parameter(Mandatory)][string]$Name ) $prop = $Result.PSObject.Properties[$Name] if ($null -eq $prop) { return $null } return $prop.Value } function Write-PpoObfuscateStatus { param([Parameter(Mandatory)]$Result) $demoVal = Get-PpoResultProperty -Result $Result -Name 'demo' $isDemo = $false if ($null -ne $demoVal) { $isDemo = [bool]$demoVal } if (-not $isDemo) { Write-Host '[i] file successfully obfuscated' $expiredVal = Get-PpoResultProperty -Result $Result -Name 'expired' if ($null -ne $expiredVal -and [bool]$expiredVal) { Write-Host '[!] your activation code has expired. Thank you for using PowerShell Pro Obfuscator :)' } else { $creditsLeft = Get-PpoResultProperty -Result $Result -Name 'credits_left' if ($null -ne $creditsLeft -and [int]$creditsLeft -gt 0) { $left = [int]$creditsLeft $creditsTotal = Get-PpoResultProperty -Result $Result -Name 'credits_total' $total = if ($null -ne $creditsTotal) { [int]$creditsTotal } else { 0 } Write-Host ("[i] you have {0} usage credits left (out of total {1})" -f $left, $total) } } } else { Write-Host '[i] file successfully obfuscated (demo mode)' } } <# .SYNOPSIS Validates a PowerShell Pro Obfuscator activation code against the Web API. .DESCRIPTION Calls command=login on https://www.pelock.com/api/powershell-pro-obfuscator/v1 and returns the JSON response as a PowerShell object (credits, expiry, etc.). .PARAMETER ActivationCode License / activation key from PELock. .EXAMPLE Connect-PowerShellProObfuscator -ActivationCode 'YOUR-KEY' #> function Connect-PowerShellProObfuscator { [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$ActivationCode ) $fields = @{ command = 'login' key = $ActivationCode } $result = Invoke-PpoApiRequest -Fields $fields if ($null -eq $result) { throw 'Empty response from the Web API.' } return $result } <# .SYNOPSIS Obfuscates a PowerShell script via the PowerShell Pro Obfuscator Web API. .DESCRIPTION Remote API client equivalent of PowerShellProObfuscatorCmd.exe. Without -ActivationCode only RenameVariables is applied (demo, 1000 char limit). With -ActivationCode and no strategy switches, all strategies are enabled. With -ActivationCode and one or more strategy switches, only those are enabled. .PARAMETER InputFilePath Path to the input .ps1 file. .PARAMETER OutputFilePath Path for the obfuscated .ps1 output. .PARAMETER Source In-memory PowerShell source. When used without -OutputFilePath, the obfuscated script is returned as a string. .PARAMETER ActivationCode License key. Omit for demo mode. .PARAMETER EnableCompression Gzip+Base64 the source payload (compression=1), matching the PHP/Python SDKs. .EXAMPLE Invoke-PowerShellProObfuscate -InputFilePath .\in.ps1 -OutputFilePath .\out.ps1 -ActivationCode 'KEY' -RenameVariables -EncryptStrings .EXAMPLE $out = Invoke-PowerShellProObfuscate -Source 'Write-Host 1' -ActivationCode 'KEY' #> function Invoke-PowerShellProObfuscate { [CmdletBinding(DefaultParameterSetName = 'File')] [OutputType([string])] param( [Parameter(Mandatory, ParameterSetName = 'File')] [ValidateNotNullOrEmpty()] [string]$InputFilePath, [Parameter(Mandatory, ParameterSetName = 'File')] [Parameter(ParameterSetName = 'Source')] [ValidateNotNullOrEmpty()] [string]$OutputFilePath, [Parameter(Mandatory, ParameterSetName = 'Source', ValueFromPipeline)] [ValidateNotNullOrEmpty()] [string]$Source, [Parameter()] [string]$ActivationCode, [Parameter()] [switch]$EnableCompression, [Parameter()][switch]$SelfDefending, [Parameter()][switch]$DetectDebugger, [Parameter()][switch]$ProtectionLinker, [Parameter()][switch]$RenameVariables, [Parameter()][switch]$RenameParameters, [Parameter()][switch]$RenameFunctions, [Parameter()][switch]$ShuffleFunctions, [Parameter()][switch]$ControlFlowFlatten, [Parameter()][switch]$StateMachine, [Parameter()][switch]$VmStrategy, [Parameter()][switch]$EncryptIntegers, [Parameter()][switch]$EncryptFloating, [Parameter()][switch]$IntegersToFloating, [Parameter()][switch]$IntegersToArrays, [Parameter()][switch]$FloatsToArrays, [Parameter()][switch]$AffineIntegerMask, [Parameter()][switch]$EncryptStrings, [Parameter()][switch]$SplitStrings, [Parameter()][switch]$StringCharArrayVault, [Parameter()][switch]$ReflectInvokeCommands, [Parameter()][switch]$InsertDeadCode, [Parameter()][switch]$DecoyFunctions, [Parameter()][switch]$OpaqueBranches, [Parameter()][switch]$ScriptblockDecoys, [Parameter()][switch]$LiteralPadding, [Parameter()][switch]$TryFinallyNoise, [Parameter()][switch]$EventStub, [Parameter()][switch]$FakeDotSourceMarkers, [Parameter()][switch]$ComplexifyBooleans, [Parameter()][switch]$RemoveComments ) begin { $strategySwitches = @{ SelfDefending = $SelfDefending.IsPresent DetectDebugger = $DetectDebugger.IsPresent ProtectionLinker = $ProtectionLinker.IsPresent RenameVariables = $RenameVariables.IsPresent RenameParameters = $RenameParameters.IsPresent RenameFunctions = $RenameFunctions.IsPresent ShuffleFunctions = $ShuffleFunctions.IsPresent ControlFlowFlatten = $ControlFlowFlatten.IsPresent StateMachine = $StateMachine.IsPresent VmStrategy = $VmStrategy.IsPresent EncryptIntegers = $EncryptIntegers.IsPresent EncryptFloating = $EncryptFloating.IsPresent IntegersToFloating = $IntegersToFloating.IsPresent IntegersToArrays = $IntegersToArrays.IsPresent FloatsToArrays = $FloatsToArrays.IsPresent AffineIntegerMask = $AffineIntegerMask.IsPresent EncryptStrings = $EncryptStrings.IsPresent SplitStrings = $SplitStrings.IsPresent StringCharArrayVault = $StringCharArrayVault.IsPresent ReflectInvokeCommands = $ReflectInvokeCommands.IsPresent InsertDeadCode = $InsertDeadCode.IsPresent DecoyFunctions = $DecoyFunctions.IsPresent OpaqueBranches = $OpaqueBranches.IsPresent ScriptblockDecoys = $ScriptblockDecoys.IsPresent LiteralPadding = $LiteralPadding.IsPresent TryFinallyNoise = $TryFinallyNoise.IsPresent EventStub = $EventStub.IsPresent FakeDotSourceMarkers = $FakeDotSourceMarkers.IsPresent ComplexifyBooleans = $ComplexifyBooleans.IsPresent RemoveComments = $RemoveComments.IsPresent } } process { if ($PSCmdlet.ParameterSetName -eq 'File') { if (-not (Test-Path -LiteralPath $InputFilePath -PathType Leaf)) { throw ("Cannot read input file ""{0}""" -f $InputFilePath) } try { $textSourceCode = [System.IO.File]::ReadAllText((Resolve-Path -LiteralPath $InputFilePath).Path) } catch { throw ("Cannot read input file ""{0}"". {1}" -f $InputFilePath, $_.Exception.Message) } Write-Host ("[i] input file successfully read ""{0}""" -f $InputFilePath) } else { $textSourceCode = $Source } $textSourceCode = $textSourceCode.Trim().Replace("`r`n", "`n") if ([string]::IsNullOrEmpty($textSourceCode)) { throw (Get-PpoApiErrorMessage -ErrorCode 1) } $enabled = Resolve-PpoStrategies -ActivationCode $ActivationCode -StrategySwitches $strategySwitches $fields = @{ command = 'obfuscate' key = if ($ActivationCode) { $ActivationCode } else { '' } compression = '0' source = $textSourceCode } if ($EnableCompression) { $fields['source'] = Compress-PpoSource -Source $textSourceCode $fields['compression'] = '1' } Add-PpoStrategyFields -Fields $fields -Enabled $enabled Write-Host '[i] obfuscation in progress, please wait...' Write-Verbose ("POST {0}" -f $script:PpoApiUrl) $result = Invoke-PpoApiRequest -Fields $fields if ($null -eq $result) { throw 'Empty response from the Web API.' } $errorCode = 0 if ($null -ne $result.error) { $errorCode = [int]$result.error } if ($errorCode -ne 0) { throw (Get-PpoApiErrorMessage -ErrorCode $errorCode) } $outputCode = [string]$result.output if ($EnableCompression -and -not [string]::IsNullOrEmpty($outputCode)) { $outputCode = Expand-PpoSource -CompressedBase64 $outputCode } if ([string]::IsNullOrEmpty($outputCode)) { throw (Get-PpoApiErrorMessage -ErrorCode 5) } Write-PpoObfuscateStatus -Result $result if (-not [string]::IsNullOrWhiteSpace($OutputFilePath)) { try { $outDir = Split-Path -Parent $OutputFilePath if (-not [string]::IsNullOrWhiteSpace($outDir) -and -not (Test-Path -LiteralPath $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null } [System.IO.File]::WriteAllText($OutputFilePath, $outputCode, [System.Text.UTF8Encoding]::new($false)) } catch { throw ("Cannot create output file ""{0}"". {1}" -f $OutputFilePath, $_.Exception.Message) } Write-Host ("[i] obfuscated file saved to ""{0}""" -f $OutputFilePath) } if ($PSCmdlet.ParameterSetName -eq 'Source' -and [string]::IsNullOrWhiteSpace($OutputFilePath)) { return $outputCode } if ($PSCmdlet.ParameterSetName -eq 'Source' -and -not [string]::IsNullOrWhiteSpace($OutputFilePath)) { return $outputCode } } } Export-ModuleMember -Function @( 'Invoke-PowerShellProObfuscate', 'Connect-PowerShellProObfuscator' ) |