Project 5: Heap Exploitation Lab (Use-After-Free)

A program that manages “notes” for a user. The user can create a note, delete a note, and print a note. The vulnerability is that after a note is deleted (free‘d), the pointer to it is not cleared (it becomes a “dangling pointer”). Your goal is to exploit this to call an arbitrary function.

Quick Reference

Attribute Value
Primary Language C
Alternative Languages C++ (where it’s even more common with objects)
Difficulty Level 4: Expert
Time Estimate 2-3 weeks
Knowledge Area Exploit Development / Heap Exploitation / Type Confusion
Tooling GCC/Clang, GDB
Prerequisites Solid understanding of the heap, function pointers, and GDB.

What You Will Build

A program that manages “notes” for a user. The user can create a note, delete a note, and print a note. The vulnerability is that after a note is deleted (free‘d), the pointer to it is not cleared (it becomes a “dangling pointer”). Your goal is to exploit this to call an arbitrary function.

Why It Matters

This project builds core skills that appear repeatedly in real-world systems and tooling.

Core Challenges

  • Creating the UAF vulnerability → maps to free(ptr) without ptr = NULL
  • Understanding heap allocator behavior (tcache/fastbins) → maps to knowing that a subsequent malloc will return the same memory address
  • Grooming the heap → maps to making allocations and deallocations to control what malloc returns
  • Achieving type confusion → maps to allocating an object of a different type in the place of the freed one
  • Hijacking control flow → maps to calling a function pointer from your fake object

Key Concepts

  • Use-After-Free: OWASP article on UAF.
  • Heap Grooming/Feng Shui: “The Art of Software Security Assessment” by Dowd, McDonald, and Schuh.
  • tcache and fastbins: Understanding modern glibc heap implementation is key. Search for writeups on tcache poisoning.

Real-World Outcome

// Conceptual vulnerable flow
struct Note {
    void (*print_note)();
    char data[64];
};
struct Action {
    long long command_id;
    void (*execute_action)();
};

// 1. Allocate a Note. Its print_note points to a safe function.
Note* note = malloc(sizeof(Note));
note->print_note = print_note_safely;

// 2. Free the note, but the `note` pointer is still valid (dangling).
free(note);

// 3. Allocate an Action. If the size is right, malloc returns the SAME memory.
// We control the data written here.
Action* action = malloc(sizeof(Action));
action->execute_action = system; // Points to system()
// And we write "/bin/sh" into the part of the struct that overlaps `note->data`.

// 4. The program later calls the print function on the dangling pointer.
note->print_note();
// This now executes `system("/bin/sh")` because the memory has been replaced.

Implementation Guide

  1. Reproduce the simplest happy-path scenario.
  2. Build the smallest working version of the core feature.
  3. Add input validation and error handling.
  4. Add instrumentation/logging to confirm behavior.
  5. Refactor into clean modules with tests.

Milestones

  • Milestone 1: Minimal working program that runs end-to-end.
  • Milestone 2: Correct outputs for typical inputs.
  • Milestone 3: Robust handling of edge cases.
  • Milestone 4: Clean structure and documented usage.

Validation Checklist

  • Output matches the real-world outcome example
  • Handles invalid inputs safely
  • Provides clear errors and exit codes
  • Repeatable results across runs

References

  • Main guide: LEARN_C_SECURE_CODING_DEEP_DIVE.md
  • “The Shellcoder’s Handbook”