ROP
Introduction
This article covers techniques for bypassing Data Execution Prevention (DEP) using Return Oriented Programming (ROP) on Win32 systems. It explains the underlying concepts, the relevant Windows API functions, and details how to build a ROP chain for DEP bypass.
Hardware DEP in the Win32 World
Hardware DEP leverages the NX (No Execute) or XD (Execute Disable) bit on CPUs to mark certain memory regions (such as the default heap, stack, and memory pools) as non-executable. When code execution is attempted on these data pages, an access violation (STATUS_ACCESS_VIOLATION (0xc0000005)) occurs, often resulting in process termination.
DEP was introduced in Windows XP SP2 and Windows Server 2003 SP1. It works on a per-virtual memory page basis by setting flags in the Page Table Entries (PTEs). For DEP to function, the processor must run in Physical Address Extension (PAE) mode, which Windows enables by default.
DEP can be configured in one of four modes:
- OptIn: Only a limited set of Windows system modules/binaries are protected.
- OptOut: All programs, processes, and services are protected except those explicitly excluded.
- AlwaysOn: All programs, processes, and services are protected with no exceptions.
- AlwaysOff: DEP is disabled.
Additionally, “Permanent DEP” can be enabled via SetProcessDEPPolicy(PROCESS_DEP_ENABLE), which is automatically applied on Vista and later for executables linked with the /NXCOMPAT option.
Default settings for various Windows versions include:
- Windows XP SP2, XP SP3, Vista SP0: OptIn
- Windows Vista SP1: OptIn + Permanent DEP
- Windows 7: OptIn + Permanent DEP
- Windows Server 2003 SP1 and later: OptOut (with newer versions often including Permanent DEP)
DEP settings can be modified using boot parameters (for XP/2003) or the bcdedit command (for Vista/2008/7).
For further details:
Bypassing DEP – Building Blocks
When DEP is enabled, shellcode cannot be executed from the stack. The solution is to reuse existing executable code by constructing a chain of small code snippets—known as ROP gadgets—which together set up the conditions required for executing the payload.
The available approaches include:
- Ret-to-libc/Code Reuse: Reusing functions like
WinExecto execute commands. - Changing Memory Protection: Marking the memory page containing the shellcode as executable.
- Copying Shellcode: Transferring shellcode to a memory region that is already executable.
- Altering DEP Settings: Calling functions to modify the current process’s DEP policy.
Each technique depends on the DEP configuration (OptIn, OptOut, AlwaysOn, AlwaysOff) and may require different parameters and stack setups.
Windows API Functions for DEP Bypass
Several Windows API functions can be used in a ROP chain to bypass DEP:
VirtualAlloc()
Allocates memory with specified protection attributes. Its prototype is:
LPVOID WINAPI VirtualAlloc(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
After a successful call, the allocated address is returned (typically in EAX). On XP SP3, VirtualAlloc() is located at 0x7C809AF1 in kernel32.dll. Often, VirtualAlloc() is chained with another API (such as memcpy() or WriteProcessMemory()) to copy the shellcode into the newly allocated memory.
HeapCreate() and HeapAlloc()
These functions create a private heap and allocate memory from it. When HeapCreate() is used with the flag HEAP_CREATE_ENABLE_EXECUTE, memory allocated from that heap will be executable.
- HeapCreate is located at
0x7C812C56on XP SP3. - HeapAlloc is located at
0x7C8090F6on XP SP3.
SetProcessDEPPolicy()
This function changes the DEP policy for the current process (works on XP SP3, Vista SP1, and Windows 2008). It requires that DEP is set to OptIn or OptOut and can only be called once. Its prototype is:
BOOL WINAPI SetProcessDEPPolicy(DWORD dwFlags);
A ROP chain would set up the stack as follows:
- Pointer to SetProcessDEPPolicy()
- Pointer to the shellcode (for the return)
- A zero value (as the parameter)
On XP SP3, the address is 0x7C8622A4 in kernel32.dll.
NtSetInformationProcess()
This function alters DEP settings for the current process. It is typically used as follows:
NtSetInformationProcess(
NtCurrentProcess(), // (HANDLE)-1 (0xFFFFFFFF)
ProcessExecuteFlags, // 0x22
&ExecuteFlags, // pointer to 0x2
sizeof(ExecuteFlags) // 0x4
);
The call requires five parameters on the stack:
- Return address (where to jump after the call)
- NtCurrentProcess() value (0xFFFFFFFF)
- ProcessExecuteFlags (0x22)
- Pointer to a location holding 0x2
- Size of the value (0x4)
On XP SP3, NtSetInformationProcess() is at 0x7C90DC9E in ntdll.dll.
VirtualProtect()
Changes memory protection for a region of memory. Its prototype is:
BOOL WINAPI VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
For ROP, the following stack parameters are needed:
- Return address (usually the address of the shellcode)
lpAddress(the base address of the shellcode)dwSize(the size of the shellcode region)flNewProtect(for example, 0x40 forPAGE_EXECUTE_READWRITE)lpflOldProtect(a pointer to a writable memory location)
On XP SP3, VirtualProtect() is at 0x7C801AD4 in kernel32.dll.
WriteProcessMemory()
Allows copying of shellcode to a writable and executable location. Its prototype is:
BOOL WINAPI WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
It requires these parameters:
- Return address (after the function call)
hProcess(typically -1 or 0xFFFFFFFF for the current process)lpBaseAddress(destination address for the shellcode)lpBuffer(address of the shellcode on the stack)nSize(number of bytes to copy)lpNumberOfBytesWritten(pointer to a writable location)
On XP SP3, WriteProcessMemory() is located at 0x7C802213 in kernel32.dll.
ROP Exploit Transportability
ROP exploits often involve hardcoded pointers to functions in system DLLs. This can limit portability across different Windows versions. One way to improve portability is to use addresses from modules that are imported by the target application (and which may not be affected by ASLR). For example, in msvcr71.dll on XP SP3:
- HeapCreate, HeapAlloc, VirtualAlloc, and VirtualProtect functions might have addresses that can be used reliably.
From EIP to ROP
The process of gaining control over execution starts with exploiting a buffer overflow to overwrite the instruction pointer (EIP). In a typical direct RET exploit, EIP is overwritten with a pointer that jumps to the shellcode on the stack. However, when DEP is enabled, the shellcode on the stack cannot be executed directly. A ROP chain is needed instead.
Direct RET Exploits
With a direct RET, the overwritten EIP points to a RET instruction. The ROP chain is then executed by having each gadget’s RET jump to the next gadget.
SEH Based Exploits
For Structured Exception Handler (SEH) based exploits, the overwritten SEH record causes a controlled exception. A common technique is to perform a stack pivot—adjusting the stack pointer (ESP) to point to the controlled buffer containing the ROP chain. Techniques for pivoting include:
add esp, offset; retmov esp, register; retxchg register, esp; ret- Using
call registeror similar instructions
Direct RET – The ROP Version Using VirtualProtect()
When DEP prevents execution of shellcode on the stack, one solution is to build a ROP chain that calls VirtualProtect() to change the memory protection to executable. For example, in a vulnerable application with an overflow offset of 26,094 bytes, the exploit process is as follows:
- Overwrite EIP with a pointer that begins the ROP chain.
- Build a chain that sets up the parameters for VirtualProtect():
- Return address (address of shellcode)
lpAddress(address of the shellcode)dwSize(size covering the shellcode)flNewProtect(typically 0x40 forPAGE_EXECUTE_READWRITE)lpflOldProtect(address of a writable memory location)
- After VirtualProtect() successfully changes the memory protection, control is transferred to the shellcode.
Example ROP Chain Snippet
Consider a chain using two gadgets:
- Gadget 1:
POP EAX; RETat0x10026D56 - Gadget 2:
ADD EAX, 0x80; POP EBX; RETat0x1002DC24
The stack would be set up as follows:
| Stack Value | Description |
|---|---|
0x10026D56 | Address of POP EAX; RET (Gadget 1) |
0x50505050 | Value to be loaded into EAX |
0x1002DC24 | Address of ADD EAX,0x80; POP EBX; RET (Gadget 2) |
0xDEADBEEF | Padding value for EBX |
A pointer to a simple RET instruction is used to start the chain. By executing these gadgets in sequence, the registers are manipulated to generate the correct values for the VirtualProtect() parameters.
A sample Perl script snippet for this chain might look like:
my $buffersize = 26094;
my $junk = "A" x $buffersize;
my $eip = pack('V', 0x100102DC);
# pointer to RET
my $junk2 = "AAAA";
# padding to adjust ESP
my $rop = pack('V', 0x10026D56);
# POP EAX; RET (gadget 1)
$rop .= pack('V', 0x50505050);
# value to pop into EAX
$rop .= pack('V', 0x1002DC24);
# ADD EAX,0x80; POP EBX; RET (gadget 2)
$rop .= pack('V', 0xDEADBEEF);
# value for EBX (padding)
my $rest = "C" x 1000; my $payload = $junk . $eip . $junk2 . $rop . $rest;
This ROP chain, when executed, sets up the parameters for VirtualProtect() so that the shellcode becomes executable.
Finding ROP Gadgets
To build a ROP chain, useful gadgets must be identified. Two common approaches are:
- Search for specific instructions (e.g., POP, ADD) that are immediately followed by a RET instruction.
- Search for RET instructions and then examine preceding instructions to determine if they form a useful gadget.
For example, a gadget ending at 0x0040127F with a RET might be combined with the preceding instruction (such as an ADD or POP) to form a complete gadget. Tools like !pvefindaddr rop (available in Immunity Debugger) can automate the search for ROP gadgets across loaded modules. This tool also provides options to filter out gadgets from modules affected by ASLR.
In cases where a gadget includes a CALL register instruction before the RET, it is important to control the register’s value so that the call proceeds correctly.
Conclusion
This technical overview explained the following:
- DEP Fundamentals: How hardware DEP works on Windows and the significance of NX/XD.
- DEP Bypass Techniques: Various Windows API functions (VirtualAlloc, HeapCreate, SetProcessDEPPolicy, NtSetInformationProcess, VirtualProtect, WriteProcessMemory) that can be used to bypass DEP.
- ROP Concept: Building a ROP chain by chaining together small code snippets (gadgets) to set up the stack and registers for calling a Windows API.
- Practical ROP Chain Construction: Examples of stack layouts, ROP chain snippets, and strategies to pivot execution control to a controlled buffer.
This foundation provides the technical background necessary for developing ROP-based exploits to bypass DEP on Win32 systems.