src/Private/Parse/Get-FotonRegfHiveWalk.ps1
|
# Offline REGF (hive) walk. Bytes only. Used by ConvertFrom-FotonAmcache. # Offsets in cells are relative to the first hbin (file offset 0x1000). # Fail closed: return $null rather than a guessed name or time. function Get-FotonRegfCellPayload { param( [byte[]]$Bytes, [int]$CellOffset ) if ($null -eq $Bytes) { return $null } if ($CellOffset -lt 0) { return $null } $pos = 0x1000 + $CellOffset if ($pos -lt 0x1000 -or ($pos + 4) -gt $Bytes.Length) { return $null } $rawSize = [BitConverter]::ToInt32($Bytes, $pos) if ($rawSize -eq 0) { return $null } $len = [Math]::Abs($rawSize) if ($len -lt 8) { return $null } if (($pos + $len) -gt $Bytes.Length) { return $null } $payloadLen = $len - 4 $payload = New-Object byte[] $payloadLen [Array]::Copy($Bytes, ($pos + 4), $payload, 0, $payloadLen) return $payload } function Get-FotonRegfNk { param( [byte[]]$Bytes, [int]$CellOffset ) $p = Get-FotonRegfCellPayload -Bytes $Bytes -CellOffset $CellOffset if ($null -eq $p -or $p.Length -lt 0x4C) { return $null } if (-not ($p[0] -eq 0x6E -and $p[1] -eq 0x6B)) { return $null } $flags = [BitConverter]::ToUInt16($p, 2) $nameLen = [BitConverter]::ToUInt16($p, 0x48) if ((0x4C + $nameLen) -gt $p.Length) { return $null } $name = '' if ($nameLen -gt 0) { if (($flags -band 0x0020) -ne 0) { $name = [Text.Encoding]::ASCII.GetString($p, 0x4C, $nameLen).Trim([char]0) } else { $name = [Text.Encoding]::Unicode.GetString($p, 0x4C, $nameLen).Trim([char]0) } } $lw = ConvertFrom-FotonFileTimeUtc -Bytes $p -Offset 4 [PSCustomObject]@{ CellOffset = $CellOffset Name = $name KeyLastWriteUtc = $lw SubkeyCount = [BitConverter]::ToInt32($p, 0x14) SubkeyListOffset = [BitConverter]::ToInt32($p, 0x1C) ValueCount = [BitConverter]::ToInt32($p, 0x24) ValueListOffset = [BitConverter]::ToInt32($p, 0x28) } } function Get-FotonRegfSubkeyOffsetsFromList { param( [byte[]]$Bytes, [int]$ListOffset, [int]$ExpectedCount ) $out = New-Object System.Collections.Generic.List[int] if ($ListOffset -lt 0 -or $ExpectedCount -le 0) { return @() } $p = Get-FotonRegfCellPayload -Bytes $Bytes -CellOffset $ListOffset if ($null -eq $p -or $p.Length -lt 4) { return @() } $sig = [Text.Encoding]::ASCII.GetString($p, 0, 2) $count = [BitConverter]::ToUInt16($p, 2) if ($sig -eq 'ri') { $i = 0 while ($i -lt $count) { $offPos = 4 + ($i * 4) if (($offPos + 4) -gt $p.Length) { break } $inner = [BitConverter]::ToInt32($p, $offPos) $nested = @(Get-FotonRegfSubkeyOffsetsFromList -Bytes $Bytes -ListOffset $inner -ExpectedCount $ExpectedCount) foreach ($n in $nested) { $out.Add([int]$n) } $i++ } return @($out.ToArray()) } $entrySize = 4 if ($sig -eq 'lf' -or $sig -eq 'lh') { $entrySize = 8 } elseif ($sig -eq 'li') { $entrySize = 4 } else { return @() } $i = 0 while ($i -lt $count) { $offPos = 4 + ($i * $entrySize) if (($offPos + 4) -gt $p.Length) { break } $out.Add([BitConverter]::ToInt32($p, $offPos)) $i++ } return @($out.ToArray()) } function Get-FotonRegfSubkeys { param( [byte[]]$Bytes, $Nk ) $list = @() if ($null -eq $Nk) { return @() } $n = [int]$Nk.SubkeyCount if ($n -le 0) { return @() } if ($n -gt 20000) { $n = 20000 } $offs = @(Get-FotonRegfSubkeyOffsetsFromList -Bytes $Bytes -ListOffset ([int]$Nk.SubkeyListOffset) -ExpectedCount $n) $i = 0 foreach ($off in $offs) { if ($i -ge $n) { break } $cellOff = 0 try { $cellOff = [int]$off } catch { $i++; continue } $child = Get-FotonRegfNk -Bytes $Bytes -CellOffset $cellOff if ($null -ne $child) { $list += $child } $i++ } return @($list) } function Get-FotonRegfVkDecoded { param( [byte[]]$Bytes, [int]$VkOffset ) $p = Get-FotonRegfCellPayload -Bytes $Bytes -CellOffset $VkOffset if ($null -eq $p -or $p.Length -lt 0x14) { return $null } if (-not ($p[0] -eq 0x76 -and $p[1] -eq 0x6B)) { return $null } $nameLen = [BitConverter]::ToUInt16($p, 2) $dataSizeField = [BitConverter]::ToUInt32($p, 4) $dataOff = [BitConverter]::ToInt32($p, 8) $vtype = [BitConverter]::ToInt32($p, 0x0C) $vflags = [BitConverter]::ToUInt16($p, 0x10) if ((0x14 + $nameLen) -gt $p.Length) { return $null } $vname = '(default)' if ($nameLen -gt 0) { if (($vflags -band 0x0001) -ne 0) { $vname = [Text.Encoding]::ASCII.GetString($p, 0x14, $nameLen).Trim([char]0) } else { $vname = [Text.Encoding]::Unicode.GetString($p, 0x14, $nameLen).Trim([char]0) } } $inline = (($dataSizeField -band 0x80000000) -ne 0) $dlen = [int]($dataSizeField -band 0x7FFFFFFF) $data = $null if ($dlen -lt 0) { return $null } if ($inline) { $data = [BitConverter]::GetBytes($dataOff) if ($dlen -lt $data.Length) { $trim = New-Object byte[] $dlen [Array]::Copy($data, 0, $trim, 0, $dlen) $data = $trim } } else { if ($dlen -eq 0) { $data = [byte[]]@() } else { $dp = Get-FotonRegfCellPayload -Bytes $Bytes -CellOffset $dataOff if ($null -eq $dp) { return $null } if ($dp.Length -lt $dlen) { $dlen = $dp.Length } $data = New-Object byte[] $dlen [Array]::Copy($dp, 0, $data, 0, $dlen) } } $text = '' if ($vtype -eq 1 -or $vtype -eq 2) { if ($data.Length -ge 2) { $text = [Text.Encoding]::Unicode.GetString($data).Trim([char]0) } elseif ($data.Length -gt 0) { $text = [Text.Encoding]::ASCII.GetString($data).Trim([char]0) } } elseif ($vtype -eq 4 -and $data.Length -ge 4) { $text = [BitConverter]::ToUInt32($data, 0).ToString([Globalization.CultureInfo]::InvariantCulture) } [PSCustomObject]@{ Name = $vname Type = $vtype Text = $text } } function Get-FotonRegfValues { param( [byte[]]$Bytes, $Nk ) $map = @{} if ($null -eq $Nk) { return ,$map } $n = [int]$Nk.ValueCount if ($n -le 0) { return ,$map } if ($n -gt 500) { $n = 500 } $lp = Get-FotonRegfCellPayload -Bytes $Bytes -CellOffset ([int]$Nk.ValueListOffset) if ($null -eq $lp) { return ,$map } $i = 0 while ($i -lt $n) { $pos = $i * 4 if (($pos + 4) -gt $lp.Length) { break } $vkOff = [BitConverter]::ToInt32($lp, $pos) $vk = Get-FotonRegfVkDecoded -Bytes $Bytes -VkOffset $vkOff if ($null -ne $vk -and $vk.Name -and $vk.Text) { $map[$vk.Name] = $vk.Text } $i++ } return ,$map } function Get-FotonRegfRootNk { param([byte[]]$Bytes) if ($null -eq $Bytes -or $Bytes.Length -lt 0x1020) { return $null } $magic = [Text.Encoding]::ASCII.GetString($Bytes, 0, 4) if ($magic -ne 'regf') { return $null } $rootOff = [BitConverter]::ToInt32($Bytes, 0x24) return (Get-FotonRegfNk -Bytes $Bytes -CellOffset $rootOff) } function Get-FotonRegfFindSubkey { param( [byte[]]$Bytes, $StartNk, [string]$Name, [int]$MaxDepth = 4 ) if ($null -eq $StartNk -or -not $Name) { return $null } $want = $Name.ToLowerInvariant() $queue = New-Object System.Collections.Queue $queue.Enqueue(@($StartNk, 0)) $seen = 0 while ($queue.Count -gt 0 -and $seen -lt 8000) { $item = $queue.Dequeue() $nk = $item[0] $depth = [int]$item[1] $seen++ if ([string]$nk.Name -and ([string]$nk.Name).ToLowerInvariant() -eq $want) { return $nk } if ($depth -ge $MaxDepth) { continue } foreach ($ch in @(Get-FotonRegfSubkeys -Bytes $Bytes -Nk $nk)) { $queue.Enqueue(@($ch, ($depth + 1))) } } return $null } |