// question

On Windows devices, hunt for PowerShell activity where a remote script is fetched and executed to perform LLMNR/NBNS spoofing. Which cmdlet kicked off the listener?

Reference KQL answer and model-by-model comparison.

deecd55f-afe0-4a62-9fba-4d1ba2deb321
question id
11/23
models correct
1
valid answer
48%
success rate
// reference answer

Human-validated correct answer for this question.

Invoke-Inveigh
// model comparison
ModelAnswerKQL queryStatusTriesTimeCost
gemini-2.5-flash-preview-04-17
Invoke-Inveigh
DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has "LLMNR" or ProcessCommandLine has "NBNS" or ProcessCommandLine has "spoof" or ProcessCommandLine has "listener" or ProcessCommandLine has "Responder"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessCommandLine
pass117.93s0.0095
gpt-35-turbo
no answer
no query generatedfail50.00s0.0114
gpt-4-turbo-2024-04-09
no answer
no query generatedfail50.00s0.2498
gpt-4.1
Inveigh
DeviceProcessEvents
| where FileName =~ "powershell.exe" or FileName =~ "pwsh.exe"
| where ProcessCommandLine has_any ("Invoke-WebRequest", "IEX", "Invoke-Expression", "wget", "curl")
| where ProcessCommandLine has_any ("responder", "inveigh", "NBNS", "LLMNR", "-NBNS", "-LLMNR")
| extend ListenerCmdlet = case(
    ProcessCommandLine has "Inveigh", "Inveigh",
    ProcessCommandLine has "Responder", "Responder",
    ProcessCommandLine has "NBNS", "NBNS",
    ProcessCommandLine has "LLMNR", "LLMNR",
    "Unknown"
)
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, ListenerCmdlet
pass17.04s0.0128
gpt-4.1-finetuned
no answer
no query generatedfail50.00s0.0718
gpt-4.1-mini
Invoke-Inveigh
DeviceProcessEvents
| where FileName =~ "powershell.exe" or FileName =~ "pwsh.exe"
// Looking for command lines that download and execute remote scripts, commonly using 'Invoke-WebRequest', 'IEX', or 'Invoke-Expression'
| where ProcessCommandLine has_any ("Invoke-WebRequest", "IEX", "Invoke-Expression", "DownloadString", "DownloadFile", "curl", "wget")
// Look further for indications related to LLMNR/NBNS spoofing in command line
| where ProcessCommandLine has_any ("LLMNR", "NBNS", "NetBIOS", "spoof", "spoofer", "Responder", "Impacket", "Invoke-DNSResponse", "nbtstat")
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine
| top 10 by TimeGenerated desc
pass14.20s0.0024
gpt-4.1-nano
no answer
no query generatedfail50.00s0.0019
gpt-4o
no answer
no query generatedfail50.00s0.0563
gpt-5-high
Invoke-Inveigh
let psExecs = dynamic(["powershell.exe","pwsh.exe"]);
let fetchApis = dynamic([
  "invoke-webrequest","invoke-restmethod","new-object net.webclient","downloadstring","downloadfile",
  "[system.net.webclient]","start-bitstransfer","iwr","irm","wget","curl","iex","invoke-expression"
]);
let urlTokens = dynamic(["http://","https://"]);
// PowerShell processes
let psProcs = DeviceProcessEvents
| where FileName in~ (psExecs)
| project DeviceId, DeviceName, ProcessId, ProcessCreationTime, ProcessCommandLine, AccountDomain, AccountName;
// HTTP/S from those PowerShell processes (remote fetch evidence)
let psNet = DeviceNetworkEvents
| where InitiatingProcessFileName in~ (psExecs)
| where isnotempty(RemoteUrl) or RemotePort in (80,443,8080,8443)
| summarize NetFirst=min(TimeGenerated), NetLast=max(TimeGenerated),
            RemoteUrls=make_set(RemoteUrl, 100), RemoteIPs=make_set(RemoteIP, 100)
            by DeviceId, ProcId=tolong(InitiatingProcessId);
// PowerShell command/scriptblock content
let psContent = DeviceEvents
| where ActionType in~ ("PowerShellCommand","PowerShellScriptBlockLogging")
| extend PSContent = tostring(iff(ActionType == "PowerShellScriptBlockLogging", AdditionalFields.ScriptBlockText, AdditionalFields.Command))
| where isnotempty(PSContent)
| project DeviceId, PSTime=TimeGenerated, PSProcId=coalesce(tolong(ProcessId), tolong(InitiatingProcessId)), PSContent;
// Fetch indicators seen in PS content
let fetchEvidenceFromPSContent = psContent
| where tolower(PSContent) has_any (fetchApis) or PSContent has_any (urlTokens)
| summarize PSFetchFirst=min(PSTime), PSFetchLast=max(PSTime), PSFetchSamples=make_set(substring(PSContent,0,200), 20)
  by DeviceId, PSProcId;
