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
DiscoveryT1018 Remote System Discovery

Event coverage

Rule body kusto

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

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)

References args (defined above).

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

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
ActingProcessFileNamein
  • cmd.exe
  • powershell.exe
  • pwsh.exe
ActingProcessNamematch
  • cmd.exe
  • powershell.exe
  • pwsh.exe
CommandLinematch
  • Domain Admins corpus 3 (sigma 2, splunk 1)
  • adinfo corpus 2 (sigma 2)
  • computers_pwdnotreqd corpus 2 (sigma 2)
  • dclist
  • dcmodes corpus 2 (sigma 2)
  • domainlist corpus 2 (sigma 2)
  • objectcategory
  • objectcategory=*
  • objectcategory=computer
  • objectcategory=person
  • trustdmp corpus 2 (sigma 2)
Processends_with
  • AdFind.exe
TargetProcessSHA256eq
  • c92c158d7c37fea795114fa6491fe5f145ad2f8c08776b18ae79db811e8e36a3 transforms: cased corpus 2 (kusto 2)

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
ActingProcessFileNameextend
AlgorithmTypeextend
AccountNTDomainextend
AccountNameextend
HostNameextend
HostNameDomainextend