Files
WELA/scripts/FailedLogonProbeNative.cs
田中ザック Isaac Mathis 6d228fedef Add a native local failed-logon audit probe (#444)
* 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
2026-09-21 22:13:20 +09:00

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;
}
}
}