mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-01 03:54:44 +02:00
* Add native local nonexistent-account failed-logon probe * Match actual MSV1 local authentication event package * Refuse coerced identity and authentication receipt fields * Preserve explicit UTC DateTime receipts on older PowerShell7 * Reject unknown failed-logon probe options before dispatch
40 lines
2.6 KiB
C#
40 lines
2.6 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.RegularExpressions;
|
|
namespace Wela.FailedLogonProbe {
|
|
public sealed class Attempt {
|
|
public string UserName, Domain, StartedUtc, CompletedUtc, Clock;
|
|
public int MissingAccountStatus, LogonType, LogonProvider, NativeError;
|
|
public bool Succeeded;
|
|
}
|
|
public static class Native {
|
|
[DllImport("kernel32.dll", ExactSpelling=true)] private static extern void GetSystemTimePreciseAsFileTime(out long value);
|
|
[DllImport("Netapi32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)] private static extern int NetUserGetInfo(string server,string user,int level,out IntPtr buffer);
|
|
[DllImport("Netapi32.dll", ExactSpelling=true)] private static extern int NetApiBufferFree(IntPtr buffer);
|
|
[DllImport("advapi32.dll", CharSet=CharSet.Unicode, ExactSpelling=true, SetLastError=true)]
|
|
[return:MarshalAs(UnmanagedType.Bool)] private static extern bool LogonUserW(string user,string domain,string password,int type,int provider,out IntPtr token);
|
|
[DllImport("kernel32.dll", ExactSpelling=true, SetLastError=true)]
|
|
[return:MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr handle);
|
|
public static DateTime UtcNow(){long value;GetSystemTimePreciseAsFileTime(out value);return DateTime.FromFileTimeUtc(value);}
|
|
public static Attempt Run(string nonce){
|
|
if(!Regex.IsMatch(nonce??"","\\A[a-f0-9]{32}\\z"))throw new ArgumentException("A generated lowercase GUID nonce is required.");
|
|
string user="WL"+nonce.Substring(0,18);IntPtr buffer=IntPtr.Zero;
|
|
int missing;
|
|
try{missing=NetUserGetInfo(null,user,0,out buffer);}finally{if(buffer!=IntPtr.Zero)NetApiBufferFree(buffer);}
|
|
// Never attempt a known or unreadable real account, and never query a domain server.
|
|
if(missing!=2221)throw new InvalidOperationException("Exact local account absence is not established; NetUserGetInfo="+missing);
|
|
Attempt result=new Attempt();result.UserName=user;result.Domain=".";result.MissingAccountStatus=missing;result.LogonType=3;result.LogonProvider=2;result.Clock="GetSystemTimePreciseAsFileTime";
|
|
IntPtr token=IntPtr.Zero;
|
|
result.StartedUtc=UtcNow().ToString("o");
|
|
try{
|
|
// This fixed public dummy is not a credential. There is exactly one attempt.
|
|
result.Succeeded=LogonUserW(user,".","WELA-public-noncredential",3,2,out token);
|
|
result.NativeError=result.Succeeded?0:Marshal.GetLastWin32Error();
|
|
result.CompletedUtc=UtcNow().ToString("o");
|
|
}finally{if(token!=IntPtr.Zero && !CloseHandle(token))throw new Win32Exception(Marshal.GetLastWin32Error());}
|
|
return result;
|
|
}
|
|
}
|
|
}
|