From f89994f644abf5f60678b3a15a0641797d93b23c Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 00:48:26 -0500 Subject: [PATCH 1/8] fixing psscript issues --- .github/workflows/lint.yml | 17 +++++++++++++++++ PSScriptAnalyzerSettings.psd1 | 5 +++++ 2 files changed, 22 insertions(+) create mode 100644 .github/workflows/lint.yml create mode 100644 PSScriptAnalyzerSettings.psd1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..f9e5231 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,17 @@ +name: Check installation +on: pull_request + +jobs: + install-invoke: + name: Install Invoke-Atomic + runs-on: "ubuntu-latest" + steps: + - name: Install Invoke-AtomicRedTeam + shell: pwsh + run: | + Install-Module -Name PSScriptAnalyzer -Force + Invoke-ScriptAnalyzer -Recurse ./ -Settings ./PSScriptAnalyzerSettings.psd1 -Fix + - name: Check whether there are any file changes + shell: bash + run: | + git diff --exit-code diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 0000000..0280d7e --- /dev/null +++ b/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,5 @@ +# PSScriptAnalyzerSettings.psd1 +@{ + ExcludeRules=@('PSUseSingularNouns', + 'PSAvoidUsingWriteHost') +} \ No newline at end of file From d9e056c748cc53332731efd29a45701dd2b558af Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 00:49:12 -0500 Subject: [PATCH 2/8] fixing psscript issues --- Private/Get-PrereqExecutor.ps1 | 4 +- Private/Get-TargetInfo.ps1 | 9 ++-- Private/Invoke-ExecuteCommand.ps1 | 12 ++--- Private/Invoke-Process.ps1 | 26 +++++------ Private/Show-Details.ps1 | 4 +- Public/Attire-ExecutionLogger.psm1 | 12 ++--- Public/Default-ExecutionLogger.psm1 | 12 ++--- Public/Get-AtomicTechnique.ps1 | 60 ++++++++++++------------- Public/Get-PreferredIPAddress.ps1 | 4 +- Public/Invoke-AtomicRunner.ps1 | 26 +++++------ Public/Invoke-AtomicTest.ps1 | 24 +++++----- Public/Invoke-FetchFromZip.ps1 | 10 ++--- Public/Invoke-KickoffAtomicRunner.ps1 | 12 ++--- Public/Invoke-RunnerScheduleMethods.ps1 | 30 ++++++------- Public/Invoke-SetupAtomicRunner.ps1 | 8 ++-- Public/Invoke-WebRequestVerifyHash.ps1 | 8 ++-- Public/New-Atomic.ps1 | 4 +- Public/Start-AtomicGUI.ps1 | 44 +++++++++--------- Public/Syslog-ExecutionLogger.psm1 | 10 ++--- Public/WinEvent-ExecutionLogger.psm1 | 8 ++-- Public/config.ps1 | 4 +- sandbox/setupsandbox.ps1 | 6 +-- 22 files changed, 168 insertions(+), 169 deletions(-) diff --git a/Private/Get-PrereqExecutor.ps1 b/Private/Get-PrereqExecutor.ps1 index ef9278b..1f5c119 100644 --- a/Private/Get-PrereqExecutor.ps1 +++ b/Private/Get-PrereqExecutor.ps1 @@ -1,5 +1,5 @@ -function Get-PrereqExecutor ($test) { - if ($nul -eq $test.dependency_executor_name) { $executor = $test.executor.name } +function Get-PrereqExecutor ($test) { + if ($nul -eq $test.dependency_executor_name) { $executor = $test.executor.name } else { $executor = $test.dependency_executor_name } $executor } \ No newline at end of file diff --git a/Private/Get-TargetInfo.ps1 b/Private/Get-TargetInfo.ps1 index 9c54bcb..d75150c 100644 --- a/Private/Get-TargetInfo.ps1 +++ b/Private/Get-TargetInfo.ps1 @@ -1,4 +1,4 @@ -function Get-TargetInfo($Session) { +function Get-TargetInfo($Session) { $tmpDir = "$env:TEMP\" $isElevated = $false $targetHostname = hostname @@ -17,7 +17,7 @@ function Get-TargetInfo($Session) { } if ($IsLinux -or $IsMacOS) { $isElevated = $false - $privid = id -u + $privid = id -u if ($privid -eq 0) { $isElevated = $true } } $targetPlatform, $isElevated, $tmpDir, $targetHostname, $targetUser @@ -28,7 +28,7 @@ function Get-TargetInfo($Session) { if ($IsLinux -or $IsMacOS) { $tmpDir = "/tmp/" $isElevated = $false - $privid = id -u + $privid = id -u if ($privid -eq 0) { $isElevated = $true } if ($IsMacOS) { $targetPlatform = "macos" } } @@ -36,7 +36,6 @@ function Get-TargetInfo($Session) { $targetPlatform = "windows" $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } - + } $targetPlatform, $isElevated, $tmpDir, $targetHostname, $targetUser -} \ No newline at end of file diff --git a/Private/Invoke-ExecuteCommand.ps1 b/Private/Invoke-ExecuteCommand.ps1 index f9fbdba..2952980 100644 --- a/Private/Invoke-ExecuteCommand.ps1 +++ b/Private/Invoke-ExecuteCommand.ps1 @@ -1,4 +1,4 @@ -function Invoke-ExecuteCommand ($finalCommand, $executor, $executionPlatform, $TimeoutSeconds, $session = $null, $interactive) { +function Invoke-ExecuteCommand ($finalCommand, $executor, $executionPlatform, $TimeoutSeconds, $session = $null, $interactive) { $null = @( if ($null -eq $finalCommand) { return 0 } $finalCommand = $finalCommand.trim() @@ -6,10 +6,10 @@ function Invoke-ExecuteCommand ($finalCommand, $executor, $executionPlatform, $T if ($executor -eq "command_prompt" -or $executor -eq "sh" -or $executor -eq "bash") { $execPrefix = "-c" $execExe = $executor - if ($executor -eq "command_prompt") { - $execPrefix = "/c"; - $execExe = "cmd.exe"; - $execCommand = $finalCommand -replace "`n", " & " + if ($executor -eq "command_prompt") { + $execPrefix = "/c"; + $execExe = "cmd.exe"; + $execCommand = $finalCommand -replace "`n", " & " $arguments = $execPrefix,"$execCommand" } else { @@ -67,7 +67,7 @@ function Invoke-ExecuteCommand ($finalCommand, $executor, $executionPlatform, $T else { # Local execution that DO NOT contain interactive prompts # In this situation, capture the stdout/stderr for Invoke-AtomicTest to send to the caller - $res = Invoke-Process -filename $execExe -Arguments $arguments -TimeoutSeconds $TimeoutSeconds -stdoutFile "art-out.txt" -stderrFile "art-err.txt" + $res = Invoke-Process -filename $execExe -Arguments $arguments -TimeoutSeconds $TimeoutSeconds -stdoutFile "art-out.txt" -stderrFile "art-err.txt" } } ) diff --git a/Private/Invoke-Process.ps1 b/Private/Invoke-Process.ps1 index 603334e..8ea0e5c 100644 --- a/Private/Invoke-Process.ps1 +++ b/Private/Invoke-Process.ps1 @@ -1,4 +1,4 @@ -# The Invoke-Process function is loosely based on code from https://github.com/guitarrapc/PowerShellUtil/blob/master/Invoke-Process/Invoke-Process.ps1 +# 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()] @@ -9,7 +9,7 @@ function Invoke-Process { [Parameter(Mandatory = $false, Position = 1)] [string[]]$Arguments = "", - + [Parameter(Mandatory = $false, Position = 3)] [Int]$TimeoutSeconds = 120, @@ -27,11 +27,11 @@ function Invoke-Process { if ($stdoutFile) { # new Process $process = NewProcess -FileName $FileName -Arguments $Arguments -WorkingDirectory $WorkingDirectory - + # Event Handler for Output $stdSb = New-Object -TypeName System.Text.StringBuilder $errorSb = New-Object -TypeName System.Text.StringBuilder - $scripBlock = + $scripBlock = { $x = $Event.SourceEventArgs.Data if (-not [String]::IsNullOrEmpty($x)) @@ -54,7 +54,7 @@ function Invoke-Process { $isTimeout = $true Invoke-KillProcessTree $process.id Write-Host -ForegroundColor Red "Process Timed out after $TimeoutSeconds seconds, use '-TimeoutSeconds' to specify a different timeout" - } + } $process.CancelOutputRead() $process.CancelErrorRead() @@ -91,7 +91,7 @@ function Invoke-Process { # Add a warning in stdoutFile in case of timeout # problem: $stdoutFile was locked in writing by the process we just killed, sometimes it's too fast and the lock isn't released immediately # solution: retry at most 10 times with 100ms between each attempt - For($i=0;$i -lt 10;$i++) { + For($i=0;$i -lt 10;$i++) { try { "" | Out-File (Join-Path $WorkingDirectory $stdoutFile) -Append -Encoding ASCII break # if we're here it means the file wasn't locked and Out-File worked, so we can leave the retry loop @@ -104,7 +104,7 @@ function Invoke-Process { 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 [PSCustomObject]@{ StandardOutput = "" @@ -115,7 +115,7 @@ function Invoke-Process { } } - + } finally { if ($null -ne $process) { $process.Dispose() } @@ -134,16 +134,16 @@ function Invoke-Process { ( [parameter(Mandatory = $true)] [string]$FileName, - + [parameter(Mandatory = $false)] [string[]]$Arguments, - + [parameter(Mandatory = $false)] [string]$WorkingDirectory ) # ProcessStartInfo - $psi = New-object System.Diagnostics.ProcessStartInfo + $psi = New-object System.Diagnostics.ProcessStartInfo $psi.CreateNoWindow = $true $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true @@ -153,7 +153,7 @@ function Invoke-Process { $psi.WorkingDirectory = $WorkingDirectory # Set Process - $process = New-Object System.Diagnostics.Process + $process = New-Object System.Diagnostics.Process $process.StartInfo = $psi $process.EnableRaisingEvents = $true return $process @@ -177,7 +177,7 @@ function Invoke-Process { [parameter(Mandatory = $true)] [Bool]$IsTimeout ) - + return [PSCustomObject]@{ StandardOutput = $StandardStringBuilder.ToString().Trim() ErrorOutput = $ErrorStringBuilder.ToString().Trim() diff --git a/Private/Show-Details.ps1 b/Private/Show-Details.ps1 index 2ca55c2..b1dca15 100644 --- a/Private/Show-Details.ps1 +++ b/Private/Show-Details.ps1 @@ -1,10 +1,10 @@ -function Invoke-CleanupDescription(){ +function Invoke-CleanupDescription(){ $ret1 = $test.description.ToString().trim() -replace '(? - + [CmdletBinding(DefaultParameterSetName = 'FilePath')] [OutputType([AtomicTechnique])] param ( @@ -322,7 +322,7 @@ filter Get-AtomicTechnique { $j = 0 - foreach ($InputArgName in $AtomicTest['input_arguments'].Keys) { + foreach ($InputArgName in $AtomicTest['input_arguments'].Keys) { $InputArgument = [AtomicInputArgument]::new() diff --git a/Public/Get-PreferredIPAddress.ps1 b/Public/Get-PreferredIPAddress.ps1 index 78e46e9..aa7d2db 100644 --- a/Public/Get-PreferredIPAddress.ps1 +++ b/Public/Get-PreferredIPAddress.ps1 @@ -1,10 +1,10 @@ -function Get-PreferredIPAddress($isWindows){ +function Get-PreferredIPAddress($isWindows){ if ($isWindows){ return (Get-NetIPAddress | Where-Object { $_.PrefixOrigin -ne "WellKnown"}).IPAddress } elseif ($IsMacOS) { return ifconfig -l | xargs -n1 ipconfig getifaddr - } + } elseif ($IsLinux) { return ip -4 -br addr show |sed -n -e 's/^.*UP\s* //p'|cut -d "/" -f 1 } diff --git a/Public/Invoke-AtomicRunner.ps1 b/Public/Invoke-AtomicRunner.ps1 index a550d1e..d84753e 100755 --- a/Public/Invoke-AtomicRunner.ps1 +++ b/Public/Invoke-AtomicRunner.ps1 @@ -1,4 +1,4 @@ -. "$PSScriptRoot\Invoke-RunnerScheduleMethods.ps1" +. "$PSScriptRoot\Invoke-RunnerScheduleMethods.ps1" function Invoke-AtomicRunner { [CmdletBinding( @@ -41,7 +41,7 @@ function Invoke-AtomicRunner { $OtherArgs ) Begin { } - Process { + Process { function Get-GuidFromHostName( $basehostname ) { $guid = [System.Net.Dns]::GetHostName() -replace $($basehostname + "-"), "" @@ -50,7 +50,7 @@ function Invoke-AtomicRunner { LogRunnerMsg "Hostname has not been updated or could not parse out the Guid: " + $guid return } - + # Confirm hostname contains a guid [regex]$guidRegex = '(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$' @@ -139,9 +139,9 @@ function Invoke-AtomicRunner { } exit } - + } - + function Get-TimingVariable ($sched) { $atcount = $sched.Count if ($null -eq $atcount) { $atcount = 1 } @@ -151,7 +151,7 @@ function Invoke-AtomicRunner { if ($sleeptime -lt 120) { $sleeptime = 120 } # minimum 2 minute sleep time return $sleeptime } - + # Convert OtherArgs to hashtable so we can pass it through to the call to Invoke-AtomicTest $htvars = @{} if ($OtherArgs) { @@ -203,7 +203,7 @@ function Invoke-AtomicRunner { Write-Host -ForegroundColor Yellow "Exiting script because $($artConfig.stopFile) does exist."; Start-Sleep 10; exit } - + # Find current test to run $guid = Get-GuidFromHostName $artConfig.basehostname if ([string]::IsNullOrWhiteSpace($guid)) { @@ -230,21 +230,21 @@ function Invoke-AtomicRunner { } # Load next scheduled test before renaming computer - $nextIndex += $currentIndex + 1 + $nextIndex += $currentIndex + 1 if ($nextIndex -ge ($schedule.count)) { $tr = $schedule[0] } else { $tr = $schedule[$nextIndex] } - - if ($null -eq $tr) { - LogRunnerMsg "Could not determine the next row to execute from the schedule, Starting from 1st row"; - $tr = $schedule[0] + + if ($null -eq $tr) { + LogRunnerMsg "Could not determine the next row to execute from the schedule, Starting from 1st row"; + $tr = $schedule[0] } #Rename Computer and Restart Rename-ThisComputer $tr $artConfig.basehostname - + } } \ No newline at end of file diff --git a/Public/Invoke-AtomicTest.ps1 b/Public/Invoke-AtomicTest.ps1 index c7479e5..eaebb50 100644 --- a/Public/Invoke-AtomicTest.ps1 +++ b/Public/Invoke-AtomicTest.ps1 @@ -1,4 +1,4 @@ -function Invoke-AtomicTest { +function Invoke-AtomicTest { [CmdletBinding(DefaultParameterSetName = 'technique', SupportsShouldProcess = $true, PositionalBinding = $false, @@ -93,7 +93,7 @@ function Invoke-AtomicTest { ParameterSetName = 'technique')] [HashTable] $InputArgs, - + [Parameter(Mandatory = $false, ParameterSetName = 'technique')] [Int] @@ -128,10 +128,10 @@ function Invoke-AtomicTest { BEGIN { } # Intentionally left blank and can be removed PROCESS { $PathToAtomicsFolder = (Resolve-Path $PathToAtomicsFolder).Path - + Write-Verbose -Message 'Attempting to run Atomic Techniques' if (-not $supressPathToAtomicsFolder) { Write-Host -ForegroundColor Cyan "PathToAtomicsFolder = $PathToAtomicsFolder`n" } - + $executionPlatform, $isElevated, $tmpDir, $executionHostname, $executionUser = Get-TargetInfo $Session $PathToPayloads = if ($Session) { "$tmpDir`AtomicRedTeam" } else { $PathToAtomicsFolder } @@ -158,7 +158,7 @@ function Invoke-AtomicTest { else { $LoggingModule = $artConfig.LoggingModule } - } + } } if ($isLoggingModuleSet) { @@ -198,7 +198,7 @@ function Invoke-AtomicTest { if ($AtomicTechnique -is [array]) { $AtomicTechnique = $AtomicTechnique -join "," } - + # Splitting Atomic Technique short form into technique and test numbers. $AtomicTechniqueParams = ($AtomicTechnique -split '-') $AtomicTechnique = $AtomicTechniqueParams[0] @@ -210,7 +210,7 @@ function Invoke-AtomicTest { if ($null -eq $TestNumbers -and $null -ne $ShortTestNumbers) { $TestNumbers = $ShortTestNumbers -split ',' } - + # Here we're rebuilding an equivalent command line to put in the logs $commandLine = "Invoke-AtomicTest $AtomicTechnique" @@ -367,7 +367,7 @@ function Invoke-AtomicTest { Write-Verbose -Message 'Determining tests for target platform' $testCount++ - + if (-not $anyOS) { if ( -not $(Platform-IncludesCloud) -and -Not $test.supported_platforms.Contains($executionPlatform) ) { Write-Verbose -Message "Unable to run non-$executionPlatform tests" @@ -376,14 +376,14 @@ function Invoke-AtomicTest { if ( $executionPlatform -eq "windows" -and ($test.executor.name -eq "sh" -or $test.executor.name -eq "bash")) { Write-Verbose -Message "Unable to run sh or bash on $executionPlatform" - continue + continue } if ( ("linux", "macos") -contains $executionPlatform -and $test.executor.name -eq "command_prompt") { Write-Verbose -Message "Unable to run cmd.exe on $executionPlatform" - continue + continue } } - + if ($null -ne $TestNumbers) { if (-Not ($TestNumbers -contains $testCount) ) { continue } @@ -510,7 +510,7 @@ function Invoke-AtomicTest { } $AllAtomicTests.GetEnumerator() | Foreach-Object { Invoke-AtomicTestSingle $_ } } - + 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 diff --git a/Public/Invoke-FetchFromZip.ps1 b/Public/Invoke-FetchFromZip.ps1 index 396d679..2f4180f 100644 --- a/Public/Invoke-FetchFromZip.ps1 +++ b/Public/Invoke-FetchFromZip.ps1 @@ -1,4 +1,4 @@ -function Invoke-FetchFromZip { +function Invoke-FetchFromZip { Param( [Parameter(Mandatory = $true, Position = 0)] [String] @@ -29,11 +29,11 @@ function Invoke-FetchFromZip { } # find all files in ZIP that match the filter (i.e. file extension) - $zip.Entries | - Where-Object { - ($_.FullName -like $targetFilter) + $zip.Entries | + Where-Object { + ($_.FullName -like $targetFilter) } | - ForEach-Object { + ForEach-Object { # extract the selected items from the ZIP archive # and copy them to the out folder $dstDir = Join-Path $destinationPath ($_.FullName | split-path | split-path -Leaf) diff --git a/Public/Invoke-KickoffAtomicRunner.ps1 b/Public/Invoke-KickoffAtomicRunner.ps1 index d495ce1..12902b0 100644 --- a/Public/Invoke-KickoffAtomicRunner.ps1 +++ b/Public/Invoke-KickoffAtomicRunner.ps1 @@ -1,4 +1,4 @@ -function Invoke-KickoffAtomicRunner { +function Invoke-KickoffAtomicRunner { #log rotation function function Rotate-Log { @@ -6,16 +6,16 @@ function Invoke-KickoffAtomicRunner { $datetime = Get-Date -uformat "%Y-%m-%d-%H%M" $log = Get-Item $logPath - if ($log.Length / 1MB -ge $max_filesize) { + if ($log.Length / 1MB -ge $max_filesize) { Write-Host "file named $($log.name) is bigger than $max_filesize MB" $newname = "$($log.Name)_${datetime}.arclog" Rename-Item $log.PSPath $newname - Write-Host "Done rotating file" + Write-Host "Done rotating file" } $logdir_content = Get-ChildItem $artConfig.atomicLogsPath -filter "*.arclog" $cutoff_date = (get-date).AddDays($max_age) - $logdir_content | ForEach-Object { + $logdir_content | ForEach-Object { if ($_.LastWriteTime -gt $cutoff_date) { Remove-Item $_ Write-Host "Removed $($_.PSPath)" @@ -25,8 +25,8 @@ function Invoke-KickoffAtomicRunner { #Create log files as needed $all_log_file = Join-Path $artConfig.atomicLogsPath "all-out-$($artConfig.basehostname).txt" - New-Item $all_log_file -ItemType file -ErrorAction Ignore - New-Item $artConfig.logFile -ItemType File -ErrorAction Ignore + New-Item $all_log_file -ItemType file -ErrorAction Ignore + New-Item $artConfig.logFile -ItemType File -ErrorAction Ignore #Rotate logs based on FileSize and Date max_filesize $max_filesize = 200 #in MB diff --git a/Public/Invoke-RunnerScheduleMethods.ps1 b/Public/Invoke-RunnerScheduleMethods.ps1 index 6b19b06..1498c73 100644 --- a/Public/Invoke-RunnerScheduleMethods.ps1 +++ b/Public/Invoke-RunnerScheduleMethods.ps1 @@ -1,12 +1,12 @@ -# Loop through all atomic yaml files to load into list of objects +# Loop through all atomic yaml files to load into list of objects function Loop($fileList, $atomicType) { $AllAtomicTests = New-Object System.Collections.ArrayList $fileList | ForEach-Object { $currentTechnique = [System.IO.Path]::GetFileNameWithoutExtension($_.FullName) - if ( $currentTechnique -ne "index" ) { + if ( $currentTechnique -ne "index" ) { $technique = Get-AtomicTechnique -Path $_.FullName - if ($technique) { + if ($technique) { $technique.atomic_tests | ForEach-Object -Process { $test = New-Object -TypeName psobject $test | Add-Member -MemberType NoteProperty -Name Order -Value $null @@ -29,17 +29,17 @@ function Loop($fileList, $atomicType) { return $AllAtomicTests } - + function Get-NewSchedule() { if (Test-Path $artConfig.PathToPublicAtomicsFolder) { - $publicAtomicFiles = Get-ChildItem $artConfig.PathToPublicAtomicsFolder -Recurse -Exclude Indexes -Filter T*.yaml -File + $publicAtomicFiles = Get-ChildItem $artConfig.PathToPublicAtomicsFolder -Recurse -Exclude Indexes -Filter T*.yaml -File $publicAtomics = Loop $publicAtomicFiles "Public" } else { Write-Host -ForegroundColor Yellow "Public Atomics Folder not Found $($artConfig.PathToPublicAtomicsFolder)" } if (Test-Path $artConfig.PathToPrivateAtomicsFolder) { - $privateAtomicFiles = Get-ChildItem $artConfig.PathToPrivateAtomicsFolder -Recurse -Exclude Indexes -Filter T*.yaml -File + $privateAtomicFiles = Get-ChildItem $artConfig.PathToPrivateAtomicsFolder -Recurse -Exclude Indexes -Filter T*.yaml -File $privateAtomics = Loop $privateAtomicFiles "Private" } else { @@ -54,7 +54,7 @@ function Get-NewSchedule() { function Get-ScheduleRefresh() { $AllAtomicTests = Get-NewSchedule $schedule = Get-Schedule $null $false # get schedule, including inactive (ie not filtered) - + # Creating new schedule object for updating changes in atomics $newSchedule = New-Object System.Collections.ArrayList @@ -68,25 +68,25 @@ function Get-ScheduleRefresh() { $update = $true $newSchedule += $fresh } - + # Updating schedule with changes else { if ($fresh -is [array]) { $fresh = $fresh[0] - LogRunnerMsg "Duplicated auto_generated_guid found $($fresh.auto_generated_guid) with technique $($fresh.Technique). + LogRunnerMsg "Duplicated auto_generated_guid found $($fresh.auto_generated_guid) with technique $($fresh.Technique). `nCannot Continue Execution. System Exit" - Write-Host -ForegroundColor Yellow "Duplicated auto_generated_guid found $($fresh.auto_generated_guid) with technique $($fresh.Technique). + Write-Host -ForegroundColor Yellow "Duplicated auto_generated_guid found $($fresh.auto_generated_guid) with technique $($fresh.Technique). `nCannot Continue Execution. System Exit"; Start-Sleep 10 exit } $old.Technique = $fresh.Technique $old.TestName = $fresh.TestName $old.supported_platforms = $fresh.supported_platforms - + $update = $true $newSchedule += $old } - + } if ($update) { $newSchedule | Export-Csv $artConfig.scheduleFile @@ -104,11 +104,11 @@ function Get-Schedule($listOfAtomics, $filtered = $true, $testGuids = $null) { else { $schedule = Import-Csv $artConfig.scheduleFile } - + # Filter schedule to either Active/Supported Platform or TestGuids List if ($TestGuids) { $schedule = $schedule | Where-Object { - ($Null -ne $TestGuids -and $TestGuids -contains $_.auto_generated_guid) + ($Null -ne $TestGuids -and $TestGuids -contains $_.auto_generated_guid) } } elseif ($filtered) { @@ -121,7 +121,7 @@ function Get-Schedule($listOfAtomics, $filtered = $true, $testGuids = $null) { else { Write-Host -ForegroundColor Yellow "Couldn't find schedule file ($($artConfig.scheduleFile)) Update the path to the schedule file in the config or generate a new one with 'Invoke-GenerateNewSchedule'" } - + if (($null -eq $schedule) -or ($schedule.length -eq 0)) { Write-Host -ForegroundColor Yellow "No active tests were found. Edit the 'enabled' column of your schedule file and set some to enabled (True)"; return $null } return $schedule } diff --git a/Public/Invoke-SetupAtomicRunner.ps1 b/Public/Invoke-SetupAtomicRunner.ps1 index 0b1f235..18df2c4 100755 --- a/Public/Invoke-SetupAtomicRunner.ps1 +++ b/Public/Invoke-SetupAtomicRunner.ps1 @@ -1,4 +1,4 @@ -function Invoke-SetupAtomicRunner { +function Invoke-SetupAtomicRunner { # ensure running with admin privs if ($artConfig.OS -eq "windows") { # auto-elevate on Windows @@ -49,7 +49,7 @@ function Invoke-SetupAtomicRunner { # create credential file for the user since we aren't using a group managed service account $cred = Get-Credential -UserName $artConfig.user -message "Enter password for $($artConfig.user) in order to create the runner scheduled task" $cred.Password | ConvertFrom-SecureString | Out-File $artConfig.credFile - + } # setup scheduled task that will start the runner after each restart @@ -89,7 +89,7 @@ function Invoke-SetupAtomicRunner { $exists = cat /etc/crontab | Select-String -Quiet "KickoffAtomicRunner" #checks if the Kickoff-AtomicRunner job exists. If not appends it to the system crontab. if ($null -eq $exists) { - $(echo "$job" >> /etc/crontab) + $(Write-Output "$job" >> /etc/crontab) write-host "setting cronjob" } else { @@ -110,7 +110,7 @@ function Invoke-SetupAtomicRunner { else { Add-Content $profile $importStatement } - + # Install the Posh-SYLOG module if we are configured to use it and it is not already installed if ((-not (Get-Module -ListAvailable "Posh-SYSLOG")) -and [bool]$artConfig.syslogServer -and [bool]$artConfig.syslogPort) { write-verbose "Posh-SYSLOG" diff --git a/Public/Invoke-WebRequestVerifyHash.ps1 b/Public/Invoke-WebRequestVerifyHash.ps1 index 6b303fb..619b5c7 100644 --- a/Public/Invoke-WebRequestVerifyHash.ps1 +++ b/Public/Invoke-WebRequestVerifyHash.ps1 @@ -1,12 +1,12 @@ -function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { +function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { $success = $false - $null = @( + $null = @( New-Item -ItemType Directory (Split-Path $outfile) -Force | Out-Null $ms = New-Object IO.MemoryStream [Net.ServicePointManager]::SecurityProtocol = ([Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12) (New-Object System.Net.WebClient).OpenRead($url).copyto($ms) $ms.seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null - $actualHash = (Get-FileHash -InputStream $ms).Hash + $actualHash = (Get-FileHash -InputStream $ms).Hash if ( $hash -eq $actualHash) { $ms.seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null $fileStream = New-Object IO.FileStream $outfile, ([System.IO.FileMode]::Create) @@ -15,7 +15,7 @@ function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { $success = $true } else { - Write-Host -ForegroundColor red "File hash mismatch, expected: $hash, actual: $actualHash" + Write-Host -ForegroundColor red "File hash mismatch, expected: $hash, actual: $actualHash" } ) $success diff --git a/Public/New-Atomic.ps1 b/Public/New-Atomic.ps1 index 1a66d5e..645b909 100644 --- a/Public/New-Atomic.ps1 +++ b/Public/New-Atomic.ps1 @@ -1,4 +1,4 @@ -# The class definitions that these functions rely upon are located in Private\AtomicClassSchema.ps1 +# The class definitions that these functions rely upon are located in Private\AtomicClassSchema.ps1 function New-AtomicTechnique { <# @@ -263,7 +263,7 @@ The output of New-AtomicTest can be piped to ConvertTo-Yaml. The resulting outpu } } - if ($DependencyExecutorType) { + if ($DependencyExecutorType) { switch ($DependencyExecutorType) { 'CommandPrompt' { $AtomicTestInstance.dependency_executor_name = 'command_prompt' } default { $AtomicTestInstance.dependency_executor_name = $DependencyExecutorType.ToLower() } diff --git a/Public/Start-AtomicGUI.ps1 b/Public/Start-AtomicGUI.ps1 index e7db1fd..ca1b506 100644 --- a/Public/Start-AtomicGUI.ps1 +++ b/Public/Start-AtomicGUI.ps1 @@ -1,11 +1,11 @@ -function Start-AtomicGUI { +function Start-AtomicGUI { param ( [Int] $port = 8487 ) # Install-Module UniversalDashboard if not already installed $UDcommunityInstalled = Get-InstalledModule -Name "UniversalDashboard.Community" -ErrorAction:SilentlyContinue $UDinstalled = Get-InstalledModule -Name "UniversalDashboard" -ErrorAction:SilentlyContinue - if (-not $UDcommunityInstalled -and -not $UDinstalled) { + if (-not $UDcommunityInstalled -and -not $UDinstalled) { Write-Host "Installing UniversalDashboard.Community" Install-Module -Name UniversalDashboard.Community -Scope CurrentUser -Force } @@ -30,8 +30,8 @@ function Start-AtomicGUI { $cardNumber = $InputArgCards.count + 1 $newCard = New-UDCard -ID "InputArgCard$cardNumber" -Content { New-UDTextBoxX "InputArgCard$cardNumber-InputArgName" "Input Argument Name" - New-UDTextAreaX "InputArgCard$cardNumber-InputArgDescription" "Description" - New-UDTextBoxX "InputArgCard$cardNumber-InputArgDefault" "Default Value" + New-UDTextAreaX "InputArgCard$cardNumber-InputArgDescription" "Description" + New-UDTextBoxX "InputArgCard$cardNumber-InputArgDefault" "Default Value" New-UDLayout -columns 4 { New-UDSelect -ID "InputArgCard$cardNumber-InputArgType" -Label "Type" -Option { New-UDSelectOption -Name "Path" -Value "path" @@ -56,8 +56,8 @@ function Start-AtomicGUI { $cardNumber = $depCards.count + 1 $newCard = New-UDCard -ID "depCard$cardNumber" -Content { New-UDTextBoxX "depCard$cardNumber-depDescription" "Prereq Description" - New-UDTextAreaX "depCard$cardNumber-prereqCommand" "Check prereqs Command" - New-UDTextAreaX "depCard$cardNumber-getPrereqCommand" "Get Prereqs Command" + New-UDTextAreaX "depCard$cardNumber-prereqCommand" "Check prereqs Command" + New-UDTextAreaX "depCard$cardNumber-getPrereqCommand" "Get Prereqs Command" New-UDButton -Text "Remove this Prereq" -OnClick ( New-UDEndpoint -Endpoint { Remove-UDElement -Id "depCard$cardNumber" @@ -72,7 +72,7 @@ function Start-AtomicGUI { function New-UDSelectX ($Id, $Label) { New-UDSelect -Label $Label -Id $Id -Option { New-UDSelectOption -Name "PowerShell" -Value "PowerShell" -Selected - New-UDSelectOption -Name "Command Prompt" -Value "CommandPrompt" + New-UDSelectOption -Name "Command Prompt" -Value "CommandPrompt" New-UDSelectOption -Name "Bash" -Value "Bash" New-UDSelectOption -Name "Sh" -Value "Sh" } @@ -87,7 +87,7 @@ function Start-AtomicGUI { -Module @("..\Invoke-AtomicRedTeam.psd1") ############## EndPoint (ep) Definitions: Dynamic code called to generate content for an element or perfrom onClick actions - $BuildAndDisplayYamlScriptBlock = { + $BuildAndDisplayYamlScriptBlock = { $testName = (Get-UDElement -Id atomicName).Attributes['value'] $testDesc = (Get-UDElement -Id atomicDescription).Attributes['value'] $platforms = @() @@ -138,11 +138,11 @@ function Start-AtomicGUI { if (($cleanupCommands -ne "") -and ($null -ne $cleanupCommands)) { $depParams.add("ExecutorCleanupCommand", $cleanupCommands) } $depParams.add("ExecutorElevationRequired", $elevationRequired) - $AtomicTest = New-AtomicTest -Name $testName -Description $testDesc -SupportedPlatforms $platforms -InputArguments $inputArgs -ExecutorType $executor -ExecutorCommand $attackCommands -WarningVariable +warnings @depParams + $AtomicTest = New-AtomicTest -Name $testName -Description $testDesc -SupportedPlatforms $platforms -InputArguments $inputArgs -ExecutorType $executor -ExecutorCommand $attackCommands -WarningVariable +warnings @depParams $yaml = ($AtomicTest | ConvertTo-Yaml) -replace "^", "- " -replace "`n", "`n " foreach ($warning in $warnings) { Show-UDToast $warning -BackgroundColor LightYellow -Duration 10000 } New-UDElement -ID yaml -Tag pre -Content { $yaml } - } + } $epYamlModal = New-UDEndpoint -Endpoint { Show-UDModal -Header { New-UDHeading -Size 3 -Text "Test Definition YAML" } -Content { @@ -172,14 +172,14 @@ function Start-AtomicGUI { New-UDEndpoint -Endpoint { $yaml = (Get-UDElement -Id "yaml").Content[0] Set-UDClipboard -Data $yaml - Show-UDToast -Message "Copied YAML to the Clipboard" -BackgroundColor YellowGreen + Show-UDToast -Message "Copied YAML to the Clipboard" -BackgroundColor YellowGreen } ) } } $epFillTestData = New-UDEndpoint -Endpoint { - Add-UDElement -ParentId "inputCard" -Content { New-InputArgCard } + Add-UDElement -ParentId "inputCard" -Content { New-InputArgCard } Add-UDElement -ParentId "depCard" -Content { New-depCard } Start-Sleep 1 Set-UDElement -Id atomicName -Attributes @{value = "My new atomic" } @@ -189,19 +189,19 @@ function Start-AtomicGUI { # InputArgs $cardNumber = 1 Set-UDElement -Id "InputArgCard$cardNumber-InputArgName" -Attributes @{value = "input_arg_1" } - Set-UDElement -Id "InputArgCard$cardNumber-InputArgDescription" -Attributes @{value = "InputArg1 description" } - Set-UDElement -Id "InputArgCard$cardNumber-InputArgDefault" -Attributes @{value = "this is the default value" } + Set-UDElement -Id "InputArgCard$cardNumber-InputArgDescription" -Attributes @{value = "InputArg1 description" } + Set-UDElement -Id "InputArgCard$cardNumber-InputArgDefault" -Attributes @{value = "this is the default value" } # dependencies Set-UDElement -Id "depCard$cardNumber-depDescription" -Attributes @{value = "This file must exist" } - Set-UDElement -Id "depCard$cardNumber-prereqCommand" -Attributes @{value = "if (this) then that" } - Set-UDElement -Id "depCard$cardNumber-getPrereqCommand" -Attributes @{value = "iwr" } - + Set-UDElement -Id "depCard$cardNumber-prereqCommand" -Attributes @{value = "if (this) then that" } + Set-UDElement -Id "depCard$cardNumber-getPrereqCommand" -Attributes @{value = "iwr" } + } ############## End EndPoint (ep) Definitions ############## Static Definitions $supportedPlatforms = New-UDLayout -Columns 4 { - New-UDElement -Tag Label -Attributes @{ style = @{"font-size" = "15px" } } -Content { "Supported Platforms:" } + New-UDElement -Tag Label -Attributes @{ style = @{"font-size" = "15px" } } -Content { "Supported Platforms:" } New-UDCheckbox -FilledIn -Label "Windows" -Checked -Id spWindows New-UDCheckbox -FilledIn -Label "Linux" -Id spLinux New-UDCheckbox -FilledIn -Label "macOS"-Id spMacOS @@ -209,7 +209,7 @@ function Start-AtomicGUI { $executorRow = New-UDLayout -Columns 4 { New-UDSelectX 'executorSelector' "Executor for Attack Commands" - New-UDCheckbox -ID elevationRequired -FilledIn -Label "Requires Elevation to Execute Successfully?" + New-UDCheckbox -ID elevationRequired -FilledIn -Label "Requires Elevation to Execute Successfully?" } $genarateYamlButton = New-UDRow -Columns { @@ -232,7 +232,7 @@ function Start-AtomicGUI { New-UDTextAreaX "attackCommands" "Attack Commands" $executorRow New-UDTextAreaX "cleanupCommands" "Cleanup Commands (Optional)" - $genarateYamlButton + $genarateYamlButton } # input args @@ -248,9 +248,9 @@ function Start-AtomicGUI { New-UDButton -Text "Add Prerequisite (Optional)" -OnClick ( New-UDEndpoint -Endpoint { Add-UDElement -ParentId "depCard" -Content { New-depCard } } ) - New-UDSelectX 'preReqEx' "Executor for Prereq Commands" + New-UDSelectX 'preReqEx' "Executor for Prereq Commands" } - } + } } # button to fill form with test data for development purposes diff --git a/Public/Syslog-ExecutionLogger.psm1 b/Public/Syslog-ExecutionLogger.psm1 index d87c925..2817496 100644 --- a/Public/Syslog-ExecutionLogger.psm1 +++ b/Public/Syslog-ExecutionLogger.psm1 @@ -1,4 +1,4 @@ -function Start-ExecutionLog($startTime, $logPath, $targetHostname, $targetUser, $commandLine, $isWindows) { +function Start-ExecutionLog($startTime, $logPath, $targetHostname, $targetUser, $commandLine, $isWindows) { } @@ -7,9 +7,9 @@ function Write-ExecutionLog($startTime, $stopTime, $technique, $testNum, $testNa $timeLocal = (Get-Date($startTime) -uformat "%Y-%m-%dT%H:%M:%SZ").ToString() $ipAddress = Get-PreferredIPAddress $isWindows - $msg = [PSCustomObject][ordered]@{ + $msg = [PSCustomObject][ordered]@{ "Execution Time (UTC)" = $timeUTC - "Execution Time (Local)" = $timeLocal + "Execution Time (Local)" = $timeLocal "Technique" = $technique "Test Number" = $testNum "Test Name" = $testName @@ -21,8 +21,8 @@ function Write-ExecutionLog($startTime, $stopTime, $technique, $testNum, $testNa "CustomTag" = $artConfig.CustomTag "ProcessId" = $res.ProcessId "ExitCode" = $res.ExitCode - } - + } + # send syslog message if a syslog server is defined in Public/config.ps1 if ([bool]$artConfig.syslogServer -and [bool]$artConfig.syslogPort) { $jsonMsg = $msg | ConvertTo-Json -Compress diff --git a/Public/WinEvent-ExecutionLogger.psm1 b/Public/WinEvent-ExecutionLogger.psm1 index c08f10d..cbf8fe6 100644 --- a/Public/WinEvent-ExecutionLogger.psm1 +++ b/Public/WinEvent-ExecutionLogger.psm1 @@ -1,4 +1,4 @@ -function Start-ExecutionLog($startTime, $logPath, $targetHostname, $targetUser, $commandLine, $isWindows) { +function Start-ExecutionLog($startTime, $logPath, $targetHostname, $targetUser, $commandLine, $isWindows) { if($isWindows -and -not [System.Diagnostics.EventLog]::Exists('Atomic Red Team')){ New-EventLog -Source "Applications and Services Logs" -LogName "Atomic Red Team" } @@ -9,9 +9,9 @@ function Write-ExecutionLog($startTime, $stopTime, $technique, $testNum, $testNa $timeLocal = (Get-Date($startTime) -uformat "%Y-%m-%dT%H:%M:%SZ").ToString() $ipAddress = Get-PreferredIPAddress $isWindows - $msg = [PSCustomObject][ordered]@{ + $msg = [PSCustomObject][ordered]@{ "Execution Time (UTC)" = $timeUTC - "Execution Time (Local)" = $timeLocal + "Execution Time (Local)" = $timeLocal "Technique" = $technique "Test Number" = $testNum "Test Name" = $testName @@ -24,7 +24,7 @@ function Write-ExecutionLog($startTime, $stopTime, $technique, $testNum, $testNa "ProcessId" = $res.ProcessId "ExitCode" = $res.ExitCode } - + Write-EventLog -Source "Applications and Services Logs" -LogName "Atomic Red Team" -EventID 3001 -EntryType Information -Message $msg -Category 1 -RawData 10,20 } diff --git a/Public/config.ps1 b/Public/config.ps1 index 14174bd..9a2ea27 100644 --- a/Public/config.ps1 +++ b/Public/config.ps1 @@ -1,4 +1,4 @@ - + $artConfig = [PSCustomObject]@{ # [optional] These two configs are calculated programatically, you probably don't need to change them @@ -31,7 +31,7 @@ $artConfig = [PSCustomObject]@{ syslogServer = '' # set to empty string '' if you don't want to log atomic execution details to a syslog server (don't includle http(s):\\) syslogPort = 514 syslogProtocol = 'UDP' # options are UDP, TCP, TCPwithTLS - + verbose = $true; # set to true for more log output # [optional] logfile filename configs diff --git a/sandbox/setupsandbox.ps1 b/sandbox/setupsandbox.ps1 index 6b818a5..758ba0e 100644 --- a/sandbox/setupsandbox.ps1 +++ b/sandbox/setupsandbox.ps1 @@ -1,8 +1,8 @@ -Set-ExecutionPolicy Bypass -Scope Process -Force; +Set-ExecutionPolicy Bypass -Scope Process -Force; Write-Host "Installing NuGet" Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force Write-Host "Installing Atomic Red Team" -IEX (IWR 'https://raw.githubusercontent.com/redcanaryco/invoke-atomicredteam/master/install-atomicredteam.ps1'-UseBasicParsing); +Invoke-Expression (Invoke-WebRequest 'https://raw.githubusercontent.com/redcanaryco/invoke-atomicredteam/master/install-atomicredteam.ps1'-UseBasicParsing); Install-AtomicRedTeam -getAtomics -Force; New-Item $PROFILE -Force; Set-Variable -Name "ARTPath" -Value "C:\AtomicRedTeam" @@ -15,4 +15,4 @@ Import-Module "$ARTPath/invoke-atomicredteam/Invoke-AtomicRedTeam.psd1" -Force; . $PROFILE -cd C:\AtomicRedTeam \ No newline at end of file +Set-Location C:\AtomicRedTeam \ No newline at end of file From 96feaa3bf9d69725323f2c90a82b5346a1dbe304 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 00:51:08 -0500 Subject: [PATCH 3/8] adding linting files --- .github/workflows/lint.yml | 17 +++++++++++++++++ PSScriptAnalyzerSettings.psd1 | 5 +++++ 2 files changed, 22 insertions(+) create mode 100644 .github/workflows/lint.yml create mode 100644 PSScriptAnalyzerSettings.psd1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..aa4a9a3 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,17 @@ +name: Check installation +on: pull_request + +jobs: + install-invoke: + name: Install Invoke-Atomic + runs-on: "ubuntu-latest" + steps: + - name: Install Invoke-AtomicRedTeam + shell: pwsh + run: | + Install-Module -Name PSScriptAnalyzer -Force + Invoke-ScriptAnalyzer -Recurse ./ -Settings ./PSScriptAnalyzerSettings.psd1 -Fix + - name: Check whether there are any file changes + shell: bash + run: | + git diff --exit-code \ No newline at end of file diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 new file mode 100644 index 0000000..0280d7e --- /dev/null +++ b/PSScriptAnalyzerSettings.psd1 @@ -0,0 +1,5 @@ +# PSScriptAnalyzerSettings.psd1 +@{ + ExcludeRules=@('PSUseSingularNouns', + 'PSAvoidUsingWriteHost') +} \ No newline at end of file From 341a1001a1fbedd00b013474e618a3edb7710efb Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 00:56:43 -0500 Subject: [PATCH 4/8] Update lint.yml --- .github/workflows/lint.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f9e5231..04e6682 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,14 +1,17 @@ -name: Check installation +name: Lint on: pull_request jobs: install-invoke: name: Install Invoke-Atomic - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest steps: + - name: checkout + uses: actions/checkout@v2 - name: Install Invoke-AtomicRedTeam shell: pwsh run: | + ls Install-Module -Name PSScriptAnalyzer -Force Invoke-ScriptAnalyzer -Recurse ./ -Settings ./PSScriptAnalyzerSettings.psd1 -Fix - name: Check whether there are any file changes From a1c82c3b7b37d85dc9284562c8b0340bc9837965 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 00:59:43 -0500 Subject: [PATCH 5/8] Update lint.yml --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f25e693..0efe6a9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: steps: - name: checkout uses: actions/checkout@v2 - - name: Install Invoke-AtomicRedTeam + - name: Run lint checks shell: pwsh run: | ls @@ -17,4 +17,4 @@ jobs: - name: Check whether there are any file changes shell: bash run: | - git diff --exit-code \ No newline at end of file + git diff --exit-code From 6f50db9d9f93850b3252479212322784ca4af9e4 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 01:02:08 -0500 Subject: [PATCH 6/8] fix lint.yml --- .github/workflows/lint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0efe6a9..b7b8a13 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,7 +11,6 @@ jobs: - name: Run lint checks shell: pwsh run: | - ls Install-Module -Name PSScriptAnalyzer -Force Invoke-ScriptAnalyzer -Recurse ./ -Settings ./PSScriptAnalyzerSettings.psd1 -Fix - name: Check whether there are any file changes From b84a4f64223ebe38577453189030fd52b4e5e0d9 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Sun, 24 Dec 2023 01:05:06 -0500 Subject: [PATCH 7/8] fix file --- Private/Get-TargetInfo.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Private/Get-TargetInfo.ps1 b/Private/Get-TargetInfo.ps1 index d75150c..64f03a1 100644 --- a/Private/Get-TargetInfo.ps1 +++ b/Private/Get-TargetInfo.ps1 @@ -39,3 +39,4 @@ } $targetPlatform, $isElevated, $tmpDir, $targetHostname, $targetUser +} \ No newline at end of file From 47be37d289c1765740cf5073bdda22826443fa80 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Mon, 25 Dec 2023 22:43:24 -0500 Subject: [PATCH 8/8] more formatting --- Invoke-AtomicRedTeam.psd1 | 14 ++--- Private/AtomicClassSchema.ps1 | 4 +- Private/Get-TargetInfo.ps1 | 5 +- Private/Invoke-CheckPrereqs.ps1 | 2 +- Private/Invoke-ExecuteCommand.ps1 | 39 ++++++------- Private/Invoke-Process.ps1 | 50 ++++++++--------- Private/Replace-InputArgs.ps1 | 3 +- Private/Show-Details.ps1 | 4 +- Private/Write-KeyValue.ps1 | 4 +- Public/Attire-ExecutionLogger.psm1 | 80 ++++++++++++++------------- Public/Get-AtomicTechnique.ps1 | 24 ++++---- Public/Get-PreferredIPAddress.ps1 | 8 +-- Public/Invoke-AtomicTest.ps1 | 6 +- Public/Invoke-KickoffAtomicRunner.ps1 | 2 +- Public/Invoke-SetupAtomicRunner.ps1 | 9 ++- Public/WinEvent-ExecutionLogger.psm1 | 4 +- Public/config.ps1 | 2 +- 17 files changed, 134 insertions(+), 126 deletions(-) diff --git a/Invoke-AtomicRedTeam.psd1 b/Invoke-AtomicRedTeam.psd1 index 6e77cf7..9af0f28 100644 --- a/Invoke-AtomicRedTeam.psd1 +++ b/Invoke-AtomicRedTeam.psd1 @@ -25,11 +25,11 @@ PowerShellVersion = '5.0' # Modules that must be imported into the global environment prior to importing this module - RequiredModules = @('powershell-yaml') + RequiredModules = @('powershell-yaml') # Script files (.ps1) that are run in the caller's environment prior to importing this module. # AtomicClassSchema.ps1 needs to be present in the caller's scope in order for the built-in classes to surface properly. - ScriptsToProcess = @('Private\AtomicClassSchema.ps1','Public\config.ps1') + ScriptsToProcess = @('Private\AtomicClassSchema.ps1', 'Public\config.ps1') # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( @@ -53,12 +53,12 @@ # Variables to export from this module VariablesToExport = '*' - NestedModules = @( + NestedModules = @( "Public\Default-ExecutionLogger.psm1", "Public\Attire-ExecutionLogger.psm1", "Public\Syslog-ExecutionLogger.psm1", "Public\WinEvent-ExecutionLogger.psm1" - ) + ) # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. PrivateData = @{ @@ -66,13 +66,13 @@ PSData = @{ # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('Security', 'Defense') + Tags = @('Security', 'Defense') # A URL to the license for this module. - LicenseUri = 'https://github.com/redcanaryco/invoke-atomicredteam/blob/master/LICENSE.txt' + LicenseUri = 'https://github.com/redcanaryco/invoke-atomicredteam/blob/master/LICENSE.txt' # A URL to the main website for this project. - ProjectUri = 'https://github.com/redcanaryco/invoke-atomicredteam' + ProjectUri = 'https://github.com/redcanaryco/invoke-atomicredteam' # A URL to an icon representing this module. # IconUri = '' diff --git a/Private/AtomicClassSchema.ps1 b/Private/AtomicClassSchema.ps1 index 47090c8..543ef56 100644 --- a/Private/AtomicClassSchema.ps1 +++ b/Private/AtomicClassSchema.ps1 @@ -15,7 +15,7 @@ class AtomicExecutorBase { [Bool] $elevation_required # Implemented to facilitate improved PS object display - [String] ToString(){ + [String] ToString() { return $this.Name } } @@ -43,7 +43,7 @@ class AtomicTest { [AtomicExecutorBase] $executor # Implemented to facilitate improved PS object display - [String] ToString(){ + [String] ToString() { return $this.name } } diff --git a/Private/Get-TargetInfo.ps1 b/Private/Get-TargetInfo.ps1 index 64f03a1..e07d545 100644 --- a/Private/Get-TargetInfo.ps1 +++ b/Private/Get-TargetInfo.ps1 @@ -10,8 +10,9 @@ $targetHostname = hostname $targetUser = whoami if ($IsLinux) { $targetPlatform = "linux" } - elseif ($IsMacOS) { $targetPlatform = "macos" } - else { # windows + elseif ($IsMacOS) { $targetPlatform = "macos" } + else { + # windows $tmpDir = "$env:TEMP\" $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } diff --git a/Private/Invoke-CheckPrereqs.ps1 b/Private/Invoke-CheckPrereqs.ps1 index 2a6bc69..0219310 100644 --- a/Private/Invoke-CheckPrereqs.ps1 +++ b/Private/Invoke-CheckPrereqs.ps1 @@ -6,7 +6,7 @@ function Invoke-CheckPrereqs ($test, $isElevated, $executionPlatform, $customInp foreach ($dep in $test.dependencies) { $executor = Get-PrereqExecutor $test $final_command = Merge-InputArgs $dep.prereq_command $test $customInputArgs $PathToAtomicsFolder - if($executor -ne "powershell") { $final_command = ($final_Command.trim()).Replace("`n", " && ") } + if ($executor -ne "powershell") { $final_command = ($final_Command.trim()).Replace("`n", " && ") } $res = Invoke-ExecuteCommand $final_command $executor $executionPlatform $TimeoutSeconds $session $description = Merge-InputArgs $dep.description $test $customInputArgs $PathToAtomicsFolder if ($res.ExitCode -ne 0) { diff --git a/Private/Invoke-ExecuteCommand.ps1 b/Private/Invoke-ExecuteCommand.ps1 index 2952980..0d93f36 100644 --- a/Private/Invoke-ExecuteCommand.ps1 +++ b/Private/Invoke-ExecuteCommand.ps1 @@ -7,16 +7,16 @@ $execPrefix = "-c" $execExe = $executor if ($executor -eq "command_prompt") { - $execPrefix = "/c"; - $execExe = "cmd.exe"; - $execCommand = $finalCommand -replace "`n", " & " - $arguments = $execPrefix,"$execCommand" - } + $execPrefix = "/c"; + $execExe = "cmd.exe"; + $execCommand = $finalCommand -replace "`n", " & " + $arguments = $execPrefix, "$execCommand" + } else { - $finalCommand = $finalCommand -replace "[\\](?!;)", "`\$&" - $finalCommand = $finalCommand -replace "[`"]", "`\$&" - $execCommand = $finalCommand -replace "(?" | Out-File (Join-Path $WorkingDirectory $stdoutFile) -Append -Encoding ASCII break # if we're here it means the file wasn't locked and Out-File worked, so we can leave the retry loop - } catch {} # file is locked + } + catch {} # file is locked Start-Sleep -m 100 } } @@ -108,10 +107,10 @@ function Invoke-Process { # Get Process result return [PSCustomObject]@{ StandardOutput = "" - ErrorOutput = "" - ExitCode = $process.ExitCode - ProcessId = $Process.Id - IsTimeOut = $IsTimeout + ErrorOutput = "" + ExitCode = $process.ExitCode + ProcessId = $Process.Id + IsTimeOut = $IsTimeout } } @@ -119,15 +118,13 @@ function Invoke-Process { } finally { if ($null -ne $process) { $process.Dispose() } - if ($null -ne $stdEvent){ $stdEvent.StopJob(); $stdEvent.Dispose() } - if ($null -ne $errorEvent){ $errorEvent.StopJob(); $errorEvent.Dispose() } + if ($null -ne $stdEvent) { $stdEvent.StopJob(); $stdEvent.Dispose() } + if ($null -ne $errorEvent) { $errorEvent.StopJob(); $errorEvent.Dispose() } } } - begin - { - function NewProcess - { + begin { + function NewProcess { [OutputType([System.Diagnostics.Process])] [CmdletBinding()] param @@ -149,7 +146,7 @@ function Invoke-Process { $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.FileName = $FileName - $psi.Arguments+= $Arguments + $psi.Arguments += $Arguments $psi.WorkingDirectory = $WorkingDirectory # Set Process @@ -159,8 +156,7 @@ function Invoke-Process { return $process } - function GetCommandResult - { + function GetCommandResult { [OutputType([PSCustomObject])] [CmdletBinding()] param @@ -180,10 +176,10 @@ function Invoke-Process { return [PSCustomObject]@{ StandardOutput = $StandardStringBuilder.ToString().Trim() - ErrorOutput = $ErrorStringBuilder.ToString().Trim() - ExitCode = $Process.ExitCode - ProcessId = $Process.Id - IsTimeOut = $IsTimeout + ErrorOutput = $ErrorStringBuilder.ToString().Trim() + ExitCode = $Process.ExitCode + ProcessId = $Process.Id + IsTimeOut = $IsTimeout } } } diff --git a/Private/Replace-InputArgs.ps1 b/Private/Replace-InputArgs.ps1 index f5a60b3..d0a9c27 100644 --- a/Private/Replace-InputArgs.ps1 +++ b/Private/Replace-InputArgs.ps1 @@ -8,7 +8,8 @@ function Get-InputArgs([hashtable]$ip, $customInputArgs, $PathToAtomicsFolder) { if ($defaultArgs.Keys -contains $key) { # replace default with user supplied $defaultArgs.set_Item($key, $customInputArgs[$key]) - } else { + } + else { Write-Verbose "The specified input argument *$key* was ignored as not applicable" } } diff --git a/Private/Show-Details.ps1 b/Private/Show-Details.ps1 index b1dca15..de22bb7 100644 --- a/Private/Show-Details.ps1 +++ b/Private/Show-Details.ps1 @@ -1,4 +1,4 @@ -function Invoke-CleanupDescription(){ +function Invoke-CleanupDescription() { $ret1 = $test.description.ToString().trim() -replace '(?[^}]+)\}' [String[]] $InputArgumentNamesFromExecutor = $StringsWithPotentialInputArgs | - ForEach-Object { $Regex.Matches($_) } | - Select-Object -ExpandProperty Groups | - Where-Object { $_.Name -eq 'ArgName' } | - Select-Object -ExpandProperty Value | - Sort-Object -Unique + ForEach-Object { $Regex.Matches($_) } | + Select-Object -ExpandProperty Groups | + Where-Object { $_.Name -eq 'ArgName' } | + Select-Object -ExpandProperty Value | + Sort-Object -Unique # Validate that all executor input arg names are defined input arg names. @@ -504,7 +508,7 @@ function Get-TechniqueNumbers { $PathToAtomicsFolder = if ($IsLinux -or $IsMacOS) { $Env:HOME + "/AtomicRedTeam/atomics" } else { $env:HOMEDRIVE + "\AtomicRedTeam\atomics" } $techniqueNumbers = Get-ChildItem $PathToAtomicsFolder -Directory | - ForEach-Object { $_.BaseName } + ForEach-Object { $_.BaseName } return $techniqueNumbers } diff --git a/Public/Get-PreferredIPAddress.ps1 b/Public/Get-PreferredIPAddress.ps1 index aa7d2db..33c40bc 100644 --- a/Public/Get-PreferredIPAddress.ps1 +++ b/Public/Get-PreferredIPAddress.ps1 @@ -1,12 +1,12 @@ -function Get-PreferredIPAddress($isWindows){ - if ($isWindows){ - return (Get-NetIPAddress | Where-Object { $_.PrefixOrigin -ne "WellKnown"}).IPAddress +function Get-PreferredIPAddress($isWindows) { + if ($isWindows) { + return (Get-NetIPAddress | Where-Object { $_.PrefixOrigin -ne "WellKnown" }).IPAddress } elseif ($IsMacOS) { return ifconfig -l | xargs -n1 ipconfig getifaddr } elseif ($IsLinux) { - return ip -4 -br addr show |sed -n -e 's/^.*UP\s* //p'|cut -d "/" -f 1 + return ip -4 -br addr show | sed -n -e 's/^.*UP\s* //p' | cut -d "/" -f 1 } else { return '' diff --git a/Public/Invoke-AtomicTest.ps1 b/Public/Invoke-AtomicTest.ps1 index eaebb50..0df73dc 100644 --- a/Public/Invoke-AtomicTest.ps1 +++ b/Public/Invoke-AtomicTest.ps1 @@ -471,10 +471,10 @@ elseif ($Cleanup) { Write-KeyValue "Executing cleanup for test: " $testId $final_command = Merge-InputArgs $test.executor.cleanup_command $test $InputArgs $PathToPayloads - if (Get-Command 'Invoke-ARTPreAtomicCleanupHook' -errorAction SilentlyContinue) { Invoke-ARTPreAtomicCleanupHook $test $InputArgs} + if (Get-Command 'Invoke-ARTPreAtomicCleanupHook' -errorAction SilentlyContinue) { Invoke-ARTPreAtomicCleanupHook $test $InputArgs } $res = Invoke-ExecuteCommand $final_command $test.executor.name $executionPlatform $TimeoutSeconds $session -Interactive:$Interactive Write-KeyValue "Done executing cleanup for test: " $testId - if (Get-Command 'Invoke-ARTPostAtomicCleanupHook' -errorAction SilentlyContinue) { Invoke-ARTPostAtomicCleanupHook $test $InputArgs} + if (Get-Command 'Invoke-ARTPostAtomicCleanupHook' -errorAction SilentlyContinue) { Invoke-ARTPostAtomicCleanupHook $test $InputArgs } if ($(Test-IncludesTerraform $AT $testCount)) { Remove-TerraformFiles $AT $testCount } @@ -486,7 +486,7 @@ if (Get-Command 'Invoke-ARTPreAtomicHook' -errorAction SilentlyContinue) { Invoke-ARTPreAtomicHook $test $InputArgs } $res = Invoke-ExecuteCommand $final_command $test.executor.name $executionPlatform $TimeoutSeconds $session -Interactive:$Interactive Write-Host "Exit code: $($res.ExitCode)" - if (Get-Command 'Invoke-ARTPostAtomicHook' -errorAction SilentlyContinue) { Invoke-ARTPostAtomicHook $test $InputArgs} + if (Get-Command 'Invoke-ARTPostAtomicHook' -errorAction SilentlyContinue) { Invoke-ARTPostAtomicHook $test $InputArgs } $stopTime = Get-Date if ($isLoggingModuleSet) { &"$LoggingModule\Write-ExecutionLog" $startTime $stopTime $AT $testCount $test.name $test.auto_generated_guid $test.executor.name $test.description $final_command $ExecutionLogPath $executionHostname $executionUser $res (-Not($IsLinux -or $IsMacOS)) diff --git a/Public/Invoke-KickoffAtomicRunner.ps1 b/Public/Invoke-KickoffAtomicRunner.ps1 index 12902b0..0431b16 100644 --- a/Public/Invoke-KickoffAtomicRunner.ps1 +++ b/Public/Invoke-KickoffAtomicRunner.ps1 @@ -37,7 +37,7 @@ # Optional additional delay before starting Start-Sleep $artConfig.kickOffDelay.TotalSeconds - if ($artConfig.debug) { Invoke-AtomicRunner *>> $all_log_file } else { Invoke-AtomicRunner } + if ($artConfig.debug) { Invoke-AtomicRunner *>> $all_log_file } else { Invoke-AtomicRunner } } function LogRunnerMsg ($message) { diff --git a/Public/Invoke-SetupAtomicRunner.ps1 b/Public/Invoke-SetupAtomicRunner.ps1 index 18df2c4..ba3be89 100755 --- a/Public/Invoke-SetupAtomicRunner.ps1 +++ b/Public/Invoke-SetupAtomicRunner.ps1 @@ -1,15 +1,18 @@ function Invoke-SetupAtomicRunner { # ensure running with admin privs - if ($artConfig.OS -eq "windows") { # auto-elevate on Windows + if ($artConfig.OS -eq "windows") { + # auto-elevate on Windows $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent()) $testadmin = $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if ($testadmin -eq $false) { Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition)) exit $LASTEXITCODE } - } else { # linux and macos check - doesn't auto-elevate - if((id -u) -ne 0 ){ + } + else { + # linux and macos check - doesn't auto-elevate + if ((id -u) -ne 0 ) { Throw "You must run the Invoke-SetupAtomicRunner script as root" exit } diff --git a/Public/WinEvent-ExecutionLogger.psm1 b/Public/WinEvent-ExecutionLogger.psm1 index cbf8fe6..0ed9a1f 100644 --- a/Public/WinEvent-ExecutionLogger.psm1 +++ b/Public/WinEvent-ExecutionLogger.psm1 @@ -1,5 +1,5 @@ function Start-ExecutionLog($startTime, $logPath, $targetHostname, $targetUser, $commandLine, $isWindows) { - if($isWindows -and -not [System.Diagnostics.EventLog]::Exists('Atomic Red Team')){ + if ($isWindows -and -not [System.Diagnostics.EventLog]::Exists('Atomic Red Team')) { New-EventLog -Source "Applications and Services Logs" -LogName "Atomic Red Team" } } @@ -25,7 +25,7 @@ function Write-ExecutionLog($startTime, $stopTime, $technique, $testNum, $testNa "ExitCode" = $res.ExitCode } - Write-EventLog -Source "Applications and Services Logs" -LogName "Atomic Red Team" -EventID 3001 -EntryType Information -Message $msg -Category 1 -RawData 10,20 + Write-EventLog -Source "Applications and Services Logs" -LogName "Atomic Red Team" -EventID 3001 -EntryType Information -Message $msg -Category 1 -RawData 10, 20 } function Stop-ExecutionLog($startTime, $logPath, $targetHostname, $targetUser, $isWindows) { diff --git a/Public/config.ps1 b/Public/config.ps1 index 9a2ea27..4ae3617 100644 --- a/Public/config.ps1 +++ b/Public/config.ps1 @@ -25,7 +25,7 @@ $artConfig = [PSCustomObject]@{ gmsaAccount = $null # [optional] Logging Module, uses Syslog-ExecutionLogger if left blank and the syslogServer and syslogPort are set, otherwise it uses the Default-ExecutionLogger - LoggingModule = '' + LoggingModule = '' # [optional] Syslog configuration, default execution logs will be sent to this server:port syslogServer = '' # set to empty string '' if you don't want to log atomic execution details to a syslog server (don't includle http(s):\\)