apply this method to my driver
the method
#define WINDOWS_1803 17134
#define WINDOWS_1809 17763
#define WINDOWS_1903 18362
#define WINDOWS_1909 18363
#define WINDOWS_2004 19041
#define WINDOWS_20H2 19569
#define WINDOWS_21H1 20180
#define WINDOWS_22H2 19045
 
const DWORD GetUserDirectoryTableBaseOffset()
{
	RTL_OSVERSIONINFOW ver = { 0 };
	RtlGetVersion(&ver);
	switch (ver.dwBuildNumber)
	{
	case WINDOWS_1803:
		return 0x0278;
		break;
	case WINDOWS_1809:
		return 0x0278;
		break;
	case WINDOWS_1903:
		return 0x0280;
		break;
	case WINDOWS_1909:
		return 0x0280;
		break;
	case WINDOWS_2004:
		return 0x0388;
		break;
	case WINDOWS_20H2:
		return 0x0388;
		break;
	case WINDOWS_21H1:
		return 0x0388;
		break;
	case WINDOWS_22H2:
		return 0x0388;
		break;
	default:
		return 0x0388;
	}
}
 
NTSTATUS ReadPhysicalAddress(PVOID TargetAddress, PVOID lpBuffer, SIZE_T Size, SIZE_T* BytesRead)
{
	MM_COPY_ADDRESS AddrToRead = { 0 };
	AddrToRead.PhysicalAddress.QuadPart = (LONGLONG)TargetAddress;
	return MmCopyMemory(lpBuffer, AddrToRead, Size, MM_COPY_MEMORY_PHYSICAL, BytesRead);
}
 
NTSTATUS WritePhysicalAddress(PVOID TargetAddress, PVOID lpBuffer, SIZE_T Size, SIZE_T* BytesWritten)
{
	if (!TargetAddress)
		return STATUS_UNSUCCESSFUL;
 
	PHYSICAL_ADDRESS AddrToWrite = { 0 };
	AddrToWrite.QuadPart = LONGLONG(TargetAddress);
 
	PVOID pmapped_mem = MmMapIoSpaceEx(AddrToWrite, Size, PAGE_READWRITE);
 
	if (!pmapped_mem)
		return STATUS_UNSUCCESSFUL;
 
	memcpy(pmapped_mem, lpBuffer, Size);
 
	*BytesWritten = Size;
	MmUnmapIoSpace(pmapped_mem, Size);
	return STATUS_SUCCESS;
}
 
const UINT64 GetProcessCr3(const PEPROCESS pProcess)
{
	PUCHAR process = (PUCHAR)pProcess;
	ULONG_PTR process_dirbase = *(PULONG_PTR)(process + 0x28); //dirbase x64, 32bit is 0x18
	if (process_dirbase == 0)
	{
		DWORD UserDirOffset = GetUserDirectoryTableBaseOffset();
 
		ULONG_PTR process_userdirbase = *(PULONG_PTR)(process + UserDirOffset);
		return process_userdirbase;
	}
	return process_dirbase;
}
 
 
const UINT64 GetKernelDirBase()
{
	PUCHAR process = (PUCHAR)PsGetCurrentProcess();
	ULONG_PTR cr3 = *(PULONG_PTR)(process + 0x28); //dirbase x64, 32bit is 0x18
	return cr3;
}
 
 
#define PAGE_OFFSET_SIZE 12
static const UINT64 PMASK = (~0xfull << 8) & 0xfffffffffull;
 
const UINT64 TranslateLinearAddress(UINT64 directoryTableBase, UINT64 virtualAddress)
{
	directoryTableBase &= ~0xf;
 
	UINT64 pageOffset = virtualAddress & ~(~0ul << PAGE_OFFSET_SIZE);
	UINT64 pte = ((virtualAddress >> 12) & (0x1ffll));
	UINT64 pt = ((virtualAddress >> 21) & (0x1ffll));
	UINT64 pd = ((virtualAddress >> 30) & (0x1ffll));
	UINT64 pdp = ((virtualAddress >> 39) & (0x1ffll));
 
	SIZE_T readsize = 0;
	UINT64 pdpe = 0;
	ReadPhysicalAddress(PVOID(directoryTableBase + 8 * pdp), &pdpe, sizeof(pdpe), &readsize);
	if (~pdpe & 1)
		return 0;
 
	UINT64 pde = 0;
	ReadPhysicalAddress(PVOID((pdpe & PMASK) + 8 * pd), &pde, sizeof(pde), &readsize);
	if (~pde & 1)
		return 0;
 
	/* 1GB large page, use pde's 12-34 bits */
	if (pde & 0x80)
		return (pde & (~0ull << 42 >> 12)) + (virtualAddress & ~(~0ull << 30));
 
	UINT64 pteAddr = 0;
	ReadPhysicalAddress(PVOID((pde & PMASK) + 8 * pt), &pteAddr, sizeof(pteAddr), &readsize);
	if (~pteAddr & 1)
		return 0;
 
	/* 2MB large page */
	if (pteAddr & 0x80)
		return (pteAddr & PMASK) + (virtualAddress & ~(~0ull << 21));
 
	virtualAddress = 0;
	ReadPhysicalAddress(PVOID((pteAddr & PMASK) + 8 * pte), &virtualAddress, sizeof(virtualAddress), &readsize);
	virtualAddress &= PMASK;
 
	if (!virtualAddress)
		return 0;
 
	return virtualAddress + pageOffset;
}
 
namespace physical
{
 
	NTSTATUS WriteProcessMemory(int pid, uint64_t Address, uint64_t AllocatedBuffer, SIZE_T size, SIZE_T* written)
	{
		DbgPrintEx(0, 0, "\n[Write] Address is: %llx", Address);
		PEPROCESS pProcess = NULL;
		if (pid == 0) return STATUS_UNSUCCESSFUL;
 
		NTSTATUS NtRet = PsLookupProcessByProcessId((HANDLE)pid, &pProcess);
		if (NtRet != STATUS_SUCCESS) return NtRet;
 
		ULONG_PTR process_dirbase = GetProcessCr3(pProcess);
		ObDereferenceObject(pProcess);
		DbgPrintEx(0, 0, "\n[Write] DirBase is: %llx", process_dirbase);
		SIZE_T CurOffset = 0;
		SIZE_T TotalSize = size;
		while (TotalSize)
		{
			uint64_t CurPhysAddr = TranslateLinearAddress(process_dirbase, (ULONG64)Address + CurOffset);
			if (!CurPhysAddr) return STATUS_UNSUCCESSFUL;
 
			ULONG64 WriteSize = min(PAGE_SIZE - (CurPhysAddr & 0xFFF), TotalSize);
			SIZE_T BytesWritten = 0;
			NtRet = WritePhysicalAddress((PVOID)CurPhysAddr, (PVOID)((ULONG64)AllocatedBuffer + CurOffset), WriteSize, &BytesWritten);
			TotalSize -= BytesWritten;
			CurOffset += BytesWritten;
			if (NtRet != STATUS_SUCCESS) break;
			if (BytesWritten == 0) break;
		}
 
		*written = CurOffset;
		return NtRet;
	}
	NTSTATUS ReadProcessMemory(int pid, uint64_t Address, uint64_t AllocatedBuffer, SIZE_T size, SIZE_T* read)
	{
		DbgPrintEx(0, 0, "\n[Read] Address is: %llx", Address);
		PEPROCESS pProcess = NULL;
		if (pid == 0) return STATUS_UNSUCCESSFUL;
 
		NTSTATUS NtRet = PsLookupProcessByProcessId((HANDLE)pid, &pProcess);
		if (NtRet != STATUS_SUCCESS) return NtRet;
 
		ULONG_PTR process_dirbase = GetProcessCr3(pProcess);
		DbgPrintEx(0, 0, "\n[Read] DirBase is: %llx", process_dirbase);
		ObDereferenceObject(pProcess);
 
		SIZE_T CurOffset = 0;
		SIZE_T TotalSize = size;
		while (TotalSize)
		{
 
			uint64_t CurPhysAddr = TranslateLinearAddress(process_dirbase, (ULONG64)Address + CurOffset);
			if (!CurPhysAddr) return STATUS_UNSUCCESSFUL;
 
			ULONG64 ReadSize = min(PAGE_SIZE - (CurPhysAddr & 0xFFF), TotalSize);
			SIZE_T BytesRead = 0;
			NtRet = ReadPhysicalAddress((PVOID)CurPhysAddr, (PVOID)((ULONG64)AllocatedBuffer + CurOffset), ReadSize, &BytesRead);
			TotalSize -= BytesRead;
			CurOffset += BytesRead;
			if (NtRet != STATUS_SUCCESS) break;
			if (BytesRead == 0) break;
		}
 
		*read = CurOffset;
		return NtRet;
	}
}
 
