Windows Memory Monitoring

·

For memory monitoring on Windows, especially targeting virtual memory allocations, page faults, and memory protection changes, there are several safer alternatives that don’t rely on hooking or unsupported mechanisms like ALPC. Below are some methods to achieve memory monitoring at the kernel level and provide user-mode notifications without performance bottlenecks.

Key Approaches for Memory Monitoring

  1. Process and Thread Notifications: Use PsSetCreateProcessNotifyRoutineEx and PsSetCreateThreadNotifyRoutine to monitor process and thread creation events.
  2. Page Fault Monitoring: While page faults aren’t directly exposed via a kernel API, monitoring changes in memory protections (like NtProtectVirtualMemory) could serve as a way to track suspicious memory activities.
  3. Memory Region Monitoring: You can periodically check for virtual memory allocations by inspecting the memory regions of processes using functions like ZwQueryVirtualMemory.
  4. Filter Drivers: File system filter drivers can also be used to monitor specific file-related memory operations.

Method 1: Virtual Memory Monitoring with Virtual Address Descriptors (VADs)

While VADs (Virtual Address Descriptors) aren’t directly accessible through public APIs, you can inspect them indirectly in the kernel. This method requires deep knowledge of the Windows Memory Manager, but is the most efficient way to monitor memory changes.

Method 2: Using PsSetLoadImageNotifyRoutine for Monitoring Memory-Mapped Files

If you want to monitor memory allocations related to executable images or DLLs being loaded into memory, you can use the PsSetLoadImageNotifyRoutine function. This doesn’t give complete coverage of all memory operations but can be useful for monitoring memory-mapped files (like DLLs).

VOID NTAPI LoadImageNotifyRoutine(
    PUNICODE_STRING FullImageName,
    HANDLE ProcessId,
    PIMAGE_INFO ImageInfo
)
{
    if (ImageInfo->SystemModeImage) {
        DbgPrint("System Mode Image Loaded: %wZ\n", FullImageName);
    } else {
        DbgPrint("User Mode Image Loaded: %wZ in Process %d\n", FullImageName, ProcessId);
    }
}

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

    // Register the image load notification routine
    status = PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine);
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to register load image notify routine\n");
        return status;
    }

    DriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
    PsRemoveLoadImageNotifyRoutine(LoadImageNotifyRoutine);
    DbgPrint("Driver Unloaded\n");
}

Method 3: Periodic Virtual Memory Inspections

You can use ZwQueryVirtualMemory to inspect the memory regions of a process periodically and gather information on virtual memory allocations, protection levels, and committed pages.

Example of Using ZwQueryVirtualMemory:

NTSTATUS QueryMemoryRegions(HANDLE ProcessHandle)
{
    MEMORY_BASIC_INFORMATION memInfo;
    PVOID baseAddress = NULL;
    NTSTATUS status;

    while (NT_SUCCESS(status = ZwQueryVirtualMemory(
            ProcessHandle, baseAddress, MemoryBasicInformation, &memInfo, sizeof(memInfo), NULL)))
    {
        DbgPrint("BaseAddress: %p, RegionSize: %llu, State: %x, Protect: %x\n",
                 memInfo.BaseAddress, memInfo.RegionSize, memInfo.State, memInfo.Protect);

        // Move to the next memory region
        baseAddress = (PBYTE)baseAddress + memInfo.RegionSize;
    }

    return status;
}

Method 4: ETW (Event Tracing for Windows) for Page Faults and Memory Monitoring

ETW is a powerful mechanism in Windows that can be used for tracing low-level system events, including memory-related operations such as page faults.

  1. Enable page fault and virtual memory event tracing using ETW.
  2. Collect and analyze ETW events for memory operations.

Example of Setting Up ETW for Memory Operations:

Using ETW, you can set up listeners for specific system events related to memory, such as:

  • Page faults
  • Memory allocations
  • Memory protection changes

This requires using the Windows Performance Toolkit or programmatically setting up ETW sessions via the EventTrace APIs.

Efficient Communication to User Mode

To handle the high volume of kernel events without performance bottlenecks, you can use one of the following mechanisms for notifying user-mode applications:

  1. I/O Completion Ports: If you have a user-mode application that interacts with your driver, I/O completion ports are an efficient way to handle asynchronous notifications for memory changes.
  2. APCs (Asynchronous Procedure Calls): APCs allow you to execute code in the context of a user-mode thread. This is useful for delivering memory change notifications in a non-blocking manner.
  3. Shared Memory: If the volume of data is extremely high, you can create a shared memory region between your kernel-mode driver and user-mode application to pass information efficiently.

