What is a Proto-PTE and how Windows VMM works with it

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
Let's talk about each of them more detailed.
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.


 typedef struct _SECTION
{
      struct _RTL_BALANCED_NODE SectionNode;
      UINT64 StartingVpn; //starting virtual page number of mapping
      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
      {
            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
{
      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;      

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.

Article Link: https://artemonsecurity.blogspot.com/2018/10/what-is-proto-pte-and-how-windows-vmm.html