Public/Get-OffsetEntropy.ps1

function Get-OffsetEntropy {
    <#
    .SYNOPSIS
        Computes per-window Shannon entropy across a file to locate packed or encrypted regions.

    .DESCRIPTION
        Reads the file in fixed-size windows (bounded memory) and reports the entropy of each
        window in bits per byte, flagging windows at or above the high-entropy threshold. High,
        sustained entropy (roughly 7.5-8.0) is a common indicator of compressed, encrypted, or
        packed content. The returned window offsets feed directly into Invoke-OffsetInspect and
        can be cross-referenced with Invoke-OffsetThreatScanRegion detections. Cross-platform.

    .PARAMETER FilePath
        The file to analyse.

    .PARAMETER WindowSize
        Bytes per entropy window (default 256).

    .PARAMETER HighEntropyThreshold
        Bits-per-byte at or above which a window is flagged high entropy (default 7.0).

    .PARAMETER HighOnly
        Return only the flagged high-entropy windows.

    .EXAMPLE
        Get-OffsetEntropy .\sample.bin -WindowSize 512 -HighOnly |
            Select-Object -ExpandProperty Windows
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath,

        [ValidateRange(16, 16777216)]
        [int]$WindowSize = 256,

        [ValidateRange(0.0, 8.0)]
        [double]$HighEntropyThreshold = 7.0,

        [switch]$HighOnly
    )

    $resolvedPath = (Resolve-Path -LiteralPath $FilePath -ErrorAction Stop).Path
    $item = Get-Item -LiteralPath $resolvedPath -ErrorAction Stop
    if ($item.PSIsContainer) { throw "Path is a directory, not a file: $resolvedPath" }
    $fileSize = $item.Length

    $windows = New-Object 'System.Collections.Generic.List[object]'
    $overallFrequencies = New-Object 'long[]' 256
    $totalBytes = [int64]0
    $windowCount = 0
    $highWindowCount = 0

    $stream = $null
    try {
        $stream = [System.IO.File]::Open(
            $resolvedPath,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::Read
        )

        $start = [int64]0
        while ($start -lt $fileSize) {
            $bytes = Read-OIFileRange -Stream $stream -Start $start -Length $WindowSize
            $length = $bytes.Length
            if ($length -le 0) { break }

            $entropy = Get-OIShannonEntropy -Bytes $bytes -Length $length
            $isHigh = $entropy -ge $HighEntropyThreshold

            for ($i = 0; $i -lt $length; $i++) { $overallFrequencies[$bytes[$i]]++ }
            $totalBytes += $length
            if ($isHigh) { $highWindowCount++ }

            if (-not $HighOnly -or $isHigh) {
                $windows.Add([pscustomobject]@{
                    Index       = $windowCount
                    StartOffset = $start
                    StartHex    = '0x{0:X}' -f $start
                    EndOffset   = $start + $length - 1
                    Length      = $length
                    Entropy     = $entropy
                    IsHigh      = $isHigh
                })
            }

            $windowCount++
            $start += $length
        }
    }
    finally {
        if ($null -ne $stream) { $stream.Dispose() }
    }

    $overallEntropy = 0.0
    if ($totalBytes -gt 0) {
        foreach ($frequency in $overallFrequencies) {
            if ($frequency -gt 0) {
                $probability = $frequency / $totalBytes
                $overallEntropy -= $probability * [Math]::Log($probability, 2)
            }
        }
        $overallEntropy = [Math]::Round($overallEntropy, 6)
    }

    $result = [pscustomobject]@{
        File                 = $resolvedPath
        FileSize             = $fileSize
        WindowSize           = $WindowSize
        WindowCount          = $windowCount
        OverallEntropy       = $overallEntropy
        HighEntropyThreshold = $HighEntropyThreshold
        HighWindowCount      = $highWindowCount
        Windows              = $windows.ToArray()
    }
    $result.PSObject.TypeNames.Insert(0, 'OffsetInspect.EntropyResult')
    return $result
}