Inconsistent memory write access behavior

I’m observing inconsistent behavior with memory write access and I’m not quite sure why.

I’m modifying images as they load from a work item after the process has been suspended. I recognize many on here may have issues with the approach but put that aside for now. For the primary executable and for libraries that are loaded by an APC via LdrLoadDll, this works fine. It’s only when libraries load normally that that modifications fail.

All the images are marked EXECUTE_WRITECOPY which is expected. The DLLs have the MMVAD.u.VadFlags.NoChange bit set to 1. Trying to modify the protection of the memory with ZwProtectVirtualMemory returns 0xC0000045 (STATUS_INVALID_PAGE_PROTECTION) which according to the ReactOS code, is what happens when that bit is set.

Below are three stack traces at PsImageLoadCallback along with the VAD printouts of the image base addresses. Is there another flag somewhere that I’m missing that says even EXECUTE_WRITECOPY are actually not-writable?

I’m testing this on Windows 10 x64 (10240) VM.

-- EXE (CAN MODIFY) --
01 ffffd000`23fba6b0 fffff803`8432c00f nt!PsCallImageNotifyRoutines+0x13c
02 ffffd000`23fba710 fffff803`8432bcfb nt!DbgkCreateThread+0x15b
03 ffffd000`23fba950 fffff803`83fc6e46 nt!PspUserThreadStartup+0x9f
04 ffffd000`23fba9c0 fffff803`83fc6dc0 nt!KiStartUserThread+0x16
05 ffffd000`23fbab00 00007ff9`f9939f30 nt!KiStartUserThreadReturn

0: kd> !vad 0x00007ff7`bac30000 1

VAD @ ffffe0000a7b5a40
  Start VPN            7ff7bac30  End VPN        7ff7bac3d  Control Area  ffffe00009a38b50
  FirstProtoPte ffffc001b3bbde70  LastPte ffffc001b3bbded8  Commit Charge                f (0n15)
  Secured.Flink                0  Blink                  0  Banked/Extend                0
  File Offset                  0  
      ImageMap ViewShare EXECUTE_WRITECOPY 

ControlArea  @ ffffe00009a38b50
  Segment      ffffc001b4f5ac70  Flink      ffffe0000a7b5aa0  Blink        ffffe0000a7b5aa0
  Section Ref                 1  Pfn Ref                   e  Mapped Views                1
  User Ref                    2  WaitForDel                0  Flush Count              8d50
  File Object  ffffe000091f7f20  ModWriteCount             0  System Views             ffff
  WritableRefs         c0000001  PartitionId                0  
  Flags (a0) Image File 

Segment @ ffffc001b4f5ac70
  ControlArea       ffffe00009a38b50  BasedAddress  00007ff7bac30000
  Total Ptes                       e
  Segment Size                  e000  Committed                    0
  Image Commit                     1  Image Info    ffffc001b4f5acb8
  ProtoPtes         ffffc001b3bbde70
  Flags (820000) ProtectionMask 
-- DLL (CAN MODIFY) --
01 ffffd000`285b4680 fffff803`8432e7fa nt!PsCallImageNotifyRoutines+0x13c
02 ffffd000`285b46e0 fffff803`8427bc2d nt!MiMapViewOfImageSection+0x68a
03 ffffd000`285b4830 fffff803`842817e1 nt!MiMapViewOfSection+0x33d
04 ffffd000`285b49a0 fffff803`83fcc263 nt!NtMapViewOfSection+0x2e1
05 ffffd000`285b4a90 00007ff9`f99c37ca nt!KiSystemServiceCopyEnd+0x13
06 000000bd`354be8e8 00007ff9`f997070e ntdll!NtMapViewOfSection+0xa
07 000000bd`354be8f0 00007ff9`f9970255 ntdll!LdrpMapViewOfSection+0xbe
08 000000bd`354be990 00007ff9`f9970125 ntdll!LdrpMapImage+0x75
09 000000bd`354bea30 00007ff9`f996efd8 ntdll!LdrpMapDllWithSectionHandle+0x2d
0a 000000bd`354bea70 00007ff9`f9972d37 ntdll!LdrpMapDllNtFileName+0x130
0b 000000bd`354beb40 00007ff9`f9968e4c ntdll!LdrpMapDllFullPath+0xcb
0c 000000bd`354becc0 00007ff9`f9950911 ntdll!LdrpProcessWork+0x60
0d 000000bd`354bed10 00007ff9`f99505ca ntdll!LdrpLoadDllInternal+0x14d
0e 000000bd`354bed90 00007ff9`f994af86 ntdll!LdrpLoadDll+0xf2
0f 000000bd`354bef30 000000bd`354f002a ntdll!LdrLoadDll+0x96

0: kd> !vad 0x00007ffa`6085c000 1

