information it is passed—it must preallocate enough room in the stack for the
largest chunk of data it expects to receive. Of course, properly written code ver-
ifies that the received data fits into the stack buffer before copying it, but you’d
be surprised how frequently programmers neglect to perform this verification.
What happens when a buffer of an unknown size is copied over into a lim-
ited-sized stack buffer? If the buffer is too long to fit into the memory space
allocated for it, the copy operation will cause anything residing after the buffer
in the stack to be overwritten with whatever is sent as input. This will fre-
quently overwrite variables that reside after the buffer in the stack, but more
importantly, if the copied buffer is long enough, it might overwrite the current
function’s return address.
For example, consider a function that defines the following local variables:
int counter;
char string[8];
float number;
What if the function would like to fill stringwith user-supplied data? It
would copy the user supplied data onto string, but if the function doesn’t
confirm that the user data is eight characters or less and simply copies as many
characters as it finds, it would certainly overwrite number, and possibly what-
ever resides after it in memory.
Figure 7.1 shows the function’s stack area before and after a stack overwrite.
The stringvariable can only contain eight characters, but far more have been
written to it. Note that this figure ignores the (very likely) possibility that the
compiler would store some of these variables in registers and not in a stack.
The most likely candidate is counter, but this would not affect the stack over-
flow condition.
The important thing to notice about this is the value of CopiedBuffer +
0x10, because CopiedBuffer + 0x10now replaces the function’s return
address. This means that when the function tries to return to the caller (typi-
cally by invoking the RETinstruction), the CPU will try to jump to whatever
address was stored in CopiedBuffer + 0x10. It is easy to see how this
could allow an attacker to take control over a system. All that would need to
be done is for the attacker to carefully prepare a buffer that contains a pointer
to the attacker’s code at the correct offset, so that this address would overwrite
the function’s return address.
A typical buffer overflow includes a short code sequence as the payload (the
shellcode [Koziol]) and a pointer to the beginning of that code as the return
address. This brings us to one the most difficult parts of effectively overflow-
ing the stack—how do you determine the current stack address in the target
program in order to point the return address to the right place? The details of
how this is done are really beyond the scope of this book, but the generally
strategy is to perform some educated guesses.
246 Chapter 7