// question
A suspicious BITS transfer was orchestrated via bitsadmin.exe on Windows, creating a job to download and then execute a payload. Investigate the process event logs to determine what custom job name was specified when the BITS job was created.
Reference KQL answer and model-by-model comparison.
62a06ec5-5754-47d2-bcfc-123d8314c6ae
question id
9/23
models correct
1
valid answer
39%
success rate
// reference answer
Human-validated correct answer for this question.
AtomicBITS
// model comparison
| Model | Answer | KQL query | Status | Tries | Time | Cost |
|---|---|---|---|---|---|---|
| gemini-2.5-flash-preview-04-17 | AtomicBITS | DeviceProcessEvents | where FileName =~ "bitsadmin.exe" | where ProcessCommandLine contains "/create" or ProcessCommandLine contains "/addfile" | extend JobName_create = extract(@"/create\s+([^\s]+)", 1, ProcessCommandLine) | extend JobName_addfile = extract(@"/addfile\s+([^\s]+)", 1, ProcessCommandLine) | extend CustomJobName = coalesce(JobName_create, JobName_addfile) | where isnotnull(CustomJobName) | project Timestamp, DeviceName, ProcessCommandLine, CustomJobName | pass | 1 | 10.24s | 0.0045 |
| gpt-35-turbo | no answer | no query generated | fail | 5 | 0.00s | 0.0121 |
| gpt-4-turbo-2024-04-09 | no answer | no query generated | fail | 5 | 0.00s | 0.2233 |
| gpt-4.1 | AtomicBITS | DeviceProcessEvents | where FileName =~ "bitsadmin.exe" | where ProcessCommandLine has "/create" | extend JobName = extract(@"/create\s+([^\s]+)", 1, ProcessCommandLine) | project TimeGenerated, DeviceName, AccountName, ProcessCommandLine, JobName | where isnotempty(JobName) | sort by TimeGenerated desc | pass | 1 | 4.30s | 0.0110 |
| gpt-4.1-finetuned | no answer | no query generated | fail | 5 | 0.00s | 0.0406 |
| gpt-4.1-mini | AtomicBITS | DeviceProcessEvents | where FileName =~ "bitsadmin.exe" | where ProcessCommandLine contains "create" | extend JobName = extract(@"create\s+(\S+)", 1, ProcessCommandLine) | project DeviceName, TimeGenerated, JobName, InitiatingProcessAccountName, ProcessCommandLine | where isnotempty(JobName) | sort by TimeGenerated desc | pass | 1 | 25.77s | 0.0023 |
| gpt-4.1-nano | None | DeviceProcessEvents | where FileName endswith 'bitsadmin.exe' | where ProcessCommandLine contains 'create' | extend JobName = extract(@'\s/\s*(\S+)$', 1, ProcessCommandLine) | project TimeGenerated, DeviceName, JobName | fail | 5 | 26.40s | 0.0015 |
| gpt-4o | no answer | no query generated | fail | 5 | 0.00s | 0.0597 |
| gpt-5-high | AtomicBITS | DeviceProcessEvents | where ActionType == "ProcessCreated" | where FileName =~ "bitsadmin.exe" or ProcessCommandLine has "bitsadmin" // Extract job name from /create or /transfer (handles quoted and unquoted names, optional /download|/upload|/upload-reply) | extend CreateQuoted = extract(@"(?i)(?:^|\s)(?:-|/)create\s+(?:(?:-|/)?(?:download|upload|upload-reply)\s+)?""([^""]+)""", 1, ProcessCommandLine) | extend CreateUnquoted = iff(isempty(CreateQuoted), extract(@"(?i)(?:^|\s)(?:-|/)create\s+(?:(?:-|/)?(?:download|upload|upload-reply)\s+)?(\S+)", 1, ProcessCommandLine), "") | extend TransferQuoted = iff(isempty(CreateQuoted) and isempty(CreateUnquoted), extract(@"(?i)(?:^|\s)(?:-|/)transfer\s+(?:(?:-|/)?(?:download|upload|upload-reply)\s+)?""([^""]+)""", 1, ProcessCommandLine), "") | extend TransferUnquoted = iff(isempty(CreateQuoted) and isempty(CreateUnquoted) and isempty(TransferQuoted), extract(@"(?i)(?:^|\s)(?:-|/)transfer\s+(?:(?:-|/)?(?:download|upload|upload-reply)\s+)?(\S+)", 1, ProcessCommandLine), "") | extend JobName = coalesce(CreateQuoted, CreateUnquoted, TransferQuoted, TransferUnquoted) | where isnotempty(JobName) | project TimeGenerated, DeviceId, DeviceName, AccountDomain, AccountName, ProcessCommandLine, JobName | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Examples=make_set(ProcessCommandLine, 3) by DeviceId, DeviceName, AccountDomain, AccountName, JobName | order by LastSeen desc | pass | 4 | 295.19s | 0.1891 |
| gpt-5-mini-high | no answer | no query generated | fail | 5 | 0.00s | 0.0195 |
| gpt-5-mini-low | no answer | no query generated | fail | 5 | 0.00s | 0.0088 |
| gpt-5-mini-medium | no answer | no query generated | fail | 5 | 0.00s | 0.0252 |
| gpt-5-nano-high | no answer | no query generated | fail | 5 | 0.00s | 0.0075 |
| gpt-5-nano-medium | no answer | no query generated | fail | 5 | 0.00s | 0.0088 |
| grok-3-beta | no answer | no query generated | fail | 5 | 0.00s | 0.0876 |
| grok-3-mini-beta | AtomicBITS | DeviceProcessEvents | where FileName =~ 'bitsadmin.exe' | where ProcessCommandLine contains '/create' | extend JobName = extract(@'/create\s+([^\s]+)', 1, ProcessCommandLine) | project Timestamp, DeviceName, ProcessCommandLine, JobName | summarize JobNames = makeset(JobName) by DeviceName | pass | 1 | 13.77s | 0.0015 |
| o1-high | AtomicBITS | DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where FileName =~ "bitsadmin.exe"
| where ProcessCommandLine has "/create"
// Use a regex parse that captures either a quoted or unquoted job name
| parse kind=regex ProcessCommandLine with ".*bitsadmin\\.exe\\s+/create\\s+(\"[^\"]+\"|\\S+).*" JobName
// Clean up any surrounding double-quotes
| extend CustomJobName = trim('"', JobName)
| project TimeGenerated, DeviceName, InitiatingProcessAccountName, ProcessCommandLine, CustomJobName | pass | 3 | 92.64s | 0.5674 |
| o1-low | no answer | no query generated | fail | 5 | 0.00s | 0.9217 |
| o3-high | AtomicBITS | DeviceProcessEvents
// -- filter to bitsadmin executions that create a BITS job ------------------------------------
| where FileName =~ 'bitsadmin.exe' or ProcessCommandLine has_cs 'bitsadmin.exe'
| extend cmd = tostring(ProcessCommandLine)
// keep only invocations that attempt to *create* a job
| where tolower(cmd) has '/create'
// -- extract the argument that represents the custom job name ---------------------------------
| extend idx = indexof(tolower(cmd), '/create') // location of the verb
| extend afterCreate = substring(cmd, idx + 7) // text that follows "/create"
| extend afterCreate = trim_start(' ', afterCreate) // remove leading spaces
| extend tokens = split(afterCreate, ' ') // tokenise
| extend firstTok = tostring(tokens[0]) // first argument after the verb
// if the first argument is itself a switch (/download or /upload) take the next one instead
| extend jobTok = case(tolower(firstTok) == '/download' or tolower(firstTok) == '/upload',
tostring(tokens[1]),
firstTok)
| extend JobName = trim("'\"", jobTok) // strip any surrounding quotes
| where isnotempty(JobName)
// -- output ------------------------------------------------------------------------
| project TimeGenerated, DeviceName, DeviceId, AccountName, ProcessCommandLine, JobName
| summarize Executions = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by DeviceName, DeviceId, AccountName, JobName
| order by LastSeen desc | pass | 5 | 236.86s | 0.1186 |
| o3-mini-high | AtomicBITS | DeviceProcessEvents | where tolower(InitiatingProcessFileName) == "bitsadmin.exe" or tolower(FileName) == "bitsadmin.exe" | where ProcessCommandLine has "/create" | extend CustomJobName = extract(@"/create\s+(\S+)", 1, ProcessCommandLine) | project TimeGenerated, DeviceName, ProcessCommandLine, CustomJobName | order by TimeGenerated desc | pass | 1 | 11.13s | 0.0084 |
| o3-mini-low | no answer | no query generated | fail | 5 | 0.00s | 0.0347 |
| o4-mini-high | AtomicBITS | DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where FileName has_cs "bitsadmin.exe"
| where ProcessCommandLine has_cs "/create"
| extend JobName = extract('(?i)/create\\s+"?([^"\\s]+)"?', 1, ProcessCommandLine)
| where isnotempty(JobName)
| project TimeGenerated, DeviceName, AccountName, ProcessId, JobName, ProcessCommandLine | pass | 4 | 150.26s | 0.0429 |
| o4-mini-low | no answer | no query generated | fail | 5 | 0.00s | 0.0445 |