pwnfreehard

priority-queue

b01lersc

Task: a PIE binary implements a min-heap priority queue of heap-allocated strings, but it also mallocs the flag at startup and loses the pointer. Solution: combine a root use-after-free, a 32-byte heap overwrite, fake-chunk creation, and glibc 2.31 tcache poisoning to overlap the hidden flag allocation and print it.

$ ls tags/ techniques/
tcache_poisoningtcache_metadata_leakheap_layout_inferencefake_chunk_forgeryoverlapping_allocationflag_recovery_from_heap

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

priority-queue — b01lersc (BCTF 2026)

Overview

ncat --ssl priority-queue.opus4-7.b01le.rs 8443

We are given a menu-driven priority queue of strings. At first glance it looks like a small heap challenge with insert, delete, peek, and edit operations. The important twist is in main(): the program opens flag.txt, allocates 100 bytes for it on the heap, reads the flag there, and then immediately loses the pointer.

So this is not a shell challenge. The goal is to recover an already allocated heap object that contains the flag.

The final exploit from solve.py uses three ideas:

  1. leak a heap pointer from freed 0x20 tcache entries,
  2. infer the hidden flag allocation from the stable startup allocation order,
  3. poison tcache to allocate a chunk overlapping flag_ptr - 0x20, then make puts() print through into the untouched flag string.

Files and environment

Relevant files:

  • chall — target binary
  • chall.c — source code
  • solve.py — working exploit
  • libc.so.6 — Debian GLIBC 2.31-13+deb11u13
  • Dockerfile — remote packaging

Binary protections:

  • PIE enabled
  • Partial RELRO
  • NX enabled
  • no stack canary

Allocator detail that matters most: the provided libc is glibc 2.31, so tcache has no safe-linking. That makes classic tcache poisoning practical once we can overwrite a freed chunk's next pointer.

Source analysis

The challenge source is short enough to understand fully.

Hidden flag allocation

At startup:

FILE *file = fopen("flag.txt", "r"); if (file) { char *flag = malloc(100); fgets(flag, 100, file); fclose(file); }

The flag is read into a heap chunk, but flag is a local variable and is never stored anywhere global. After main() continues, that heap object still exists, but the program has no reference to it anymore.

...

$ grep --similar

Similar writeups