Exercise: Use-After-Free (heap-3) — Solution¶
Tools: nc, GDB, pwntools
This is the solving write-up, for a challenge that is already deployed.
How the challenge is built and deployed is in README.chall.md.
Goal¶
There is no overflow here.
Free the object x, get the same chunk back from the allocator, and write "pico" into its flag field through the pointer the program never cleared.
The bug¶
The global object and its win condition:
check_win() prints the flag as soon as x->flag reads "pico"; it starts as "bico".
free_memory() frees x but never clears it:
Every other option keeps dereferencing x — a textbook use-after-free.
alloc_object() then lets you allocate a chunk of a chosen size and write a chosen string into it.
The returned pointer is thrown away, but that does not matter: if the request lands in the same tcache bin as the just-freed object, glibc returns the identical chunk x still points to, so the write goes through x.
Why size 35¶
glibc's tcache is a per-size-class free list.
A free() pushes the chunk onto its bin, and the next malloc() of that same size pops it straight back.
So the reclaim only works if you ask for the object's own size: sizeof(struct object) is 10 + 10 + 10 + 5 = 35 bytes.
The write¶
The filler fields a/b/c push flag to offset 30 in the struct.
So fill 30 bytes, then "pico", and the last field lines up exactly:
Solving it¶
All three forms do the same thing: free (option 5), allocate size 35 with that payload (option 2), check the win (option 4).
By hand, over nc¶
- Choose
5(Free x). - Choose
2(Allocate object), enter size35, then enter the dataAAAAAAAAAABBBBBBBBBBCCCCCCCCCCpico. - Choose
4(Check for win).
Shell script¶
solve/solve.sh sends those choices with printf:
./solve.sh # against a local ./chall (uses the placeholder flag.txt)
./solve.sh 141.85.224.106 31013 # against the deployed service
pwntools¶
solve/exploit.py does the same in Python:
References¶
man 3 free— clearing the pointer (x = NULL) is the fix, andfree(NULL)is safe- glibc malloc internals (tcache)