uint64_t VirtualAddressToPhysicalAddress(void* VirtualAddress)
{
	return MmGetPhysicalAddress(VirtualAddress).QuadPart;
}
 
uint64_t PhysicalAddressToVirtualAddress(uint64_t PhysicalAddress)
{
	PHYSICAL_ADDRESS PhysicalAddr = { 0 };
	PhysicalAddr.QuadPart = PhysicalAddress;
 
	return reinterpret_cast<uint64_t>(MmGetVirtualForPhysical(PhysicalAddr));
}
usermode
using System;
using NtApiDotNet;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

namespace WnfSubscriber
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public struct INSTRUCTIONS
    {
        public byte close;           // 1 byte
        public byte read;            // 1 byte
        public byte write;           // 1 byte
        public byte reqBase;         // 1 byte
        public ulong processId;      // 8 bytes (matches UINT_PTR in C code)
        public UIntPtr address;      // 8 bytes
        public ulong size;           // 8 bytes
        public IntPtr bufferAddress; // 8 bytes
        public IntPtr output;        // 8 bytes
        public ulong baseAddress;    // 8 bytes
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public string moduleName;
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]
        public string data; // Data to write
    }


    public class KernelCom : IDisposable
    {
        private const ulong MY_WNF_STATE_NAME = 0x41C64E6DA3BC1074;
        private readonly WnfClient _client;
        private bool _initialized = false;

        public KernelCom()
        {
            _client = new WnfClient();
        }

        public void Initialize()
        {
            if (_initialized)
                return;

            _client.SubscribeState(new WnfStateName(MY_WNF_STATE_NAME), OnWnfStateChange);
            Console.WriteLine("[+] Subscribed to WNF state.");
            _initialized = true;
        }

        public void SendMessage(string message)
        {
            byte[] dataToSend = Encoding.ASCII.GetBytes(message);
            NtWnf.PublishStateData(new WnfStateName(MY_WNF_STATE_NAME), dataToSend);
            Console.WriteLine("[+] Sent message to kernel-mode: " + message);
        }

        public void SendInstructions(INSTRUCTIONS instructions)
        {
            int size = Marshal.SizeOf(instructions);
            byte[] dataToSend = new byte[size];

            IntPtr ptr = Marshal.AllocHGlobal(size);
            try
            {
                Marshal.StructureToPtr(instructions, ptr, false);
                Marshal.Copy(ptr, dataToSend, 0, size);
                NtWnf.PublishStateData(new WnfStateName(MY_WNF_STATE_NAME), dataToSend);
                Console.WriteLine("[+] Sent instructions to kernel-mode.");
            }
            catch (Exception ex)
            {
               Console.WriteLine($"[!] Failed to send instructions: {ex.Message}");
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);
            }

            NtWnf.PublishStateData(new WnfStateName(MY_WNF_STATE_NAME), dataToSend);
            Console.WriteLine("[+] Sent instructions to kernel-mode.");
        }



        // New Method: ReadMemory
        public void ReadMemory(uint processId, ulong address, ulong size)
        {
            INSTRUCTIONS instructions = new INSTRUCTIONS
            {
                close = 0,
                read = 1,
                write = 0,
                reqBase = 0,
                processId = processId,
                address = new UIntPtr(address),
                size = size,
                bufferAddress = IntPtr.Zero,
                output = IntPtr.Zero,
                baseAddress = 0,
                moduleName = string.Empty,
                data = string.Empty
            };

            SendInstructions(instructions);
            Console.WriteLine($"[+] Requested to read {size} bytes from process {processId} at address 0x{address:X}.");
        }

        // New Method: WriteMemory
        public void WriteMemory(uint processId, ulong address, byte[] data)
        {
            if (data.Length > 512) // Adjust the limit as per your requirements and kernel driver capability
            {
                Console.WriteLine("[!] Data size exceeds the maximum allowed (512 bytes).");
                return;
            }
        
            string dataString = Convert.ToBase64String(data); // Encode data as Base64 string
        
            INSTRUCTIONS instructions = new INSTRUCTIONS
            {
                close = 0,
                read = 0,
                write = 1,
                reqBase = 0,
                processId = processId,
                address = new UIntPtr(address),
                size = (ulong)data.Length,
                bufferAddress = IntPtr.Zero,
                output = IntPtr.Zero,
                baseAddress = 0,
                moduleName = string.Empty,
                data = dataString
            };
        
            SendInstructions(instructions);
            Console.WriteLine($"[+] Requested to write {data.Length} bytes to process {processId} at address 0x{address:X}.");
        }


        private void OnWnfStateChange(WnfStateChange change)
        {
            Console.WriteLine("[] WNF State Changed!");

            if (change.Data == null || change.Data.Length == 0)
            {
                Console.WriteLine("[!] Received empty data.");
                return;
            }

            // Attempt to parse the data as INSTRUCTIONS struct
            try
            {
                INSTRUCTIONS response = ByteArrayToStructure<INSTRUCTIONS>(change.Data);

                if (response.read == 1)
                {
                    Console.WriteLine($"[] Read Memory Response from process {response.processId}:");

                    if (string.IsNullOrEmpty(response.data))
                    {
                        Console.WriteLine("[!] No data received for read operation.");
                        return;
                    }

                    // Decode the Base64 data received
                    byte[] readData = Convert.FromBase64String(response.data);
                    Console.WriteLine("[] Data: " + BitConverter.ToString(readData));
                }
                else if (response.write == 1)
                {
                    Console.WriteLine("[] Write Memory operation completed successfully.");
                }
                else if (response.close == 1)
                {
                    Console.WriteLine("[] Received instruction to close the driver.");
                    // Perform any cleanup if necessary
                }
                else if (response.reqBase == 1)
                {
                    Console.WriteLine($"[] Base Address for module '{response.moduleName}': 0x{response.baseAddress:X}");
                }
                else
                {
                    string receivedMessage = Encoding.ASCII.GetString(change.Data);
                    Console.WriteLine("[] Received message from kernel-mode: " + receivedMessage);

                    if (receivedMessage == "Hi")
                    {
                        Console.WriteLine("[] Received 'Hi' from kernel-mode. Sending instructions...");

                        INSTRUCTIONS instructions = new INSTRUCTIONS
                        {
                            close = 0,
                            read = 1,
                            write = 0,
                            reqBase = 0,
                            processId = 0,
                            address = UIntPtr.Zero,
                            size = 0,
                            bufferAddress = IntPtr.Zero,
                            output = IntPtr.Zero,
                            baseAddress = 0,
                            moduleName = "ExampleModule",
                            data = string.Empty
                        };

                        SendInstructions(instructions);
                    }
                    else if (receivedMessage == "Read operation completed")
                    {
                        Console.WriteLine("[] Kernel-mode has completed the read operation.");
                        // Process the results if any
                        // ...
                    }
                    else
                    {
                        // Handle other messages or results
                        Console.WriteLine("[] Received data: " + receivedMessage);
                    }
                }
            }
            catch (Exception ex)
            {
                // If parsing as INSTRUCTIONS struct fails, treat it as a simple string message
                string receivedMessage = Encoding.ASCII.GetString(change.Data);
                Console.WriteLine("[] Received message from kernel-mode: " + receivedMessage);
            }
        }

        private T ByteArrayToStructure<T>(byte[] bytes) where T : struct
        {
            int size = Marshal.SizeOf(typeof(T));
            if (bytes.Length < size)
                throw new ArgumentException("Byte array is too small for the specified structure.");

            IntPtr ptr = Marshal.AllocHGlobal(size);
            try
            {
                Marshal.Copy(bytes, 0, ptr, size);
                return Marshal.PtrToStructure<T>(ptr);
            }
            finally
            {
                Marshal.FreeHGlobal(ptr);
            }
        }

        public void CloseDriver()
        {
            INSTRUCTIONS instructions = new INSTRUCTIONS
            {
                close = 1,
                read = 0,
                write = 0,
                reqBase = 0,
                processId = 0,
                address = UIntPtr.Zero,
                size = 0,
                bufferAddress = IntPtr.Zero,
                output = IntPtr.Zero,
                baseAddress = 0,
                moduleName = string.Empty,
                data = string.Empty
            };

            SendInstructions(instructions);
        }

        public void GetModuleBaseAddress(string moduleName)
        {
            INSTRUCTIONS instructions = new INSTRUCTIONS
            {
                close = 0,
                read = 0,
                write = 0,
                reqBase = 1,
                processId = 0,
                address = UIntPtr.Zero,
                size = 0,
                bufferAddress = IntPtr.Zero,
                output = IntPtr.Zero,
                baseAddress = 0,
                moduleName = moduleName,
                data = string.Empty
            };

            SendInstructions(instructions);
        }

        public void Dispose()
        {
            _client.Dispose();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (KernelCom kernelCom = new KernelCom())
            {
                // Initialize WNF subscription
                kernelCom.Initialize();

                Console.WriteLine("[>] Subscribed to WNF state. Sending 'Hi' to kernel-mode.");

                // Send 'Hi' message to kernel-mode
                kernelCom.SendMessage("Hi");

                Console.WriteLine("[>] Waiting for kernel-mode responses...");

                // Start the interactive menu in a separate thread to keep listening for WNF notifications
                Thread menuThread = new Thread(() => InteractiveMenu(kernelCom));
                menuThread.Start();

                // Keep the main thread alive to receive WNF notifications
                menuThread.Join();
            }
        }

        static void InteractiveMenu(KernelCom kernelCom)
        {
            while (true)
            {
                Console.WriteLine("\nSelect an option:");
                Console.WriteLine("1. Read Memory");
                Console.WriteLine("2. Write Memory");
                Console.WriteLine("3. Get Module Base Address");
                Console.WriteLine("4. Close Driver");
                Console.WriteLine("5. Exit");

                Console.Write("Choice: ");
                string choice = Console.ReadLine();

                switch (choice)
                {
                    case "1":
                        PerformReadMemory(kernelCom);
                        break;

                    case "2":
                        PerformWriteMemory(kernelCom);
                        break;

                    case "3":
                        PerformGetModuleBaseAddress(kernelCom);
                        break;

                    case "4":
                        kernelCom.CloseDriver();
                        Console.WriteLine("[>] Driver close instruction sent.");
                        break;

                    case "5":
                        Console.WriteLine("[>] Exiting application.");
                        return;

                    default:
                        Console.WriteLine("[!] Invalid choice. Please select a valid option.");
                        break;
                }

                // Allow some time for the kernel driver to respond
                Thread.Sleep(500);
            }
        }

        static void PerformReadMemory(KernelCom kernelCom)
        {
            Console.Write("Enter Process ID: ");
            if (uint.TryParse(Console.ReadLine(), out uint readPid))
            {
                Console.Write("Enter Address (hex, e.g., 7FF6C2D40000): ");
                if (ulong.TryParse(Console.ReadLine(), System.Globalization.NumberStyles.HexNumber, null, out ulong readAddress))
                {
                    Console.Write("Enter Size (in bytes): ");
                    if (ulong.TryParse(Console.ReadLine(), out ulong readSize))
                    {
                        kernelCom.ReadMemory(readPid, readAddress, readSize);
                    }
                    else
                    {
                        Console.WriteLine("[!] Invalid size. Please enter a numeric value.");
                    }
                }
                else
                {
                    Console.WriteLine("[!] Invalid address format. Please enter a hexadecimal value.");
                }
            }
            else
            {
                Console.WriteLine("[!] Invalid Process ID. Please enter a numeric value.");
            }
        }

        static void PerformWriteMemory(KernelCom kernelCom)
        {
            Console.Write("Enter Process ID: ");
            if (uint.TryParse(Console.ReadLine(), out uint writePid))
            {
                Console.Write("Enter Address (hex, e.g., 7FF6C2D40000): ");
                if (ulong.TryParse(Console.ReadLine(), System.Globalization.NumberStyles.HexNumber, null, out ulong writeAddress))
                {
                    Console.Write("Enter Data to Write (hex, e.g., DEADBEEF): ");
                    string dataInput = Console.ReadLine();

                    try
                    {
                        byte[] dataBytes = StringToByteArray(dataInput);
                        kernelCom.WriteMemory(writePid, writeAddress, dataBytes);
                    }
                    catch
                    {
                        Console.WriteLine("[!] Invalid data format. Please enter hexadecimal digits only.");
                    }
                }
                else
                {
                    Console.WriteLine("[!] Invalid address format. Please enter a hexadecimal value.");
                }
            }
            else
            {
                Console.WriteLine("[!] Invalid Process ID. Please enter a numeric value.");
            }
        }

        static void PerformGetModuleBaseAddress(KernelCom kernelCom)
        {
            Console.Write("Enter Module Name (e.g., notepad.exe): ");
            string moduleName = Console.ReadLine();

            if (!string.IsNullOrWhiteSpace(moduleName))
            {
                kernelCom.GetModuleBaseAddress(moduleName);
            }
            else
            {
                Console.WriteLine("[!] Module name cannot be empty.");
            }
        }

        // Helper method to convert hex string to byte array
        static byte[] StringToByteArray(string hex)
        {
            hex = hex.Replace("0x", "").Replace(" ", "").Replace("-", "");
            if (hex.Length % 2 != 0)
                throw new FormatException("Invalid hex string length.");

            byte[] bytes = new byte[hex.Length / 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                string byteValue = hex.Substring(i * 2, 2);
                bytes[i] = Convert.ToByte(byteValue, 16);
            }
            return bytes;
        }
    }
}

