Private/Threat.Trigger.ps1
|
function Get-OIStringByteEnd { # Absolute offset of the LAST byte of an extracted string. ASCII is one byte per # char; UTF-16LE is two. Length is the character count from Get-OIByteString. [CmdletBinding()] param( [Parameter(Mandatory = $true)] $StringHit ) $byteLength = if ($StringHit.Encoding -eq 'Unicode') { [int64]$StringHit.Length * 2 } else { [int64]$StringHit.Length } return [int64]$StringHit.Offset + $byteLength - 1 } function Get-OIDetectionTrigger { <# Correlates a detection boundary to the file content that most likely produced it. A prefix boundary means the byte at BoundaryOffset is the LAST byte of the earliest detected prefix, so the triggering content is a run of bytes ENDING at that offset. Given the already-read region around the boundary, this reports: the PE section it falls in, the entropy of the run up to the boundary (plaintext vs packed), and the extracted strings that end at or straddle the boundary ranked by proximity - i.e. the candidate signature content. Pure and cross-platform; the public wrapper does the I/O. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [byte[]]$RegionBytes, [Parameter(Mandatory = $true)] [int64]$RegionStart, [Parameter(Mandatory = $true)] [int64]$BoundaryOffset, [Parameter(Mandatory = $true)] [int64]$FileSize, [ValidateRange(2, 1024)] [int]$MinimumLength = 4, [int]$MaxCandidates = 8, [AllowNull()] $Image = $null, [AllowNull()] [string]$SignatureName = $null, [AllowNull()] [string]$File = $null ) $regionEnd = $RegionStart + $RegionBytes.Length - 1 # Entropy of the run up to and including the boundary (the part that flipped the verdict). $preLength = [int]([Math]::Max([int64]0, [Math]::Min([int64]$RegionBytes.Length, $BoundaryOffset - $RegionStart + 1))) $preBoundaryEntropy = if ($preLength -gt 0) { Get-OIShannonEntropy -Bytes $RegionBytes -Length $preLength } else { 0.0 } $section = $null if ($null -ne $Image) { $section = Get-OIPESectionForOffset -Image $Image -Offset $BoundaryOffset } $boundaryByte = $null $boundaryIndex = [int]($BoundaryOffset - $RegionStart) if ($boundaryIndex -ge 0 -and $boundaryIndex -lt $RegionBytes.Length) { $boundaryByte = $RegionBytes[$boundaryIndex] } # Rank extracted strings by how they relate to the boundary: ones containing it first, # then ones ending just before it. A string starting AFTER the boundary cannot be the # trigger for a prefix that ends at the boundary, so it is excluded. $candidates = New-Object 'System.Collections.Generic.List[object]' foreach ($hit in (Get-OIByteString -Bytes $RegionBytes -BaseOffset $RegionStart -MinimumLength $MinimumLength -Encoding 'Both')) { $endAbs = Get-OIStringByteEnd -StringHit $hit $contains = ([int64]$hit.Offset -le $BoundaryOffset -and $BoundaryOffset -le $endAbs) if ($contains) { $distance = [int64]0 } elseif ($endAbs -lt $BoundaryOffset) { $distance = $BoundaryOffset - $endAbs } else { continue } $candidates.Add([pscustomobject]@{ Offset = [int64]$hit.Offset OffsetHex = '0x{0:X}' -f [int64]$hit.Offset Encoding = $hit.Encoding Length = $hit.Length Value = $hit.Value EndsAtOffset = $endAbs EndsAtHex = '0x{0:X}' -f $endAbs ContainsBoundary = $contains DistanceToBoundary = $distance }) } $ranked = @($candidates | Sort-Object -Property @{ Expression = 'ContainsBoundary'; Descending = $true }, @{ Expression = 'DistanceToBoundary'; Descending = $false }, @{ Expression = 'Length'; Descending = $true } | Select-Object -First $MaxCandidates) # Heuristic read of what the boundary content most likely is. $topString = if ($ranked.Count -gt 0) { $ranked[0] } else { $null } $sectionSuffix = if ($null -ne $section) { " in section '$section'" } else { '' } $interpretation = if ($null -ne $topString -and ($topString.ContainsBoundary -or $topString.DistanceToBoundary -le 2) -and $preBoundaryEntropy -lt 5.0) { $preview = if ($topString.Value.Length -gt 80) { $topString.Value.Substring(0, 80) + '...' } else { $topString.Value } "Likely textual signature - content ending at the boundary: '$preview'" } elseif ($preBoundaryEntropy -ge 7.0) { "High-entropy region ($preBoundaryEntropy bits/byte) at the boundary - signature content is likely binary or packed$sectionSuffix" } else { "Binary signature near the boundary$sectionSuffix" } return [pscustomobject]@{ PSTypeName = 'OffsetInspect.DetectionTrigger' File = $File SignatureName = $SignatureName BoundaryOffset = $BoundaryOffset BoundaryHex = '0x{0:X}' -f $BoundaryOffset BoundaryByteDecimal = if ($null -ne $boundaryByte) { [int]$boundaryByte } else { $null } BoundaryByteHex = if ($null -ne $boundaryByte) { '0x{0:X2}' -f $boundaryByte } else { $null } Section = $section RegionStart = $RegionStart RegionEnd = $regionEnd RegionSize = $RegionBytes.Length PreBoundaryEntropy = $preBoundaryEntropy CandidateStrings = $ranked Interpretation = $interpretation HexDump = @(Format-OIHexDump -Data $RegionBytes -StartOffset $RegionStart -HighlightOffset $BoundaryOffset -FileSize $FileSize) } } |