Detection rules › Kusto

Probable AdFind Recon Tool Usage (Normalized Process Events)

Severity
high
Time window
1h
Author
Yuval Naor
Source
github.com/Azure/Azure-Sentinel

Identifies the host and account that executed AdFind by hash and filename in addition to common and unique flags that are used by many threat actors in discovery. To use this analytics rule, make sure you have deployed the ASIM normalization parsers

MITRE ATT&CK coverage

TacticTechniques
Discovery

Telemetry coverage

Rule body

id: 45076281-35ae-45e0-b443-c32aa0baf965
name: Probable AdFind Recon Tool Usage (Normalized Process Events)
description: |
  'Identifies the host and account that executed AdFind by hash and filename in addition to common and unique flags that are used by many threat actors in discovery.
  To use this analytics rule, make sure you have deployed the [ASIM normalization parsers](https://aka.ms/ASimProcessEvent)'
severity: High
requiredDataConnectors: []
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Discovery
relevantTechniques:
  - T1018
tags:
  - Id: c63ae777-d5e0-4113-8c9a-c2c9d3d09fcd
    version: 1.0.0
  - Schema: ASIMProcessEvent
    SchemaVersion: 0.1.0

query: |
  let args = dynamic(["objectcategory","domainlist","dcmodes","adinfo","trustdmp","computers_pwdnotreqd","Domain Admins", "objectcategory=person", "objectcategory=computer", "objectcategory=*","dclist"]);
  let parentProcesses = dynamic(["pwsh.exe","powershell.exe","cmd.exe"]);
  imProcessCreate
  //looks for execution from a shell
  | where ActingProcessName has_any (parentProcesses)
  | extend ActingProcessFileName = tostring(split(ActingProcessName, '\\')[-1])
  | where ActingProcessFileName in~ (parentProcesses)
  // main filter
  | where Process hassuffix "AdFind.exe" or TargetProcessSHA256 == "c92c158d7c37fea795114fa6491fe5f145ad2f8c08776b18ae79db811e8e36a3"
  // AdFind common Flags to check for from various threat actor TTPs
  or CommandLine has_any (args)
  | extend AlgorithmType = "SHA256"
  | extend AccountName = tostring(split(User, @'\')[1]), AccountNTDomain = tostring(split(User, @'\')[0])
  | extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountNTDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Dvc
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: ActingProcessName
      - identifier: CommandLine
        columnName: CommandLine
  - entityType: FileHash
    fieldMappings:
      - identifier: Algorithm
        columnName: AlgorithmType
      - identifier: Value
        columnName: TargetProcessSHA256
version: 1.1.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Yuval Naor
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Intelligence" ]

Stages and Predicates

Parameters

let parentProcesses = dynamic(["pwsh.exe","powershell.exe","cmd.exe"]);

Let binding: args used in Stage 5

let args = dynamic(["objectcategory","domainlist","dcmodes","adinfo","trustdmp","computers_pwdnotreqd","Domain Admins", "objectcategory=person", "objectcategory=computer", "objectcategory=*","dclist"]);

Stage 1: source

imProcessCreate

Stage 2: where

| where ActingProcessName has_any (parentProcesses)

Stage 3: extend

| extend ActingProcessFileName = tostring(split(ActingProcessName, '\\')[-1])

Stage 4: where

| where ActingProcessFileName in~ (parentProcesses)

Stage 5: where

| where Process hassuffix "AdFind.exe" or TargetProcessSHA256 == "c92c158d7c37fea795114fa6491fe5f145ad2f8c08776b18ae79db811e8e36a3"
or CommandLine has_any (args)

Stage 6: extend (4 consecutive steps)

| extend AlgorithmType = "SHA256"
| extend AccountName = tostring(split(User, @'\')[1]), AccountNTDomain = tostring(split(User, @'\')[0])
| extend HostName = tostring(split(Dvc, ".")[0]), DomainIndex = toint(indexof(Dvc, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Dvc, DomainIndex + 1), Dvc)

Stage 7: project-away

| project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActingProcessFileNamein
  • cmd.exe
  • powershell.exe
  • pwsh.exe
field:"ActingProcessFileName" kind:in
ActingProcessNamematch
  • cmd.exe transforms: term
  • powershell.exe transforms: term
  • pwsh.exe transforms: term
field:"ParentImage" kind:match
CommandLinematch
  • Domain Admins transforms: term corpus 5 (sigma 2, kusto 2, splunk 1)
  • adinfo transforms: term corpus 4 (sigma 2, kusto 2)
  • computers_pwdnotreqd transforms: term corpus 4 (sigma 2, kusto 2)
  • dclist transforms: term corpus 2 (kusto 2)
  • dcmodes transforms: term corpus 4 (sigma 2, kusto 2)
  • domainlist transforms: term corpus 4 (sigma 2, kusto 2)
  • objectcategory transforms: term corpus 2 (kusto 2)
  • objectcategory=* transforms: term corpus 2 (kusto 2)
  • objectcategory=computer transforms: term corpus 2 (kusto 2)
  • objectcategory=person transforms: term corpus 2 (kusto 2)
  • trustdmp transforms: term corpus 4 (sigma 2, kusto 2)
field:"CommandLine" kind:match
Processends_with
  • AdFind.exe
field:"Process" kind:ends_with value:"AdFind.exe"
TargetProcessSHA256eq
  • c92c158d7c37fea795114fa6491fe5f145ad2f8c08776b18ae79db811e8e36a3 corpus 2 (kusto 2)
field:"sha256" kind:eq value:"c92c158d7c37fea795114fa6491fe5f145ad2f8c08776b18ae79db811e8e36a3"

Output fields

These fields are emitted when the rule matches.

FieldSource
ActingProcessFileNameextend
AlgorithmTypeextend
AccountNTDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend