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)withoutptr = NULL - Understanding heap allocator behavior (
tcache/fastbins) → maps to knowing that a subsequentmallocwill return the same memory address - Grooming the heap → maps to making allocations and deallocations to control what
mallocreturns - 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.
tcacheandfastbins: Understanding modernglibcheap implementation is key. Search for writeups ontcachepoisoning.
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
- Reproduce the simplest happy-path scenario.
- Build the smallest working version of the core feature.
- Add input validation and error handling.
- Add instrumentation/logging to confirm behavior.
- 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”