// question

On Windows, review PowerShell process events to spot where a remote .ps1 was fetched and run to pull vault credentials. Determine the name of the script file that was downloaded.

Reference KQL answer and model-by-model comparison.

c89becbe-1758-4e7d-a0f4-97d2188a23e3
question id
8/23
models correct
1
valid answer
35%
success rate
// reference answer

Human-validated correct answer for this question.

GetCredmanCreds.ps1
// model comparison
ModelAnswerKQL queryStatusTriesTimeCost
gemini-2.5-flash-preview-04-17
GetCredmanCreds.ps1
DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where FileName =~ "powershell.exe"
// Look for common in-memory execution patterns involving remote sources
| where ProcessCommandLine has_any ("iex", "invoke-expression", "downloadstring", "invoke-webrequest")
   and (ProcessCommandLine has "http://" or ProcessCommandLine has "https://")
// Extract potential remote source URL from the command line
| extend RemoteSourceUrl = extract(@'http[s]?://[^\s"]+', 0, ProcessCommandLine)
// Attempt to get the path/filename part from the URL
| extend PotentialScriptName = iif(isnotempty(RemoteSourceUrl), parse_url(RemoteSourceUrl).Path, "")
| project Timestamp, DeviceName, InitiatingProcessCommandLine, ProcessCommandLine, AccountName, RemoteSourceUrl, PotentialScriptName, ProcessId
pass131.70s0.0162
gpt-35-turbo
no answer
no query generatedfail50.00s0.0110
gpt-4-turbo-2024-04-09
no answer
no query generatedfail50.00s0.2672
gpt-4.1
no answer
no query generatedfail50.00s0.0601
gpt-4.1-finetuned
no answer
no query generatedfail50.00s0.0325
gpt-4.1-mini
GetCredmanCreds.ps1
DeviceProcessEvents
| where FileName has "powershell" or FileName has "pwsh"
| where ProcessCommandLine has "http" or ProcessCommandLine has "https"
| extend ScriptUri = extract(@"(https?://[^\s']+\.ps1)", 1, ProcessCommandLine)
| where isnotempty(ScriptUri)
| extend ScriptName = extract(@"([^/]+\.ps1)", 1, ScriptUri)
| project TimeGenerated, DeviceName, AccountName, ProcessCreationTime, ProcessCommandLine, ScriptUri, ScriptName
| order by TimeGenerated desc
pass14.50s0.0036
gpt-4.1-nano
no answer
no query generatedfail50.00s0.0015
gpt-4o
no answer
no query generatedfail50.00s0.0686
gpt-5-high
GetCredmanCreds.ps1
let PwshBinaries = dynamic(["powershell.exe","pwsh.exe","powershell_ise.exe"]);
let VaultKeywords = dynamic(["vault","cyberark","hashicorp","secret","secrets","credential","credentials","creds"]);
let UrlPs1Regex = @"(?i)https?://[^\s'""`]+?\.ps1(?:[?#][^\s'""`]*)?";
let InMemoryExecRegex = @"(?i)(\|\s*iex\b|\binvoke-expression\b)";
let PSFetch = 
DeviceProcessEvents
| where FileName in (PwshBinaries)
| where ProcessCommandLine has_any ("http","https",".ps1")
| where ProcessCommandLine matches regex @"(?i)(iwr|invoke-webrequest|irm|invoke-restmethod|start-bitstransfer|bitsadmin|downloadstring|downloadfile|System\.Net\.WebClient|Net\.WebClient|curl|wget)"
| extend RemotePs1Url = tostring(extract(UrlPs1Regex, 0, ProcessCommandLine))
| extend LocalOutFile = coalesce(
    tostring(extract(@"(?i)-Out(File)?\s+(?:['""])?([^\s'""]+?\.ps1)", 2, ProcessCommandLine)),
    tostring(extract(@"(?i)-Destination\s+(?:['""])?([^\s'""]+?\.ps1)", 1, ProcessCommandLine)),
    tostring(extract(@"(?i)DownloadFile\([^,]+,\s*['""]([^'""]+?\.ps1)['""]", 1, ProcessCommandLine)),
    tostring(extract(@"(?i)\s-o\s+(?:['""])?([^\s'""]+?\.ps1)", 1, ProcessCommandLine))
)
| extend RemoteScriptName = iif(isnotempty(RemotePs1Url), tostring(split(parse_url(RemotePs1Url).Path, "/")[-1]), "")
| extend LocalScriptName = iif(isnotempty(LocalOutFile), tostring(split(replace_string(LocalOutFile,'/','\\'), "\\")[-1]), "")
| extend DownloadedScriptName = coalesce(LocalScriptName, RemoteScriptName)
| extend ExecutedInMemory = iif(ProcessCommandLine matches regex InMemoryExecRegex, true, false)
| extend VaultRelated = iif(ProcessCommandLine has_any (VaultKeywords) or tostring(DownloadedScriptName) has_any (VaultKeywords) or tostring(RemotePs1Url) has_any (VaultKeywords), true, false)
| project FetchEventTime=TimeGenerated, DeviceId, DeviceName, AccountDomain, AccountName, ProcessId, ProcessCommandLine, RemotePs1Url, LocalOutFile, DownloadedScriptName, ExecutedInMemory, VaultRelated, SHA1
;
let PSExecLocal =
DeviceProcessEvents
| where FileName in (PwshBinaries)
| where ProcessCommandLine matches regex @"(?i)\s-File\s+['""]?([^\s'""]+\.ps1)"
| extend ExecutedLocalPath = tostring(extract(@"(?i)\s-File\s+['""]?([^\s'""]+\.ps1)", 1, ProcessCommandLine))
| extend ExecutedScriptName = tostring(split(replace_string(ExecutedLocalPath,'/','\\'), "\\")[-1])
| project ExecEventTime=TimeGenerated, DeviceId, ExecutedLocalPath, ExecutedScriptName
;
PSFetch
| join kind=leftouter (
    PSExecLocal
) on DeviceId
| where isnull(ExecEventTime) or (ExecEventTime between (FetchEventTime .. FetchEventTime + 30m))
| extend CorrelatedExecution = iif(isnotempty(ExecutedLocalPath) and (tolower(ExecutedScriptName) == tolower(DownloadedScriptName) or (isnotempty(DownloadedScriptName) and tolower(ExecutedLocalPath) has tolower(DownloadedScriptName))), true, ExecutedInMemory)
| extend FinalScriptName = coalesce(DownloadedScriptName, ExecutedScriptName)
| where VaultRelated == true
| project Time=FetchEventTime, DeviceName, AccountName, ProcessId, RemotePs1Url, LocalOutFile, FinalScriptName, WasExecuted=CorrelatedExecution, InMemoryIEX=ExecutedInMemory, ProcessCommandLine
| order by Time desc
pass1144.05s0.0825
gpt-5-mini-high
no answer
no query generatedfail50.00s0.0235
gpt-5-mini-low
no answer
no query generatedfail50.00s0.0301
gpt-5-mini-medium
no answer
no query generatedfail50.00s0.0205
gpt-5-nano-high
no answer
no query generatedfail50.00s0.0084
gpt-5-nano-medium
no answer
no query generatedfail50.00s0.0112
grok-3-beta
no answer
no query generatedfail50.00s0.0987
grok-3-mini-beta
T1572-doh-beacon.ps1
DeviceProcessEvents
| where FileName == 'powershell.exe'
| where ProcessCommandLine contains '.ps1' and (ProcessCommandLine contains 'http' or ProcessCommandLine contains 'Invoke-WebRequest')
| project ScriptFileName = extract(@"[^\\]+?\.ps1$", 0, ProcessCommandLine), Timestamp, DeviceName, ProcessCommandLine
| summarize arg_max(Timestamp, *) by ScriptFileName
| order by Timestamp desc
| limit 100  // Limit results for review; adjust as needed
fail116.97s0.0016
o1-high
no answer
no query generatedfail50.00s0.8259
o1-low
GetCredmanCreds.ps1
DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where FileName in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe")
| where ProcessCommandLine has_any ("http://", "https://") and ProcessCommandLine has ".ps1"
| project TimeGenerated,
          DeviceName,
          AccountName,
          ProcessCommandLine,
          DownloadedScript = extract("(https?://[^ ]+\\.ps1)", 0, ProcessCommandLine)