driver
#include <ntifs.h>
#include <ntddk.h>

// ==========================================================
// Definitions and Macros
// ==========================================================

#define TARGET_PROCESS_NAME L"notepad.exe" // Replace with your target process name
#define MY_WNF_STATE_NAME 0x41C64E6DA3BC1074ULL // WNF_AUDC_CAPTURE
#define BASE64_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
// Windows Build Numbers
#define WINDOWS_1803 17134
#define WINDOWS_1809 17763
#define WINDOWS_1903 18362
#define WINDOWS_1909 18363
#define WINDOWS_2004 19041
#define WINDOWS_20H2 19569
#define WINDOWS_21H1 20180

// Page size definitions
#define PAGE_OFFSET_SIZE 12
#define PMASK 0x000FFFFFFFFFF000ull // Corrected PMASK for x64

// ==========================================================
// Function Pointer Types for WNF (Undocumented APIs)
// ==========================================================

typedef ULONG64 WNF_STATE_NAME;
typedef ULONG64 WNF_CHANGE_STAMP;

typedef enum _WNF_SUBSCRIPTION_FLAG {
    WnfSubscriptionNone = 0x0,
    WnfSubscriptionPublishCurrentState = 0x1,
    // Add other flags as needed
} WNF_SUBSCRIPTION_FLAG;

