From 6c3da6874180a6ff7c9a570a01b8c4ff7b1b11fd Mon Sep 17 00:00:00 2001 From: Andras32 Date: Mon, 11 Nov 2019 15:26:23 -0600 Subject: [PATCH] Multi platform invoke art (#641) * Non-Windows OS Support Added OS Identification to determine tests to run Added SH and Bash executors for Linux and MacOS Changed some Print statement oddities in ART Updated Installation script to work on non-windows machines * Updated Documentation Edited the readme to be more OS neutral Added information for the -force option in the installer Added instructions for downloading powershell core on Mac and Linux * Last Bugs added chown to install script * Install -force test install path if (Test-Path $InstallPath){ Remove-Item -Path $InstallPath -Recurse -Force -ErrorAction Stop | Out-Null } * minor changes Write-Host error messages Installer - Import-Module $modulePath -Force * Chown weird on MacOS chown -R $env:SUDO_USER $InstallPath * README edits clearing up $home $homedrive shenanigans * \n in mardown issues * Readme edits #2 --- .../Public/Invoke-AtomicTest.ps1 | 92 ++++++++++--------- .../Invoke-AtomicRedTeam/README.md | 20 +++- .../install-atomicredteam.ps1 | 81 ++++++++++------ execution-frameworks/README.md | 4 +- 4 files changed, 122 insertions(+), 75 deletions(-) diff --git a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Public/Invoke-AtomicTest.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Public/Invoke-AtomicTest.ps1 index 2075254f04..afd1c5d019 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Public/Invoke-AtomicTest.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Public/Invoke-AtomicTest.ps1 @@ -51,7 +51,7 @@ function Invoke-AtomicTest { [Parameter(Mandatory = $false, ParameterSetName = 'technique')] [String] - $PathToAtomicsFolder = "C:\AtomicRedTeam\atomic-red-team-master\atomics", + $PathToAtomicsFolder = $( if($IsLinux -or $IsMacOS) {$Env:HOME + "/AtomicRedTeam/atomic-red-team-master/atomics"} else{$env:HOMEDRIVE + "\AtomicRedTeam\atomic-red-team-master\atomics"}), [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, @@ -90,11 +90,14 @@ function Invoke-AtomicTest { # $InformationPrefrence = 'Continue' Write-Verbose -Message 'Attempting to run Atomic Techniques' $isElevated = $false + $targetPlatform = "linux" if ($IsLinux -or $IsMacOS){ + if ($IsMacOS){ $targetPlatform = "macos"} $privid = id -u if ($privid -eq 0){ $isElevated = $true } } else { + $targetPlatform = "windows" $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } @@ -138,9 +141,15 @@ function Invoke-AtomicTest { function Invoke-AtomicTestSingle ($AT) { - - $AtomicTechniqueHash = Get-AtomicTechnique -Path $PathToAtomicsFolder\$AT\$AT.yaml + $AT=$AT.ToUpper() + $pathToYaml = Join-Path $PathToAtomicsFolder "\$AT\$AT.yaml" + if (Test-Path -Path $pathToYaml){$AtomicTechniqueHash = Get-AtomicTechnique -Path $pathToYaml} + else{ + Write-Host -ForegroundColor Red "ERROR: $PathToYaml does not exist`nCheck your Atomic Number and Path to Atomics" + continue + } $techniqueCount = 0 + foreach ($technique in $AtomicTechniqueHash) { $techniqueCount++ @@ -156,6 +165,14 @@ function Invoke-AtomicTest { $testCount = 0 foreach ($test in $technique.atomic_tests) { + + Write-Verbose -Message 'Determining tests for target operating system' + + if (-Not $test.supported_platforms.Contains($targetPlatform)) { + Write-Verbose -Message "Unable to run non-$targetPlatform tests" + continue + } + $testCount++ if ($null -ne $TestNumbers) { @@ -173,13 +190,6 @@ function Invoke-AtomicTest { } Write-Progress @props - Write-Verbose -Message 'Determining tests for Windows' - - if (-Not $test.supported_platforms.Contains('windows')) { - Write-Verbose -Message 'Unable to run non-Windows tests' - continue - } - Write-Verbose -Message 'Determining manual tests' if ($test.executor.name.Contains('manual')) { @@ -231,42 +241,38 @@ function Invoke-AtomicTest { if ($pscmdlet.ShouldProcess($testName, 'Execute Atomic Test')) { $testId = "$AT-$testCount $testName" $attackExecuted = $false - switch ($test.executor.name) { - "command_prompt" { - Write-Information -MessageData "Command Prompt:`n $finalCommand" -Tags 'AtomicTest' - $finalCommandEscaped = $finalCommand -replace "`"", "```"" - $execCommand = $finalCommandEscaped.Split("`n") | Where-Object { $_ -ne "" } - $exitCodes = New-Object System.Collections.ArrayList - $execCommand | ForEach-Object { - Invoke-Expression "cmd.exe /c `"$_`" " - $exitCodes.Add($LASTEXITCODE) | Out-Null - if (!$CheckPrereqs -and !$Cleanup) { $attackExecuted = $true } - } - $nonZeroExitCodes = $exitCodes | Where-Object { $_ -ne 0 } - Write-PrereqResults ($nonZeroExitCodes.Count -eq 0) $testId - if (-not $NoExecutionLog -and $attackExecuted) { Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath } - continue + $executor = $test.executor.name + $finalCommandEscaped = $finalCommand -replace "`"", "```"" + Write-Information -MessageData $finalCommandEscaped + if ($executor -eq "command_prompt" -or $executor -eq "sh" -or $executor -eq "bash"){ + $execCommand = $finalCommandEscaped.Split("`n") | Where-Object { $_ -ne "" } + $exitCodes = New-Object System.Collections.ArrayList + $execPrefix = "cmd.exe /c" + if ($executor -eq "sh"){$execPrefix = "sh -c"} + if ($executor -eq "bash"){$execPrefix = "bash -c"} + $execCommand | ForEach-Object { + Invoke-Expression "$execPrefix `"$_`" " + $exitCodes.Add($LASTEXITCODE) | Out-Null } - "powershell" { - Write-Information -MessageData "PowerShell`n $finalCommand" -Tags 'AtomicTest' - $execCommand = "Invoke-Command -ScriptBlock {$finalCommand}" - $res = Invoke-Expression $execCommand - if (!$CheckPrereqs -and !$Cleanup) { $attackExecuted = $true } - Write-PrereqResults ([string]::IsNullOrEmpty($finalCommand) -or $res -eq 0) $testId - if (-not $NoExecutionLog -and $attackExecuted) { Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath } - continue - } - default { - Write-Warning -Message "Unable to generate or execute the command line properly." - continue - } - } # End of executor switch + $nonZeroExitCodes = $exitCodes | Where-Object { $_ -ne 0 } + $success = $nonZeroExitCodes.Count -eq 0 + } + elseif ($executor -eq "powershell"){ + $execCommand = "Invoke-Command -ScriptBlock {$finalCommand}" + $res = Invoke-Expression $execCommand + $success = [string]::IsNullOrEmpty($finalCommand) -or $res -eq 0 + } + else { + Write-Warning -Message "Unable to generate or execute the command line properly." + continue + } + if (!$CheckPrereqs -and !$Cleanup) { $attackExecuted = $true } + Write-PrereqResults ($success) $testId + if (-not $NoExecutionLog -and $attackExecuted) { Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath} } # End of if ShouldProcess block } # End of else statement + Write-Information -MessageData "[!!!!!!!!END TEST!!!!!!!]`n`n" -Tags 'Details' } # End of foreach Test in single Atomic Technique - - Write-Information -MessageData "[!!!!!!!!END TEST!!!!!!!]`n`n" -Tags 'Details' - } # End of foreach Technique in Atomic Tests } # End of Invoke-AtomicTestSingle function @@ -291,4 +297,4 @@ function Invoke-AtomicTest { } # End of PROCESS block END { } # Intentionally left blank and can be removed -} \ No newline at end of file +} diff --git a/execution-frameworks/Invoke-AtomicRedTeam/README.md b/execution-frameworks/Invoke-AtomicRedTeam/README.md index b7a4ef1b94..5f174eaa9a 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/README.md +++ b/execution-frameworks/Invoke-AtomicRedTeam/README.md @@ -12,13 +12,20 @@ solution in place, and that the endpoint is checking in and active. It is best t We made installing Atomic Red Team extremely easy. +For those running Atomic Red Team on MacOS or Linux download and install PowerShell Core. + +[Linux](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-6) +[MacOS](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-6) + Once the environment is ready, run PowerShell as an adminstrator and run the following PowerShell one liner: `IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1'); Install-AtomicRedTeam -verbose` [Source](install-atomicredteam.ps1) -By default, it will download and Install Atomic Red Team to `c:\AtomicRedTeam` +By default, it will download and Install Atomic Red Team to `\AtomicRedTeam` + +Where `` is `C:` in Windows or `~` in Linux/MacOS Running the [Install script](install-atomicredteam.ps1) locally provides three parameters: @@ -32,6 +39,11 @@ DownloadPath `Install-AtomicRedTeam -DownloadPath c:\tools\` +Force +- Force the new installation removing any previous installations in -InstallPath. **BE CAREFUL this will delete the entire install path folder** + + `Install-AtomicRedTeam -Force` + ### Development If you will be contributing to Atomic Red Team or plan on running it from a cloned github repo, move it to the following folder on your Windows computer for compatibility with most tests as many of them still have hard-coded paths. @@ -69,7 +81,9 @@ Execute all Atomic tests: Invoke-AtomicTest All ``` -This assumes your atomics folder is in the default location of `C:\AtomicRedTeam\atomic-red-team-master\atomics` +This assumes your atomics folder is in the default location of `\AtomicRedTeam\atomic-red-team-master\atomics` + +Where `` is `C:` in Windows or `~` in Linux/MacOS You can override the default path to the atomics folder using the `$PSDefaultParameterValues` preference variable as shown below. @@ -132,7 +146,7 @@ By default, test execution details are written to `Invoke-AtomicTest-ExecutionLo Invoke-AtomicTest T1117 -CheckPrereqs ``` -For the "command_prompt" executor, if any of the prereq_command's return a non-zero exit code, the pre-requisites are not met. Example: **fltmc.exe filters | findstr #{sysmon_driver}** +For the "command_prompt", "bash", and "sh" executors, if any of the prereq_command's return a non-zero exit code, the pre-requisites are not met. Example: **fltmc.exe filters | findstr #{sysmon_driver}** For the "powershell" executor, the prereq_command's are run as a script block and the script must return 0 if the pre-requisites are met. Example: **if(Test-Path C:\Windows\System32\cmd.exe) { 0 } else { -1 }** diff --git a/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 index 8195f7b142..c606675293 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 @@ -19,6 +19,10 @@ function Install-AtomicRedTeam { Specifies the desired path for where to install Atomic Red Team. + .PARAMETER Force + + Delete the existing InstallPath before installation if it exists. + .EXAMPLE Install Atomic Red Team @@ -32,57 +36,80 @@ function Install-AtomicRedTeam { [CmdletBinding()] Param( [Parameter(Mandatory = $False, Position = 0)] - [string]$InstallPath = 'C:\AtomicRedTeam', + [string]$InstallPath = $( if ($IsLinux -or $IsMacOS) { $Env:HOME + "/AtomicRedTeam" } else { $env:HOMEDRIVE + "\AtomicRedTeam" }), - [Parameter(Mandatory = $False, Position = 0)] - [string]$DownloadPath = 'C:\AtomicRedTeam' + [Parameter(Mandatory = $False, Position = 1)] + [string]$DownloadPath = $( if ($IsLinux -or $IsMacOS) { $Env:HOME + "/AtomicRedTeam" } else { $env:HOMEDRIVE + "\AtomicRedTeam" }), + [Parameter(Mandatory = $False)] + [switch]$Force = $False # delete the existing install directory and reinstall ) Write-Verbose "Checking if we are Admin" - $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) - if (-not $isElevated) { Write-Error "This script must be run as an administrator."; exit} - + $isElevated = $false + if ($IsLinux -or $IsMacOS) { + $privid = id -u + if ($privid -eq 0) { + $isElevated = $true + } + } + else { + $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + } + if (-not $isElevated) { Write-Error "This script must be run as an administrator."; return } - if (!(Test-Path -Path $InstallPath )) { + $modulePath = Join-Path "$InstallPath" "atomic-red-team-master\execution-frameworks\Invoke-AtomicRedTeam\Invoke-AtomicRedTeam\Invoke-AtomicRedTeam.psm1" + if ($Force -or -Not (Test-Path -Path $InstallPath )) { write-verbose "Directory Creation" + if ($Force) { + Try { + if (Test-Path $InstallPath){ Remove-Item -Path $InstallPath -Recurse -Force -ErrorAction Stop | Out-Null } + } + Catch { + Write-Host -ForegroundColor Red $_.Exception.Message + return + } + } New-Item -ItemType directory -Path $InstallPath | Out-Null - write-verbose "Setting Execution Policy to Unrestricted" - set-executionpolicy Unrestricted + + if ($IsWindows -or $null -eq $IsWindows) { + write-verbose "Setting Execution Policy to Unrestricted" + set-executionpolicy Unrestricted + } write-verbose "Setting variables for remote URL and download Path" $url = "https://github.com/redcanaryco/atomic-red-team/archive/master.zip" - $path = "$DownloadPath\master.zip" + $path = Join-Path $DownloadPath "master.zip" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $webClient = new-object System.Net.WebClient write-verbose "Beginning download from Github" $webClient.DownloadFile( $url, $path ) - write-verbose "Extracting ART to C:\AtomicRedTeam\" - expand-archive -LiteralPath "$DownloadPath\master.zip" -DestinationPath "$InstallPath" + write-verbose "Extracting ART to $InstallPath" + $lp = Join-Path "$DownloadPath" "master.zip" + expand-archive -LiteralPath $lp -DestinationPath "$InstallPath" -Force:$Force - write-verbose "Installing NuGet PackageProvider" - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + if (-not $IsMacOS -and -not $IsLinux) { + write-verbose "Installing NuGet PackageProvider" + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + } + else { + chown -R $env:SUDO_USER $InstallPath + } write-verbose "Installing powershell-yaml" Install-Module -Name powershell-yaml -Force write-verbose "Importing invoke-atomicRedTeam module" - Import-Module "$InstallPath\atomic-red-team-master\execution-frameworks\Invoke-AtomicRedTeam\Invoke-AtomicRedTeam\Invoke-AtomicRedTeam.psm1" - - write-verbose "Changing current work directory Invoke-AtomicRedTeam" - cd "$InstallPath\atomic-red-team-master\execution-frameworks\Invoke-AtomicRedTeam\" + Import-Module $modulePath -Force - write-verbose "Clearing screen" - clear - - Write-Host "Installation of Invoke-AtomicRedTeam is complete" -Fore Yellow + Write-Host "Installation of Invoke-AtomicRedTeam is complete. You can now use the Invoke-AtomicTest function" -Fore Yellow + Write-Host "See README at https://github.com/redcanaryco/atomic-red-team/tree/master/execution-frameworks/Invoke-AtomicRedTeam for complete details" -Fore Yellow } else { - Write-Host "Atomic Redteam already exists at $InstallPath. Importing existing Invoke-atomicRedTeam module" - cd "$InstallPath\atomic-red-team-master\execution-frameworks\Invoke-AtomicRedTeam\" - Import-Module "$InstallPath\atomic-red-team-master\execution-frameworks\Invoke-AtomicRedTeam\Invoke-AtomicRedTeam\Invoke-AtomicRedTeam.psm1" -Force - + Write-Host -ForegroundColor Yellow "Atomic Redteam already exists at $InstallPath. No changes were made." + Write-Host -ForegroundColor Cyan "Try the install again with the '-Force' parameter if you want to delete the existing installion and re-install." + Write-Host -ForegroundColor Red "Warning: All files within the install directory ($InstallPath) will be deleted when using the '-Force' parameter." } -} \ No newline at end of file +} diff --git a/execution-frameworks/README.md b/execution-frameworks/README.md index 0eb57ceaea..3d61774726 100644 --- a/execution-frameworks/README.md +++ b/execution-frameworks/README.md @@ -6,7 +6,7 @@ Here is an [example markdown file](https://github.com/redcanaryco/atomic-red-tea ## Invoke-AtomicRedTeam -Invoke-AtomicRedTeam is written in PowerShell, which can be used cross-platform, but currently only supports the **_powershell_** and **_command_prompt_** executors (Windows stuff). +Invoke-AtomicRedTeam is written in PowerShell, which can be executed cross-platform using PowerShell Core for Linux and MacOS. For detailed installation and usage instructions refer to the [README](https://github.com/redcanaryco/atomic-red-team/tree/master/execution-frameworks/Invoke-AtomicRedTeam) file inside of the **_Invoke-AtomicRedTeam_** folder. ## Python @@ -15,4 +15,4 @@ Surprise, this framework is written in Python. For detailed installation and usa ## Ruby -Ruby version of the execution framework. \ No newline at end of file +Ruby version of the execution framework.