There is no any way to get an executable stack or heap (without VirtualProtect) in a 64-bit program on Windows 11. I went about trying to disable DEP and NX on an executable before I knew that. Spoiler, it does not work.
If you want to follow along you'll need Visual Studio or the Visual Studio Build Tools installed for the the developer command prompt.
Porting some (not affiliated) pwn.college shellcode exercises to Windows right now. These challenges take some shellcode, place it in a stack variable, and run it. I initially thought the linker option /NXCOMPAT:NO would give an executable stack. When it did not I hit it with the old VirtualProtect, made the memory EXECUTE_READWRITE, and moved on with my life.
Eventually I got curious whether I could get an executable stack and dove a little deeper. That also led to questions about an executable heap. It turns out that DEP is always enforced on 64-bit Windows no matter what.
Some documentation talked about the system DEP policy, so I started there. You need to run Command Prompt as an Administrator then check with bcdedit.
C:\Windows\System32>bcdedit Windows Boot Manager -------------------- identifier {bootmgr} device partition=\Device\HarddiskVolume1 path \EFI\Microsoft\Boot\bootmgfw.efi description Windows Boot Manager locale en-US inherit {globalsettings} default {current} resumeobject {9378e909-ec67-49fd-ab7b-46c2525300e3} displayorder {current} toolsdisplayorder {memdiag} timeout 30 Windows Boot Loader ------------------- identifier {current} device partition=C: path \Windows\system32\winload.efi description Windows 11 locale en-US inherit {bootloadersettings} recoverysequence {9378e909-ec67-49fd-ab7b-46c2525300e3} displaymessageoverride Recovery recoveryenabled Yes isolatedcontext Yes allowedinmemorysettings 0x15000075 osdevice partition=C: systemroot \Windows resumeobject {9378e909-ec67-49fd-ab7b-46c2525300e3} nx OptIn bootmenupolicy Standard
Note the nx OptIn line. Using bcdedit /set nx OptOut works. Due to Secure Boot through, I cannot set it to AlwaysOff. Secure Boot ruins all the fun.
C:\Windows\System32>bcdedit /set nx alwaysoff An error has occurred setting the element data. The value is protected by Secure Boot policy and cannot be modified or deleted.
Might as well switch Secure Boot off in BIOS and set NX to AlwaysOff. This requires a couple of reboots.
C:\Windows\System32>bcdedit /set nx alwaysoff The operation completed successfully.
There is also the Exploit Protections menu. Since Windows messed around with Control Panel (perfect) and added Settings (awful), which is kind of like Control Panel Lite, who knows what this changes. Let's set DEP to off though.
I also added a program specific override.
What clued me off that this might just be impossible is when I came across the API SetProcessDEPPolicy. The documentation reads: If the DEP system policy is OptIn or OptOut and DEP is enabled for the process, setting dwFlags to 0 disables DEP for the process. Let me tell you though, it does not.
Once I read those docs in more detail, I saw If this function is called on a 64-bit process, it fails with ERROR_NOT_SUPPORTED.
Once I read that I started to believe my efforts had been in vain (they had), so I went searching a little more. This support article states In 64-bit versions of Windows, hardware-enforced DEP is always enabled for 64-bit native programs. However, depending on your configuration, hardware-enforced DEP may be disabled for 32-bit programs. So it looks like all the DEP and NX options only apply to 32-bit processes.
First, I would like to apologize. I just started this company and cannot afford syntax highlighting at this time.
This program queries a pointer for its memory protections and prints them out. It declares a stack pointer and a heap pointer and sets them up with a nop sled (0x90) and return (0xC3). Note, x86/64 opcodes, will not work on ARM. The program then prints out their memory protections.
Following that, we crash our program by casting our memory regions as function pointers and calling them. The commented out VirtualProtect calls are there so you can verify that these do run when the stack and heap regions are EXECUTE_READWRITE. Finally, there is a cute little print statement so we can see if the regions ran to completion.
#include <windows.h> #include <stdio.h> void PrintMemoryProtection(DWORD protect) { switch (protect) { case PAGE_EXECUTE: printf("Execute\n"); break; case PAGE_EXECUTE_READ: printf("Execute, Read\n"); break; case PAGE_EXECUTE_READWRITE: printf("Execute, Read, Write\n"); break; case PAGE_EXECUTE_WRITECOPY: printf("Execute, Write Copy\n"); break; case PAGE_NOACCESS: printf("No Access\n"); break; case PAGE_READONLY: printf("Read Only\n"); break; case PAGE_READWRITE: printf("Read, Write\n"); break; case PAGE_WRITECOPY: printf("Write Copy\n"); break; case PAGE_GUARD: printf("Guard Page\n"); break; case PAGE_NOCACHE: printf("No Cache\n"); break; case PAGE_WRITECOMBINE: printf("Write Combine\n"); break; default: printf("Unknown protection\n"); break; } } int main() { // Not applicable, 32-bit only SetProcessDEPPolicy(0); MEMORY_BASIC_INFORMATION mbi; BYTE stack_ptr[0x100]; PBYTE heap_ptr = (PBYTE)malloc(0x100); for (int i=0; i<0x100; i++) { stack_ptr[i] = 0x90; // nop } stack_ptr[0xFF] = 0xC3; // ret for (int i=0; i<0x100; i++) { heap_ptr[i] = 0x90; // nop } heap_ptr[0xFF] = 0xC3; // ret if (VirtualQuery(stack_ptr, &mbi, sizeof(mbi)) != 0) { printf("Memory Protection of %p (stack): ", stack_ptr); PrintMemoryProtection(mbi.Protect); } else { printf("Error getting memory protection\n"); } if (VirtualQuery(heap_ptr, &mbi, sizeof(mbi)) != 0) { printf("Memory Protection of %p (heap): ", heap_ptr); PrintMemoryProtection(mbi.Protect); } else { printf("Error getting memory protection\n"); } DWORD oldProtect; // VirtualProtect(stack_ptr, 0x100, PAGE_EXECUTE_READWRITE, &oldProtect); ((void (*)())stack_ptr)(); // VirtualProtect(heap_ptr, 0x100, PAGE_EXECUTE_READWRITE, &oldProtect); ((void (*)())heap_ptr)(); printf("ExeCUTEd!\n"); free(heap_ptr); return 0; }
We will be compiling with cl test.c /link /NXCOMPAT:NO /DYNAMICBASE:NO /HIGHENTROPYVA:NO. Turning off ASLR does work for 64-bit processes.
First, if we compile a plain executable with cl test.c, then run dumpbin /HEADERS text.exe we see the following DLL Characteristics.
8160 DLL characteristics High Entropy Virtual Addresses Dynamic base NX compatible Terminal Server Aware
Recompiling without NX compatibility and ASLR like so, cl test.c /link /NXCOMPAT:NO /DYNAMICBASE:NO /HIGHENTROPYVA:NO, we can see that the the compiler recognizes them.
C:\Users\user\Documents\Development\pwncollege-win\shellcode-injection>cl test.c /link /NXCOMPAT:NO /DYNAMICBASE:NO /HIGHENTROPYVA:NO Microsoft (R) C/C++ Optimizing Compiler Version 19.36.32537 for x64 Copyright (C) Microsoft Corporation. All rights reserved. test.c Microsoft (R) Incremental Linker Version 14.36.32537.0 Copyright (C) Microsoft Corporation. All rights reserved. /out:test.exe /NXCOMPAT:NO /DYNAMICBASE:NO /HIGHENTROPYVA:NO test.obj
That is confirmed with another dumpbin /HEADERS test.exe
8000 DLL characteristics Terminal Server Aware
The moment everyone has been waiting for! We run the program... and it crashes.
C:\Users\user\Documents\Development\pwncollege-win\shellcode-injection>test.exe Memory Protection of 000000000014FDD0 (stack): Read, Write Memory Protection of 00000000004A4080 (heap): Read, Write C:\Users\user\Documents\Development\pwncollege-win\shellcode-injection>echo %errorlevel% -1073741819
The memory is still only RW and we do not reach our post-call print statement. That error code is our dear friend STATUS_ACCESS_VIOLATION 0xc0000005. We can comment and uncomment the different run stack, run heap, and virtual protect combinations to validate we are not slowly losing our mind.
Overall a negative result for what I was attempting, but a positive result for security. It took a decent amount of fiddling with settings before I found the doc that explained that this was just the way of the world now.
While it makes it impossible to develop an educational 64-bit stack smash on Windows, it is for the best. This makes our computers more secure and our bugs more valuable.