typedef struct _WNF_SUBSCRIPTION {
    // Define necessary fields based on WNF structures
} WNF_SUBSCRIPTION, PWNF_SUBSCRIPTION;

// Function pointer typedefs
typedef NTSTATUS(PExSubscribeWnfStateChange)(
    PWNF_SUBSCRIPTION Subscription,
    WNF_STATE_NAME StateName,
    WNF_SUBSCRIPTION_FLAG SubscriptionFlag,
    PVOID Callback,
    PVOID CallbackContext,
    PVOID TypeId,
    PVOID DeliveryDescriptor,
    ULONG DeliveryDescriptorSize
);

typedef NTSTATUS(PExPublishWnfStateData)(
    PCWNF_STATE_NAME StateName,
    PVOID Buffer,
    ULONG Length,
    PCWNF_TYPE_ID TypeId,
    PVOID ExplicitScope,
    ULONG MatchingChangeStamp
);

typedef NTSTATUS(PExUnsubscribeWnfStateChange)(
    PWNF_SUBSCRIPTION Subscription
);

// Global function pointers
PExPublishWnfStateData ExPublishWnfStateDataFunc = NULL;
PExUnsubscribeWnfStateChange ExUnsubscribeWnfStateChangeFunc = NULL;

// ==========================================================
// Data Structures
// ==========================================================
#pragma pack(push, 1)

typedef struct _INSTRUCTIONS {
    BOOLEAN close;
    BOOLEAN read;
    BOOLEAN write;
    BOOLEAN reqBase;
    UINT_PTR processId;
    UINT_PTR address;
    ULONGLONG size;
    PVOID bufferAddress;
    PVOID output;
    ULONG64 baseAddress;
    char moduleName[256];
    char data[1024]; // Added to match user-mode
} INSTRUCTIONS, PINSTRUCTIONS;

#pragma pack(pop)

// ==========================================================
// Function Declarations
// ==========================================================

NTKERNELAPI
PVOID
PsGetProcessSectionBaseAddress(
    __in PEPROCESS Process
);

PVOID GetProcessBaseAddress(HANDLE pid);
DWORD GetUserDirectoryTableBaseOffset();
ULONG_PTR GetProcessCr3(PEPROCESS pProcess);
ULONG_PTR GetKernelDirBase();
NTSTATUS ReadVirtual(uint64_t dirbase, uint64_t address, uint8_t buffer, SIZE_T size, SIZE_T read);
NTSTATUS WriteVirtual(uint64_t dirbase, uint64_t address, uint8_t buffer, SIZE_T size, SIZE_T written);
NTSTATUS ReadPhysicalAddress(PVOID TargetAddress, PVOID lpBuffer, SIZE_T Size, SIZE_T BytesRead);
NTSTATUS WritePhysicalAddress(PVOID TargetAddress, PVOID lpBuffer, SIZE_T Size, SIZE_T* BytesWritten);
uint64_t TranslateLinearAddress(uint64_t directoryTableBase, uint64_t virtualAddress);
NTSTATUS ReadProcessMemory(int pid, PVOID Address, PVOID AllocatedBuffer, SIZE_T size, SIZE_T* read);
NTSTATUS WriteProcessMemory(int pid, PVOID Address, PVOID AllocatedBuffer, SIZE_T size, SIZE_T* written);
VOID ProcessNotifyCallback(
    __in HANDLE ParentId,
    __in HANDLE ProcessId,
    __in BOOLEAN Create
);
VOID DriverUnload(PDRIVER_OBJECT DriverObject);

// ==========================================================
// Global Variables
// ==========================================================

PWNF_SUBSCRIPTION g_WnfSubscription = NULL;

// ==========================================================
// Function Definitions
// ==========================================================

// Retrieve the base address of a process given its PID
PVOID GetProcessBaseAddress(HANDLE pid) {
    PEPROCESS pProcess = NULL;
    if (pid == NULL) return NULL;

    NTSTATUS NtRet = PsLookupProcessByProcessId(pid, &pProcess);
    if (NtRet != STATUS_SUCCESS) return NULL;

    PVOID Base = PsGetProcessSectionBaseAddress(pProcess);
    ObDereferenceObject(pProcess);
    return Base;
}

// Get the offset for the UserDirectoryTableBase based on Windows version
DWORD GetUserDirectoryTableBaseOffset() {
    RTL_OSVERSIONINFOW ver = { 0 };
    RtlGetVersion(&ver);

    switch (ver.dwBuildNumber) {
    case WINDOWS_1803:
    case WINDOWS_1809:
        return 0x0278;
    case WINDOWS_1903:
    case WINDOWS_1909:
    case WINDOWS_2004:
    case WINDOWS_20H2:
    case WINDOWS_21H1:
    default:
        return 0x0388;
    }
}
UCHAR Base64DecodeChar(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    if (c >= '0' && c <= '9') return c - '0' + 52;
    if (c == '+') return 62;
    if (c == '/') return 63;
    return 0xFF; // Invalid character
}

