#python import type shi $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition . "$scriptPath\util\strings\strings.ps1" . "$scriptPath\util\variables\variables.ps1" . "$scriptPath\util\commands\commands.ps1" . "$scriptPath\util\commands\function_names.ps1" . "$scriptPath\util\final\encodeOutput.ps1" . "$scriptPath\util\numbers\obfuscate_numbers.ps1" $global:pass_number = 1 $times = 2 $verbose = $false $verbose_out_file = $true if ($verbose_out_file) { #redirect standard output when we need the verbose in a file $VerbosePreference = "Continue" $date_and_time = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $VerboseOutput = "obfuscate$date_and_time.log" Start-Transcript -Path $VerboseOutput } # this is EXTREMELY NEEDED because the Get-Command function is so utterly slow. $CommandTypeCache = @{} $functionNamesIgnore = @("CheckValidationResult") $built_in_aliases = @('foreach', 'where', 'clc', 'cli', 'clp', 'clv', 'cpi', 'cvpa', 'dbp', 'ebp', 'epal', 'epcsv', 'fl', 'ft', 'fw', 'gal', 'gbp', 'gc', 'gci', 'gcm', 'gdr', 'gcs', 'ghy', 'gi', 'gl', 'gm', 'gmo', 'gp', 'gpv', 'gps', 'group', 'gu', 'gv', 'iex', 'ihy', 'ii', 'ipmo', 'ipal', 'ipcsv', 'measure', 'mi', 'mp', 'nal', 'ndr', 'ni', 'nv', 'nmo', 'oh', 'rbp', 'rdr', 'ri', 'rni', 'rnp', 'rp', 'rmo', 'rv', 'gerr', 'rvpa', 'sal', 'sbp', 'select', 'si', 'sl', 'sp', 'saps', 'spps', 'sv', 'irm', 'iwr', 'ac', 'clear', 'compare', 'cpp', 'diff', 'gsv', 'sleep', 'sort', 'start', 'sasv', 'spsv', 'tee', 'write', 'cat', 'cp', 'ls', 'man', 'mount', 'mv', 'ps', 'rm', 'rmdir', 'cnsn', 'dnsn', 'ogv', 'shcm', 'cd', 'dir', 'echo', 'fc', 'kill', 'pwd', 'type', 'h', 'history', 'md', 'popd', 'pushd', 'r', 'cls', 'chdir', 'copy', 'del', 'erase', 'move', 'rd', 'ren', 'set', 'icm', 'clhy', 'gjb', 'rcjb', 'rjb', 'sajb', 'spjb', 'wjb', 'nsn', 'gsn', 'rsn', 'etsn', 'rcsn', 'exsn', 'sls') function ObfuscateCode($code) { $code_copy = $code $functionReplacementMap = @{} $variableReplacementMap = @{} $parameterReplacementMap = @{} $stringReplacementMap = @{} $numberReplacementMap = @{} $comments = [System.Management.Automation.PSParser]::Tokenize($code_copy, [ref]$null) | Where-Object { $_.Type -eq "Comment" } foreach ($comment in $comments) { $code_copy = $code_copy.Replace($comment.Content, "") } # first pass - handle everything except barewords $ast = [System.Management.Automation.Language.Parser]::ParseInput($code_copy, [ref]$null, [ref]$null) # get all function definitions $functionDefinitions = $ast.FindAll({ param([System.Management.Automation.Language.Ast] $Ast) $Ast -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) # get all command calls $allCommandCalls = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) # get all variable expressions $variableExpressions = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.VariableExpressionAst] }, $true) $stringAsts = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and ($args[0].StringConstantType -eq "DoubleQuoted" -or $args[0].StringConstantType -eq "SingleQuoted") }, $true) $numberAsts = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.ConstantExpressionAst] -and $args[0].StaticType.Name -eq "Int32" }, $true) # Process function definitions and create replacement map foreach ($func in $functionDefinitions) { if (-not $functionReplacementMap.ContainsKey($func.Name)) { $functionReplacementMap[$func.Name] = ObfuscateFunctionNames $func.Name } # handle parameters in function definition if ($func.Parameters) { foreach ($param in $func.Parameters) { $paramName = $param.Name.VariablePath.UserPath if (-not $parameterReplacementMap.ContainsKey($paramName)) { $newParamName = ObfuscateVariables $paramName $true $parameterReplacementMap[$paramName] = $newParamName.TrimStart('$') $variableReplacementMap[$paramName] = $newParamName } } } if ($func.Body.ParamBlock) { foreach ($param in $func.Body.ParamBlock.Parameters) { $paramName = $param.Name.VariablePath.UserPath if (-not $parameterReplacementMap.ContainsKey($paramName)) { $newParamName = ObfuscateVariables $paramName $true $parameterReplacementMap[$paramName] = $newParamName.TrimStart('$') $variableReplacementMap[$paramName] = $newParamName } } } } # process regular variables foreach ($var in $variableExpressions) { $varName = $var.VariablePath.UserPath if (-not $variableReplacementMap.ContainsKey($varName) -and -not $parameterReplacementMap.ContainsKey($varName)) { $variableReplacementMap[$varName] = ObfuscateVariables $var.Extent.Text } } $allReplacements = @() # add function definitions with improved handling foreach ($func in $functionDefinitions) { if ($func.Name -in $functionNamesIgnore) { continue } $functionKeyword = "function " $fullStartOffset = $func.Extent.StartOffset $nameStartOffset = $func.Extent.StartOffset # find actual start of function name by checking for the keyword if ($func.Extent.Text.TrimStart().StartsWith($functionKeyword)) { $nameStartOffset = $fullStartOffset + $func.Extent.Text.IndexOf($functionKeyword) + $functionKeyword.Length } $allReplacements += @{ StartOffset = $nameStartOffset Length = $func.Name.Length OriginalName = $func.Name Text = $func.Name Type = "Function" RequiresKeyword = $true # new flag to indicate this needs special handling FullStartOffset = $fullStartOffset } } # add function calls and parameters with improved validation foreach ($call in $allCommandCalls) { $commandName = $call.CommandElements[0].Extent.Text if ($commandName -in $functionNamesIgnore) { continue } if ($functionReplacementMap.ContainsKey($commandName)) { # verify the replacement exists and is valid $newName = $functionReplacementMap[$commandName] $allReplacements += @{ StartOffset = $call.CommandElements[0].Extent.StartOffset Length = $commandName.Length OriginalName = $commandName Text = $commandName Type = "Function" RequiresKeyword = $false # function calls don't need the keyword } # process parameters for ($i = 1; $i -lt $call.CommandElements.Count; $i++) { $element = $call.CommandElements[$i] if ($element.Extent.Text.StartsWith('-')) { $paramName = $element.Extent.Text.TrimStart('-') if ($parameterReplacementMap.ContainsKey($paramName)) { $allReplacements += @{ StartOffset = $element.Extent.StartOffset Length = $element.Extent.Text.Length OriginalName = $paramName Text = "-" + $paramName Type = "ParameterName" } } } } } } # add variables foreach ($var in $variableExpressions) { $varName = $var.VariablePath.UserPath $parent = $var.Parent $isParameterName = $false while ($parent) { if ($parent -is [System.Management.Automation.Language.CommandAst]) { $isParameterName = $parent.CommandElements | Where-Object { $_.Extent.Text -eq "-$($var.Extent.Text)" } if ($isParameterName) { break } } $parent = $parent.Parent } if (-not $isParameterName) { $allReplacements += @{ StartOffset = $var.Extent.StartOffset Length = $var.Extent.Text.Length OriginalName = $varName Text = $var.Extent.Text Type = "Variable" } } } # add strings foreach ($string in $stringAsts) { $stringText = $string.Extent.Text # handle empty strings if ([string]::IsNullOrWhiteSpace($stringText) -or $stringText -eq '""' -or $stringText -eq "''") { $allReplacements += @{ StartOffset = $string.Extent.StartOffset Length = $string.Extent.Text.Length OriginalName = $stringText Text = $string.Extent.Text Type = "EmptyString" NewName = '[string]::Empty' } continue } # check to see if string only contains single or double quotes inside. (if example: "''" or "''''") if the string is empty then just keep going. if ($stringText -match "^['""]+$") { $allReplacements += @{ StartOffset = $string.Extent.StartOffset Length = $string.Extent.Text.Length OriginalName = $stringText Text = $string.Extent.Text Type = "EmptyString" NewName = '[string]::Empty' } continue } # handle normal strings if ($string.Extent.Text.Length -lt 3) { continue } if (-not $stringReplacementMap.ContainsKey($stringText)) { $stringReplacementMap[$stringText] = ObfuscateString $stringText } $allReplacements += @{ StartOffset = $string.Extent.StartOffset Length = $string.Extent.Text.Length OriginalName = $stringText Text = $string.Extent.Text Type = "String" } } # first pass replacements $allReplacements = $allReplacements | Sort-Object { $_.StartOffset } -Descending foreach ($replacement in $allReplacements) { $newName = switch ($replacement.Type) { "Function" { $functionReplacementMap[$replacement.OriginalName] } "ParameterName" { "-" + $parameterReplacementMap[$replacement.OriginalName] } "Variable" { $variableReplacementMap[$replacement.OriginalName] } "String" { $stringReplacementMap[$replacement.OriginalName] } "EmptyString" { $replacement.NewName } } Write-Host "First Pass - Replacing '$($replacement.Text)' at position $($replacement.StartOffset) with '$newName' (Type: $($replacement.Type))" $code_copy = Replace-TextAtPosition -SourceText $code_copy ` -StartPosition $replacement.StartOffset ` -Length $replacement.Length ` -ReplacementText $newName } $newAst = [System.Management.Automation.Language.Parser]::ParseInput($code_copy, [ref]$null, [ref]$null) $barewordAsts = $newAst.FindAll({ ($args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and $args[0].StringConstantType -eq "BareWord") -or ($args[0] -is [System.Management.Automation.Language.TypeExpressionAst]) }, $true) $bareword_Commands = $newAst.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) $numberAsts = $newAst.FindAll({ $args[0] -is [System.Management.Automation.Language.ConstantExpressionAst] -and $args[0].StaticType.Name -eq "Int32" }, $true) $barewordReplacements = @() $numberReplacements = @() foreach ($bareword in $barewordAsts) { if ($bareword.Extent.Text.Length -lt 3) { continue } if ($functionReplacementMap.ContainsKey($bareword.Extent.Text)) { continue } # check parent to see if this is a command $isCommandFirst = $false if ($bareword.Parent -is [System.Management.Automation.Language.CommandAst]) { # check if its the first bareword (the actual command) $commandElements = $bareword.Parent.CommandElements $isCommandFirst = $commandElements[0].Extent.Text -eq $bareword.Extent.Text # if not then skip if (-not $isCommandFirst) { continue } } # get command type information $commandInfo = Get-CommandType -CommandName $bareword.Extent.Text # generate a new random replacement for each instance # pass the command info to ObfuscateCommandTypes $newBarewordName = ObfuscateCommandTypes -CommandText $bareword.Extent.Text -CommandInfo $commandInfo -RealBearWord $isCommandFirst $barewordReplacements += @{ StartOffset = $bareword.Extent.StartOffset Length = $bareword.Extent.Text.Length OriginalName = $bareword.Extent.Text Text = $bareword.Extent.Text NewName = $newBarewordName Type = "Bareword" CommandType = $commandInfo.Type IsBuiltIn = $commandInfo.IsBuiltIn } } # too many numbers lol if ($global:pass_number -lt 2) { foreach ($number in $numberAsts) { $numberText = $number.Extent.Text if ($numberReplacementMap.ContainsKey($numberText)) { continue } $newNumber = ObfuscateNumbers $numberText $numberReplacements += @{ StartOffset = $number.Extent.StartOffset Length = $number.Extent.Text.Length OriginalName = $numberText Text = $numberText NewName = $newNumber Type = "Number" } } } $allReplacements = @() foreach ($replacement in $barewordReplacements) { $allReplacements += $replacement } foreach ($replacement in $numberReplacements) { $allReplacements += $replacement } $allReplacements = $allReplacements | Sort-Object { $_.StartOffset } -Descending foreach ($replacement in $allReplacements) { $newName = switch ($replacement.Type) { "Bareword" { $replacement.NewName } "Number" { $replacement.NewName } } Write-Host "Second Pass - Replacing '$($replacement.Text)' at position $($replacement.StartOffset) with '$newName' (Type: $($replacement.Type))" $code_copy = Replace-TextAtPosition -SourceText $code_copy ` -StartPosition $replacement.StartOffset ` -Length $replacement.Length ` -ReplacementText $newName } return $code_copy } function Replace-TextAtPosition { param( [string]$SourceText, [int]$StartPosition, [int]$Length, [string]$ReplacementText ) try { $before = $SourceText.Substring(0, $StartPosition) $after = $SourceText.Substring($StartPosition + $Length) return $before + $ReplacementText + $after } catch { Write-Host "Error in Replace-TextAtPosition:" Write-Host "Source length: $($SourceText.Length)" Write-Host "Start: $StartPosition" Write-Host "Length: $Length" Write-Host "Replacement: $ReplacementText" throw $_ } } function Encrypt-Payload($string_payload) { $placeholder_code = @" function Create-AesManagedObject(`$key, `$IV, `$mode) {`$aesManaged = New-Object "System.Security.Cryptography.AesManaged";if (`$mode="CBC") { `$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC }elseif (`$mode="CFB") {`$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CFB}elseif (`$mode="CTS") {`$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CTS}elseif (`$mode="ECB") {`$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::ECB}elseif (`$mode="OFB"){`$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::OFB};`$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;`$aesManaged.BlockSize = 128;`$aesManaged.KeySize = 256;if (`$IV) {if (`$IV.getType().Name -eq "String") {`$aesManaged.IV = [System.Convert]::FromBase64String(`$IV)}else {`$aesManaged.IV = `$IV}};if (`$key) {if (`$key.getType().Name -eq "String") {`$aesManaged.Key = [System.Convert]::FromBase64String(`$key)}else {`$aesManaged.Key = `$key}};return `$aesManaged};function Decrypt-String(`$key, `$encryptedStringWithIV) {`$bytes = [System.Convert]::FromBase64String(`$encryptedStringWithIV);`$IV = `$bytes[0..15];`$aesManaged = Create-AesManagedObject `$key `$IV;`$decryptor = `$aesManaged.CreateDecryptor();;`$unencryptedData = `$decryptor.TransformFinalBlock(`$bytes, 16, `$bytes.Length - 16);;`$aesManaged.Dispose();return [System.Text.Encoding]::UTF8.GetString(`$unencryptedData).Trim([char]0)};iex(Decrypt-String "YOUR_KEY_HERE" "YOUR_ENCRYPTED_STRING_HERE") "@ $key = Create-AesKey $encryptedString = Encrypt-String $key $string_payload $new_code1 = $placeholder_code -replace "YOUR_KEY_HERE", $key $final_code = $new_code1 -replace "YOUR_ENCRYPTED_STRING_HERE", $encryptedString return $final_code } function Get-CommandType { param( [string]$CommandName ) # simple cache because Get-Command is slow ash if ($CommandTypeCache.ContainsKey($CommandName)) { return $CommandTypeCache[$CommandName] } $command = Get-Command -Name $CommandName -ErrorAction Ignore $result = if ($command) { @{ IsBuiltIn = $true Type = $command.CommandType Name = $CommandName } } else { @{ IsBuiltIn = $false Type = "Unknown" Name = $CommandName } } # cache the result for future calls $CommandTypeCache[$CommandName] = $result return $result } function Main($payload) { $obfuscatedCode = ObfuscateCode $payload if ($times -ne 0) { $totalSteps = ($times * 2) + 1 $currentStep = 0 while ($times -ne 1) { $global:pass_number++ $times = $times - 1 $obfuscatedCode = Encrypt-Payload $obfuscatedCode $currentStep++ Write-Progress -Activity "Obfuscating Code" -Status "Encrypting Payload" -PercentComplete (($currentStep / $totalSteps) * 100) $obfuscatedCode = ObfuscateCode $obfuscatedCode $currentStep++ Write-Progress -Activity "Obfuscating Code" -Status "Obfuscating Code" -PercentComplete (($currentStep / $totalSteps) * 100) } } Write-Progress -Activity "Obfuscating Code" -Status "Completed" -PercentComplete 100 -Completed return $obfuscatedCode } function Get-FileLocation { while ($true) { $file_location = Read-Host "Enter the file location -> " if ((Test-Path $file_location) -and $file_location -like "*.ps1") { return $file_location } else { Write-Host "File not found or not a .ps1 file: $file_location" } } } $location_good = Get-FileLocation $stuff = Get-Content $location_good -Raw $obfuscatedCode = Main $stuff $out_file = $location_good -replace ".ps1", "_obf.ps1" $obfuscatedCode | Out-File $out_file -Force