01 / LOG ANALYSIS AND INDICATORS · LESSON 04

Match indicators precisely

Match parsed domain fields against an indicator set while excluding misleading substrings.

12 min · learn at your pace

Decide what a match means

An indicator list is useful only when matching has a clear meaning. This exercise uses exact domain membership after two explicit normalisation steps: remove trailing dots and convert to lowercase. It does not match arbitrary subdomains or lookalike names.

beacon.example and BEACON.EXAMPLE. match the same supplied indicator. notbeacon.example and beacon.example.safe.example do not. A substring search would include those unrelated names. A regular expression also gives characters such as . special meaning unless escaped.

Parse, normalise, compare

Load the JSON list and CSV observations as objects. Normalise both sides consistently, then use -in or -contains for membership. These are collection operators: $value -in $collection and $collection -contains $value express the same membership check.

'BEACON.EXAMPLE.'.TrimEnd('.').ToLowerInvariant()

Keep the record ID and computer with every result. Two matching observations are still two records, even if they refer to the same indicator. A downstream analyst needs to know which host queried the name and where the evidence came from.

All domains here are fictional .example names. In an investigation, an indicator match is context to review, not a verdict. Indicator quality, observation time, shared infrastructure, and legitimate business use can all affect interpretation.

Go deeper

Read the official reference.

Try an example.ps1
(Get-Content '/home/user/indicators.json' -Raw | ConvertFrom-Json).Domains
YOUR TURN

Put it into practice.

Match dns.csv Query values to indicators.json Domains using exact membership after removing trailing dots and converting to lowercase. Emit RecordId, Computer, and Indicator (the normalised domain) for each matching observation. Preserve duplicate observations; exclude substring lookalikes.

See an explained solution
One way to solve it.ps1
$indicators = Get-Content '/home/user/indicators.json' -Raw | ConvertFrom-Json
$known = @($indicators.Domains | ForEach-Object { $_.TrimEnd('.').ToLowerInvariant() })
foreach ($row in Import-Csv '/home/user/dns.csv') {
    $query = $row.Query.TrimEnd('.').ToLowerInvariant()
    if ($query -in $known) { [pscustomobject]@{ RecordId=$row.RecordId; Computer=$row.Computer; Indicator=$query } }
}

Membership compares whole normalised fields. The output retains both D01 and D02 because they are separate observations, while excluding names that merely contain an indicator.