NTSTATUS DecodeBase64(const char* base64Input, UCHAR* outputBuffer, SIZE_T bufferSize, SIZE_T* decodedSize) {
    if (!base64Input || !outputBuffer || !decodedSize)
        return STATUS_INVALID_PARAMETER;

    SIZE_T inputLen = strlen(base64Input);
    if (inputLen % 4 != 0)
        return STATUS_INVALID_PARAMETER;

    SIZE_T outputLen = (inputLen / 4) * 3;
    if (outputLen > bufferSize)
        return STATUS_BUFFER_TOO_SMALL;

    *decodedSize = 0;

    for (SIZE_T i = 0; i < inputLen; i += 4) {
        UCHAR sextet_a = Base64DecodeChar(base64Input[i]);
        UCHAR sextet_b = Base64DecodeChar(base64Input[i + 1]);
        UCHAR sextet_c = Base64DecodeChar(base64Input[i + 2]);
        UCHAR sextet_d = Base64DecodeChar(base64Input[i + 3]);

        if (sextet_a == 0xFF || sextet_b == 0xFF)
            return STATUS_INVALID_PARAMETER;

        UCHAR triple[3] = {0};

        triple[0] = (sextet_a << 2) | (sextet_b >> 4);
        if (base64Input[i + 2] != '=') {
            if (sextet_c == 0xFF)
                return STATUS_INVALID_PARAMETER;
            triple[1] = ((sextet_b & 0xF) << 4) | (sextet_c >> 2);
        }
        if (base64Input[i + 3] != '=') {
            if (sextet_d == 0xFF)
                return STATUS_INVALID_PARAMETER;
            triple[2] = ((sextet_c & 0x3) << 6) | sextet_d;
        }

        // Copy the decoded bytes to the output buffer
        outputBuffer[decodedSize] = triple[0];
        (decodedSize)++;

        if (base64Input[i + 2] != '=') {
            outputBuffer[decodedSize] = triple[1];
            (decodedSize)++;
        }

        if (base64Input[i + 3] != '=') {
            outputBuffer[decodedSize] = triple[2];
            (decodedSize)++;
        }
    }

    return STATUS_SUCCESS;
}

NTSTATUS EncodeBase64(const UCHAR input, SIZE_T inputSize, char outputBuffer, SIZE_T bufferSize, SIZE_T encodedSize) {
    if (!input || !outputBuffer || !encodedSize)
        return STATUS_INVALID_PARAMETER;

    const char base64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    SIZE_T outputNeeded = ((inputSize + 2) / 3) * 4;
    if (outputNeeded + 1 > bufferSize) // +1 for null terminator if needed
        return STATUS_BUFFER_TOO_SMALL;

    SIZE_T i = 0, j = 0;
    for (; i + 3 <= inputSize; i += 3) {
        UCHAR a = input[i];
        UCHAR b = input[i + 1];
        UCHAR c = input[i + 2];

        outputBuffer[j++] = base64_table[(a >> 2) & 0x3F];
        outputBuffer[j++] = base64_table[((a & 0x3) << 4) | ((b & 0xF0) >> 4)];
        outputBuffer[j++] = base64_table[((b & 0xF) << 2) | ((c & 0xC0) >> 6)];
        outputBuffer[j++] = base64_table[c & 0x3F];
    }

    // Handle padding
    if (i < inputSize) {
        UCHAR a = input[i];
        UCHAR b = (i + 1 < inputSize) ? input[i + 1] : 0;

        outputBuffer[j++] = base64_table[(a >> 2) & 0x3F];
        outputBuffer[j++] = base64_table[((a & 0x3) << 4) | ((b & 0xF0) >> 4)];
        outputBuffer[j++] = (i + 1 < inputSize) ? base64_table[((b & 0xF) << 2)] : '=';
        outputBuffer[j++] = '=';
    }

    encodedSize = j;
    return STATUS_SUCCESS;
}


// Retrieve the CR3 (Page Directory Base) of a process
ULONG_PTR GetProcessCr3(PEPROCESS pProcess) {
    PUCHAR process = (PUCHAR)pProcess;
#ifdef _WIN64
    ULONG_PTR* process_dirbase_ptr = (ULONG_PTR*)(process + 0x28); // dirbase for x64
#else
    ULONG_PTR* process_dirbase_ptr = (ULONG_PTR*)(process + 0x18); // dirbase for x86
#endif
    ULONG_PTR process_dirbase = process_dirbase_ptr;

    if (process_dirbase == 0) {
        DWORD UserDirOffset = GetUserDirectoryTableBaseOffset();
        ULONG_PTR process_userdirbase_ptr = (ULONG_PTR*)(process + UserDirOffset);
        return process_userdirbase_ptr;
    }
    return process_dirbase;
}


ULONG_PTR GetKernelDirBase() {
	PUCHAR process = (PUCHAR)PsGetCurrentProcess();
	ULONG_PTR cr3 = *(PULONG_PTR)(process + 0x28); //dirbase x64, 32bit is 0x18
	return cr3;

}

// Translate a virtual address to a physical address using the provided directory table base
uint64_t TranslateLinearAddress(uint64_t directoryTableBase, uint64_t virtualAddress) {
    directoryTableBase &= 0xFFFFFFFFFF000ull; // Align directoryTableBase

    uint64_t pageOffset = virtualAddress & 0xFFF;
    uint64_t pte = (virtualAddress >> 12) & 0x1FF;
    uint64_t pt = (virtualAddress >> 21) & 0x1FF;
    uint64_t pd = (virtualAddress >> 30) & 0x1FF;
    uint64_t pdp = (virtualAddress >> 39) & 0x1FF;

    SIZE_T readsize = 0;
    uint64_t pdpe = 0;
    if (!NT_SUCCESS(ReadPhysicalAddress((PVOID)(directoryTableBase + 8 * pdp), &pdpe, sizeof(pdpe), &readsize)) || (pdpe & 1))
        return 0;

    uint64_t pde = 0;
    if (!NT_SUCCESS(ReadPhysicalAddress((PVOID)((pdpe & PMASK) + 8 * pd), &pde, sizeof(pde), &readsize)) || (pde & 1))
        return 0;

    // 1GB large page
    if (pde & (1 << 7)) // Check the PS (Page Size) bit
        return (pde & 0x000FFFFFFFFFF000ull) + (virtualAddress & 0x3FFFFFFFull);

    uint64_t pteAddr = 0;
    if (!NT_SUCCESS(ReadPhysicalAddress((PVOID)((pde & PMASK) + 8 * pt), &pteAddr, sizeof(pteAddr), &readsize)) || (pteAddr & 1))
        return 0;

    // 2MB large page
    if (pteAddr & (1 << 7)) // Check the PS bit
        return (pteAddr & PMASK) + (virtualAddress & 0x1FFFFFull);

    uint64_t physPage = 0;
    if (!NT_SUCCESS(ReadPhysicalAddress((PVOID)((pteAddr & PMASK) + 8 * pte), &physPage, sizeof(physPage), &readsize)) || !(physPage & 1))
        return 0;

    physPage &= PMASK;
    return physPage + pageOffset;
}

