Phoenix / Protostar Stack Zero - exploit.education
How a 64-Byte Buffer Breaks Program Logic
Stack Zero - Phoenix / Protostar
The full challenge and source code can be found at: https://exploit.education/phoenix/stack-zero/ Difficulty (platform): Easy
Difficulty (my view): Easy
TL;DR
The binary reads user input into a 64-byte stack buffer using
gets(). A variable namedchangemeis placed directly after that buffer. By overflowing the buffer, we overwritechangemeand change program logic without crashing or executing code.
Why this matters in the real world
This kind of bug is everywhere:
- Feature flags stored next to user input
- Authentication state flipped by memory corruption
- Security checks that “should never fail” …until they do
In incident response for example, this kind of bug can explain why a security control appears to have been “bypassed” without any clear exploit chain or crash artifacts.
Reading the code
This challenge helpfully gives us access to the source code. One line immediately stands out:
1
gets(local.buffer);
That function alone should set off alarms but what makes this easy is the surrounding code:
1
2
3
4
5
6
struct {
char buffer[64];
volatile int changeme;
} locals;
local.changeme = 0;
Two variables. One struct. One stack frame.
What actually goes wrong
gets() doesn’t know how big buffer is. It keeps writing until it hits a newline. And because buffer and changeme are sitting next to each other in memory, writing more than 64 bytes doesn’t stop, it simply continues writing into changeme.
Basically the stack looks like this:
1
[ buffer (64 bytes) ][changeme (4 bytes)]
No addresses to calculate. No protections to bypass. Just write past a boundary for the win. This is called a data-only buffer overflow.
Turning the bug into a win
The program checks one single thing:
1
if (locals.changeme != 0)
So our goal is very simple:
Make
changemenon-zero.
The plan
- Fill the buffer completely. (64 bytes)
- Keep writing.
- Overwrite
changemewith anything that isn’t zero.
The payload
Heres the entire exploit:
1
python -c 'print("A"*64 + "BBBB")' | ./stack0
"A"*64fills the buffer"BBBB"overwriteschangemewith0x42424242We don’t care about the value. We only care that it’s not zero.
The result
The program responds with:
1
Well done, the 'changeme' variable has been changed!
No crash… no shell… just a program admitting it made the wrong decision.
Why this is more interesting than it looks
What makes Stack Zero so important isn’t what you do, it’s what you don’t do.
- No control-flow hijacking
- No return address overwrite
- No code-injection
And yet the program is still compromised.
How this would be prevented
This challenge exists because of one function call. To avoid bugs like this in real code:
- Never use
gets(). It got removed from the C standard for a reason… - Use bounded input functions like
fgetsorgets_s - Enable compiler protections like stack canaries
- Treat “logic-only” corruption as a real security issue!
Lessons worth keeping
Technical
- Local stack variables are typically placed next to eachother in memory.
- Writing past a buffer doesn’t need to crash to be dangerous.
- Data corruption alone can fully alter program logic.
Mindset
- Read the source before touching a debugger.
- Ask what needs to change, not what do I want to execute.
Closing thoughts
Stack Zero doesn’t teach exploitation tricks, but it teaches how you need to think about exploitation. Once you fully understand this level, the later ones stop feeling like magic and start feeling inevitable.
Tools used:
- Linux shell
- Python
- Reading the source code
