mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-25 22:48:12 +01:00
Add fixed current-user registry value audit probe
This commit is contained in:
1 parent
7ef29df61f
commit
9575c8204d
15 files changed
+596
-2
No files matched your search
@@ -0,0 +1,144 @@
|
||||
# Fixed current-user diagnostic key only. Existing auditing is required, never installed.
|
||||
function Initialize-WelaRegistryValueProbe {
|
||||
Initialize-WelaWmiProbeNative
|
||||
$source=Join-Path $PSScriptRoot 'RegistryValueProbeNative.cs';$bytes=[IO.File]::ReadAllBytes($source);$hash=Get-WelaArrivalHash $bytes
|
||||
if(-not ('Wela.RegistryValueProbe.Target' -as [type])){Add-Type -TypeDefinition ([Text.UTF8Encoding]::new($false,$true).GetString($bytes).Replace('__WELA_REGISTRY_VALUE_PROBE_SOURCE_SHA256__',$hash)) -ErrorAction Stop}
|
||||
if([Wela.RegistryValueProbe.Descriptor]::SourceSha256 -cne $hash){throw 'Loaded registry probe helper differs from source; start a fresh session.'}
|
||||
}
|
||||
function Get-WelaRegistryValueProbeSources {
|
||||
$sources=[ordered]@{}
|
||||
foreach($name in @('WELA.ps1','scripts/RegistryValueProbe.ps1','scripts/RegistryValueProbeNative.cs','scripts/FileAccessProbe.ps1','scripts/WmiProbe.ps1','scripts/WmiProbeNative.cs','scripts/ChannelRead.ps1','scripts/ChannelReadNative.cs','scripts/WefArrival.ps1','scripts/Configuration.ps1','modules/AuditProfiles.psm1','scripts/CustomAuditProfiles.ps1','scripts/IpsecPrerequisites.ps1','config/audit_profiles.json')){$sources[$name]=(Get-FileHash -LiteralPath (Join-Path $PSScriptRoot ('../'+$name)) -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant()}
|
||||
[pscustomobject]$sources
|
||||
}
|
||||
function Get-WelaRegistryValueProbeState {
|
||||
if($env:OS -ne 'Windows_NT' -or -not [Environment]::Is64BitProcess){throw 'Native 64-bit Windows is required.'}
|
||||
Initialize-WelaRegistryValueProbe
|
||||
$tokenBefore=[Wela.RegistryValueProbe.TokenReader]::Snapshot()
|
||||
$services=@(Get-Service EventLog,Winmgmt,RpcSs -ErrorAction Stop|Sort-Object Name|ForEach-Object {[pscustomobject]@{Name=$_.Name;Status=[string]$_.Status}})
|
||||
if($services.Count -ne 3 -or @($services|Where-Object Status -ne Running).Count){throw 'EventLog, Winmgmt and RpcSs must already be running.'}
|
||||
$reader=Get-WelaChannelReader;$null=Get-WelaFileProbeReaderKey $reader
|
||||
$hostState=Get-WelaChannelReadHost
|
||||
$target=[Wela.RegistryValueProbe.Target]::new($false)
|
||||
try{$registry=$target.Read()}finally{$target.Dispose()}
|
||||
$channel=[Diagnostics.Eventing.Reader.EventLogConfiguration]::new('Security')
|
||||
try{$log=[pscustomobject]@{Name=$channel.LogName;Enabled=$channel.IsEnabled;SecurityDescriptor=$channel.SecurityDescriptor;MaximumSize=$channel.MaximumSizeInBytes;Mode=[string]$channel.LogMode}}finally{$channel.Dispose()}
|
||||
$policy=Get-WelaEffectiveAuditPolicy;$masks=[ordered]@{};foreach($guid in @($policy.Keys|Sort-Object)){$masks[$guid]=$policy[$guid]}
|
||||
$engine=(Get-Process -Id $PID -ErrorAction Stop).Path;$token=[Wela.RegistryValueProbe.TokenReader]::Snapshot()
|
||||
if((Get-WelaFileProbeTokenKey $tokenBefore) -cne (Get-WelaFileProbeTokenKey $token)){throw 'Registry prerequisite observation changed the full process token.'}
|
||||
[pscustomobject][ordered]@{Computer=[Environment]::MachineName;Host=$hostState;Services=$services;Reader=($reader|Select-Object UserSid,UserName,AuthenticationId,GroupSids,GroupCount,PrivilegeCount,ElevatedAdministrator,TokenType,Impersonation);Token=$token;Registry=$registry;AuditPolicies=[pscustomobject]$masks;Precedence=Get-WelaRegistryState 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' SCENoApplyLegacyAuditPolicy;Channel=$log;Engine=$engine;EngineHash=(Get-FileHash -LiteralPath $engine).Hash.ToLowerInvariant();Sources=Get-WelaRegistryValueProbeSources}
|
||||
}
|
||||
function Get-WelaRegistryValueProbeStateKey {
|
||||
param($State)
|
||||
$null=Get-WelaFileProbeTokenKey $State.Token
|
||||
if($State.Computer -isnot [string] -or -not $State.Computer -or -not(Test-WelaFileProbeInteger $State.Host.ProductType) -or $State.Host.ProductType -notin 1,2,3 -or -not(Test-WelaFileProbeInteger $State.Host.Build) -or $State.Host.Build -notin 22000,22621,22631,20348,26100,26200 -or $State.Host.DomainJoined -isnot [bool]){throw 'Complete supported native host context is required.'}
|
||||
if($State.Services -isnot [array] -or ($State.Services.Name -join ',') -cne 'EventLog,RpcSs,Winmgmt' -or @($State.Services|Where-Object Status -ne Running).Count){throw 'Required services must already be running.'}
|
||||
if($State.Reader.ElevatedAdministrator -isnot [bool] -or -not $State.Reader.ElevatedAdministrator -or $State.Reader.TokenType -cne 'Primary' -or $State.Reader.Impersonation -cne 'Absent' -or $State.Reader.UserSid -cne $State.Token.Sid){throw 'Actual elevated non-impersonating primary token required.'}
|
||||
$expected='HKEY_USERS\'+$State.Token.Sid+'\Software\WELA\AuditProbe'
|
||||
$r=$State.Registry
|
||||
if($r.Path -ine $expected -or $r.Kind -cne 'Registry' -or $r.IsDirectory -isnot [bool] -or $r.IsDirectory -or -not(Test-WelaFileProbeInteger $r.SecurityInformation) -or $r.SecurityInformation -ne 511 -or $r.DescriptorBase64 -isnot [string] -or -not $r.DescriptorBase64 -or $r.Identity -isnot [string] -or -not $r.Identity -or $r.Values -isnot [array] -or $r.Values.Count -gt 128 -or $r.Aces -isnot [array] -or $r.Aces.Count -gt 128){throw 'Complete fixed registry-key observation is required.'}
|
||||
foreach($ace in $r.Aces){if($ace.Ordinary -isnot [bool] -or -not(Test-WelaFileProbeInteger $ace.Type) -or -not(Test-WelaFileProbeInteger $ace.Flags) -or -not(Test-WelaFileProbeInteger $ace.Mask)){throw 'Typed audit ACE evidence is required.'}}
|
||||
$mask=$State.AuditPolicies.'0CCE921E-69AE-11D9-BED3-505054503030'
|
||||
if(-not(Test-WelaFileProbeInteger $mask) -or $mask -notin 1,3 -or $State.Precedence.ValueExists -isnot [bool] -or -not $State.Precedence.ValueExists -or $State.Precedence.Type -cne 'DWord' -or -not(Test-WelaFileProbeInteger $State.Precedence.Value) -or $State.Precedence.Value -ne 1){throw 'Registry success auditing and typed precedence DWORD1 must already be configured.'}
|
||||
if($State.Channel.Name -cne 'Security' -or $State.Channel.Enabled -isnot [bool] -or -not $State.Channel.Enabled -or -not $State.Channel.SecurityDescriptor){throw 'Security channel must already be enabled and readable.'}
|
||||
$sids=@($State.Token.Sid)+@($State.Token.Groups|Where-Object {($_.Attributes -band 4) -and -not($_.Attributes -band 16)}|ForEach-Object Sid)
|
||||
$aces=@($r.Aces|Where-Object {$_.Ordinary -and $_.Type -eq 2 -and ($_.Flags -band 64) -and -not($_.Flags -band 8) -and ($_.Mask -band 2) -and $_.Sid -in $sids})
|
||||
if(-not $aces.Count){throw 'An existing matching success SetValue audit ACE is required; no SACL is added.'}
|
||||
if($State.Engine -isnot [string] -or -not $State.Engine -or $State.EngineHash -cnotmatch '^[a-f0-9]{64}$'){throw 'Native engine identity is incomplete.'}
|
||||
if(-not @($State.Sources.PSObject.Properties).Count){throw 'Implementation fingerprints are missing.'}
|
||||
foreach($source in $State.Sources.PSObject.Properties){if($source.Value -isnot [string] -or $source.Value -cnotmatch '^[a-f0-9]{64}$'){throw 'Invalid implementation fingerprint.'}}
|
||||
# Registry last-write time changes after the owned temporary value lifecycle; preserve it in raw receipts.
|
||||
$stable=$State|Select-Object * -ExcludeProperty Registry
|
||||
$stable|Add-Member NoteProperty Registry ($r|Select-Object * -ExcludeProperty Identity)
|
||||
Get-WelaFileProbeKey $stable
|
||||
}
|
||||
function Invoke-WelaRegistryValueProbeOperation {
|
||||
param($State,[string]$Nonce)
|
||||
$fresh=Get-WelaRegistryValueProbeState
|
||||
if((Get-WelaRegistryValueProbeStateKey $fresh) -cne (Get-WelaRegistryValueProbeStateKey $State) -or $fresh.Registry.Identity -cne $State.Registry.Identity){throw 'Registry prerequisites changed before the owned value operation.'}
|
||||
$watermark=Get-WelaFileProbeWatermark;$before=[Wela.RegistryValueProbe.TokenReader]::Snapshot();$target=$null;$native=$null
|
||||
$launch=[Wela.RegistryValueProbe.Target]::UtcNow()
|
||||
try{$target=[Wela.RegistryValueProbe.Target]::new($true);$native=$target.Run($Nonce,$State.Registry.Identity,$State.Registry.DescriptorBase64)}finally{if($target){$target.Dispose()}}
|
||||
$after=[Wela.RegistryValueProbe.TokenReader]::Snapshot()
|
||||
$operation=[pscustomobject]@{Kind='WelaOwnedRegistryValueModification';Nonce=$Nonce;ProcessId=$PID;Executable=$State.Engine;RecordIdBefore=$watermark;BeforeToken=$before;AfterToken=$after;LaunchedUtc=$launch.ToString('o');ObservedUtc=[Wela.RegistryValueProbe.Target]::UtcNow().ToString('o');Native=$native}
|
||||
return $operation
|
||||
}
|
||||
function Assert-WelaRegistryValueProbeOperation {
|
||||
param($Operation,$State)
|
||||
$r=$Operation.Native
|
||||
if($Operation.Kind -cne 'WelaOwnedRegistryValueModification' -or $Operation.Nonce -cnotmatch '^[a-f0-9]{32}$' -or -not(Test-WelaFileProbeInteger $Operation.ProcessId) -or $Operation.ProcessId -le 0 -or $Operation.Executable -ine $State.Engine -or -not(Test-WelaFileProbeInteger $Operation.RecordIdBefore) -or $Operation.RecordIdBefore -le 0){throw 'Incomplete registry operation authority.'}
|
||||
if($r.Succeeded -isnot [bool] -or -not $r.Succeeded -or $r.CleanupComplete -isnot [bool] -or -not $r.CleanupComplete -or $r.Nonce -cne $Operation.Nonce -or $r.Name -cne ('WELA_Probe_'+$Operation.Nonce) -or $r.BeforeValue -cne ('WELA_BEFORE_'+$Operation.Nonce) -or $r.AfterValue -cne ('WELA_AFTER_'+$Operation.Nonce) -or $r.HandleId -cnotmatch '^0x[1-9a-f][0-9a-f]*$'){throw ('Owned value operation or cleanup failed: '+$r.Diagnostic)}
|
||||
if($r.Before.Path -ine $State.Registry.Path -or $r.After.Path -ine $State.Registry.Path -or (Get-WelaFileProbeKey $r.Before.Values) -cne (Get-WelaFileProbeKey $State.Registry.Values) -or $r.Before.Identity -cne $State.Registry.Identity -or $r.Before.DescriptorBase64 -cne $State.Registry.DescriptorBase64 -or $r.After.DescriptorBase64 -cne $State.Registry.DescriptorBase64 -or (Get-WelaFileProbeKey $r.Before.Values) -cne (Get-WelaFileProbeKey $r.After.Values)){throw 'Held registry descriptor or unrelated values changed.'}
|
||||
$start=ConvertTo-WelaArrivalUtc $r.StartedUtc;$returned=ConvertTo-WelaArrivalUtc $r.WriteReturnedUtc;$end=ConvertTo-WelaArrivalUtc $r.CompletedUtc
|
||||
if($start -lt (ConvertTo-WelaArrivalUtc $Operation.LaunchedUtc) -or $returned -lt $start -or $end -lt $returned -or $end -gt (ConvertTo-WelaArrivalUtc $Operation.ObservedUtc) -or ($end-$start).TotalSeconds -gt 10){throw 'Invalid precise native value-modification interval.'}
|
||||
if((Get-WelaFileProbeTokenKey $Operation.BeforeToken) -cne (Get-WelaFileProbeTokenKey $Operation.AfterToken) -or (Get-WelaFileProbeTokenKey $Operation.BeforeToken) -cne (Get-WelaFileProbeTokenKey $State.Token)){throw 'Registry operation changed the full process token.'}
|
||||
}
|
||||
function Read-WelaRegistryValueProbeEvents {
|
||||
param($Operation)
|
||||
# Keep out-of-interval candidates for diagnosis; the matcher never credits them.
|
||||
$query="*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4657 and EventRecordID>$($Operation.RecordIdBefore)]]"
|
||||
$reader=$null;$records=New-Object 'System.Collections.Generic.List[string]'
|
||||
try {
|
||||
$q=[Diagnostics.Eventing.Reader.EventLogQuery]::new('Security',[Diagnostics.Eventing.Reader.PathType]::LogName,$query);$q.TolerateQueryErrors=$false
|
||||
$reader=[Diagnostics.Eventing.Reader.EventLogReader]::new($q);$reader.BatchSize=16
|
||||
while($records.Count -lt 256){$event=$reader.ReadEvent([TimeSpan]::FromSeconds(1));if($null -eq $event){break};try{$xml=$event.ToXml();if($xml.Length -gt 131072){throw 'Security event exceeds the XML bound.'};$records.Add($xml)}finally{$event.Dispose()}}
|
||||
$status=@($reader.LogStatus|ForEach-Object {[pscustomobject]@{LogName=$_.LogName;StatusCode=$_.StatusCode}});Assert-WelaChannelQueryStatus Security $status
|
||||
[pscustomobject]@{Xml=@($records.ToArray());Capped=($records.Count -ge 256);Query=$query;MaximumEvents=256;LogStatus=$status}
|
||||
}finally{if($reader){$reader.Dispose()}}
|
||||
}
|
||||
function Test-WelaRegistryValueProbeEvent {
|
||||
param([string]$Xml,$Operation,$State)
|
||||
$reader=$null
|
||||
try {
|
||||
if($Xml.Length -gt 131072){return $false}
|
||||
$settings=[Xml.XmlReaderSettings]::new();$settings.DtdProcessing=[Xml.DtdProcessing]::Prohibit;$settings.XmlResolver=$null;$settings.MaxCharactersInDocument=131072
|
||||
$reader=[Xml.XmlReader]::Create([IO.StringReader]::new($Xml),$settings);$doc=[Xml.XmlDocument]::new();$doc.XmlResolver=$null;$doc.Load($reader)
|
||||
$ns=[Xml.XmlNamespaceManager]::new($doc.NameTable);$ns.AddNamespace('e','http://schemas.microsoft.com/win/2004/08/events/event')
|
||||
if($doc.DocumentElement.LocalName -cne 'Event' -or $doc.DocumentElement.NamespaceURI -cne $ns.LookupNamespace('e') -or $doc.SelectNodes('/e:Event/e:System',$ns).Count -ne 1 -or $doc.SelectNodes('/e:Event/e:EventData',$ns).Count -ne 1 -or $doc.SelectNodes('/e:Event/e:UserData',$ns).Count){return $false}
|
||||
$system=@{};foreach($name in @('Provider','EventID','Version','Keywords','EventRecordID','Channel','Computer','TimeCreated','Level','Task','Opcode')){$nodes=$doc.SelectNodes("/e:Event/e:System/e:$name",$ns);if($nodes.Count -ne 1){return $false};$system[$name]=$nodes[0]}
|
||||
if($system.Provider.GetAttribute('Name') -cne 'Microsoft-Windows-Security-Auditing' -or $system.Provider.GetAttribute('Guid').Trim('{}') -ine '54849625-5478-4994-a5ba-3e3b0328c30d' -or $system.EventID.InnerText -cne '4657' -or $system.Version.InnerText -cne '0' -or $system.Keywords.InnerText -ine '0x8020000000000000' -or $system.Channel.InnerText -cne 'Security' -or $system.Level.InnerText -cne '0' -or $system.Task.InnerText -cne '12801' -or $system.Opcode.InnerText -cne '0' -or $system.EventRecordID.InnerText -cnotmatch '^[1-9][0-9]*$' -or [long]$system.EventRecordID.InnerText -le $Operation.RecordIdBefore){return $false}
|
||||
$computers=@($State.Computer);if($State.Host.DomainJoined){$computers+=$State.Computer+'.'+$State.Host.Domain};if($system.Computer.InnerText -notin $computers){return $false}
|
||||
$time=ConvertTo-WelaArrivalUtc $system.TimeCreated.GetAttribute('SystemTime');if($time -lt (ConvertTo-WelaArrivalUtc $Operation.Native.StartedUtc) -or $time -gt (ConvertTo-WelaArrivalUtc $Operation.Native.CompletedUtc)){return $false}
|
||||
$data=@{};foreach($node in $doc.SelectSingleNode('/e:Event/e:EventData',$ns).ChildNodes){if($node.NodeType -eq 'Whitespace'){continue};if($node.NodeType -ne 'Element' -or $node.LocalName -cne 'Data' -or $node.NamespaceURI -cne $ns.LookupNamespace('e')){return $false};$name=$node.GetAttribute('Name');if(-not $name -or $data.ContainsKey($name) -or @($node.ChildNodes|Where-Object NodeType -eq Element).Count){return $false};$data[$name]=$node.InnerText}
|
||||
foreach($name in @('SubjectUserSid','SubjectUserName','SubjectDomainName','SubjectLogonId','ObjectName','ObjectValueName','HandleId','OperationType','OldValueType','OldValue','NewValueType','NewValue','ProcessId','ProcessName')){if(-not $data.ContainsKey($name)){return $false}}
|
||||
$nativePath='\REGISTRY\USER\'+$State.Token.Sid+'\Software\WELA\AuditProbe'
|
||||
if($data.Count -ne 14 -or $data.ObjectName -ine $nativePath -or $data.ObjectValueName -cne $Operation.Native.Name -or $data.ProcessName -ine $State.Engine -or $data.SubjectUserSid -cne $Operation.BeforeToken.Sid -or $data.OperationType -cne '%%1905' -or $data.OldValueType -cne '%%1873' -or $data.NewValueType -cne '%%1873' -or $data.OldValue -cne $Operation.Native.BeforeValue -or $data.NewValue -cne $Operation.Native.AfterValue){return $false}
|
||||
foreach($name in @('SubjectLogonId','ProcessId','HandleId')){if($data[$name] -cnotmatch '^0x[0-9a-fA-F]+$'){return $false}}
|
||||
if([Convert]::ToUInt64($data.SubjectLogonId.Substring(2),16) -ne [Convert]::ToUInt64($Operation.BeforeToken.AuthenticationId.Substring(2),16) -or [Convert]::ToUInt64($data.ProcessId.Substring(2),16) -ne $Operation.ProcessId -or [Convert]::ToUInt64($data.HandleId.Substring(2),16) -ne [Convert]::ToUInt64($Operation.Native.HandleId.Substring(2),16)){return $false}
|
||||
$true
|
||||
}catch{$false}finally{if($reader){$reader.Dispose()}}
|
||||
}
|
||||
function Invoke-WelaRegistryValueProbe {
|
||||
param([ValidateSet('Plan','Run')][string]$Action='Plan',[string]$OutputPath,[ValidateRange(1,30)][int]$TimeoutSeconds=15)
|
||||
if(($Action -eq 'Run') -ne (-not [string]::IsNullOrWhiteSpace($OutputPath))){throw 'Run requires a new RegistryProbeOutputPath; Plan creates no output.'}
|
||||
$report=[pscustomobject][ordered]@{SchemaVersion=1;Kind='WelaRegistryValueProbe';Action=$Action;Status='Unverified';ExitCode=1;RecordedUtc=[datetime]::UtcNow.ToString('o');Before=$null;After=$null;Operation=$null;Candidates=0;Matches=0;Query=$null;Artifacts=@();Diagnostic='';OutputPath=$null;ConfigurationChanges=0;TemporaryRegistryValueWrites='Only Run: create/modify/delete one owned nonce value in fixed current-user probe key.';SigmaEvtxCredit=0;Scope='One owned current-user registry REG_SZ modification only; other keys/users, inherited coverage, forwarding and Sigma are unverified. Native key last-write metadata changes.'}
|
||||
$outputKey=$null;$beforeKey=$null
|
||||
try {
|
||||
if($Action -eq 'Run'){$report.OutputPath=New-WelaArrivalOutput $OutputPath $script:ScriptRoot;$outputKey=Get-WelaFileProbeOutputKey $report.OutputPath}
|
||||
$before=Get-WelaRegistryValueProbeState;$beforeKey=Get-WelaRegistryValueProbeStateKey $before;$report.Before=$before
|
||||
if($Action -eq 'Plan'){$report.After=$before;$report.Status='PrerequisitesObserved';$report.ExitCode=0;return $report}
|
||||
$report.Artifacts+=Write-WelaFileProbeArtifact $report.OutputPath $outputKey 'before.json' ($before|ConvertTo-Json -Depth 24)
|
||||
$nonce=[guid]::NewGuid().ToString('N');$intent=[pscustomobject]@{Kind='WelaOwnedRegistryValueIntent';Nonce=$nonce;Path=$before.Registry.Path;Name=('WELA_Probe_'+$nonce);Original='Absent';Outcome='Pending; interruption may leave the owned marker value. Inspect receipts before manual cleanup.'}
|
||||
$report.Artifacts+=Write-WelaFileProbeArtifact $report.OutputPath $outputKey 'intent.json' ($intent|ConvertTo-Json -Depth 8)
|
||||
$operation=Invoke-WelaRegistryValueProbeOperation $before $nonce;$report.Operation=$operation
|
||||
$report.Artifacts+=Write-WelaFileProbeArtifact $report.OutputPath $outputKey 'operation.json' ($operation|ConvertTo-Json -Depth 24)
|
||||
Assert-WelaRegistryValueProbeOperation $operation $before
|
||||
$timer=[Diagnostics.Stopwatch]::StartNew();$matches=@()
|
||||
do{$batch=Read-WelaRegistryValueProbeEvents $operation;$report.Candidates=@($batch.Xml).Count;$report.Query=$batch.Query
|
||||
if($batch.Capped -isnot [bool] -or $batch.Capped){throw 'Security query reached its 256-event cap or completeness is unknown.'}
|
||||
$matches=@($batch.Xml|Where-Object {Test-WelaRegistryValueProbeEvent $_ $operation $before});if($matches.Count){break};Start-Sleep -Milliseconds 250
|
||||
}while($timer.Elapsed.TotalSeconds -lt $TimeoutSeconds)
|
||||
$report.Matches=$matches.Count
|
||||
if($matches.Count -ne 1){$i=0;foreach($xml in @($batch.Xml|Select-Object -First 4)){$i++;$report.Artifacts+=Write-WelaFileProbeArtifact $report.OutputPath $outputKey ('candidate-'+$i+'.xml') $xml};throw 'Exactly one attributable native4657 was not observed in the measured owned-value modification phase.'}
|
||||
$report.Artifacts+=Write-WelaFileProbeArtifact $report.OutputPath $outputKey 'event.xml' $matches[0]
|
||||
if((Get-WelaFileProbeWatermark) -lt $operation.RecordIdBefore){throw 'Security record boundary moved backwards.'}
|
||||
$after=Get-WelaRegistryValueProbeState;$report.After=$after
|
||||
if((Get-WelaRegistryValueProbeStateKey $after) -cne $beforeKey){throw 'Registry values/security, policy, channel, host, token or implementation changed during the probe.'}
|
||||
$report.Status='RegistryValueModificationObserved';$report.ExitCode=0
|
||||
}catch{$report.Diagnostic=$_.Exception.Message}
|
||||
finally{if($report.Before -and -not $report.After){try{$report.After=Get-WelaRegistryValueProbeState}catch{$report.Diagnostic+=' Final observation failed: '+$_.Exception.Message}}}
|
||||
if($report.OutputPath -and $outputKey){
|
||||
if($report.After){$report.Artifacts+=Write-WelaFileProbeArtifact $report.OutputPath $outputKey 'after.json' ($report.After|ConvertTo-Json -Depth 24)}
|
||||
$null=Write-WelaFileProbeArtifact $report.OutputPath $outputKey 'manifest.json' ($report|ConvertTo-Json -Depth 28)
|
||||
}
|
||||
$report
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Fixed existing current-user WELA probe key: one owned temporary value lifecycle.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.AccessControl;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
namespace Wela.RegistryValueProbe {
|
||||
public sealed class Ace { public string Binary; public int Type,Flags,Mask; public string Sid; public bool Ordinary; }
|
||||
public sealed class Value {public string Name,DataBase64; public uint Type;}
|
||||
public sealed class Operation {public bool Succeeded,CleanupComplete;public string Nonce,Name,BeforeValue,AfterValue,HandleId,StartedUtc,WriteReturnedUtc,CompletedUtc,Diagnostic;public Snapshot Before,After;}
|
||||
public sealed class Snapshot {
|
||||
public string Path,Kind,Identity; public bool IsDirectory;
|
||||
public string DescriptorBase64,Owner,Group,DaclBase64; public int ControlFlags,SecurityInformation;
|
||||
public string DescriptorScope; public Ace[] Aces; public Value[] Values;
|
||||
}
|
||||
public static class Descriptor {
|
||||
public const string SourceSha256="__WELA_REGISTRY_VALUE_PROBE_SOURCE_SHA256__";
|
||||
public static string Bytes(GenericAcl value) { if(value==null)return null;byte[] b=new byte[value.BinaryLength];value.GetBinaryForm(b,0);return Convert.ToBase64String(b); }
|
||||
public static string Bytes(GenericAce value) { byte[] b=new byte[value.BinaryLength];value.GetBinaryForm(b,0);return Convert.ToBase64String(b); }
|
||||
static string Sid(SecurityIdentifier value) {return value==null?null:value.Value;}
|
||||
public static RawSecurityDescriptor Parse(string value) {
|
||||
byte[] b=Convert.FromBase64String(value);
|
||||
if(b.Length<20||b.Length>1048576||Convert.ToBase64String(b)!=value)throw new InvalidOperationException("Invalid or noncanonical descriptor bytes.");
|
||||
RawSecurityDescriptor sd=new RawSecurityDescriptor(b,0);
|
||||
return sd;
|
||||
}
|
||||
public static Snapshot Observe(string path,string identity,byte[] bytes) {
|
||||
string encoded=Convert.ToBase64String(bytes);RawSecurityDescriptor sd=Parse(encoded);List<Ace> entries=new List<Ace>();
|
||||
if(sd.SystemAcl!=null)foreach(GenericAce ace in sd.SystemAcl){CommonAce common=ace as CommonAce;bool ordinary=common!=null&&!common.IsCallback&&common.AceType==AceType.SystemAudit;entries.Add(new Ace {Binary=Bytes(ace),Type=(int)ace.AceType,Flags=(int)ace.AceFlags,Mask=ordinary?common.AccessMask:0,Sid=ordinary?common.SecurityIdentifier.Value:null,Ordinary=ordinary});}
|
||||
return new Snapshot {Path=path,Kind="Registry",Identity=identity,IsDirectory=false,DescriptorBase64=encoded,Owner=Sid(sd.Owner),Group=Sid(sd.Group),DaclBase64=Bytes(sd.DiscretionaryAcl),ControlFlags=(int)sd.ControlFlags,SecurityInformation=511,DescriptorScope="WinSDK-defined sections 0x1ff; future sections unobserved",Aces=entries.ToArray()};
|
||||
}
|
||||
}
|
||||
sealed class Privilege : IDisposable {
|
||||
[StructLayout(LayoutKind.Sequential)] struct Luid {public uint Low;public int High;}
|
||||
[StructLayout(LayoutKind.Sequential)] struct TokenPrivileges {public uint Count;public Luid Luid;public uint Attributes;}
|
||||
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
|
||||
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentThread();
|
||||
[DllImport("kernel32.dll",SetLastError=true)] static extern bool CloseHandle(IntPtr value);
|
||||
[DllImport("advapi32.dll",SetLastError=true)] static extern bool OpenProcessToken(IntPtr process,uint access,out IntPtr token);
|
||||
[DllImport("advapi32.dll",SetLastError=true)] static extern bool OpenThreadToken(IntPtr thread,uint access,bool self,out IntPtr token);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,SetLastError=true)] static extern bool LookupPrivilegeValue(string system,string name,out Luid luid);
|
||||
[DllImport("advapi32.dll",SetLastError=true)] static extern bool AdjustTokenPrivileges(IntPtr token,bool disable,ref TokenPrivileges value,uint size,out TokenPrivileges previous,out uint required);
|
||||
IntPtr token;TokenPrivileges previous;
|
||||
public Privilege(){IntPtr thread;
|
||||
if(OpenThreadToken(GetCurrentThread(),8,true,out thread)){CloseHandle(thread);throw new InvalidOperationException("Impersonated recovery is unsupported.");}
|
||||
int error=Marshal.GetLastWin32Error();if(error!=1008)throw new Win32Exception(error);
|
||||
if(!OpenProcessToken(GetCurrentProcess(),0x28,out token))throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
try{Luid luid;if(!LookupPrivilegeValue(null,"SeSecurityPrivilege",out luid))throw new Win32Exception(Marshal.GetLastWin32Error());TokenPrivileges request=new TokenPrivileges {Count=1,Luid=luid,Attributes=2};uint required;bool ok=AdjustTokenPrivileges(token,false,ref request,(uint)Marshal.SizeOf(typeof(TokenPrivileges)),out previous,out required);error=Marshal.GetLastWin32Error();if(!ok||error!=0)throw new Win32Exception(error,"SeSecurityPrivilege is unavailable.");}
|
||||
catch{CloseHandle(token);token=IntPtr.Zero;throw;}
|
||||
}
|
||||
public void Dispose(){if(token==IntPtr.Zero)return;try{TokenPrivileges ignored;uint required;bool ok=AdjustTokenPrivileges(token,false,ref previous,(uint)Marshal.SizeOf(typeof(TokenPrivileges)),out ignored,out required);int error=Marshal.GetLastWin32Error();if(!ok||error!=0)throw new Win32Exception(error,"SeSecurityPrivilege restoration failed.");}finally{CloseHandle(token);token=IntPtr.Zero;}}
|
||||
}
|
||||
public sealed class Target : IDisposable {
|
||||
[DllImport("kernel32.dll")] static extern IntPtr LocalFree(IntPtr value);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegOpenKeyExW(IntPtr root,string name,uint options,uint access,out IntPtr key);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegQueryValueExW(IntPtr key,string name,IntPtr reserved,out uint type,IntPtr data,ref uint size);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegQueryInfoKeyW(IntPtr key,IntPtr cls,IntPtr clsSize,IntPtr reserved,IntPtr subKeys,IntPtr maxSubKey,IntPtr maxClass,IntPtr values,IntPtr maxValueName,IntPtr maxValue,IntPtr securitySize,out long written);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegEnumKeyExW(IntPtr key,uint index,StringBuilder name,ref uint length,IntPtr reserved,IntPtr cls,IntPtr clsLength,IntPtr written);
|
||||
[DllImport("advapi32.dll")] static extern int RegCloseKey(IntPtr key);
|
||||
[DllImport("ntdll.dll")] static extern int NtQueryKey(IntPtr key,int cls,IntPtr information,uint length,out uint resultLength);
|
||||
[DllImport("advapi32.dll")] static extern uint GetSecurityInfo(IntPtr handle,uint kind,uint flags,out IntPtr owner,out IntPtr group,out IntPtr dacl,out IntPtr sacl,out IntPtr descriptor);
|
||||
[DllImport("advapi32.dll")] static extern uint GetSecurityDescriptorLength(IntPtr descriptor);
|
||||
|
||||
readonly string path,nativePath;readonly List<IntPtr> keys=new List<IntPtr>();IntPtr handle;Privilege privilege;
|
||||
readonly bool writable;
|
||||
public Target(bool write){
|
||||
writable=write;
|
||||
string sid=TokenReader.Snapshot().Sid;
|
||||
string path="HKEY_USERS\\"+sid+"\\Software\\WELA\\AuditProbe";
|
||||
this.path=path;
|
||||
if(String.IsNullOrEmpty(path)||path.IndexOfAny(new char[]{'/','*','?','%','\0'})>=0)throw new InvalidOperationException("Exact local registry path required.");
|
||||
string[] parts=path.Split('\\');IntPtr root;
|
||||
if(parts[0]=="HKEY_LOCAL_MACHINE"){root=new IntPtr(unchecked((int)0x80000002));nativePath="\\REGISTRY\\MACHINE";}
|
||||
else if(parts[0]=="HKEY_USERS"){root=new IntPtr(unchecked((int)0x80000003));nativePath="\\REGISTRY\\USER";}
|
||||
else throw new InvalidOperationException("Only selected HKLM/HKU keys are supported.");
|
||||
if(parts.Length<2)throw new InvalidOperationException("Hive roots cannot be recovered.");
|
||||
for(int i=1;i<parts.Length;i++){if(parts[i].Length==0||parts[i]=="."||parts[i]=="..")throw new InvalidOperationException("Ambiguous registry path.");nativePath+="\\"+parts[i];}
|
||||
try{
|
||||
privilege=new Privilege();IntPtr current=root;
|
||||
for(int i=1;i<parts.Length;i++){
|
||||
IntPtr opened;int error=RegOpenKeyExW(current,parts[i],8,0x01020101U|(i==parts.Length-1?(8U|(write?2U:0U)):0U),out opened);
|
||||
if(error!=0)throw new Win32Exception(error,"Selected registry component open failed.");keys.Add(opened);current=opened;
|
||||
uint type,size=0;error=RegQueryValueExW(current,"SymbolicLinkValue",IntPtr.Zero,out type,IntPtr.Zero,ref size);
|
||||
if(error==0&&type==6)throw new InvalidOperationException("Registry symbolic-link component refused.");if(error!=0&&error!=2)throw new Win32Exception(error,"Registry link state is unreadable.");
|
||||
}
|
||||
handle=current;Check();AssertEmpty();
|
||||
}catch{Dispose();throw;}
|
||||
}
|
||||
string Check(){
|
||||
if(handle==IntPtr.Zero)throw new ObjectDisposedException("Target");uint required;int status=NtQueryKey(handle,3,IntPtr.Zero,0,out required);
|
||||
if((status!=unchecked((int)0xC0000023)&&status!=unchecked((int)0x80000005))||required<6||required>65536)throw new InvalidOperationException("Native registry name query is unavailable.");
|
||||
IntPtr buffer=Marshal.AllocHGlobal((int)required);
|
||||
try{uint actual;status=NtQueryKey(handle,3,buffer,required,out actual);if(status!=0||actual>required)throw new InvalidOperationException("Native registry name query failed.");int size=Marshal.ReadInt32(buffer);if(size<2||size%2!=0||size>required-4)throw new InvalidOperationException("Native registry name is malformed.");string name=Marshal.PtrToStringUni(IntPtr.Add(buffer,4),size/2);if(!String.Equals(name,nativePath,StringComparison.OrdinalIgnoreCase))throw new InvalidOperationException("Held registry name differs from the selected path.");}finally{Marshal.FreeHGlobal(buffer);}
|
||||
long written;int error=RegQueryInfoKeyW(handle,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,out written);if(error!=0)throw new Win32Exception(error,"Registry last-write observation failed.");return path+":"+written;
|
||||
}
|
||||
public void AssertEmpty(){if(handle==IntPtr.Zero)throw new ObjectDisposedException("Target");StringBuilder name=new StringBuilder(256);uint size=256;int error=RegEnumKeyExW(handle,0,name,ref size,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero,IntPtr.Zero);if(error==0||error==234)throw new InvalidOperationException("Recovery requires an observed empty registry descendant inventory.");if(error!=259)throw new Win32Exception(error,"Registry child enumeration is unknown.");}
|
||||
public Snapshot Read(){
|
||||
string identity=Check();AssertEmpty();IntPtr owner,group,dacl,sacl,descriptor;uint error=GetSecurityInfo(handle,4,511,out owner,out group,out dacl,out sacl,out descriptor);if(error!=0)throw new Win32Exception((int)error,"Full SDK-defined registry descriptor read failed.");byte[] bytes;
|
||||
try{uint size=GetSecurityDescriptorLength(descriptor);if(size<20||size>1048576)throw new InvalidOperationException("Invalid native descriptor size.");bytes=new byte[size];Marshal.Copy(descriptor,bytes,0,(int)size);}finally{LocalFree(descriptor);}
|
||||
AssertEmpty();if(Check()!=identity)throw new InvalidOperationException("Registry last-write identity changed during observation.");Snapshot result=Descriptor.Observe(path,identity,bytes);result.Values=ReadValues();if(Check()!=identity)throw new InvalidOperationException("Registry changed during value observation.");return result;
|
||||
}
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegEnumValueW(IntPtr key,uint index,StringBuilder name,ref uint nameLength,IntPtr reserved,out uint type,byte[] data,ref uint size);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegSetValueExW(IntPtr key,string name,uint reserved,uint type,byte[] data,uint size);
|
||||
[DllImport("advapi32.dll",CharSet=CharSet.Unicode,ExactSpelling=true)] static extern int RegDeleteValueW(IntPtr key,string name);
|
||||
[DllImport("kernel32.dll",ExactSpelling=true)] static extern void GetSystemTimePreciseAsFileTime(out long value);
|
||||
public static DateTime UtcNow(){long n;GetSystemTimePreciseAsFileTime(out n);return DateTime.FromFileTimeUtc(n);}
|
||||
Value[] ReadValues(){
|
||||
List<Value> values=new List<Value>();long total=0;
|
||||
for(uint i=0;i<=128;i++){
|
||||
StringBuilder name=new StringBuilder(16384);uint length=16384,type,size=65536;byte[] data=new byte[size];
|
||||
int error=RegEnumValueW(handle,i,name,ref length,IntPtr.Zero,out type,data,ref size);
|
||||
if(error==259)break;if(error!=0)throw new Win32Exception(error,"Bounded registry value inventory failed.");
|
||||
if(i==128||size>65536||length>=16384||total+size>1048576)throw new InvalidOperationException("Registry value inventory exceeds bound.");
|
||||
total+=size;byte[] exact=new byte[size];Array.Copy(data,exact,size);values.Add(new Value{Name=name.ToString(),Type=type,DataBase64=Convert.ToBase64String(exact)});
|
||||
}
|
||||
values.Sort((a,b)=>String.CompareOrdinal(a.Name,b.Name));return values.ToArray();
|
||||
}
|
||||
static bool SameValues(Value[] a,Value[] b){if(a.Length!=b.Length)return false;for(int i=0;i<a.Length;i++)if(a[i].Name!=b[i].Name||a[i].Type!=b[i].Type||a[i].DataBase64!=b[i].DataBase64)return false;return true;}
|
||||
Value Find(string name){foreach(Value v in ReadValues())if(String.Equals(v.Name,name,StringComparison.OrdinalIgnoreCase))return v;return null;}
|
||||
static string Encoded(string value){return Convert.ToBase64String(Encoding.Unicode.GetBytes(value+"\0"));}
|
||||
void Put(string name,string text){byte[] data=Encoding.Unicode.GetBytes(text+"\0");int error=RegSetValueExW(handle,name,0,1,data,(uint)data.Length);if(error!=0)throw new Win32Exception(error,"Owned probe value write failed.");Value v=Find(name);if(v==null||v.Type!=1||v.DataBase64!=Encoded(text))throw new InvalidOperationException("Owned value readback differs.");}
|
||||
public Operation Run(string nonce,string expectedIdentity,string expectedDescriptor){
|
||||
if(!writable||String.IsNullOrEmpty(nonce)||nonce.Length!=32)throw new InvalidOperationException("Fixed writable probe and generated nonce required.");
|
||||
foreach(char c in nonce)if(!((c>='0'&&c<='9')||(c>='a'&&c<='f')))throw new InvalidOperationException("Invalid probe nonce.");
|
||||
Operation r=new Operation{Nonce=nonce,Name="WELA_Probe_"+nonce,BeforeValue="WELA_BEFORE_"+nonce,AfterValue="WELA_AFTER_"+nonce,HandleId="0x"+handle.ToInt64().ToString("x"),CleanupComplete=false,Diagnostic=""};
|
||||
r.Before=Read();if(r.Before.Identity!=expectedIdentity||r.Before.DescriptorBase64!=expectedDescriptor)throw new InvalidOperationException("Probe key changed after planning.");
|
||||
if(Find(r.Name)!=null)throw new InvalidOperationException("Owned nonce value already exists; refusing overwrite.");
|
||||
bool attempted=false;
|
||||
try{
|
||||
attempted=true;Put(r.Name,r.BeforeValue);
|
||||
// Credit only the explicit existing REG_SZ modification, not creation/deletion.
|
||||
r.StartedUtc=UtcNow().ToString("o");Put(r.Name,r.AfterValue);r.WriteReturnedUtc=UtcNow().ToString("o");
|
||||
Snapshot during=Read();if(during.DescriptorBase64!=r.Before.DescriptorBase64)throw new InvalidOperationException("Probe key security changed.");
|
||||
r.CompletedUtc=UtcNow().ToString("o");r.Succeeded=true;
|
||||
}catch(Exception e){r.Diagnostic=e.ToString();}
|
||||
finally{
|
||||
try{
|
||||
Value owned=Find(r.Name);
|
||||
if(owned!=null){if(!attempted||owned.Type!=1||(owned.DataBase64!=Encoded(r.BeforeValue)&&owned.DataBase64!=Encoded(r.AfterValue)))throw new InvalidOperationException("Owned probe value drifted; refusing deletion.");int error=RegDeleteValueW(handle,r.Name);if(error!=0)throw new Win32Exception(error,"Owned probe cleanup failed.");}
|
||||
if(Find(r.Name)!=null)throw new InvalidOperationException("Owned probe value remains.");
|
||||
r.After=Read();if(r.After.DescriptorBase64!=r.Before.DescriptorBase64||!SameValues(r.Before.Values,r.After.Values))throw new InvalidOperationException("Probe changed unrelated values or key security.");
|
||||
r.CleanupComplete=true;
|
||||
}catch(Exception e){r.Diagnostic+=" Cleanup: "+e.ToString();r.Succeeded=false;}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
public void Dispose(){try{for(int i=keys.Count-1;i>=0;i--)RegCloseKey(keys[i]);keys.Clear();handle=IntPtr.Zero;}finally{if(privilege!=null){privilege.Dispose();privilege=null;}}}
|
||||
}
|
||||
public sealed class Group { public string Sid; public uint Attributes; }
|
||||
public sealed class TokenPrivilege { public string Luid; public uint Attributes; }
|
||||
public sealed class Token {
|
||||
public string Sid, Name, AuthenticationId, AuthenticationType, ImpersonationLevel, TokenSource;
|
||||
public Group[] Groups; public TokenPrivilege[] Privileges;
|
||||
}
|
||||
public static class TokenReader {
|
||||
[DllImport("kernel32.dll",ExactSpelling=true)] static extern void GetSystemTimePreciseAsFileTime(out long value);
|
||||
public static DateTime UtcNow() {long value;GetSystemTimePreciseAsFileTime(out value);return DateTime.FromFileTimeUtc(value);}
|
||||
[StructLayout(LayoutKind.Sequential)] struct Luid {public uint Low; public int High;}
|
||||
[StructLayout(LayoutKind.Sequential)] struct Statistics {public Luid TokenId,AuthenticationId;public long Expiration;public int Type,Level;public uint Charged,Available,Groups,Privileges;public Luid Modified;}
|
||||
[StructLayout(LayoutKind.Sequential)] struct SidAndAttributes {public IntPtr Sid;public uint Attributes;}
|
||||
[StructLayout(LayoutKind.Sequential)] struct TokenGroups {public uint Count;public SidAndAttributes First;}
|
||||
[StructLayout(LayoutKind.Sequential)] struct LuidAndAttributes {public Luid Luid;public uint Attributes;}
|
||||
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
|
||||
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentThread();
|
||||
[DllImport("kernel32.dll",SetLastError=true)] static extern bool CloseHandle(IntPtr h);
|
||||
[DllImport("advapi32.dll",SetLastError=true)] static extern bool OpenProcessToken(IntPtr p,uint access,out IntPtr t);
|
||||
[DllImport("advapi32.dll",SetLastError=true)] static extern bool OpenThreadToken(IntPtr p,uint access,bool self,out IntPtr t);
|
||||
[DllImport("advapi32.dll",SetLastError=true)] static extern bool GetTokenInformation(IntPtr t,int cls,IntPtr data,int length,out int needed);
|
||||
static string Hex(Luid id) {return "0x"+(((ulong)(uint)id.High<<32)|id.Low).ToString("x");}
|
||||
static IntPtr Read(IntPtr token,int cls,out int length) {
|
||||
GetTokenInformation(token,cls,IntPtr.Zero,0,out length);
|
||||
if(Marshal.GetLastWin32Error()!=122||length<4||length>65536)throw new InvalidOperationException("Unknown or oversized token information.");
|
||||
IntPtr data=Marshal.AllocHGlobal(length);
|
||||
if(!GetTokenInformation(token,cls,data,length,out length)){int error=Marshal.GetLastWin32Error();Marshal.FreeHGlobal(data);throw new Win32Exception(error);}
|
||||
return data;
|
||||
}
|
||||
static Token ReadToken(IntPtr token,string source) {
|
||||
Token result=new Token();result.TokenSource=source;using(WindowsIdentity identity=new WindowsIdentity(token)){result.Sid=identity.User.Value;result.Name=identity.Name;result.AuthenticationType=identity.AuthenticationType;result.ImpersonationLevel=identity.ImpersonationLevel.ToString();}
|
||||
int length;IntPtr p=Read(token,10,out length);
|
||||
try {if(length<Marshal.SizeOf(typeof(Statistics)))throw new InvalidOperationException("Truncated token statistics.");result.AuthenticationId=Hex(((Statistics)Marshal.PtrToStructure(p,typeof(Statistics))).AuthenticationId);}finally{Marshal.FreeHGlobal(p);}
|
||||
p=Read(token,2,out length);
|
||||
try {int count=Marshal.ReadInt32(p),offset=(int)Marshal.OffsetOf(typeof(TokenGroups),"First"),size=Marshal.SizeOf(typeof(SidAndAttributes));if(count<0||count>4096||offset+(long)count*size>length)throw new InvalidOperationException("Invalid token groups.");List<Group> groups=new List<Group>();for(int i=0;i<count;i++){SidAndAttributes g=(SidAndAttributes)Marshal.PtrToStructure(IntPtr.Add(p,offset+i*size),typeof(SidAndAttributes));groups.Add(new Group{Sid=new SecurityIdentifier(g.Sid).Value,Attributes=g.Attributes});}groups.Sort((a,b)=>String.CompareOrdinal(a.Sid,b.Sid));result.Groups=groups.ToArray();}finally{Marshal.FreeHGlobal(p);}
|
||||
p=Read(token,3,out length);
|
||||
try {int count=Marshal.ReadInt32(p),size=Marshal.SizeOf(typeof(LuidAndAttributes));if(count<0||count>4096||4+(long)count*size>length)throw new InvalidOperationException("Invalid token privileges.");List<TokenPrivilege> privileges=new List<TokenPrivilege>();for(int i=0;i<count;i++){LuidAndAttributes v=(LuidAndAttributes)Marshal.PtrToStructure(IntPtr.Add(p,4+i*size),typeof(LuidAndAttributes));privileges.Add(new TokenPrivilege{Luid=Hex(v.Luid),Attributes=v.Attributes});}privileges.Sort((a,b)=>String.CompareOrdinal(a.Luid,b.Luid));result.Privileges=privileges.ToArray();}finally{Marshal.FreeHGlobal(p);}
|
||||
return result;
|
||||
}
|
||||
[DllImport("advapi32.dll")] static extern bool IsTokenRestricted(IntPtr token);
|
||||
public static Token Snapshot() {
|
||||
IntPtr thread=IntPtr.Zero,process=IntPtr.Zero;
|
||||
if(!OpenThreadToken(GetCurrentThread(),8,true,out thread)){int error=Marshal.GetLastWin32Error();if(error!=1008)throw new Win32Exception(error);}
|
||||
try {
|
||||
if(!OpenProcessToken(GetCurrentProcess(),8,out process))throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
if(IsTokenRestricted(process)||(thread!=IntPtr.Zero&&IsTokenRestricted(thread)))throw new InvalidOperationException("Restricted tokens are unsupported.");
|
||||
Token primary=ReadToken(process,"Process");
|
||||
if(thread!=IntPtr.Zero)throw new InvalidOperationException("Impersonated recovery is unsupported.");
|
||||
return primary;
|
||||
} finally {if(process!=IntPtr.Zero)CloseHandle(process);if(thread!=IntPtr.Zero)CloseHandle(thread);}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user