// Read a physical address into a buffer
NTSTATUS ReadPhysicalAddress(PVOID TargetAddress, PVOID lpBuffer, SIZE_T Size, SIZE_T BytesRead) {
    if (TargetAddress == NULL || lpBuffer == NULL || BytesRead == NULL)
        return STATUS_INVALID_PARAMETER;

    MM_COPY_ADDRESS AddrToRead = { 0 };
    AddrToRead.PhysicalAddress.QuadPart = (ULONG_PTR)TargetAddress;

    NTSTATUS status = MmCopyMemory(lpBuffer, AddrToRead, Size, MM_COPY_MEMORY_PHYSICAL, BytesRead);
    return status;
}

// Write a buffer to a physical address
NTSTATUS WritePhysicalAddress(PVOID TargetAddress, PVOID lpBuffer, SIZE_T Size, SIZE_T* BytesWritten) {
    if (!TargetAddress || !lpBuffer || !BytesWritten)
        return STATUS_INVALID_PARAMETER;

    PHYSICAL_ADDRESS AddrToWrite = { 0 };
    AddrToWrite.QuadPart = (ULONG_PTR)TargetAddress;

    // Limit MmMapIoSpaceEx to PAGE_SIZE (4096 bytes)
    if (Size > PAGE_SIZE)
        return STATUS_INVALID_PARAMETER;

    PVOID pmapped_mem = MmMapIoSpaceEx(AddrToWrite, Size, PAGE_READWRITE);
    if (!pmapped_mem)
        return STATUS_UNSUCCESSFUL;

    RtlCopyMemory(pmapped_mem, lpBuffer, Size);
    BytesWritten = Size;
    MmUnmapIoSpace(pmapped_mem, Size);
    return STATUS_SUCCESS;
}

// Read virtual memory from a process
NTSTATUS ReadVirtual(uint64_t dirbase, uint64_t address, uint8_t buffer, SIZE_T size, SIZE_T* read) {
    if (!buffer || !read)
        return STATUS_INVALID_PARAMETER;

    uint64_t paddress;
    NTSTATUS status = STATUS_SUCCESS;
    SIZE_T CurOffset = 0;
    SIZE_T TotalSize = size;

    while (TotalSize > 0) {
        paddress = TranslateLinearAddress(dirbase, address + CurOffset);
        if (!paddress)
            return STATUS_UNSUCCESSFUL;

        ULONG64 ReadSize = min(PAGE_SIZE - (paddress & 0xFFF), TotalSize);
        SIZE_T BytesReadLocal = 0;

        status = ReadPhysicalAddress((PVOID)paddress, buffer + CurOffset, ReadSize, &BytesReadLocal);
        if (!NT_SUCCESS(status) || BytesReadLocal == 0)
            break;

        TotalSize -= BytesReadLocal;
        CurOffset += BytesReadLocal;
    }

    read = CurOffset;
    return status;
}

// Write virtual memory to a process
NTSTATUS WriteVirtual(uint64_t dirbase, uint64_t address, uint8_t buffer, SIZE_T size, SIZE_T* written) {
    if (!buffer || !written)
        return STATUS_INVALID_PARAMETER;

    uint64_t paddress;
    NTSTATUS status = STATUS_SUCCESS;
    SIZE_T CurOffset = 0;
    SIZE_T TotalSize = size;

    while (TotalSize > 0) {
        paddress = TranslateLinearAddress(dirbase, address + CurOffset);
        if (!paddress)
            return STATUS_UNSUCCESSFUL;

        ULONG64 WriteSize = min(PAGE_SIZE - (paddress & 0xFFF), TotalSize);
        SIZE_T BytesWrittenLocal = 0;

        status = WritePhysicalAddress((PVOID)paddress, buffer + CurOffset, WriteSize, &BytesWrittenLocal);
        if (!NT_SUCCESS(status) || BytesWrittenLocal == 0)
            break;

        TotalSize -= BytesWrittenLocal;
        CurOffset += BytesWrittenLocal;
    }

    written = CurOffset;
    return status;
}

// Read the memory of a process by PID
NTSTATUS ReadProcessMemory(int pid, PVOID Address, PVOID AllocatedBuffer, SIZE_T size, SIZE_T read) {
    if (pid == 0 || Address == NULL || AllocatedBuffer == NULL || read == NULL)
        return STATUS_INVALID_PARAMETER;

    PEPROCESS pProcess = NULL;
    NTSTATUS NtRet = PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)pid, &pProcess);
    if (NtRet != STATUS_SUCCESS)
        return NtRet;

    ULONG_PTR process_dirbase = GetProcessCr3(pProcess);
    ObDereferenceObject(pProcess);

    NtRet = ReadVirtual(process_dirbase, (ULONG_PTR)Address, (uint8_t*)AllocatedBuffer, size, read);
    return NtRet;
}

// Write the memory of a process by PID
NTSTATUS WriteProcessMemory(int pid, PVOID Address, PVOID AllocatedBuffer, SIZE_T size, SIZE_T* written) {
    if (pid == 0 || Address == NULL || AllocatedBuffer == NULL || written == NULL)
        return STATUS_INVALID_PARAMETER;

    PEPROCESS pProcess = NULL;
    NTSTATUS NtRet = PsLookupProcessByProcessId((HANDLE)(ULONG_PTR)pid, &pProcess);
    if (NtRet != STATUS_SUCCESS)
        return NtRet;

    ULONG_PTR process_dirbase = GetProcessCr3(pProcess);
    ObDereferenceObject(pProcess);

    NtRet = WriteVirtual(process_dirbase, (ULONG_PTR)Address, (uint8_t*)AllocatedBuffer, size, written);
    return NtRet;
}

