Sitemap
SoBatista

SoBatista

Ethical Hacker | Entrepreneur | Content Creator YouTube: youtube.com/@SoBatistaCyber

Press enter or click to view image in full size

CyLab Writeup: PIE TIME

5 min read1 day ago

--

Welcome back, hackers! In this challenge, we’re getting into some binary exploitation, and the challenge makes sure we know what we’re up against right in the description: PIE is enabled.

PIE has a reputation for making things harder, but it is also a nice way to see how one small piece of information can quietly undo all of that protection.

Let’s take a look.

Challenge Overview

  • Event: picoCTF 2025
  • Challenge Name: PIE TIME
  • Category: Binary Exploitation
  • Difficulty: Easy

Challenge Description

Can you try to get the flag? Beware we have PIE! Connect to the program with netcat:

$ nc <host> <port>

The program’s source code can be downloaded here. The binary can be downloaded here.

Hint 1: Can you figure out what changed between the address you found locally and in the server output?

Step 1: Reading the Source Code

The challenge gives us both the binary and the source, so I started with the source. The interesting part is at the bottom of main:

cat vuln.c
Press enter or click to view image in full size

Let me explain what this does, because it is wild: the program reads a number from me and then calls that number as if it were a function. Whatever address I type, execution jumps there.

Scrolling up, there is also a function the challenge clearly wants me to reach:

So the goal is simple to state: make foo() point at win. If I can jump to win, it reads flag.txt for me.

Step 2: Understanding the Obstacle (PIE)

If this were that easy, there would be no challenge. The catch is PIE.

PIE stands for Position-Independent Executable. It means that every time the program runs, the operating system loads it at a different random base address. So the address of win is not fixed. It changes on every single connection.

I confirmed it with file:

file vuln
Press enter or click to view image in full size

Two things there matter to us:

  • pie executable means the addresses change every time the program runs.
  • not stripped means the function names (like win and main) are still stored inside the binary, which helps us in a moment.

Because the addresses change, I can’t just look up where win is once and reuse that number. The moment I reconnect, it has already moved somewhere else.

Step 3: The Key Idea

This is the part that makes PIE beatable here.

Get SoBatista’s stories in your inbox

Join Medium for free to get updates from this writer.

PIE shuffles where the program gets loaded in memory, but it does not rearrange the functions inside it. Think of the program as a row of houses on a street: PIE picks a new spot for the whole street every time, but the houses always stay in the same order and the same distance apart.

win and main are two of those houses. So no matter where the street ends up, the gap between win and main is always exactly the same.

And here is the gift the program gives us. Remember Step 1, where it prints the address of main? That is one house telling me exactly where it is, on every single run.

So the plan is simple: if I know the fixed gap between main and win, I take the main address that the program hands me and move over by that gap to land on win.

win address = (leaked main address) - (gap between main and win)

All I need now is that gap, and because the binary still has its symbols, finding it is easy.

Step 4: Finding the Offsets

Because the binary is not stripped, nm reads the function offsets straight out of it:

nm vuln | grep -E ' (win|main)$'

So inside the file:

  • main lives at offset 0x134c
  • win lives at offset 0x12aa

Step 5: Doing the Math

Now I can work out the fixed gap between them:

0x134c - 0x12aa = 0xA2 (162 in decimal)

So win sits 0xA2 bytes before main, always.

That gives me my formula for any connection:

win = (leaked main address) - 0xA2

That is the entire exploit. Leak main, subtract 0xA2, send it back.

Step 6: Connecting and Getting the Flag

Now that I knew the gap, the rest was easy. There was no need for an exploit script or a complicated payload, just one small subtraction I could do by hand. So I connected with netcat and let the program start:

It handed me the address of main for this run and waited for me to say where I wanted to go.

From there it was easy. I took that leaked main address (0x647f927a434c) and subtracted the 0xA2 gap to reach win. The result was 0x647f927a42aa, which was precisely what I gave to the program. And that got me the flag.

Press enter or click to view image in full size

Final Thoughts

I really like this one because PIE sounds like a wall, and then you realize the program is practically holding the door open for you by printing the address of main. The randomization only moves the base, so a single leak plus a constant offset is all it takes to pin down any other function in the binary.

Happy hacking, and I’ll see you in the next write-up!

Cheers,
SoBatista

--

--

SoBatista
SoBatista