|
Buffer overruns have long been recognized as a problem in low-level languages. The core problem is that user data and program flow control information are intermingled for the sake of performance, and low-level languages allow direct access to application memory. C and C++ are the two most popular languages afflicted with buffer overruns.
Strictly speaking, a buffer overrun occurs when a program allows input to write beyond the end of the allocated buffer, but there are several associated problems that often have the same effect. One of the most interesting is format string bugs. Another incarnation of the problem occurs when an attacker is allowed to write at an arbitrary memory location outside of an array in the application, and while, strictly speaking, this isn’t a classic buffer overrun, we’ll cover that here too.
The effect of a buffer overrun is anything from a crash to the attacker gaining complete control of the application, and if the application is running as a high-level user (root, administrator, or local system), then control of the entire operating system and any other users who are currently logged on, or will log on, is in the hands of the attacker. If the application in question is a network service, the result of the flaw could be a worm. The first well-known Internet worm exploited a buffer overrun in the finger server, and was known as the Robert T. Morris (or just Morris) finger worm. Although it would seem as if we’d have learned how to avoid buffer overruns since one nearly brought down the Internet in 1988, we continue to see frequent reports of buffer overruns in many types of software.
Although one might think that only sloppy, careless programmers fall prey to buffer overruns, the problem is complex, many of the solutions are not simple, and anyone who has written enough C/C++ code has almost certainly made this mistake. Even very good, very careful programmers make mistakes, and the very best programmers know how easy it is to slip up and put solid testing practices in place to catch errors.
Affected Languages
C is the most common language used to create buffer overruns, closely followed by C++. It’s easy to create buffer overruns when writing in assembler given it has no safeguards at all. Although C++ is inherently as dangerous as C, because it is a superset of C, using the Standard Template Library (STL) with care can greatly reduce the potential to mishandle strings. The increased strictness of the C++ compiler will help a programmer avoid some mistakes. Our advice is that even if you are writing pure C code, using the C++ compiler will result in cleaner code.
More recently invented higher-level languages abstract direct memory access away from the programmer, generally at a substantial performance cost. Languages such as Java, C#, and Visual Basic have native string types, bounds-checked arrays, and generally prohibit direct memory access. Although some would say that this makes buffer overruns impossible, it’s more accurate to say that buffer overruns are much less likely. In reality, most of these languages are implemented in C/C++, and implementation flaws can result in buffer overruns. Another potential source of buffer overruns in higher-level code exists because the code must ultimately interface with an operating system, and that operating system is almost certainly written in C/C++. C# enables you to perform without a net by declaring unsafe sections; however, while it provides easier interoperability with the underlying operating system and libraries written in C/C++, you can make the same mistakes you can in C/C++. If you primarily program in higher-level languages, the main action item for you is to continue to validate data passed to external libraries, or you may act as the conduit to their flaws.
Although we’re not going to provide an exhaustive list of affected languages, most older languages are vulnerable to buffer overruns
The Sin Explained
The classic incarnation of a buffer overrun is known as “smashing the stack.” In a compiled program, the stack is used to hold control information, such as arguments, where the application needs to return to once it is done with the function and because of the small number of registers available on x86 processors, quite often registers get stored temporarily on the stack. Unfortunately, variables that are locally allocated are also stored on the stack. These stack variables are sometimes inaccurately referred to as being statically allocated, as opposed to being dynamically allocated heap memory. If you hear someone talking about a static buffer overrun, what they really mean is a stack buffer overrun. The root of the problem is that if the application writes beyond the bounds of an array allocated on the stack, the attacker gets to specify control information. And this is critical to success; the attacker wants to modify control data to values of his bidding.
One might ask why we continue to use such an obviously dangerous system. We had an opportunity to escape the problem, at least in part, with a migration to Intel’s 64-bit Itanium chip, where return addresses are stored in a register. The problem is that we’d have to tolerate a significant backwards compatibility loss, and as of this writing, it appears that the x64 chip will likely end up the more popular chip.
You may also be asking why we just don’t all migrate to code that performs strict array checking and disallows direct memory access. The problem is that for many types of applications, the performance characteristics of higher-level languages are not adequate. One middle ground is to use higher-level languages for the top-level interfaces that interact with dangerous things (like users!), and lower-level languages for the core code. Another solution is to fully use the capabilities of C++, and use string libraries and collection classes. For example, the Internet Information Server (IIS) 6.0 web server switched entirely to a C++ string class for handling input, and one brave developer claimed he’d amputate his little finger if any buffer overruns were found in his code. As of this writing, the developer still has his finger and no security bulletins have been issued against the web server in the nearly two years since its release. Modern compilers deal well with templatized classes, and it is possible to write very high-performance C++ code.
Enough theory—let’s consider an example:
#include <stdio.h>
void DontDoThis(char* input)
{
char buf[16];
strcpy(buf, input);
printf("%s\n", buf);
}
int main(int argc, char* argv[])
{
//so we're not checking arguments
//what do you expect from an app that uses strcpy?
DontDoThis(argv[1]);
return 0;
}
Now let’s compile the application and take a look at what happens. For this demonstration, the author used a release build with debugging symbols enabled and stack checking disabled. A good compiler will also want to inline a function as small as DontDoThis, especially if it is only called once, so he also disabled optimizations. Here’s what the stack looks like on his system immediately prior to calling strcpy:
0x0012FEC0 c8 fe 12 00 eþ.. <- address of the buf argument
0x0012FEC4 c4 18 32 00 Ä.2. <- address of the input argument
0x0012FEC8 d0 fe 12 00 Ðþ.. <- start of buf
0x0012FECC 04 80 40 00 .<<Unicode: 80>>@.
0x0012FED0 e7 02 3f 4f ç.?O
0x0012FED4 66 00 00 00 f... <- end of buf
0x0012FED8 e4 fe 12 00 äþ.. <- contents of EBP register
0x0012FEDC 3f 10 40 00 ?.@. <- return address
0x0012FEE0 c4 18 32 00 Ä.2. <- address of argument to DontDoThis
0x0012FEE4 c0 ff 12 00 aÿ..
0x0012FEE8 10 13 40 00 ..@. <- address main() will return to
Remember that all of the values on the stack are backwards. This example is from an Intel system, which is “little-endian.” This means the least significant byte of a value comes first, so if you see a return address in memory as “3f104000,” it’s really address 0x0040103f.
Now let’s look at what happens when buf is overwritten. The first control information on the stack is the contents of the Extended Base Pointer (EBP) register. EBP contains the frame pointer, and if an off-by-one overflow happens, EBP will be truncated. If the attacker can control the memory at 0x0012fe00 (the off-by-one zeros out the last byte), the program jumps to that location and executes attacker-supplied code.
If the overrun isn’t constrained to one byte, the next item to go is the return address. If the attacker can control this value, and is able to place enough assembly into a buffer that they know the location of, you’re looking at a classic exploitable buffer overrun. Note that the assembly code (often known as shell code because the most common exploit is to invoke a command shell) doesn’t have to be placed into the buffer that’s being overwritten. It’s the classic case, but in general, the arbitrary code that the attacker has placed into your program could be located elsewhere. Don’t take any comfort from thinking that the overrun is confined to a small area.
Once the return address has been overwritten, the attacker gets to play with the arguments of the exploitable function. If the program writes to any of these arguments before returning, it represents an opportunity for additional mayhem. This point becomes important when considering the effectiveness of stack tampering countermeasures such as Crispin Cowan’s Stackguard, IBM’s ProPolice, and Microsoft’s /GS compiler flag.
As you can see, we’ve just given the attacker at least three ways to take control of our application, and this is only in a very simple function. If a C++ class with virtual functions is declared on the stack, then the virtual function pointer table will be available, and this can easily lead to exploits. If one of the arguments to the function happens to be a function pointer, which is quite common in any windowing system (for example, X Window System or Microsoft Windows), then overwriting the function pointer prior to use is an obvious way to divert control of the application.
Many, many more clever ways to seize control of an application exist than our feeble brains can think of. There is an imbalance between our abilities as developers and the abilities and resources of the attacker. You’re not allowed an infinite amount of time to write your application, but attackers may not have anything else to do with their copious spare time than figure out how to make your code do what they want. Your code may protect an asset that’s valuable enough to justify months of effort to subvert your application. Attackers spend a great deal of time learning about the latest developments in causing mayhem, and have resources like www.metasploit.com where they can point and click their way to shell code that does nearly anything they want while operating within a constrained character set.
If you try to determine whether something is exploitable, it is highly likely that you will get it wrong. In most cases, it is only possible to prove that something is either exploitable or that you are not smart enough (or possibly have not spent enough time) to determine how to write an exploit. It is extremely rare to be able to prove with any confidence at all that an overrun is not exploitable.
The point of this diatribe is that the smart thing to do is to just fix the bugs! There have been multiple times that “code quality improvements” have turned out to be security fixes in retrospect. This author just spent more than three hours arguing with a development team about whether they ought to fix a bug. The e-mail thread had a total of eight people on it, and we easily spent 20 hours (half a person-week) debating whether to fix the problem or not because the development team wanted proof that the code was exploitable. Once the security experts proved the bug was really a problem, the fix was estimated at one hour of developer time and a few hours of test time. That’s an incredible waste of time.
The one time when you want to be analytical is immediately prior to shipping an application. If an application is in the final stages, you’d like to be able to make a good guess whether the problem is exploitable to justify the risk of regressions and destabilizing the product.
It’s a common misconception that overruns in heap buffers are less exploitable than stack overruns, but this turns out not to be the case. Most heap implementations suffer from the same basic flaw as the stack—the user data and the control data are intermingled. Depending on the implementation of the memory allocator, it is often possible to get the heap manager to place four bytes of the attacker’s choice into the location specified by the attacker. The details of how to attack a heap are somewhat arcane. A recent and clearly written presentation on the topic, “Reliable Windows Heap Exploits” by Matthew “shok” Conover & Oded Horovitz, can be found at http://cansecwest.com/ csw04/csw04-Oded+Connover.ppt. Even if the heap manager cannot be subverted to do an attacker’s bidding, the data in the adjoining allocations may contain function pointers, or pointers that will be used to write information. At one time, exploiting heap overflows was considered exotic and hard—heap overflows are now some of the more frequent types of exploited errors.
Sinful C/C++
There are many, many ways to overrun a buffer in C/C++. Here’s what caused the Morris finger worm:
char buf[20];
gets(buf);
There is absolutely no way to use gets to read input from stdin without risking an overflow of the buffer—use fgets instead. Perhaps the second most popular way to overflow buffers is to use strcpy (see the previous example). This is another way to cause problems:
char buf[20];
char prefix[] = "http://";
strcpy(buf, prefix);
strncat(buf, path, sizeof(buf));
What went wrong? The problem here is that strncat has a poorly designed interface. The function wants the number of characters of available buffer, or space left, not the total size of the destination buffer. Here’s another favorite way to cause overflows:
char buf[MAX_PATH];
sprintf(buf, "%s - %d\n", path, errno);
It’s nearly impossible, except for in a few corner cases, to use sprintf safely. A critical security bulletin for Microsoft Windows was released because sprintf was used in a debug logging function. Refer to bulletin MS04-011 for more information (see the link in the “Other Resources” section).
Here’s another favorite:
char buf[32];
strncpy(buf, data, strlen(data));
So what’s wrong with this? The last argument is the length of the incoming buffer, not the size of the destination buffer!
Another way to cause problems is by mistaking character count for byte count. If you’re dealing with ASCII characters, these are the same, but if you’re dealing with Unicode, there are two bytes to one character. Here’s an example:
_snwprintf(wbuf, sizeof(wbuf), "%s\n", input);
The following overrun is a little more interesting:
bool CopyStructs(InputFile* pInFile, unsigned long count)
{
unsigned long i;
m_pStructs = new Structs[count];
for(i = 0; i < count; i++)
{
if(!ReadFromFile(pInFile, &(m_pStructs[i])))
break;
}
}
How can this fail? Consider that when you call the C++ new[] operator, it is similar to the following code:
ptr = malloc(sizeof(type) * count);
If the user supplies the count, it isn’t hard to specify a value that overflows the multiplication operation internally. You’ll then allocate a buffer much smaller than you need, and the attacker is able to write over your buffer. The upcoming C++ compiler in Microsoft Visual Studio 2005 contains an internal check to prevent this problem. The same problem can happen internally in many implementations of calloc, which performs the same operation. This is the crux of many integer overflow bugs: It’s not the integer overflow that causes the security problem; it’s the buffer overrun that follows swiftly that causes the headaches. But more about this in Sin 3.
Here’s another way a buffer overrun can get created:
#define MAX_BUF 256
void BadCode(char* input)
{
short len;
char buf[MAX_BUF];
len = strlen(input);
//of course we can use strcpy safely
if(len < MAX_BUF)
strcpy(buf, input);
}
This looks as if it ought to work, right? The code is actually riddled with problems. We’ll get into this in more detail when we discuss integer overflows in Sin 3, but first consider that literals are always of type signed int. An input longer than 32K will flip len to a negative number; it will get upcast to an int and maintain sign; and now it is always smaller than MAX_BUF, causing an overflow. A second way you’ll encounter problems is if the string is larger than 64K. Now you have a truncation error: len will be a small positive number. The main fix is to remember that size_t is defined in the language as the correct type to use for variables that represent sizes by the language specification. Another problem that’s lurking is that input may not be null-terminated. Here’s what better code looks like:
const size_t MAX_BUF = 256;
void LessBadCode(char* input)
{
size_t len;
char buf[MAX_BUF];
len = strlen(input);
//of course we can use strcpy safely
if(len < MAX_BUF)
strcpy(buf, input);
}
Related Sins
One closely related sin is integer overflows. If you do choose to mitigate buffer overruns by using counted string handling calls, or are trying to determine how much room to allocate on the heap, the arithmetic becomes critical to the safety of the application.
Format string bugs can be used to accomplish the same effect as a buffer overrun, but aren’t truly overruns. A format string bug is normally accomplished without overrunning any buffers at all.
A variant on a buffer overrun is an unbounded write to an array. If the attacker can supply the index of your array, and you don’t correctly validate whether it’s within the correct bounds of the array, a targeted write to a memory location of the attacker’s choosing will be performed. Not only can all of the same diversion of program flow happen, but also the attacker may not have to disrupt adjacent memory, which hampers any countermeasures you might have in place against buffer overruns.
Spotting the Sin Pattern
Here are the components to look for:
-
Input, whether read from the network, a file, or from the command line
-
Transfer of data from said input to internal structures
-
Use of unsafe string handling calls
-
Use of arithmetic to calculate an allocation size or remaining buffer size
Spotting the Sin During Code Review
Spotting this sin during code review ranges from being very easy to extremely difficult. The easy things to look for are usage of unsafe string handling functions. One issue to be aware of is that you can find many instances of safe usage, but it’s been our experience that there are problems hiding among the correct calls. Converting code to use only safe calls has a very low regression rate (anywhere from 1/10th to 1/100th of the normal bug-fix regression rate), and it will remove exploits from your code.
One good way to do this is to let the compiler find dangerous function calls for you. If you undefined strcpy, strcat, sprintf, and similar functions, the compiler will find all of them for you. A problem to be aware of is that some apps have re-implemented all or a portion of the C run-time library internally.
A more difficult task is looking for heap overruns. Basically, you want to first look for allocations, and then examine the arithmetic used to calculate the buffer size.
The overall best approach is to trace user input from the entry points of your application through all the function calls. Being aware of what the attacker controls makes a big difference
Testing Techniques to Find the Sin
Fuzz testing, which subjects your application to semi-random inputs, is one of the better testing techniques to use. Try increasing the length of input strings while observing the behavior of the app. Something to look out for is that sometimes mismatches between input checking will result in relatively small windows of vulnerable code. For example, someone might put a check in one place that the input must be less than 260 characters, and then allocate a 256 byte buffer. If you test a very long input, it will simply be rejected, but if you hit the overflow exactly, you may find an exploit. Lengths that are multiples of two and multiples of two plus or minus one will often find problems.
Other tricks to try are looking for any place in the input where the length of something is user specified. Change the length so that it does not match the length of the string, and especially look for integer overflow possibilities—conditions where length + 1 = 0 are often dangerous.
Something that you should do when fuzz testing is to create a specialized test build. Debug builds often have asserts that change program flow and will keep you from hitting exploitable conditions. On the other hand, debug builds on modern compilers typically contain more advanced stack corruption detection. Depending on your heap and operating system, you can also enable more stringent heap corruption checking.
One change you may want to make in your code is that if an assert is checking user input, change the following from
assert(len < MAX_PATH);
to
if(len >= MAX_PATH)
{
assert(false);
return false;
}
You should always test your code under some form of memory error detection tool, such as AppVerifier on Windows
Example Sins
The following entries, which come directly from the Common Vulnerabilities and Exposures list, or CVE (http://cve.mitre.org), are examples of buffer overruns. An interesting bit of trivia is that as of this writing, 1,734 CVE entries that match “buffer overrun” exist. A search of CERT advisories, which document only the more widespread and serious vulnerabilities, yields 107 hits on “buffer overrun.”
CVE-1999-0042
From the CVE description: “Buffer overflow in University of Washington’s implementation of IMAP and POP servers.“
This CVE entry is thoroughly documented in CERT advisory CA-1997-09, and involved a buffer overrun in the authentication sequence of the University of Washington’s Post Office Protocol (POP) and Internet Message Access Protocol (IMAP) servers. A related vulnerability was that the e-mail server failed to implement least privilege, and the exploit granted root access to attackers. The overflow led to widespread exploitation of vulnerable systems.
Network vulnerability checks designed to find vulnerable versions of this server found similar flaws in Seattle Labs SLMail 2.5 as reported at www.winnetmag.com/Article/ArticleID/9223/9223.html.
CVE-2000-0389–CVE-2000-0392
From CVE-2000-0389: “Buffer overflow in krb_rd_req function in Kerberos 4 and 5 allows remote attackers to gain root privileges.”
From CVE-2000-0390: “Buffer overflow in krb425_conv_principal function in Kerberos 5 allows remote attackers to gain root privileges.“
From CVE-2000-0391: “Buffer overflow in krshd in Kerberos 5 allows remote attackers to gain root privileges.“
From CVE-2000-0392: “Buffer overflow in ksu in Kerberos 5 allows local users to gain root privileges.“
This series of problems in the MIT implementation of Kerberos is documented as CERT advisory CA-2000-06, found at www.cert.org/advisories/CA-2000-06.html. Although the source code had been available to the public for several years, and the problem stemmed from the use of dangerous string handling functions (strcat), it was only reported in 2000.
CVE-2002-0842, CVE-2003-0095, CAN-2003-0096
From CVE-2002-0842:
Format string vulnerability in certain third-party modifications to mod_dav for logging bad gateway messages (e.g., Oracle9i Application Server 9.0.2) allows remote attackers to execute arbitrary code via a destination URI that forces a “502 Bad Gateway” response, which causes the format string specifiers to be returned from dav_lookup_uri() in mod_dav.c, which is then used in a call to ap_log_rerror().
From CVE-2003-0095:
Buffer overflow in ORACLE.EXE for Oracle Database Server 9i, 8i, 8.1.7, and 8.0.6 allows remote attackers to execute arbitrary code via a long username that is provided during login as exploitable through client applications that perform their own authentication, as demonstrated using LOADPSP.
From CAN-2003-0096:
Multiple buffer overflows in Oracle 9i Database Release 2, Release 1, 8i, 8.1.7, and 8.0.6 allow remote attackers to execute arbitrary code via (1) a long conversion string argument to the TO_TIMESTAMP_TZ function, (2) a long time zone argument to the TZ_OFFSET function, or (3) a long DIRECTORY parameter to the BFILENAME function.
These vulnerabilities are documented in CERT advisory CA-2003-05, located at www.cert.org/advisories/CA-2003-05.html. The problems are one set of several found by David Litchfield and his team at Next Generation Security Software Ltd. As an aside, this demonstrates that advertising one’s application as “unbreakable” may not be the best thing to do whilst Mr. Litchfield is investigating your applications.
CAN-2003-0352
From the CVE description:
Buffer overflow in a certain DCOM interface for RPC in Microsoft Windows NT 4.0, 2000, XP, and Server 2003 allows remote attackers to execute arbitrary code via a malformed message, as exploited by the Blaster/MSblast/ LovSAN and Nachi/ Welchia worms.
This overflow is interesting because it led to widespread exploitation by two very destructive worms that both caused significant disruption on the Internet. The overflow was in the heap, and was evidenced by the fact that it was possible to build a worm that was very stable. A contributing factor was a failure of principle of least privilege: the interface should not have been available to anonymous users. Another interesting note is that overflow countermeasures in Windows 2003 degraded the attack from escalation of privilege to denial of service.
More information on this problem can be found at www.cert.org/advisories/ CA-2003-23.html, and www.microsoft.com/technet/security/bulletin/MS03-039.asp
Redemption Steps
The road to buffer overrun redemption is long and filled with potholes. We discuss a wide variety of techniques that help you avoid buffer overruns, and a number of other techniques that reduce the damage buffer overruns can cause. Let’s look at how you can improve your code.
Replace Dangerous String Handling Functions
You should, at minimum, replace unsafe functions like strcpy, strcat, and sprintf with the counted versions of each of these functions. You have a number of choices of what to replace them with. Keep in mind that older counted functions have interface problems, and ask you to do arithmetic in many cases to determine parameters. Computers aren’t as good at math as you might hope. Newer libraries like strsafe, the Safe CRT (C run-time library) that will be shipped in Microsoft Visual Studio (and is on a fast track to become part of the ANSI C/C++ standard), and strlcat/strlcpy for *nix. You also need to take care with how each of these functions handle termination and truncation of strings. Some functions guarantee null termination, but most of the older counted functions do not. The Microsoft Office group’s experience with replacing unsafe string handling functions for the Office 2003 release was that the regression rate (new bugs caused per fix) was extremely low, so don’t let fear of regressions stop you.
Audit Allocations
Another source of buffer overruns comes from arithmetic errors.
Check Loops and Array Accesses
A third way that buffer overruns are caused is not properly checking termination in loops, and not properly checking array bounds prior to write access. This is one of the most difficult areas, and you will find that, in some cases, the problem and the earth-shattering-kaboom are in completely different modules.
Replace C String Buffers with C++ Strings
This is more effective than just replacing the usual C calls, but can cause tremendous amounts of change in existing code, particularly if the code isn’t already compiled as C++. You should also be aware of and understand the performance characteristics of the STL container classes. It is very possible to write high-performance STL code, but like many other aspects of programming, a failure to Read The Fine Manual (RTFM) will often result in less than optimal results. The most common replacement is to use the STL std::string or std:wstring template classes.
Replace Static Arrays with STL Containers
All of the problems noted above apply to STL containers like vector, but an additional problem is that not all implementations of the vector::iterator construct check for out of bounds access. This measure may help, and the author finds that using the STL makes it possible for him to write correct code more quickly, but be aware that this isn’t a silver bullet.
Use Analysis Tools
There are some good tools coming on the market that analyze C/C++ code for security defects; examples include Coverity, PREfast, and Klocwork. Visual Studio .NET 2005 will include PREfast and another tool called Source code Annotation Language (SAL) to help track down security defects such as buffer overruns. The best way to describe SAL is by way of code.
In the (silly) example that follows, you know the relationship between the data and count arguments: data is count bytes long. But the compiler doesn’t know; it just sees a char * and a size_t.
void *DoStuff(char *data, size_t count) {
static char buf[32];
return memcpy(buf, data, count);
}
This code looks OK (ignoring the fact we loath returning static buffers, but humor us). However, if count is larger that 32, then you have a buffer overrun. A SAL annotated version of this would catch the bug:
void *DoStuff(__in_ecount(count) char *data, size_t count) {
static char buf[32];
return memcpy(buf, data, count);
}
This is because the compiler and/or PREfast now knows that data and count are tightly related.
Extra Defensive Measures
Consider additional defensive measures the same way you think of seat belts in your car. Seat belts will often reduce the severity of a crash, but you still do not want to get into an accident. It’s important to note that for every major class of buffer overrun mitigation, previously exploitable conditions that are no longer exploitable at all exist; and for any given mitigation technique, a sufficiently complex attack can overcome the technique completely. Let’s look at a few of them.
Stack Protection
Stack protection was pioneered by Crispin Cowan in his Stackguard product, and was independently implemented by Microsoft as the /GS compiler switch. At its most basic, stack protection places a value known as a canary on the stack between the local variables and the return address. Newer implementations may also re-order variables for increased effectiveness. The advantage of this approach is that it is cheap, has minimal performance overhead, and has the additional benefit of making debugging stack corruption bugs easier. Another example is ProPolice, a Gnu Compiler Collection (GCC) extension created by IBM. Any current product should utilize stack protection.
You should be aware that stack protection can be overcome by a variety of techniques. If a virtual function pointer table is overwritten and the function is called prior to return from the function—virtual destructors are good candidates—then the exploit will occur before stack protection can come into play.
Non-executable Stack and Heap
This countermeasure offers considerable protection against an attacker, but it can have a significant application compatibility impact. Some applications legitimately compile and execute code on the fly, such as many applications written in Java and C#. It’s also important to note that if the attacker can cause your application to fall prey to a return into libc attack, where a legitimate function call is made to accomplish nefarious ends, then the execute protection on the memory page may be removed.
Unfortunately, most of the hardware currently available is unable to support this option, and support varies with CPU-type, operating system, and operating system version as well. As a result, you cannot count on this protection being present in the field, but you must test with it enabled to ensure that your application is compatible with a non-executable stack and heap, by running your application on hardware that supports hardware protection, and the target operating system set to use the protection. For example, if you are targeting Windows XP, then make sure you run all your tests on a Windows XP SP2 computer using an AMD Athlon 64 FX processor. On Windows, this technology is called Data Execution Protection (DEP); it was once known as No eXecute (NX).
Windows Server 2003 SP1 also supports this capability. PaX for Linux and OpenBSD also support non-executable memory
Other Resources
|