heap 3

Published: April 3, 2024

Description

This program mishandles memory. Can you exploit it to get the flag?

Local + remote

Download chall and chall.c for local analysis.

Connect to tethys.picoctf.net <PORT_FROM_INSTANCE> to interact with the menu.

wget https://artifacts.picoctf.net/c_tethys/6/chall && \
chmod +x chall && \
wget https://artifacts.picoctf.net/c_tethys/6/chall.c && \
nc tethys.picoctf.net <PORT_FROM_INSTANCE>

Solution

This is the final heap challenge in the series. After progressing through heap 0 (basic overflow), heap 1 (specific value), and heap 2 (function pointers), you now exploit use-after-free vulnerabilities. The menu offers: 2. Allocate heap object (controlled length). 3. Print current x value. 4. Check for win (requires x == "pico"). 5. Free x (this sets up the use-after-free). The Buffer Overflow and Binary Exploitation guide covers use-after-free and tcache poisoning in depth.
  1. Step 1Free the chunk first
    Option 5 must run before anything else so the program continues to use a dangling pointer to x.
    Learn more

    A use-after-free (UAF) vulnerability occurs when a program frees a heap chunk but retains a pointer to it (a dangling pointer) and later uses that pointer to read or write memory. The freed chunk may be reallocated for a different purpose, so data written through a new allocation overwrites memory that the old dangling pointer still points to.

    The sequence matters critically: free first, then allocate. If you allocate before freeing, the new allocation goes to a different chunk (the old one is still in use). Only after freeing does the chunk enter the allocator's freelist, available for the next allocation of the same or smaller size.

    In modern glibc (the standard C library), freed chunks go into size-specific bins: fastbins for small chunks, unsorted bins for medium chunks, and large bins for large chunks. The next malloc() of a suitable size will return the freed chunk's memory. This deterministic recycling is what makes UAF exploitation reliable.

    Use-after-free is one of the most common vulnerability classes in modern software, particularly in web browsers (V8, SpiderMonkey, JavaScriptCore) and media parsers. The Chrome bug tracker regularly contains UAF vulnerabilities rated Critical. Browser sandboxes partially mitigate the impact, but UAF remains a primary pathway to browser exploitation.

  2. Step 2Allocate with controlled data
    Option 2 asks for a length. Enter 31 so you can write 30 filler characters followed by pico, which overwrites the freed structure.
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAApico
    Learn more

    After freeing the original chunk, requesting a new allocation of the same or slightly smaller size causes the allocator to return the same chunk that was just freed. Your new write then populates the freed chunk with fresh data - which the dangling pointer from the previous allocation will read.

    The length of 31 bytes is chosen so that the total write (30 filler bytes + "pico" + null terminator) fits within the freed chunk without overflowing into the allocator's metadata. Exceeding the chunk size would corrupt the heap metadata, likely causing a crash when the next allocation or free occurs.

    This technique - heap grooming - is a broader concept where an attacker carefully controls the sequence and size of allocations and frees to achieve a desired heap layout. Advanced UAF exploits may require dozens of carefully sized allocations to position the target chunk exactly where the dangling pointer expects it, especially in modern hardened allocators.

    The specific filler length (30 bytes) comes from reading chall.c: the structure that x points to has some fields before the string that the check reads. Writing 30 bytes of filler covers those fields, and "pico" occupies the string field that the win check examines. Without source code access, you'd need to determine this offset by examining the binary in a debugger.

  3. Step 3Verify and print
    Option 3 now echoes pico, and option 4 prints the flag because the dangling pointer points to your crafted data.
    If the check fails, ensure you freed first and used exactly 30 filler characters before pico.
    Learn more

    Option 3 ("print current x value") reads through the dangling pointer that still references the freed chunk. Since you reallocated that chunk with your controlled data, the read now returns "pico". This demonstrates the UAF: the program believes it's reading from x, but the memory at that address now contains attacker-controlled data.

    Option 4's win check (x == "pico") uses the same dangling pointer, finds the string you wrote, and prints the flag. The original value of x (whatever was in the structure before freeing) is irrelevant - the UAF lets you substitute any value you can allocate.

    In real-world UAF exploitation, the "controlled allocation" step is often more complex. Browser exploits might allocate JavaScript ArrayBuffers, Uint8Arrays, or other typed arrays at a predictable size to reclaim freed DOM element structures. The attacker must understand both the target object's layout and the layout of available attacker-controlled objects to find a suitable "replacement" that the vulnerable code will misinterpret usefully.

    Mitigations against UAF include: setting freed pointers to NULL (prevents use but not double-free), heap quarantine (keeping freed memory out of circulation for a period to detect UAF), PartitionAlloc (Chrome's allocator which segregates objects by type, making cross-type UAF harder), and hardware features like Memory Tagging Extension (MTE) on ARM that tag pointers and detect stale pointer use at the hardware level.

Flag

picoCTF{now_thats_free_real_estate_a11...}

Once the freed chunk is reallocated with pico, the win check passes and prints the flag.

Want more picoCTF 2024 writeups?

Useful tools for Binary Exploitation

Related reading

Do these first

What to try next