| order by TimeGenerated desc
pass255.42s0.4529
o3-high
GetCredmanCreds.ps1
// --- Remote PowerShell script download & execution ----------------------------
let PowerShellHosts = dynamic(["powershell.exe","pwsh.exe","powershell_ise.exe"]);
DeviceProcessEvents
// (The hunting portal will inject the overall time-range automatically)
| where FileName in~ (PowerShellHosts)
// High-level filter – keep only PowerShell that references the web
| where ProcessCommandLine has "http" or ProcessCommandLine has "https"
// -----------------------------------------------------------------------------
// If the command is Base-64 encoded (-enc/-encodedCommand) pull it out & decode
| extend EncodedCmd = extract(@"-(enc|encodedcommand)\s+([A-Za-z0-9+/=]+)", 2, ProcessCommandLine, typeof(string))
| extend DecodedCmd = iff(isnotempty(EncodedCmd), base64_decode_tostring(EncodedCmd), "")
| extend FullCommand = iff(isnotempty(DecodedCmd), strcat(ProcessCommandLine, " ", DecodedCmd), ProcessCommandLine)
// -----------------------------------------------------------------------------
// Locate the first HTTP/S URL that ends with .ps1 in the clear or decoded text
| extend RemoteScriptUrl = extract("https?://\\S+\\.ps1", 0, FullCommand, typeof(string))
| where isnotempty(RemoteScriptUrl)
// Derive the script file name (text after the final "/")
| extend ScriptName = tostring(split(RemoteScriptUrl, "/")[-1])
// -----------------------------------------------------------------------------
// Analyst-friendly view
| project TimeGenerated, DeviceName, AccountName, ScriptName, RemoteScriptUrl, FullCommand
| order by TimeGenerated desc

