Hardware supported NX is dependent on PAE (Windows Internals chapter 9. Memory). But why would that be?

The AMD64 Architecture Programmer's Manual (Volume 2: System Programming) mentions this:

No Execute (NX) Bit. Bit 63. This bit is present in the translation-table entries defined for PAE paging, with the exception that the legacy-mode PDPE does not contain this bit. This bit is not supported by non-PAE paging.

Again from Windows Internals we know that PTEs with PAE enabled are 64 bits long; without PAE they are only 32 bits long. There simply is no bit 63 for non-PAE paging. We can see it in the debugger.

Here is a call stack from the non-PAE crash dump:

0: kd> k

ChildEBP RetAddr

8292dc0c 968a1160 nt!KeBugCheckEx+0x1e

8292dc3c 968a1768 i8042prt!I8xProcessCrashDump+0x251

8292dc88 82840d4d i8042prt!I8042KeyboardInterruptService+0x2ce

8292dc88 8286449a nt!KiInterruptDispatch+0x6d

8292dd24 00000000 nt!KiIdleLoop+0x1a

 

If we dump out the the ChildEBP for the 2nd frame:

0: kd> !pte 8292dc3c

VA 8292dc3c

PDE at C0300828 PTE at C020A4B4

contains 001BF063 contains 0292D963

pfn 1bf ---DA--KWEV pfn 292d -G-DA—KWEV

 

You see that the PTE contains a 32 bit value (0292D963).

The same exercise on a PAE enabled system gives us this call stack:

1: kd> k

ChildEBP RetAddr

807e1c0c 928bd160 nt!KeBugCheckEx+0x1e

807e1c3c 928bd768 i8042prt!I8xProcessCrashDump+0x251

807e1c88 828587cd i8042prt!I8042KeyboardInterruptService+0x2ce

807e1c88 8288101a nt!KiInterruptDispatch+0x6d

807e1d24 00000000 nt!KiIdleLoop+0x1a

 

The PTE here is 64 bit:

1: kd> !pte 807e1c3c

VA 807e1c3c

PDE at C0602018 PTE at C0403F08

contains 000000007A986863 contains 800000002D21F963

pfn 7a986 ---DA--KWEV pfn 2d21f -G-DA--KW-V

 

We would expect bit 63 on the ChildEBP to be set (EBP is on the stack and we would not want to execute any code from the stack: NX should be 1).

1: kd> .formats 800000002D21F963

Evaluate expression:

Hex: 80000000`2d21f963

Decimal: -9223372036097574557

Octal: 1000000000005510374543

Binary: 10000000 00000000 00000000 00000000 00101101 00100001 11111001 01100011

 

How about the return address of that same frame?

1: kd> !pte 928bd768

VA 928bd768

PDE at C06024A0 PTE at C04945E8

contains 0000000023615863 contains 000000007B9CD121

pfn 23615 ---DA--KWEV pfn 7b9cd -G--A—KREV

 

There the bit is not set:

1: kd> .formats 000000007B9CD121

Evaluate expression:

Hex: 7b9cd121

Decimal: 2073874721

Octal: 17347150441

Binary: 01111011 10011100 11010001 00100001

 

Note that .formats truncates the preceding 0s.