Private/ConvertTo-CustomDetectionTactics.ps1
|
function ConvertTo-CustomDetectionTactics { <# .SYNOPSIS Builds the alertTemplate.tactics collection. .DESCRIPTION An explicit tactics list wins. Otherwise the alert category becomes the single tactic and the flat technique list is grouped so that sub-techniques sit under their parent technique. #> [CmdletBinding()] [OutputType([object[]])] param( [Parameter()] [AllowNull()] [AllowEmptyString()] [string]$Category, [Parameter()] [AllowNull()] [object[]]$Techniques, [Parameter()] [AllowNull()] [object]$Tactics ) function Group-Technique { param([object[]]$Items) $parents = [ordered]@{} foreach ($item in $Items) { if ($null -eq $item) { continue } $techniqueId = $null $subTechniqueIds = @() if ($item -is [string]) { $techniqueId = $item.Trim() } else { $map = ConvertTo-CustomDetectionHashtable -InputObject $item if (-not $map) { continue } $techniqueId = "$($map['technique'])".Trim() if ($map['subTechniques']) { $subTechniqueIds = @($map['subTechniques'] | ForEach-Object { "$_".Trim() }) } } if ([string]::IsNullOrEmpty($techniqueId)) { continue } $parentId = $techniqueId.Split('.')[0] if (-not $parents.Contains($parentId)) { $parents[$parentId] = [System.Collections.Generic.List[string]]::new() } if ($techniqueId -ne $parentId) { $subTechniqueIds += $techniqueId } foreach ($sub in $subTechniqueIds) { if ($sub -and -not $parents[$parentId].Contains($sub)) { $parents[$parentId].Add($sub) } } } $grouped = [System.Collections.Generic.List[object]]::new() foreach ($parentId in ($parents.Keys | Sort-Object)) { $entry = [ordered]@{ technique = $parentId } if ($parents[$parentId].Count -gt 0) { $entry['subTechniques'] = [object[]]@($parents[$parentId] | Sort-Object) } $grouped.Add($entry) } return $grouped.ToArray() } if ($Tactics) { $result = [System.Collections.Generic.List[object]]::new() foreach ($tacticItem in @($Tactics)) { $map = ConvertTo-CustomDetectionHashtable -InputObject $tacticItem if (-not $map -or [string]::IsNullOrWhiteSpace("$($map['tactic'])")) { throw "Each tactics entry needs a non-empty 'tactic' value." } $entry = [ordered]@{ tactic = "$($map['tactic'])".Trim() } $grouped = @() if ($map['techniques']) { $grouped = @(Group-Technique -Items @($map['techniques'])) } if ($grouped.Count -gt 0) { $entry['techniques'] = [object[]]$grouped } $result.Add($entry) } return $result.ToArray() } if ([string]::IsNullOrWhiteSpace($Category)) { if ($Techniques -and @($Techniques).Count -gt 0) { throw 'MITRE techniques need an alert category or an explicit tactics list.' } return $null } $entry = [ordered]@{ tactic = $Category.Trim() } $grouped = @() if ($Techniques) { $grouped = @(Group-Technique -Items $Techniques) } if ($grouped.Count -gt 0) { $entry['techniques'] = [object[]]$grouped } return $entry } |