From 0dcde71a15a7bfa2af9884ced2db40a4bebd870c Mon Sep 17 00:00:00 2001 From: Carrie Roberts Date: Wed, 22 Jan 2020 20:36:20 -0700 Subject: [PATCH] Asynchronous Attack Execution and other handy things (#790) * execute attack in separate process * install from custom repoOwner and branch * remove zip after install * added showdetails brief and sleep for linux output * remove positional param spec * replacing special PathToAtomicsFolder in commands * use pwsh on linux * kill proc tree linux * include path in remove-item * update readme * update readme * update readme Co-authored-by: Tony M Lambert --- atomics/T1003/T1003.yaml | 14 ++-- atomics/T1035/T1035.yaml | 2 +- atomics/T1076/T1076.yaml | 2 +- atomics/T1086/T1086.yaml | 4 +- atomics/T1089/T1089.yaml | 6 +- atomics/T1197/T1197.yaml | 2 +- .../Private/Invoke-CheckPrereqs.ps1 | 7 +- .../Private/Invoke-ExecuteCommand.ps1 | 34 +++----- .../Private/Invoke-KillProcessTree.ps1 | 12 +++ .../Private/Invoke-Process.ps1 | 44 +++++++++++ .../Private/Replace-InputArgs.ps1 | 10 +-- .../Public/Invoke-AtomicTest.ps1 | 56 ++++++++----- .../Invoke-AtomicRedTeam/README.md | 79 ++++++++++++------- .../install-atomicredteam.ps1 | 17 ++-- 14 files changed, 192 insertions(+), 97 deletions(-) create mode 100644 execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-KillProcessTree.ps1 create mode 100644 execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-Process.ps1 diff --git a/atomics/T1003/T1003.yaml b/atomics/T1003/T1003.yaml index 6adf7cae..c7f961cf 100644 --- a/atomics/T1003/T1003.yaml +++ b/atomics/T1003/T1003.yaml @@ -36,7 +36,7 @@ atomic_tests: - description: | Windows Credential Editor must exist on disk at specified location (#{gsecdump_exe}) prereq_command: | - if (Test-Path #{gsecdump_exe}) {0} else {1} + if (Test-Path #{gsecdump_exe}) {exit 0} else {exit 1} get_prereq_command: | Write-Host Automated installer not implemented yet @@ -75,7 +75,7 @@ atomic_tests: - description: | Windows Credential Editor must exist on disk at specified location (#{wce_exe}) prereq_command: | - if (Test-Path #{wce_exe}) {0} else {1} + if (Test-Path #{wce_exe}) {exit 0} else {exit 1} get_prereq_command: | $parentpath = Split-Path "#{wce_exe}"; $zippath = "$parentpath\wce.zip" if(Invoke-WebRequestVerifyHash "#{wce_url}" "$zippath" #{wce_zip_hash}){ @@ -131,7 +131,7 @@ atomic_tests: - description: | ProcDump tool from Sysinternals must exist on disk at specified location (#{procdump_exe}) prereq_command: | - if (Test-Path #{procdump_exe}) {0} else {1} + if (Test-Path #{procdump_exe}) {exit 0} else {exit 1} get_prereq_command: | Invoke-WebRequest "https://download.sysinternals.com/files/Procdump.zip" -OutFile "$env:TEMP\Procdump.zip" Expand-Archive $env:TEMP\Procdump.zip $env:TEMP\Procdump -Force @@ -308,7 +308,7 @@ atomic_tests: - description: | Computer must be domain joined prereq_command: | - if((Get-CIMInstance -Class Win32_ComputerSystem).PartOfDomain) {0} else {1} + if((Get-CIMInstance -Class Win32_ComputerSystem).PartOfDomain) {exit 0} else {exit 1} get_prereq_command: | Write-Host Joining this computer to a domain must be done manually @@ -340,14 +340,14 @@ atomic_tests: - description: | Get-GPPPassword PowerShell Script must exist at #{gpp_script_path} prereq_command: | - if(Test-Path "#{gpp_script_path}") { 0 } else { 1 } + if(Test-Path "#{gpp_script_path}") {exit 0 } else {exit 1 } get_prereq_command: | New-Item -ItemType Directory (Split-Path "#{gpp_script_path}") -Force | Out-Null Invoke-WebRequest #{gpp_script_url} -OutFile "#{gpp_script_path}" - description: | Computer must be domain joined prereq_command: | - if((Get-CIMInstance -Class Win32_ComputerSystem).PartOfDomain) {0} else {1} + if((Get-CIMInstance -Class Win32_ComputerSystem).PartOfDomain) {exit 0} else {exit 1} get_prereq_command: | Write-Host Joining this computer to a domain must be done manually @@ -385,7 +385,7 @@ atomic_tests: - description: | Invoke-NinjaCopy PowerShell Script must exist at #{ninjacopy_script_path} prereq_command: | - if(Test-Path "#{ninjacopy_script_path}") { 0 } else { 1 } + if(Test-Path "#{ninjacopy_script_path}") {exit 0 } else {exit 1 } get_prereq_command: | New-Item -ItemType Directory (Split-Path "#{ninjacopy_script_path}") -Force | Out-Null Invoke-WebRequest #{ninjacopy_script_url} -OutFile "#{ninjacopy_script_path}" diff --git a/atomics/T1035/T1035.yaml b/atomics/T1035/T1035.yaml index 11568b99..47376548 100644 --- a/atomics/T1035/T1035.yaml +++ b/atomics/T1035/T1035.yaml @@ -45,7 +45,7 @@ atomic_tests: - description: | PsExec tool from Sysinternals must exist on disk at specified location (#{psexec_exe}) prereq_command: | - if (Test-Path "#{psexec_exe}"") {0} else {1} + if (Test-Path "#{psexec_exe}"") { exit 0} else { exit 1} get_prereq_command: | Invoke-WebRequest "https://download.sysinternals.com/files/PSTools.zip" -OutFile "$env:TEMP\PsTools.zip" Expand-Archive $env:TEMP\PsTools.zip $env:TEMP\PsTools -Force diff --git a/atomics/T1076/T1076.yaml b/atomics/T1076/T1076.yaml index 8613c263..6d350557 100644 --- a/atomics/T1076/T1076.yaml +++ b/atomics/T1076/T1076.yaml @@ -37,7 +37,7 @@ atomic_tests: - description: | Computer must be domain joined prereq_command: | - if((Get-CIMInstance -Class Win32_ComputerSystem).PartOfDomain) {0} else {1} + if((Get-CIMInstance -Class Win32_ComputerSystem).PartOfDomain) { exit 0} else { exit 1} get_prereq_command: | Write-Host Joining this computer to a domain must be done manually diff --git a/atomics/T1086/T1086.yaml b/atomics/T1086/T1086.yaml index 9a4dbc0c..4016baca 100644 --- a/atomics/T1086/T1086.yaml +++ b/atomics/T1086/T1086.yaml @@ -208,7 +208,7 @@ atomic_tests: - description: | PowerShell version 2 must be installed prereq_command: | - if(2 -in $PSVersionTable.PSCompatibleVersions.Major) {0} else {1} + if(2 -in $PSVersionTable.PSCompatibleVersions.Major) {exit 0} else {exit 1} get_prereq_command: | Write-Host Automated installer not implemented yet, please install PowerShell v2 manually @@ -233,7 +233,7 @@ atomic_tests: - description: | Homedrive must be an NTFS drive prereq_command: | - if((Get-Volume -DriveLetter $env:HOMEDRIVE[0]).FileSystem -contains "NTFS") {0} else {1} + if((Get-Volume -DriveLetter $env:HOMEDRIVE[0]).FileSystem -contains "NTFS") {exit 0} else {exit 1} get_prereq_command: | Write-Host Prereq's for this test cannot be met automatically diff --git a/atomics/T1089/T1089.yaml b/atomics/T1089/T1089.yaml index 1bb59b91..bb1e192b 100644 --- a/atomics/T1089/T1089.yaml +++ b/atomics/T1089/T1089.yaml @@ -143,7 +143,7 @@ atomic_tests: executor: name: powershell prereq_command: | - if(Test-Path C:\Windows\System32\inetsrv\appcmd.exe) {0} else {1} + if(Test-Path C:\Windows\System32\inetsrv\appcmd.exe) {exit 0} else {exit 1} command: | C:\Windows\System32\inetsrv\appcmd.exe set config "#{website_name}" /section:httplogging /dontLog:true cleanup_command: | @@ -166,7 +166,7 @@ atomic_tests: - description: | Sysmon executable must be available prereq_command: | - if(cmd /c where sysmon) {0} else {1} + if(cmd /c where sysmon) {exit 0} else {exit 1} get_prereq_command: | $parentpath = Split-Path "#{sysmon_exe}"; $zippath = "$parentpath\Sysmon.zip" New-Item -ItemType Directory $parentpath -Force | Out-Null @@ -176,7 +176,7 @@ atomic_tests: - description: | Sysmon must be installed prereq_command: | - if(cmd /c sc query sysmon) {0} else {1} + if(cmd /c sc query sysmon) { exit 0} else { exit 1} get_prereq_command: | cmd /c sysmon -i -accepteula diff --git a/atomics/T1197/T1197.yaml b/atomics/T1197/T1197.yaml index 76d49047..74ab6fcb 100644 --- a/atomics/T1197/T1197.yaml +++ b/atomics/T1197/T1197.yaml @@ -46,7 +46,7 @@ atomic_tests: command: | Start-BitsTransfer -Priority foreground -Source #{remote_file} -Destination #{local_file} cleanup_command: | - Remove-Item #{local_file} + Remove-Item #{local_file} -ErrorAction Ignore - name: Persist, Download, & Execute description: | diff --git a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-CheckPrereqs.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-CheckPrereqs.ps1 index 3834d96f..5e4c2246 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-CheckPrereqs.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-CheckPrereqs.ps1 @@ -1,4 +1,4 @@ -function Invoke-CheckPrereqs ($test, $isElevated, $customInputArgs, $PathToAtomicsFolder) { +function Invoke-CheckPrereqs ($test, $isElevated, $customInputArgs, $PathToAtomicsFolder, $TimeoutSeconds) { $FailureReasons = New-Object System.Collections.ArrayList if ($test.executor.elevation_required -and -not $isElevated) { $FailureReasons.add("Elevation required but not provided`n") | Out-Null @@ -6,9 +6,10 @@ function Invoke-CheckPrereqs ($test, $isElevated, $customInputArgs, $PathToAtomi foreach ($dep in $test.dependencies) { $executor = Get-PrereqExecutor $test $final_command = Merge-InputArgs $dep.prereq_command $test $customInputArgs $PathToAtomicsFolder - $success = Invoke-ExecuteCommand $final_command $executor + $final_command = ($final_Command.trim()).Replace("`n", " && ") + $res = Invoke-ExecuteCommand $final_command $executor $TimeoutSeconds $description = Merge-InputArgs $dep.description $test $customInputArgs $PathToAtomicsFolder - if (-not $success) { + if ($res -ne 0) { $FailureReasons.add($description) | Out-Null } } diff --git a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-ExecuteCommand.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-ExecuteCommand.ps1 index 277df4d0..d04de038 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-ExecuteCommand.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-ExecuteCommand.ps1 @@ -1,33 +1,25 @@ function Invoke-ExecuteCommand ($finalCommand, $executor) { $null = @( - $success = $true - if($null -eq $finalCommand) { return $success} + if($null -eq $finalCommand){return 0} + $finalCommand = $finalCommand.trim() Write-Verbose -Message 'Invoking Atomic Tests using defined executor' - $finalCommandEscaped = $finalCommand -replace "`"", "```"" 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 { - $retval = Invoke-Expression "$execPrefix `"$_`" " - if ($nul -ne $retval) { $retval | Write-Host } - $exitCodes.Add($LASTEXITCODE) | Out-Null - } - $nonZeroExitCodes = $exitCodes | Where-Object { $_ -ne 0 } - $success = $nonZeroExitCodes.Count -eq 0 + $execCommand = $finalCommand.Replace("`n", " & ") + $execPrefix = "-c" + $execExe = $executor + if ($executor -eq "command_prompt") { $execPrefix = "/c"; $execExe = "cmd.exe" } + $res = Invoke-Process -filename $execExe -Arguments "$execPrefix `"$execCommand`"" -TimeoutSeconds $TimeoutSeconds } elseif ($executor -eq "powershell") { - $finalCommand = $finalCommand.trim() - $execCommand = "Invoke-Command -ScriptBlock {$finalCommand}" - $res = Invoke-Expression $execCommand - $success = [string]::IsNullOrEmpty($finalCommand) -or $res -eq 0 + $execCommand = $finalCommand -replace "`"", "`\`"`"" + $execExe = "powershell.exe"; if ($IsLinux -or $IsMacOS) { $execExe = "pwsh" } + $res = Invoke-Process -filename $execExe -Arguments "& {$execCommand}" -TimeoutSeconds $TimeoutSeconds + } else { Write-Warning -Message "Unable to generate or execute the command line properly. Unknown executor" - $success = $false + $res = -1 } ) - $success + $res } \ No newline at end of file diff --git a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-KillProcessTree.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-KillProcessTree.ps1 new file mode 100644 index 00000000..e8091e32 --- /dev/null +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-KillProcessTree.ps1 @@ -0,0 +1,12 @@ +function Invoke-KillProcessTree { + Param([int]$ppid) + if ($IsLinux -or $IsMacOS) { + sh -c pkill -9 -P $ppid + } + else { + while ($null -ne ($gcim = Get-CimInstance Win32_Process | Where-Object { $_.ParentProcessId -eq $ppid })) { + $gcim | ForEach-Object { Invoke-KillProcessTree $_.ProcessId; Start-Sleep -Seconds 0.5 } + } + Stop-Process -Id $ppid -ErrorAction Ignore + } +} \ No newline at end of file diff --git a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-Process.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-Process.ps1 new file mode 100644 index 00000000..e44165c7 --- /dev/null +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Invoke-Process.ps1 @@ -0,0 +1,44 @@ +# The Invoke-Process function is loosely based on code from https://github.com/guitarrapc/PowerShellUtil/blob/master/Invoke-Process/Invoke-Process.ps1 +function Invoke-Process { + [OutputType([PSCustomObject])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $false, Position = 0)] + [string]$FileName = "PowerShell.exe", + + [Parameter(Mandatory = $false, Position = 1)] + [string]$Arguments = "", + + [Parameter(Mandatory = $false, Position = 2)] + [string]$WorkingDirectory = ".", + + [Parameter(Mandatory = $false, Position = 3)] + [Int]$TimeoutSeconds = 120 + ) + + end { + try { + # new Process + $process = Start-Process -FilePath $FileName -ArgumentList $Arguments -WorkingDirectory $WorkingDirectory -NoNewWindow -PassThru + $handle = $process.Handle # cache process.Handle, otherwise ExitCode is null from powershell processes + + # wait for complete + $Timeout = [System.TimeSpan]::FromSeconds(($TimeoutSeconds)) + if (-not $process.WaitForExit($Timeout.TotalMilliseconds)) { + Write-Host -ForegroundColor Red "Process Timed out after $TimeoutSeconds seconds, use '-TimeoutSeconds' to specify a different timeout" + Invoke-KillProcessTree $process.id + } + + if ($IsLinux -or $IsMacOS) { + Start-Sleep -Seconds 5 # On nix, the last 4 lines of stdout get overwritten upon return so pause for a bit to ensure user can view results + } + + # Get Process result + return $process.ExitCode + } + finally { + if ($null -ne $process) { $process.Dispose() } + } + } +} diff --git a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Replace-InputArgs.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Replace-InputArgs.ps1 index c3847c4c..725a9c6c 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Replace-InputArgs.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Private/Replace-InputArgs.ps1 @@ -10,10 +10,6 @@ function Get-InputArgs([hashtable]$ip, $customInputArgs, $PathToAtomicsFolder) { $defaultArgs.set_Item($key, $customInputArgs[$key]) } } - # Replace $PathToAtomicsFolder or PathToAtomicsFolder with the actual -PathToAtomicsFolder value - foreach ($key in $defaultArgs.Clone().Keys) { - $defaultArgs.set_Item($key, ($defaultArgs[$key] -replace "\`$PathToAtomicsFolder", $PathToAtomicsFolder -replace "PathToAtomicsFolder", $PathToAtomicsFolder)) - } $defaultArgs } @@ -27,5 +23,9 @@ function Merge-InputArgs($finalCommand, $test, $customInputArgs, $PathToAtomicsF $finalCommand = $finalCommand.Replace($findValue, $inputArgs[$key]) } } + + # Replace $PathToAtomicsFolder or PathToAtomicsFolder with the actual -PathToAtomicsFolder value + $finalCommand = ($finalCommand -replace "\`$PathToAtomicsFolder", $PathToAtomicsFolder) -replace "PathToAtomicsFolder", $PathToAtomicsFolder + $finalCommand -} \ No newline at end of file +} \ No newline at end of file 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 c5df7c4a..f443bc43 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Public/Invoke-AtomicTest.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/Invoke-AtomicRedTeam/Public/Invoke-AtomicTest.ps1 @@ -32,12 +32,17 @@ function Invoke-AtomicTest { $AtomicTechnique, [Parameter(Mandatory = $false, - Position = 1, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'technique')] [switch] $ShowDetails, + [Parameter(Mandatory = $false, + ValueFromPipelineByPropertyName = $true, + ParameterSetName = 'technique')] + [switch] + $ShowDetailsBrief, + [Parameter(Mandatory = $false, ParameterSetName = 'technique')] [String[]] @@ -89,7 +94,12 @@ function Invoke-AtomicTest { [Parameter(Mandatory = $false, ParameterSetName = 'technique')] [HashTable] - $InputArgs + $InputArgs, + + [Parameter(Mandatory = $false, + ParameterSetName = 'technique')] + [Int] + $TimeoutSeconds = 120 ) BEGIN { } # Intentionally left blank and can be removed PROCESS { @@ -166,38 +176,43 @@ function Invoke-AtomicTest { continue } + $testId = "$AT-$testCount $($test.name)" if ($ShowDetails) { Show-Details $test $testCount $technique $InputArgs $PathToAtomicsFolder continue } + if($ShowDetailsBrief){ + Write-KeyValue $testId + continue + } Write-Debug -Message 'Gathering final Atomic test command' - $testId = "$AT-$testCount $($test.name)" if ($CheckPrereqs) { Write-KeyValue "CheckPrereq's for: " $testId - $failureReasons = Invoke-CheckPrereqs $test $isElevated $InputArgs $PathToAtomicsFolder + $failureReasons = Invoke-CheckPrereqs $test $isElevated $InputArgs $PathToAtomicsFolder $TimeoutSeconds Write-PrereqResults $FailureReasons $testId } elseif ($GetPrereqs) { Write-KeyValue "GetPrereq's for: " $testId - if ($nul -eq $test.dependencies) { Write-KeyValue "No Preqs Defined"; continue} + if ($nul -eq $test.dependencies) { Write-KeyValue "No Preqs Defined"; continue } foreach ($dep in $test.dependencies) { $executor = Get-PrereqExecutor $test $description = (Merge-InputArgs $dep.description $test $InputArgs $PathToAtomicsFolder).trim() Write-KeyValue "Attempting to satisfy prereq: " $description $final_command_prereq = Merge-InputArgs $dep.prereq_command $test $InputArgs $PathToAtomicsFolder + $final_command_prereq = ($final_command_prereq.trim()).Replace("`n", " && ") $final_command_get_prereq = Merge-InputArgs $dep.get_prereq_command $test $InputArgs $PathToAtomicsFolder - $success = Invoke-ExecuteCommand $final_command_prereq $executor - if ($success) { + $res = Invoke-ExecuteCommand $final_command_prereq $executor $TimeoutSeconds + + if ($res -eq 0) { Write-KeyValue "Prereq already met: " $description } else { - - $retval = Invoke-ExecuteCommand $final_command_get_prereq $executor - $success = Invoke-ExecuteCommand $final_command_prereq $executor - if ($success) { + $res = Invoke-ExecuteCommand $final_command_get_prereq $executor $TimeoutSeconds + $res = Invoke-ExecuteCommand $final_command_prereq $executor $TimeoutSeconds + if ($res -eq 0) { Write-KeyValue "Prereq successfully met: " $description } else { @@ -210,18 +225,18 @@ function Invoke-AtomicTest { } } elseif ($Cleanup) { - Write-KeyValue "Executing Cleanup for Test: " $testId + Write-KeyValue "Executing cleanup for test: " $testId $final_command = Merge-InputArgs $test.executor.cleanup_command $test $InputArgs $PathToAtomicsFolder - Invoke-ExecuteCommand $final_command $test.executor.name | Out-Null - Write-KeyValue "Done" + $res = Invoke-ExecuteCommand $final_command $test.executor.name $TimeoutSeconds + Write-KeyValue "Done executing cleanup for test: " $testId } else { - Write-KeyValue "Executing Test: " $testId + Write-KeyValue "Executing test: " $testId $startTime = get-date $final_command = Merge-InputArgs $test.executor.command $test $InputArgs $PathToAtomicsFolder - Invoke-ExecuteCommand $final_command $test.executor.name | Out-Null - Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath - Write-KeyValue "Done" + $res = Invoke-ExecuteCommand $final_command $test.executor.name $TimeoutSeconds + Write-ExecutionLog $startTime $AT $testCount $testName $ExecutionLogPath $TimeoutSeconds + Write-KeyValue "Done executing test: " $testId } } # End of foreach Test in single Atomic Technique @@ -238,7 +253,7 @@ function Invoke-AtomicTest { $AllAtomicTests.GetEnumerator() | Foreach-Object { Invoke-AtomicTestSingle $_ } } - if ( ($Force -or $CheckPrereqs -or $ShowDetails -or $GetPrereqs) -or $psCmdlet.ShouldContinue( 'Do you wish to execute all tests?', + if ( ($Force -or $CheckPrereqs -or $ShowDetails -or $ShowDetailsBrief -or $GetPrereqs) -or $psCmdlet.ShouldContinue( 'Do you wish to execute all tests?', "Highway to the danger zone, Executing All Atomic Tests!" ) ) { Invoke-AllTests } @@ -249,4 +264,5 @@ function Invoke-AtomicTest { } # End of PROCESS block END { } # Intentionally left blank and can be removed -} \ No newline at end of file +} +# Invoke-AtomicTest t1003 -TestNumbers 13 -GetPrereqs \ No newline at end of file diff --git a/execution-frameworks/Invoke-AtomicRedTeam/README.md b/execution-frameworks/Invoke-AtomicRedTeam/README.md index 33853876..417fdee7 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/README.md +++ b/execution-frameworks/Invoke-AtomicRedTeam/README.md @@ -46,6 +46,16 @@ Force `Install-AtomicRedTeam -Force` +RepoOwner +- Install ART from another repo. Default RepoOwner is "redcanaryco" + + `Install-AtomicRedTeam -RepoOwner clr2of8` + +Branch +- Install ART from another branch. Default Branch is "master" + + `Install-AtomicRedTeam -RepoOwner clr2of8 -Branch start-process-branch` + ### Manual Installation @@ -69,12 +79,26 @@ Import-Module C:\AtomicRedTeam\execution-frameworks\Invoke-AtomicRedTeam\Invoke- Note: Your path to the **_Invoke-AtomicRedTeam.psm1_** may be different. -#### Execute All Tests -Execute all Atomic tests: +#### Display Test Details for the given Technique Number without Executing any Tests ```powershell -Invoke-AtomicTest All +Invoke-AtomicTest T1089 -ShowDetails +``` + +Using the `ShowDetails` switch causes the test details to be printed to the screen and allows for easy copy and paste execution. +Note: you may need to change the path where the test definitions are found with the `PathToAtomicsFolder` parameter. + +#### Display Only Test Names and Numbers + +```powershell +Invoke-AtomicTest All -ShowDetailsBrief +``` + +#### Execute All Attacks for a Given Technique + +```powershell +Invoke-AtomicTest T1117 ``` This assumes your atomics folder is in the default location of `\AtomicRedTeam\atomics` @@ -89,29 +113,6 @@ $PSDefaultParameterValues = @{"Invoke-AtomicTest:PathToAtomicsFolder"="C:\Users\ Tip: Add this to your PowerShell profile so it is always set to your preferred default value. -#### Execute All Tests - Specific Directory - -Specify a path to atomics folder, example C:\AtomicRedTeam\atomics - -```powershell -Invoke-AtomicTest All -PathToAtomicsFolder C:\AtomicRedTeam\atomics -``` - -#### Display Test Details without Executing the Test - -```powershell -Invoke-AtomicTest All -ShowDetails -``` - -Using the `ShowDetails` switch causes the test details to be printed to the screen and allows for easy copy and paste execution. -Note: you may need to change the path where the test definitions are found with the `PathToAtomicsFolder` parameter. - -#### Execute All Attacks for a Given Technique - -```powershell -Invoke-AtomicTest T1117 -``` - #### Execute Specific Attacks (by Attack Number) for a Given Technique ```powershell @@ -124,7 +125,29 @@ Invoke-AtomicTest T1117 -TestNumbers 1, 2 Invoke-AtomicTest T1117 -TestNames "Regsvr32 remote COM scriptlet execution","Regsvr32 local DLL execution" ``` -By default, test execution details are written to `Invoke-AtomicTest-ExecutionLog.csv` in the current directory. +#### Speficy a Process Timeout + +```powershell +Invoke-AtomicTest T1117 -TimeoutSeconds 15 +``` + +If the attack commands do not exit (return) within in the specified `-TimeoutSeconds`, the process and it's children will be forcefully terminated. The default value of `-TimeoutSeconds` is 120. This allows the `Invoke-AtomicTest` script to move on to the next test. + +#### Execute All Tests + +Execute all Atomic tests: + +```powershell +Invoke-AtomicTest All +``` + +#### Execute All Tests from a Specific Directory + +Specify a path to atomics folder, example C:\AtomicRedTeam\atomics + +```powershell +Invoke-AtomicTest All -PathToAtomicsFolder C:\AtomicRedTeam\atomics +``` #### Specify an Alternate Path for the Execution Log @@ -142,7 +165,7 @@ Invoke-AtomicTest T1117 -TestNumber 1 -CheckPrereqs 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 }** +For the "powershell" executor, the prereq_command's are run as a script block and the script must exit 0 if the pre-requisites are met. Example: **if(Test-Path C:\Windows\System32\cmd.exe) { exit 0 } else { exit 1 }** Pre-requisites will also be reported as not met if the test is defined with `elevation_required: true` but the current context is not elevated. You can still execute an attack even if the pre-requisites are not met but execution may fail. diff --git a/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 b/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 index fb0bd5b7..a58ab458 100644 --- a/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 +++ b/execution-frameworks/Invoke-AtomicRedTeam/install-atomicredteam.ps1 @@ -41,6 +41,12 @@ function Install-AtomicRedTeam { [Parameter(Mandatory = $False, Position = 1)] [string]$DownloadPath = $( if ($IsLinux -or $IsMacOS) { $Env:HOME + "/AtomicRedTeam" } else { $env:HOMEDRIVE + "\AtomicRedTeam" }), + [Parameter(Mandatory = $False, Position = 2)] + [string]$RepoOwner = "redcanaryco", + + [Parameter(Mandatory = $False, Position = 3)] + [string]$Branch = "master", + [Parameter(Mandatory = $False)] [switch]$Force = $False # delete the existing install directory and reinstall ) @@ -60,19 +66,20 @@ function Install-AtomicRedTeam { New-Item -ItemType directory -Path $InstallPath | Out-Null write-verbose "Setting variables for remote URL and download Path" - $url = "https://github.com/redcanaryco/atomic-red-team/archive/master.zip" - $path = Join-Path $DownloadPath "master.zip" + $url = "https://github.com/$RepoOwner/atomic-red-team/archive/$Branch.zip" + $path = Join-Path $DownloadPath "$Branch.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 $InstallPath" - $lp = Join-Path "$DownloadPath" "master.zip" + $lp = Join-Path "$DownloadPath" "$Branch.zip" expand-archive -LiteralPath $lp -DestinationPath "$InstallPath" -Force:$Force - $unzipPath = Join-Path $InstallPath "atomic-red-team-master" + $unzipPath = Join-Path $InstallPath "atomic-red-team-$Branch" Get-ChildItem $unzipPath -Force | Move-Item -dest $InstallPath Remove-Item $unzipPath + Remove-Item $path if (-not (Get-InstalledModule -Name "powershell-yaml" -ErrorAction:SilentlyContinue)) { write-verbose "Installing powershell-yaml" @@ -83,7 +90,7 @@ function Install-AtomicRedTeam { Import-Module $modulePath -Force 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 + Write-Host "See README at https://github.com/$RepoOwner/atomic-red-team/tree/$Branch/execution-frameworks/Invoke-AtomicRedTeam for complete details" -Fore Yellow } else {