// 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
ModelAnswerKQL queryStatusTriesTimeCost
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
pass110.24s0.0045
gpt-35-turbo
no answer
no query generatedfail50.00s0.0121
gpt-4-turbo-2024-04-09
no answer
no query generatedfail50.00s0.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
pass14.30s0.0110
gpt-4.1-finetuned
no answer
no query generatedfail50.00s0.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
pass125.77s0.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
fail526.40s0.0015
gpt-4o
no answer
no query generatedfail50.00s0.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
pass4295.19s0.1891
gpt-5-mini-high
no answer
no query generatedfail50.00s0.0195
gpt-5-mini-low
no answer
no query generatedfail50.00s0.0088
gpt-5-mini-medium
no answer
no query generatedfail50.00s0.0252
gpt-5-nano-high
no answer
no query generatedfail50.00s0.0075
gpt-5-nano-medium
no answer
no query generatedfail50.00s0.0088
grok-3-beta
no answer
no query generatedfail50.00s0.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
pass113.77s0.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
pass392.64s0.5674
o1-low
no answer
no query generatedfail50.00s0.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
pass5236.86s0.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
pass111.13s0.0084
o3-mini-low
no answer
no query generatedfail50.00s0.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
pass4150.26s0.0429
o4-mini-low
no answer
no query generatedfail50.00s0.0445