// Listener activity (Inveigh / LLMNR/NBNS spoofing) from PS content
let listenersFromPS = psContent
| where tolower(PSContent) has "inveigh" or (tolower(PSContent) has "llmnr" and tolower(PSContent) has "nbns")
| extend ListenerCmdlet = extract(@"(?i)\b((?:Invoke|Start)-(?:Inveigh(?:Relay)?))\b", 1, PSContent)
| extend ListenerCmdlet = iif(isempty(ListenerCmdlet) and PSContent matches regex @"(?i)\bInveigh\b", "Invoke-Inveigh", ListenerCmdlet)
| project DeviceId, PSProcId, PSTime, PSContent, ListenerCmdlet;
// Fallback: listener evidence directly from PS process command line (if PS content not available)
let listenersFromProcCL = DeviceProcessEvents
| where FileName in~ (psExecs)
| where ProcessCommandLine matches regex @"(?i)\b(?:Invoke|Start)-(?:Inveigh(?:Relay)?)\b"
   or (tolower(ProcessCommandLine) has "inveigh" and (tolower(ProcessCommandLine) has "llmnr" or tolower(ProcessCommandLine) has "nbns"))
| extend ListenerCmdlet = extract(@"(?i)\b((?:Invoke|Start)-(?:Inveigh(?:Relay)?))\b", 1, ProcessCommandLine)
| project DeviceId, PSProcId=ProcessId, PSTime=ProcessCreationTime, PSContent=ProcessCommandLine, ListenerCmdlet;
let listeners = union listenersFromPS, listenersFromProcCL;
psProcs
| extend User = strcat(AccountDomain, "\\", AccountName),
         HasFetchCL = (tolower(ProcessCommandLine) has_any (fetchApis) and ProcessCommandLine has_any (urlTokens))
| join kind=leftouter (psNet) on DeviceId, $left.ProcessId == $right.ProcId
| join kind=leftouter (fetchEvidenceFromPSContent) on DeviceId, $left.ProcessId == $right.PSProcId
| extend HasFetch = HasFetchCL or isnotempty(RemoteUrls) or isnotempty(PSFetchSamples)
| where HasFetch
| join kind=inner (listeners) on DeviceId, $left.ProcessId == $right.PSProcId
| where PSTime between (ProcessCreationTime .. ProcessCreationTime + 6h)
| project ListenerTime=PSTime, DeviceName, User,
          ListenerCmdlet=iff(isempty(ListenerCmdlet), "Unknown (see PSContent)", ListenerCmdlet),
          PSContentSnippet = substring(PSContent, 0, 400),
          ProcessCreationTime, ProcessCommandLine,
          FetchEvidence = case(HasFetchCL and isnotempty(RemoteUrls), "CmdLine+HTTP",
                               HasFetchCL, "CmdLine",
                               isnotempty(RemoteUrls), "HTTP",
                               isnotempty(PSFetchSamples), "PSContent",
                               "Unknown"),
          RemoteUrls, RemoteIPs, PSFetchSamples
