<script> // policy issue hide_googlead(); </script>
I'd like to first point out that this is a speculative method to evade hardware-enforced DEP based on various documentation. There is not yet a proof-of-concept, but this does not mean there is not a vulnerability. I will make a short blog if and when a POC is available, or if it turns out that I was wrong in my analysis.
This method applies to any system where proper protections on memory can prevent it from being executable, whether by hardware facilities or software emulation, if and only if those systems do not employ appropriate countermeasures such as memory protection restrictions (
VirtualProtect()) or Address Space Layout Randomization.
This means that systems such as PaX, Exec Shield, and W^X are not vulnerable. PaX supplies high quality ASLR and
mprotect() restrictions on Linux; while Exec Shield and W^X both supply ASLR for shared libraries at least. This technique still applies if certain information leaks (/proc/[pid]/maps) are not obscured, however.
The original problem that deploying these memory protections was meant to solve is shellcode injection. Some vulnerabilities, such as those in US-CERT Technical Alerts TA04-315A, TA04-260A, and TA04-293A lead to arbitrary code execution. While in these cases upgrading to Service Pack 2 brings fixes to Internet Explorer, future vulnerabilites similar to these will not be protected by DEP itself.
There are two reasons why DEP can be exploited. First, the
VirtualProtect() function can still be called with any protecitons. There is no restriction at the time of this writing to
VirtualProtect(), and so arbitrary memory can be made executable, or executable and writable.
Second, there is also ASLR, which makes locating the address of the
VirtualProtect() function both easy and reliable. Even if
VirtualProtect() were restricted properly,
CreateFileMapping() and other functions could be used with
write() to simply write the data to a file and map it in as executable data.
memcpy() could be used, since "VirtualAlloc can commit [(allocate)] an already committed page." It will seriously corrupt memory, but this is already a memory corruption attack so who cares?
To explain this exploit, we'll start with a normal proof-of-concept overflow. eEye Digital Security discovered a vulnerability in USER32.dll allowing animated cursor files to cause a buffer overflow and execute arbitrary code. A proof-of-concept was later released by Assaf Reshef to demonstrate this vulnerability.
This proof-of-concept falls in a class that would be stopped by DEP. It uses a buffer overflow to inject code into the stack and modify the return pointer to execute that code. Upon execution, the CPU raises a Segmentation Fault because the memory area is not executable. Thus, Windows is able to stop this exploit on Service Pack 2 on supporting processors.
Below is explained a hypothetical modification to the above cited proof-of-concept exploit for this particular overflow. The exploit as described below has not been written or tested, and is purely theoretical.
The process can be modified to inject a modified sest of data during the overflow. This data would contain a modified stack frame pointer, return pointer, a stack frame, and a block of payload shellcode, as shown below.
[SFPT][RETP][STACK FRAME][STACK FRAME2][SHELLCODE]
The SFPT would point at the STACK FRAME, and RETP would point to
VirtualAlloc(). The STACK FRAME would have a return pointer to SHELLCODE, and appropriate layout for a call to
VirtualAlloc() as shown below.
VirtualAlloc(REMOTE_BASE, SHELLCODE_LENGTH, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
Upon RET from the overflowed function, the above call to
VirtualAlloc() would be made to allocate an area big enough for the shellcode with protecitons PAGE_EXECUTE_READWRITE. This would leave the area readable, writable, and executable, all at the same time. Because
VirtualAlloc() will allocate overtop of already allocated memory, REMOTE_BASE need only to be some remote address not near
memcpy(), or the injected stack frames and shellcode.
Because the stack frame for the call to
VirtualAlloc() was part of the initial overflow, the attacker has complete control of its contents. The return pointer in the stack frame therefore should point to
memcpy(), with a proper pointer to STACK FRAME2. This means that, upon RET,
memcpy() is executed. It should be executed as shown below.
memcpy(REMOTE_BASE, SHELLCODE_BASE, SHELLCODE_LENGTH);
This copies SHELLCODE into the newly allocated area of memory. Again, the attacker has complete control over the stack frame. On RET, SHELLCODE is returned to. This causes SHELLCODE to execute.
When SHELLCODE is executed at the end of this process, it has been copied to a newly created executable area by existing code supplied by the Windows operating system. This means, as stated above, that SHELLCODE can safely be executed without DEP interfering. This attack method should be plausible for any attack in which shellcode is injected, and is compatible with older, non-DEP Microsoft Windows systems as well.
Note that the original overflow string must not contain NULL characters in buffer overflows involving
strcpy() and related functions. This is because the string will end there and not be copied to the stack. Access to ASCII armored areas (addresses containing a NULL byte) will not normally be possible, although there may be ways to load the heap with prepared data, such as by loading certain data files or running certain scripts.
The NULL byte dilema may be evadable if a UUE, Base64, or MIME decoding function is available, and does not start at an ASCII armored address. In these cases, the first return can be a return-to-
UUDecode() and can decode the rest of the attack, then continue with it. The
UUDecode() address and stack frame must not contain any NULL bytes for this to work.
In conclusion, Microsoft's Hardware DEP protection does not prevent future exploits from being successful; it only adds a trivial amount of complexity to the attack. I believe that any attacker able to create the exploit as it would normally work will be able to handle the less complex task of incorporating a return-to-
memcpy() attack into the process. This could only be properly protected against by incorporating Address Space Layout Randomization into the protection scheme.