Exploiting a stack-based buffer overflow
How an unchecked strcpy into a stack buffer lets an attacker overwrite the return address, and why a real exploit uses a NOP sled, repeated buffer addresses and null-free shellcode.
- Explain how an unchecked copy into a local buffer overwrites the saved return address.
- State the three steps of a stack-based buffer overflow exploit.
- Explain why an exploit includes a NOP sled and repeated buffer addresses.
- Explain why shellcode must not contain null bytes when the overflow uses strcpy.
15 min readJump to the quiz ↓
An unchecked local copy can overwrite the saved return address
A function can copy attacker input into a fixed-size local buffer without checking its length. If the bytes reach the saved return address, the attacker can change where the CPU goes when the function returns.
The lecture’s vulnerable function:
void f(const char* input) {
char buf[10];
// No checking of length
strcpy(buf, input);
return;
}
buf is a local variable, so it lives in the stack frame of f. Suppose the input is 20 bytes. strcpy(buf, input) copies it blindly, with no bounds checking, so the buffer overflows and corrupts the memory at the higher addresses above it. One of the things stored there is the saved EIP, so the user input can set EIP to a value the attacker controls.
The notes walk through the contrast. With input of 10 bytes or fewer, nothing outside buf is touched and the function returns normally to its caller. With a 30-byte input of \x41 bytes, the saved EIP becomes \x41\x41\x41\x41. At the end of the function the CPU tries to jump to that address, which is invalid, and the program stops with a segmentation fault. That is a crash, and it is already a denial of service. The attack is worse when the bytes are chosen instead of random.
An overflow becomes code execution in three steps
The exploit has three steps in the lecture.
- Insert malicious input that overruns the allocated buffer space.
- Jump to and execute malicious code. Overwrite the return address so it points at the address of the malicious code, the shellcode.
- Exploit the system. The shellcode lets the attacker run arbitrary code under the current user.
NOP sleds and repeated addresses tolerate uncertain stack layouts
Finding the offset. To put a chosen value on EIP, the attacker needs the byte offset between the start of the buffer and the saved EIP. Bytes before that offset are filler, and the four bytes at that offset become the new return address. The slide’s idea is: find the byte offset to overwrite EIP, then overwrite it with the start address of attacker-controlled content in the buffer.
Why the NOP sled and the repeated addresses. The stack frame is created dynamically and its layout depends on compiler settings. The absolute addresses of EIP and of the start of the buffer are hard to predict. They can, however, be approximated by trial and error, and the payload is built to tolerate that.
- Many copies of the buffer address after the shellcode. If the attacker does not know exactly where EIP sits, repeating the address over the whole region after the shellcode makes it likely that one copy lands on EIP.
- NOP bytes before the shellcode.
0x90is the Intel instruction for “do nothing”. If the return address points anywhere inside the run of NOPs, the CPU steps through each one until it reaches the shellcode. So the address does not have to be exact. Other inconsequential instructions, such as moving a register to itself or adding zero, work the same way.
Writing shellcode. Shellcode is input to the buffer, so it has to be written in the machine language (binary) of the target system. It may be written first in the target’s assembly language so a human can read it. It must not contain null characters, 0x00, because that is read as the end of a string and strcpy() stops on it. Tools now generate shellcode automatically; the slide names Metasploit, which also takes care of getting around the null character problem.
Pitfall
The NOP sled does not make the attack more powerful. It makes the attacker’s guess about the address cheaper to get right. When a question asks “why NOPs”, the marked answer is that the overwritten return address can be reasonably imprecise.
Bounds checking and layered defences interrupt the attack
Nothing on this page stops the attack, because C does no bounds checking. The cure is either a language that does (memory-safe languages) or the layers added around C, covered in the next topic: canaries, return address backups, address randomisation and non-executable stacks.
Exam detail
Expect to be given the strcpy function and asked why it is vulnerable, to list the three exploit steps in order, to explain the purpose of the NOP sled and of repeating the address, and to give the null-byte constraint with its reason. Say “the saved return address, EIP” rather than “the stack”, so the marker sees which value is overwritten.
Optional aside
The notes name the underlying cause in plain terms. Once memory is allocated to a variable, C has no built-in check that the data fits, and the compiler leaves that responsibility to the programmer because checking everything would slow programs down. The notes add that many internet worms have used buffer overflows to spread.
Recall
How does an unchecked copy redirect execution, and why do the payload layout and null bytes matter?
strcpyinto a local buffer with no length check lets input overflow into higher stack addresses, including the saved EIP.- Steps: overrun the buffer, point the return address at shellcode, run code as the current user.
- Exact addresses are unpredictable, so the payload uses a NOP sled and repeated buffer addresses.
- Shellcode cannot contain
0x00becausestrcpystops at it. Metasploit can generate it.
Check yourself
Source
Week 10 slides PDF