The stack bottom varies from operating system to operating system, and the height of the stack top depends on the previous requests to the program. Thus, the absolute address of automatic variables is not known beforehand. On the other hand, automatic buffers are attractive because the return address from the function lies directly near their end. If this return address is overwritten, then a different branch of the program will gain control. The situation with the heap is much more complicated. Nevertheless, hackers manage to overflow even it.
Stack Overflow
Cases of automatic buffer overflow are the most frequent and the most perfidious. They occur frequently because the size of such buffers is hard-encoded at compile time, and procedures of checking the data being processed for correctness are either missing or implemented with blatant errors. They are perfidious because directly near automatic buffers there is the return address, overwriting which allows the intruder to pass control to arbitrary code.
In addition, the stack contains the pointer to the frame of the parent function saved by the compiler before opening the frame of the child function. In general, optimizing compilers supporting the "floating" frame technology do without it, using the stack top pointer as a normal general-purpose register. However, even superficial analysis detects a large number of vulnerable applications with the frame inside; therefore, this technique
retains its importance. Modification of the stack frame corrupts addressing of local variables and arguments of the parent function and provides the attacker with the possibility of controlling them as desired. By setting the frame of the parent function to the chosen buffer, the intruder can assign any values to the variables or arguments of the parent function (including knowingly invalid ones, because the check of the
arguments' validity is usually carried out before the call to any child functions, and automatic variables usually are not checked for correctness after initialization).
Important
Because after return from the child function, all local variables belonging to it are automatically released, it is not recommended that you use the child buffer for storing variables of the parent function (more precisely, it is possible to do so, but the hacker must be careful). It is better to use the heap, static memory, or automatic memory of the parallel thread influencing it indirectly.
The general scheme of stack memory allocation is shown in Fig. 4.3.
Free space
Automatic variables of the child function Saved registers
Stack frame of the parent function Return address to the parent function
Arguments of the child function
Automatic variables of the parent function Saved registers
Stack frame of the grandparent function Return address to the grandparent function
Arguments of the parent function
…
Stack bottom
Figure 4.3: Map of stack memory allocation
Above the stack frame are saved values of the registers, which are restored after exiting from the function. If the parent function stores critical variables in one or more such
registers, then the attacker can freely influence them.
Next there is the area occupied by local variables (including overflowing buffer).
Depending on the whim of the compiler, the latter might be located either on the top of the stack frame or in the middle of local variables. Variables located "below" the
overflowing buffer might be overwritten during sequential overflow — the most common type of overflow. Variables located "above" the overflowing buffer are overwritten only in the course of index overflow, which is encountered rarely.
Finally, above the stack frame is the free stack space. There is nothing to overwrite here, and this space can be used for auxiliary needs of the shellcode. At the same time, the hacker must bear in mind that, first, the stack size is limited and second, if one of the sleeping objects of the victim process suddenly wakes up, the contents of the free stack memory will be modified. To avoid such a situation, shellcode must "pull" the ESP
register to the top level, thus reserving the required number of memory bytes. Because the stack memory belonging to the thread is allocated dynamically, any attempt to go beyond the limits of the page guard throws an exception. Thus, the hacker must not request more than 4 KB or read at least one cell from each page being reserved, going from bottom to top. More detailed information on this topic can be found in Advanced Windows by Jeffrey Richter.
Depending on the level of limitations implied on the maximum allowed length of the
overflowing buffer, either local variables or auxiliary data structures might be overwritten.
It is highly possible that the shellcode won't succeed in reaching the return address.
Even if the shellcode achieves this, the function may crash long before its completion.
Assume that directly after the end of the overflowing string buffer, there is a pointer from which something is read or into which something is written after the overflow. Because buffer overflow inevitably overwrites the pointer, any attempt at reading it causes an immediate exception and, consequently, abnormal termination of the program. It probably will be impossible to overwrite the return address by supplying the correct address to the pointer, because in Windows all addresses guaranteed to be available are located considerably lower than 01010101h — the smallest address that can be inserted into the middle of the string buffer (see Chapter 10 for more details). Thus, buffers located at the bottom of the stack frame are preferred targets for overflow.
After the end of the return address lies the memory area belonging to parent functions and containing arguments of the child function, automatic variables of the parent function, saved registers and the stack frame of the grandparent function, the return address to the grandparent function, etc. (Fig. 4.3). In theory, an overflowing buffer can overwrite all this information (there are such aggressive buffers); however, in practice this is either unneeded or impossible. If the hacker can force the program to accept the correct
return address (in other words, the return address pointing to the shellcode or to any address of the "native" code of the program), it will not return to the parent function and all machinations with the parent variables will remain unnoticed. If for some reason it is impossible to supply the correct return address, then, even more so, the parent function won't obtain control.
Reading the parent memory area is much more informative (see "Pointers and
Indexes"), because lots of interesting information can be encountered here, including confidential data (such as passwords or credit card numbers), descriptors of secret files that cannot be opened in a normal way, and sockets of established TCP connections that can be used for bypassing firewalls.
Modification of the arguments of the child function is less practical, although sometimes it can be useful. Traditionally, there are lots of pointers among C/C++ arguments. As a rule, these are pointers to data; however, pointers to code can be encountered. From the attacker's point of view, they are the most promising because they allow the intruder to gain control over the program before it crashes. Naturally, pointers to data are also good targets for the attack (especially those that allow writing of fictitious data at forced addresses, in other words, the ones that work like the poke function in Basic). However, to reach these arguments in the course of sequential buffer overflow, it is necessary to pass over the cells that are occupied by the return address.
Overwriting of the return address relates to one particularly interesting feature: the return address is the absolute address. Consequently, if the hacker needs to pass control directly to the overflowing buffer, it is necessary either to hope that the overflowing buffer of the vulnerable program will be located at the specific address (which can't be guaranteed) or to search for the mechanism of passing control to the stack top.
The Love San worm solved the problem by replacing the return address with the address of the jmp esp machine instruction located in the domain of the operating system. Drawbacks of such an approach are obvious. First, it won't work when the overflowing buffer is located below the stack top. Second, the location of the jmp esp instruction is closely related to the version of the operating system. However, there are no better methods of passing control.