MMWSL

The MMWSL is the Memory Manager’s modelling of a Working Set List. Every working set, as modelled by an MMSUPPORT, has one: it’s pointed to from the latter’s VmWorkingSetList member.

Availability

Windows was born with the notion of working sets for the management of virtual address space. All known versions have the MMWSL structure, but the 1607 edition of Windows 10 renames it as MMWSL_FULL. This comprises new MMWSL_INSTANCE and MMWSL_SHARED structures which each have a different selection of the old MMWSL members.

Some measure of the MMWSL structure’s importance in memory management is that it’s one of the few structures (in contrast to mere arrays of small types) that Windows builds at a preset address. Through much of the history of Windows, every process has its working set’s MMWSL at the same address:

Version Address (x86) Address (PAE) Address (x64)
3.10 to 3.50 0xC0482000    
3.51 to 5.0 0xC0502000 0xC0882000  
5.1 to early 5.2 0xC0503000
0xC0503800
0xC0883000
0xC0883800
 
late 5.2 0xC0502000
0xC0502800
0xC0881000
0xC0881800
0xFFFFF700`00081000
6.0 0xC0401000 0xC0801000
6.1 to 6.2 0xC0402000 0xC0802000 0xFFFFF700`01080000
6.3 to 1511   0xC0802000 0xFFFFF580`10804000

The early 32-bit builds, both with and without support for Physical Address Extension (PAE), access the current process’s MMWSL through an internal variable named MmWorkingSetList which is initialised to this fixed address and is thereafter left alone. Versions 5.1 and 5.2 can vary this variable’s initialisation by 0x00000800 when a virtual bias applies, as for the /3GB boot option. Starting with version 6.1, the variable is discarded in favour of hard-coding the address. All known 64-bit kernels that have the structure hard-code the address.

Whether the predictability of this address for the MMWSL was ever exploitable is not known, but the 1703 release of Windows 10 does away with it.

Variability

As an internal structure with little, if any, visibility outside the kernel, the MMWSL varies greatly between versions and even between builds:

Version Size (x86) Size (PAE) Size (x64)
3.10 0x0868    
3.50 0x0468    
3.51 to 4.0 0x0470    
early 5.0 (before SP3) 0x06A0 0x0D00  
late 5.0 (starting with SP3) 0x06A8 0x0D08  
5.1 0x069C 0x0CFC  
5.2 0x0698 0x0CF8 0x80
early 6.0 (before SP1) 0x06A8 0x0D08 0x0488
late 6.0 (starting with SP1) 0x06B8 0x0D18 0x0498
6.1 0x06A8 0x0D08 0x0488
6.2   0x0D9C 0x0530
6.3   0x0DBC 0x0260
10.0   0x0E20 0x0300
1511   0x0E48 0x0350

Layout

The preceding sizes, and the names, offsets and types in the tables that follow, are from Microsoft’s symbol files for the kernel, starting with Windows 2000 SP3. What little information is given below for earlier versions—even for early builds of version 5.0—is at best tentative. What’s known of Microsoft’s names and types for these versions is something of a guess. Where use of a member corresponds closely with that of a version for which Microsoft’s symbols are available, it seems reasonable to suppose continuity. Some use, however, has no correspondence, the code having changed too much. Even where the use hasn’t changed, tracking it down exhaustively would be difficult, if not impossible, even with source code—and I haven’t yet tried to track it down exhaustively.

For many of the integral members, the value is a 0-based index into an array of MMWSLE structures. This index was widened from 32 bits to 64 for 64-bit Windows 8.1. Microsoft almost certainly defines a type for this index. The KDEXTS.DLL debugger extension confirms with high confidence that Microsoft’s name for this type is WSLE_NUMBER. For the table below, WSLE_NUMBER is ordinarily a ULONG but is a ULONG_PTR in 64-bit Windows version 6.3 and higher.

Offset (x86) Offset (x64) Definition Versions Remarks
0x00 (3.10 to 5.1)  
ULONG Quota;
3.10 to 5.1  
0x04 (3.10 to 5.1);
0x00
0x00
WSLE_NUMBER FirstFree;
3.10 to 1511 next at 0x00 in MMWSL_SHARED
0x08 (3.10 to 5.1);
0x04
0x04 (5.2 to 6.2);
0x08
WSLE_NUMBER FirstDynamic;
3.10 to 1511 next at 0x04 and 0x08 in MMWSL_SHARED
0x0C (3.10 to 5.1);
0x08
0x08 (5.2 to 6.2);
0x10
WSLE_NUMBER LastEntry;
3.10 to 1511 next at 0x08 and 0x10 in MMWSL_SHARED
0x10 (3.10 to 5.1);
0x0C
0x0C (5.2 to 6.2);
0x18
WSLE_NUMBER NextSlot;
3.10 to 1511 next at 0x00 in MMWSL_INSTANCE
0x14 (3.10 to 5.1);
0x10 (5.2 to 6.1)
0x10 (5.2 to 6.1)
MMWSLE *Wsle;
3.10 to 6.1 next at 0x9C and 0xB0
0x14 (6.0 to 6.1) 0x18 (6.0 to 6.1)
PVOID LowestPagableAddress;
6.0 to 6.1 next at 0x2C and 0x30
0x18 (3.10 to 5.0)  
ULONG NumberOfCommittedPageTables;
3.10 to 5.0 next at 0x28
0x1C (3.10 to 3.50)   unaccounted 3.10 to 3.50  

0x20 (3.10 to 3.50);
0x1C (3.51 to 5.0);
0x18 (5.1);
0x14 (5.2);
0x18 (6.0 to 6.1);
0x10
0x18 (5.2);
0x20 (6.0 to 6.1);
0x10 (6.2);
0x20
WSLE_NUMBER LastInitializedWsle;
3.10 to 1511 next at 0x0C and 0x18 in MMWSL_SHARED
0x1C (6.0) 0x24 (6.0)
WSLE_NUMBER NextEstimationSlot;
6.0 only  
0x20 (6.0);
0x1C (6.1);
0x14
0x28 (6.0);
0x24 (6.1);
0x14 (6.2);
0x28
WSLE_NUMBER NextAgingSlot;
6.0 to 1511 next at 0x04 and 0x08 in MMWSL_INSTANCE
0x18 0x18 (6.2);
0x30
WSLE_NUMBER NextAccessClearingSlot;
6.2 to 1511 next at 0x08 and 0x10 in MMWSL_INSTANCE
0x1C 0x1C (6.2);
0x38
ULONG LastAccessClearingRemainder;
6.2 to 1511 next at 0x0C and 0x18 in MMWSL_INSTANCE
0x20 0x20 (6.2);
0x3C
ULONG LastAgingRemainder;
6.2 to 1511 next at 0x10 and 0x1C in MMWSL_INSTANCE
0x24 0x24 (6.2);
0x40
ULONG WsleSize;
6.2 to 1511 next at 0x10 and 0x20 in MMWSL_SHARED
0x24 (6.0) 0x2C (6.0)
ULONG EstimatedAvailable;
6.0 only  
0x28 (6.0) 0x30 (6.0)
ULONG GrowthSinceLastEstimate;
6.0 only  
0x2C (6.0);
0x20 (6.1)
0x34 (6.0);
0x28 (6.1)
ULONG NumberOfCommittedPageTables;
6.0 to 6.1 previously at 0x24 and 0x2C;
next at 0x00 in MI_USER_VA_INFO
0x30 (6.0);
0x24 (6.1)
0x38 (6.0);
0x2C (6.1)
ULONG VadBitMapHint;
6.0 to 6.1 previously at 0x34 and 0x44;
next at 0x08 in MI_USER_VA_INFO
0x20 (5.0);
0x1C (5.1);
0x18 (5.2);
0x34 (6.0);
0x28
0x1C (5.2);
0x3C (6.0);
0x30 (6.1);
0x28 (6.2);
0x48
WSLE_NUMBER NonDirectCount;
5.0 to 1511 next at 0x14 and 0x28 in MMWSL_SHARED
0x24 (5.0);
0x20 (5.1);
0x1C (5.2)
0x20 (5.2)
MMWSLE_HASH *HashTable;
5.0 to 5.2  
0x28 (5.0);
0x24 (5.1);
0x20 (5.2)
0x28 (5.2)
ULONG HashTableSize;
5.0 to 5.2  
0x24 (3.10 to 3.50);
0x2C (3.51 to 5.0)
 
KEVENT *ImageMappingPteEvent;
3.10 to early 5.0  
KEVENT ImageMappingPteEvent;
late 5.0 only  
0x28 (5.1);
0x24 (5.2)
0x2C (5.2)
ULONG NumberOfCommittedPageTables;
5.1 to 5.2 previously at 0x18;
next at 0x2C and 0x34
0x38 (late 6.0);
0x2C (6.1)
0x40 (late 6.0);
0x34 (6.1)
ULONG LastVadBit;
late 6.0 to 6.1  
0x3C (late 6.0);
0x30 (6.1)
0x44 (late 6.0);
0x38 (6.1)
ULONG MaximumLastVadBit;
late 6.0 to 6.1 next at 0x1C in MI_USER_VA_INFO
0x40 (late 6.0);
0x34 (6.1)
0x48 (late 6.0);
0x3C (6.1)
ULONG LastAllocationSizeHint;
late 6.0 to 6.1 next at 0x0C in MI_USER_VA_INFO
0x44 (late 6.0);
0x38 (6.1)
0x4C (late 6.0);
0x40 (6.1)
ULONG LastAllocationSize;
late 6.0 to 6.1 next at 0x10 in MI_USER_VA_INFO
0x2C 0x30 (6.2);
0x50
PVOID LowestPagableAddress;
6.2 to 1511 previously at 0x14 and 0x18;
next at 0x18 and 0x30 in MMWSL_SHARED
0x38 (early 6.0);
0x48 (late 6.0);
0x3C (6.1);
0x30
0x40 (early 6.0);
0x50 (late 6.0);
0x48 (6.1);
0x38 (6.2);
0x58
MMWSLE_NONDIRECT_HASH *NonDirectHash;
6.0 to 1511 next at 0x1C and 0x38 in MMWSL_SHARED
0x30 (early 5.0);
0x3C (late 5.0);
0x2C (5.1);
0x28 (5.2);
0x3C (early 6.0);
0x4C (late 6.0);
0x40 (6.1);
0x34
0x30 (5.2);
0x48 (early 6.0);
0x58 (late 6.0);
0x50 (6.1);
0x40 (6.2);
0x60
PVOID HashTableStart;
5.0 to 5.2  
MMWSLE_HASH *HashTableStart;
6.0 to 1511 next at 0x20 and 0x40 in MMWSL_SHARED
0x34 (early 5.0);
0x40 (late 5.0);
0x30 (5.1);
0x2C (5.2);
0x40 (early 6.0);
0x50 (late 6.0);
0x44 (6.1);
0x38
0x38 (5.2);
0x50 (early 6.0);
0x60 (late 6.0);
0x58 (6.1);
0x48 (6.2);
0x68
PVOID HighestPermittedHashAddress;
5.0 to 5.2  
MMWSLE_HASH *HighestPermittedHashAddress;
6.0 to 1511 next at 0x24 and 0x48 in MMWSL_SHARED
0x44 (late 5.0);
0x34 (5.1);
0x30 (5.2)
0x40 (5.2)
ULONG NumberOfImageWaiters;
late 5.0 to 5.2  
0x38 (5.1);
0x34 (5.2)
0x44 (5.2)
ULONG VadBitMapHint;
5.1 to 5.2 next at 0x30 and 0x38
0x44 (early 6.0);
0x54 (late 6.0)
0x48 (5.2);
0x58 (early 6.0);
0x68 (late 6.0)
PVOID HighestUserAddress;
6.0 only (x86);
5.2 to 6.0 (x64)
 

The x86 builds of versions before version 6.2 end with arrays whose sizes vary with how many page tables can be needed for the whole of the user-mode address space. Description is eased by hypothesising MAX_USER_PAGE_TABLES as a symbolic name for this number:

Offset (x86) Offset (PAE) Definition Versions Remarks
0x28 (3.10 to 3.50);
0x30 (3.51 to 4.0);
0x40 (early 5.0);
0x48 (late 5.0);
0x3C (5.1);
0x38 (5.2);
0x48 (early 6.0);
0x58 (late 6.0);
0x48 (6.1)
0x40 (early 5.0);
0x48 (late 5.0);
0x3C (5.1);
0x38 (5.2);
0x48 (early 6.0);
0x58 (late 6.0);
0x48 (6.1)
USHORT UsedPageTableEntries [0x0400];
3.10 only  
USHORT UsedPageTableEntries [MAX_USER_PAGE_TABLES];
3.50 to 6.1 next at 0x3C in MI_USER_VA_INFO
0x0828 (3.10);
0x0428 (3.50);
0x0430 (3.51 to 4.0);
0x0640 (early 5.0);
0x0648 (late 5.0);
0x063C (5.1);
0x0638 (5.2);
0x0648 (early 6.0);
0x0658 (late 6.0);
0x0648 (6.1)
0x0C40 (early 5.0);
0x0C48 (late 5.0);
0x0C3C (5.1);
0x0C38 (5.2);
0x0C48 (early 6.0);
0x0C58 (late 6.0);
0x0C48 (6.1)
ULONG CommittedPageTables [MAX_USER_PAGE_TABLES / 0x20];
3.10 to 6.1 next at 0x0C3C in MI_USER_VA_INFO;
last member in 3.10 to 6.1 (x86)

The x64 builds of versions before 6.2 end very differently. The one member in common with the x86 builds is CommittedPageTables, but it is a pointer, not an array.

Offset (x64) Definition Versions Remarks
0x50 (5.2);
0x60 (early 6.0);
0x70 (late 6.0);
0x60 (6.1)
ULONG MaximumUserPageTablePages;
5.2 to 6.1  
0x54 (5.2);
0x64 (early 6.0);
0x74 (late 6.0);
0x64 (6.1)
ULONG MaximumUserPageDirectoryPages;
5.2 to 6.1  
0x58 (5.2);
0x68 (early 6.0);
0x78 (late 6.0);
0x68 (6.1)
ULONG *CommittedPageTables;
5.2 to 6.1  
0x60 (5.2);
0x70 (early 6.0);
0x80 (late 6.0);
0x70 (6.1)
ULONG NumberOfCommittedPageDirectories;
5.2 to 6.1  
0x68 (5.2);
0x78 (early 6.0);
0x88 (late 6.0);
0x78 (6.1)
ULONG *CommittedPageDirectories;
5.2 only  
ULONGLONG CommittedPageDirectories [0x80];
6.0 to 6.1 next at 0x70 in MI_USER_VA_INFO
0x70 (5.2);
0x0478 (early 6.0);
0x0488 (late 6.0);
0x0478 (6.1)
ULONG NumberOfCommittedPageDirectoryParents;
5.2 to 6.1  
0x78 (5.2);
0x0480 (early 6.0);
0x0490 (late 6.0);
0x0480 (6.1)
ULONGLONG CommittedPageDirectoryParents [1];
5.2 to 6.1 next at 0x0470 in MI_USER_VA_INFO;
last member in 5.2 to 6.1 (x64)

Added For Windows 8  

Offset (x86) Offset (x64) Definition Versions Remarks
0x3C 0x50 (6.2);
0x70
ULONG ActiveWsleCounts [8];
6.2 only  
ULONG_PTR ActiveWsleCounts [8];
6.3 only  
ULONG_PTR ActiveWsleCounts [0x10];
10.0 to 1511 next at 0x14 and 0x20 in MMWSL_INSTANCE
0x5C (6.2 to 6.3);
0x7C
0x70 (6.2);
0xB0 (6.3);
0xF0
MI_ACTIVE_WSLE ActiveWsles [8];
6.2 only  
MI_ACTIVE_WSLE_LISTHEAD ActiveWsles [8];
6.3 only  
MI_ACTIVE_WSLE_LISTHEAD ActiveWsles [0x10];
10.0 to 1511 next at 0x54 and 0xA0 in MMWSL_INSTANCE
0x9C (6.2 to 6.3);
0xFC
0xB0 (6.2);
0x0130 (6.3);
0x01F0
MMWSLE *Wsle;
6.2 to 1511 previously at 0x10;
next at 0x30 and 0x50 in MMWSL_SHARED
0xA0 (6.2 to 6.3);
0x0100
0xB8 (6.2);
0x0138 (6.3);
0x01F8
MI_USER_VA_INFO UserVaInfo;
6.2 to 1511