mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-07 23:14:45 +02:00
Use precise native DNS operation timestamps and nonce-only event reads
This commit is contained in:
1 parent
cddafd04e3
commit
2973eafb98
5 files changed
+30
-11
No files matched your search
@@ -59,6 +59,7 @@ function Get-WelaDnsClientProbeWatermark {
|
||||
}
|
||||
function Start-WelaDnsClientProbeQuery {
|
||||
param($State,[string]$Resolver,[string]$QueryName,$Report)
|
||||
Initialize-WelaDnsClientProbeNative
|
||||
$fresh=Get-WelaDnsClientProbeState
|
||||
if((Get-WelaDnsClientProbeStateKey $fresh) -cne (Get-WelaDnsClientProbeStateKey $State)){throw 'DNS prerequisites changed before query.'}
|
||||
$boundary=Get-WelaDnsClientProbeWatermark
|
||||
@@ -67,7 +68,7 @@ function Start-WelaDnsClientProbeQuery {
|
||||
$info=[Diagnostics.ProcessStartInfo]::new();$info.FileName=$State.Engine;$info.Arguments='-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "'+$worker+'" -Resolver "'+$Resolver+'" -QueryName "'+$QueryName+'"';$info.UseShellExecute=$false;$info.CreateNoWindow=$true;$info.RedirectStandardOutput=$true;$info.RedirectStandardError=$true;$info.StandardOutputEncoding=[Text.UTF8Encoding]::new($false,$true);$info.StandardErrorEncoding=[Text.UTF8Encoding]::new($false,$true)
|
||||
$process=[Diagnostics.Process]::new();$process.StartInfo=$info;$started=$false
|
||||
try{
|
||||
$launch=[DateTimeOffset]::UtcNow;$started=$process.Start();if(-not $started){throw 'DNS probe worker did not start.'}
|
||||
$launch=[DateTimeOffset][Wela.DnsClientProbe.Native]::UtcNow();$started=$process.Start();if(-not $started){throw 'DNS probe worker did not start.'}
|
||||
$output=$process.StandardOutput.ReadToEndAsync();$errorText=$process.StandardError.ReadToEndAsync()
|
||||
if(-not $process.WaitForExit(20000)){throw 'DNS query worker exceeded twenty seconds; operation completion is unverified.'}
|
||||
if(-not [Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]@($output,$errorText),5000)){throw 'DNS worker output did not finish.'}
|
||||
@@ -79,7 +80,7 @@ function Start-WelaDnsClientProbeQuery {
|
||||
if($operation.ProcessId -ne $process.Id -or $operation.Query.QueryName -cne $QueryName -or $operation.Query.Resolver -cne $Resolver -or $operation.Query.Options -ne 2103790 -or $operation.Query.Status -ne $operation.Query.ResultStatus -or $operation.Query.Status -notin @(0,9003,9501)){throw ('Unexpected DNS worker response or unsupported native outcome: PID='+$operation.ProcessId+' expectedPID='+$process.Id+' options='+$operation.Query.Options+' APIstatus='+$operation.Query.Status+' resultStatus='+$operation.Query.ResultStatus)}
|
||||
$begin=ConvertTo-WelaArrivalUtc $operation.StartedUtc;$end=ConvertTo-WelaArrivalUtc $operation.CompletedUtc
|
||||
$operation.StartedUtc=$begin.UtcDateTime.ToString('o');$operation.CompletedUtc=$end.UtcDateTime.ToString('o')
|
||||
if($begin -lt $launch -or $end -lt $begin -or $end -gt [DateTimeOffset]::UtcNow -or ($end-$begin).TotalSeconds -gt 20){throw 'Invalid DNS operation timestamps.'}
|
||||
if($operation.Clock -cne 'GetSystemTimePreciseAsFileTime' -or $begin -lt $launch -or $end -lt $begin -or $end -gt [DateTimeOffset][Wela.DnsClientProbe.Native]::UtcNow() -or ($end-$begin).TotalSeconds -gt 20){throw 'Invalid DNS operation timestamps.'}
|
||||
if((Get-WelaChannelReadKey $operation.BeforeToken) -cne (Get-WelaChannelReadKey $operation.AfterToken) -or (Get-WelaDnsClientProbeReaderKey $operation.BeforeToken) -cne (Get-WelaDnsClientProbeReaderKey $callerBefore)){throw 'DNS worker token differs from observed caller or changed.'}
|
||||
$operation|Add-Member NoteProperty RecordIdBefore $boundary
|
||||
$operation|Add-Member NoteProperty CallerBefore $callerBefore
|
||||
@@ -89,7 +90,11 @@ function Start-WelaDnsClientProbeQuery {
|
||||
function Read-WelaDnsClientProbeEvents {
|
||||
param($Operation)
|
||||
$channel='Microsoft-Windows-DNS-Client/Operational'
|
||||
$xpath="*[System[Provider[@Name='Microsoft-Windows-DNS-Client'] and EventID=3008 and EventRecordID>$($Operation.RecordIdBefore) and TimeCreated[@SystemTime>='$($Operation.StartedUtc)' and @SystemTime<='$($Operation.CompletedUtc)']]]"
|
||||
# Read only this nonce in a bounded recent interval. The validator still requires
|
||||
# exact operation timestamps; outside-interval XML is useful failure evidence only.
|
||||
$name=$Operation.Query.QueryName
|
||||
if($name -cnotmatch '^wela-[a-f0-9]{32}\.wela\.test\.$'){throw 'Unexpected DNS event query name.'}
|
||||
$xpath="*[System[Provider[@Name='Microsoft-Windows-DNS-Client'] and EventID=3008 and EventRecordID>$($Operation.RecordIdBefore) and TimeCreated[timediff(@SystemTime)<=60000]] and EventData[Data[@Name='QueryName']='$name' or Data[@Name='QueryName']='$($name.TrimEnd('.'))']]"
|
||||
$reader=$null;$record=$null;$xml=@();$timer=[Diagnostics.Stopwatch]::StartNew()
|
||||
try{
|
||||
$query=[Diagnostics.Eventing.Reader.EventLogQuery]::new($channel,[Diagnostics.Eventing.Reader.PathType]::LogName,$xpath);$query.TolerateQueryErrors=$false
|
||||
|
||||
@@ -20,15 +20,15 @@ namespace Wela.DnsClientProbe {
|
||||
// DnsQueryEx is the documented exact export; do not allow a W-suffixed name probe.
|
||||
[DllImport("dnsapi.dll",EntryPoint="DnsQueryEx",ExactSpelling=true)] static extern uint DnsQueryEx(ref Request request,ref QueryResult result,IntPtr cancel);
|
||||
[DllImport("dnsapi.dll")] static extern void DnsRecordListFree(IntPtr records,int freeType);
|
||||
[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); }
|
||||
public static string ValidateResolver(string resolver) {
|
||||
if(resolver==null||!Regex.IsMatch(resolver,@"^(0|[1-9][0-9]{0,2})(\.(0|[1-9][0-9]{0,2})){3}$"))throw new ArgumentException("One canonical dotted-decimal IPv4 resolver is required.");
|
||||
IPAddress address;if(!IPAddress.TryParse(resolver,out address)||address.AddressFamily!=System.Net.Sockets.AddressFamily.InterNetwork||address.ToString()!=resolver)throw new ArgumentException("Invalid IPv4 resolver.");
|
||||
byte[] bytes=address.GetAddressBytes();if(bytes[0]==0||bytes[0]>=224||resolver=="255.255.255.255")throw new ArgumentException("Unspecified, multicast and reserved/broadcast resolver addresses are refused.");
|
||||
return resolver;
|
||||
}
|
||||
public static Result Query(string name,string resolver) {
|
||||
if(IntPtr.Size!=8)throw new InvalidOperationException("Native 64-bit process required.");
|
||||
if(name==null||!Regex.IsMatch(name,@"^wela-[a-f0-9]{32}\.wela\.test\.\z"))throw new ArgumentException("Only the fixed random probe name is accepted.");
|
||||
static byte[] BuildServerArray(string resolver) {
|
||||
ValidateResolver(resolver);
|
||||
// SDK DNS_ADDR_ARRAY header32 + one DNS_ADDR64; sockaddr_in in its first16 bytes.
|
||||
// Match Microsoft Windows-classic-samples/DNSAsyncQuery CreateDnsServerList:
|
||||
@@ -36,6 +36,12 @@ namespace Wela.DnsClientProbe {
|
||||
byte[] server=new byte[96];BitConverter.GetBytes((uint)1).CopyTo(server,0);BitConverter.GetBytes((uint)1).CopyTo(server,4);
|
||||
BitConverter.GetBytes((ushort)2).CopyTo(server,32);
|
||||
IPAddress.Parse(resolver).GetAddressBytes().CopyTo(server,36);
|
||||
return server;
|
||||
}
|
||||
public static Result Query(string name,string resolver) {
|
||||
if(IntPtr.Size!=8)throw new InvalidOperationException("Native 64-bit process required.");
|
||||
if(name==null||!Regex.IsMatch(name,@"^wela-[a-f0-9]{32}\.wela\.test\.\z"))throw new ArgumentException("Only the fixed random probe name is accepted.");
|
||||
byte[] server=BuildServerArray(resolver);
|
||||
IntPtr servers=Marshal.AllocHGlobal(server.Length);QueryResult result=new QueryResult {Version=1};
|
||||
try {
|
||||
Marshal.Copy(server,0,servers,server.Length);
|
||||
|
||||
@@ -7,9 +7,9 @@ $ErrorActionPreference='Stop';$script:ScriptRoot=Split-Path $PSScriptRoot -Paren
|
||||
Initialize-WelaDnsClientProbeNative
|
||||
if((Get-Service Dnscache -ErrorAction Stop).Status -ne 'Running'){throw 'DNS Client must already be running.'}
|
||||
$before=Get-WelaChannelReader
|
||||
$start=[DateTime]::UtcNow.ToString('o')
|
||||
$start=[Wela.DnsClientProbe.Native]::UtcNow().ToString('o')
|
||||
$query=[Wela.DnsClientProbe.Native]::Query($QueryName,$Resolver)
|
||||
$end=[DateTime]::UtcNow.ToString('o')
|
||||
$end=[Wela.DnsClientProbe.Native]::UtcNow().ToString('o')
|
||||
$after=Get-WelaChannelReader
|
||||
if((Get-WelaChannelReadKey $before) -cne (Get-WelaChannelReadKey $after)){throw 'Worker primary token changed during DNS query.'}
|
||||
[pscustomobject]@{Query=$query;StartedUtc=$start;CompletedUtc=$end;ProcessId=$PID;BeforeToken=$before;AfterToken=$after}|ConvertTo-Json -Depth 12 -Compress
|
||||
[pscustomobject]@{Query=$query;StartedUtc=$start;CompletedUtc=$end;Clock='GetSystemTimePreciseAsFileTime';ProcessId=$PID;BeforeToken=$before;AfterToken=$after}|ConvertTo-Json -Depth 12 -Compress
|
||||
Reference in new issue
Block a user