SCENARIO
Proactive threat hunting is essential for identifying sophisticated threats that evade automated detection systems. This script automates the process of querying SentinelOne's Deep Visibility telemetry to search for Indicators of Compromise (IOCs), suspicious behavior patterns, and malicious activity across your endpoint estate.
Use this procedure when:
- Investigating specific IOCs from threat intelligence feeds (file hashes, IP addresses, domains)
- Hunting for known attack patterns (credential dumping, lateral movement, persistence mechanisms)
- Responding to security alerts that require historical endpoint analysis
- Conducting proactive threat hunts based on MITRE ATT&CK techniques
- Performing incident response forensics across multiple endpoints
- Validating detection coverage for specific threat actors or malware families
The script leverages SentinelOne's Deep Visibility SQL-like query language to search endpoint telemetry (process execution, network connections, file operations, registry modifications, DNS queries) and categorizes findings by threat severity. It generates comprehensive reports in JSON and CSV formats, with optional automated response actions (network isolation, process termination, file quarantine).
REQUIREMENTS & ASSUMPTIONS
Software Requirements:
- PowerShell 5.1 or higher (PowerShell 7+ recommended)
- Active SentinelOne Singularity platform deployment
- SentinelOne API token with
DeepVisibility.Readpermission (minimum) - SentinelOne API token with
RemoteOpspermission (if using-AutoResponsefor mitigation actions) - Internet connectivity to SentinelOne management console
Access Requirements:
- SentinelOne console access to generate API tokens
- User account with appropriate role (Analyst, SOC, Admin)
- Network path to SentinelOne management console URL (typically
https://[tenant].sentinelone.net)
System Requirements:
C:\BINdirectory for log storage (created automatically if missing)C:\BIN\S1-ThreatHuntdirectory for report exports (created automatically by default)- Sufficient disk space for query results (large hunts may generate multi-MB JSON/CSV files)
Assumptions:
- SentinelOne agents are actively reporting telemetry to the management console
- Deep Visibility feature is enabled on your SentinelOne license
- Endpoint agents have Deep Visibility data retention configured (typically 14-90 days)
- You have identified specific IOCs or threat patterns to hunt for
- Time range for queries is within the data retention window
Security Considerations:
- API tokens are sensitive credentials with broad endpoint access - store securely
- The script prompts for API token via
SecureStringif not provided as parameter - API tokens are cleared from memory after script execution (
$token = $null+ garbage collection) - Automated response actions (
-AutoResponse) require explicit confirmation before execution - Network isolation and process termination are disruptive - verify affected endpoints before confirming
- All query activity and response actions are logged to
C:\BIN\LOGS-\<date\>-S1-ThreatHunt.log
PROCESS
-
Obtain SentinelOne API Token:
- Log in to your SentinelOne management console
- Navigate to Settings > Users > [Your User] > API Token
- Click Generate API Token and copy the token securely
- Ensure the token has
DeepVisibility.Readpermission (check role assignments) - If using automated response features, verify
RemoteOpspermission is also assigned
-
Identify Your Hunt Objective:
- Determine the IOC or behavior pattern you need to search for
- Choose the appropriate
QueryTypeparameter:FileHash: Search for known malicious file hashes (SHA1 or SHA256)IPAddress: Find network connections to suspicious IP addressesDomain: Search DNS queries for malicious domainsProcessPattern: Hunt for suspicious process names or command-line patternsRegistryMod: Detect registry modifications (persistence mechanisms)NetworkConnection: Find suspicious network connections on uncommon portsCustomQuery: Execute advanced custom Deep Visibility SQL queries
-
Execute the Threat Hunt:
Example: Search for Known Malicious File Hash
.\Invoke-SentinelOne-ThreatHunt.ps1 ` -ManagementUrl "https://company.sentinelone.net" ` -QueryType FileHash ` -SearchValue "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ` -TimeRange 168Example: Hunt for Suspicious PowerShell Activity (Last 24 Hours)
.\Invoke-SentinelOne-ThreatHunt.ps1 ` -ManagementUrl "https://company.sentinelone.net" ` -QueryType ProcessPattern ` -SearchValue "powershell.exe" ` -TimeRange 24Example: Search for Network Connections to Malicious IP
.\Invoke-SentinelOne-ThreatHunt.ps1 ` -ManagementUrl "https://company.sentinelone.net" ` -QueryType IPAddress ` -SearchValue "192.0.2.100" ` -TimeRange 72Example: Custom Query for Credential Access (Mimikatz Detection)
.\Invoke-SentinelOne-ThreatHunt.ps1 ` -ManagementUrl "https://company.sentinelone.net" ` -QueryType CustomQuery ` -CustomQuery "SELECT AgentName, SrcProcName, SrcProcCmdLine, TgtProcName, EventTime FROM events WHERE (SrcProcCmdLine CONTAINS 'lsass' OR SrcProcCmdLine CONTAINS 'sekurlsa') ORDER BY EventTime DESC LIMIT 1000" ` -TimeRange 48Example: Hunt with Automated Response Capability
.\Invoke-SentinelOne-ThreatHunt.ps1 ` -ManagementUrl "https://company.sentinelone.net" ` -QueryType Domain ` -SearchValue "evil-c2-domain.com" ` -TimeRange 24 ` -AutoResponse -
Review the Threat Hunt Report:
The script generates a console report showing:
- Total events found matching the query
- Severity distribution (CRITICAL, HIGH, MEDIUM, LOW)
- Top findings with agent names, process names, and command lines
- Export file locations (JSON and CSV)
Severity Categorization:
- CRITICAL: Known attack tools detected (mimikatz, psexec, bloodhound, cobalt strike, meterpreter, ransomware indicators)
- HIGH: Suspicious PowerShell encodings, credential dumping patterns, privilege escalation commands
- MEDIUM: Elevated integrity processes, SentinelOne threat indicators, suspicious network ports
- LOW: General telemetry events matching query criteria
-
Analyze Exported Results:
Navigate to the export directory (default:
C:\BIN\S1-ThreatHunt\):cd C:\BIN\S1-ThreatHuntJSON Report: Contains full structured data including severity analysis and categorized findings
Get-Content .\S1-ThreatHunt-\<timestamp\>.json | ConvertFrom-Json | Format-ListCSV Report: Flattened event data for analysis in Excel or SIEM tools
Import-Csv .\S1-ThreatHunt-\<timestamp\>.csv | Out-GridView -
Execute Response Actions (If Using
-AutoResponse):If the script detects CRITICAL or HIGH severity findings and
-AutoResponseis enabled:- Review the list of affected endpoints displayed in the console
- Choose from available response actions:
- Option 1: Network isolate affected endpoints (disconnects from network, maintains S1 cloud connectivity)
- Option 2: Kill suspicious processes on affected endpoints
- Option 3: Quarantine malicious files detected by SentinelOne
- Option 4: No automated action (manual investigation)
- Type
yesto confirm destructive actions (network isolation, process termination)
Important: Response actions are currently logged but require manual implementation via SentinelOne API. The script displays the appropriate API endpoints to use.
-
Verify Query Results in SentinelOne Console:
Cross-reference script findings with the SentinelOne console:
- Navigate to Visibility > Deep Visibility > Query in the SentinelOne console
- Paste the query displayed in the script log file
- Verify results match the script output
- Use the console for additional pivot queries and timeline analysis
-
Review Logs for Audit Trail:
All threat hunt activity is logged to:
Get-Content "C:\BIN\LOGS-$(Get-Date -Format 'yyyy-MM-dd')-S1-ThreatHunt.log"Log entries include:
- API connectivity tests
- Deep Visibility query text and query IDs
- Event counts and severity distribution
- Export file paths
- Response action requests and confirmations
- Error messages and stack traces (if failures occur)
Script detail
Core Functionality:
The script implements a complete threat hunting workflow using the SentinelOne REST API:
-
Authentication: Securely obtains API token via
SecureStringparameter or interactive prompt, converts to plaintext for API headers, and ensures cleanup after execution. -
Deep Visibility Query Construction: The
Get-DeepVisibilityQueryfunction generates SQL-like queries based onQueryType. Each query type selects relevant telemetry fields:- FileHash: Searches
SrcProcSha1,SrcProcSha256,TgtFileSha1,TgtFileSha256columns - IPAddress: Searches
DstIpandSrcIpfor network connections - Domain: Searches
DnsRequestfor DNS query activity - ProcessPattern: Searches
SrcProcNameandSrcProcCmdLinefor process execution patterns - RegistryMod: Filters for
Registry Value CreateandRegistry Value Modifiedevents - NetworkConnection: Finds suspicious ports (4444, 5555, 8080, 8443, 1337) or unknown port names
- FileHash: Searches
-
Query Execution: The
Invoke-DeepVisibilityQueryfunction:- Converts PowerShell
$TimeRangeparameter to ISO 8601 UTC timestamps for API - Initiates query via
POST /web/api/v2.1/dv/init-queryendpoint - Polls query status every 2 seconds (max 30 attempts = 60 seconds timeout)
- Retrieves paginated results using
nextCursorpagination until all events are collected - Returns array of event objects with full telemetry fields
- Converts PowerShell
-
Threat Severity Analysis: The
Get-ThreatSeverityfunction categorizes each event:- CRITICAL indicators: Hardcoded list of known attack tool names (mimikatz, psexec, bloodhound, cobalt, meterpreter, empire, covenant, ransomware, lazagne)
- HIGH indicators: Suspicious command patterns (encoded PowerShell, download cradles, privilege escalation, shadow copy deletion)
- MEDIUM indicators: Elevated process integrity levels, SentinelOne threat indicators, uncommon network ports
- LOW indicators: All other events matching query criteria
-
Report Generation: The
New-ThreatHuntReportfunction:- Adds
ThreatSeverityproperty to each event object - Groups findings by severity level
- Generates structured hashtable report with summary statistics
- Outputs color-coded console report with top findings preview
- Logs severity distribution to audit log
- Adds
-
Export Capabilities: The
Export-ThreatHuntResultsfunction:- Creates timestamped JSON file with complete report structure (nested findings by severity)
- Creates timestamped CSV file with flattened event data for spreadsheet analysis
- Logs export file paths for easy reference
-
Automated Response Actions: The
Invoke-ThreatResponsefunction (when-AutoResponseis enabled):- Filters for CRITICAL and HIGH severity events only
- Extracts unique affected agent names
- Presents interactive menu for response actions
- Requires explicit
yesconfirmation for destructive actions - Note: Current implementation logs response requests but does not execute API calls (requires agent ID resolution which needs additional API queries)
API Integration Details:
The script uses SentinelOne API v2.1 with these endpoints:
- Authentication:
Authorization: ApiToken <token>header for all requests - Account Verification:
GET /web/api/v2.1/accounts(connectivity test) - Query Initialization:
POST /web/api/v2.1/dv/init-query(returnsqueryId) - Query Status:
GET /web/api/v2.1/dv/query-status?queryId={id}(polls forFINISHEDstate) - Query Results:
GET /web/api/v2.1/dv/events?queryId={id}&cursor={cursor}(paginated results) - Response Actions (logged but not executed):
- Network Isolation:
POST /web/api/v2.1/agents/actions/disconnect - Process Termination:
POST /web/api/v2.1/agents/actions/kill-process - File Quarantine:
POST /web/api/v2.1/threats/actions/mitigate
- Network Isolation:
Error Handling:
- All API requests wrapped in try-catch blocks with detailed logging
- Query timeout after 60 seconds (30 attempts × 2 second intervals)
- Parameter validation ensures required values are provided for each
QueryType - API token cleared from memory in
finallyblock with garbage collection - Stack traces logged for debugging failed queries
Query Result Limits:
Each Deep Visibility query is limited to 1000 events via the LIMIT clause. For hunts that may exceed this limit:
- Consider narrowing the time range (
-TimeRangeparameter) - Use more specific search values
- Break into multiple queries targeting specific timeframes
- Use custom queries with additional WHERE clause filters
Practical Use Cases (Included in Script Comments):
The script includes comprehensive query templates for common threat hunting scenarios:
- Credential Access Detection: LSASS process access, Mimikatz indicators
- Lateral Movement Detection: PSExec, WMI, Remote Services, SMB connections
- Persistence Mechanism Detection: Registry Run keys, Scheduled Tasks
- Command and Control Detection: Encoded PowerShell, suspicious network patterns
- Data Exfiltration Detection: Archiving tools, large uploads, FTP/SSH connections
- Ransomware Behavior Detection: Shadow copy deletion, mass file encryption
- Living Off The Land Binaries (LOLBins): Certutil, Bitsadmin, Regsvr32, Mshta
- Suspicious Parent-Child Processes: Office apps spawning PowerShell, web servers spawning command shells
Script contents
<#
.SYNOPSIS
Automates SentinelOne Deep Visibility threat hunting queries via API with optional response actions.
.DESCRIPTION
Connects to SentinelOne management console API to execute Deep Visibility queries for threat hunting.
Supports IOC searches (hash, IP, domain), process patterns, registry modifications, and network connections.
Generates categorized reports and optionally executes response actions (isolate, kill process, quarantine).
Logs all operations to C:\BIN\LOGS-\<date\>-S1-ThreatHunt.log.
.PARAMETER ManagementUrl
SentinelOne console URL (e.g., https://yourinstance.sentinelone.net)
.PARAMETER ApiToken
API token with Deep Visibility and response permissions (if not provided, prompts securely)
.PARAMETER QueryType
Type of hunt: FileHash, IPAddress, Domain, ProcessPattern, RegistryMod, NetworkConnection, CustomQuery
.PARAMETER SearchValue
Search term (hash, IP, domain, process name, registry key, etc.)
.PARAMETER CustomQuery
Full Deep Visibility SQL query (used when QueryType is CustomQuery)
.PARAMETER TimeRange
Hours to search back (default: 24)
.PARAMETER AutoResponse
Enable automated response actions with confirmation prompts
.PARAMETER ExportPath
Directory for CSV/JSON exports (default: C:\BIN\S1-ThreatHunt)
.EXAMPLE
.\Invoke-SentinelOne-ThreatHunt.ps1 -ManagementUrl "https://company.sentinelone.net" -QueryType FileHash -SearchValue "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
.EXAMPLE
.\Invoke-SentinelOne-ThreatHunt.ps1 -ManagementUrl "https://company.sentinelone.net" -QueryType ProcessPattern -SearchValue "powershell.exe" -TimeRange 168 -AutoResponse
.EXAMPLE
.\Invoke-SentinelOne-ThreatHunt.ps1 -ManagementUrl "https://company.sentinelone.net" -QueryType CustomQuery -CustomQuery "SELECT * FROM events WHERE processName = 'cmd.exe' AND cmdLine CONTAINS 'mimikatz'"
.NOTES
Version: 1.0
Author: CosmicBytez IT Automation
Requires: SentinelOne API token with DeepVisibility.Read and optional RemoteOps permissions
Dependencies: PowerShell 5.1+, Internet connectivity to SentinelOne console
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidatePattern('^https://.*\.sentinelone\.(net|com)$')]
[string]$ManagementUrl,
[Parameter(Mandatory = $false)]
[SecureString]$ApiToken,
[Parameter(Mandatory = $true)]
[ValidateSet('FileHash', 'IPAddress', 'Domain', 'ProcessPattern', 'RegistryMod', 'NetworkConnection', 'CustomQuery')]
[string]$QueryType,
[Parameter(Mandatory = $false)]
[string]$SearchValue,
[Parameter(Mandatory = $false)]
[string]$CustomQuery,
[Parameter(Mandatory = $false)]
[ValidateRange(1, 8760)]
[int]$TimeRange = 24,
[Parameter(Mandatory = $false)]
[switch]$AutoResponse,
[Parameter(Mandatory = $false)]
[string]$ExportPath = "C:\BIN\S1-ThreatHunt"
)
$ErrorActionPreference = 'Stop'
# Initialize logging
$timestamp = Get-Date -Format "yyyy-MM-dd"
$logFile = "C:\BIN\LOGS-$timestamp-S1-ThreatHunt.log"
# Ensure log directory exists
if (-not (Test-Path "C:\BIN")) {
New-Item -Path "C:\BIN" -ItemType Directory -Force | Out-Null
}
function Write-Log {
param(
[Parameter(Mandatory = $true)]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet('INFO', 'WARNING', 'ERROR', 'SUCCESS')]
[string]$Level = 'INFO'
)
$logTimestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$logTimestamp] [$Level] $Message"
Add-Content -Path $logFile -Value $logEntry
switch ($Level) {
'ERROR' { Write-Host $logEntry -ForegroundColor Red }
'WARNING' { Write-Host $logEntry -ForegroundColor Yellow }
'SUCCESS' { Write-Host $logEntry -ForegroundColor Green }
default { Write-Host $logEntry -ForegroundColor White }
}
}
function Get-S1ApiToken {
if ($null -eq $ApiToken) {
Write-Host "`nSentinelOne API Token required (with DeepVisibility.Read permission)" -ForegroundColor Cyan
Write-Host "Obtain from: $ManagementUrl/settings/users > API Token" -ForegroundColor Gray
$ApiToken = Read-Host "Enter API Token" -AsSecureString
}
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ApiToken)
$plainToken = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
return $plainToken
}
function Invoke-S1ApiRequest {
param(
[Parameter(Mandatory = $true)]
[string]$Endpoint,
[Parameter(Mandatory = $false)]
[string]$Method = 'GET',
[Parameter(Mandatory = $false)]
[hashtable]$Body,
[Parameter(Mandatory = $true)]
[string]$Token
)
$headers = @{
'Authorization' = "ApiToken $Token"
'Content-Type' = 'application/json'
}
$uri = "$ManagementUrl/web/api/v2.1/$Endpoint"
try {
$params = @{
Uri = $uri
Method = $Method
Headers = $headers
}
if ($Body) {
$params.Body = ($Body | ConvertTo-Json -Depth 10 -Compress)
}
Write-Log "API Request: $Method $Endpoint" -Level INFO
$response = Invoke-RestMethod @params
return $response
}
catch {
Write-Log "API Request Failed: $_" -Level ERROR
throw
}
}
function Get-DeepVisibilityQuery {
param(
[Parameter(Mandatory = $true)]
[string]$Type,
[Parameter(Mandatory = $false)]
[string]$Value,
[Parameter(Mandatory = $false)]
[string]$Custom
)
# Deep Visibility SQL-like query templates
switch ($Type) {
'FileHash' {
return @"
SELECT
AgentName,
SrcProcUser,
SrcProcCmdLine,
SrcProcName,
SrcProcSha1,
SrcProcSha256,
TgtFilePath,
TgtFileSha1,
TgtFileSha256,
EventTime
FROM events
WHERE (SrcProcSha1 = '$Value' OR SrcProcSha256 = '$Value' OR TgtFileSha1 = '$Value' OR TgtFileSha256 = '$Value')
ORDER BY EventTime DESC
LIMIT 1000
"@
}
'IPAddress' {
return @"
SELECT
AgentName,
SrcProcName,
SrcProcCmdLine,
DstIp,
DstPort,
SrcIp,
IndicatorName,
EventType,
EventTime
FROM events
WHERE (DstIp = '$Value' OR SrcIp = '$Value')
ORDER BY EventTime DESC
LIMIT 1000
"@
}
'Domain' {
return @"
SELECT
AgentName,
SrcProcName,
SrcProcCmdLine,
DnsRequest,
DnsResponse,
DstIp,
EventTime
FROM events
WHERE DnsRequest CONTAINS '$Value'
ORDER BY EventTime DESC
LIMIT 1000
"@
}
'ProcessPattern' {
return @"
SELECT
AgentName,
SrcProcUser,
SrcProcName,
SrcProcCmdLine,
SrcProcSha256,
SrcProcParentName,
SrcProcIntegrityLevel,
EventTime
FROM events
WHERE (SrcProcName CONTAINS '$Value' OR SrcProcCmdLine CONTAINS '$Value')
ORDER BY EventTime DESC
LIMIT 1000
"@
}
'RegistryMod' {
return @"
SELECT
AgentName,
SrcProcName,
SrcProcCmdLine,
RegistryKeyPath,
RegistryOldValue,
RegistryValue,
EventType,
EventTime
FROM events
WHERE (EventType = 'Registry Value Create' OR EventType = 'Registry Value Modified')
AND RegistryKeyPath CONTAINS '$Value'
ORDER BY EventTime DESC
LIMIT 1000
"@
}
'NetworkConnection' {
return @"
SELECT
AgentName,
SrcProcName,
SrcProcCmdLine,
DstIp,
DstPort,
DstPortName,
IndicatorName,
EventType,
EventTime
FROM events
WHERE EventType IN ('IP Connect', 'IP Listen')
AND (DstPort IN (4444, 5555, 8080, 8443, 1337) OR DstPortName = 'unknown')
ORDER BY EventTime DESC
LIMIT 1000
"@
}
'CustomQuery' {
return $Custom
}
}
}
function Invoke-DeepVisibilityQuery {
param(
[Parameter(Mandatory = $true)]
[string]$Query,
[Parameter(Mandatory = $true)]
[int]$Hours,
[Parameter(Mandatory = $true)]
[string]$Token
)
# Calculate time range
$toDate = Get-Date
$fromDate = $toDate.AddHours(-$Hours)
$body = @{
query = $Query
fromDate = $fromDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
toDate = $toDate.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
limit = 1000
}
Write-Log "Executing Deep Visibility Query (last $Hours hours)" -Level INFO
Write-Log "Query: $($Query -replace "`n",' ')" -Level INFO
# Create query
$queryResponse = Invoke-S1ApiRequest -Endpoint "dv/init-query" -Method POST -Body $body -Token $Token
if (-not $queryResponse.data.queryId) {
Write-Log "Failed to initialize query" -Level ERROR
return $null
}
$queryId = $queryResponse.data.queryId
Write-Log "Query ID: $queryId" -Level INFO
# Poll for query completion
$maxAttempts = 30
$attempt = 0
$queryStatus = $null
while ($attempt -lt $maxAttempts) {
Start-Sleep -Seconds 2
$attempt++
$statusResponse = Invoke-S1ApiRequest -Endpoint "dv/query-status?queryId=$queryId" -Token $Token
$queryStatus = $statusResponse.data.responseState
Write-Verbose "Query status: $queryStatus (attempt $attempt/$maxAttempts)"
if ($queryStatus -eq 'FINISHED') {
break
}
elseif ($queryStatus -eq 'FAILED') {
Write-Log "Query execution failed" -Level ERROR
return $null
}
}
if ($queryStatus -ne 'FINISHED') {
Write-Log "Query timeout after $maxAttempts attempts" -Level ERROR
return $null
}
# Retrieve results
$results = @()
$cursor = $null
do {
$resultsEndpoint = "dv/events?queryId=$queryId"
if ($cursor) {
$resultsEndpoint += "&cursor=$cursor"
}
$resultsResponse = Invoke-S1ApiRequest -Endpoint $resultsEndpoint -Token $Token
if ($resultsResponse.data) {
$results += $resultsResponse.data
}
$cursor = $resultsResponse.pagination.nextCursor
} while ($cursor)
Write-Log "Retrieved $($results.Count) events" -Level SUCCESS
return $results
}
function Get-ThreatSeverity {
param(
[Parameter(Mandatory = $true)]
[object]$Event
)
$criticalIndicators = @(
'mimikatz', 'psexec', 'bloodhound', 'sharphound', 'rubeus', 'cobalt',
'meterpreter', 'empire', 'covenant', 'ransomware', 'lazagne'
)
$highIndicators = @(
'powershell -enc', '-nop -w hidden', 'invoke-expression', 'downloadstring',
'iex(', 'invoke-mimikatz', 'net user', 'net localgroup administrators',
'wmic', 'reg add', 'schtasks', 'vssadmin delete shadows'
)
$eventText = ($Event | ConvertTo-Json -Compress).ToLower()
# Critical severity checks
foreach ($indicator in $criticalIndicators) {
if ($eventText -match $indicator) {
return 'CRITICAL'
}
}
# High severity checks
foreach ($indicator in $highIndicators) {
if ($eventText -match [regex]::Escape($indicator)) {
return 'HIGH'
}
}
# Medium severity for suspicious patterns
if ($Event.SrcProcIntegrityLevel -eq 'high' -or
$Event.IndicatorName -or
$Event.DstPort -in @(4444, 5555, 1337, 31337)) {
return 'MEDIUM'
}
return 'LOW'
}
function New-ThreatHuntReport {
param(
[Parameter(Mandatory = $true)]
[array]$Events,
[Parameter(Mandatory = $true)]
[string]$QueryType,
[Parameter(Mandatory = $false)]
[string]$SearchTerm
)
Write-Log "Analyzing $($Events.Count) events for threats..." -Level INFO
# Categorize by severity
$findings = $Events | ForEach-Object {
$severity = Get-ThreatSeverity -Event $_
$_ | Add-Member -NotePropertyName 'ThreatSeverity' -NotePropertyValue $severity -Force -PassThru
}
$critical = @($findings | Where-Object { $_.ThreatSeverity -eq 'CRITICAL' })
$high = @($findings | Where-Object { $_.ThreatSeverity -eq 'HIGH' })
$medium = @($findings | Where-Object { $_.ThreatSeverity -eq 'MEDIUM' })
$low = @($findings | Where-Object { $_.ThreatSeverity -eq 'LOW' })
# Generate report
$report = @{
GeneratedAt = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
QueryType = $QueryType
SearchTerm = $SearchTerm
TimeRangeHours = $TimeRange
TotalEvents = $Events.Count
Severity = @{
Critical = $critical.Count
High = $high.Count
Medium = $medium.Count
Low = $low.Count
}
CriticalFindings = $critical
HighFindings = $high
MediumFindings = $medium
LowFindings = $low
}
# Console output
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " SentinelOne Threat Hunt Report" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Generated: $($report.GeneratedAt)" -ForegroundColor White
Write-Host "Query Type: $QueryType" -ForegroundColor White
if ($SearchTerm) {
Write-Host "Search Term: $SearchTerm" -ForegroundColor White
}
Write-Host "Time Range: Last $TimeRange hours" -ForegroundColor White
Write-Host "`nTotal Events: $($report.TotalEvents)" -ForegroundColor White
Write-Host "`nSeverity Distribution:" -ForegroundColor Yellow
Write-Host " CRITICAL: $($critical.Count)" -ForegroundColor Red
Write-Host " HIGH: $($high.Count)" -ForegroundColor DarkRed
Write-Host " MEDIUM: $($medium.Count)" -ForegroundColor DarkYellow
Write-Host " LOW: $($low.Count)" -ForegroundColor Gray
if ($critical.Count -gt 0) {
Write-Host "`nCRITICAL Findings:" -ForegroundColor Red
$critical | Select-Object -First 5 | ForEach-Object {
Write-Host " - Agent: $($_.AgentName) | Process: $($_.SrcProcName) | Cmd: $($_.SrcProcCmdLine)" -ForegroundColor Red
}
if ($critical.Count -gt 5) {
Write-Host " ... and $($critical.Count - 5) more" -ForegroundColor Red
}
}
if ($high.Count -gt 0) {
Write-Host "`nHIGH Findings:" -ForegroundColor DarkRed
$high | Select-Object -First 3 | ForEach-Object {
Write-Host " - Agent: $($_.AgentName) | Process: $($_.SrcProcName) | Cmd: $($_.SrcProcCmdLine)" -ForegroundColor DarkRed
}
if ($high.Count -gt 3) {
Write-Host " ... and $($high.Count - 3) more" -ForegroundColor DarkRed
}
}
Write-Host "`n========================================`n" -ForegroundColor Cyan
Write-Log "Threat analysis complete: $($critical.Count) CRITICAL, $($high.Count) HIGH, $($medium.Count) MEDIUM, $($low.Count) LOW" -Level SUCCESS
return $report
}
function Export-ThreatHuntResults {
param(
[Parameter(Mandatory = $true)]
[hashtable]$Report,
[Parameter(Mandatory = $true)]
[string]$ExportDirectory
)
# Ensure export directory exists
if (-not (Test-Path $ExportDirectory)) {
New-Item -Path $ExportDirectory -ItemType Directory -Force | Out-Null
Write-Log "Created export directory: $ExportDirectory" -Level INFO
}
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$baseFilename = "$ExportDirectory\S1-ThreatHunt-$timestamp"
# Export JSON (full report)
$jsonPath = "$baseFilename.json"
$Report | ConvertTo-Json -Depth 10 | Out-File -FilePath $jsonPath -Encoding UTF8
Write-Log "Exported JSON report: $jsonPath" -Level SUCCESS
# Export CSV (flattened findings)
$csvPath = "$baseFilename.csv"
$allFindings = @()
$allFindings += $Report.CriticalFindings
$allFindings += $Report.HighFindings
$allFindings += $Report.MediumFindings
$allFindings += $Report.LowFindings
if ($allFindings.Count -gt 0) {
$allFindings | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
Write-Log "Exported CSV findings: $csvPath" -Level SUCCESS
}
return @{
JsonPath = $jsonPath
CsvPath = $csvPath
}
}
function Invoke-ThreatResponse {
param(
[Parameter(Mandatory = $true)]
[hashtable]$Report,
[Parameter(Mandatory = $true)]
[string]$Token
)
if (-not $AutoResponse) {
Write-Host "`nAutomated response actions disabled. Use -AutoResponse to enable." -ForegroundColor Yellow
return
}
$criticalEvents = $Report.CriticalFindings + $Report.HighFindings
if ($criticalEvents.Count -eq 0) {
Write-Host "`nNo CRITICAL or HIGH severity findings requiring response." -ForegroundColor Green
return
}
Write-Host "`n========================================" -ForegroundColor Red
Write-Host " Automated Response Actions" -ForegroundColor Red
Write-Host "========================================" -ForegroundColor Red
Write-Host "Found $($criticalEvents.Count) CRITICAL/HIGH severity events`n" -ForegroundColor Yellow
# Get unique agents from critical events
$affectedAgents = $criticalEvents | Select-Object -ExpandProperty AgentName -Unique
Write-Host "Affected Endpoints:" -ForegroundColor Yellow
$affectedAgents | ForEach-Object { Write-Host " - $_" -ForegroundColor White }
Write-Host "`nAvailable Response Actions:" -ForegroundColor Cyan
Write-Host " 1. Isolate affected endpoints from network" -ForegroundColor White
Write-Host " 2. Kill suspicious processes" -ForegroundColor White
Write-Host " 3. Quarantine malicious files" -ForegroundColor White
Write-Host " 4. No action (manual investigation)" -ForegroundColor White
$choice = Read-Host "`nSelect action (1-4)"
switch ($choice) {
'1' {
$confirm = Read-Host "Confirm network isolation of $($affectedAgents.Count) endpoints? (yes/no)"
if ($confirm -eq 'yes') {
Write-Log "Network isolation requested for $($affectedAgents.Count) endpoints" -Level WARNING
# Note: Actual implementation requires agent IDs and disconnect endpoint API
Write-Host "Network isolation would be executed here (requires agent ID resolution)" -ForegroundColor Yellow
Write-Host "API Endpoint: POST /web/api/v2.1/agents/actions/disconnect" -ForegroundColor Gray
}
else {
Write-Log "Network isolation cancelled by user" -Level INFO
}
}
'2' {
$confirm = Read-Host "Confirm process termination on affected endpoints? (yes/no)"
if ($confirm -eq 'yes') {
Write-Log "Process termination requested" -Level WARNING
Write-Host "Process termination would be executed here (requires agent ID and process ID)" -ForegroundColor Yellow
Write-Host "API Endpoint: POST /web/api/v2.1/agents/actions/kill-process" -ForegroundColor Gray
}
else {
Write-Log "Process termination cancelled by user" -Level INFO
}
}
'3' {
$confirm = Read-Host "Confirm file quarantine on affected endpoints? (yes/no)"
if ($confirm -eq 'yes') {
Write-Log "File quarantine requested" -Level WARNING
Write-Host "File quarantine would be executed here (requires threat ID)" -ForegroundColor Yellow
Write-Host "API Endpoint: POST /web/api/v2.1/threats/actions/mitigate" -ForegroundColor Gray
}
else {
Write-Log "File quarantine cancelled by user" -Level INFO
}
}
'4' {
Write-Log "No automated action taken - manual investigation recommended" -Level INFO
}
default {
Write-Host "Invalid selection. No action taken." -ForegroundColor Red
}
}
}
# ============================================================================
# MAIN EXECUTION
# ============================================================================
Write-Log "=== SentinelOne Threat Hunt Started ===" -Level INFO
Write-Log "Management Console: $ManagementUrl" -Level INFO
Write-Log "Query Type: $QueryType" -Level INFO
try {
# Get API token
$token = Get-S1ApiToken
# Test API connectivity
Write-Log "Testing API connectivity..." -Level INFO
$accountInfo = Invoke-S1ApiRequest -Endpoint "accounts" -Token $token
Write-Log "Connected to SentinelOne console successfully" -Level SUCCESS
# Validate parameters
if ($QueryType -ne 'CustomQuery' -and [string]::IsNullOrEmpty($SearchValue)) {
throw "SearchValue parameter required for QueryType: $QueryType"
}
if ($QueryType -eq 'CustomQuery' -and [string]::IsNullOrEmpty($CustomQuery)) {
throw "CustomQuery parameter required when QueryType is CustomQuery"
}
# Build Deep Visibility query
$dvQuery = Get-DeepVisibilityQuery -Type $QueryType -Value $SearchValue -Custom $CustomQuery
# Execute query
$queryResults = Invoke-DeepVisibilityQuery -Query $dvQuery -Hours $TimeRange -Token $token
if ($null -eq $queryResults -or $queryResults.Count -eq 0) {
Write-Log "No events found matching query criteria" -Level WARNING
Write-Host "`nNo results found. Consider expanding time range or adjusting search criteria." -ForegroundColor Yellow
exit 0
}
# Generate threat hunt report
$report = New-ThreatHuntReport -Events $queryResults -QueryType $QueryType -SearchTerm $SearchValue
# Export results
$exportedFiles = Export-ThreatHuntResults -Report $report -ExportDirectory $ExportPath
Write-Host "Results exported to:" -ForegroundColor Cyan
Write-Host " JSON: $($exportedFiles.JsonPath)" -ForegroundColor White
Write-Host " CSV: $($exportedFiles.CsvPath)" -ForegroundColor White
# Automated response actions
Invoke-ThreatResponse -Report $report -Token $token
Write-Log "=== Threat Hunt Completed Successfully ===" -Level SUCCESS
}
catch {
Write-Log "Threat hunt failed: $_" -Level ERROR
Write-Log "Stack Trace: $($_.ScriptStackTrace)" -Level ERROR
throw
}
finally {
# Secure cleanup
if ($token) {
$token = $null
[System.GC]::Collect()
}
}
<#
EXAMPLE DEEP VISIBILITY QUERIES FOR REFERENCE:
1. Credential Access Detection (Mimikatz, LSASS dumping):
SELECT AgentName, SrcProcName, SrcProcCmdLine, TgtProcName, EventTime
FROM events
WHERE (SrcProcCmdLine CONTAINS 'lsass' OR SrcProcCmdLine CONTAINS 'sekurlsa'
OR TgtProcName = 'lsass.exe')
AND EventType IN ('Process Creation', 'Open Remote Process')
ORDER BY EventTime DESC
2. Lateral Movement Detection (PSExec, WMI, Remote Services):
SELECT AgentName, SrcProcName, SrcProcCmdLine, DstIp, DstPort, EventTime
FROM events
WHERE (SrcProcName IN ('psexec.exe', 'wmic.exe', 'mmc.exe', 'wmiprvse.exe')
OR DstPort IN (445, 135, 5985, 5986))
AND EventType IN ('IP Connect', 'Process Creation')
ORDER BY EventTime DESC
3. Persistence Mechanism Detection (Registry Run Keys, Scheduled Tasks):
SELECT AgentName, SrcProcName, RegistryKeyPath, RegistryValue, EventTime
FROM events
WHERE EventType IN ('Registry Value Create', 'Registry Value Modified')
AND (RegistryKeyPath CONTAINS 'CurrentVersion\Run'
OR RegistryKeyPath CONTAINS 'CurrentVersion\Policies\Explorer\Run'
OR RegistryKeyPath CONTAINS 'CurrentVersion\Windows\Load')
ORDER BY EventTime DESC
4. Command and Control Detection (Encoded Commands, Suspicious Network):
SELECT AgentName, SrcProcName, SrcProcCmdLine, DstIp, DnsRequest, EventTime
FROM events
WHERE (SrcProcCmdLine CONTAINS '-enc' OR SrcProcCmdLine CONTAINS '-e '
OR SrcProcCmdLine CONTAINS 'FromBase64String'
OR SrcProcCmdLine CONTAINS 'DownloadString')
AND SrcProcName IN ('powershell.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe')
ORDER BY EventTime DESC
5. Data Exfiltration Detection (Large Uploads, Archiving):
SELECT AgentName, SrcProcName, SrcProcCmdLine, TgtFilePath, EventTime
FROM events
WHERE (SrcProcName IN ('7z.exe', 'winrar.exe', 'tar.exe')
OR SrcProcCmdLine CONTAINS 'compress-archive'
OR (EventType = 'IP Connect' AND DstPort IN (21, 22, 443, 8443)))
ORDER BY EventTime DESC
6. Ransomware Behavior Detection (Shadow Copy Deletion, Mass File Encryption):
SELECT AgentName, SrcProcName, SrcProcCmdLine, TgtFilePath, EventTime
FROM events
WHERE (SrcProcCmdLine CONTAINS 'vssadmin delete shadows'
OR SrcProcCmdLine CONTAINS 'wbadmin delete catalog'
OR SrcProcCmdLine CONTAINS 'bcdedit /set {default} recoveryenabled no'
OR TgtFilePath CONTAINS '.encrypted' OR TgtFilePath CONTAINS '.locked')
ORDER BY EventTime DESC
7. Living Off The Land Binaries (LOLBins):
SELECT AgentName, SrcProcName, SrcProcCmdLine, SrcProcParentName, EventTime
FROM events
WHERE SrcProcName IN ('certutil.exe', 'bitsadmin.exe', 'regsvr32.exe', 'mshta.exe',
'rundll32.exe', 'regasm.exe', 'installutil.exe', 'msbuild.exe')
AND EventType = 'Process Creation'
ORDER BY EventTime DESC
8. Suspicious Parent-Child Process Relationships:
SELECT AgentName, SrcProcParentName, SrcProcName, SrcProcCmdLine, EventTime
FROM events
WHERE (SrcProcParentName IN ('winword.exe', 'excel.exe', 'powerpnt.exe', 'outlook.exe')
AND SrcProcName IN ('powershell.exe', 'cmd.exe', 'wscript.exe', 'cscript.exe'))
OR (SrcProcParentName IN ('w3wp.exe', 'httpd.exe', 'nginx.exe')
AND SrcProcName IN ('cmd.exe', 'powershell.exe', 'net.exe'))
ORDER BY EventTime DESC
API ENDPOINTS REFERENCE:
- Deep Visibility Query Init: POST /web/api/v2.1/dv/init-query
- Query Status: GET /web/api/v2.1/dv/query-status?queryId={id}
- Query Results: GET /web/api/v2.1/dv/events?queryId={id}
- Network Isolation: POST /web/api/v2.1/agents/actions/disconnect
- Process Kill: POST /web/api/v2.1/agents/actions/kill-process
- Threat Mitigation: POST /web/api/v2.1/threats/actions/mitigate
- Agent Details: GET /web/api/v2.1/agents?computerName={name}
SENTINELCTL CLI ALTERNATIVE (Windows Agent):
sentinelctl.exe deepvisibility query --query "SELECT * FROM events WHERE..." --from "2025-11-05T00:00:00Z" --to "2025-11-06T00:00:00Z"
sentinelctl.exe network disconnect
sentinelctl.exe process kill --pid {pid}
#>