// Callback function for WNF state changes
// Callback function for WNF state changes
NTSTATUS notifCallback(
    PWNF_SUBSCRIPTION Subscription,
    WNF_STATE_NAME StateName,
    ULONG ChangeStamp,
    PVOID TypeId,
    PVOID CallbackContext,
    PVOID CallbackData,
    ULONG CallbackDataSize
)
{
    UNREFERENCED_PARAMETER(Subscription);
    UNREFERENCED_PARAMETER(StateName);
    UNREFERENCED_PARAMETER(TypeId);
    UNREFERENCED_PARAMETER(CallbackContext);

    NTSTATUS status = STATUS_SUCCESS;

    // Verify that CallbackDataSize is sufficient for INSTRUCTIONS struct
    if (CallbackData && CallbackDataSize >= sizeof(INSTRUCTIONS)) {
        PINSTRUCTIONS instructions = (PINSTRUCTIONS)CallbackData;

        if (instructions->read) {
            // Handle read operation

            // Validate parameters
            if (instructions->processId == 0 || instructions->address == 0 || instructions->size == 0) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Invalid parameters for read operation.\n");
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Read operation failed: Invalid parameters.",
                    (ULONG)strlen("Read operation failed: Invalid parameters.") + 1, // Include null terminator
                    NULL,
                    NULL,
                    0
                );
                return status;
            }

            // Allocate memory to store read data
            UCHAR* readBuffer = (UCHAR*)ExAllocatePoolWithTag(NonPagedPool, instructions->size, 'rdmK');
            if (!readBuffer) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Read operation failed: Unable to allocate memory.\n");
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Read operation failed: Unable to allocate memory.",
                    (ULONG)strlen("Read operation failed: Unable to allocate memory.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
            }

            // Zero-initialize the buffer
            RtlZeroMemory(readBuffer, instructions->size);

            // Read memory from target process
            SIZE_T bytesRead = 0;
            status = ReadProcessMemory(
                (int)(ULONG_PTR)instructions->processId,
                (PVOID)(ULONG_PTR)instructions->address,
                readBuffer,
                instructions->size,
                &bytesRead
            );

            if (!NT_SUCCESS(status) || bytesRead == 0) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] ReadProcessMemory failed: 0x%X (BytesRead: %zu)\n", status, bytesRead);
                ExFreePoolWithTag(readBuffer, 'rdmK');
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Read operation failed.",
                    (ULONG)strlen("Read operation failed.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
            }

            // Encode the read data to Base64
            SIZE_T base64Size = ((bytesRead + 2) / 3) * 4;
            // Check if encoded data size exceeds maximum allowed (e.g., 4096 bytes)
            if (base64Size + 1 > 4096) { // +1 for null terminator
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Read operation failed: Encoded data exceeds buffer size.\n");
                ExFreePoolWithTag(readBuffer, 'rdmK');
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Read operation failed: Encoded data exceeds buffer size.",
                    (ULONG)strlen("Read operation failed: Encoded data exceeds buffer size.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
        }

            // Allocate buffer for Base64 encoded data
            char* encodedData = (char*)ExAllocatePoolWithTag(NonPagedPool, base64Size + 1, 'encK'); // +1 for null terminator
            if (!encodedData) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Read operation failed: Unable to allocate Base64 buffer.\n");
                ExFreePoolWithTag(readBuffer, 'rdmK');
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Read operation failed: Unable to allocate Base64 buffer.",
                    (ULONG)strlen("Read operation failed: Unable to allocate Base64 buffer.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
            }

            // Encode to Base64
            SIZE_T encodedLength = 0;
            status = EncodeBase64(readBuffer, bytesRead, encodedData, base64Size + 1, &encodedLength);
            ExFreePoolWithTag(readBuffer, 'rdmK'); // Free read buffer

            if (!NT_SUCCESS(status)) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] EncodeBase64 failed: 0x%X\n", status);
                ExFreePoolWithTag(encodedData, 'encK');
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Read operation failed: Base64 encode failed.",
                    (ULONG)strlen("Read operation failed: Base64 encode failed.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
            }

            // Ensure null termination
            if (encodedLength < base64Size + 1) {
                encodedData[encodedLength] = '\0';
            } else {
                // Force null termination to prevent buffer overruns
                encodedData[base64Size] = '\0';
            }

            // Publish the encoded data back via WNF
            status = ExPublishWnfStateDataFunc(
                &MY_WNF_STATE_NAME,
                encodedData,
                (ULONG)(encodedLength + 1), // Include null terminator
                NULL,
                NULL,
                0
            );

            if (NT_SUCCESS(status)) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Read operation completed successfully. %zu bytes read, %zu bytes encoded.\n", bytesRead, encodedLength);
            }
            else {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] ExPublishWnfStateDataFunc failed: 0x%X\n", status);
            }

            ExFreePoolWithTag(encodedData, 'encK'); // Free encoded data buffer
        }
        else if (instructions->write) {
            // Handle write operation

            // Validate parameters
            if (instructions->processId == 0 || instructions->address == 0 || instructions->size == 0 || instructions->data[0] == '\0') {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Invalid parameters for write operation.\n");
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Write operation failed: Invalid parameters.",
                    (ULONG)strlen("Write operation failed: Invalid parameters.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
        }

            // Decode the Base64 data
            UCHAR decodedData[512];
            SIZE_T decodedSize = 0;
            status = DecodeBase64(instructions->data, decodedData, sizeof(decodedData), &decodedSize);
            if (!NT_SUCCESS(status)) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] DecodeBase64 failed: 0x%X\n", status);
                // Inform user-mode about the failure
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Write operation failed: Base64 decode failed.",
                    (ULONG)strlen("Write operation failed: Base64 decode failed.") + 1,
                    NULL,
                    NULL,
                    0
                );
                return status;
    }

            // Proceed with writing memory using decodedData and decodedSize
            SIZE_T bytesWritten = 0;
            status = WriteProcessMemory(
                (int)(ULONG_PTR)instructions->processId,
                (PVOID)(ULONG_PTR)instructions->address,
                decodedData,
                decodedSize,
                &bytesWritten
            );

            if (NT_SUCCESS(status) && bytesWritten == decodedSize) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Write operation completed successfully. %zu bytes written to process %lu at address 0x%llX\n", bytesWritten, instructions->processId, instructions->address);
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Write operation completed successfully.",
                    (ULONG)strlen("Write operation completed successfully.") + 1,
                    NULL,
                    NULL,
                    0
                );
            }
            else {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] WriteProcessMemory failed: 0x%X (BytesWritten: %zu)\n", status, bytesWritten);
                // Inform user-mode about the failure
                status = ExPublishWnfStateDataFunc(
                    &MY_WNF_STATE_NAME,
                    "Write operation failed.",
                    (ULONG)strlen("Write operation failed.") + 1,
                    NULL,
                    NULL,
                    0
                );
            }
        }
        // Handle other instructions...
    }
    else if (CallbackData && CallbackDataSize > 0) {
        // Handle messages that don't match INSTRUCTIONS struct
        // For example: simple messages like "Hi"

        // Allocate memory for the received message, ensuring no buffer overflows
        CHAR* receivedMessage = (CHAR*)ExAllocatePoolWithTag(NonPagedPoolNx, CallbackDataSize + 1, 'msgK');
        if (!receivedMessage) {
            DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to allocate memory for received message.\n");
            return STATUS_INSUFFICIENT_RESOURCES;
    }

        // Zero-initialize the allocated memory
        RtlZeroMemory(receivedMessage, CallbackDataSize + 1);

        // Copy the received data, ensuring not to exceed the allocated buffer
        RtlCopyMemory(receivedMessage, CallbackData, min(CallbackDataSize, (ULONG)strlen("Hi")));

        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Kernel received message: %s\n", receivedMessage);

        // Check if the message is 'Hi' and respond accordingly
        if (strcmp(receivedMessage, "Hi") == 0) {
            // Send 'Hi' back to user-mode
            CHAR replyMessage[] = "Hi";
            status = ExPublishWnfStateDataFunc(
                &MY_WNF_STATE_NAME,
                replyMessage,
                (ULONG)strlen(replyMessage) + 1, // Include null terminator
                NULL,
                NULL,
                0
            );
            if (NT_SUCCESS(status)) {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Kernel sent reply: %s\n", replyMessage);
            }
    else {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to send reply, status: 0x%08X\n", status);
            }
        }

        // Free the allocated memory for the received message
        ExFreePoolWithTag(receivedMessage, 'msgK');
    }
    else {
        // Handle unexpected or insufficient CallbackDataSize
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL, "[Driver] Received invalid or unsupported data.\n");
    }

    return status;
}

// ==========================================================
// WNF Subscription Function
// ==========================================================

