Post

Phoenix / Protostar Stack Zero - exploit.education

How a 64-Byte Buffer Breaks Program Logic

Phoenix / Protostar Stack Zero - exploit.education

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 named changeme is placed directly after that buffer. By overflowing the buffer, we overwrite changeme and 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 changeme non-zero.

The plan

  1. Fill the buffer completely. (64 bytes)
  2. Keep writing.
  3. Overwrite changeme with anything that isn’t zero.

The payload

Heres the entire exploit:

1
python -c 'print("A"*64 + "BBBB")' | ./stack0
  • "A"*64 fills the buffer
  • "BBBB" overwrites changeme with 0x42424242 We 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 fgets or gets_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
This post is licensed under CC BY 4.0 by the author.