From 0eedc36c72e0404ebc8fd8294b5baeb817036967 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Thu, 28 Dec 2023 06:39:20 -0500 Subject: [PATCH 1/4] Run simple test to check whether Invoke-AtomicTest is working Revert "fixing NoExecutionLog" This reverts commit 84a357a8e1c767b61b50731876298841d8b14b2a. fixing NoExecutionLog --- .github/workflows/check-installation.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-installation.yml b/.github/workflows/check-installation.yml index d17ad4a..db69915 100644 --- a/.github/workflows/check-installation.yml +++ b/.github/workflows/check-installation.yml @@ -20,3 +20,4 @@ jobs: } IEX (IWR 'https://raw.githubusercontent.com/${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}/install-atomicsfolder.ps1' -UseBasicParsing); Install-AtomicsFolder -Force + Invoke-AtomicTest All -ShowDetailsBrief From 7250851e273cc4747453fb7e62eaffee1d49831a Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Thu, 28 Dec 2023 08:19:19 -0500 Subject: [PATCH 2/4] convert encoding for all files --- Invoke-AtomicRedTeam.psd1 | 196 ++--- Private/AtomicClassSchema.ps1 | 108 +-- Private/Get-PrereqExecutor.ps1 | 2 +- Private/Get-TargetInfo.ps1 | 2 +- Private/Invoke-ExecuteCommand.ps1 | 2 +- Private/Invoke-Process.ps1 | 2 +- Private/Show-Details.ps1 | 4 +- Private/Write-KeyValue.ps1 | 2 +- Public/Attire-ExecutionLogger.psm1 | 2 +- Public/Default-ExecutionLogger.psm1 | 2 +- Public/Get-AtomicTechnique.ps1 | 2 +- Public/Get-PreferredIPAddress.ps1 | 2 +- Public/Invoke-AtomicRunner.ps1 | 498 +++++------ Public/Invoke-AtomicTest.ps1 | 2 +- Public/Invoke-FetchFromZip.ps1 | 2 +- Public/Invoke-KickoffAtomicRunner.ps1 | 92 +- Public/Invoke-RunnerScheduleMethods.ps1 | 2 +- Public/Invoke-SetupAtomicRunner.ps1 | 274 +++--- Public/Invoke-WebRequestVerifyHash.ps1 | 2 +- Public/New-Atomic.ps1 | 1030 +++++++++++------------ Public/Start-AtomicGUI.ps1 | 2 +- Public/Syslog-ExecutionLogger.psm1 | 2 +- Public/WinEvent-ExecutionLogger.psm1 | 2 +- Public/config.ps1 | 2 +- docker/setup.ps1 | 2 +- sandbox/setupsandbox.ps1 | 2 +- 26 files changed, 1120 insertions(+), 1120 deletions(-) diff --git a/Invoke-AtomicRedTeam.psd1 b/Invoke-AtomicRedTeam.psd1 index 9af0f28..d5650bd 100644 --- a/Invoke-AtomicRedTeam.psd1 +++ b/Invoke-AtomicRedTeam.psd1 @@ -1,98 +1,98 @@ -@{ - - # Script module or binary module file associated with this manifest. - RootModule = 'Invoke-AtomicRedTeam.psm1' - - # Version number of this module. - ModuleVersion = '2.0.6' - - # ID used to uniquely identify this module - GUID = '8f492621-18f8-432e-9532-b1d54d3e90bd' - - # Author of this module - Author = 'Casey Smith @subTee, Josh Rickard @MSAdministrator, Carrie Roberts @OrOneEqualsOne, Matt Graeber @mattifestation' - - # Company or vendor of this module - CompanyName = 'Red Canary, Inc.' - - # Copyright statement for this module - Copyright = '(c) 2021 Red Canary. All rights reserved.' - - # Description of the functionality provided by this module - Description = 'A PowerShell module that runs Atomic Red Team tests from yaml definition files.' - - # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' - - # Modules that must be imported into the global environment prior to importing this module - 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') - - # 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 = @( - 'Invoke-AtomicTest', - 'Get-AtomicTechnique', - 'New-AtomicTechnique', - 'New-AtomicTest', - 'New-AtomicTestInputArgument', - 'New-AtomicTestDependency', - 'Start-AtomicGUI', - 'Stop-AtomicGUI', - 'Invoke-SetupAtomicRunner', - 'Invoke-GenerateNewSchedule', - 'Invoke-RefreshExistingSchedule', - 'Invoke-AtomicRunner', - 'Get-Schedule', - 'Invoke-KickoffAtomicRunner', - 'Get-PreferredIPAddress' - ) - - # Variables to export from this module - VariablesToExport = '*' - - 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 = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('Security', 'Defense') - - # A URL to the license for this module. - 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' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - ReleaseNotes = @' -1.0.2 ------ -* Add support for custom execution loggers - -1.0.1 ------ -* Adding 'powershell-yaml' to RequiredModules in the module manifest - -1.0.0 ------ -* Initial release for submission to the PowerShell Gallery -'@ - - } # End of PSData hashtable - - } # End of PrivateData hashtable -} +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'Invoke-AtomicRedTeam.psm1' + + # Version number of this module. + ModuleVersion = '2.0.6' + + # ID used to uniquely identify this module + GUID = '8f492621-18f8-432e-9532-b1d54d3e90bd' + + # Author of this module + Author = 'Casey Smith @subTee, Josh Rickard @MSAdministrator, Carrie Roberts @OrOneEqualsOne, Matt Graeber @mattifestation' + + # Company or vendor of this module + CompanyName = 'Red Canary, Inc.' + + # Copyright statement for this module + Copyright = '(c) 2021 Red Canary. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'A PowerShell module that runs Atomic Red Team tests from yaml definition files.' + + # Minimum version of the Windows PowerShell engine required by this module + PowerShellVersion = '5.0' + + # Modules that must be imported into the global environment prior to importing this module + 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') + + # 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 = @( + 'Invoke-AtomicTest', + 'Get-AtomicTechnique', + 'New-AtomicTechnique', + 'New-AtomicTest', + 'New-AtomicTestInputArgument', + 'New-AtomicTestDependency', + 'Start-AtomicGUI', + 'Stop-AtomicGUI', + 'Invoke-SetupAtomicRunner', + 'Invoke-GenerateNewSchedule', + 'Invoke-RefreshExistingSchedule', + 'Invoke-AtomicRunner', + 'Get-Schedule', + 'Invoke-KickoffAtomicRunner', + 'Get-PreferredIPAddress' + ) + + # Variables to export from this module + VariablesToExport = '*' + + 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 = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @('Security', 'Defense') + + # A URL to the license for this module. + 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' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + ReleaseNotes = @' +1.0.2 +----- +* Add support for custom execution loggers + +1.0.1 +----- +* Adding 'powershell-yaml' to RequiredModules in the module manifest + +1.0.0 +----- +* Initial release for submission to the PowerShell Gallery +'@ + + } # End of PSData hashtable + + } # End of PrivateData hashtable +} diff --git a/Private/AtomicClassSchema.ps1 b/Private/AtomicClassSchema.ps1 index 543ef56..e3c2a46 100644 --- a/Private/AtomicClassSchema.ps1 +++ b/Private/AtomicClassSchema.ps1 @@ -1,55 +1,55 @@ -class AtomicDependency { - [String] $description - [String] $prereq_command - [String] $get_prereq_command -} - -class AtomicInputArgument { - [String] $description - [String] $type - [String] $default -} - -class AtomicExecutorBase { - [String] $name - [Bool] $elevation_required - - # Implemented to facilitate improved PS object display - [String] ToString() { - return $this.Name - } -} - -class AtomicExecutorDefault : AtomicExecutorBase { - [String] $command - [String] $cleanup_command -} - -class AtomicExecutorManual : AtomicExecutorBase { - [String] $steps - [String] $cleanup_command -} - -class AtomicTest { - [String] $name - [String] $auto_generated_guid - [String] $description - [String[]] $supported_platforms - # I wish this didn't have to be a hashtable but I don't - # want to change the schema and introduce a breaking change. - [Hashtable] $input_arguments - [String] $dependency_executor_name - [AtomicDependency[]] $dependencies - [AtomicExecutorBase] $executor - - # Implemented to facilitate improved PS object display - [String] ToString() { - return $this.name - } -} - -class AtomicTechnique { - [String[]] $attack_technique - [String] $display_name - [AtomicTest[]] $atomic_tests +class AtomicDependency { + [String] $description + [String] $prereq_command + [String] $get_prereq_command +} + +class AtomicInputArgument { + [String] $description + [String] $type + [String] $default +} + +class AtomicExecutorBase { + [String] $name + [Bool] $elevation_required + + # Implemented to facilitate improved PS object display + [String] ToString() { + return $this.Name + } +} + +class AtomicExecutorDefault : AtomicExecutorBase { + [String] $command + [String] $cleanup_command +} + +class AtomicExecutorManual : AtomicExecutorBase { + [String] $steps + [String] $cleanup_command +} + +class AtomicTest { + [String] $name + [String] $auto_generated_guid + [String] $description + [String[]] $supported_platforms + # I wish this didn't have to be a hashtable but I don't + # want to change the schema and introduce a breaking change. + [Hashtable] $input_arguments + [String] $dependency_executor_name + [AtomicDependency[]] $dependencies + [AtomicExecutorBase] $executor + + # Implemented to facilitate improved PS object display + [String] ToString() { + return $this.name + } +} + +class AtomicTechnique { + [String[]] $attack_technique + [String] $display_name + [AtomicTest[]] $atomic_tests } \ No newline at end of file diff --git a/Private/Get-PrereqExecutor.ps1 b/Private/Get-PrereqExecutor.ps1 index 1f5c119..8cf779a 100644 --- a/Private/Get-PrereqExecutor.ps1 +++ b/Private/Get-PrereqExecutor.ps1 @@ -1,4 +1,4 @@ -function Get-PrereqExecutor ($test) { +function Get-PrereqExecutor ($test) { if ($nul -eq $test.dependency_executor_name) { $executor = $test.executor.name } else { $executor = $test.dependency_executor_name } $executor diff --git a/Private/Get-TargetInfo.ps1 b/Private/Get-TargetInfo.ps1 index e07d545..afc2be9 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 diff --git a/Private/Invoke-ExecuteCommand.ps1 b/Private/Invoke-ExecuteCommand.ps1 index 0d93f36..13686a6 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() diff --git a/Private/Invoke-Process.ps1 b/Private/Invoke-Process.ps1 index 7d19a3d..5940204 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()] diff --git a/Private/Show-Details.ps1 b/Private/Show-Details.ps1 index de22bb7..f239169 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 '(?> $all_log_file } else { Invoke-AtomicRunner } -} - -function LogRunnerMsg ($message) { - $now = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) - Write-Host -fore cyan $message - Add-Content $artConfig.logFile "$now`: $message" +function Invoke-KickoffAtomicRunner { + + #log rotation function + function Rotate-Log { + Param ($logPath, $max_filesize, $max_age) + $datetime = Get-Date -uformat "%Y-%m-%d-%H%M" + + $log = Get-Item $logPath + 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" + } + + $logdir_content = Get-ChildItem $artConfig.atomicLogsPath -filter "*.arclog" + $cutoff_date = (get-date).AddDays($max_age) + $logdir_content | ForEach-Object { + if ($_.LastWriteTime -gt $cutoff_date) { + Remove-Item $_ + Write-Host "Removed $($_.PSPath)" + } + } + } + + #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 + + #Rotate logs based on FileSize and Date max_filesize + $max_filesize = 200 #in MB + $max_file_age = 30 #in days + Rotate-Log $all_log_file $max_filesize $max_file_age + Rotate-Log $artConfig.logFile $max_filesize $max_file_age #no need to repeat this. Can reduce further. + + # Optional additional delay before starting + Start-Sleep $artConfig.kickOffDelay.TotalSeconds + + if ($artConfig.debug) { Invoke-AtomicRunner *>> $all_log_file } else { Invoke-AtomicRunner } +} + +function LogRunnerMsg ($message) { + $now = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) + Write-Host -fore cyan $message + Add-Content $artConfig.logFile "$now`: $message" } \ No newline at end of file diff --git a/Public/Invoke-RunnerScheduleMethods.ps1 b/Public/Invoke-RunnerScheduleMethods.ps1 index 1498c73..cd7750c 100644 --- a/Public/Invoke-RunnerScheduleMethods.ps1 +++ b/Public/Invoke-RunnerScheduleMethods.ps1 @@ -1,4 +1,4 @@ -# 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 diff --git a/Public/Invoke-SetupAtomicRunner.ps1 b/Public/Invoke-SetupAtomicRunner.ps1 index ba3be89..45a9deb 100755 --- a/Public/Invoke-SetupAtomicRunner.ps1 +++ b/Public/Invoke-SetupAtomicRunner.ps1 @@ -1,137 +1,137 @@ -function Invoke-SetupAtomicRunner { - - # ensure running with admin privs - 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 ) { - Throw "You must run the Invoke-SetupAtomicRunner script as root" - exit - } - } - - if ($artConfig.basehostname.length -gt 15) { Throw "The hostname for this machine (minus the GUID) must be 15 characters or less. Please rename this computer." } - - #create AtomicRunner-Logs directories if they don't exist - New-Item -ItemType Directory $artConfig.atomicLogsPath -ErrorAction Ignore - New-Item -ItemType Directory $artConfig.runnerFolder -ErrorAction Ignore - - if ($artConfig.gmsaAccount) { - Start-Service WinRM - $path = Join-Path $env:ProgramFiles "WindowsPowerShell\Modules\RenameRunner\RoleCapabilities" - New-Item -ItemType Directory $path -ErrorAction Ignore - New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -GroupManagedServiceAccount $artConfig.gmsaAccount -RoleDefinitions @{ "$($artConfig.user)" = @{ 'RoleCapabilities' = 'RenameRunner' } } -path "$env:Temp\RenameRunner.pssc" - New-PSRoleCapabilityFile -VisibleCmdlets @{ 'Name' = 'Rename-Computer'; 'Parameters' = @{ 'Name' = 'NewName'; 'ValidatePattern' = 'ATOMICSOC.*' }, @{ 'Name' = 'Force' }, @{ 'Name' = 'restart' } } -path "$path\RenameRunner.psrc" - $null = Register-PSSessionConfiguration -name "RenameRunnerEndpoint" -path "$env:Temp\RenameRunner.pssc" -force - Add-LocalGroupMember "administrators" "$($artConfig.gmsaAccount)$" -ErrorAction Ignore - # Make sure WinRM is enabled and set to Automic start (not delayed) - Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name Start -Value 2 - Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name DelayedAutostart -Value 0 # default is delayed start and that is too slow given our 1 minute delay on our kickoff task - # this registry key must be set to zero for things to work get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service\ - $hklmKey = (get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs - $hkcuKey = (get-itemproperty hkcu:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs - if ((1 -eq $hklmKey) -or (1 -eq $hkcuKey)) { Write-Host -ForegroundColor Red "DisableRunAs registry Key will not allow use of the JEA endpoint with a gmsa account" } - if ((Get-ItemProperty hklm:\System\CurrentControlSet\Control\Lsa\ -name DisableDomainCreds).DisableDomainCreds) { Write-Host -ForegroundColor Red "Do not allow storage of passwords and credentials for network authentication must be disabled" } - } - - if ($artConfig.OS -eq "windows") { - - if (Test-Path $artConfig.credFile) { - Write-Host "Credential File $($artConfig.credFile) already exists, not prompting for creation of a new one." - $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $artConfig.user, (Get-Content $artConfig.credFile | ConvertTo-SecureString) - } - else { - # 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 - # local security policy --> Local Policies --> Security Options --> Network access: Do not allow storage of passwords and credentials for network authentication must be disabled - $taskName = "KickOff-AtomicRunner" - Unregister-ScheduledTask $taskName -confirm:$false -ErrorAction Ignore - # Windows scheduled task includes a 20 minutes sleep then restart if the call to Invoke-KickoffAtomicRunner fails - # this occurs occassionally when Windows has issues logging into the runner user's account and logs in as a TEMP user - $taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-exec bypass -Command Invoke-KickoffAtomicRunner; Start-Sleep 1200; Restart-Computer -Force" - $taskPrincipal = New-ScheduledTaskPrincipal -UserId $artConfig.user - $delays = @(1, 2, 4, 8, 16, 32, 64) # using multiple triggers as a retry mechanism because the built-in retry mechanism doesn't work when the computer renaming causes AD replication delays - $triggers = @() - foreach ($delay in $delays) { - $trigger = New-ScheduledTaskTrigger -AtStartup - $trigger.Delay = "PT$delay`M" - $triggers += $trigger - } - $task = New-ScheduledTask -Action $taskAction -Principal $taskPrincipal -Trigger $triggers -Description "A task that runs 1 minute or later after boot to start the atomic test runner script" - try { - $null = Register-ScheduledTask -TaskName $taskName -InputObject $task -User $artConfig.user -Password $($cred.GetNetworkCredential().password) -ErrorAction Stop - } - catch { - if ($_.CategoryInfo.Category -eq "AuthenticationError") { - # remove the credential file if the password didn't work - Write-Error "The credentials you entered are incorrect. Please run the setup script again and double check the username and password." - Remove-Item $artConfig.credFile - } - else { - Throw $_ - } - } - } - else { - # sets cronjob string using basepath from config.ps1 - $pwshPath = which pwsh - $job = "@reboot root sleep 60;$pwshPath -Command Invoke-KickoffAtomicRunner" - $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) { - $(Write-Output "$job" >> /etc/crontab) - write-host "setting cronjob" - } - else { - write-host "cronjob already exists" - } - } - - # Add Import-Module statement to the PowerShell profile - $root = Split-Path $PSScriptRoot -Parent - $pathToPSD1 = Join-Path $root "Invoke-AtomicRedTeam.psd1" - $importStatement = "Import-Module ""$pathToPSD1"" -Force" - New-Item $PROFILE -ErrorAction Ignore - $profileContent = Get-Content $profile - $line = $profileContent | Select-String ".*import-module.*invoke-atomicredTeam.psd1" | Select-Object -ExpandProperty Line - if ($line) { - $profileContent | ForEach-Object { $_.replace( $line, "$importStatement") } | Set-Content $profile - } - 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" - Install-Module -Name Posh-SYSLOG -Scope CurrentUser -Force - } - - # create the CSV schedule of atomics to run if it doesn't exist - if (-not (Test-Path $artConfig.scheduleFile)) { - Invoke-GenerateNewSchedule - } - - $schedule = Get-Schedule - if ($null -eq $schedule) { - Write-Host -ForegroundColor Yellow "There are no tests enabled on the schedule, set the 'Enabled' column to 'True' for the atomic test that you want to run. The schedule file is found here: $($artConfig.scheduleFile)" - Write-Host -ForegroundColor Yellow "Rerun this setup script after updating the schedule" - } - else { - # Get the prereqs for all of the tests on the schedule - Invoke-AtomicRunner -GetPrereqs - } -} +function Invoke-SetupAtomicRunner { + + # ensure running with admin privs + 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 ) { + Throw "You must run the Invoke-SetupAtomicRunner script as root" + exit + } + } + + if ($artConfig.basehostname.length -gt 15) { Throw "The hostname for this machine (minus the GUID) must be 15 characters or less. Please rename this computer." } + + #create AtomicRunner-Logs directories if they don't exist + New-Item -ItemType Directory $artConfig.atomicLogsPath -ErrorAction Ignore + New-Item -ItemType Directory $artConfig.runnerFolder -ErrorAction Ignore + + if ($artConfig.gmsaAccount) { + Start-Service WinRM + $path = Join-Path $env:ProgramFiles "WindowsPowerShell\Modules\RenameRunner\RoleCapabilities" + New-Item -ItemType Directory $path -ErrorAction Ignore + New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -GroupManagedServiceAccount $artConfig.gmsaAccount -RoleDefinitions @{ "$($artConfig.user)" = @{ 'RoleCapabilities' = 'RenameRunner' } } -path "$env:Temp\RenameRunner.pssc" + New-PSRoleCapabilityFile -VisibleCmdlets @{ 'Name' = 'Rename-Computer'; 'Parameters' = @{ 'Name' = 'NewName'; 'ValidatePattern' = 'ATOMICSOC.*' }, @{ 'Name' = 'Force' }, @{ 'Name' = 'restart' } } -path "$path\RenameRunner.psrc" + $null = Register-PSSessionConfiguration -name "RenameRunnerEndpoint" -path "$env:Temp\RenameRunner.pssc" -force + Add-LocalGroupMember "administrators" "$($artConfig.gmsaAccount)$" -ErrorAction Ignore + # Make sure WinRM is enabled and set to Automic start (not delayed) + Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name Start -Value 2 + Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name DelayedAutostart -Value 0 # default is delayed start and that is too slow given our 1 minute delay on our kickoff task + # this registry key must be set to zero for things to work get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service\ + $hklmKey = (get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs + $hkcuKey = (get-itemproperty hkcu:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs + if ((1 -eq $hklmKey) -or (1 -eq $hkcuKey)) { Write-Host -ForegroundColor Red "DisableRunAs registry Key will not allow use of the JEA endpoint with a gmsa account" } + if ((Get-ItemProperty hklm:\System\CurrentControlSet\Control\Lsa\ -name DisableDomainCreds).DisableDomainCreds) { Write-Host -ForegroundColor Red "Do not allow storage of passwords and credentials for network authentication must be disabled" } + } + + if ($artConfig.OS -eq "windows") { + + if (Test-Path $artConfig.credFile) { + Write-Host "Credential File $($artConfig.credFile) already exists, not prompting for creation of a new one." + $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $artConfig.user, (Get-Content $artConfig.credFile | ConvertTo-SecureString) + } + else { + # 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 + # local security policy --> Local Policies --> Security Options --> Network access: Do not allow storage of passwords and credentials for network authentication must be disabled + $taskName = "KickOff-AtomicRunner" + Unregister-ScheduledTask $taskName -confirm:$false -ErrorAction Ignore + # Windows scheduled task includes a 20 minutes sleep then restart if the call to Invoke-KickoffAtomicRunner fails + # this occurs occassionally when Windows has issues logging into the runner user's account and logs in as a TEMP user + $taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-exec bypass -Command Invoke-KickoffAtomicRunner; Start-Sleep 1200; Restart-Computer -Force" + $taskPrincipal = New-ScheduledTaskPrincipal -UserId $artConfig.user + $delays = @(1, 2, 4, 8, 16, 32, 64) # using multiple triggers as a retry mechanism because the built-in retry mechanism doesn't work when the computer renaming causes AD replication delays + $triggers = @() + foreach ($delay in $delays) { + $trigger = New-ScheduledTaskTrigger -AtStartup + $trigger.Delay = "PT$delay`M" + $triggers += $trigger + } + $task = New-ScheduledTask -Action $taskAction -Principal $taskPrincipal -Trigger $triggers -Description "A task that runs 1 minute or later after boot to start the atomic test runner script" + try { + $null = Register-ScheduledTask -TaskName $taskName -InputObject $task -User $artConfig.user -Password $($cred.GetNetworkCredential().password) -ErrorAction Stop + } + catch { + if ($_.CategoryInfo.Category -eq "AuthenticationError") { + # remove the credential file if the password didn't work + Write-Error "The credentials you entered are incorrect. Please run the setup script again and double check the username and password." + Remove-Item $artConfig.credFile + } + else { + Throw $_ + } + } + } + else { + # sets cronjob string using basepath from config.ps1 + $pwshPath = which pwsh + $job = "@reboot root sleep 60;$pwshPath -Command Invoke-KickoffAtomicRunner" + $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) { + $(Write-Output "$job" >> /etc/crontab) + write-host "setting cronjob" + } + else { + write-host "cronjob already exists" + } + } + + # Add Import-Module statement to the PowerShell profile + $root = Split-Path $PSScriptRoot -Parent + $pathToPSD1 = Join-Path $root "Invoke-AtomicRedTeam.psd1" + $importStatement = "Import-Module ""$pathToPSD1"" -Force" + New-Item $PROFILE -ErrorAction Ignore + $profileContent = Get-Content $profile + $line = $profileContent | Select-String ".*import-module.*invoke-atomicredTeam.psd1" | Select-Object -ExpandProperty Line + if ($line) { + $profileContent | ForEach-Object { $_.replace( $line, "$importStatement") } | Set-Content $profile + } + 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" + Install-Module -Name Posh-SYSLOG -Scope CurrentUser -Force + } + + # create the CSV schedule of atomics to run if it doesn't exist + if (-not (Test-Path $artConfig.scheduleFile)) { + Invoke-GenerateNewSchedule + } + + $schedule = Get-Schedule + if ($null -eq $schedule) { + Write-Host -ForegroundColor Yellow "There are no tests enabled on the schedule, set the 'Enabled' column to 'True' for the atomic test that you want to run. The schedule file is found here: $($artConfig.scheduleFile)" + Write-Host -ForegroundColor Yellow "Rerun this setup script after updating the schedule" + } + else { + # Get the prereqs for all of the tests on the schedule + Invoke-AtomicRunner -GetPrereqs + } +} diff --git a/Public/Invoke-WebRequestVerifyHash.ps1 b/Public/Invoke-WebRequestVerifyHash.ps1 index 619b5c7..3d5f930 100644 --- a/Public/Invoke-WebRequestVerifyHash.ps1 +++ b/Public/Invoke-WebRequestVerifyHash.ps1 @@ -1,4 +1,4 @@ -function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { +function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { $success = $false $null = @( New-Item -ItemType Directory (Split-Path $outfile) -Force | Out-Null diff --git a/Public/New-Atomic.ps1 b/Public/New-Atomic.ps1 index 645b909..3c9abb4 100644 --- a/Public/New-Atomic.ps1 +++ b/Public/New-Atomic.ps1 @@ -1,515 +1,515 @@ -# The class definitions that these functions rely upon are located in Private\AtomicClassSchema.ps1 - -function New-AtomicTechnique { - <# -.SYNOPSIS - -Specifies a new atomic red team technique. The output of this function is designed to be piped directly to ConvertTo-Yaml, eliminating the need to work with YAML directly. - -.PARAMETER AttackTechnique - -Specifies one or more MITRE ATT&CK techniques that to which this technique applies. Per MITRE naming convention, an attack technique should start with "T" followed by a 4 digit number. The MITRE sub-technique format is also supported: TNNNN.NNN - -.PARAMETER DisplayName - -Specifies the name of the technique as defined by ATT&CK. Example: 'Audio Capture' - -.PARAMETER AtomicTests - -Specifies one or more atomic tests. Atomic tests are created using the New-AtomicTest function. - -.EXAMPLE - -$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' -$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' - -$AtomicTest1 = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' -C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} -C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} -'@ - -# Note: the input arguments are identical for atomic test #1 and #2 -$AtomicTest2 = New-AtomicTest -Name 'InstallUtil GetHelp method call' -Description 'Executes the Help property' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' -C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} -C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /? #{filename} -'@ - -$AtomicTechnique = New-AtomicTechnique -AttackTechnique T1118 -DisplayName InstallUtil -AtomicTests $AtomicTest1, $AtomicTest2 - -# Everything is ready to convert to YAML now! -$AtomicTechnique | ConvertTo-Yaml | Out-File T1118.yaml - -.OUTPUTS - -AtomicTechnique - -Outputs an object representing an atomic technique. - -The output of New-AtomicTechnique is designed to be piped to ConvertTo-Yaml. -#> - - [CmdletBinding()] - [OutputType([AtomicTechnique])] - param ( - [Parameter(Mandatory)] - [String[]] - $AttackTechnique, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $DisplayName, - - [Parameter(Mandatory)] - [AtomicTest[]] - [ValidateNotNull()] - $AtomicTests - ) - - $AtomicTechniqueInstance = [AtomicTechnique]::new() - - foreach ($Technique in $AttackTechnique) { - # Attack techniques should match the MITRE ATT&CK [sub-]technique format. - # This is not a requirement so just warn the user. - if ($Technique -notmatch '^(?-i:T\d{4}(\.\d{3}){0,1})$') { - Write-Warning "The following supplied attack technique does not start with 'T' followed by a four digit number: $Technique" - } - } - - $AtomicTechniqueInstance.attack_technique = $AttackTechnique - $AtomicTechniqueInstance.display_name = $DisplayName - $AtomicTechniqueInstance.atomic_tests = $AtomicTests - - return $AtomicTechniqueInstance -} - -function New-AtomicTest { - <# -.SYNOPSIS - -Specifies an atomic test. - -.PARAMETER Name - -Specifies the name of the test that indicates how it tests the technique. - -.PARAMETER Description - -Specifies a long form description of the test. Markdown is supported. - -.PARAMETER SupportedPlatforms - -Specifies the OS/platform on which the test is designed to run. The following platforms are currently supported: Windows, macOS, Linux. - -A single test can support multiple platforms. - -.PARAMETER ExecutorType - -Specifies the the framework or application in which the test should be executed. The following executor types are currently supported: CommandPrompt, Sh, Bash, PowerShell. - -- CommandPrompt: The Windows Command Prompt, aka cmd.exe - Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by cmd.exe. - -- PowerShell: PowerShell - Requires the -ExecutorCommand argument to contain a multi-line PowerShell scriptblock that will be preprocessed and then executed by powershell.exe - -- Sh: Linux's bourne shell - Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by sh. - -- Bash: Linux's bourne again shell - Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by bash. - -.PARAMETER ExecutorElevationRequired - -Specifies that the test must run with elevated privileges. - -.PARAMETER ExecutorSteps - -Specifies a manual list of steps to execute. This should be specified when the atomic test cannot be executed in an automated fashion, for example when GUI steps are involved that cannot be automated. - -.PARAMETER ExecutorCommand - -Specifies the command to execute as part of the atomic test. This should be specified when the atomic test can be executed in an automated fashion. - -The -ExecutorType specified will dictate the command specified, e.g. PowerShell scriptblock code when the "PowerShell" ExecutorType is specified. - -.PARAMETER ExecutorCleanupCommand - -Specifies the command to execute if there are any artifacts that need to be cleaned up. - -.PARAMETER InputArguments - -Specifies one or more input arguments. Input arguments are defined using the New-AtomicTestInputArgument function. - -.PARAMETER DependencyExecutorType - -Specifies an override execution type for dependencies. By default, dependencies are executed using the framework specified in -ExecutorType. - -In most cases, 'PowerShell' is specified as a dependency executor type when 'CommandPrompt' is specified as an executor type. - -.PARAMETER Dependencies - -Specifies one or more dependencies. Dependencies are defined using the New-AtomicTestDependency function. - -.EXAMPLE - -$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' -$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' - -$AtomicTest = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments $InputArg1, $InputArg2 -ExecutorType CommandPrompt -ExecutorCommand @' -C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} -C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} -'@ - -.OUTPUTS - -AtomicTest - -Outputs an object representing an atomic test. This object is intended to be supplied to the New-AtomicTechnique -AtomicTests parameter. - -The output of New-AtomicTest can be piped to ConvertTo-Yaml. The resulting output can be added to an existing atomic technique YAML doc. -#> - - [CmdletBinding(DefaultParameterSetName = 'AutomatedExecutor')] - [OutputType([AtomicTest])] - param ( - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Name, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Description, - - [Parameter(Mandatory)] - [String[]] - [ValidateSet('Windows', 'macOS', 'Linux')] - $SupportedPlatforms, - - [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] - [String] - [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] - $ExecutorType, - - [Switch] - $ExecutorElevationRequired, - - [Parameter(Mandatory, ParameterSetName = 'ManualExecutor')] - [String] - [ValidateNotNullOrEmpty()] - $ExecutorSteps, - - [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] - [String] - [ValidateNotNullOrEmpty()] - $ExecutorCommand, - - [String] - [ValidateNotNullOrEmpty()] - $ExecutorCleanupCommand, - - [AtomicInputArgument[]] - $InputArguments, - - [String] - [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] - $DependencyExecutorType, - - [AtomicDependency[]] - $Dependencies - ) - - $AtomicTestInstance = [AtomicTest]::new() - - $AtomicTestInstance.name = $Name - $AtomicTestInstance.description = $Description - $AtomicTestInstance.supported_platforms = $SupportedPlatforms | ForEach-Object { $_.ToLower() } - - $StringsWithPotentialInputArgs = New-Object -TypeName 'System.Collections.Generic.List`1[String]' - - switch ($PSCmdlet.ParameterSetName) { - 'AutomatedExecutor' { - $ExecutorInstance = [AtomicExecutorDefault]::new() - $ExecutorInstance.command = $ExecutorCommand - $StringsWithPotentialInputArgs.Add($ExecutorCommand) - } - - 'ManualExecutor' { - $ExecutorInstance = [AtomicExecutorManual]::new() - $ExecutorInstance.steps = $ExecutorSteps - $StringsWithPotentialInputArgs.Add($ExecutorSteps) - } - } - - switch ($ExecutorType) { - 'CommandPrompt' { $ExecutorInstance.name = 'command_prompt' } - default { $ExecutorInstance.name = $ExecutorType.ToLower() } - } - - if ($ExecutorCleanupCommand) { - $ExecutorInstance.cleanup_command = $ExecutorCleanupCommand - $StringsWithPotentialInputArgs.Add($ExecutorCleanupCommand) - } - - if ($ExecutorElevationRequired) { $ExecutorInstance.elevation_required = $True } - - if ($Dependencies) { - foreach ($Dependency in $Dependencies) { - $StringsWithPotentialInputArgs.Add($Dependency.description) - $StringsWithPotentialInputArgs.Add($Dependency.prereq_command) - $StringsWithPotentialInputArgs.Add($Dependency.get_prereq_command) - } - } - - if ($DependencyExecutorType) { - switch ($DependencyExecutorType) { - 'CommandPrompt' { $AtomicTestInstance.dependency_executor_name = 'command_prompt' } - default { $AtomicTestInstance.dependency_executor_name = $DependencyExecutorType.ToLower() } - } - } $AtomicTestInstance.dependencies = $Dependencies - - [Hashtable] $InputArgHashtable = @{ } - - if ($InputArguments.Count) { - # Determine if any of the input argument names repeat. They must be unique. - $InputArguments | Group-Object -Property Name | Where-Object { $_.Count -gt 1 } | ForEach-Object { - Write-Error "There are $($_.Count) instances of the $($_.Name) input argument. Input argument names must be unique." - return - } - - # Convert each input argument to a hashtable where the key is the Name property. - - foreach ($InputArg in $InputArguments) { - # Create a copy of the passed input argument that doesn't include the "Name" property. - # Passing in a shallow copy adversely affects YAML serialization for some reason. - $NewInputArg = [AtomicInputArgument]::new() - $NewInputArg.default = $InputArg.default - $NewInputArg.description = $InputArg.description - $NewInputArg.type = $InputArg.type - - $InputArgHashtable[$InputArg.Name] = $NewInputArg - } - - $AtomicTestInstance.input_arguments = $InputArgHashtable - } - - # Extract all specified input arguments from executor and any dependencies. - $Regex = [Regex] '#\{(?[^}]+)\}' - [String[]] $InputArgumentNamesFromExecutor = $StringsWithPotentialInputArgs | - ForEach-Object { $Regex.Matches($_) } | - Select-Object -ExpandProperty Groups | - Where-Object { $_.Name -eq 'ArgName' } | - Select-Object -ExpandProperty Value | - Sort-Object -Unique - - - # Validate that all executor arguments are defined as input arguments - if ($InputArgumentNamesFromExecutor.Count) { - $InputArgumentNamesFromExecutor | ForEach-Object { - if ($InputArgHashtable.Keys -notcontains $_) { - Write-Error "The following input argument was specified but is not defined: '$_'" - return - } - } - } - - # Validate that all defined input args are utilized at least once in the executor. - if ($InputArgHashtable.Keys.Count) { - $InputArgHashtable.Keys | ForEach-Object { - if ($InputArgumentNamesFromExecutor -notcontains $_) { - # Write a warning since this scenario is not considered a breaking change - Write-Warning "The following input argument is defined but not utilized: '$_'." - } - } - } - - $AtomicTestInstance.executor = $ExecutorInstance - - return $AtomicTestInstance -} - -function New-AtomicTestDependency { - <# -.SYNOPSIS - -Specifies a new dependency that must be met prior to execution of an atomic test. - -.PARAMETER Description - -Specifies a human-readable description of the dependency. This should be worded in the following form: SOMETHING must SOMETHING - -.PARAMETER PrereqCommand - -Specifies commands to check if prerequisites for running this test are met. - -For the "command_prompt" executor, if any command returns a non-zero exit code, the pre-requisites are not met. - -For the "powershell" executor, all commands are run as a script block and the script block must return 0 for success. - -.PARAMETER GetPrereqCommand - -Specifies commands to meet this prerequisite or a message describing how to meet this prereq - -More specifically, this command is designed to satisfy either of the following conditions: - -1) If a prerequisite is not met, perform steps necessary to satify the prerequisite. Such a command should be implemented when prerequisites can be satisfied in an automated fashion. -2) If a prerequisite is not met, inform the user what the steps are to satisfy the prerequisite. Such a message should be presented to the user in the case that prerequisites cannot be satisfied in an automated fashion. - -.EXAMPLE - -$Dependency = New-AtomicTestDependency -Description 'Folder to zip must exist (#{input_file_folder})' -PrereqCommand 'test -e #{input_file_folder}' -GetPrereqCommand 'echo Please set input_file_folder argument to a folder that exists' - -.OUTPUTS - -AtomicDependency - -Outputs an object representing an atomic test dependency. This object is intended to be supplied to the New-AtomicTest -Dependencies parameter. - -Note: due to a bug in PowerShell classes, the get_prereq_command property will not display by default. If all fields must be explicitly displayed, they can be viewed by piping output to "Select-Object description, prereq_command, get_prereq_command". -#> - - [CmdletBinding()] - [OutputType([AtomicDependency])] - param ( - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Description, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $PrereqCommand, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $GetPrereqCommand - ) - - $DependencyInstance = [AtomicDependency]::new() - - $DependencyInstance.description = $Description - $DependencyInstance.prereq_command = $PrereqCommand - $DependencyInstance.get_prereq_command = $GetPrereqCommand - - return $DependencyInstance -} - -function New-AtomicTestInputArgument { - <# -.SYNOPSIS - -Specifies an input to an atomic test that is a requirement to run the test (think of these like function arguments). - -.PARAMETER Name - -Specifies the name of the input argument. This must be lowercase and can optionally, have underscores. The input argument name is what is specified as arguments within executors and dependencies. - -.PARAMETER Description - -Specifies a human-readable description of the input argument. - -.PARAMETER Type - -Specifies the data type of the input argument. The following data types are supported: Path, Url, String, Integer, Float. If an alternative data type must be supported, use the -TypeOverride parameter. - -.PARAMETER TypeOverride - -Specifies an unsupported input argument data type. Specifying this parameter should not be common. - -.PARAMETER Default - -Specifies a default value for an input argument if one is not specified via the Invoke-AtomicTest -InputArgs parameter. - -.EXAMPLE - -$AtomicInputArgument = New-AtomicTestInputArgument -Name 'rar_exe' -Type Path -Description 'The RAR executable from Winrar' -Default '%programfiles%\WinRAR\Rar.exe' - -.OUTPUTS - -AtomicInputArgument - -Outputs an object representing an atomic test input argument. This object is intended to be supplied to the New-AtomicTest -InputArguments parameter. -#> - - [CmdletBinding(DefaultParameterSetName = 'PredefinedType')] - [OutputType([AtomicInputArgument])] - param ( - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Name, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Description, - - [Parameter(Mandatory, ParameterSetName = 'PredefinedType')] - [String] - [ValidateSet('Path', 'Url', 'String', 'Integer', 'Float')] - $Type, - - [Parameter(Mandatory, ParameterSetName = 'TypeOverride')] - [String] - [ValidateNotNullOrEmpty()] - $TypeOverride, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Default - ) - - if ($Name -notmatch '^(?-i:[0-9a-z_]+)$') { - Write-Error "Input argument names must be lowercase and optionally, contain underscores. Input argument name supplied: $Name" - return - } - - $AtomicInputArgInstance = [AtomicInputArgument]::new() - - $AtomicInputArgInstance.description = $Description - $AtomicInputArgInstance.default = $Default - - if ($Type) { - $AtomicInputArgInstance.type = $Type - - # Validate input argument types when it makes sense to do so. - switch ($Type) { - 'Url' { - if (-not [Uri]::IsWellFormedUriString($Type, [UriKind]::RelativeOrAbsolute)) { - Write-Warning "The specified Url is not properly formatted: $Type" - } - } - - 'Integer' { - if (-not [Int]::TryParse($Type, [Ref] $null)) { - Write-Warning "The specified Int is not properly formatted: $Type" - } - } - - 'Float' { - if (-not [Double]::TryParse($Type, [Ref] $null)) { - Write-Warning "The specified Float is not properly formatted: $Type" - } - } - - # The following supported data types do not make sense to validate: - # 'Path' { } - # 'String' { } - } - } - else { - $AtomicInputArgInstance.type = $TypeOverride - } - - # Add Name as a note property since the Name property cannot be defined in the AtomicInputArgument - # since it must be stored as a hashtable where the name is the key. Fortunately, ConvertTo-Yaml - # won't convert note properties during serialization. - $InputArgument = Add-Member -InputObject $AtomicInputArgInstance -MemberType NoteProperty -Name Name -Value $Name -PassThru - - return $InputArgument -} +# The class definitions that these functions rely upon are located in Private\AtomicClassSchema.ps1 + +function New-AtomicTechnique { + <# +.SYNOPSIS + +Specifies a new atomic red team technique. The output of this function is designed to be piped directly to ConvertTo-Yaml, eliminating the need to work with YAML directly. + +.PARAMETER AttackTechnique + +Specifies one or more MITRE ATT&CK techniques that to which this technique applies. Per MITRE naming convention, an attack technique should start with "T" followed by a 4 digit number. The MITRE sub-technique format is also supported: TNNNN.NNN + +.PARAMETER DisplayName + +Specifies the name of the technique as defined by ATT&CK. Example: 'Audio Capture' + +.PARAMETER AtomicTests + +Specifies one or more atomic tests. Atomic tests are created using the New-AtomicTest function. + +.EXAMPLE + +$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' +$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' + +$AtomicTest1 = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} +C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} +'@ + +# Note: the input arguments are identical for atomic test #1 and #2 +$AtomicTest2 = New-AtomicTest -Name 'InstallUtil GetHelp method call' -Description 'Executes the Help property' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} +C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /? #{filename} +'@ + +$AtomicTechnique = New-AtomicTechnique -AttackTechnique T1118 -DisplayName InstallUtil -AtomicTests $AtomicTest1, $AtomicTest2 + +# Everything is ready to convert to YAML now! +$AtomicTechnique | ConvertTo-Yaml | Out-File T1118.yaml + +.OUTPUTS + +AtomicTechnique + +Outputs an object representing an atomic technique. + +The output of New-AtomicTechnique is designed to be piped to ConvertTo-Yaml. +#> + + [CmdletBinding()] + [OutputType([AtomicTechnique])] + param ( + [Parameter(Mandatory)] + [String[]] + $AttackTechnique, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $DisplayName, + + [Parameter(Mandatory)] + [AtomicTest[]] + [ValidateNotNull()] + $AtomicTests + ) + + $AtomicTechniqueInstance = [AtomicTechnique]::new() + + foreach ($Technique in $AttackTechnique) { + # Attack techniques should match the MITRE ATT&CK [sub-]technique format. + # This is not a requirement so just warn the user. + if ($Technique -notmatch '^(?-i:T\d{4}(\.\d{3}){0,1})$') { + Write-Warning "The following supplied attack technique does not start with 'T' followed by a four digit number: $Technique" + } + } + + $AtomicTechniqueInstance.attack_technique = $AttackTechnique + $AtomicTechniqueInstance.display_name = $DisplayName + $AtomicTechniqueInstance.atomic_tests = $AtomicTests + + return $AtomicTechniqueInstance +} + +function New-AtomicTest { + <# +.SYNOPSIS + +Specifies an atomic test. + +.PARAMETER Name + +Specifies the name of the test that indicates how it tests the technique. + +.PARAMETER Description + +Specifies a long form description of the test. Markdown is supported. + +.PARAMETER SupportedPlatforms + +Specifies the OS/platform on which the test is designed to run. The following platforms are currently supported: Windows, macOS, Linux. + +A single test can support multiple platforms. + +.PARAMETER ExecutorType + +Specifies the the framework or application in which the test should be executed. The following executor types are currently supported: CommandPrompt, Sh, Bash, PowerShell. + +- CommandPrompt: The Windows Command Prompt, aka cmd.exe + Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by cmd.exe. + +- PowerShell: PowerShell + Requires the -ExecutorCommand argument to contain a multi-line PowerShell scriptblock that will be preprocessed and then executed by powershell.exe + +- Sh: Linux's bourne shell + Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by sh. + +- Bash: Linux's bourne again shell + Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by bash. + +.PARAMETER ExecutorElevationRequired + +Specifies that the test must run with elevated privileges. + +.PARAMETER ExecutorSteps + +Specifies a manual list of steps to execute. This should be specified when the atomic test cannot be executed in an automated fashion, for example when GUI steps are involved that cannot be automated. + +.PARAMETER ExecutorCommand + +Specifies the command to execute as part of the atomic test. This should be specified when the atomic test can be executed in an automated fashion. + +The -ExecutorType specified will dictate the command specified, e.g. PowerShell scriptblock code when the "PowerShell" ExecutorType is specified. + +.PARAMETER ExecutorCleanupCommand + +Specifies the command to execute if there are any artifacts that need to be cleaned up. + +.PARAMETER InputArguments + +Specifies one or more input arguments. Input arguments are defined using the New-AtomicTestInputArgument function. + +.PARAMETER DependencyExecutorType + +Specifies an override execution type for dependencies. By default, dependencies are executed using the framework specified in -ExecutorType. + +In most cases, 'PowerShell' is specified as a dependency executor type when 'CommandPrompt' is specified as an executor type. + +.PARAMETER Dependencies + +Specifies one or more dependencies. Dependencies are defined using the New-AtomicTestDependency function. + +.EXAMPLE + +$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' +$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' + +$AtomicTest = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments $InputArg1, $InputArg2 -ExecutorType CommandPrompt -ExecutorCommand @' +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} +C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} +'@ + +.OUTPUTS + +AtomicTest + +Outputs an object representing an atomic test. This object is intended to be supplied to the New-AtomicTechnique -AtomicTests parameter. + +The output of New-AtomicTest can be piped to ConvertTo-Yaml. The resulting output can be added to an existing atomic technique YAML doc. +#> + + [CmdletBinding(DefaultParameterSetName = 'AutomatedExecutor')] + [OutputType([AtomicTest])] + param ( + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Name, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Description, + + [Parameter(Mandatory)] + [String[]] + [ValidateSet('Windows', 'macOS', 'Linux')] + $SupportedPlatforms, + + [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] + [String] + [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] + $ExecutorType, + + [Switch] + $ExecutorElevationRequired, + + [Parameter(Mandatory, ParameterSetName = 'ManualExecutor')] + [String] + [ValidateNotNullOrEmpty()] + $ExecutorSteps, + + [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] + [String] + [ValidateNotNullOrEmpty()] + $ExecutorCommand, + + [String] + [ValidateNotNullOrEmpty()] + $ExecutorCleanupCommand, + + [AtomicInputArgument[]] + $InputArguments, + + [String] + [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] + $DependencyExecutorType, + + [AtomicDependency[]] + $Dependencies + ) + + $AtomicTestInstance = [AtomicTest]::new() + + $AtomicTestInstance.name = $Name + $AtomicTestInstance.description = $Description + $AtomicTestInstance.supported_platforms = $SupportedPlatforms | ForEach-Object { $_.ToLower() } + + $StringsWithPotentialInputArgs = New-Object -TypeName 'System.Collections.Generic.List`1[String]' + + switch ($PSCmdlet.ParameterSetName) { + 'AutomatedExecutor' { + $ExecutorInstance = [AtomicExecutorDefault]::new() + $ExecutorInstance.command = $ExecutorCommand + $StringsWithPotentialInputArgs.Add($ExecutorCommand) + } + + 'ManualExecutor' { + $ExecutorInstance = [AtomicExecutorManual]::new() + $ExecutorInstance.steps = $ExecutorSteps + $StringsWithPotentialInputArgs.Add($ExecutorSteps) + } + } + + switch ($ExecutorType) { + 'CommandPrompt' { $ExecutorInstance.name = 'command_prompt' } + default { $ExecutorInstance.name = $ExecutorType.ToLower() } + } + + if ($ExecutorCleanupCommand) { + $ExecutorInstance.cleanup_command = $ExecutorCleanupCommand + $StringsWithPotentialInputArgs.Add($ExecutorCleanupCommand) + } + + if ($ExecutorElevationRequired) { $ExecutorInstance.elevation_required = $True } + + if ($Dependencies) { + foreach ($Dependency in $Dependencies) { + $StringsWithPotentialInputArgs.Add($Dependency.description) + $StringsWithPotentialInputArgs.Add($Dependency.prereq_command) + $StringsWithPotentialInputArgs.Add($Dependency.get_prereq_command) + } + } + + if ($DependencyExecutorType) { + switch ($DependencyExecutorType) { + 'CommandPrompt' { $AtomicTestInstance.dependency_executor_name = 'command_prompt' } + default { $AtomicTestInstance.dependency_executor_name = $DependencyExecutorType.ToLower() } + } + } $AtomicTestInstance.dependencies = $Dependencies + + [Hashtable] $InputArgHashtable = @{ } + + if ($InputArguments.Count) { + # Determine if any of the input argument names repeat. They must be unique. + $InputArguments | Group-Object -Property Name | Where-Object { $_.Count -gt 1 } | ForEach-Object { + Write-Error "There are $($_.Count) instances of the $($_.Name) input argument. Input argument names must be unique." + return + } + + # Convert each input argument to a hashtable where the key is the Name property. + + foreach ($InputArg in $InputArguments) { + # Create a copy of the passed input argument that doesn't include the "Name" property. + # Passing in a shallow copy adversely affects YAML serialization for some reason. + $NewInputArg = [AtomicInputArgument]::new() + $NewInputArg.default = $InputArg.default + $NewInputArg.description = $InputArg.description + $NewInputArg.type = $InputArg.type + + $InputArgHashtable[$InputArg.Name] = $NewInputArg + } + + $AtomicTestInstance.input_arguments = $InputArgHashtable + } + + # Extract all specified input arguments from executor and any dependencies. + $Regex = [Regex] '#\{(?[^}]+)\}' + [String[]] $InputArgumentNamesFromExecutor = $StringsWithPotentialInputArgs | + ForEach-Object { $Regex.Matches($_) } | + Select-Object -ExpandProperty Groups | + Where-Object { $_.Name -eq 'ArgName' } | + Select-Object -ExpandProperty Value | + Sort-Object -Unique + + + # Validate that all executor arguments are defined as input arguments + if ($InputArgumentNamesFromExecutor.Count) { + $InputArgumentNamesFromExecutor | ForEach-Object { + if ($InputArgHashtable.Keys -notcontains $_) { + Write-Error "The following input argument was specified but is not defined: '$_'" + return + } + } + } + + # Validate that all defined input args are utilized at least once in the executor. + if ($InputArgHashtable.Keys.Count) { + $InputArgHashtable.Keys | ForEach-Object { + if ($InputArgumentNamesFromExecutor -notcontains $_) { + # Write a warning since this scenario is not considered a breaking change + Write-Warning "The following input argument is defined but not utilized: '$_'." + } + } + } + + $AtomicTestInstance.executor = $ExecutorInstance + + return $AtomicTestInstance +} + +function New-AtomicTestDependency { + <# +.SYNOPSIS + +Specifies a new dependency that must be met prior to execution of an atomic test. + +.PARAMETER Description + +Specifies a human-readable description of the dependency. This should be worded in the following form: SOMETHING must SOMETHING + +.PARAMETER PrereqCommand + +Specifies commands to check if prerequisites for running this test are met. + +For the "command_prompt" executor, if any command returns a non-zero exit code, the pre-requisites are not met. + +For the "powershell" executor, all commands are run as a script block and the script block must return 0 for success. + +.PARAMETER GetPrereqCommand + +Specifies commands to meet this prerequisite or a message describing how to meet this prereq + +More specifically, this command is designed to satisfy either of the following conditions: + +1) If a prerequisite is not met, perform steps necessary to satify the prerequisite. Such a command should be implemented when prerequisites can be satisfied in an automated fashion. +2) If a prerequisite is not met, inform the user what the steps are to satisfy the prerequisite. Such a message should be presented to the user in the case that prerequisites cannot be satisfied in an automated fashion. + +.EXAMPLE + +$Dependency = New-AtomicTestDependency -Description 'Folder to zip must exist (#{input_file_folder})' -PrereqCommand 'test -e #{input_file_folder}' -GetPrereqCommand 'echo Please set input_file_folder argument to a folder that exists' + +.OUTPUTS + +AtomicDependency + +Outputs an object representing an atomic test dependency. This object is intended to be supplied to the New-AtomicTest -Dependencies parameter. + +Note: due to a bug in PowerShell classes, the get_prereq_command property will not display by default. If all fields must be explicitly displayed, they can be viewed by piping output to "Select-Object description, prereq_command, get_prereq_command". +#> + + [CmdletBinding()] + [OutputType([AtomicDependency])] + param ( + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Description, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $PrereqCommand, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $GetPrereqCommand + ) + + $DependencyInstance = [AtomicDependency]::new() + + $DependencyInstance.description = $Description + $DependencyInstance.prereq_command = $PrereqCommand + $DependencyInstance.get_prereq_command = $GetPrereqCommand + + return $DependencyInstance +} + +function New-AtomicTestInputArgument { + <# +.SYNOPSIS + +Specifies an input to an atomic test that is a requirement to run the test (think of these like function arguments). + +.PARAMETER Name + +Specifies the name of the input argument. This must be lowercase and can optionally, have underscores. The input argument name is what is specified as arguments within executors and dependencies. + +.PARAMETER Description + +Specifies a human-readable description of the input argument. + +.PARAMETER Type + +Specifies the data type of the input argument. The following data types are supported: Path, Url, String, Integer, Float. If an alternative data type must be supported, use the -TypeOverride parameter. + +.PARAMETER TypeOverride + +Specifies an unsupported input argument data type. Specifying this parameter should not be common. + +.PARAMETER Default + +Specifies a default value for an input argument if one is not specified via the Invoke-AtomicTest -InputArgs parameter. + +.EXAMPLE + +$AtomicInputArgument = New-AtomicTestInputArgument -Name 'rar_exe' -Type Path -Description 'The RAR executable from Winrar' -Default '%programfiles%\WinRAR\Rar.exe' + +.OUTPUTS + +AtomicInputArgument + +Outputs an object representing an atomic test input argument. This object is intended to be supplied to the New-AtomicTest -InputArguments parameter. +#> + + [CmdletBinding(DefaultParameterSetName = 'PredefinedType')] + [OutputType([AtomicInputArgument])] + param ( + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Name, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Description, + + [Parameter(Mandatory, ParameterSetName = 'PredefinedType')] + [String] + [ValidateSet('Path', 'Url', 'String', 'Integer', 'Float')] + $Type, + + [Parameter(Mandatory, ParameterSetName = 'TypeOverride')] + [String] + [ValidateNotNullOrEmpty()] + $TypeOverride, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Default + ) + + if ($Name -notmatch '^(?-i:[0-9a-z_]+)$') { + Write-Error "Input argument names must be lowercase and optionally, contain underscores. Input argument name supplied: $Name" + return + } + + $AtomicInputArgInstance = [AtomicInputArgument]::new() + + $AtomicInputArgInstance.description = $Description + $AtomicInputArgInstance.default = $Default + + if ($Type) { + $AtomicInputArgInstance.type = $Type + + # Validate input argument types when it makes sense to do so. + switch ($Type) { + 'Url' { + if (-not [Uri]::IsWellFormedUriString($Type, [UriKind]::RelativeOrAbsolute)) { + Write-Warning "The specified Url is not properly formatted: $Type" + } + } + + 'Integer' { + if (-not [Int]::TryParse($Type, [Ref] $null)) { + Write-Warning "The specified Int is not properly formatted: $Type" + } + } + + 'Float' { + if (-not [Double]::TryParse($Type, [Ref] $null)) { + Write-Warning "The specified Float is not properly formatted: $Type" + } + } + + # The following supported data types do not make sense to validate: + # 'Path' { } + # 'String' { } + } + } + else { + $AtomicInputArgInstance.type = $TypeOverride + } + + # Add Name as a note property since the Name property cannot be defined in the AtomicInputArgument + # since it must be stored as a hashtable where the name is the key. Fortunately, ConvertTo-Yaml + # won't convert note properties during serialization. + $InputArgument = Add-Member -InputObject $AtomicInputArgInstance -MemberType NoteProperty -Name Name -Value $Name -PassThru + + return $InputArgument +} diff --git a/Public/Start-AtomicGUI.ps1 b/Public/Start-AtomicGUI.ps1 index ca1b506..bde1fe8 100644 --- a/Public/Start-AtomicGUI.ps1 +++ b/Public/Start-AtomicGUI.ps1 @@ -1,4 +1,4 @@ -function Start-AtomicGUI { +function Start-AtomicGUI { param ( [Int] $port = 8487 ) diff --git a/Public/Syslog-ExecutionLogger.psm1 b/Public/Syslog-ExecutionLogger.psm1 index 2817496..29e3738 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) { } diff --git a/Public/WinEvent-ExecutionLogger.psm1 b/Public/WinEvent-ExecutionLogger.psm1 index 0ed9a1f..238e05b 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" } diff --git a/Public/config.ps1 b/Public/config.ps1 index 4ae3617..17de77b 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 diff --git a/docker/setup.ps1 b/docker/setup.ps1 index b70f7f0..ac3208a 100644 --- a/docker/setup.ps1 +++ b/docker/setup.ps1 @@ -13,4 +13,4 @@ Write-Output @" Import-Module "$ARTPath/invoke-atomicredteam/Invoke-AtomicRedTeam.psd1" -Force; `$PSDefaultParameterValues`["Invoke-AtomicTest:PathToAtomicsFolder"] = "$ARTPath/atomics"; `$PSDefaultParameterValues`["Invoke-AtomicTest:ExecutionLogPath"]="1.csv"; -"@ > $PROFILE \ No newline at end of file +"@ > $PROFILE diff --git a/sandbox/setupsandbox.ps1 b/sandbox/setupsandbox.ps1 index 758ba0e..f991cfe 100644 --- a/sandbox/setupsandbox.ps1 +++ b/sandbox/setupsandbox.ps1 @@ -1,4 +1,4 @@ -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" From 59ae014860118ddd610d952f3ca9f1eafd509f51 Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Thu, 28 Dec 2023 08:27:49 -0500 Subject: [PATCH 3/4] Revert "convert encoding for all files" This reverts commit 7250851e273cc4747453fb7e62eaffee1d49831a. --- Invoke-AtomicRedTeam.psd1 | 196 ++--- Private/AtomicClassSchema.ps1 | 108 +-- Private/Get-PrereqExecutor.ps1 | 2 +- Private/Get-TargetInfo.ps1 | 2 +- Private/Invoke-ExecuteCommand.ps1 | 2 +- Private/Invoke-Process.ps1 | 2 +- Private/Show-Details.ps1 | 4 +- Private/Write-KeyValue.ps1 | 2 +- Public/Attire-ExecutionLogger.psm1 | 2 +- Public/Default-ExecutionLogger.psm1 | 2 +- Public/Get-AtomicTechnique.ps1 | 2 +- Public/Get-PreferredIPAddress.ps1 | 2 +- Public/Invoke-AtomicRunner.ps1 | 498 +++++------ Public/Invoke-AtomicTest.ps1 | 2 +- Public/Invoke-FetchFromZip.ps1 | 2 +- Public/Invoke-KickoffAtomicRunner.ps1 | 92 +- Public/Invoke-RunnerScheduleMethods.ps1 | 2 +- Public/Invoke-SetupAtomicRunner.ps1 | 274 +++--- Public/Invoke-WebRequestVerifyHash.ps1 | 2 +- Public/New-Atomic.ps1 | 1030 +++++++++++------------ Public/Start-AtomicGUI.ps1 | 2 +- Public/Syslog-ExecutionLogger.psm1 | 2 +- Public/WinEvent-ExecutionLogger.psm1 | 2 +- Public/config.ps1 | 2 +- docker/setup.ps1 | 2 +- sandbox/setupsandbox.ps1 | 2 +- 26 files changed, 1120 insertions(+), 1120 deletions(-) diff --git a/Invoke-AtomicRedTeam.psd1 b/Invoke-AtomicRedTeam.psd1 index d5650bd..9af0f28 100644 --- a/Invoke-AtomicRedTeam.psd1 +++ b/Invoke-AtomicRedTeam.psd1 @@ -1,98 +1,98 @@ -@{ - - # Script module or binary module file associated with this manifest. - RootModule = 'Invoke-AtomicRedTeam.psm1' - - # Version number of this module. - ModuleVersion = '2.0.6' - - # ID used to uniquely identify this module - GUID = '8f492621-18f8-432e-9532-b1d54d3e90bd' - - # Author of this module - Author = 'Casey Smith @subTee, Josh Rickard @MSAdministrator, Carrie Roberts @OrOneEqualsOne, Matt Graeber @mattifestation' - - # Company or vendor of this module - CompanyName = 'Red Canary, Inc.' - - # Copyright statement for this module - Copyright = '(c) 2021 Red Canary. All rights reserved.' - - # Description of the functionality provided by this module - Description = 'A PowerShell module that runs Atomic Red Team tests from yaml definition files.' - - # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '5.0' - - # Modules that must be imported into the global environment prior to importing this module - 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') - - # 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 = @( - 'Invoke-AtomicTest', - 'Get-AtomicTechnique', - 'New-AtomicTechnique', - 'New-AtomicTest', - 'New-AtomicTestInputArgument', - 'New-AtomicTestDependency', - 'Start-AtomicGUI', - 'Stop-AtomicGUI', - 'Invoke-SetupAtomicRunner', - 'Invoke-GenerateNewSchedule', - 'Invoke-RefreshExistingSchedule', - 'Invoke-AtomicRunner', - 'Get-Schedule', - 'Invoke-KickoffAtomicRunner', - 'Get-PreferredIPAddress' - ) - - # Variables to export from this module - VariablesToExport = '*' - - 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 = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @('Security', 'Defense') - - # A URL to the license for this module. - 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' - - # A URL to an icon representing this module. - # IconUri = '' - - # ReleaseNotes of this module - ReleaseNotes = @' -1.0.2 ------ -* Add support for custom execution loggers - -1.0.1 ------ -* Adding 'powershell-yaml' to RequiredModules in the module manifest - -1.0.0 ------ -* Initial release for submission to the PowerShell Gallery -'@ - - } # End of PSData hashtable - - } # End of PrivateData hashtable -} +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'Invoke-AtomicRedTeam.psm1' + + # Version number of this module. + ModuleVersion = '2.0.6' + + # ID used to uniquely identify this module + GUID = '8f492621-18f8-432e-9532-b1d54d3e90bd' + + # Author of this module + Author = 'Casey Smith @subTee, Josh Rickard @MSAdministrator, Carrie Roberts @OrOneEqualsOne, Matt Graeber @mattifestation' + + # Company or vendor of this module + CompanyName = 'Red Canary, Inc.' + + # Copyright statement for this module + Copyright = '(c) 2021 Red Canary. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'A PowerShell module that runs Atomic Red Team tests from yaml definition files.' + + # Minimum version of the Windows PowerShell engine required by this module + PowerShellVersion = '5.0' + + # Modules that must be imported into the global environment prior to importing this module + 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') + + # 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 = @( + 'Invoke-AtomicTest', + 'Get-AtomicTechnique', + 'New-AtomicTechnique', + 'New-AtomicTest', + 'New-AtomicTestInputArgument', + 'New-AtomicTestDependency', + 'Start-AtomicGUI', + 'Stop-AtomicGUI', + 'Invoke-SetupAtomicRunner', + 'Invoke-GenerateNewSchedule', + 'Invoke-RefreshExistingSchedule', + 'Invoke-AtomicRunner', + 'Get-Schedule', + 'Invoke-KickoffAtomicRunner', + 'Get-PreferredIPAddress' + ) + + # Variables to export from this module + VariablesToExport = '*' + + 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 = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @('Security', 'Defense') + + # A URL to the license for this module. + 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' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + ReleaseNotes = @' +1.0.2 +----- +* Add support for custom execution loggers + +1.0.1 +----- +* Adding 'powershell-yaml' to RequiredModules in the module manifest + +1.0.0 +----- +* Initial release for submission to the PowerShell Gallery +'@ + + } # End of PSData hashtable + + } # End of PrivateData hashtable +} diff --git a/Private/AtomicClassSchema.ps1 b/Private/AtomicClassSchema.ps1 index e3c2a46..543ef56 100644 --- a/Private/AtomicClassSchema.ps1 +++ b/Private/AtomicClassSchema.ps1 @@ -1,55 +1,55 @@ -class AtomicDependency { - [String] $description - [String] $prereq_command - [String] $get_prereq_command -} - -class AtomicInputArgument { - [String] $description - [String] $type - [String] $default -} - -class AtomicExecutorBase { - [String] $name - [Bool] $elevation_required - - # Implemented to facilitate improved PS object display - [String] ToString() { - return $this.Name - } -} - -class AtomicExecutorDefault : AtomicExecutorBase { - [String] $command - [String] $cleanup_command -} - -class AtomicExecutorManual : AtomicExecutorBase { - [String] $steps - [String] $cleanup_command -} - -class AtomicTest { - [String] $name - [String] $auto_generated_guid - [String] $description - [String[]] $supported_platforms - # I wish this didn't have to be a hashtable but I don't - # want to change the schema and introduce a breaking change. - [Hashtable] $input_arguments - [String] $dependency_executor_name - [AtomicDependency[]] $dependencies - [AtomicExecutorBase] $executor - - # Implemented to facilitate improved PS object display - [String] ToString() { - return $this.name - } -} - -class AtomicTechnique { - [String[]] $attack_technique - [String] $display_name - [AtomicTest[]] $atomic_tests +class AtomicDependency { + [String] $description + [String] $prereq_command + [String] $get_prereq_command +} + +class AtomicInputArgument { + [String] $description + [String] $type + [String] $default +} + +class AtomicExecutorBase { + [String] $name + [Bool] $elevation_required + + # Implemented to facilitate improved PS object display + [String] ToString() { + return $this.Name + } +} + +class AtomicExecutorDefault : AtomicExecutorBase { + [String] $command + [String] $cleanup_command +} + +class AtomicExecutorManual : AtomicExecutorBase { + [String] $steps + [String] $cleanup_command +} + +class AtomicTest { + [String] $name + [String] $auto_generated_guid + [String] $description + [String[]] $supported_platforms + # I wish this didn't have to be a hashtable but I don't + # want to change the schema and introduce a breaking change. + [Hashtable] $input_arguments + [String] $dependency_executor_name + [AtomicDependency[]] $dependencies + [AtomicExecutorBase] $executor + + # Implemented to facilitate improved PS object display + [String] ToString() { + return $this.name + } +} + +class AtomicTechnique { + [String[]] $attack_technique + [String] $display_name + [AtomicTest[]] $atomic_tests } \ No newline at end of file diff --git a/Private/Get-PrereqExecutor.ps1 b/Private/Get-PrereqExecutor.ps1 index 8cf779a..1f5c119 100644 --- a/Private/Get-PrereqExecutor.ps1 +++ b/Private/Get-PrereqExecutor.ps1 @@ -1,4 +1,4 @@ -function Get-PrereqExecutor ($test) { +function Get-PrereqExecutor ($test) { if ($nul -eq $test.dependency_executor_name) { $executor = $test.executor.name } else { $executor = $test.dependency_executor_name } $executor diff --git a/Private/Get-TargetInfo.ps1 b/Private/Get-TargetInfo.ps1 index afc2be9..e07d545 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 diff --git a/Private/Invoke-ExecuteCommand.ps1 b/Private/Invoke-ExecuteCommand.ps1 index 13686a6..0d93f36 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() diff --git a/Private/Invoke-Process.ps1 b/Private/Invoke-Process.ps1 index 5940204..7d19a3d 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()] diff --git a/Private/Show-Details.ps1 b/Private/Show-Details.ps1 index f239169..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 '(?> $all_log_file } else { Invoke-AtomicRunner } -} - -function LogRunnerMsg ($message) { - $now = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) - Write-Host -fore cyan $message - Add-Content $artConfig.logFile "$now`: $message" +function Invoke-KickoffAtomicRunner { + + #log rotation function + function Rotate-Log { + Param ($logPath, $max_filesize, $max_age) + $datetime = Get-Date -uformat "%Y-%m-%d-%H%M" + + $log = Get-Item $logPath + 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" + } + + $logdir_content = Get-ChildItem $artConfig.atomicLogsPath -filter "*.arclog" + $cutoff_date = (get-date).AddDays($max_age) + $logdir_content | ForEach-Object { + if ($_.LastWriteTime -gt $cutoff_date) { + Remove-Item $_ + Write-Host "Removed $($_.PSPath)" + } + } + } + + #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 + + #Rotate logs based on FileSize and Date max_filesize + $max_filesize = 200 #in MB + $max_file_age = 30 #in days + Rotate-Log $all_log_file $max_filesize $max_file_age + Rotate-Log $artConfig.logFile $max_filesize $max_file_age #no need to repeat this. Can reduce further. + + # Optional additional delay before starting + Start-Sleep $artConfig.kickOffDelay.TotalSeconds + + if ($artConfig.debug) { Invoke-AtomicRunner *>> $all_log_file } else { Invoke-AtomicRunner } +} + +function LogRunnerMsg ($message) { + $now = "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) + Write-Host -fore cyan $message + Add-Content $artConfig.logFile "$now`: $message" } \ No newline at end of file diff --git a/Public/Invoke-RunnerScheduleMethods.ps1 b/Public/Invoke-RunnerScheduleMethods.ps1 index cd7750c..1498c73 100644 --- a/Public/Invoke-RunnerScheduleMethods.ps1 +++ b/Public/Invoke-RunnerScheduleMethods.ps1 @@ -1,4 +1,4 @@ -# 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 diff --git a/Public/Invoke-SetupAtomicRunner.ps1 b/Public/Invoke-SetupAtomicRunner.ps1 index 45a9deb..ba3be89 100755 --- a/Public/Invoke-SetupAtomicRunner.ps1 +++ b/Public/Invoke-SetupAtomicRunner.ps1 @@ -1,137 +1,137 @@ -function Invoke-SetupAtomicRunner { - - # ensure running with admin privs - 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 ) { - Throw "You must run the Invoke-SetupAtomicRunner script as root" - exit - } - } - - if ($artConfig.basehostname.length -gt 15) { Throw "The hostname for this machine (minus the GUID) must be 15 characters or less. Please rename this computer." } - - #create AtomicRunner-Logs directories if they don't exist - New-Item -ItemType Directory $artConfig.atomicLogsPath -ErrorAction Ignore - New-Item -ItemType Directory $artConfig.runnerFolder -ErrorAction Ignore - - if ($artConfig.gmsaAccount) { - Start-Service WinRM - $path = Join-Path $env:ProgramFiles "WindowsPowerShell\Modules\RenameRunner\RoleCapabilities" - New-Item -ItemType Directory $path -ErrorAction Ignore - New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -GroupManagedServiceAccount $artConfig.gmsaAccount -RoleDefinitions @{ "$($artConfig.user)" = @{ 'RoleCapabilities' = 'RenameRunner' } } -path "$env:Temp\RenameRunner.pssc" - New-PSRoleCapabilityFile -VisibleCmdlets @{ 'Name' = 'Rename-Computer'; 'Parameters' = @{ 'Name' = 'NewName'; 'ValidatePattern' = 'ATOMICSOC.*' }, @{ 'Name' = 'Force' }, @{ 'Name' = 'restart' } } -path "$path\RenameRunner.psrc" - $null = Register-PSSessionConfiguration -name "RenameRunnerEndpoint" -path "$env:Temp\RenameRunner.pssc" -force - Add-LocalGroupMember "administrators" "$($artConfig.gmsaAccount)$" -ErrorAction Ignore - # Make sure WinRM is enabled and set to Automic start (not delayed) - Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name Start -Value 2 - Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name DelayedAutostart -Value 0 # default is delayed start and that is too slow given our 1 minute delay on our kickoff task - # this registry key must be set to zero for things to work get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service\ - $hklmKey = (get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs - $hkcuKey = (get-itemproperty hkcu:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs - if ((1 -eq $hklmKey) -or (1 -eq $hkcuKey)) { Write-Host -ForegroundColor Red "DisableRunAs registry Key will not allow use of the JEA endpoint with a gmsa account" } - if ((Get-ItemProperty hklm:\System\CurrentControlSet\Control\Lsa\ -name DisableDomainCreds).DisableDomainCreds) { Write-Host -ForegroundColor Red "Do not allow storage of passwords and credentials for network authentication must be disabled" } - } - - if ($artConfig.OS -eq "windows") { - - if (Test-Path $artConfig.credFile) { - Write-Host "Credential File $($artConfig.credFile) already exists, not prompting for creation of a new one." - $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $artConfig.user, (Get-Content $artConfig.credFile | ConvertTo-SecureString) - } - else { - # 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 - # local security policy --> Local Policies --> Security Options --> Network access: Do not allow storage of passwords and credentials for network authentication must be disabled - $taskName = "KickOff-AtomicRunner" - Unregister-ScheduledTask $taskName -confirm:$false -ErrorAction Ignore - # Windows scheduled task includes a 20 minutes sleep then restart if the call to Invoke-KickoffAtomicRunner fails - # this occurs occassionally when Windows has issues logging into the runner user's account and logs in as a TEMP user - $taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-exec bypass -Command Invoke-KickoffAtomicRunner; Start-Sleep 1200; Restart-Computer -Force" - $taskPrincipal = New-ScheduledTaskPrincipal -UserId $artConfig.user - $delays = @(1, 2, 4, 8, 16, 32, 64) # using multiple triggers as a retry mechanism because the built-in retry mechanism doesn't work when the computer renaming causes AD replication delays - $triggers = @() - foreach ($delay in $delays) { - $trigger = New-ScheduledTaskTrigger -AtStartup - $trigger.Delay = "PT$delay`M" - $triggers += $trigger - } - $task = New-ScheduledTask -Action $taskAction -Principal $taskPrincipal -Trigger $triggers -Description "A task that runs 1 minute or later after boot to start the atomic test runner script" - try { - $null = Register-ScheduledTask -TaskName $taskName -InputObject $task -User $artConfig.user -Password $($cred.GetNetworkCredential().password) -ErrorAction Stop - } - catch { - if ($_.CategoryInfo.Category -eq "AuthenticationError") { - # remove the credential file if the password didn't work - Write-Error "The credentials you entered are incorrect. Please run the setup script again and double check the username and password." - Remove-Item $artConfig.credFile - } - else { - Throw $_ - } - } - } - else { - # sets cronjob string using basepath from config.ps1 - $pwshPath = which pwsh - $job = "@reboot root sleep 60;$pwshPath -Command Invoke-KickoffAtomicRunner" - $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) { - $(Write-Output "$job" >> /etc/crontab) - write-host "setting cronjob" - } - else { - write-host "cronjob already exists" - } - } - - # Add Import-Module statement to the PowerShell profile - $root = Split-Path $PSScriptRoot -Parent - $pathToPSD1 = Join-Path $root "Invoke-AtomicRedTeam.psd1" - $importStatement = "Import-Module ""$pathToPSD1"" -Force" - New-Item $PROFILE -ErrorAction Ignore - $profileContent = Get-Content $profile - $line = $profileContent | Select-String ".*import-module.*invoke-atomicredTeam.psd1" | Select-Object -ExpandProperty Line - if ($line) { - $profileContent | ForEach-Object { $_.replace( $line, "$importStatement") } | Set-Content $profile - } - 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" - Install-Module -Name Posh-SYSLOG -Scope CurrentUser -Force - } - - # create the CSV schedule of atomics to run if it doesn't exist - if (-not (Test-Path $artConfig.scheduleFile)) { - Invoke-GenerateNewSchedule - } - - $schedule = Get-Schedule - if ($null -eq $schedule) { - Write-Host -ForegroundColor Yellow "There are no tests enabled on the schedule, set the 'Enabled' column to 'True' for the atomic test that you want to run. The schedule file is found here: $($artConfig.scheduleFile)" - Write-Host -ForegroundColor Yellow "Rerun this setup script after updating the schedule" - } - else { - # Get the prereqs for all of the tests on the schedule - Invoke-AtomicRunner -GetPrereqs - } -} +function Invoke-SetupAtomicRunner { + + # ensure running with admin privs + 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 ) { + Throw "You must run the Invoke-SetupAtomicRunner script as root" + exit + } + } + + if ($artConfig.basehostname.length -gt 15) { Throw "The hostname for this machine (minus the GUID) must be 15 characters or less. Please rename this computer." } + + #create AtomicRunner-Logs directories if they don't exist + New-Item -ItemType Directory $artConfig.atomicLogsPath -ErrorAction Ignore + New-Item -ItemType Directory $artConfig.runnerFolder -ErrorAction Ignore + + if ($artConfig.gmsaAccount) { + Start-Service WinRM + $path = Join-Path $env:ProgramFiles "WindowsPowerShell\Modules\RenameRunner\RoleCapabilities" + New-Item -ItemType Directory $path -ErrorAction Ignore + New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -GroupManagedServiceAccount $artConfig.gmsaAccount -RoleDefinitions @{ "$($artConfig.user)" = @{ 'RoleCapabilities' = 'RenameRunner' } } -path "$env:Temp\RenameRunner.pssc" + New-PSRoleCapabilityFile -VisibleCmdlets @{ 'Name' = 'Rename-Computer'; 'Parameters' = @{ 'Name' = 'NewName'; 'ValidatePattern' = 'ATOMICSOC.*' }, @{ 'Name' = 'Force' }, @{ 'Name' = 'restart' } } -path "$path\RenameRunner.psrc" + $null = Register-PSSessionConfiguration -name "RenameRunnerEndpoint" -path "$env:Temp\RenameRunner.pssc" -force + Add-LocalGroupMember "administrators" "$($artConfig.gmsaAccount)$" -ErrorAction Ignore + # Make sure WinRM is enabled and set to Automic start (not delayed) + Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name Start -Value 2 + Set-ItemProperty hklm:\\SYSTEM\CurrentControlSet\Services\WinRM -Name DelayedAutostart -Value 0 # default is delayed start and that is too slow given our 1 minute delay on our kickoff task + # this registry key must be set to zero for things to work get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service\ + $hklmKey = (get-itemproperty hklm:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs + $hkcuKey = (get-itemproperty hkcu:\Software\Policies\Microsoft\Windows\WinRM\Service -name DisableRunAs -ErrorAction ignore).DisableRunAs + if ((1 -eq $hklmKey) -or (1 -eq $hkcuKey)) { Write-Host -ForegroundColor Red "DisableRunAs registry Key will not allow use of the JEA endpoint with a gmsa account" } + if ((Get-ItemProperty hklm:\System\CurrentControlSet\Control\Lsa\ -name DisableDomainCreds).DisableDomainCreds) { Write-Host -ForegroundColor Red "Do not allow storage of passwords and credentials for network authentication must be disabled" } + } + + if ($artConfig.OS -eq "windows") { + + if (Test-Path $artConfig.credFile) { + Write-Host "Credential File $($artConfig.credFile) already exists, not prompting for creation of a new one." + $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $artConfig.user, (Get-Content $artConfig.credFile | ConvertTo-SecureString) + } + else { + # 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 + # local security policy --> Local Policies --> Security Options --> Network access: Do not allow storage of passwords and credentials for network authentication must be disabled + $taskName = "KickOff-AtomicRunner" + Unregister-ScheduledTask $taskName -confirm:$false -ErrorAction Ignore + # Windows scheduled task includes a 20 minutes sleep then restart if the call to Invoke-KickoffAtomicRunner fails + # this occurs occassionally when Windows has issues logging into the runner user's account and logs in as a TEMP user + $taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-exec bypass -Command Invoke-KickoffAtomicRunner; Start-Sleep 1200; Restart-Computer -Force" + $taskPrincipal = New-ScheduledTaskPrincipal -UserId $artConfig.user + $delays = @(1, 2, 4, 8, 16, 32, 64) # using multiple triggers as a retry mechanism because the built-in retry mechanism doesn't work when the computer renaming causes AD replication delays + $triggers = @() + foreach ($delay in $delays) { + $trigger = New-ScheduledTaskTrigger -AtStartup + $trigger.Delay = "PT$delay`M" + $triggers += $trigger + } + $task = New-ScheduledTask -Action $taskAction -Principal $taskPrincipal -Trigger $triggers -Description "A task that runs 1 minute or later after boot to start the atomic test runner script" + try { + $null = Register-ScheduledTask -TaskName $taskName -InputObject $task -User $artConfig.user -Password $($cred.GetNetworkCredential().password) -ErrorAction Stop + } + catch { + if ($_.CategoryInfo.Category -eq "AuthenticationError") { + # remove the credential file if the password didn't work + Write-Error "The credentials you entered are incorrect. Please run the setup script again and double check the username and password." + Remove-Item $artConfig.credFile + } + else { + Throw $_ + } + } + } + else { + # sets cronjob string using basepath from config.ps1 + $pwshPath = which pwsh + $job = "@reboot root sleep 60;$pwshPath -Command Invoke-KickoffAtomicRunner" + $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) { + $(Write-Output "$job" >> /etc/crontab) + write-host "setting cronjob" + } + else { + write-host "cronjob already exists" + } + } + + # Add Import-Module statement to the PowerShell profile + $root = Split-Path $PSScriptRoot -Parent + $pathToPSD1 = Join-Path $root "Invoke-AtomicRedTeam.psd1" + $importStatement = "Import-Module ""$pathToPSD1"" -Force" + New-Item $PROFILE -ErrorAction Ignore + $profileContent = Get-Content $profile + $line = $profileContent | Select-String ".*import-module.*invoke-atomicredTeam.psd1" | Select-Object -ExpandProperty Line + if ($line) { + $profileContent | ForEach-Object { $_.replace( $line, "$importStatement") } | Set-Content $profile + } + 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" + Install-Module -Name Posh-SYSLOG -Scope CurrentUser -Force + } + + # create the CSV schedule of atomics to run if it doesn't exist + if (-not (Test-Path $artConfig.scheduleFile)) { + Invoke-GenerateNewSchedule + } + + $schedule = Get-Schedule + if ($null -eq $schedule) { + Write-Host -ForegroundColor Yellow "There are no tests enabled on the schedule, set the 'Enabled' column to 'True' for the atomic test that you want to run. The schedule file is found here: $($artConfig.scheduleFile)" + Write-Host -ForegroundColor Yellow "Rerun this setup script after updating the schedule" + } + else { + # Get the prereqs for all of the tests on the schedule + Invoke-AtomicRunner -GetPrereqs + } +} diff --git a/Public/Invoke-WebRequestVerifyHash.ps1 b/Public/Invoke-WebRequestVerifyHash.ps1 index 3d5f930..619b5c7 100644 --- a/Public/Invoke-WebRequestVerifyHash.ps1 +++ b/Public/Invoke-WebRequestVerifyHash.ps1 @@ -1,4 +1,4 @@ -function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { +function Invoke-WebRequestVerifyHash ($url, $outfile, $hash) { $success = $false $null = @( New-Item -ItemType Directory (Split-Path $outfile) -Force | Out-Null diff --git a/Public/New-Atomic.ps1 b/Public/New-Atomic.ps1 index 3c9abb4..645b909 100644 --- a/Public/New-Atomic.ps1 +++ b/Public/New-Atomic.ps1 @@ -1,515 +1,515 @@ -# The class definitions that these functions rely upon are located in Private\AtomicClassSchema.ps1 - -function New-AtomicTechnique { - <# -.SYNOPSIS - -Specifies a new atomic red team technique. The output of this function is designed to be piped directly to ConvertTo-Yaml, eliminating the need to work with YAML directly. - -.PARAMETER AttackTechnique - -Specifies one or more MITRE ATT&CK techniques that to which this technique applies. Per MITRE naming convention, an attack technique should start with "T" followed by a 4 digit number. The MITRE sub-technique format is also supported: TNNNN.NNN - -.PARAMETER DisplayName - -Specifies the name of the technique as defined by ATT&CK. Example: 'Audio Capture' - -.PARAMETER AtomicTests - -Specifies one or more atomic tests. Atomic tests are created using the New-AtomicTest function. - -.EXAMPLE - -$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' -$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' - -$AtomicTest1 = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' -C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} -C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} -'@ - -# Note: the input arguments are identical for atomic test #1 and #2 -$AtomicTest2 = New-AtomicTest -Name 'InstallUtil GetHelp method call' -Description 'Executes the Help property' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' -C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} -C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /? #{filename} -'@ - -$AtomicTechnique = New-AtomicTechnique -AttackTechnique T1118 -DisplayName InstallUtil -AtomicTests $AtomicTest1, $AtomicTest2 - -# Everything is ready to convert to YAML now! -$AtomicTechnique | ConvertTo-Yaml | Out-File T1118.yaml - -.OUTPUTS - -AtomicTechnique - -Outputs an object representing an atomic technique. - -The output of New-AtomicTechnique is designed to be piped to ConvertTo-Yaml. -#> - - [CmdletBinding()] - [OutputType([AtomicTechnique])] - param ( - [Parameter(Mandatory)] - [String[]] - $AttackTechnique, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $DisplayName, - - [Parameter(Mandatory)] - [AtomicTest[]] - [ValidateNotNull()] - $AtomicTests - ) - - $AtomicTechniqueInstance = [AtomicTechnique]::new() - - foreach ($Technique in $AttackTechnique) { - # Attack techniques should match the MITRE ATT&CK [sub-]technique format. - # This is not a requirement so just warn the user. - if ($Technique -notmatch '^(?-i:T\d{4}(\.\d{3}){0,1})$') { - Write-Warning "The following supplied attack technique does not start with 'T' followed by a four digit number: $Technique" - } - } - - $AtomicTechniqueInstance.attack_technique = $AttackTechnique - $AtomicTechniqueInstance.display_name = $DisplayName - $AtomicTechniqueInstance.atomic_tests = $AtomicTests - - return $AtomicTechniqueInstance -} - -function New-AtomicTest { - <# -.SYNOPSIS - -Specifies an atomic test. - -.PARAMETER Name - -Specifies the name of the test that indicates how it tests the technique. - -.PARAMETER Description - -Specifies a long form description of the test. Markdown is supported. - -.PARAMETER SupportedPlatforms - -Specifies the OS/platform on which the test is designed to run. The following platforms are currently supported: Windows, macOS, Linux. - -A single test can support multiple platforms. - -.PARAMETER ExecutorType - -Specifies the the framework or application in which the test should be executed. The following executor types are currently supported: CommandPrompt, Sh, Bash, PowerShell. - -- CommandPrompt: The Windows Command Prompt, aka cmd.exe - Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by cmd.exe. - -- PowerShell: PowerShell - Requires the -ExecutorCommand argument to contain a multi-line PowerShell scriptblock that will be preprocessed and then executed by powershell.exe - -- Sh: Linux's bourne shell - Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by sh. - -- Bash: Linux's bourne again shell - Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by bash. - -.PARAMETER ExecutorElevationRequired - -Specifies that the test must run with elevated privileges. - -.PARAMETER ExecutorSteps - -Specifies a manual list of steps to execute. This should be specified when the atomic test cannot be executed in an automated fashion, for example when GUI steps are involved that cannot be automated. - -.PARAMETER ExecutorCommand - -Specifies the command to execute as part of the atomic test. This should be specified when the atomic test can be executed in an automated fashion. - -The -ExecutorType specified will dictate the command specified, e.g. PowerShell scriptblock code when the "PowerShell" ExecutorType is specified. - -.PARAMETER ExecutorCleanupCommand - -Specifies the command to execute if there are any artifacts that need to be cleaned up. - -.PARAMETER InputArguments - -Specifies one or more input arguments. Input arguments are defined using the New-AtomicTestInputArgument function. - -.PARAMETER DependencyExecutorType - -Specifies an override execution type for dependencies. By default, dependencies are executed using the framework specified in -ExecutorType. - -In most cases, 'PowerShell' is specified as a dependency executor type when 'CommandPrompt' is specified as an executor type. - -.PARAMETER Dependencies - -Specifies one or more dependencies. Dependencies are defined using the New-AtomicTestDependency function. - -.EXAMPLE - -$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' -$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' - -$AtomicTest = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments $InputArg1, $InputArg2 -ExecutorType CommandPrompt -ExecutorCommand @' -C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} -C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} -'@ - -.OUTPUTS - -AtomicTest - -Outputs an object representing an atomic test. This object is intended to be supplied to the New-AtomicTechnique -AtomicTests parameter. - -The output of New-AtomicTest can be piped to ConvertTo-Yaml. The resulting output can be added to an existing atomic technique YAML doc. -#> - - [CmdletBinding(DefaultParameterSetName = 'AutomatedExecutor')] - [OutputType([AtomicTest])] - param ( - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Name, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Description, - - [Parameter(Mandatory)] - [String[]] - [ValidateSet('Windows', 'macOS', 'Linux')] - $SupportedPlatforms, - - [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] - [String] - [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] - $ExecutorType, - - [Switch] - $ExecutorElevationRequired, - - [Parameter(Mandatory, ParameterSetName = 'ManualExecutor')] - [String] - [ValidateNotNullOrEmpty()] - $ExecutorSteps, - - [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] - [String] - [ValidateNotNullOrEmpty()] - $ExecutorCommand, - - [String] - [ValidateNotNullOrEmpty()] - $ExecutorCleanupCommand, - - [AtomicInputArgument[]] - $InputArguments, - - [String] - [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] - $DependencyExecutorType, - - [AtomicDependency[]] - $Dependencies - ) - - $AtomicTestInstance = [AtomicTest]::new() - - $AtomicTestInstance.name = $Name - $AtomicTestInstance.description = $Description - $AtomicTestInstance.supported_platforms = $SupportedPlatforms | ForEach-Object { $_.ToLower() } - - $StringsWithPotentialInputArgs = New-Object -TypeName 'System.Collections.Generic.List`1[String]' - - switch ($PSCmdlet.ParameterSetName) { - 'AutomatedExecutor' { - $ExecutorInstance = [AtomicExecutorDefault]::new() - $ExecutorInstance.command = $ExecutorCommand - $StringsWithPotentialInputArgs.Add($ExecutorCommand) - } - - 'ManualExecutor' { - $ExecutorInstance = [AtomicExecutorManual]::new() - $ExecutorInstance.steps = $ExecutorSteps - $StringsWithPotentialInputArgs.Add($ExecutorSteps) - } - } - - switch ($ExecutorType) { - 'CommandPrompt' { $ExecutorInstance.name = 'command_prompt' } - default { $ExecutorInstance.name = $ExecutorType.ToLower() } - } - - if ($ExecutorCleanupCommand) { - $ExecutorInstance.cleanup_command = $ExecutorCleanupCommand - $StringsWithPotentialInputArgs.Add($ExecutorCleanupCommand) - } - - if ($ExecutorElevationRequired) { $ExecutorInstance.elevation_required = $True } - - if ($Dependencies) { - foreach ($Dependency in $Dependencies) { - $StringsWithPotentialInputArgs.Add($Dependency.description) - $StringsWithPotentialInputArgs.Add($Dependency.prereq_command) - $StringsWithPotentialInputArgs.Add($Dependency.get_prereq_command) - } - } - - if ($DependencyExecutorType) { - switch ($DependencyExecutorType) { - 'CommandPrompt' { $AtomicTestInstance.dependency_executor_name = 'command_prompt' } - default { $AtomicTestInstance.dependency_executor_name = $DependencyExecutorType.ToLower() } - } - } $AtomicTestInstance.dependencies = $Dependencies - - [Hashtable] $InputArgHashtable = @{ } - - if ($InputArguments.Count) { - # Determine if any of the input argument names repeat. They must be unique. - $InputArguments | Group-Object -Property Name | Where-Object { $_.Count -gt 1 } | ForEach-Object { - Write-Error "There are $($_.Count) instances of the $($_.Name) input argument. Input argument names must be unique." - return - } - - # Convert each input argument to a hashtable where the key is the Name property. - - foreach ($InputArg in $InputArguments) { - # Create a copy of the passed input argument that doesn't include the "Name" property. - # Passing in a shallow copy adversely affects YAML serialization for some reason. - $NewInputArg = [AtomicInputArgument]::new() - $NewInputArg.default = $InputArg.default - $NewInputArg.description = $InputArg.description - $NewInputArg.type = $InputArg.type - - $InputArgHashtable[$InputArg.Name] = $NewInputArg - } - - $AtomicTestInstance.input_arguments = $InputArgHashtable - } - - # Extract all specified input arguments from executor and any dependencies. - $Regex = [Regex] '#\{(?[^}]+)\}' - [String[]] $InputArgumentNamesFromExecutor = $StringsWithPotentialInputArgs | - ForEach-Object { $Regex.Matches($_) } | - Select-Object -ExpandProperty Groups | - Where-Object { $_.Name -eq 'ArgName' } | - Select-Object -ExpandProperty Value | - Sort-Object -Unique - - - # Validate that all executor arguments are defined as input arguments - if ($InputArgumentNamesFromExecutor.Count) { - $InputArgumentNamesFromExecutor | ForEach-Object { - if ($InputArgHashtable.Keys -notcontains $_) { - Write-Error "The following input argument was specified but is not defined: '$_'" - return - } - } - } - - # Validate that all defined input args are utilized at least once in the executor. - if ($InputArgHashtable.Keys.Count) { - $InputArgHashtable.Keys | ForEach-Object { - if ($InputArgumentNamesFromExecutor -notcontains $_) { - # Write a warning since this scenario is not considered a breaking change - Write-Warning "The following input argument is defined but not utilized: '$_'." - } - } - } - - $AtomicTestInstance.executor = $ExecutorInstance - - return $AtomicTestInstance -} - -function New-AtomicTestDependency { - <# -.SYNOPSIS - -Specifies a new dependency that must be met prior to execution of an atomic test. - -.PARAMETER Description - -Specifies a human-readable description of the dependency. This should be worded in the following form: SOMETHING must SOMETHING - -.PARAMETER PrereqCommand - -Specifies commands to check if prerequisites for running this test are met. - -For the "command_prompt" executor, if any command returns a non-zero exit code, the pre-requisites are not met. - -For the "powershell" executor, all commands are run as a script block and the script block must return 0 for success. - -.PARAMETER GetPrereqCommand - -Specifies commands to meet this prerequisite or a message describing how to meet this prereq - -More specifically, this command is designed to satisfy either of the following conditions: - -1) If a prerequisite is not met, perform steps necessary to satify the prerequisite. Such a command should be implemented when prerequisites can be satisfied in an automated fashion. -2) If a prerequisite is not met, inform the user what the steps are to satisfy the prerequisite. Such a message should be presented to the user in the case that prerequisites cannot be satisfied in an automated fashion. - -.EXAMPLE - -$Dependency = New-AtomicTestDependency -Description 'Folder to zip must exist (#{input_file_folder})' -PrereqCommand 'test -e #{input_file_folder}' -GetPrereqCommand 'echo Please set input_file_folder argument to a folder that exists' - -.OUTPUTS - -AtomicDependency - -Outputs an object representing an atomic test dependency. This object is intended to be supplied to the New-AtomicTest -Dependencies parameter. - -Note: due to a bug in PowerShell classes, the get_prereq_command property will not display by default. If all fields must be explicitly displayed, they can be viewed by piping output to "Select-Object description, prereq_command, get_prereq_command". -#> - - [CmdletBinding()] - [OutputType([AtomicDependency])] - param ( - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Description, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $PrereqCommand, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $GetPrereqCommand - ) - - $DependencyInstance = [AtomicDependency]::new() - - $DependencyInstance.description = $Description - $DependencyInstance.prereq_command = $PrereqCommand - $DependencyInstance.get_prereq_command = $GetPrereqCommand - - return $DependencyInstance -} - -function New-AtomicTestInputArgument { - <# -.SYNOPSIS - -Specifies an input to an atomic test that is a requirement to run the test (think of these like function arguments). - -.PARAMETER Name - -Specifies the name of the input argument. This must be lowercase and can optionally, have underscores. The input argument name is what is specified as arguments within executors and dependencies. - -.PARAMETER Description - -Specifies a human-readable description of the input argument. - -.PARAMETER Type - -Specifies the data type of the input argument. The following data types are supported: Path, Url, String, Integer, Float. If an alternative data type must be supported, use the -TypeOverride parameter. - -.PARAMETER TypeOverride - -Specifies an unsupported input argument data type. Specifying this parameter should not be common. - -.PARAMETER Default - -Specifies a default value for an input argument if one is not specified via the Invoke-AtomicTest -InputArgs parameter. - -.EXAMPLE - -$AtomicInputArgument = New-AtomicTestInputArgument -Name 'rar_exe' -Type Path -Description 'The RAR executable from Winrar' -Default '%programfiles%\WinRAR\Rar.exe' - -.OUTPUTS - -AtomicInputArgument - -Outputs an object representing an atomic test input argument. This object is intended to be supplied to the New-AtomicTest -InputArguments parameter. -#> - - [CmdletBinding(DefaultParameterSetName = 'PredefinedType')] - [OutputType([AtomicInputArgument])] - param ( - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Name, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Description, - - [Parameter(Mandatory, ParameterSetName = 'PredefinedType')] - [String] - [ValidateSet('Path', 'Url', 'String', 'Integer', 'Float')] - $Type, - - [Parameter(Mandatory, ParameterSetName = 'TypeOverride')] - [String] - [ValidateNotNullOrEmpty()] - $TypeOverride, - - [Parameter(Mandatory)] - [String] - [ValidateNotNullOrEmpty()] - $Default - ) - - if ($Name -notmatch '^(?-i:[0-9a-z_]+)$') { - Write-Error "Input argument names must be lowercase and optionally, contain underscores. Input argument name supplied: $Name" - return - } - - $AtomicInputArgInstance = [AtomicInputArgument]::new() - - $AtomicInputArgInstance.description = $Description - $AtomicInputArgInstance.default = $Default - - if ($Type) { - $AtomicInputArgInstance.type = $Type - - # Validate input argument types when it makes sense to do so. - switch ($Type) { - 'Url' { - if (-not [Uri]::IsWellFormedUriString($Type, [UriKind]::RelativeOrAbsolute)) { - Write-Warning "The specified Url is not properly formatted: $Type" - } - } - - 'Integer' { - if (-not [Int]::TryParse($Type, [Ref] $null)) { - Write-Warning "The specified Int is not properly formatted: $Type" - } - } - - 'Float' { - if (-not [Double]::TryParse($Type, [Ref] $null)) { - Write-Warning "The specified Float is not properly formatted: $Type" - } - } - - # The following supported data types do not make sense to validate: - # 'Path' { } - # 'String' { } - } - } - else { - $AtomicInputArgInstance.type = $TypeOverride - } - - # Add Name as a note property since the Name property cannot be defined in the AtomicInputArgument - # since it must be stored as a hashtable where the name is the key. Fortunately, ConvertTo-Yaml - # won't convert note properties during serialization. - $InputArgument = Add-Member -InputObject $AtomicInputArgInstance -MemberType NoteProperty -Name Name -Value $Name -PassThru - - return $InputArgument -} +# The class definitions that these functions rely upon are located in Private\AtomicClassSchema.ps1 + +function New-AtomicTechnique { + <# +.SYNOPSIS + +Specifies a new atomic red team technique. The output of this function is designed to be piped directly to ConvertTo-Yaml, eliminating the need to work with YAML directly. + +.PARAMETER AttackTechnique + +Specifies one or more MITRE ATT&CK techniques that to which this technique applies. Per MITRE naming convention, an attack technique should start with "T" followed by a 4 digit number. The MITRE sub-technique format is also supported: TNNNN.NNN + +.PARAMETER DisplayName + +Specifies the name of the technique as defined by ATT&CK. Example: 'Audio Capture' + +.PARAMETER AtomicTests + +Specifies one or more atomic tests. Atomic tests are created using the New-AtomicTest function. + +.EXAMPLE + +$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' +$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' + +$AtomicTest1 = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} +C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} +'@ + +# Note: the input arguments are identical for atomic test #1 and #2 +$AtomicTest2 = New-AtomicTest -Name 'InstallUtil GetHelp method call' -Description 'Executes the Help property' -SupportedPlatforms Windows -InputArguments @($InputArg1, $InputArg2) -ExecutorType CommandPrompt -ExecutorCommand @' +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} +C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /? #{filename} +'@ + +$AtomicTechnique = New-AtomicTechnique -AttackTechnique T1118 -DisplayName InstallUtil -AtomicTests $AtomicTest1, $AtomicTest2 + +# Everything is ready to convert to YAML now! +$AtomicTechnique | ConvertTo-Yaml | Out-File T1118.yaml + +.OUTPUTS + +AtomicTechnique + +Outputs an object representing an atomic technique. + +The output of New-AtomicTechnique is designed to be piped to ConvertTo-Yaml. +#> + + [CmdletBinding()] + [OutputType([AtomicTechnique])] + param ( + [Parameter(Mandatory)] + [String[]] + $AttackTechnique, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $DisplayName, + + [Parameter(Mandatory)] + [AtomicTest[]] + [ValidateNotNull()] + $AtomicTests + ) + + $AtomicTechniqueInstance = [AtomicTechnique]::new() + + foreach ($Technique in $AttackTechnique) { + # Attack techniques should match the MITRE ATT&CK [sub-]technique format. + # This is not a requirement so just warn the user. + if ($Technique -notmatch '^(?-i:T\d{4}(\.\d{3}){0,1})$') { + Write-Warning "The following supplied attack technique does not start with 'T' followed by a four digit number: $Technique" + } + } + + $AtomicTechniqueInstance.attack_technique = $AttackTechnique + $AtomicTechniqueInstance.display_name = $DisplayName + $AtomicTechniqueInstance.atomic_tests = $AtomicTests + + return $AtomicTechniqueInstance +} + +function New-AtomicTest { + <# +.SYNOPSIS + +Specifies an atomic test. + +.PARAMETER Name + +Specifies the name of the test that indicates how it tests the technique. + +.PARAMETER Description + +Specifies a long form description of the test. Markdown is supported. + +.PARAMETER SupportedPlatforms + +Specifies the OS/platform on which the test is designed to run. The following platforms are currently supported: Windows, macOS, Linux. + +A single test can support multiple platforms. + +.PARAMETER ExecutorType + +Specifies the the framework or application in which the test should be executed. The following executor types are currently supported: CommandPrompt, Sh, Bash, PowerShell. + +- CommandPrompt: The Windows Command Prompt, aka cmd.exe + Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by cmd.exe. + +- PowerShell: PowerShell + Requires the -ExecutorCommand argument to contain a multi-line PowerShell scriptblock that will be preprocessed and then executed by powershell.exe + +- Sh: Linux's bourne shell + Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by sh. + +- Bash: Linux's bourne again shell + Requires the -ExecutorCommand argument to contain a multi-line script that will be preprocessed and then executed by bash. + +.PARAMETER ExecutorElevationRequired + +Specifies that the test must run with elevated privileges. + +.PARAMETER ExecutorSteps + +Specifies a manual list of steps to execute. This should be specified when the atomic test cannot be executed in an automated fashion, for example when GUI steps are involved that cannot be automated. + +.PARAMETER ExecutorCommand + +Specifies the command to execute as part of the atomic test. This should be specified when the atomic test can be executed in an automated fashion. + +The -ExecutorType specified will dictate the command specified, e.g. PowerShell scriptblock code when the "PowerShell" ExecutorType is specified. + +.PARAMETER ExecutorCleanupCommand + +Specifies the command to execute if there are any artifacts that need to be cleaned up. + +.PARAMETER InputArguments + +Specifies one or more input arguments. Input arguments are defined using the New-AtomicTestInputArgument function. + +.PARAMETER DependencyExecutorType + +Specifies an override execution type for dependencies. By default, dependencies are executed using the framework specified in -ExecutorType. + +In most cases, 'PowerShell' is specified as a dependency executor type when 'CommandPrompt' is specified as an executor type. + +.PARAMETER Dependencies + +Specifies one or more dependencies. Dependencies are defined using the New-AtomicTestDependency function. + +.EXAMPLE + +$InputArg1 = New-AtomicTestInputArgument -Name filename -Description 'location of the payload' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.dll' +$InputArg2 = New-AtomicTestInputArgument -Name source -Description 'location of the source code to compile' -Type Path -Default 'PathToAtomicsFolder\T1118\src\T1118.cs' + +$AtomicTest = New-AtomicTest -Name 'InstallUtil uninstall method call' -Description 'Executes the Uninstall Method' -SupportedPlatforms Windows -InputArguments $InputArg1, $InputArg2 -ExecutorType CommandPrompt -ExecutorCommand @' +C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:#{filename} #{source} +C:\Windows\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U #{filename} +'@ + +.OUTPUTS + +AtomicTest + +Outputs an object representing an atomic test. This object is intended to be supplied to the New-AtomicTechnique -AtomicTests parameter. + +The output of New-AtomicTest can be piped to ConvertTo-Yaml. The resulting output can be added to an existing atomic technique YAML doc. +#> + + [CmdletBinding(DefaultParameterSetName = 'AutomatedExecutor')] + [OutputType([AtomicTest])] + param ( + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Name, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Description, + + [Parameter(Mandatory)] + [String[]] + [ValidateSet('Windows', 'macOS', 'Linux')] + $SupportedPlatforms, + + [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] + [String] + [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] + $ExecutorType, + + [Switch] + $ExecutorElevationRequired, + + [Parameter(Mandatory, ParameterSetName = 'ManualExecutor')] + [String] + [ValidateNotNullOrEmpty()] + $ExecutorSteps, + + [Parameter(Mandatory, ParameterSetName = 'AutomatedExecutor')] + [String] + [ValidateNotNullOrEmpty()] + $ExecutorCommand, + + [String] + [ValidateNotNullOrEmpty()] + $ExecutorCleanupCommand, + + [AtomicInputArgument[]] + $InputArguments, + + [String] + [ValidateSet('CommandPrompt', 'Sh', 'Bash', 'PowerShell')] + $DependencyExecutorType, + + [AtomicDependency[]] + $Dependencies + ) + + $AtomicTestInstance = [AtomicTest]::new() + + $AtomicTestInstance.name = $Name + $AtomicTestInstance.description = $Description + $AtomicTestInstance.supported_platforms = $SupportedPlatforms | ForEach-Object { $_.ToLower() } + + $StringsWithPotentialInputArgs = New-Object -TypeName 'System.Collections.Generic.List`1[String]' + + switch ($PSCmdlet.ParameterSetName) { + 'AutomatedExecutor' { + $ExecutorInstance = [AtomicExecutorDefault]::new() + $ExecutorInstance.command = $ExecutorCommand + $StringsWithPotentialInputArgs.Add($ExecutorCommand) + } + + 'ManualExecutor' { + $ExecutorInstance = [AtomicExecutorManual]::new() + $ExecutorInstance.steps = $ExecutorSteps + $StringsWithPotentialInputArgs.Add($ExecutorSteps) + } + } + + switch ($ExecutorType) { + 'CommandPrompt' { $ExecutorInstance.name = 'command_prompt' } + default { $ExecutorInstance.name = $ExecutorType.ToLower() } + } + + if ($ExecutorCleanupCommand) { + $ExecutorInstance.cleanup_command = $ExecutorCleanupCommand + $StringsWithPotentialInputArgs.Add($ExecutorCleanupCommand) + } + + if ($ExecutorElevationRequired) { $ExecutorInstance.elevation_required = $True } + + if ($Dependencies) { + foreach ($Dependency in $Dependencies) { + $StringsWithPotentialInputArgs.Add($Dependency.description) + $StringsWithPotentialInputArgs.Add($Dependency.prereq_command) + $StringsWithPotentialInputArgs.Add($Dependency.get_prereq_command) + } + } + + if ($DependencyExecutorType) { + switch ($DependencyExecutorType) { + 'CommandPrompt' { $AtomicTestInstance.dependency_executor_name = 'command_prompt' } + default { $AtomicTestInstance.dependency_executor_name = $DependencyExecutorType.ToLower() } + } + } $AtomicTestInstance.dependencies = $Dependencies + + [Hashtable] $InputArgHashtable = @{ } + + if ($InputArguments.Count) { + # Determine if any of the input argument names repeat. They must be unique. + $InputArguments | Group-Object -Property Name | Where-Object { $_.Count -gt 1 } | ForEach-Object { + Write-Error "There are $($_.Count) instances of the $($_.Name) input argument. Input argument names must be unique." + return + } + + # Convert each input argument to a hashtable where the key is the Name property. + + foreach ($InputArg in $InputArguments) { + # Create a copy of the passed input argument that doesn't include the "Name" property. + # Passing in a shallow copy adversely affects YAML serialization for some reason. + $NewInputArg = [AtomicInputArgument]::new() + $NewInputArg.default = $InputArg.default + $NewInputArg.description = $InputArg.description + $NewInputArg.type = $InputArg.type + + $InputArgHashtable[$InputArg.Name] = $NewInputArg + } + + $AtomicTestInstance.input_arguments = $InputArgHashtable + } + + # Extract all specified input arguments from executor and any dependencies. + $Regex = [Regex] '#\{(?[^}]+)\}' + [String[]] $InputArgumentNamesFromExecutor = $StringsWithPotentialInputArgs | + ForEach-Object { $Regex.Matches($_) } | + Select-Object -ExpandProperty Groups | + Where-Object { $_.Name -eq 'ArgName' } | + Select-Object -ExpandProperty Value | + Sort-Object -Unique + + + # Validate that all executor arguments are defined as input arguments + if ($InputArgumentNamesFromExecutor.Count) { + $InputArgumentNamesFromExecutor | ForEach-Object { + if ($InputArgHashtable.Keys -notcontains $_) { + Write-Error "The following input argument was specified but is not defined: '$_'" + return + } + } + } + + # Validate that all defined input args are utilized at least once in the executor. + if ($InputArgHashtable.Keys.Count) { + $InputArgHashtable.Keys | ForEach-Object { + if ($InputArgumentNamesFromExecutor -notcontains $_) { + # Write a warning since this scenario is not considered a breaking change + Write-Warning "The following input argument is defined but not utilized: '$_'." + } + } + } + + $AtomicTestInstance.executor = $ExecutorInstance + + return $AtomicTestInstance +} + +function New-AtomicTestDependency { + <# +.SYNOPSIS + +Specifies a new dependency that must be met prior to execution of an atomic test. + +.PARAMETER Description + +Specifies a human-readable description of the dependency. This should be worded in the following form: SOMETHING must SOMETHING + +.PARAMETER PrereqCommand + +Specifies commands to check if prerequisites for running this test are met. + +For the "command_prompt" executor, if any command returns a non-zero exit code, the pre-requisites are not met. + +For the "powershell" executor, all commands are run as a script block and the script block must return 0 for success. + +.PARAMETER GetPrereqCommand + +Specifies commands to meet this prerequisite or a message describing how to meet this prereq + +More specifically, this command is designed to satisfy either of the following conditions: + +1) If a prerequisite is not met, perform steps necessary to satify the prerequisite. Such a command should be implemented when prerequisites can be satisfied in an automated fashion. +2) If a prerequisite is not met, inform the user what the steps are to satisfy the prerequisite. Such a message should be presented to the user in the case that prerequisites cannot be satisfied in an automated fashion. + +.EXAMPLE + +$Dependency = New-AtomicTestDependency -Description 'Folder to zip must exist (#{input_file_folder})' -PrereqCommand 'test -e #{input_file_folder}' -GetPrereqCommand 'echo Please set input_file_folder argument to a folder that exists' + +.OUTPUTS + +AtomicDependency + +Outputs an object representing an atomic test dependency. This object is intended to be supplied to the New-AtomicTest -Dependencies parameter. + +Note: due to a bug in PowerShell classes, the get_prereq_command property will not display by default. If all fields must be explicitly displayed, they can be viewed by piping output to "Select-Object description, prereq_command, get_prereq_command". +#> + + [CmdletBinding()] + [OutputType([AtomicDependency])] + param ( + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Description, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $PrereqCommand, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $GetPrereqCommand + ) + + $DependencyInstance = [AtomicDependency]::new() + + $DependencyInstance.description = $Description + $DependencyInstance.prereq_command = $PrereqCommand + $DependencyInstance.get_prereq_command = $GetPrereqCommand + + return $DependencyInstance +} + +function New-AtomicTestInputArgument { + <# +.SYNOPSIS + +Specifies an input to an atomic test that is a requirement to run the test (think of these like function arguments). + +.PARAMETER Name + +Specifies the name of the input argument. This must be lowercase and can optionally, have underscores. The input argument name is what is specified as arguments within executors and dependencies. + +.PARAMETER Description + +Specifies a human-readable description of the input argument. + +.PARAMETER Type + +Specifies the data type of the input argument. The following data types are supported: Path, Url, String, Integer, Float. If an alternative data type must be supported, use the -TypeOverride parameter. + +.PARAMETER TypeOverride + +Specifies an unsupported input argument data type. Specifying this parameter should not be common. + +.PARAMETER Default + +Specifies a default value for an input argument if one is not specified via the Invoke-AtomicTest -InputArgs parameter. + +.EXAMPLE + +$AtomicInputArgument = New-AtomicTestInputArgument -Name 'rar_exe' -Type Path -Description 'The RAR executable from Winrar' -Default '%programfiles%\WinRAR\Rar.exe' + +.OUTPUTS + +AtomicInputArgument + +Outputs an object representing an atomic test input argument. This object is intended to be supplied to the New-AtomicTest -InputArguments parameter. +#> + + [CmdletBinding(DefaultParameterSetName = 'PredefinedType')] + [OutputType([AtomicInputArgument])] + param ( + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Name, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Description, + + [Parameter(Mandatory, ParameterSetName = 'PredefinedType')] + [String] + [ValidateSet('Path', 'Url', 'String', 'Integer', 'Float')] + $Type, + + [Parameter(Mandatory, ParameterSetName = 'TypeOverride')] + [String] + [ValidateNotNullOrEmpty()] + $TypeOverride, + + [Parameter(Mandatory)] + [String] + [ValidateNotNullOrEmpty()] + $Default + ) + + if ($Name -notmatch '^(?-i:[0-9a-z_]+)$') { + Write-Error "Input argument names must be lowercase and optionally, contain underscores. Input argument name supplied: $Name" + return + } + + $AtomicInputArgInstance = [AtomicInputArgument]::new() + + $AtomicInputArgInstance.description = $Description + $AtomicInputArgInstance.default = $Default + + if ($Type) { + $AtomicInputArgInstance.type = $Type + + # Validate input argument types when it makes sense to do so. + switch ($Type) { + 'Url' { + if (-not [Uri]::IsWellFormedUriString($Type, [UriKind]::RelativeOrAbsolute)) { + Write-Warning "The specified Url is not properly formatted: $Type" + } + } + + 'Integer' { + if (-not [Int]::TryParse($Type, [Ref] $null)) { + Write-Warning "The specified Int is not properly formatted: $Type" + } + } + + 'Float' { + if (-not [Double]::TryParse($Type, [Ref] $null)) { + Write-Warning "The specified Float is not properly formatted: $Type" + } + } + + # The following supported data types do not make sense to validate: + # 'Path' { } + # 'String' { } + } + } + else { + $AtomicInputArgInstance.type = $TypeOverride + } + + # Add Name as a note property since the Name property cannot be defined in the AtomicInputArgument + # since it must be stored as a hashtable where the name is the key. Fortunately, ConvertTo-Yaml + # won't convert note properties during serialization. + $InputArgument = Add-Member -InputObject $AtomicInputArgInstance -MemberType NoteProperty -Name Name -Value $Name -PassThru + + return $InputArgument +} diff --git a/Public/Start-AtomicGUI.ps1 b/Public/Start-AtomicGUI.ps1 index bde1fe8..ca1b506 100644 --- a/Public/Start-AtomicGUI.ps1 +++ b/Public/Start-AtomicGUI.ps1 @@ -1,4 +1,4 @@ -function Start-AtomicGUI { +function Start-AtomicGUI { param ( [Int] $port = 8487 ) diff --git a/Public/Syslog-ExecutionLogger.psm1 b/Public/Syslog-ExecutionLogger.psm1 index 29e3738..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) { } diff --git a/Public/WinEvent-ExecutionLogger.psm1 b/Public/WinEvent-ExecutionLogger.psm1 index 238e05b..0ed9a1f 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" } diff --git a/Public/config.ps1 b/Public/config.ps1 index 17de77b..4ae3617 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 diff --git a/docker/setup.ps1 b/docker/setup.ps1 index ac3208a..b70f7f0 100644 --- a/docker/setup.ps1 +++ b/docker/setup.ps1 @@ -13,4 +13,4 @@ Write-Output @" Import-Module "$ARTPath/invoke-atomicredteam/Invoke-AtomicRedTeam.psd1" -Force; `$PSDefaultParameterValues`["Invoke-AtomicTest:PathToAtomicsFolder"] = "$ARTPath/atomics"; `$PSDefaultParameterValues`["Invoke-AtomicTest:ExecutionLogPath"]="1.csv"; -"@ > $PROFILE +"@ > $PROFILE \ No newline at end of file diff --git a/sandbox/setupsandbox.ps1 b/sandbox/setupsandbox.ps1 index f991cfe..758ba0e 100644 --- a/sandbox/setupsandbox.ps1 +++ b/sandbox/setupsandbox.ps1 @@ -1,4 +1,4 @@ -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" From 901b1d28958bd83c6303241289043e3cabcdb85f Mon Sep 17 00:00:00 2001 From: Hare Sudhan Date: Thu, 28 Dec 2023 08:29:57 -0500 Subject: [PATCH 4/4] Fixing BOM --- Invoke-AtomicRedTeam.psd1 | 2 +- Invoke-AtomicRedTeam.psm1 | 2 +- PSScriptAnalyzerSettings.psd1 | 2 +- Private/AtomicClassSchema.ps1 | 2 +- Private/Get-PrereqExecutor.ps1 | 4 ++-- Private/Get-TargetInfo.ps1 | 4 ++-- Private/Invoke-ExecuteCommand.ps1 | 2 +- Private/Invoke-Process.ps1 | 2 +- Private/Show-Details.ps1 | 4 ++-- Private/Write-KeyValue.ps1 | 2 +- Private/Write-PrereqResults.ps1 | 2 +- Public/Attire-ExecutionLogger.psm1 | 2 +- Public/Default-ExecutionLogger.psm1 | 4 ++-- Public/Get-AtomicTechnique.ps1 | 2 +- Public/Get-PreferredIPAddress.ps1 | 2 +- Public/Invoke-AtomicRunner.ps1 | 4 ++-- Public/Invoke-AtomicTest.ps1 | 4 ++-- Public/Invoke-FetchFromZip.ps1 | 2 +- Public/Invoke-KickoffAtomicRunner.ps1 | 4 ++-- Public/Invoke-RunnerScheduleMethods.ps1 | 4 ++-- Public/Invoke-SetupAtomicRunner.ps1 | 2 +- Public/Invoke-WebRequestVerifyHash.ps1 | 2 +- Public/New-Atomic.ps1 | 2 +- Public/Start-AtomicGUI.ps1 | 4 ++-- Public/Syslog-ExecutionLogger.psm1 | 2 +- Public/WinEvent-ExecutionLogger.psm1 | 2 +- Public/config.ps1 | 4 ++-- docker/setup.ps1 | 2 +- sandbox/setupsandbox.ps1 | 4 ++-- 29 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Invoke-AtomicRedTeam.psd1 b/Invoke-AtomicRedTeam.psd1 index 9af0f28..87b5067 100644 --- a/Invoke-AtomicRedTeam.psd1 +++ b/Invoke-AtomicRedTeam.psd1 @@ -1,4 +1,4 @@ -@{ +@{ # Script module or binary module file associated with this manifest. RootModule = 'Invoke-AtomicRedTeam.psm1' diff --git a/Invoke-AtomicRedTeam.psm1 b/Invoke-AtomicRedTeam.psm1 index 9ba5c34..546874f 100644 --- a/Invoke-AtomicRedTeam.psm1 +++ b/Invoke-AtomicRedTeam.psm1 @@ -20,4 +20,4 @@ Foreach ($import in @($Public + $Private)) { Catch { Write-Error -Message "Failed to import function $($import.fullname): $_" } -} \ No newline at end of file +} diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 index 0280d7e..089d453 100644 --- a/PSScriptAnalyzerSettings.psd1 +++ b/PSScriptAnalyzerSettings.psd1 @@ -2,4 +2,4 @@ @{ ExcludeRules=@('PSUseSingularNouns', 'PSAvoidUsingWriteHost') -} \ No newline at end of file +} diff --git a/Private/AtomicClassSchema.ps1 b/Private/AtomicClassSchema.ps1 index 543ef56..f106883 100644 --- a/Private/AtomicClassSchema.ps1 +++ b/Private/AtomicClassSchema.ps1 @@ -52,4 +52,4 @@ class AtomicTechnique { [String[]] $attack_technique [String] $display_name [AtomicTest[]] $atomic_tests -} \ No newline at end of file +} diff --git a/Private/Get-PrereqExecutor.ps1 b/Private/Get-PrereqExecutor.ps1 index 1f5c119..225947c 100644 --- a/Private/Get-PrereqExecutor.ps1 +++ b/Private/Get-PrereqExecutor.ps1 @@ -1,5 +1,5 @@ -function Get-PrereqExecutor ($test) { +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 e07d545..57df27d 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 @@ -40,4 +40,4 @@ } $targetPlatform, $isElevated, $tmpDir, $targetHostname, $targetUser -} \ No newline at end of file +} diff --git a/Private/Invoke-ExecuteCommand.ps1 b/Private/Invoke-ExecuteCommand.ps1 index 0d93f36..13686a6 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() diff --git a/Private/Invoke-Process.ps1 b/Private/Invoke-Process.ps1 index 7d19a3d..5940204 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()] diff --git a/Private/Show-Details.ps1 b/Private/Show-Details.ps1 index de22bb7..f239169 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 '(? $PROFILE \ No newline at end of file +"@ > $PROFILE diff --git a/sandbox/setupsandbox.ps1 b/sandbox/setupsandbox.ps1 index 758ba0e..cdca9a0 100644 --- a/sandbox/setupsandbox.ps1 +++ b/sandbox/setupsandbox.ps1 @@ -1,4 +1,4 @@ -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" @@ -15,4 +15,4 @@ Import-Module "$ARTPath/invoke-atomicredteam/Invoke-AtomicRedTeam.psd1" -Force; . $PROFILE -Set-Location C:\AtomicRedTeam \ No newline at end of file +Set-Location C:\AtomicRedTeam