| order by ListenerTime desc
pass3399.42s0.1821
gpt-5-mini-high
Invoke-Inveigh
union DeviceProcessEvents, DeviceEvents, DeviceFileEvents
| extend Context = coalesce(ProcessCommandLine, InitiatingProcessCommandLine, tostring(AdditionalFields), FileOriginUrl, RemoteUrl, FileOriginReferrerUrl)
| where isnotempty(Context)
// focus on PowerShell (classic/pwsh)
| where tolower(Context) contains "powershell" or tolower(Context) contains "pwsh"
// look for remote fetch/execute patterns commonly used to fetch & run scripts
| where (
    tolower(Context) contains "iwr" or
    tolower(Context) contains "invoke-webrequest" or
    tolower(Context) contains "invoke-restmethod" or
    tolower(Context) contains "start-bitstransfer" or
    tolower(Context) contains "new-object net.webclient" or
    tolower(Context) contains "downloadstring" or
    tolower(Context) contains "downloadfile" or
    tolower(Context) contains "iex" or
    tolower(Context) contains "invoke-expression" or
    tolower(Context) contains "curl" or
    tolower(Context) contains "wget" or
    tolower(Context) contains "-encodedcommand" or
    tolower(Context) contains "-enc"
)
// and LLMNR/NBNS/spoofing tool keywords
| where (
    tolower(Context) contains "inveigh" or
    tolower(Context) contains "llmnr" or
    tolower(Context) contains "nbns" or
    tolower(Context) contains "responder" or
    tolower(Context) contains "nbtsspoof" or
    tolower(Context) contains "spoofer" or
    tolower(Context) contains "inveighrelay"
)
// try to extract the cmdlet that likely kicks off the listener (Invoke-*, Start-*, Inveigh/Responder)
| extend Cmdlet = extract(@"(?i)\b(Invoke-[A-Za-z0-9_]+|Start-[A-Za-z0-9_]+|Invoke-Inveigh|Invoke-InveighRelay|Inveigh|Responder|Start-Inveigh)\b", 1, Context)
// simple download URL grab (starts at "http" and reads up to 400 chars)
| extend httpIndex = indexof(Context, "http")
| extend DownloadUrl = iff(httpIndex >= 0, substring(Context, httpIndex, 400), "")
| project TimeGenerated, DeviceName, FileName, ProcessId, InitiatingProcessId, InitiatingProcessFileName, InitiatingProcessCommandLine, ProcessCommandLine, Cmdlet, DownloadUrl, FileOriginUrl, RemoteUrl, SourceSystem, Context
| where isnotempty(Cmdlet) or isnotempty(DownloadUrl)
| sort by TimeGenerated desc
| take 200
pass374.00s0.0196
gpt-5-mini-low
no answer
no query generatedfail50.00s0.0216
gpt-5-mini-medium
Invoke-Inveigh
// Hunt PowerShell fetching remote scripts that then perform LLMNR/NBNS spoofing and identify which cmdlet started the listener
let fetchMethods = dynamic(["Invoke-WebRequest","Invoke-RestMethod","iwr","wget","Start-BitsTransfer","IEX","Invoke-Expression","DownloadString","DownloadFile","WebClient","System.Net.WebClient","New-Object","curl"]);
let spoofIndicators = dynamic(["responder","inveigh","llmnr","nbns","nbt","mdns","multicastdns","spoof","llmnrsp","nbtname","nbnsr"]);

// PowerShell processes that may have fetched remote content or contain spoofing indicators
let PowerShellFetch = DeviceProcessEvents
| where FileName has_any ("powershell.exe","pwsh.exe")
| extend CmdLine = tostring(ProcessCommandLine)
| where CmdLine has_any (fetchMethods) or CmdLine has_any (spoofIndicators) or CmdLine matches regex "(?i)\biex\b" or CmdLine matches regex "(?i)\binvoke-expression\b"
| extend FetchMethodsFound = extract_all(@"(?i)\b(Invoke-WebRequest|Invoke-RestMethod|iwr|wget|Start-BitsTransfer|IEX|Invoke-Expression|DownloadString|DownloadFile|New-Object|System\.Net\.WebClient|curl)\b", CmdLine)
// Extract candidate listener-related cmdlets (Start-*, New-*, Invoke-*) and common listener names like Start-Responder, Start-InveighListener, Start-Listener
| extend ListenerCmdlets = extract_all(@"(?i)\b(Start|New|Invoke)-[A-Za-z0-9_-]+\b", CmdLine)
| extend ListenerCandidates = iff(array_length(ListenerCmdlets) > 0, strcat_array(ListenerCmdlets, ", "), "")
| project TimeGenerated, DeviceName, DeviceId, AccountName=InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessCommandLine, ProcessId, CmdLine, FetchMethodsFound, ListenerCandidates;

// Network activity on LLMNR(5355) and NBNS/NetBIOS(137) UDP ports
let NetLLMNR_NBNS = DeviceNetworkEvents
| where Protocol has "UDP"
| where LocalPort in (137, 5355)
| project NetTime = TimeGenerated, DeviceName, NetLocalPort = LocalPort, Protocol, NetInitiatingCmdLine = InitiatingProcessCommandLine, NetInitiatingFile = InitiatingProcessFileName;