Using APC for Memory Change Notifications

In place of ALPC, you can use APC to notify user-mode applications about significant memory changes asynchronously. Here’s an outline of how to use APCs:

  1. Queue an APC to a user-mode thread when a memory protection change occurs.
  2. Execute APC in the user-mode thread context, passing memory-related information to user-mode.

APC Example for Memory Change Notification

VOID NTAPI MemoryChangeApcRoutine(
    PKAPC Apc,
    PKNORMAL_ROUTINE *NormalRoutine,
    PVOID *NormalContext,
    PVOID *SystemArgument1,
    PVOID *SystemArgument2
)
{
    // Log or notify about the memory change
    DbgPrint("Memory change APC triggered\n");

    // Cleanup APC
    ExFreePool(Apc);
}

VOID QueueMemoryChangeApc(PEPROCESS Process)
{
    PKAPC Apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC));
    if (!Apc) {
        return;
    }

    // Initialize and queue APC
    KeInitializeApc(Apc,
                    PsGetCurrentThread(), // Thread to queue the APC to
                    OriginalApcEnvironment,
                    (PKKERNEL_ROUTINE)MemoryChangeApcRoutine,
                    NULL, // Rundown routine
                    NULL, // Normal routine
                    KernelMode,
                    NULL);

    // Insert APC into the queue
    if (!KeInsertQueueApc(Apc, NULL, NULL, 0)) {
        ExFreePool(Apc);
    }
}

Conclusion

For monitoring memory allocations, protection changes, and page faults in Windows 11, without using methods like SSDT hooking, you can:

  • Use PsSetCreateProcessNotifyRoutineEx and PsSetLoadImageNotifyRoutine for high-level monitoring of process and image loads.
  • Use ZwQueryVirtualMemory to inspect memory regions for allocation and protection changes.
  • Use APCs to asynchronously notify user-mode applications of memory changes without impacting performance.
  • Consider ETW for detailed tracing of memory events like page faults.

These techniques help monitor memory efficiently without causing performance bottlenecks or violating Windows’ kernel integrity protections.For memory monitoring on Windows, especially targeting virtual memory allocations, page faults, and memory protection changes, there are several safer alternatives that don’t rely on hooking or unsupported mechanisms like ALPC. Below are some methods to achieve memory monitoring at the kernel level and provide user-mode notifications without performance bottlenecks.

Key Approaches for Memory Monitoring

  1. Process and Thread Notifications: Use PsSetCreateProcessNotifyRoutineEx and PsSetCreateThreadNotifyRoutine to monitor process and thread creation events.
  2. Page Fault Monitoring: While page faults aren’t directly exposed via a kernel API, monitoring changes in memory protections (like NtProtectVirtualMemory) could serve as a way to track suspicious memory activities.
  3. Memory Region Monitoring: You can periodically check for virtual memory allocations by inspecting the memory regions of processes using functions like ZwQueryVirtualMemory.
  4. Filter Drivers: File system filter drivers can also be used to monitor specific file-related memory operations.

Method 1: Virtual Memory Monitoring with Virtual Address Descriptors (VADs)

While VADs (Virtual Address Descriptors) aren’t directly accessible through public APIs, you can inspect them indirectly in the kernel. This method requires deep knowledge of the Windows Memory Manager, but is the most efficient way to monitor memory changes.

Method 2: Using PsSetLoadImageNotifyRoutine for Monitoring Memory-Mapped Files

If you want to monitor memory allocations related to executable images or DLLs being loaded into memory, you can use the PsSetLoadImageNotifyRoutine function. This doesn’t give complete coverage of all memory operations but can be useful for monitoring memory-mapped files (like DLLs).

VOID NTAPI LoadImageNotifyRoutine(
    PUNICODE_STRING FullImageName,
    HANDLE ProcessId,
    PIMAGE_INFO ImageInfo
)
{
    if (ImageInfo->SystemModeImage) {
        DbgPrint("System Mode Image Loaded: %wZ\n", FullImageName);
    } else {
        DbgPrint("User Mode Image Loaded: %wZ in Process %d\n", FullImageName, ProcessId);
    }
}

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

    // Register the image load notification routine
    status = PsSetLoadImageNotifyRoutine(LoadImageNotifyRoutine);
    if (!NT_SUCCESS(status)) {
        DbgPrint("Failed to register load image notify routine\n");
        return status;
    }

    DriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
    PsRemoveLoadImageNotifyRoutine(LoadImageNotifyRoutine);
    DbgPrint("Driver Unloaded\n");
}

