TL;DR
This guide explains how the Execution Pointer (EIP) is overwritten in a simple buffer overflow, focusing on different calling conventions. Understanding this is crucial for exploiting vulnerabilities and building robust cyber security defenses.
Understanding Buffer Overflows & EIP
A buffer overflow happens when a program writes data beyond the allocated space of a buffer. In many cases, this can overwrite adjacent memory locations, including the return address on the stack. The return address points to where the program should continue execution after the current function finishes. Overwriting it with malicious code allows an attacker to control the program’s flow.
The EIP (Instruction Pointer) register holds the address of the next instruction to be executed. When a function returns, its return address is popped from the stack into EIP, directing execution to the overwritten value.
How EIP Overwrite Happens: Different Calling Conventions
The way arguments are passed to functions and how the stack is managed varies depending on the calling convention. This affects how you overwrite the EIP.
1. Standard C Calling Convention (x86-64 Linux)
- Argument Passing: Arguments are typically pushed onto the stack in reverse order.
- Stack Frame: A function’s local variables and saved registers are stored on a stack frame created before execution. The return address is also part of this frame.
- EIP Overwrite: To overwrite EIP, you need to calculate the offset from the beginning of your buffer to the location where the return address (and thus EIP) resides on the stack. This often involves knowing the size of local variables and pushed arguments.
Example: Consider a vulnerable program with a buffer and a function call.
#include <stdio.h>
#include <string.h>
void vulnerable_function(char *input) {
char buffer[64];
strcpy(buffer, input);
}
int main() {
char user_input[256];
printf("Enter your input: ");
fgets(user_input, sizeof(user_input), stdin);
vulnerable_function(user_input);
return 0;
}
In this example, if the user provides more than 64 bytes of input, `strcpy` will overflow `buffer`. To overwrite EIP, you’d need to send enough data to fill `buffer`, then overwrite the saved return address on the stack.
2. Fastcall Calling Convention (x86-64 Linux)
- Argument Passing: Arguments are passed in registers (e.g., RDI, RSI, RDX, RCX, R8, R9) as much as possible. Only larger arguments or those exceeding the register limit are pushed onto the stack.
- Stack Frame: The stack frame is smaller because fewer arguments are on the stack.
- EIP Overwrite: Because fewer arguments are pushed onto the stack, the offset to overwrite EIP will be different compared to the standard calling convention. You need to determine which registers are used for argument passing and calculate the remaining space before the return address.
The exact register usage depends on the compiler and optimization level.
3. Variadic Functions (e.g., printf)
- Argument Passing: Variadic functions take a variable number of arguments. Arguments are pushed onto the stack in reverse order, but there’s no fixed argument list.
- Stack Frame: The stack frame is more complex because the number of arguments isn’t known at compile time.
- EIP Overwrite: Overwriting EIP with a variadic function like `printf` requires careful consideration of format string vulnerabilities in addition to buffer overflow techniques. You need to account for the variable number of arguments and their sizes on the stack.
Example (simplified):
#include <stdio.h>
int main() {
char user_input[256];
printf(user_input);
return 0;
}
If the `user_input` contains format specifiers like `%x`, it can read data from the stack, potentially revealing or overwriting EIP.
Practical Steps for Exploitation
- Identify Vulnerability: Locate a buffer overflow vulnerability in the target program.
- Determine Offset: Use debugging tools (e.g., GDB) to find the offset from the beginning of your input buffer to the return address on the stack. Pattern creation tools like `msf-pattern_create` and `pwntools` can help with this.
- Craft Payload: Create a payload that fills the buffer, overwrites the return address with the desired malicious code (e.g., shellcode or a function address), and any necessary padding.
- Execute Exploit: Run the program with your crafted input to trigger the overflow and overwrite EIP.
Mitigation Techniques
- Use Safe Functions: Replace unsafe functions like `strcpy` with safer alternatives like `strncpy`, which limit the number of bytes copied.
- Stack Canaries: Implement stack canaries to detect buffer overflows before they overwrite critical data.
- Address Space Layout Randomization (ASLR): Randomize the base addresses of libraries and the stack to make it harder for attackers to predict memory locations.
- Data Execution Prevention (DEP/NX): Mark certain memory regions as non-executable, preventing shellcode from running in those areas.