mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-23 22:53:00 +02:00
Fix WEC variant decoding and native authorization fixture
This commit is contained in:
1 parent
61f5fd1688
commit
59a83a20cc
4 files changed
+73
-13
No files matched your search
@@ -31,6 +31,20 @@ namespace Wela.WecAuthorization {
|
||||
}
|
||||
if(!String.Equals(expected.ToString(),value,StringComparison.Ordinal))throw new ArgumentException("Unsupported authorization descriptor.");
|
||||
}
|
||||
// Public only for allocated-buffer ABI/type regression tests; performs no native calls.
|
||||
public static object Decode(IntPtr buffer,uint size,int property) {
|
||||
if(buffer==IntPtr.Zero||size<16||size>1048576||property!=0&&property!=6&&property!=7&&property!=10&&property!=11&&property!=19&&property!=27&&property!=31)throw new InvalidOperationException("Invalid native property buffer or selection.");
|
||||
int type=Marshal.ReadInt32(buffer,12);
|
||||
if(property==27){if(type!=2)throw new InvalidOperationException("Subscription type is not UInt32.");return unchecked((uint)Marshal.ReadInt32(buffer));}
|
||||
if(property==0){if(type!=1)throw new InvalidOperationException("Enabled is not a scalar Boolean.");int value=Marshal.ReadInt32(buffer);if(value!=0&&value!=1)throw new InvalidOperationException("Invalid native Boolean.");return value==1;}
|
||||
if(type==0&&property==6)return "";
|
||||
if(type!=4)throw new InvalidOperationException("Expected scalar native string.");
|
||||
IntPtr pointer=Marshal.ReadIntPtr(buffer);long offset=pointer.ToInt64()-buffer.ToInt64();
|
||||
if(pointer==IntPtr.Zero||offset<16||offset>size-2)throw new InvalidOperationException("Native string pointer is outside its buffer.");
|
||||
StringBuilder text=new StringBuilder();
|
||||
for(int i=0;i<524288&&offset+2L*i+2<=size;i++){char c=(char)(ushort)Marshal.ReadInt16(pointer,2*i);if(c==0)return text.ToString();text.Append(c);}
|
||||
throw new InvalidOperationException("Unterminated native string.");
|
||||
}
|
||||
static object Read(IntPtr h,int property) {
|
||||
uint size=16;
|
||||
for(int attempt=0;attempt<3;attempt++) {
|
||||
@@ -39,16 +53,7 @@ namespace Wela.WecAuthorization {
|
||||
uint used;bool ok=EcGetSubscriptionProperty(h,property,0,size,buffer,out used);int error=Marshal.GetLastWin32Error();
|
||||
if(!ok){if(error!=122)throw new Win32Exception(error);if(used<=size||used>1048576)throw new InvalidOperationException("Invalid native property buffer size.");size=used;continue;}
|
||||
if(used<16||used>size)throw new InvalidOperationException("Invalid native property length.");
|
||||
int type=Marshal.ReadInt32(buffer,12);
|
||||
if(property==27){if(type!=8)throw new InvalidOperationException("Subscription type is not UInt32.");return unchecked((uint)Marshal.ReadInt32(buffer));}
|
||||
if(property==0){if(type!=1)throw new InvalidOperationException("Enabled is not a scalar Boolean.");int value=Marshal.ReadInt32(buffer);if(value!=0&&value!=1)throw new InvalidOperationException("Invalid native Boolean.");return value==1;}
|
||||
if(type==0&&property==6)return "";
|
||||
if(type!=4)throw new InvalidOperationException("Expected scalar native string.");
|
||||
IntPtr pointer=Marshal.ReadIntPtr(buffer);long offset=pointer.ToInt64()-buffer.ToInt64();
|
||||
if(pointer==IntPtr.Zero||offset<16||offset>used-2)throw new InvalidOperationException("Native string pointer is outside its buffer.");
|
||||
StringBuilder text=new StringBuilder();
|
||||
for(int i=0;i<524288&&offset+2L*i+2<=used;i++){char c=(char)(ushort)Marshal.ReadInt16(pointer,2*i);if(c==0)return text.ToString();text.Append(c);}
|
||||
throw new InvalidOperationException("Unterminated native string.");
|
||||
return Decode(buffer,used,property);
|
||||
}finally{Marshal.FreeHGlobal(buffer);}
|
||||
}
|
||||
throw new InvalidOperationException("Native property changed repeatedly.");
|
||||
|
||||
@@ -18,6 +18,32 @@ Assert ((Get-WelaWecAuthorizationKey @(Get-WelaWecAuthorizationSids @($sidB,$sid
|
||||
foreach($good in @($authA,$authAB)){[Wela.WecAuthorization.Edit]::ValidateAuthorization($good);$count++}
|
||||
foreach($bad in @('',$authA.Replace('GA','GR'),$authA.Replace('NSG:NS','SYG:SY'),($authA+$authA),$authA.Replace('11-22','011-22'),$authA.Replace('11-22','4294967296-22'),$authAB.Replace($sidB,$sidA),('O:NSG:NSD:(A;;GA;;;'+$sidB+')(A;;GA;;;'+$sidA+')'),($authA+'S:(AU;SA;GA;;;WD)'))){Reject {[Wela.WecAuthorization.Edit]::ValidateAuthorization($bad)}}
|
||||
Assert ([Wela.WecAuthorization.Edit]::SourceSha256 -ceq (Get-WelaArrivalHash ([IO.File]::ReadAllBytes("$repo/scripts/WecAuthorizationNative.cs")))) 'Compiled native helper binds the exact source bytes'
|
||||
# Allocated EC_VARIANT buffers exercise the WEC ABI rather than EVT_VARIANT values.
|
||||
$buffer=[Runtime.InteropServices.Marshal]::AllocHGlobal(64)
|
||||
try {
|
||||
for($i=0;$i -lt 64;$i++){[Runtime.InteropServices.Marshal]::WriteByte($buffer,$i,0)}
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,12,2)
|
||||
Assert ([Wela.WecAuthorization.Edit]::Decode($buffer,16,27) -eq [uint32]0) 'EcVarTypeUInt32 is 2 and source-initiated value is zero'
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,0,1)
|
||||
Assert ([Wela.WecAuthorization.Edit]::Decode($buffer,16,27) -eq [uint32]1) 'Collector-initiated scalar remains distinguishable'
|
||||
foreach($type in @(0,1,4,8,130)){[Runtime.InteropServices.Marshal]::WriteInt32($buffer,12,$type);Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,16,27)} 'UInt32'}
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,12,1)
|
||||
Assert ([Wela.WecAuthorization.Edit]::Decode($buffer,16,0) -eq $true) 'Native scalar Boolean decodes true'
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,0,0)
|
||||
Assert ([Wela.WecAuthorization.Edit]::Decode($buffer,16,0) -eq $false) 'Native scalar Boolean decodes false'
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,0,2);Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,16,0)} 'Boolean'
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,12,4)
|
||||
[Runtime.InteropServices.Marshal]::WriteIntPtr($buffer,[IntPtr]::Add($buffer,16));[Runtime.InteropServices.Marshal]::WriteInt16($buffer,16,65)
|
||||
Assert ([Wela.WecAuthorization.Edit]::Decode($buffer,20,31) -ceq 'A') 'String data is decoded within returned bounds'
|
||||
Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,18,31)} 'Unterminated'
|
||||
[Runtime.InteropServices.Marshal]::WriteIntPtr($buffer,[IntPtr]::Add($buffer,64));Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,20,31)} 'outside'
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,12,132);Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,20,31)} 'scalar'
|
||||
Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,15,31)} 'buffer';Reject {[Wela.WecAuthorization.Edit]::Decode([IntPtr]::Zero,16,31)} 'buffer'
|
||||
Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,16,1)} 'selection'
|
||||
[Runtime.InteropServices.Marshal]::WriteInt32($buffer,12,0)
|
||||
Assert ([Wela.WecAuthorization.Edit]::Decode($buffer,16,6) -ceq '') 'Absent description normalizes to empty'
|
||||
Reject {[Wela.WecAuthorization.Edit]::Decode($buffer,16,31)} 'scalar'
|
||||
}finally{[Runtime.InteropServices.Marshal]::FreeHGlobal($buffer)}
|
||||
$before=Get-WelaWecAuthorizationDefinition $base;$after=Get-WelaWecAuthorizationDefinition ($base.Replace($authA,$authAB))
|
||||
Assert ($before.SourceSids.Count -eq 1 -and $after.SourceSids.Count -eq 2 -and $before.PreservedKey -ceq $after.PreservedKey -and $before.WholeKey -cne $after.WholeKey) 'Only the explicit allow list is excluded from preserved XML'
|
||||
foreach($bad in @($base.Replace('SourceInitiated','CollectorInitiated'),$base.Replace('>false</Enabled>','>true</Enabled>'),$base.Replace($authA,'D:(A;;GA;;;WD)'),$base.Replace($authA,''),$base.Replace($authA,$authAB.Replace($sidB,$sidA)),$base.Replace('Path="Security"','Path="Microsoft-Windows-Sysmon/Operational"'),$base.Replace('</Subscription>','<AllowedSourceDomainComputers>bad</AllowedSourceDomainComputers></Subscription>'))){Reject {Get-WelaWecAuthorizationDefinition $bad}}
|
||||
|
||||
@@ -15,12 +15,13 @@ function Key($Value){Get-WelaWecAuthorizationKey $Value}
|
||||
function Services {@(Get-CimInstance Win32_Service -Filter "Name='Wecsvc' OR Name='Winmgmt' OR Name='EventLog' OR Name='WinRM'"|Sort-Object Name|Select-Object Name,State,StartMode)}
|
||||
function Channel {$c=[Diagnostics.Eventing.Reader.EventLogConfiguration]::new('ForwardedEvents');try{[pscustomobject]@{Name=$c.LogName;Enabled=$c.IsEnabled;Mode=[string]$c.LogMode;MaximumBytes=$c.MaximumSizeInBytes;Path=$c.LogFilePath;SecurityDescriptor=$c.SecurityDescriptor}}finally{$c.Dispose()}}
|
||||
function EnableChannel([bool]$Value){$c=[Diagnostics.Eventing.Reader.EventLogConfiguration]::new('ForwardedEvents');try{$c.IsEnabled=$Value;$c.SaveChanges()}finally{$c.Dispose()}}
|
||||
function Ids {@((Invoke-WelaNative 'wecutil.exe' @('es')).Output|ForEach-Object {$_.ToString().Trim()}|Where-Object {$_}|Sort-Object)}
|
||||
function Inventory {$ids=@(Ids);if($ids.Count -gt 64){throw 'Disposable inventory exceeds bound.'};@($ids|ForEach-Object {[pscustomobject]@{Id=$_;Xml=Read-WelaWecSubscriptionXml $_}})}
|
||||
Add-Type -Path (Join-Path $PSScriptRoot 'WecAuthorizationFixtureNative.cs')
|
||||
function Ids {[Wela.WecAuthorizationFixture.Inventory]::Read()|Sort-Object}
|
||||
function Inventory {$ids=@(Ids);Save 'last-observed-ids.json' $ids;@($ids|ForEach-Object {[pscustomobject]@{Id=$_;Xml=Read-WelaWecSubscriptionXml $_}})}
|
||||
$nonce=[guid]::NewGuid().ToString('N');$id='WELA-Authorization-'+$nonce;$description='Owned authorization '+$nonce+' '+[char]0x65e5+[char]0x672c
|
||||
$sidA='S-1-5-21-111111111-222222222-333333333-1234';$sidB='S-1-5-21-111111111-222222222-333333333-1235'
|
||||
$root=Join-Path $env:RUNNER_TEMP ('wela-wec-authorization-'+$nonce);$null=New-Item -ItemType Directory $root
|
||||
function Save($Name,$Value){$Value|ConvertTo-Json -Depth 24|Set-Content -LiteralPath (Join-Path $root $Name) -Encoding UTF8}
|
||||
function Save($Name,$Value){ConvertTo-Json -InputObject $Value -Depth 24|Set-Content -LiteralPath (Join-Path $root $Name) -Encoding UTF8}
|
||||
# Native -File argument binding cannot portably carry a string[] on both engines.
|
||||
# This fixture wrapper supplies the selected array to the actual public script.
|
||||
$wrapper=Join-Path $root 'invoke-public.ps1'
|
||||
@@ -32,7 +33,9 @@ if($PSBoundParameters.ContainsKey('Id')){$p.WecAuthorizationId=$Id}
|
||||
if($PSBoundParameters.ContainsKey('Sids')){$p.WecAuthorizationSourceSid=@($Sids.Split(';'))}
|
||||
if($PSBoundParameters.ContainsKey('PlanPath')){$p.WecAuthorizationPlanPath=$PlanPath}
|
||||
if($PSBoundParameters.ContainsKey('PlanHash')){$p.WecAuthorizationPlanHash=$PlanHash}
|
||||
$global:LASTEXITCODE=0
|
||||
& $WelaPath @p
|
||||
exit $LASTEXITCODE
|
||||
'@|Set-Content -LiteralPath $wrapper -Encoding UTF8
|
||||
function Public([string[]]$Arguments,[string]$Output,[bool]$Success=$true){
|
||||
$prior=$ErrorActionPreference;try{$ErrorActionPreference='Continue';$text=@(&$engine -NoLogo -NoProfile -NonInteractive -File $wrapper -WelaPath "$repo/WELA.ps1" @Arguments -OutputPath $Output 2>&1);$code=$LASTEXITCODE}finally{$ErrorActionPreference=$prior}
|
||||
@@ -48,6 +51,7 @@ try {
|
||||
$wec=@($beforeServices|Where-Object Name -eq Wecsvc);Assert ($wec.Count -eq 1 -and $wec[0].State -in @('Running','Stopped') -and $wec[0].StartMode -in @('Auto','Manual','Disabled')) 'Stable original service state required'
|
||||
if($wec[0].StartMode -eq 'Disabled'){Set-Service Wecsvc -StartupType Manual};if($wec[0].State -eq 'Stopped'){Start-Service Wecsvc}
|
||||
if(-not $beforeChannel.Enabled){EnableChannel $true}
|
||||
Save 'original-console-enumeration.json' (Invoke-WelaNative 'wecutil.exe' @('es'))
|
||||
$original=@(Inventory);Save 'original-inventory.json' $original;Assert (@(Ids) -notcontains $id) 'Unique owned subscription is initially absent'
|
||||
$query='<QueryList><Query Id="0" Path="Application"><Select Path="Application">*[System[(EventID=1)]]</Select></Query></QueryList>'
|
||||
$xml=@"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Read-only disposable-fixture inventory; bypasses console/native text decoding.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
namespace Wela.WecAuthorizationFixture {
|
||||
public static class Inventory {
|
||||
[DllImport("wecapi.dll",SetLastError=true)] static extern IntPtr EcOpenSubscriptionEnum(uint flags);
|
||||
[DllImport("wecapi.dll",CharSet=CharSet.Unicode,SetLastError=true)] [return:MarshalAs(UnmanagedType.Bool)] static extern bool EcEnumNextSubscription(IntPtr enumeration,uint size,StringBuilder name,out uint used);
|
||||
[DllImport("wecapi.dll",SetLastError=true)] [return:MarshalAs(UnmanagedType.Bool)] static extern bool EcClose(IntPtr handle);
|
||||
public static string[] Read() {
|
||||
IntPtr handle=EcOpenSubscriptionEnum(0);if(handle==IntPtr.Zero)throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
try {
|
||||
var names=new List<string>();
|
||||
while(true) {
|
||||
var name=new StringBuilder(4096);uint used;
|
||||
if(!EcEnumNextSubscription(handle,4096,name,out used)) {int error=Marshal.GetLastWin32Error();if(error==259)return names.ToArray();throw new Win32Exception(error);}
|
||||
if(used<2||used>4096||name.Length==0||name.Length+1!=used||names.Count>=64||names.Contains(name.ToString()))throw new InvalidOperationException("Disposable native subscription inventory is invalid, duplicated or exceeds its bound.");
|
||||
names.Add(name.ToString());
|
||||
}
|
||||
}finally {EcClose(handle);}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user