TestHarnesses/T1059.001_PowerShell/OutPowerShellCommandLineParameter.ps1
function Out-ATHPowerShellCommandLineParameter { <# .SYNOPSIS A powershell.exe command-line generator. Technique ID: T1059.001 (Command and Scripting Interpreter: PowerShell) .DESCRIPTION Out-ATHPowerShellCommandLineParameter helps to generate powershell.exe command-line invocations based on the set of all possible ways in which PowerShell code can be supplied at the command-line. Many detections rely upon the extraction and interpretation of PowerShell code supplied at the command-line to powershell.exe. There are many different supported ways to supply PowerShell code at the command-line and Out-ATHPowerShellCommandLineParameter can generate them all for the purposes of validating powershell.exe detection coverage. Note: PowerShell being a very broad technique, note that this test harness does not constitute full coverage for the technique. The specific scope of this test harness is as follows: * powershell.exe command-line parameter variations where PowerShell code is explicitly supplied in-line at the command-line. Technique variations (non-exhaustive list) that fall outside the scope of this test harness: * A script file supplied via the command-line. For example, using the -File parameter. * Malicious PowerShell code executed by installing a malicious profile and executing powershell.exe with no arguments. * The use of command-line parameters that are not directly related to the execution of inline PowerShell code. .PARAMETER CommandParamVariation Specifies one of the support "Command" parameter variations. The following case-insensitive options are supported by powershell.exe: C, Co, Com, Comm, Comma, Comman, Command .PARAMETER EncodedCommandParamVariation Specifies one of the support "EncodedCommand" parameter variations. The following case-insensitive options are supported by powershell.exe: EC, E, En, Enc, Enco, Encod, Encode, Encoded, EncodedC, EncodedCo, EncodedCom, EncodedComm, EncodedComma, EncodedComman, EncodedCommand .PARAMETER CommandLineSwitchType Specifies the command-line switch type to use. powershell.exe supports the following switches: Hyphen, EnDash, EmDash, HorizontalBar, ForwardSlash .PARAMETER UseEncodedArguments Specifies that "EncodedArguments" will be supplied to the command-line. powershell.exe supports an undocumented feature where arguments can be supplied to script code in the form of a serialized ArrayList object that is then base64-encoded. .PARAMETER EncodedArgumentsParamVariation Specifies one of the support "EncodedArguments" parameter variations. The following case-insensitive options are supported by powershell.exe: EA, EncodedA, EncodedAr, EncodedArg, EncodedArgu, EncodedArgum, EncodedArgume, EncodedArgume, EncodedArgumen, EncodedArgument, EncodedArguments .PARAMETER Arguments Specifies one or more arguments to supply to your PowerShell code. If -UseEncodedArguments is specified, but -Arguments is not specified, a generated GUID value will serve as the argument. In PowerShell code, these arguments are received via the $args built-in variable: e.g. $args[0], $args[1], etc. .PARAMETER GenerateAllParamVariations Specifies that all command-line parameters should be generated. This must be used in combination with either -UseCommandParam or -UseEncodedCommandParam. Note: Mixed-case variations are not generated. powershell.exe interprets command-line parameters in a case-insensitive fashion so ensure that any detection logic, accordingly, is not case-sensitive. .PARAMETER UseCommandParam Specifies that "Command" parameter variations are to be generated. .PARAMETER UseEncodedCommandParam Specifies that "EncodedCommand" parameter variations are to be generated. .PARAMETER ScriptBlock Optionally, specify a PowerShell scriptblock to execute. If -ScriptBlock is not specified, a default ScriptBlock calling Write-Host on a generated GUID will be used. .PARAMETER TestGuid Optionally, specify a test GUID value to use to override the generated test GUID behavior. .PARAMETER Execute Specifies that the generated command line should be executed. The generated command-line will be spawned with the WMI Win32_Process Create method. .OUTPUTS String When the -Execute switch is not supplied, Out-ATHPowerShellCommandLineParameter returns a string representation of the generated powershell.exe invocation. PSObject When the -Execute switch is specified, Out-ATHPowerShellCommandLineParameter returns an object consisting of relevant execution details. The following object properties may be populated: * TechniqueID - Specifies the relevant MITRE ATT&CK Technique ID. * TestSuccess - Indicates that the powershell.exe process successfully spawned. * TestGuid - Specifies the test GUID that was used for the test. This property will not be populated when arguments are manually supplied. * ProcessId - Specifies the process ID of the spawned powershell.exe process. * CommandLine - Specifies the command-line of the spawned powershell.exe process. .EXAMPLE Out-ATHPowerShellCommandLineParameter Generates a powershell.exe command-line string using "C". .EXAMPLE Out-ATHPowerShellCommandLineParameter -CommandParamVariation Comman -Execute Executes a powershell.exe command-line using "Comman" as the specified execution parameter. .EXAMPLE Out-ATHPowerShellCommandLineParameter -CommandParamVariation Command -CommandLineSwitchType EmDash -Execute Executes a powershell.exe command-line using "Command" as the specified execution parameter and an EmDash as the switch type. .EXAMPLE Out-ATHPowerShellCommandLineParameter -CommandParamVariation Command -CommandLineSwitchType HorizontalBar -ScriptBlock { Write-Host Foo } -Execute Executes a powershell.exe command-line using "Command" as the specified execution parameter, a HorizontalBar as the switch type, and specifies a PowerShell scriptblock to execute versus the default scriptblock. .EXAMPLE Out-ATHPowerShellCommandLineParameter -CommandParamVariation C -UseEncodedArguments Generates a powershell.exe command-line string using "C", passing the embedded PowerShell code encoded arguments. .EXAMPLE Out-ATHPowerShellCommandLineParameter -EncodedCommandParamVariation EC -UseEncodedArguments -EncodedArgumentsParamVariation EncodedA Generates a powershell.exe command-line string using "EC", passing the embedded PowerShell code encoded arguments with the "EncodedA" parameter. .EXAMPLE Out-ATHPowerShellCommandLineParameter -EncodedCommandParamVariation Enc -ScriptBlock { Write-Host $args[0] $args[1] } -UseEncodedArguments -Arguments 'foo', 'bar' -Execute Executes an encoded command that receives custom arguments via encoded arguments. .EXAMPLE Out-ATHPowerShellCommandLineParameter -CommandParamVariation Comma -TestGuid 11111111-1111-1111-1111-111111111111 -Execute Executes a "Command" variation using the specified test GUID. .EXAMPLE Out-ATHPowerShellCommandLineParameter -GenerateAllParamVariations -UseCommandParam -CommandLineSwitchType Hyphen Generate all "Command" variations using hyphen as the command-line switch type. .EXAMPLE Out-ATHPowerShellCommandLineParameter -GenerateAllParamVariations -UseCommandParam -UseEncodedArguments -CommandLineSwitchType Hyphen -Execute Generate and execute all "Command" variations using hyphen as the command-line switch type. .EXAMPLE Out-ATHPowerShellCommandLineParameter -GenerateAllParamVariations -UseEncodedCommandParam -CommandLineSwitchType ForwardSlash Generate all "EncodedCommand" variations using forward slash as the command-line switch type. #> [CmdletBinding(DefaultParameterSetName = 'Command')] param ( [Parameter(ParameterSetName = 'Command')] [Parameter(ParameterSetName = 'UseEncodedArgumentsCommand')] [String] [ValidateSet('C', 'Co', 'Com', 'Comm', 'Comma', 'Comman', 'Command')] $CommandParamVariation = 'C', [Parameter(ParameterSetName = 'EncodedCommand')] [Parameter(ParameterSetName = 'UseEncodedArgumentsEncodedCommand')] [String] [ValidateSet('EC', 'E', 'En', 'Enc', 'Enco', 'Encod', 'Encode', 'Encoded', 'EncodedC', 'EncodedCo', 'EncodedCom', 'EncodedComm', 'EncodedComma', 'EncodedComman', 'EncodedCommand')] $EncodedCommandParamVariation = 'EC', [String] [ValidateSet('Hyphen', 'EnDash', 'EmDash', 'HorizontalBar', 'ForwardSlash')] $CommandLineSwitchType = 'Hyphen', [Parameter(Mandatory, ParameterSetName = 'UseEncodedArgumentsCommand')] [Parameter(Mandatory, ParameterSetName = 'UseEncodedArgumentsEncodedCommand')] [Parameter(ParameterSetName = 'CommandAllVariations')] [Parameter(ParameterSetName = 'EncodedCommandAllVariations')] [Switch] $UseEncodedArguments, [Parameter(ParameterSetName = 'UseEncodedArgumentsCommand')] [Parameter(ParameterSetName = 'UseEncodedArgumentsEncodedCommand')] [String] [ValidateSet('EA', 'EncodedA', 'EncodedAr', 'EncodedArg', 'EncodedArgu', 'EncodedArgum', 'EncodedArgume', 'EncodedArgume', 'EncodedArgumen', 'EncodedArgument', 'EncodedArguments')] $EncodedArgumentsParamVariation = 'EA', [Parameter(ParameterSetName = 'UseEncodedArgumentsCommand')] [Parameter(ParameterSetName = 'UseEncodedArgumentsEncodedCommand')] [String[]] $Arguments, [Parameter(Mandatory, ParameterSetName = 'CommandAllVariations')] [Parameter(Mandatory, ParameterSetName = 'EncodedCommandAllVariations')] [Switch] $GenerateAllParamVariations, [Parameter(Mandatory, ParameterSetName = 'CommandAllVariations')] [Switch] $UseCommandParam, [Parameter(Mandatory, ParameterSetName = 'EncodedCommandAllVariations')] [Switch] $UseEncodedCommandParam, [ScriptBlock] $ScriptBlock, [Guid] $TestGuid = (New-Guid), [Switch] $Execute ) function New-EncodedArgument { [CmdletBinding()] [OutputType([String])] param ( [String[]] $Arguments ) $ArgumentList = New-Object -TypeName System.Collections.ArrayList foreach ($Argument in $Arguments) { $null = $ArgumentList.Add($Argument) } $TempCliXmlFile = New-TemporaryFile # Save the serialized ArrayList object to disk. Export-Clixml -Path $TempCliXmlFile.FullName -InputObject $ArgumentList # Read the contents of the serialized ArrayList object $CliXmlText = Get-Content -Path $TempCliXmlFile.FullName -Raw $CliXmlEncoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($CliXmlText)) $TempCliXmlFile | Remove-Item $CliXmlEncoded } $PowerShellCommandLine = $null $SpawnedProcCommandLine = $null $EncodedArguments = $null $TestGuidToUse = $null switch($CommandLineSwitchType) { 'Hyphen' { $SwitchChar = [Char] '-' } 'EnDash' { $SwitchChar = [Char] 0x2013 } 'EmDash' { $SwitchChar = [Char] 0x2014 } 'HorizontalBar' { $SwitchChar = [Char] 0x2015 } 'ForwardSlash' { $SwitchChar = [Char] '/' } } if (-not $ScriptBlock) { $TestGuidToUse = $TestGuid } if ($UseEncodedArguments) { if ($Arguments) { $TestGuidToUse = $null $EncodedArguments = New-EncodedArgument -Arguments $Arguments } else { $EncodedArguments = New-EncodedArgument -Arguments $TestGuid.Guid } } switch ($PSCmdlet.ParameterSetName) { 'Command' { if ($ScriptBlock) { $PowerShellCommand = "`"$($ScriptBlock.ToString().Trim())`"" } else { $PowerShellCommand = "Write-Host $TestGuid" } $PowerShellCommandLine = "powershell.exe {0}NoProfile {0}$CommandParamVariation $PowerShellCommand" -f $SwitchChar } 'EncodedCommand' { if ($ScriptBlock) { $PowerShellCommand = "`"$($ScriptBlock.ToString().Trim())`"" } else { $PowerShellCommand = "Write-Host $TestGuid" } $EncodedPowerShellCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($PowerShellCommand)) $PowerShellCommandLine = "powershell.exe {0}NoProfile {0}$EncodedCommandParamVariation $EncodedPowerShellCommand" -f $SwitchChar } 'UseEncodedArgumentsCommand' { if ($ScriptBlock) { $PowerShellCommand = "`"$($ScriptBlock.ToString().Trim())`"" } else { $PowerShellCommand = 'Write-Host $args[0]' } $PowerShellCommandLine = "powershell.exe {0}NoProfile {0}$EncodedArgumentsParamVariation $EncodedArguments {0}$CommandParamVariation $PowerShellCommand" -f $SwitchChar } 'UseEncodedArgumentsEncodedCommand' { if ($ScriptBlock) { $PowerShellCommand = "`"$($ScriptBlock.ToString().Trim())`"" } else { $PowerShellCommand = 'Write-Host $args[0]' } $EncodedPowerShellCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($PowerShellCommand)) $PowerShellCommandLine = "powershell.exe {0}NoProfile {0}$EncodedArgumentsParamVariation $EncodedArguments {0}$EncodedCommandParamVariation $EncodedPowerShellCommand" -f $SwitchChar } 'CommandAllVariations' { 'C', 'Co', 'Com', 'Comm', 'Comma', 'Comman', 'Command' | ForEach-Object { $CurrentParamVariation = $_ # Preserve all arguments except -GenerateAllParamVariations and -UseCommandParam $ArgsToPassThru = @{} $PSBoundParameters.Keys | Where-Object { ($_ -ne 'GenerateAllParamVariations') -and ($_ -ne 'UseCommandParam') } | ForEach-Object { $ArgsToPassThru[$_] = $PSBoundParameters[$_] } if ($PSBoundParameters['UseEncodedArguments']) { 'EA', 'EncodedA', 'EncodedAr', 'EncodedArg', 'EncodedArgu', 'EncodedArgum', 'EncodedArgume', 'EncodedArgume', 'EncodedArgumen', 'EncodedArgument', 'EncodedArguments' | ForEach-Object { Out-ATHPowerShellCommandLineParameter -CommandParamVariation $CurrentParamVariation -EncodedArgumentsParamVariation $_ @ArgsToPassThru } } else { Out-ATHPowerShellCommandLineParameter -CommandParamVariation $CurrentParamVariation @ArgsToPassThru } } } 'EncodedCommandAllVariations' { 'EC', 'E', 'En', 'Enc', 'Enco', 'Encod', 'Encode', 'Encoded', 'EncodedC', 'EncodedCo', 'EncodedCom', 'EncodedComm', 'EncodedComma', 'EncodedComman', 'EncodedCommand' | ForEach-Object { $CurrentParamVariation = $_ # Preserve all arguments except -GenerateAllParamVariations and -UseEncodedCommandParam $ArgsToPassThru = @{} $PSBoundParameters.Keys | Where-Object { ($_ -ne 'GenerateAllParamVariations') -and ($_ -ne 'UseEncodedCommandParam') } | ForEach-Object { $ArgsToPassThru[$_] = $PSBoundParameters[$_] } if ($PSBoundParameters['UseEncodedArguments']) { 'EA', 'EncodedA', 'EncodedAr', 'EncodedArg', 'EncodedArgu', 'EncodedArgum', 'EncodedArgume', 'EncodedArgume', 'EncodedArgumen', 'EncodedArgument', 'EncodedArguments' | ForEach-Object { Out-ATHPowerShellCommandLineParameter -EncodedCommandParamVariation $CurrentParamVariation -EncodedArgumentsParamVariation $_ @ArgsToPassThru } } else { Out-ATHPowerShellCommandLineParameter -EncodedCommandParamVariation $CurrentParamVariation @ArgsToPassThru } } } } if (-not $GenerateAllParamVariations) { if ($Execute) { $ProcessStartup = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly $ProcessStartupInstance = Get-CimInstance -InputObject $ProcessStartup $ProcessStartupInstance.ShowWindow = [UInt16] 0 # Hide the window $ProcStartResult = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = $PowerShellCommandLine; ProcessStartupInformation = $ProcessStartupInstance } if ($ProcStartResult.ReturnValue -eq 0) { # Retrieve the actual command-line of the spawned PowerShell process $SpawnedProcCommandLine = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $($ProcStartResult.ProcessId)" -Property CommandLine | Select-Object -ExpandProperty CommandLine [PSCustomObject] @{ TechniqueID = 'T1059.001' TestSuccess = $True TestGuid = $TestGuidToUse ProcessId = $ProcStartResult.ProcessId CommandLine = $SpawnedProcCommandLine } } else { Write-Error "powershell.exe child process was not spawned." } } else { $PowerShellCommandLine } } } |