Skip to main content

basic-file-exploit picoCTF 2022 Solution

Exploit an unchecked edge case in a simple file-storage program to leak the flag.

Published: July 20, 2023Updated: August 13, 2026

Description

A netcat binary lets you write and read back numbered entries. The read path parses your entry number with strtol, which returns 0 for non-numeric input. There is a hidden branch that prints the flag when the entry number is 0, but the read code only runs once at least one entry exists. So: create one dummy entry, then read with a non-numeric value to land on entry 0 and dump the flag.

Launch a challenge instance from the picoCTF web panel; it shows the host and a port shown here as <PORT_FROM_INSTANCE>.

Connect with netcat and read the provided source. Note the read path uses strtol and a special entry_number == 0 branch.

Explore the menu: option 1 writes an entry, option 2 reads one back.

bash
nc saturn.picoctf.net <PORT_FROM_INSTANCE>

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Read the source and find the strtol quirk
    Observation
    The challenge ships its source, and the description mentions strtol. So read the source for the exact path where strtol's return value becomes an array index without anyone checking the endptr.
    The source ships with the challenge. The read handler does something like entry_number = strtol(input, ...) and then has a branch: if entry_number == 0, print the flag. strtol returns 0 when the input has no leading digits, so any non-numeric input selects entry 0. But the read handler is only reachable after at least one entry has been created, so you must add a dummy entry first.
    bash
    # Read the source: confirm strtol on the read input and an if (entry_number == 0) flag branch.
    bash
    # Confirm the read path requires num_entries >= 1.
    What didn't work first

    Tried: Try reading entry 0 immediately without writing any entry first.

    The read handler checks the entry count before it looks at the entry number at all, so it returns early with a no-entries message and the strtol trick never fires. Create at least one dummy entry to make the read path reachable.

    Tried: Assume the flag branch is triggered by passing the literal string '0' as the entry number.

    '0' is a valid numeric string, so strtol returns 0 exactly as it would for 'a'. The point is that it returns 0 for completely non-numeric input too. The challenge is built around that path, so passing '0' works while missing the actual bug class, the unchecked endptr, which the source review exists to surface.

    Learn more

    strtol parses an optional sign and leading digits and returns 0 if there are none (e.g. the input "a"). Many programs treat that 0 as a valid index. Here entry 0 is the sentinel that prints the flag, so the bug is feeding the parser something it silently turns into 0, not any memory corruption.

  2. Step 2Create one dummy entry, then read a non-numeric value
    Observation
    The read handler sits behind a check that at least one entry exists, and strtol returns 0 for non-numeric input. That makes the exploit two steps: write a dummy entry to pass the guard, then send a non-numeric string to land on the entry 0 flag branch.
    First use the write option to create any entry (the content does not matter; it just makes num_entries >= 1 so the read path runs). Then use the read option and, instead of a number, type a non-numeric string such as 'a'. strtol turns that into 0, the entry_number == 0 branch fires, and the flag is printed.
    bash
    nc saturn.picoctf.net <PORT_FROM_INSTANCE>
    bash
    # 1) Option 1 (write): add any entry (e.g. data 'x', length 1)
    bash
    # 2) Option 2 (read): when asked for the entry number, type a non-numeric string:
    bash
    a

    Expected output

    picoCTF{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_...}
    What didn't work first

    Tried: Type '0' as the entry number during the read step instead of a non-numeric string.

    Entering '0' does trigger the flag branch, since strtol legitimately returns 0 and the entry-number check fires. But the program may first validate that entry 0 does not exist in the array, and the exact control flow depends on the source. Reading it first shows which path is guarded and which is the real way in.

    Tried: Skip the dummy write step and go straight to the read option with a non-numeric string.

    With nothing stored, the entry count is 0 and the read handler errors out before it ever calls strtol on your input. The trick is unreachable until at least one entry exists, so the server just reports no entries and stops.

    Learn more

    The dummy write is the part that is easy to miss: with zero entries the read handler returns early before it ever evaluates the entry number, so the strtol trick alone does nothing. Once one entry exists, reading with non-numeric input lands on entry 0 and prints the flag.

    The proper fix is to validate that the input is actually numeric (check strtol's endptr) and to bounds-check the index, rather than letting a parse failure silently select a privileged slot.

  3. Step 3Collect the flag
    Observation
    Once the entry 0 branch runs, the server prints the flag straight to stdout. The exploit worked, and the flag only needs copying out of the terminal.
    Once the entry_number == 0 branch runs, the server prints the flag.
    Learn more

    Static analyzers and careful review catch the unchecked strtol return; fuzzers surface it by feeding non-numeric and boundary inputs (empty string, "a", "0", very large numbers).

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{M4K3_5UR3_70_CH3CK_Y0UR_1NPU75_...}

Not an integer underflow. strtol returns 0 for non-numeric input, and there is a flag branch for entry_number == 0, but the read path only runs once an entry exists. Create one dummy entry, then read with a non-numeric value (e.g. 'a') to trip the entry-0 branch and print the flag.

Key takeaway

Parsing functions like strtol have well-defined but surprising edges: it returns 0 for non-numeric input rather than signalling an error, and a program that skips the endptr check silently accepts garbage. Trusting a parser's return value without validating success shows up in file parsers, protocol handlers, and CLI tools in any language with C-style conversions. Check the out-parameters and bounds-validate the result before using it as an index.

Related reading

Useful tools for Binary Exploitation

Where to go next