Description
You've stumbled upon a mysterious cash register that doesn't keep money - it keeps secrets in memory. Traverse the free list and find all the free chunks to get to the flag. Download: heapedit, heapedit.c, libc.so.6, and the Makefile.
Setup
cat heapedit.cchmod +x heapeditSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Understand the memory layout from the source
Observationheapedit.c ships with the binary, so the allocation size and loop count are readable without reversing anything: six calls to malloc(0x80). Add glibc's 0x10 header and the real stride between chunks is 0x90.Read heapedit.c. The program creates 6 chunks of size 0x80 (128) bytes each in a loop, frees them in reverse order, then asks you to provide the address of each free chunk. Each chunk has a malloc header of 0x10 (16) bytes, so each block takes 0x90 (144) bytes total. The chunks are allocated sequentially in memory.bashcat heapedit.cWhat didn't work first
Tried: Assume each chunk is exactly 0x80 bytes apart because that is the malloc argument
malloc(0x80) hands back 128 bytes of usable space, and glibc puts a 16-byte header in front holding the chunk size and flags. The allocator therefore steps in 0x90 increments, not 0x80, and using the smaller stride puts every address 16 bytes low.
Tried: Assume the free list order matches allocation order (chunk 0 first freed, chunk 0 at head)
The source frees in reverse, from the last chunk down to the first, so the tcache head is chunk 0 and the tail is chunk 5, the same order as allocation. It is tempting to assume the last-in-first-out list reverses that, but chunk 0 is freed last and therefore sits at the head.
Learn more
When
malloc(0x80)allocates 128 bytes, the allocator adds a 16-byte header (containing size information), making each chunk 0x90 bytes wide. Since the 6 chunks are allocated sequentially, they lie back-to-back in memory: chunk 0 at address X, chunk 1 at X + 0x90, chunk 2 at X + 0x120, etc.The tcache free list stores chunks in LIFO order. Since the chunks are freed in reverse order (chunk 5 first, chunk 0 last), the free list head is chunk 0, then chunk 1, etc. The program gives you the address of the free list head and asks you for the addresses of all 6 chunks.
Step 2Compute the addresses by adding 0x90 repeatedly
ObservationThe program prints the free list head and then asks for all six chunk addresses in turn. With the stride known, that is arithmetic; no debugger required.Connect to the challenge. It tells you the starting address of the free list head. Compute each subsequent chunk address by adding 0x90. Enter all 6 addresses to get the flag.bashnc <HOST> <PORT_FROM_INSTANCE>bash# The program prints: 'Start of free list: 0x<ADDRESS>'bash# Compute: addr1 = start, addr2 = start + 0x90, addr3 = start + 0x120, etc.pythonpython3 - <<'EOF' start = int(input("Enter the free list start address (hex, e.g. 0x55a3f2b9b510): "), 16) for i in range(6): print(hex(start + i * 0x90)) EOFWhat didn't work first
Tried: Enter the addresses in decimal instead of hex because the python script prints hex strings
The parser wants hexadecimal, the same format it printed the list head in. Paste decimals and each answer is rejected or compared against the wrong range.
Tried: Use GDB to read the tcache forward pointers out of the freed chunks instead of computing addresses arithmetically
GDB works locally, and the remote addresses differ anyway thanks to ASLR and a different heap base. The head address the server prints is the authoritative starting point, and stepping 0x90 from it is both simpler and correct whatever ASLR does.
Learn more
The key insight is that malloc allocates chunks sequentially in memory for a fresh heap. Each chunk is 0x80 (128) bytes of user data plus 0x10 (16) bytes of header = 0x90 (144) bytes per chunk. Add 0x90 to move from one chunk to the next.
The tcache is the "Thread Local Allocation Cache" - a per-thread LIFO cache for small allocations. Learning to reason about tcache free list traversal is foundational for heap exploitation challenges. This challenge teaches the basic concept before moving to tcache poisoning attacks.
Interactive tools
- pwntools Payload BuilderPack integers into little-endian bytes (p32 / p64), unpack bytes back to integers, and build flat ROP payloads with offset-based insertion.
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
- Cyclic Pattern GeneratorGenerate de Bruijn cyclic patterns and find buffer overflow offsets. The browser equivalent of pwntools cyclic and cyclic_find.
Flag
Reveal flag
picoCTF{08324d1e3dca69bf...}
The program tells you the free list start address. Compute the 6 chunk addresses by adding 0x90 each time (0x80 user data + 0x10 malloc header = 0x90 per chunk). Enter them all to get the flag.