Private/Core.String.ps1
|
function Get-OITrailingRunLength { <# Returns how many bytes at the end of a buffer could belong to a printable run that continues into the next window. Get-OffsetString holds these bytes back and re-reads them with the following window, so a string straddling a window seam is reported once, whole, rather than split into two truncated halves. An ASCII run is a trailing sequence of printable bytes; it stops at any non- printable byte, so the NUL padding that dominates PE files yields 0 and the carry-over stays small. A UTF-16LE run is a trailing sequence of printable/0x00 pairs, optionally preceded by a lone printable low byte whose 0x00 high byte falls in the next window. The larger of the two is returned. The result is always < $Bytes.Length so the caller is guaranteed to advance; a single run longer than an entire window degenerates to the previous behaviour (split at the seam) rather than stalling. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [byte[]]$Bytes, [ValidateSet('Ascii', 'Unicode', 'Both')] [string]$Encoding = 'Both' ) if ($null -eq $Bytes -or $Bytes.Length -eq 0) { return 0 } $count = $Bytes.Length $asciiRun = 0 if ($Encoding -eq 'Ascii' -or $Encoding -eq 'Both') { $i = $count - 1 while ($i -ge 0 -and $Bytes[$i] -ge 0x20 -and $Bytes[$i] -le 0x7E) { $asciiRun++ $i-- } } $unicodeRun = 0 if ($Encoding -eq 'Unicode' -or $Encoding -eq 'Both') { $i = $count - 1 if ($Bytes[$i] -ge 0x20 -and $Bytes[$i] -le 0x7E) { # Lone low byte; its high byte is the first byte of the next window. $unicodeRun++ $i-- } while ($i -ge 1 -and $Bytes[$i] -eq 0x00 -and $Bytes[$i - 1] -ge 0x20 -and $Bytes[$i - 1] -le 0x7E) { $unicodeRun += 2 $i -= 2 } } $carry = [Math]::Max($asciiRun, $unicodeRun) if ($carry -ge $count) { $carry = 0 } return $carry } function Get-OIByteString { <# Extracts printable ASCII and/or UTF-16LE strings from a byte buffer, tagging each with its offset (BaseOffset + position within the buffer). Pure, cross- platform, unit-tested directly. Printable = 0x20..0x7E; UTF-16LE runs are a printable low byte followed by a 0x00 high byte. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [byte[]]$Bytes, [int64]$BaseOffset = 0, [ValidateRange(2, 1024)] [int]$MinimumLength = 4, [ValidateSet('Ascii', 'Unicode', 'Both')] [string]$Encoding = 'Both' ) $results = New-Object 'System.Collections.Generic.List[object]' if ($Encoding -eq 'Ascii' -or $Encoding -eq 'Both') { $runStart = -1 for ($i = 0; $i -le $Bytes.Length; $i++) { $printable = $i -lt $Bytes.Length -and $Bytes[$i] -ge 0x20 -and $Bytes[$i] -le 0x7E if ($printable) { if ($runStart -lt 0) { $runStart = $i } } elseif ($runStart -ge 0) { $length = $i - $runStart if ($length -ge $MinimumLength) { $value = [System.Text.Encoding]::ASCII.GetString($Bytes, $runStart, $length) $results.Add([pscustomobject]@{ Offset = $BaseOffset + $runStart; Encoding = 'Ascii'; Length = $length; Value = $value }) } $runStart = -1 } } } if ($Encoding -eq 'Unicode' -or $Encoding -eq 'Both') { $runStart = -1 $charCount = 0 $i = 0 while ($i -lt $Bytes.Length - 1) { $printable = $Bytes[$i] -ge 0x20 -and $Bytes[$i] -le 0x7E -and $Bytes[$i + 1] -eq 0x00 if ($printable) { if ($runStart -lt 0) { $runStart = $i; $charCount = 0 } $charCount++ $i += 2 } else { if ($runStart -ge 0 -and $charCount -ge $MinimumLength) { $value = [System.Text.Encoding]::Unicode.GetString($Bytes, $runStart, $charCount * 2) $results.Add([pscustomobject]@{ Offset = $BaseOffset + $runStart; Encoding = 'Unicode'; Length = $charCount; Value = $value }) } $runStart = -1 $charCount = 0 $i++ } } if ($runStart -ge 0 -and $charCount -ge $MinimumLength) { $value = [System.Text.Encoding]::Unicode.GetString($Bytes, $runStart, $charCount * 2) $results.Add([pscustomobject]@{ Offset = $BaseOffset + $runStart; Encoding = 'Unicode'; Length = $charCount; Value = $value }) } } return $results.ToArray() } |