Detection rules › Kusto

Bitsadmin Activity

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

Background Intelligent Transfer Service (BITS) is a way to reliably download files from webservers or SMB servers. This service is commonly used for legitimate purposes, but can also be used as part of a malware downloader. Additionally, bitsadmin can be used to upload files and therefore can be used for data exfiltration. This query will identify use of bitsadmin.exe for either purpose and will identify directionality file transfer directionality.

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: 2a1dc4c2-a8d6-4a0e-8539-9b971c851195
name: Bitsadmin Activity
description: |
  Background Intelligent Transfer Service (BITS) is a way to reliably download files from webservers or SMB servers. 
  This service is commonly used for legitimate purposes, but can also be used as part of a malware downloader. 
  Additionally, bitsadmin can be used to upload files and therefore can be used for data exfiltration. This
  query will identify use of bitsadmin.exe for either purpose and will identify directionality file transfer
  directionality.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceProcessEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics: 
  - Persistence
  - CommandAndControl
  - Exfiltration
relevantTechniques:
  - T1197
  - T1105
  - T1048
query: |
  DeviceProcessEvents
  | where 
      (FileName =~ "bitsadmin.exe" or column_ifexists('ProcessVersionInfoOriginalFileName','ColumnNotAvailable') =~ 'bitsadmin.exe')
      and ProcessCommandLine has_any ('/Transfer','/AddFile', '/AddFileSet','/AddFileWithRanges')
  | extend 
      ParsedCommandLine = parse_command_line(ProcessCommandLine,'windows')
  | extend     
      RemoteUrl = tostring(ParsedCommandLine[-2]),
      LocalFile= tostring(ParsedCommandLine[-1]),
      Direction = iff(ProcessCommandLine has "/Upload", 'Upload', 'Download')
  | project-reorder 
      TimeGenerated,
      DeviceId,
      DeviceName,
      Direction,
      RemoteUrl,
      LocalFile,
      InitiatingProcessFolderPath,
      InitiatingProcessAccountDomain,
      InitiatingProcessAccountName,
      InitiatingProcessSHA256,
      ProcessId,
      ProcessCommandLine
  | extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
  | extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: ProcessId
      - identifier: CommandLine
        columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where 
    (FileName =~ "bitsadmin.exe" or column_ifexists('ProcessVersionInfoOriginalFileName','ColumnNotAvailable') =~ 'bitsadmin.exe')
    and ProcessCommandLine has_any ('/Transfer','/AddFile', '/AddFileSet','/AddFileWithRanges')

Stage 3: extend

| extend 
    ParsedCommandLine = parse_command_line(ProcessCommandLine,'windows')

Stage 4: extend

| extend     
    RemoteUrl = tostring(ParsedCommandLine[-2]),
    LocalFile= tostring(ParsedCommandLine[-1]),
    Direction = iff(ProcessCommandLine has "/Upload", 'Upload', 'Download')
Direction =
ifProcessCommandLine has "/Upload"'Upload'
else'Download'

Stage 5: project-reorder

| project-reorder 
    TimeGenerated,
    DeviceId,
    DeviceName,
    Direction,
    RemoteUrl,
    LocalFile,
    InitiatingProcessFolderPath,
    InitiatingProcessAccountDomain,
    InitiatingProcessAccountName,
    InitiatingProcessSHA256,
    ProcessId,
    ProcessCommandLine

Stage 6: extend

| extend HostName = iff(DeviceName has '.', substring(DeviceName, 0, indexof(DeviceName, '.')), DeviceName)
HostName =
ifDeviceName has "."substring(DeviceName, 0, indexof(DeviceName, '.'))
elseDeviceName

Stage 7: extend

| extend DnsDomain = iff(DeviceName has '.', substring(DeviceName, indexof(DeviceName, '.') + 1), "")
DnsDomain =
ifDeviceName has "."substring(DeviceName, (indexof(DeviceName, '.') + 1))
else""

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
FileNameeq
  • bitsadmin.exe
ProcessCommandLinematch
  • /AddFile corpus 2 (sigma 1, kusto 1)
  • /AddFileSet
  • /AddFileWithRanges
  • /Transfer corpus 2 (sigma 1, kusto 1)
ProcessVersionInfoOriginalFileNameeq
  • bitsadmin.exe transforms: column_ifexists corpus 12 (sigma 9, splunk 2, kusto 1)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
ParsedCommandLineextend
Directionextend
LocalFileextend
RemoteUrlextend
HostNameextend
DnsDomainextend