2.5 Stack Smashing
Stack smashing occurs when a buffer overflow overwrites data in the memory allocated to the execution stack. This can have serious consequences for the reliability and security of a program. Buffer overflows in the stack segment may allow an attacker to modify the values of automatic variables or execute arbitrary code.
Overwriting automatic variables can result in a loss of data integrity, or in some cases, a security breach (for example, if a variable containing the UID is overwritten). More often, a buffer overflow in the stack segment can lead to an attacker executing arbitrary code by overwriting a pointer address to which control is (eventually) transferred. A common example is overwriting the return address (which is also located on the stack). Additionally, it is possible to overwrite a frame or stack-based exception handler pointer, function pointer, or other address to which control may be transferred.
The example password program shown in Figure 2–9 and compiled using Visual C++ 2003 for Windows XP is vulnerable to a stack smashing attack. To understand why this program is vulnerable, it is necessary to understand how, exactly, the stack is being used.
Before the program makes the call to the function IsPasswordOkay(), the stack contains the information shown in Figure 2–16, which is the memory for the local variable that stores the status returned by the function IsPasswordOkay() along with the caller's frame pointer and return address.
While the program is executing the function IsPasswordOkay(), the stack contains the information shown in Figure 2–17. Notice that the password is located on the stack with the return address of the caller main(), which is located after the memory that is used to store the password. It is also important to understand that the stack will change during function calls made by IsPasswordOkay().
Figure 2–16. Program stack before call to IsPasswordOkay()
Figure 2-17 Program stack during call to IsPasswordOkay()
After the program returns from the IsPasswordOkay() function, the stack is restored to its initial state shown in Figure 2–18 and the program continues based on the value returned from the IsPasswordOkay() function.
As discussed earlier, this program has a security flaw because the Password array can only hold an 11-character password plus a trailing null byte. This flaw can easily be demonstrated by entering a 20-character password of "12345678901234567890" that causes the program to crash as shown in Figure 2–19. To determine the cause of the crash, one must understand the effect
Figure 2–18. Program stack after call to IsPasswordOkay()
Figure 2–19. Program crash caused by a 20-byte password
of the 20-byte password on the program stack. Recall that when 20 bytes are input by the user, the amount of memory required to store the string is actually 21 bytes because of the null terminator that is added. Because the space available to store the password is only 12 bytes, 9 bytes of the stack (21 – 12 = 9) that have already been allocated to store other information will be overwritten with password data. Figure 2–20 shows the corrupted program stack caused by the 20-byte password. Notice that the caller's frame pointer, return address, and part of the storage space used for the PwStatus variable have been corrupted.
When a program fault occurs, the typical user generally does not assume that a potential vulnerability exists. The typical user only wants to restart the program, but an attacker wants to investigate to see if the programing flaw can be exploited.
The program crashes because the return address is altered by the buffer overflow and the new address is either invalid, or memory at that address (a) does not contain a valid CPU instruction; (b) does contain a valid instruction, but the CPU registers are not set up for proper execution of the instruction; or (c) is not executable.
A carefully crafted input string can make the program produce unexpected results, as shown in Figure 2–21. Figure 2–22 shows how the input string changes the contents of the stack by overwriting 9 extra bytes of memory on the stack. The input string consists of a number of funny-looking characters
Figure 2–20. Corrupted stack caused by the 20-byte password
Figure 2–21. Program results for a carefully crafted input string
"j*!". These are all displayable ASCII characters that can be input using the keyboard or character map. Each of these characters has a corresponding hexadecimal value: 'j'=0x6A, ''=0x10, '*'=0x2A, and '!'=0x21. In memory, this sequence of four characters corresponds to a 4-byte address that overwrites the return address on the stack—changing the program's execution path as shown in Figure 2–23. As a result, when the program returns from the function IsPasswordOkay() it returns to line 7 instead of line 3. This allows an attacker to skip the execution of lines 3–6, circumventing the password validation logic (shown in bold font), and access the system.
Figure 2–22. Crafted input string's effect on the stack
1. puts("Enter Password:"); 2. PwStatus=IsPasswordOkay(); 3. if (PwStatus == false) { 4. puts("Access denied"); 5. exit(-1); 6. } 7. else puts("Access granted");
Figure 2–23. Circumventing the password validation logic
This vulnerability can also be exploited to execute arbitrary code embedded in the input string. This technique of code injection is examined in the next section.