There is actually no easy way to do it with all restrictions you have got. ETW is the lowest you can go, however given your restriction of not using it, you have 2 options:
- Write kernel driver yourself within C and expose its interface, then use P/Invoke from c#
- Use
evntrace.dll to monitor ETW events (OpenTrace and ProcessTrace) and further tdh.dll if you need detailed information.
You can find example of parsing detailed data using tdh here
Bellow is example of the implementation to give you an idea (AI generated, not tested):
* Note 1 - This is Event Id for Start Process event. You can also subscribe to Close (id 2) or others. Referee to the documentation
* Note 2 - This is guid of Kernel Process
using System;
using System.Runtime.InteropServices;
public class EtwProcessMonitor
{
private const int PROCESS_START_EVENT_ID = 1; // Note 1
private static readonly Guid KernelProcessGuid = new Guid("3d6fa8d1-fe05-11d0-9dda-00c04fd7ba7c"); // Note 2
private delegate void EventRecordCallback(ref EVENT_RECORD eventRecord);
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_TRACE_LOGFILE
{
[MarshalAs(UnmanagedType.LPWStr)]
public string LogFileName;
[MarshalAs(UnmanagedType.LPWStr)]
public string LoggerName;
public IntPtr ProcessTraceMode;
public EventRecordCallback EventCallback;
public uint BufferSize;
public uint MinimumBuffers;
public uint MaximumBuffers;
public uint MaximumFileSize;
public uint LogFileMode;
public uint FlushTimer;
public uint EnableFlags;
public int AgeLimit;
public IntPtr Context;
public Guid Guid;
public IntPtr UserContext;
public IntPtr EventCallback2;
}
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_HEADER
{
public ushort Size;
public ushort HeaderType;
public ushort Flags;
public ushort EventProperty;
public uint ThreadId;
public uint ProcessId;
public long TimeStamp;
public Guid ProviderId;
public EVENT_DESCRIPTOR EventDescriptor;
public ulong ProcessorTime;
public Guid ActivityId;
}
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_DESCRIPTOR
{
public ushort Id;
public byte Version;
public byte Channel;
public byte Level;
public byte Opcode;
public ushort Task;
public ulong Keyword;
}
[StructLayout(LayoutKind.Sequential)]
public struct EVENT_RECORD
{
public EVENT_HEADER EventHeader;
public IntPtr BufferContext;
public ushort ExtendedDataCount;
public ushort UserDataLength;
public IntPtr ExtendedData;
public IntPtr UserData;
public IntPtr UserContext;
}
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
private static extern ulong OpenTrace(ref EVENT_TRACE_LOGFILE logfile);
[DllImport("advapi32.dll")]
private static extern uint ProcessTrace(ulong[] handleArray, uint handleCount, IntPtr startTime, IntPtr endTime);
public void Start()
{
EVENT_TRACE_LOGFILE logfile = new EVENT_TRACE_LOGFILE
{
LoggerName = "NT Kernel Logger",
ProcessTraceMode = (IntPtr)(0x00000100 | 0x00000020), // PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD
EventCallback = new EventRecordCallback(OnEvent),
Guid = KernelProcessGuid
};
ulong traceHandle = OpenTrace(ref logfile);
if (traceHandle == ulong.MaxValue)
{
Console.WriteLine("Failed to open trace.");
return;
}
Console.WriteLine("Listening for process start events...");
ProcessTrace(new[] { traceHandle }, 1, IntPtr.Zero, IntPtr.Zero);
}
private void OnEvent(ref EVENT_RECORD record)
{
if (record.EventHeader.EventDescriptor.Id == PROCESS_START_EVENT_ID)
{
Console.WriteLine($"Process started: PID = {record.EventHeader.ProcessId}");
}
}
}