Method 3: Periodic Virtual Memory Inspections

You can use ZwQueryVirtualMemory to inspect the memory regions of a process periodically and gather information on virtual memory allocations, protection levels, and committed pages.

Example of Using ZwQueryVirtualMemory:

NTSTATUS QueryMemoryRegions(HANDLE ProcessHandle)
{
    MEMORY_BASIC_INFORMATION memInfo;
    PVOID baseAddress = NULL;
    NTSTATUS status;

    while (NT_SUCCESS(status = ZwQueryVirtualMemory(
            ProcessHandle, baseAddress, MemoryBasicInformation, &memInfo, sizeof(memInfo), NULL)))
    {
        DbgPrint("BaseAddress: %p, RegionSize: %llu, State: %x, Protect: %x\n",
                 memInfo.BaseAddress, memInfo.RegionSize, memInfo.State, memInfo.Protect);

        // Move to the next memory region
        baseAddress = (PBYTE)baseAddress + memInfo.RegionSize;
    }

    return status;
}

Method 4: ETW (Event Tracing for Windows) for Page Faults and Memory Monitoring

ETW is a powerful mechanism in Windows that can be used for tracing low-level system events, including memory-related operations such as page faults.

  1. Enable page fault and virtual memory event tracing using ETW.
  2. Collect and analyze ETW events for memory operations.

Example of Setting Up ETW for Memory Operations:

Using ETW, you can set up listeners for specific system events related to memory, such as:

  • Page faults
  • Memory allocations
  • Memory protection changes

This requires using the Windows Performance Toolkit or programmatically setting up ETW sessions via the EventTrace APIs.

Efficient Communication to User Mode

To handle the high volume of kernel events without performance bottlenecks, you can use one of the following mechanisms for notifying user-mode applications:

  1. I/O Completion Ports: If you have a user-mode application that interacts with your driver, I/O completion ports are an efficient way to handle asynchronous notifications for memory changes.
  2. APCs (Asynchronous Procedure Calls): APCs allow you to execute code in the context of a user-mode thread. This is useful for delivering memory change notifications in a non-blocking manner.
  3. Shared Memory: If the volume of data is extremely high, you can create a shared memory region between your kernel-mode driver and user-mode application to pass information efficiently.

Using APC for Memory Change Notifications

In place of ALPC, you can use APC to notify user-mode applications about significant memory changes asynchronously. Here’s an outline of how to use APCs:

  1. Queue an APC to a user-mode thread when a memory protection change occurs.
  2. Execute APC in the user-mode thread context, passing memory-related information to user-mode.

APC Example for Memory Change Notification

VOID NTAPI MemoryChangeApcRoutine(
    PKAPC Apc,
    PKNORMAL_ROUTINE *NormalRoutine,
    PVOID *NormalContext,
    PVOID *SystemArgument1,
    PVOID *SystemArgument2
)
{
    // Log or notify about the memory change
    DbgPrint("Memory change APC triggered\n");

    // Cleanup APC
    ExFreePool(Apc);
}

VOID QueueMemoryChangeApc(PEPROCESS Process)
{
    PKAPC Apc = (PKAPC)ExAllocatePool(NonPagedPool, sizeof(KAPC));
    if (!Apc) {
        return;
    }

    // Initialize and queue APC
    KeInitializeApc(Apc,
                    PsGetCurrentThread(), // Thread to queue the APC to
                    OriginalApcEnvironment,
                    (PKKERNEL_ROUTINE)MemoryChangeApcRoutine,
                    NULL, // Rundown routine
                    NULL, // Normal routine
                    KernelMode,
                    NULL);

    // Insert APC into the queue
    if (!KeInsertQueueApc(Apc, NULL, NULL, 0)) {
        ExFreePool(Apc);
    }
}

Conclusion

For monitoring memory allocations, protection changes, and page faults in Windows 11, without using methods like SSDT hooking, you can:

  • Use PsSetCreateProcessNotifyRoutineEx and PsSetLoadImageNotifyRoutine for high-level monitoring of process and image loads.
  • Use ZwQueryVirtualMemory to inspect memory regions for allocation and protection changes.
  • Use APCs to asynchronously notify user-mode applications of memory changes without impacting performance.
  • Consider ETW for detailed tracing of memory events like page faults.

These techniques help monitor memory efficiently without causing performance bottlenecks or violating Windows’ kernel integrity protections.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *