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
- Process and Thread Notifications: Use
PsSetCreateProcessNotifyRoutineEx
and PsSetCreateThreadNotifyRoutine
to monitor process and thread creation events.
- 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.
- Memory Region Monitoring: You can periodically check for virtual memory allocations by inspecting the memory regions of processes using functions like
ZwQueryVirtualMemory
.
- 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.
- Enable page fault and virtual memory event tracing using ETW.
- 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:
- 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.
- 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.
- 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:
- Queue an APC to a user-mode thread when a memory protection change occurs.
- 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
- Process and Thread Notifications: Use
PsSetCreateProcessNotifyRoutineEx
and PsSetCreateThreadNotifyRoutine
to monitor process and thread creation events.
- 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.
- Memory Region Monitoring: You can periodically check for virtual memory allocations by inspecting the memory regions of processes using functions like
ZwQueryVirtualMemory
.
- 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.
- Enable page fault and virtual memory event tracing using ETW.
- 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:
- 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.
- 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.
- 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:
- Queue an APC to a user-mode thread when a memory protection change occurs.
- 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.