AD2016: MarkImageAsNXCompatible¶
Summary¶
| Property | Value |
|---|---|
| ID | AD2016 |
| Name | MarkImageAsNXCompatible |
| Category | Security |
| Severity | Error |
| Applies to | PE (Windows) |
Description¶
Binaries should be marked as NX compatible to help prevent execution of untrusted data as code. The NXCompat bit, also known as "Data Execution Prevention" (DEP) or "Execute Disable" (XD), triggers a processor security feature that allows a program to mark a piece of memory as non-executable.
This helps mitigate memory corruption vulnerabilities by preventing an attacker from supplying direct shellcode in their exploit.
How It Works¶
The rule checks for the IMAGE_DLLCHARACTERISTICS_NX_COMPAT flag (0x0100) in the PE optional header's DLL characteristics field.
When set, Windows enforces DEP for the process, preventing code execution from data pages (stack, heap, etc.).
Why This Matters¶
Data Execution Prevention (DEP) represents one of the most fundamental security improvements in computing history. It transforms exploitation from a straightforward exercise into a complex multi-step process.
The Pre-DEP Era¶
Before DEP (hardware NX bit), exploitation was straightforward:
1. Find buffer overflow
2. Overwrite return address with stack address
3. Place shellcode on stack
4. Control achieved
Success rate: ~100%
Skill required: Moderate
Shellcode could be placed anywhere in memory and executed directly.
How DEP Changed Exploitation¶
With DEP enforced, the stack, heap, and data sections are non-executable:
1. Find buffer overflow
2. Can't execute stack shellcode (DEP blocks it)
3. Must use Return-Oriented Programming (ROP)
4. Chain together existing code gadgets
5. Eventually call VirtualProtect or similar
6. Finally execute payload
Success rate: Lower, more fragile
Skill required: Significantly higher
Hardware vs Software DEP¶
| Type | Implementation | Protection |
|---|---|---|
| Software DEP | SEH validation only | Limited |
| Hardware DEP (NX/XD) | CPU-enforced | Comprehensive |
The NXCompat flag enables hardware DEP, which the CPU enforces on every memory access.
The NX Bit¶
Modern CPUs have a hardware "No eXecute" bit (called NX on AMD, XD on Intel):
- Each memory page has an NX bit in its page table entry
- CPU checks this bit on every instruction fetch
- Attempting to execute from an NX page triggers an exception
- Cannot be bypassed through software tricks
Exploit Technique Evolution¶
DEP forced exploit developers to invent new techniques:
| Era | Technique | Complexity |
|---|---|---|
| Pre-DEP | Direct shellcode | Simple |
| Early DEP | Return-to-libc | Moderate |
| Modern | ROP chains | Complex |
| Current | JIT-ROP, data-only | Very complex |
Why Some Binaries Lack NXCompat¶
Legitimate reasons are rare:
- JIT compilers: Must generate and execute code at runtime
- Self-modifying code: Some protection/obfuscation schemes
- Very old toolchains: Pre-Vista compilers didn't set the flag
None of these apply to typical applications.
Performance Considerations¶
DEP has zero performance overhead:
- The NX bit is checked by hardware on every instruction fetch anyway
- No additional instructions or checks needed
- Memory usage is identical
Resolution¶
Enable DEP¶
Linker flag:
This is enabled by default in modern Visual Studio.
Project Properties¶
- Open Project Properties
- Navigate to Linker → Advanced
- Set "Data Execution Prevention (DEP)" to "Yes (/NXCOMPAT)"
CMake¶
Remove /NXCOMPAT:NO¶
Search for any disabled DEP settings:
When to Suppress¶
This rule should rarely be suppressed. Exceptions might include:
- JIT compilers: Must explicitly manage executable memory
- Self-modifying code: Very specific legacy scenarios
- Old driver models: Some kernel code requirements
Caveats¶
- JIT compilers need to use
VirtualAllocwithPAGE_EXECUTE_READ - Plugins or extensions must also be NX compatible
- Mixing NX and non-NX modules can be problematic
Proper JIT Compilation¶
If you need to generate code at runtime:
// Allocate non-executable memory for writing
void* code = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
// Write your code
memcpy(code, machineCode, size);
// Change to executable (non-writable)
DWORD oldProtect;
VirtualProtect(code, size, PAGE_EXECUTE_READ, &oldProtect);
// Now execute
DEP Process Policies¶
| Policy | Description |
|---|---|
| AlwaysOn | DEP enabled, cannot be disabled |
| AlwaysOff | DEP disabled (insecure) |
| OptIn | DEP for system binaries only |
| OptOut | DEP for all except opt-out |
Modern Windows uses AlwaysOn for 64-bit processes.