Virtual Memory
In this documentation we will discuss Virtual Memory in Windows Kernel.
What is Virtual Memory
“Windows uses a virtual memory system that makes it appear as if each process has a large, dedicated address space. Virtual memory provides a more abstract view of memory, independent of the actual layout of physical memory. During execution, the memory manager — with hardware support — translates virtual addresses into the physical addresses where the data is actually stored. This allows the operating system to prevent any process from interfering with another process’s memory or overwriting the operating system’s critical data.” -Windows Internals Part 1 - Chapter 1, Page 39
Virtual Memory is a memory management technique that provides an idealized abstraction of the data stored in physical memory addresses. This allows the operating system to prevent one process from interfering with another process’s memory or overwriting the operating system’s critical data.
This system uses a method to store data both in RAM and on the hard disk. The data is divided into small units called pages, each typically 4 KB in size.
These pages do not need to be stored contiguously in memory. In other words, an application’s data can be scattered in memory — some parts may reside in RAM while others are stored on disk. This method optimizes memory usage without affecting application performance.
An advantage of this system is that applications do not need to be specially modified to benefit from paging. The memory management system performs these operations automatically. The diagram below illustrates this more clearly.

The size of the virtual address space varies depending on the hardware platform. For example, in Windows, the total virtual address space on 32-bit x86 systems is at most 4 GB. On this platform, by default, Windows allocates the lower half of this address space (addresses from 0x00000000 to 0x7FFFFFFF) for user-mode processes and the upper half (addresses from 0x80000000 to 0xFFFFFFFF) for the protected memory used by the operating system.
In 64-bit (x64) Windows systems, the virtual address space is much larger. Theoretically, a 64-bit addressing system provides up to 16 exabytes (2^64 bytes) of virtual address space. However, due to current hardware and operating system limitations, not all of this space is usable. In Windows x64 operating systems, the virtual address space is typically limited to 256 terabytes (2^48 bytes).
Coding
We will allocate a memory from the kernel space via the driver and then map it to the virtual address.
You can click here for the Github link.
#pragma warning(disable: 4996)
#include <ntddk.h>
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
DbgPrintEx(0, 0, "Driver Unloaded\n");
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
SIZE_T MemorySize = 0x1000;
PVOID VirtualAddress = NULL;
PVOID MappedAddress = NULL;
PMDL MDL = NULL;
VirtualAddress = ExAllocatePool(NonPagedPool, MemorySize);
if (NULL == VirtualAddress) {
DbgPrintEx(0, 0, "Failed to Allocate Memory!\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
DbgPrintEx(0, 0, "Allocated Memory at 0x%p\n", VirtualAddress);
MDL = IoAllocateMdl(VirtualAddress, (ULONG)MemorySize, FALSE, FALSE, NULL);
if (NULL == MDL) {
DbgPrintEx(0, 0, "Failed to MDL Creation!\n");
ExFreePool(VirtualAddress);
return STATUS_INSUFFICIENT_RESOURCES;
}
MmBuildMdlForNonPagedPool(MDL);
MappedAddress = MmMapLockedPagesSpecifyCache(MDL, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
if (NULL == MappedAddress) {
DbgPrintEx(0, 0, "Failed to Virtual Address Mapping!\n");
IoFreeMdl(MDL);
ExFreePool(VirtualAddress);
return STATUS_INSUFFICIENT_RESOURCES;
}
DbgPrintEx(0, 0, "Virtual Address mapping is successful! Address: 0x%p\n", MappedAddress);
MmUnmapLockedPages(MappedAddress, MDL);
IoFreeMdl(MDL);
ExFreePool(VirtualAddress);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
This is our code. Now let’s analyze this code:
SIZE_T MemorySize = 0x1000;
PVOID VirtualAddress = NULL;
PVOID MappedAddress = NULL;
PMDL MDL = NULL;
First, we start by defining the parameters in our driver. Their purposes are as follows:
-
MemorySize: Specifies the size of the memory block to be allocated. In this example, I set it to
0x1000
(4 KB). -
VirtualAddress: Holds the address returned when the memory allocation is performed.
-
MappedAddress: Holds the address to which the memory is mapped with a virtual address.
-
MDL: Holds the MDL (Memory Descriptor List) structure used for mapping the memory to a virtual address.
VirtualAddress = ExAllocatePool(NonPagedPool, MemorySize);
if (NULL == VirtualAddress) {
DbgPrintEx(0, 0, "Memory allocation failed\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
DbgPrintEx(0, 0, "Allocated Memory Address: 0x%p\n", VirtualAddress);
In the first step, we allocate a 4 KB memory block from the Nonpaged Pool. If the allocation fails, we print an error message and exit. If it succeeds, we print the address of the allocated memory.
If you are unfamiliar with the Nonpaged Pool, here’s a short explanation:
The Nonpaged Pool is one of the memory pools used by the operating system. It refers to a memory area that is always accessible and cannot be paged out to disk (i.e., cannot undergo “paging”). Memory in the nonpaged pool is reserved for tasks of critical importance to the system. For example, hardware drivers or other kernel-mode components allocate from this pool because their memory access must always be fast and uninterrupted.
Alongside this, there is also the Paged Pool, another memory pool used by the operating system. This pool refers to a memory area that can be paged out to disk. Memory in the paged pool is generally allocated for user-mode applications and services.
MDL = IoAllocateMdl(VirtualAddress, (ULONG)MemorySize, FALSE, FALSE, NULL);
if (NULL == MDL) {
DbgPrintEx(0, 0, "MDL allocation failed!\n");
ExFreePool(VirtualAddress);
return STATUS_INSUFFICIENT_RESOURCES;
}
Next, we create an MDL (Memory Descriptor List) structure to access the physical addresses of the memory block and map this block to a virtual address. The MDL stores the physical addresses and size of the memory, describing how the associated data is mapped from virtual to physical memory.
MmBuildMdlForNonPagedPool(MDL);
We then use this API to build the MDL structure. While building, this function fills in the physical memory addresses and size within the MDL.
MappedAddress = MmMapLockedPagesSpecifyCache(MDL, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
if (NULL == MappedAddress) {
DbgPrintEx(0, 0, "Virtual address mapping failed!\n");
IoFreeMdl(MDL);
ExFreePool(VirtualAddress);
return STATUS_INSUFFICIENT_RESOURCES;
}
DbgPrintEx(0, 0, "Virtual address mapping successful! Address: 0x%p\n", MappedAddress);
Finally, to map the memory to a virtual address, we use the MmMapLockedPagesSpecifyCache API. This function uses the MDL to map the specified memory block into virtual memory. If the operation fails, we print an error message and exit. If it succeeds, we print the mapped virtual address.
MmUnmapLockedPages(MappedAddress, MDL);
IoFreeMdl(MDL);
ExFreePool(VirtualAddress);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
At the end, when we are done, we free the memory. This is done by first calling MmUnmapLockedPages to unmap the virtual memory, then IoFreeMdl to free the MDL structure, and finally ExFreePool to release the allocated memory.
Running the Driver
Well, let’s run our driver:

Conclusion
In this documentation, we explored how Windows implements virtual memory in the kernel and how developers can work with it programmatically. We learned that virtual memory abstracts the physical memory layout, giving each process its own protected address space while enabling efficient memory utilization through paging.
Understanding how virtual memory works at the kernel level — and how to manage memory safely and efficiently — is a critical skill for Windows kernel development. The techniques covered here form the basis for more advanced topics, such as direct hardware access, DMA operations, and custom memory management strategies in drivers.