mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-01 03:54:44 +02:00
75 lines
4.6 KiB
PowerShell
75 lines
4.6 KiB
PowerShell
# Value-only recovery for three built-in logging switches. No arbitrary registry replay.
|
|
function Get-WelaNamedRecoveryCatalog {
|
|
foreach ($item in @(
|
|
@('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit','ProcessCreationIncludeCmdLine_Enabled'),
|
|
@('HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging','EnableScriptBlockLogging'),
|
|
@('HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging','EnableModuleLogging')
|
|
)) {[pscustomobject]@{Id=('Registry/'+$item[0]+'/'+$item[1]);Path=$item[0];Name=$item[1]}}
|
|
}
|
|
function Get-WelaNamedRecoverySources {
|
|
foreach ($relative in @('scripts/NamedRegistryRecovery.ps1','scripts/NamedRegistryRecoveryNative.cs','scripts/AuditRecovery.ps1','scripts/Configuration.ps1')) {
|
|
[pscustomobject]@{Path=$relative;Sha256=(Get-FileHash -LiteralPath (Join-Path $PSScriptRoot ('../'+$relative)) -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant()}
|
|
}
|
|
}
|
|
function Initialize-WelaNamedRecoveryNative {
|
|
$path=Join-Path $PSScriptRoot 'NamedRegistryRecoveryNative.cs'
|
|
$bytes=[IO.File]::ReadAllBytes($path);$hash=Get-WelaRecoveryHash $bytes
|
|
if ('Wela.NamedRegistryRecovery.Key' -as [type]) {
|
|
if ([Wela.NamedRegistryRecovery.Key]::SourceSha256 -cne $hash) {throw 'Loaded named-registry native source differs; start a fresh process.'}
|
|
return
|
|
}
|
|
$source=(New-Object Text.UTF8Encoding($false,$true)).GetString($bytes).Replace('__WELA_SOURCE_SHA256__',$hash)
|
|
Add-Type -TypeDefinition $source -ErrorAction Stop
|
|
}
|
|
function Assert-WelaNamedRecoveryValue {
|
|
param($State)
|
|
if ($State.KeyExists -isnot [bool] -or $State.ValueExists -isnot [bool]) {throw 'Logging registry state requires typed existence flags.'}
|
|
if ($State.ValueExists) {
|
|
if (-not $State.KeyExists -or $State.Type -cne 'DWord' -or ($State.Value -isnot [int] -and $State.Value -isnot [long]) -or $State.Value -notin @(0,1)) {throw 'Only prior DWORD 0/1 or value absence is supported.'}
|
|
} elseif ($null -ne $State.Value -or $null -ne $State.Type) {throw 'Absent logging value has inconsistent state.'}
|
|
}
|
|
function Get-WelaNamedRecoveryGuard {
|
|
param($Observation)
|
|
[pscustomobject][ordered]@{ObjectName=$Observation.ObjectName;OtherValues=$Observation.OtherValues;Children=$Observation.Children;Security=$Observation.Security}
|
|
}
|
|
function Get-WelaNamedRecoveryState {
|
|
param($Observation)
|
|
[pscustomobject]@{KeyExists=$true;ValueExists=[bool]$Observation.Exists;Value=$(if ($Observation.Exists) {[int]$Observation.Value} else {$null});Type=$(if ($Observation.Exists) {'DWord'} else {$null})}
|
|
}
|
|
function Open-WelaNamedRecoveryKey {
|
|
param($Target,[bool]$Write=$false)
|
|
$known=@(Get-WelaNamedRecoveryCatalog | Where-Object {$_.Path -ceq $Target.Path -and $_.Name -ceq $Target.Name})
|
|
if ($known.Count -ne 1) {throw 'Unknown logging recovery target.'}
|
|
Initialize-WelaNamedRecoveryNative
|
|
[Wela.NamedRegistryRecovery.Key]::new($Target.Path,$Write)
|
|
}
|
|
function Get-WelaNamedRecoveryObservation {
|
|
param($Target)
|
|
$key=Open-WelaNamedRecoveryKey $Target
|
|
try {
|
|
$observation=$key.Read($Target.Name)
|
|
if ($observation.ObjectName -ine ('\REGISTRY\MACHINE\'+$Target.Path.Substring(6))) {throw 'Native registry name does not match the selected path.'}
|
|
$observation
|
|
} finally {$key.Dispose()}
|
|
}
|
|
function Assert-WelaNamedRecoveryGuard {
|
|
param($Control,$Observation)
|
|
if ((Get-WelaRecoveryKey (Get-WelaNamedRecoveryGuard $Observation)) -cne (Get-WelaRecoveryKey $Control.RegistryGuard)) {throw 'Logging registry path, other values, children or security changed since planning.'}
|
|
}
|
|
function Set-WelaNamedRecoveryValue {
|
|
param($Control)
|
|
$key=Open-WelaNamedRecoveryKey $Control.Target $true
|
|
try {
|
|
$before=$key.Read($Control.Target.Name)
|
|
Assert-WelaNamedRecoveryGuard $Control $before
|
|
if ((Get-WelaRecoveryKey (Get-WelaNamedRecoveryState $before)) -cne (Get-WelaRecoveryKey $Control.Expected)) {throw 'Logging value changed before recovery.'}
|
|
$value=if ($Control.RecoverTo.ValueExists) {[int]$Control.RecoverTo.Value} else {0}
|
|
$after=$key.Restore($Control.Target.Name,$before,$Control.RecoverTo.ValueExists,$value)
|
|
Assert-WelaNamedRecoveryGuard $Control $after
|
|
# Reopen the selected path after the handle-based write to detect visible path drift.
|
|
$fresh=Get-WelaNamedRecoveryObservation $Control.Target
|
|
Assert-WelaNamedRecoveryGuard $Control $fresh
|
|
if ((Get-WelaRecoveryKey (Get-WelaNamedRecoveryState $fresh)) -cne (Get-WelaRecoveryKey $Control.RecoverTo)) {throw 'Reopened logging value differs after recovery.'}
|
|
} finally {$key.Dispose()}
|
|
}
|