// Uncomment below if you need a quick unique list of scripts and how often/where
// | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Executions=count(), Devices=make_set(DeviceName) by ScriptName, RemoteScriptUrl
// | order by Executions desc
pass3133.48s0.0902
o3-mini-high
GetCredmanCreds.ps1
DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine matches regex "https?://[^\\s]+\\.ps1"
| extend ScriptFullURL = extract("(https?://[^\\s]+\\.ps1)", 1, ProcessCommandLine)
| extend ScriptName = tostring(split(ScriptFullURL, "/")[-1])
| project TimeGenerated, DeviceName, ProcessCommandLine, ScriptName
pass331.53s0.0281
o3-mini-low
GetCredmanCreds.ps1
DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine contains ".ps1" and ProcessCommandLine contains "http"
| extend ScriptName = extract(@"(?:http[s]?://\S*/)?([\w\-.]+\.ps1)", 1, ProcessCommandLine)
| project TimeGenerated, DeviceName, AccountName, ProcessId, ScriptName, ProcessCommandLine
| order by TimeGenerated desc
pass2101.05s0.0243
o4-mini-high
GetCredmanCreds.ps1
DeviceProcessEvents
| where FileName in ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine matches regex @"https?://\S+?\.ps1"
| extend ScriptUrl = extract(@"https?://\S+?\.ps1", 0, ProcessCommandLine)
| extend ScriptName = extract(@"([^/]+\.ps1)$", 1, ScriptUrl)
| project TimeGenerated, DeviceName, InitiatingProcessAccountName, ScriptUrl, ScriptName, ProcessCommandLine
| sort by TimeGenerated desc
pass2103.80s0.0260
o4-mini-low
no answer
no query generatedfail50.00s0.0420