VAD @ ffffe001fd9fcf70
  Start VPN            7ffa60850  End VPN        7ffa60861  Control Area  ffffe001ffbc7ba0
  FirstProtoPte ffffc00200594820  LastPte ffffc002005948a8  Commit Charge                3 (0n3)
  Secured.Flink                0  Blink                  0  Banked/Extend                0
  File Offset                  0  
      ImageMap ViewShare NoChange EXECUTE_WRITECOPY 

ControlArea  @ ffffe001ffbc7ba0
  Segment      ffffc00202af86d0  Flink      ffffe001fd9fcfd0  Blink        ffffe002007bdd40
  Section Ref                 1  Pfn Ref                  10  Mapped Views                9
  User Ref                    a  WaitForDel                0  Flush Count              7da0
  File Object  ffffe001fce3bc40  ModWriteCount             0  System Views             9f77
  WritableRefs         c0000002  PartitionId                0  
  Flags (a0) Image File 

Segment @ ffffc00202af86d0
  ControlArea       ffffe001ffbc7ba0  BasedAddress  00007ffa60850000
  Total Ptes                      12
  Segment Size                 12000  Committed                    0
  Image Commit                     3  Image Info    ffffc00202af8718
  ProtoPtes         ffffc00200594820
  Flags (820000) ProtectionMask 
-- DLL (CANNOT MODIFY) --
01 ffffd000`299b7680 fffff803`8432e7fa nt!PsCallImageNotifyRoutines+0x13c
02 ffffd000`299b76e0 fffff803`8427bc2d nt!MiMapViewOfImageSection+0x68a
03 ffffd000`299b7830 fffff803`842817e1 nt!MiMapViewOfSection+0x33d
04 ffffd000`299b79a0 fffff803`83fcc263 nt!NtMapViewOfSection+0x2e1
05 ffffd000`299b7a90 00000000`4a54001a nt!KiSystemServiceCopyEnd+0x13
06 000000bd`f49ce918 00007ff6`7d5d2af1 0x4a54001a
07 000000bd`f49ce920 00007ff9`f6e70aeb +0x581
08 000000bd`f49cf040 00007ff9`f6e70a0e KERNELBASE!MapViewOfFileExNuma+0xcb
09 000000bd`f49cf0c0 00007ff6`7d5c1fbf KERNELBASE!MapViewOfFile+0x1e

1: kd> !vad 0x00007ffa`28200000 1

VAD @ ffffe002003fa250
  Start VPN            7ffa28200  End VPN        7ffa28429  Control Area  ffffe0020036c010
  FirstProtoPte ffffc001ff24d000  LastPte ffffc001ff24e148  Commit Charge                7 (0n7)
  Secured.Flink                0  Blink                  0  Banked/Extend                0
  File Offset                  0  
      ImageMap ViewShare NoChange EXECUTE_WRITECOPY 

ControlArea  @ ffffe0020036c010
  Segment      ffffc001ffeeef70  Flink      ffffe002003fa2b0  Blink        ffffe002002e7fd0
  Section Ref                 1  Pfn Ref                 227  Mapped Views                2
  User Ref                    3  WaitForDel                0  Flush Count              c248
  File Object  ffffe001fd3a7290  ModWriteCount             0  System Views             d7bb
  WritableRefs         c0000023  PartitionId                0  
  Flags (a0) Image File 

Segment @ ffffc001ffeeef70
  ControlArea       ffffe0020036c010  BasedAddress  00007ffa28200000
  Total Ptes                     22a
  Segment Size                22a000  Committed                    0
  Image Commit                     7  Image Info    ffffc001ffeeefb8
  ProtoPtes         ffffc001ff24d000
  Flags (820000) ProtectionMask 

Hey,
Can you show the code that calls ZwProtectVirtualMemory?

Anyway, I’m pretty sure you’re aware this is a dangerous design… I don’t know if you care about Windows 7, but just so you know - I think that this will just cause a deadlock in Windows 7 - From MSDN:

In Windows 7, Windows Server 2008 R2, and earlier versions of Windows, the operating system holds an internal system lock during calls to load-image notify routines for images loaded in user process address space (user space). To avoid deadlocks, load-image notify routines must not call system routines that map, allocate, query, free, or perform other operations on user-space virtual memory.

Also, other than the fact that ZwProtectVirtualMemory is not documented, it’s not even exported in Windows 7 (Yeah you can get the address from SSDT but this becomes more annoying) - why are you trying to call this routine? Maybe there’s another way to achieve your goal…

EDIT: I misread the question - I thought you call ZwProtectVirtualMemory inside the image callback - so the deadlock issue is not relevant

The design and goals aren’t important. I was just making the point about ZwProtectVirtualMemory that I can’t change the protection flag when NoChange is set and I fully understand the limitations.

What I’m more interested with is why two memory sections with seemingly identical protection and VAD flags do not act the same way when you write to them. One succeeds and the other fails (BSOD if you aren’t wrapped in __try/__catch). If memory is marked as EXECUTE_WRITECOPY, you should be able to write to it but that’s not the case so something else is going on.