fs
Author: 0xIDA
Platform: FlagYard · Category: pwn · Difficulty: Hard
Solvers
Summary
Hard heap note challenge on glibc 2.31 built without tcache. UAF (free without nulling content) plus edit/view length derived from the chunk size field enable an off-by-one size corruption, unsorted-bin libc leak, fastbin poison into __malloc_hook-0x33, and a one_gadget shell.
Challenge Description
there is always a way
- Protocol: TCP
- Files:
fs,libc.so.6(2.31 no-tcache),ld.so.2 - Protections: Full RELRO, no canary, NX, PIE
- Menu: create / edit / view / delete (indices 0–4)
- Fixed
malloc(0x66)→ 0x70 chunks
Vulnerability Analysis
Struct layout (stack)
struct file { char name[8]; char *content; }; // 0x10 each, 5 slots
Bugs
- UAF:
deletecallsfree(content)thenmemset(slot, 0, 8)— only clears the name, not the content pointer. - Size-derived R/W: edit/view use
len = *(uint64_t*)(content - 8) - 8(raw size field, flags included) → for in-use 0x71,len = 0x69into 0x60 usable user data → overflow into next chunk (prev_size + 1 byte of size). Classic off-by-one on next size LSB. - create already
read(0x66)into 0x60 usable (6-byte overflow into next prev_size).
Handout libc
Path strings show glibc_2.31_no-tcache — no tcache freelist; 0x70 frees go to fastbin.
Exploitation
Stage 1 — libc leak
- Create five 0x70 notes.
- Edit note 0 with
0x60 padding + fake prev_size + 0xe1so note 1’s size becomes0xe1(span0xe0bytes → lands on note 3). - Delete note 1 → free large chunk into unsorted bin (size
0xe0> max fast0xa0). - View UAF note 1 →
fd=main_arena+0x60= libc +0x3b5bc0.
Stage 2 — fastbin → malloc_hook
- Create once to drain/split unsorted.
- Free notes 3 and 4 into fastbin (
4 → 3). - UAF-edit note 4’s
fdto chunk pointer__malloc_hook - 0x33(not the user pointer).
Nearby misaligned field: size0x7fatmalloc_hook-0x2b→chunksize0x78maps to the same fastbin index as0x70. - Alloc once (pops 4), alloc again into fake user at
malloc_hook-0x23. - Write
A*0x23 + one_gadget→ set__malloc_hook.
one_gadget used: 0xc4eef (execve("/bin/sh", r13, r12) with NULL constraints — satisfied here).
Stage 3 — trigger
Create again: after reading the name, malloc(0x66) hits the hook → shell → cat flag.
# Core offsets (handout libc)
LIBC_UNSORTED = 0x3b5bc0
ONE_GADGET = 0xc4eef
# fake_chunk = libc.sym['__malloc_hook'] - 0x33
Flag Retrieval
Live on tcp.flagyard.com:27644:
uid=1000 gid=1000 groups=1000
FlagY{3ddc0a11c5b726276bc1c3aa378093cc}
Submit: Success.
Key Takeaways
- Confirm tcache presence (this handout is explicitly no-tcache) — changes freelist attack surface.
- Fastbin
fdis a chunk pointer; targetmalloc_hook-0x33, not-0x23. - Size
0x7f/0x78shares fastbin index with0x70— classic misaligned hook write. - Edit/view length from raw chunk size is a powerful OOB when combined with UAF.
Tools Used
- pwntools, one_gadget, objdump, checksec
- Flagyard MCP (spawn/submit)
Files
- Challenge binary + handout libc (not redistributed)
- Solver: published on R2 (see Solvers above)