// Correlate PowerShell fetches with LLMNR/NBNS network activity in a 5 minute window and summarize
PowerShellFetch
| join kind=leftouter NetLLMNR_NBNS on DeviceName
| where isnull(NetTime) or abs(datetime_diff('second', TimeGenerated, NetTime)) < 300
| summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Occurrences = count(), Devices = make_set(DeviceName), SamplePowerShellCmdLines = make_set(CmdLine, 10), SampleInitiatingProcessFiles = make_set(InitiatingProcessFileName, 10), SampleInitiatingCmds = make_set(InitiatingProcessCommandLine, 10), FetchMethods = make_set(FetchMethodsFound), ListenerCandidates = make_set(ListenerCandidates), NetworkPorts = make_set(NetLocalPort) by InitiatingProcessFileName, InitiatingProcessCommandLine
| order by FirstSeen desc
| project FirstSeen, LastSeen, Occurrences, InitiatingProcessFileName, InitiatingProcessCommandLine, Devices, ListenerCandidates, FetchMethods, SamplePowerShellCmdLines, SampleInitiatingProcessFiles, SampleInitiatingCmds, NetworkPorts
pass366.31s0.0184
gpt-5-nano-high
no answer
no query generatedfail50.00s0.0089
gpt-5-nano-medium
no answer
no query generatedfail50.00s0.0122
grok-3-beta
no answer
no query generatedfail50.00s0.1244
grok-3-mini-beta
Invoke-Inveigh
DeviceProcessEvents
| where FileName == 'powershell.exe'
| where ProcessCommandLine has_any ('Invoke-WebRequest', 'IWR', 'download', 'LLMNR', 'NBNS', 'spoofing')
| project DeviceName, AccountName, ProcessCommandLine, Timestamp
| summarize arg_max(Timestamp, *) by ProcessCommandLine
| project ProcessCommandLine
pass114.75s0.0022
o1-high
Invoke-Inveigh
DeviceProcessEvents
| where (FileName == "powershell.exe" or FileName == "pwsh.exe")
// Look for commands that reference a remote HTTP source
| where ProcessCommandLine has_any ("http://", "https://")
// Identify the download or web request methods
| where ProcessCommandLine has_any ("Invoke-WebRequest", "Invoke-RestMethod", "iwr", "irm", "New-Object Net.WebClient", "wget")
// Look for signs of LLMNR/NBNS spoofing or references to Responder
| where ProcessCommandLine has_any ("LLMNR", "NBNS", "Responder", "spoofing")
// Extract which cmdlet actually started the listener
| extend SuspectedCmdlet = extract(@"(Invoke-WebRequest|Invoke-RestMethod|iwr|irm|New-Object\s+[Nn]et\.WebClient|wget|Start-Process|netsh)",1,ProcessCommandLine)
| project TimeGenerated, DeviceName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, SuspectedCmdlet
| order by TimeGenerated desc
pass145.83s0.3035
o1-low
IEX
DeviceProcessEvents
| where FileName in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe")
| where isnotempty(ProcessCommandLine)
// Identify references to remote script fetching and LLMNR/NBNS spoofing
| where ProcessCommandLine has_any ("IEX", "Invoke-WebRequest", "Invoke-Expression", "Net.WebClient", "DownloadString", "Inveigh", "Responder")
| where ProcessCommandLine has_any ("LLMNR", "NBNS")
// Extract the cmdlet that might have kicked off the listener
| extend ListenerCmdlet = extract(@"(?i)(Start-Inveigh|Invoke-Inveigh|Invoke-Expression|Invoke-WebRequest|IEX|Net\\.WebClient|DownloadString)", 1, ProcessCommandLine)
| project TimeGenerated, DeviceName, FileName, ProcessCommandLine, ListenerCmdlet
| order by TimeGenerated desc
fail5123.03s0.8322
o3-high
Invoke-Inveigh
// Hunt for PowerShell-delivered LLMNR / NBNS spoofing listeners
// Time range is taken from the query run-pane – nothing is hard-coded here
// 1.  Look for a PowerShell process that downloads / fetches a remote script
// 2.  Correlate that process (or its child) starting a listener such as Inveigh / Responder
// 3.  Surface which cmdlet actually enabled the listener

let TimeWindow          = 1h;      // Listener must appear within 1-hour of the download (tune as required)

let RemoteScriptSigns   = dynamic(
    ["invoke-webrequest", "invoke-restmethod", "downloadstring",
     "new-object net.webclient", "start-bitstransfer", "curl", "wget"]);

let ListenerSigns       = dynamic(
    ["invoke-inveigh", "start-inveigh", "inveigh", "responder", "-llmnr", "-nbns"]);

// ---- Phase-1 : PowerShell that fetches a remote script -------------------------------------
let RemoteFetch = DeviceProcessEvents
| where FileName in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe")
| extend CmdLower = tolower(ProcessCommandLine)
| where CmdLower has_any (RemoteScriptSigns)
| project
        DeviceId,
        DeviceName,
        FetchTime         = TimeGenerated,
        FetchProcessId    = ProcessId,
        FetchSessionId    = CreatedProcessSessionId,
        FetchLogonId      = LogonId,
        FetchCmd          = ProcessCommandLine,
        FetchAccount      = strcat(AccountDomain, "\\", AccountName);