// Function to subscribe to WNF state changes
NTSTATUS CallExSubscribeWnfStateChange() {
    PWNF_SUBSCRIPTION wnfSubscription = NULL;
    WNF_STATE_NAME stateName = MY_WNF_STATE_NAME;
    NTSTATUS status;

    status = ExSubscribeWnfStateChange(&wnfSubscription, stateName, WnfSubscriptionPublishCurrentState,
        NULL, notifCallback, NULL, NULL, 0);
    if (NT_SUCCESS(status)) {
        g_WnfSubscription = wnfSubscription;
        DbgPrint("Subscription address: %p\n", (PVOID)wnfSubscription);
    }

    return status;
}

// ==========================================================
// Process Notification Callback
// ==========================================================

VOID ProcessNotifyCallback(
    __in HANDLE ParentId,
    __in HANDLE ProcessId,
    __in BOOLEAN Create
)
{
    UNREFERENCED_PARAMETER(ParentId);

    if (Create) {
        PEPROCESS pProcess = NULL;
        NTSTATUS status = PsLookupProcessByProcessId(ProcessId, &pProcess);
        if (NT_SUCCESS(status)) {
            // Get process image file name
            WCHAR imageName[300] = { 0 };
            UNICODE_STRING uProcessImageName = { 0 };

            // Use SeLocateProcessImageName
            status = SeLocateProcessImageName(pProcess, &uProcessImageName);
            if (NT_SUCCESS(status)) {
                // Extract file name from full path
                PWSTR processName = wcsrchr(uProcessImageName.Buffer, L'\'); // Corrected backslash
                if (processName) {
                    processName++; // Move past the backslash

                    // Check if this is the target process
                    if (_wcsicmp(processName, TARGET_PROCESS_NAME) == 0) {
                        // Perform memory operations here

                        // Get base address
                        PVOID baseAddress = GetProcessBaseAddress(ProcessId);
                        if (baseAddress) {
                            DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Base address of %ws: %p\n", processName, baseAddress);

                            // Example: Read some memory from the process
                            UCHAR buffer[256] = { 0 };
                            SIZE_T bytesRead = 0;
                            NTSTATUS ntStatus = ReadProcessMemory((int)(ULONG_PTR)ProcessId, baseAddress, buffer, sizeof(buffer), &bytesRead);

                            if (NT_SUCCESS(ntStatus)) {
                                // Do something with the read data
                                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Successfully read %zu bytes from %ws\n", bytesRead, processName);
                            }
                            else {
                                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to read process memory: 0x%X\n", ntStatus);
                            }

                            // Example: Write to process memory (commented out for safety)
                            /*
                            UCHAR writeData[256] = { ... };
                            SIZE_T bytesWritten = 0;
                            ntStatus = WriteProcessMemory((int)(ULONG_PTR)ProcessId, baseAddress, writeData, sizeof(writeData), &bytesWritten);
                            
                            if (NT_SUCCESS(ntStatus)) {
                                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Successfully wrote %zu bytes to %ws\n", bytesWritten, processName);
                            }
                            else {
                                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to write process memory: 0x%X\n", ntStatus);
                            }
                            */
                        }
                        else {
                            DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to get base address of %ws\n", processName);
                        }
                    }
                }
                else {
                    DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to parse process name\n");
                }

                // Free the UNICODE_STRING allocated by SeLocateProcessImageName
                ExFreePoolWithTag(uProcessImageName.Buffer, 'imgN');
            }
            else {
                DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] SeLocateProcessImageName failed with status: 0x%X\n", status);
            }

            ObDereferenceObject(pProcess);
        }
        else {
            DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] PsLookupProcessByProcessId failed with status: 0x%X\n", status);
        }
    }
}

// ==========================================================
// Driver Unload Routine
// ==========================================================

VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
    UNREFERENCED_PARAMETER(DriverObject);

    // Remove the process notify routine
    NTSTATUS status = PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, TRUE);
    if (!NT_SUCCESS(status)) {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to remove process notify routine: 0x%X\n", status);
    }
    else {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Process notify routine removed successfully\n");
    }

    // Unsubscribe WNF state change if subscribed
    if (g_WnfSubscription != NULL && ExUnsubscribeWnfStateChangeFunc != NULL) {
        ExUnsubscribeWnfStateChangeFunc(g_WnfSubscription);
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] WNF subscription unsubscribed successfully\n");
    }

    DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Driver unloaded\n");
}

// ==========================================================
// WNF Initialization Function
// ==========================================================

// Function to initialize WNF communication
NTSTATUS InitializeWNF() {
    // Dynamically resolve the ExPublishWnfStateData function
    UNICODE_STRING publishFunctionName;
    RtlInitUnicodeString(&publishFunctionName, L"ExPublishWnfStateData");
    ExPublishWnfStateDataFunc = (PExPublishWnfStateData)MmGetSystemRoutineAddress(&publishFunctionName);

    // Dynamically resolve the ExUnsubscribeWnfStateChange function
    UNICODE_STRING unsubscribeFunctionName;
    RtlInitUnicodeString(&unsubscribeFunctionName, L"ExUnsubscribeWnfStateChange");
    ExUnsubscribeWnfStateChangeFunc = (PExUnsubscribeWnfStateChange)MmGetSystemRoutineAddress(&unsubscribeFunctionName);

    if (ExPublishWnfStateDataFunc == NULL || ExUnsubscribeWnfStateChangeFunc == NULL) {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] Failed to resolve WNF functions\n");
        return STATUS_UNSUCCESSFUL;
    }

    // Prepare your data (if needed)
    /
    INSTRUCTIONS instructions = { 0 };
    instructions.reqBase = TRUE;
    instructions.baseAddress = 0x12345678; // Example base address
    instructions.output = (PVOID)instructions.baseAddress;

    // Publish the data to the WNF state
    NTSTATUS status = ExPublishWnfStateDataFunc(&MY_WNF_STATE_NAME, &instructions, sizeof(INSTRUCTIONS), NULL, NULL, 0);

    if (!NT_SUCCESS(status)) {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] ExPublishWnfStateData failed: 0x%08X\n", status);
        return status;
    }

    DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Published WNF state data successfully\n");
    */

    // Subscribe to the WNF state change
    NTSTATUS status = CallExSubscribeWnfStateChange();
    if (!NT_SUCCESS(status)) {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] ExSubscribeWnfStateChange failed: 0x%08X\n", status);
        return status;
    }

    DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Subscribed to WNF state changes successfully\n");
    return STATUS_SUCCESS;
}

// ==========================================================
// DriverEntry Function
// ==========================================================

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
    UNREFERENCED_PARAMETER(RegistryPath);

    NTSTATUS status;

    // Register the process creation notify routine
    status = PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, FALSE);
    if (!NT_SUCCESS(status)) {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] PsSetCreateProcessNotifyRoutine failed: 0x%X\n", status);
        return status;
    }
    else {
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Process notify routine set successfully\n");
    }

    // Initialize WNF communication
    status = InitializeWNF();
    if (!NT_SUCCESS(status)) {
        // If WNF initialization fails, unload the driver
        PsSetCreateProcessNotifyRoutine(ProcessNotifyCallback, TRUE);
        DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[Driver] WNF initialization failed. Driver will unload.\n");
        return status;
    }

    // Set the unload routine
    DriverObject->DriverUnload = DriverUnload;

    DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[Driver] Driver loaded successfully\n");
    return STATUS_SUCCESS;
}