diff --git a/.github/workflows/lint-action.yml b/.github/workflows/lint-action.yml new file mode 100644 index 0000000..d2419ec --- /dev/null +++ b/.github/workflows/lint-action.yml @@ -0,0 +1,20 @@ +name: Lint Atomic Red Team +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Lint Atomic Red Team + uses: ./ + with: + cleanup: true + remote: false + adversary-emulation: true + list-of-atomics: "./koko.csv" + logging_module: File + execution_log_path: "" + technique: T1033 + test-numbers: 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..80b2b9c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "atomic-red-team"] + path = atomic-red-team + url = https://github.com/redcanaryco/atomic-red-team.git diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..993bfd5 --- /dev/null +++ b/action.yml @@ -0,0 +1,105 @@ +name: 'Invoke-AtomicRedTeam Action' +description: 'Run local Atomic Red Team tests and adversary emulation' +author: 'smolse' + +inputs: + technique: + description: 'ID of the technique to execute' + required: false + test-names: + description: 'Test names to execute' + required: false + test-numbers: + description: 'Test numbers to execute' + required: false + test-guids: + description: 'Test GUIDs to execute' + required: false + get-prereqs: + description: 'Get prerequisites for tests execution' + required: false + default: 'true' + cleanup: + description: 'Clean up after tests execution' + required: false + default: 'true' + adversary-emulation: + description: 'Execute adversary emulation based on a schedule file' + required: false + default: 'false' + list-of-atomics: + description: 'Path to adversary emulation CSV schedule file' + required: false + private-config: + description: 'Path to the private configuration file' + required: false + +runs: + using: 'composite' + steps: + + - name: Validate action inputs + shell: pwsh + run: | + Write-Output '${{ toJson(inputs) }}' + if ('${{ inputs.adversary-emulation }}' -ne 'true' -and '${{ inputs.technique }}' -eq '') { + Write-Output "::error::Technique ID must be provided for atomic test execution" + exit 1 + } + + if ('${{ inputs.adversary-emulation }}' -eq 'true' -and '${{ inputs.list-of-atomics }}' -eq '') { + Write-Output "::error::List of atomics must be provided when adversary emulation is enabled" + exit 1 + } + + - name: Install Invoke-AtomicRedTeam + shell: pwsh + run: | + if (-not (Get-Module -ListAvailable -Name Invoke-AtomicRedTeam)) { + Install-Module -Name Invoke-AtomicRedTeam -Scope CurrentUser -Force + Write-Output "::notice::Invoke-AtomicRedTeam module has been installed" + } else { + Write-Output "::notice::Invoke-AtomicRedTeam module is already installed" + } + + - name: Install atomics folder + shell: pwsh + run: | + $atomicsPath = if ($IsWindows) { 'C:\AtomicRedTeam\atomics' } else { "$env:HOME/AtomicRedTeam/atomics" } + if (-not (Test-Path $atomicsPath)) { + ./scripts/Install-AtomicsFolder.ps1; Install-AtomicsFolder + Write-Output "::notice::Atomics folder has been installed" + } else { + Write-Output "::notice::Atomics folder is already installed" + } + working-directory: ${{ github.action_path }} + + - name: Execute atomic technique + if: inputs.adversary-emulation != 'true' + shell: pwsh + run: | + $Params = @{} + if ('${{ inputs.test-names }}' -ne '') { $Params.TestNames = '${{ inputs.test-names }}' } + if ('${{ inputs.test-numbers }}' -ne '') { $Params.TestNumbers = '${{ inputs.test-numbers }}' } + if ('${{ inputs.test-guids }}' -ne '') { $Params.TestGuids = '${{ inputs.test-guids }}' } + if ('${{ inputs.get-prereqs }}' -eq 'true') { $Params.GetPrereqs = $true } + if ('${{ inputs.cleanup }}' -eq 'true') { $Params.Cleanup = $true } + + Invoke-AtomicTest ${{ inputs.technique }} @Params + + - name: Execute adversary emulation + if: inputs.adversary-emulation == 'true' + shell: pwsh + run: | + ls -la ~ + $Params = @{ + ListOfAtomics = '${{ inputs.list-of-atomics }}' + } + if ('${{ inputs.get-prereqs }}' -eq 'true') { $Params.GetPrereqs = $true } + if ('${{ inputs.cleanup }}' -eq 'true') { $Params.Cleanup = $true } + + Invoke-AtomicRunner @Params + +branding: + icon: 'shield' + color: 'blue' diff --git a/atomic-red-team b/atomic-red-team new file mode 160000 index 0000000..12afd8e --- /dev/null +++ b/atomic-red-team @@ -0,0 +1 @@ +Subproject commit 12afd8e372da603f93d5f3ba51477d6fcd7965dd diff --git a/koko.csv b/koko.csv new file mode 100644 index 0000000..2070279 --- /dev/null +++ b/koko.csv @@ -0,0 +1,2 @@ +Order,Technique,TestName,auto_generated_guid,supported_platforms,TimeoutSeconds,InputArgs,AtomicsFolder,enabled,notes +1,T1003.002,Scheduled task Local,42f53695-ad4a-4546-abb6-7d837f644a71,linux,120,,Public,TRUE,Emulation converted from https://github.com/Atomics-on-A-Friday/Emulation-Tools \ No newline at end of file diff --git a/scripts/Install-AtomicsFolder.ps1 b/scripts/Install-AtomicsFolder.ps1 new file mode 100644 index 0000000..31210a5 --- /dev/null +++ b/scripts/Install-AtomicsFolder.ps1 @@ -0,0 +1,145 @@ +function Install-AtomicsFolder { + + <# + .SYNOPSIS + + This is a simple script to download the atttack definitions in the "atomics" folder of the Red Canary Atomic Red Team project. + + License: MIT License + Required Dependencies: powershell-yaml + Optional Dependencies: None + + .PARAMETER DownloadPath + + Specifies the desired path to download atomics zip archive to. + + .PARAMETER InstallPath + + Specifies the desired path for where to unzip the atomics folder. + + .PARAMETER Force + + Delete the existing atomics folder before installation if it exists. + + .EXAMPLE + + Install atomics folder + PS> Install-AtomicsFolder.ps1 + + .NOTES + + Use the '-Verbose' option to print detailed information. + +#> + [CmdletBinding()] + Param( + [Parameter(Mandatory = $False, Position = 0)] + [string]$InstallPath = $( if ($IsLinux -or $IsMacOS) { $Env:HOME + "/AtomicRedTeam" } else { $env:HOMEDRIVE + "\AtomicRedTeam" }), + + [Parameter(Mandatory = $False, Position = 1)] + [string]$DownloadPath = $InstallPath, + + [Parameter(Mandatory = $False, Position = 2)] + [string]$RepoOwner = "redcanaryco", + + [Parameter(Mandatory = $False, Position = 3)] + [string]$Branch = "master", + + [Parameter(Mandatory = $False)] + [switch]$Force = $False, # delete the existing install directory and reinstall + + [Parameter(Mandatory = $False)] + [switch]$NoPayloads = $False + ) + Try { + $InstallPathwAtomics = Join-Path $InstallPath "atomics" + if ($Force -or -Not (Test-Path -Path $InstallPathwAtomics )) { + write-verbose "Directory Creation" + if ($Force) { + Try { + if ((Test-Path $InstallPathwAtomics) -and (-not $NoPayloads)) { Remove-Item -Path $InstallPathwAtomics -Recurse -Force -ErrorAction Stop | Out-Null } + } + Catch { + Write-Host -ForegroundColor Red $_.Exception.Message + return + } + } + if (-not (Test-Path $InstallPath)) { New-Item -ItemType directory -Path $InstallPath | Out-Null } + + $url = "https://github.com/$RepoOwner/atomic-red-team/archive/$Branch.zip" + $path = Join-Path $DownloadPath "$Branch.zip" + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + write-verbose "Beginning download of atomics folder from Github" + + # disable progress bar for faster performances + $ProgressPreference_backup = $global:ProgressPreference + $global:ProgressPreference = "SilentlyContinue" + + if ($NoPayloads) { + # download zip to memory and only extract atomic yaml files + # load ZIP methods + Write-Host -ForegroundColor Yellow "Reading the Atomic Red Team repo into a memory stream. This could take up to 3 minutes." + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression') | Out-Null + + # read github zip archive into memory + $ms = New-Object IO.MemoryStream + [Net.ServicePointManager]::SecurityProtocol = ([Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12) + + Add-Type -AssemblyName System.Net.Http + $httpClient = New-Object System.Net.Http.HttpClient + $httpClient.Timeout = New-Object System.TimeSpan(0, 3, 0) + $response = $httpClient.GetAsync($url).Result + $response.Content.CopyToAsync($ms).Wait() + $zip = New-Object System.IO.Compression.ZipArchive($ms) + + $Filter = '*.yaml' + + # ensure the output folder exists + $exists = Test-Path -Path $InstallPathwAtomics + if ($exists -eq $false) { + $null = New-Item -Path $InstallPathwAtomics -ItemType Directory -Force + } + + # find all files in ZIP that match the filter (i.e. file extension) + $zip.Entries | + Where-Object { + ($_.FullName -like $Filter) ` + -and (($_.FullName | split-path | split-path -Leaf) -eq [System.IO.Path]::GetFileNameWithoutExtension($_.Name)) ` + -and ($_.FullName | split-path | split-path | split-path -Leaf) -eq "atomics" + } | + ForEach-Object { + # extract the selected items from the ZIP archive + # and copy them to the out folder + $dstDir = Join-Path $InstallPathwAtomics ($_.FullName | split-path | split-path -Leaf) + New-Item -ItemType Directory -Force -Path $dstDir | Out-Null + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, (Join-Path $dstDir $_.Name), $true) + } + $zip.Dispose() + } + else { + Invoke-WebRequest $url -OutFile $path + + write-verbose "Extracting ART to $InstallPath" + $zipDest = Join-Path "$DownloadPath" "tmp" + Microsoft.PowerShell.Archive\Expand-Archive -LiteralPath $path -DestinationPath "$zipDest" -Force:$Force + $atomicsFolderUnzipped = Join-Path (Join-Path $zipDest "atomic-red-team-$Branch") "atomics" + Move-Item $atomicsFolderUnzipped $InstallPath + Remove-Item $zipDest -Recurse -Force + Remove-Item $path + } + + # restore progress bar preferences + $global:ProgressPreference = $ProgressPreference_backup + } + else { + Write-Host -ForegroundColor Yellow "An atomics folder already exists at $InstallPathwAtomics. No changes were made." + Write-Host -ForegroundColor Cyan "Try the install again with the '-Force' parameter if you want to delete the existing installion and re-install." + Write-Host -ForegroundColor Red "Warning: All files within the atomics folder ($InstallPathwAtomics) will be deleted when using the '-Force' parameter." + } + } + Catch { + Write-Error "Installation of the AtomicsFolder Failed." + Write-Host $_.Exception.Message`n + } +}