// ---- Phase-2 : PowerShell that starts LLMNR/NBNS spoofing listener --------------------------
let ListenerStart = DeviceProcessEvents
| where FileName in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe")
| extend CmdLower = tolower(ProcessCommandLine)
| where CmdLower has_any (ListenerSigns)
| extend ListenerCmdlet = case(
        CmdLower has "start-inveigh",   "Start-Inveigh",
        CmdLower has "invoke-inveigh",  "Invoke-Inveigh",
        CmdLower matches regex @"\\binveigh\\b", "Inveigh",
        CmdLower contains "responder",   "Responder",
        "Unknown")
| project
        DeviceId,
        DeviceName,
        ListenerTime               = TimeGenerated,
        ListenerProcessId          = ProcessId,
        ListenerInitiatingProcessId= InitiatingProcessId,
        ListenerSessionId          = CreatedProcessSessionId,
        ListenerLogonId            = LogonId,
        ListenerCmd                = ProcessCommandLine,
        ListenerCmdlet,
        ListenerAccount            = strcat(AccountDomain, "\\", AccountName);

// ---- Correlate the two phases --------------------------------------------------------------
RemoteFetch
| join kind=inner (ListenerStart) on DeviceId
| where ListenerTime between (FetchTime .. FetchTime + TimeWindow)
      and (
            ListenerProcessId == FetchProcessId                      // same process
         or ListenerInitiatingProcessId == FetchProcessId            // child proc started by downloader
         or ListenerSessionId          == FetchSessionId             // same PowerShell run-space
         or ListenerLogonId            == FetchLogonId               // fall-back correlation
      )
| project
        DeviceName,
        FetchTime,
        ListenerTime,
        TimeDelta = ListenerTime - FetchTime,
        FetchCmd,
        ListenerCmd,
        ListenerCmdlet,
        FetchAccount,
        ListenerAccount,
        DeviceId,
        FetchProcessId,
        ListenerProcessId,
        ListenerInitiatingProcessId
| order by FetchTime desc
pass3123.23s0.0865
o3-mini-high
set-itemproperty
DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has "DownloadString" and ProcessCommandLine has "IEX"
| extend ListenerCmdlet = extract(@"(?i)(\w+-\w+)\s+.*", 1, ProcessCommandLine)
| project TimeGenerated, DeviceName, ProcessCommandLine, ListenerCmdlet
| sort by TimeGenerated desc
fail126.79s0.0193
o3-mini-low
Invoke-Inveigh
DeviceProcessEvents
| where FileName =~ "powershell.exe"
// Look for remote execution (script fetched via http(s)) and signs of LLMNR/NBNS spoofing
| where ProcessCommandLine has_any ("http://", "https://")
      and (ProcessCommandLine has "LLMNR" or ProcessCommandLine has "NBNS" or ProcessCommandLine has "spoof")
// Extract the first PowerShell cmdlet that appears in the command line (e.g. Invoke-Expression, Invoke-WebRequest, etc.)
| extend ListenerCmdlet = extract("(?i)(Invoke-[a-zA-Z]+)", 1, ProcessCommandLine)
| where isnotempty(ListenerCmdlet)
| summarize Count = count() by ListenerCmdlet, bin(TimeGenerated, 1h)
| sort by TimeGenerated desc
pass126.09s0.0144
o4-mini-high
Invoke-Inveigh
DeviceProcessEvents
// Time range is applied externally by the hunting dashboard or query tool
| where FileName in ("powershell.exe", "pwsh.exe")
// Identify remote fetch and execution patterns
| where ProcessCommandLine has_any ("Invoke-WebRequest", "iwr", "DownloadString", "New-Object Net.WebClient", "Invoke-Expression", "iex")
// Narrow to LLMNR/NBNS spoofing indicators
| where ProcessCommandLine matches regex @"(?i)\b(?:llmnr|nbns|spoof)\b"
// Extract the spoofing-related cmdlet (e.g., Invoke-LlmnrSpoofing, Start-NbnsSpoofing)
| extend SpoofingCmdlet = extract(@"(?i)\b((?:Invoke|Start)-[A-Za-z0-9]+)\b", 1, ProcessCommandLine)
| where isnotempty(SpoofingCmdlet)
// Summarize which cmdlet kicked off the listener
| summarize Occurrences = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), ExampleCommand = any(ProcessCommandLine) by SpoofingCmdlet
| order by Occurrences desc
pass5181.57s0.0542
o4-mini-low
no answer
no query generatedfail50.00s0.0507