A Proto-PTE (Prototype PTE, PPTE) is a basic block of the Windows VMM (Virtual Memory Manager) for help of which the OS can work with memory-mapped files (or Sections in the Native/NT kernel API terms). What I have learned from discussions with Windows Internals researchers, and my own experience, the PPTE is most tricky stuff a researcher can face with. But, in fact, here is nothing complicated with PPTE concept understanding if we can view at it from right side. Honestly, it was already eleven years ago when I defended my coursework at the university that named "Inside Windows XP VMM". I have uploaded its Russian edition to famous KM forum. It was written in SoftICE times when you could break the Windows kernel execution with Ctrl-X and debug a local system without remote actions. :)
That my coursework has covered a lot of VMM subsystems, including, Hyperspace, PTEs, Session space, WSL, PFN database, Sections, Cache Internals. But, unfourtunately, it has been oriented only on x86 architecture. Thus, I took my chapters dedicated to PTE and Section, and has adapted them for actual x64 architecture on today. I have started to learn Windows Internals since 2nd version of the book of the series was released (Inside Windows 2000). Windows Internals and Rootkits are both my favs directions of researching on today as it was more than ten years ago.
A PPTE (that actually is a kind of Software PTE, SPTE) is an original basic block of the VMM that helps it to attach to specific proctess a new view of already mapped section. Mentioned SPTE term just means that the OS organizes structure of such always Invalid PTE by itself, i. e. CPU doesn't know anything about this structure. A task of CPU in dispatching such SPTE (Invalid PTE) is just to interrupt execution of a current thread and forward execution to the NT Kernel KiPageFault (KiTrap0E) handler (formally belongs to Interruption Managers or VMM).
To understand how Windows works with PPTE, let's put attention to the following structures.
- Section (nt!_SECTION). The kernel structure that describes section object.
- Segment (nt!_SEGMENT). Actually is a core structure of PPTE architecture that contains PPTE page table.
- Segment Control Area (SCA, nt!_CONTROL_AREA). Along with SEGMENT is a key structure of Section and for understanding how PPTE works. Control area is intended for storing information that helps VMM to perform I/O operations to read data from file or to write data into it.
- Subsection (nt!_SUBSECTION`). Is a data structure that contains a necessary information for calculation an offset inside mapped file via PPTEs
As we can see from the picture above, clients (threads) from separate processes can create sections for one specific file to execute it. For example, all Windows processes use kernel32.dll library that is mapped as section in every process. Basic SECTION structure represents a kernel object that is created when a thread tries to create memory-mapped file. If section is created for a file for the first time, the OS has to initialize related kernel structures like SEGMENT and CONTROL_AREA to describe that memory-mapped file, including, PPTE table. From other side, if a thread tries to create section for the file that already has corresponding VMM structs, its newly created section just attaches a specific SEGMENT. When a client calls Windows API to map some range of file into memory, the VMM just takes corresponding to allocated VM PTEs and performs attaching them to PPTE and these SPTEs now is called the PTE Pointed to Prototype (PTEPP).
Let's look at major fields of the section structure.
Below you can see CA structure format.
Let's look at major fields of the section structure.
typedef struct _SECTION
{
struct _RTL_BALANCED_NODE SectionNode;
UINT64 StartingVpn; //starting virtual page number of mapping
UINT64 EndingVpn; //ending virtual page number
UINT64 EndingVpn; //ending virtual page number
union
{
struct _CONTROL_AREA* ControlArea; //ptr to corresponding control area
struct _FILE_OBJECT* FileObject; //or to file object
struct
{
UINT64 RemoteImageFileObject : 1; //for remote files cases
UINT64 RemoteDataFileObject : 1;
};
}u1;
UINT64 SizeOfSection; //size of section
union
{
ULONG32 LongFlags;
struct _MMSECTION_FLAGS Flags; //flags from ZwCreateSection
}u;
struct
{
struct
{
ULONG32 InitialPageProtection : 12;
ULONG32 SessionId : 19;
ULONG32 NoValidationNeeded : 1;
};
}SECTION, *PSECTION;Below you can see CA structure format.
typedef struct _CONTROL_AREA
{
struct _SEGMENT* Segment; //ptr to corresponding segment
union
{
struct _LIST_ENTRY ListHead;
VOID* AweContext;
};
UINT64 NumberOfSectionReferences;
UINT64 NumberOfPfnReferences;
UINT64 NumberOfMappedViews; //count of sections that have been mapped with this CA
UINT64 NumberOfUserReferences;
...
union
{
struct
{
union
{
ULONG32 NumberOfSystemCacheViews;
ULONG32 ImageRelocationStartBit;
};
union
{
LONG32 WritableUserReferences;
struct
{
ULONG32 ImageRelocationSizeIn64k : 16;
ULONG32 LargePage : 1;
ULONG32 AweSection : 1;
ULONG32 SystemImage : 1;
ULONG32 StrongCode : 2;
ULONG32 CantMove : 1;
ULONG32 BitMap : 2;
ULONG32 ImageActive : 1;
ULONG32 ImageBaseOkToReuse : 1;
};
};
union
{
ULONG32 FlushInProgressCount;
ULONG32 NumberOfSubsections;
struct _MI_IMAGE_SECURITY_REFERENCE* SeImageStub;
};
}e2;
}u2;
...
}CONTROL_AREA, *PCONTROL_AREA; //defines section's properties that are actual for all clients
When a client requests unmap view of section operation, Windows just removes links to corresponding PPTE entries from process's PTE that describes a view of mapped section. Thus, PPTE is comfortable and universal interface for attaching view and detaching it from the specific process address space. And this is its major purpose. The Segment structure contains pointer to PPTE table as we can see below.
typedef struct _SEGMENT
When a client requests unmap view of section operation, Windows just removes links to corresponding PPTE entries from process's PTE that describes a view of mapped section. Thus, PPTE is comfortable and universal interface for attaching view and detaching it from the specific process address space. And this is its major purpose. The Segment structure contains pointer to PPTE table as we can see below.
typedef struct _SEGMENT
{
struct _CONTROL_AREA* ControlArea; //-> ptr to corresponding CA
ULONG32 TotalNumberOfPtes;
struct _SEGMENT_FLAGS SegmentFlags;
UINT64 NumberOfCommittedPages;
UINT64 SizeOfSegment;
...
struct _MMPTE* PrototypePte; //-> PPTE table that are pointing to Subsections
}SEGMENT, *PSEGMENT;
Another key structure for understanding how Windows uses PPTE to work with Section (memory-mapped file) is so-called Subsection (nt!_SUBSECTION). Subsection is a data structure that contains a necessary information for calculation an offset inside mapped file via PPTEs, which are described this file. For usual binary file there is always (with some exceptions) a single subsection, but for an executable PE files there is one subsection for each PE section plus another one for PE header. A subsection is intended for storing memory protection constants for all PTEs that contain specific PE file section, i. e. VMM will assign to all PTEs that are pointed by Subsection memory protection constant from Sybsection structure.
All PPTEs are referencing to specific subsection, in case of mapping section as binary, all PPTEs will point to single subsection, in case of mapping section as executable, PPTEs will point to specific PE's section subsection. A subsection contains so-called starting sector field that describes beginning of specific section inside PE file (takes this value from PE header - Raw Section Offset / SECTOR_SIZE). Also a subsection contains a pointer to first PPTE in the PPTE table of specific Segment and total number of PPTEs for itself (i. e. number of pages for specific PE section that, in fact, represents its VirtualSize that is rounded to be multiple with PAGE_SIZE).
If we have address of PPTE, we can easy calculate offset inside PE file that this PTE describes (as a distance between base and current PTE). If Pte variable is a pointer to current PPTE, than we can calculate an address of Subsection.
(((PUCHAR)Pte - (PUCHAR)Subsection->SubsectionBase) / sizeof(PTE)) << PAGE_SHIFT + Subsection->StartingSector * SECTOR_SIZE
If Subsection – address of subsection, than first PPTE describes, FirstPte = &Subsection->SubsectionBase[0], and last range, LastPte = &Subsection->SubsectionBase[Subsection->PtesInSubsection]. I. e. if X – address of the section in memory and its Pte, than &Subsection->SubsectionBase[0] <= Pte < &Subsection->SubsectionBase[Subsection->PtesInSubsection].
typedef struct _SUBSECTION
{
struct _CONTROL_AREA* ControlArea;
struct _MMPTE* SubsectionBase;
struct _SUBSECTION* NextSubsection;
...
union
{
ULONG32 LongFlags;
struct _MMSUBSECTION_FLAGS SubsectionFlags;
}u;
ULONG32 StartingSector;
ULONG32 NumberOfFullSectors;
ULONG32 PtesInSubsection;
...
}SUBSECTION, *PSUBSECTION;
typedef struct _MMSUBSECTION_FLAGS
{
struct
{
UINT16 SubsectionAccessed : 1;
UINT16 Protection : 5;
UINT16 StartingSector : 10;
};
struct
{
UINT16 SubsectionStatic : 1;
UINT16 GlobalMemory : 1;
UINT16 Spare : 1;
UINT16 OnDereferenceList : 1;
UINT16 SectorEndOffset : 12;
};
}MMSUBSECTION_FLAGS, *PMMSUBSECTION_FLAGS;
Another key structure for understanding how Windows uses PPTE to work with Section (memory-mapped file) is so-called Subsection (nt!_SUBSECTION). Subsection is a data structure that contains a necessary information for calculation an offset inside mapped file via PPTEs, which are described this file. For usual binary file there is always (with some exceptions) a single subsection, but for an executable PE files there is one subsection for each PE section plus another one for PE header. A subsection is intended for storing memory protection constants for all PTEs that contain specific PE file section, i. e. VMM will assign to all PTEs that are pointed by Subsection memory protection constant from Sybsection structure.
All PPTEs are referencing to specific subsection, in case of mapping section as binary, all PPTEs will point to single subsection, in case of mapping section as executable, PPTEs will point to specific PE's section subsection. A subsection contains so-called starting sector field that describes beginning of specific section inside PE file (takes this value from PE header - Raw Section Offset / SECTOR_SIZE). Also a subsection contains a pointer to first PPTE in the PPTE table of specific Segment and total number of PPTEs for itself (i. e. number of pages for specific PE section that, in fact, represents its VirtualSize that is rounded to be multiple with PAGE_SIZE).
If we have address of PPTE, we can easy calculate offset inside PE file that this PTE describes (as a distance between base and current PTE). If Pte variable is a pointer to current PPTE, than we can calculate an address of Subsection.
(((PUCHAR)Pte - (PUCHAR)Subsection->SubsectionBase) / sizeof(PTE)) << PAGE_SHIFT + Subsection->StartingSector * SECTOR_SIZE
If Subsection – address of subsection, than first PPTE describes, FirstPte = &Subsection->SubsectionBase[0], and last range, LastPte = &Subsection->SubsectionBase[Subsection->PtesInSubsection]. I. e. if X – address of the section in memory and its Pte, than &Subsection->SubsectionBase[0] <= Pte < &Subsection->SubsectionBase[Subsection->PtesInSubsection].
typedef struct _SUBSECTION
{
struct _CONTROL_AREA* ControlArea;
struct _MMPTE* SubsectionBase;
struct _SUBSECTION* NextSubsection;
...
union
{
ULONG32 LongFlags;
struct _MMSUBSECTION_FLAGS SubsectionFlags;
}u;
ULONG32 StartingSector;
ULONG32 NumberOfFullSectors;
ULONG32 PtesInSubsection;
...
}SUBSECTION, *PSUBSECTION;
typedef struct _MMSUBSECTION_FLAGS
{
struct
{
UINT16 SubsectionAccessed : 1;
UINT16 Protection : 5;
UINT16 StartingSector : 10;
};
struct
{
UINT16 SubsectionStatic : 1;
UINT16 GlobalMemory : 1;
UINT16 Spare : 1;
UINT16 OnDereferenceList : 1;
UINT16 SectorEndOffset : 12;
};
}MMSUBSECTION_FLAGS, *PMMSUBSECTION_FLAGS;
Let's take a real example.
> !process 0 0
PROCESS ffffca0cabb485c0
SessionId: 0 Cid: 07d0 Peb: ef0697b000 ParentCid: 0338
DirBase: 13692000 ObjectTable: ffffb981ce2b7380 HandleCount: 149.
Image: VSSVC.exe
> !handle 0 3 ffffca0cabb485c0
0030: Object: ffffb981c8277a50 GrantedAccess: 00000003 (Inherit) Entry: ffffb981ce3c70c0
Object: ffffb981c8277a50 Type: (ffffca0ca8c71c50) Directory
ObjectHeader: ffffb981c8277a20 (new version)
HandleCount: 43 PointerCount: 1407269
Directory Object: ffffb981c7c16b20 Name: KnownDlls
Hash Address Type Name
---- ------- ---- ----
00 ffffb981c8287c10 Section kernel32.dll
> !object ffffb981c8287c10
Object: ffffb981c8287c10 Type: (ffffca0ca8d0ada0) Section
ObjectHeader: ffffb981c8287be0 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: ffffb981c8277a50 Name: kernel32.dll
> dt _SECTION ffffb981c8287c10 -r1
nt!_SECTION
+0x000 SectionNode : _RTL_BALANCED_NODE
...
+0x018 StartingVpn : 0
+0x020 EndingVpn : 0
+0x028 u1 : <unnamed-tag>
+0x000 ControlArea : 0xffffca0c`aa900880 _CONTROL_AREA
+0x000 FileObject : 0xffffca0c`aa900880 _FILE_OBJECT
+0x000 RemoteImageFileObject : 0y0
+0x000 RemoteDataFileObject : 0y0
+0x030 SizeOfSection : 0xae000
...
> !ca 0xffffca0c`aa900880
ControlArea @ ffffca0caa900880
Segment ffffb981c8297cb0 Flink ffffca0cabb4d230 Blink ffffca0caab19e00
Section Ref 1 Pfn Ref 6f Mapped Views 2a
User Ref 2b WaitForDel 0 Flush Count a88
File Object ffffca0caa900c90 ModWriteCount 0 System Views 348f
WritableRefs c0000b
Flags (a0) Image File
\Windows\System32\kernel32.dll
Segment @ ffffb981c8297cb0
ControlArea ffffca0caa900880 BasedAddress 00007ffbcb640000
Total Ptes ae
Segment Size ae000 Committed 0
Image Commit 2 Image Info ffffb981c8297cf8
ProtoPtes ffffb981c7f24a90
Flags (c4820000) ProtectionMask
> dq ffffb981c7f24a90
ffffb981`c7f24a90 8a000000`37295121 00000000`2c624860
ffffb981`c7f24aa0 0a000000`2c625121 0a000000`2c626121
ffffb981`c7f24ab0 0a000000`2c627121 0a000000`2c628121 -> Subsection address
We can also take a real example from my coursework for 32bit Windows XP SP3. Let's take a specific cache slot, because creation of PPTE may be delayed in Ring 3 process before someone performed access to it. Print list of slots. For example, at my machine, the next items are existing. In this case, cache manager mapped binary system registry file named NTUSER.DAT. As this file is mapped as binary, here will exist only one subsection for it.
Vacb #186 0x81936170 -> 0xc7080000
File: 0x81749818
Offset: 0x00080000
\Documents and Settings\Art\NTUSER.DAT
We can see that this slot maps file with offset 0x80000 and base address 0xc7080000.
0: kd> !pte 0xc7080000
VA c7080000
PDE at C0300C70 PTE at C031C200
contains 01CF0963 contains 0554A921
pfn 1cf0 -G-DA--KWEV pfn 554a -G--A—KREV
A PTE is valid, than we can restore a content of PPTE from PFN database.
0: kd> !pfn 554a
PFN 0000554A at address 8107FEF0
flink 000018C8 blink / share count 00000001 pteaddress E15B7208
reference count 0001 Cached color 0
restore pte 86D204CE containing page 00496E Active P
Shared
Now we have that PPTE address is 0xE15B7208 and its original content is 0x86D204CE. We can translate it to subsection with formula.
SubsectionAddress = MmNonPagedPoolStart + PrototypeIndex << 3.
86D204CE = 1 00001101101001000000 1 00110 0111 0
| |
| |->is ptr to subsection
|->is mapped file
000011011010010000000111 = DA407 * 8 + 81181000 = 6D2038 + 81181000 = 81853038
Print a subsection.
> dt _subsection 81853038
nt!_SUBSECTION
+0x000 ControlArea : 0x81853008 _CONTROL_AREA
+0x004 u : __unnamed
+0x008 StartingSector : 0
+0x00c NumberOfFullSectors : 0x100
+0x010 SubsectionBase : 0xe15b7008 _MMPTE
+0x014 UnusedPtes : 0
+0x018 PtesInSubsection : 0x100
+0x01c NextSubsection : (null)
and
+0x004 u : __unnamed
+0x000 LongFlags : 0x60
+0x000 SubsectionFlags : _MMSUBSECTION_FLAGS
+0x000 ReadOnly : 0y0
+0x000 ReadWrite : 0y0
+0x000 SubsectionStatic : 0y0
+0x000 GlobalMemory : 0y0
+0x000 Protection : 0y00110 (0x6) - MM_EXECUTE_READWRITE
+0x000 LargePages : 0y0
+0x000 StartingSector4132 : 0y0000000000 (0)
+0x000 SectorEndOffset : 0y000000000000 (0)
Print control area.
> dt _control_area 0x81853008
nt!_CONTROL_AREA
+0x000 Segment : 0xe1559ba0 _SEGMENT
+0x004 DereferenceList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x00c NumberOfSectionReferences : 1
+0x010 NumberOfPfnReferences : 0xe5
+0x014 NumberOfMappedViews : 4
+0x018 NumberOfSubsections : 1
+0x01a FlushInProgressCount : 0
+0x01c NumberOfUserReferences : 0
+0x020 u : __unnamed
+0x024 FilePointer : 0x81749818 _FILE_OBJECT
+0x028 WaitingForDeletion : (null)
+0x02c ModifiedWriteCount : 0
+0x02e NumberOfSystemCacheViews : 4
Number of subsections – 1, because it was mapped as binary.
As a result.
- Get an offset, from which into slot of the cache files was mapped (E15B7208 - e15b7008) / 4 *1000 + 0 = 80000, as we can see in VACB.
- Protection bits for virtual pages are granting maximum rights - MM_EXECUTE_READWRITE as we can see into PPTE Protection fiels – 110, i. e. 6.
Let's take more interesting stuff with executable section ole32.dll.
> !ca 817ab818
ControlArea @ 817ab818
Segment e172eaa0 Flink 00000000 Blink 00000000
Section Ref 1 Pfn Ref 8f Mapped Views 13
User Ref 14 WaitForDel 0 Flush Count 0
File Object 81847da0 ModWriteCount 0 System Views 0
Flags (90000a0) Image File HadUserReference Accessed
|
|->mapped as image
File: \WINDOWS\system32\ole32.dll
Segment @ e172eaa0
ControlArea 817ab818 BasedAddress 774e0000
Total Ptes 13d
WriteUserRef 0 SizeOfSegment 13d000
Committed 0 PTE Template 862a8c3a
Based Addr 774e0000 Image Base 0
Image Commit 7 Image Info e172efd0
ProtoPtes e172ead8
Subsection 1 @ 817ab848
ControlArea 817ab818 Starting Sector 0 Number Of Sectors 2
Base Pte e172ead8 Ptes In Subsect 1 Unused Ptes 0
Flags 11 Sector Offset 0 Protection 1
Subsection 2 @ 817ab868
ControlArea 817ab818 Starting Sector 2 Number Of Sectors 8f8
Base Pte e172eadc Ptes In Subsect 11f Unused Ptes 0
Flags 31 Sector Offset 0 Protection 3
Subsection 3 @ 817ab888
ControlArea 817ab818 Starting Sector 8fa Number Of Sectors 30
Base Pte e172ef58 Ptes In Subsect 6 Unused Ptes 0
Flags 31 Sector Offset 0 Protection 3
Subsection 4 @ 817ab8a8
ControlArea 817ab818 Starting Sector 92a Number Of Sectors 33
Base Pte e172ef70 Ptes In Subsect 7 Unused Ptes 0
Flags 51 Sector Offset 0 Protection 5
Subsection 5 @ 817ab8c8
ControlArea 817ab818 Starting Sector 95d Number Of Sectors c
Base Pte e172ef8c Ptes In Subsect 2 Unused Ptes 0
Flags 11 Sector Offset 0 Protection 1
Subsection 6 @ 817ab8e8
ControlArea 817ab818 Starting Sector 969 Number Of Sectors 69
Base Pte e172ef94 Ptes In Subsect e Unused Ptes 0
Flags 11 Sector Offset 0 Protection 1
It's comfortable to present results inside table. I have used PETools and we can see that ole32 cjntains five sections and first is reserved for the header.
- We can see that subsections 2-3, which are mapped to PE sections .text and .orpc are executable, i. e. adress PPTE with code sections. Fourth subsection belongs to global data and has copy-on-write protection. Other are using only for read access.
- Second subsection describes first section inside PE file with executable code. In fact, it begins from second sector (400 / SECTOR_SIZE == 2). Virtual address of section is 0x11ef5e, i. e. with rounding to multiple page size 0x11ef5e + 0xA2 = 0x11F000 / PAGE_SIZE = 0x11F, i. e. number of PTEs in subsection. Raw size in header is 0x11f000 / 0x200 = 0x8F8, i. e. number of sectors in subsection.
- Third section that also contains an executable code and begins from sector 0x11F400 / 0x200 = 0x8FA. Size is 0x6000 (in this case we take physical, because it larger than virtual, i. e. 0x6000/0x1000 = 6 PTEs.
- Forth subsection begins 0x125400 / 0x200 = 0x92A, size 0x7000 / 0x1000 = 7 PTEs.
- Fifth 0x12BA00 / 0x200 = 0x95D, size 0x2000 / 0x1000 = 2 PTEs.
- Sixth 0x12D200 / 0x200 = 0x969, 0xE000 / 0x1000 = 0xE PTEs.
As final step, let's check pointed above formula at practice. Take 3rd subsection, which describes section of ole32.dll starting from offset (in sectors) 0x8fa.
> dt _subsection 817ab888 SubsectionBase
nt!_SUBSECTION
+0x010 SubsectionBase : 0xe172ef58 _MMPTE
Get content of the first PTE that describes this section.
0: kd> dd 0xe172ef58 l1
e172ef58 0c779121
It is valid, than.
0: kd> !pfn c779
PFN 0000C779 at address 8112B358
flink 000006E7 blink / share count 00000007 pteaddress E172EF58
reference count 0001 Cached color 0
restore pte 862A8C62 containing page 00B8A9 Active P
Shared
862A8C62 = 1 00001100010101010001 1 00011 0001 0;
000011000101010100010001 = C5511 * 8 + 81181000 = 817AB888, this is an address of our subsection.
Now, using pointer to PPTE, get an offset inside file that it describes with help of formula.
(((PUCHAR)Pte - (PUCHAR)Subsection->SubsectionBase) / 4) << 12 + Subsection->StartingSector * SECTOR_SIZE.
(E172EF58 - 0xE172EF58) = 0 + 8fa * 200 = 11F400, that we can find in our table above.