Shop picoCTF 2021 Solution

Published: April 2, 2026

Description

Best Hacker's Shoppe! You start with 40 coins but the flag costs 100. Find a way to get more coins.

Remote

Connect via netcat to interact with the shop.

bash
nc mercury.picoctf.net 42159

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    Use a negative quantity to gain coins
    Observation
    I noticed the shop's balance formula is 'balance - (price * quantity)', and there was no visible constraint on what quantity value the server would accept, which suggested that entering a negative quantity would flip the subtraction into an addition and raise the balance instead of draining it.
    From the main menu, select option 1 (Buy). Choose the cheapest item (quiet quiches, item 0). When prompted for quantity, enter a negative number like -5. If your balance increases, the validation is missing. Buy enough negative quiches to accumulate at least 100 coins, then buy the flag.
    Learn more

    This is a classic integer sign validation bug. The shop computes your new balance as: balance = balance - (price * quantity). With a negative quantity, the subtraction becomes an addition. This class of bug has caused real-world financial exploits in video games and occasionally in production payment systems where quantity fields lack server-side non-negativity checks.

    Why this vulnerability exists: Developers often validate that a quantity is a number, but forget to validate that it is a positive number. Input validation must cover both type (is it an integer?) and range (is it within acceptable bounds?). The fix is a single server-side check: if quantity <= 0: return error. Client-side validation alone is never sufficient - an attacker can bypass it by sending a crafted request directly with curl or a proxy like Burp Suite.

    Related vulnerability - integer overflow: A different but related bug occurs when a very large positive quantity causes the product price * quantity to overflow a fixed-width integer, wrapping around to a negative value. In C with signed 32-bit integers, multiplying 1000 * 2200000 overflows and produces a negative number. This negative cost then gets subtracted from the balance, again adding instead of deducting. Both vulnerabilities stem from failing to validate arithmetic results before applying them.

    Real-world impact: In 2013, a flight booking platform bug allowed users to purchase tickets with a negative number of passengers, generating a refund larger than the original ticket price. Similar issues have appeared in cryptocurrency exchange order books where negative trade quantities were accepted. These bugs are consistently listed in the OWASP Top 10 under "Insecure Design" - business logic that does not account for adversarial inputs.

  2. Step 2
    Buy the flag and decode the output
    Observation
    I noticed the shop printed the flag as a space-separated list of plain integers rather than readable text, which suggested each number was a decimal ASCII code point that needed to be converted with Python's chr() function.
    Now select option 1 again and buy item 2 (Fruitful Flag). The shop outputs the flag as a list of decimal ASCII values. Use Python to convert each number to its corresponding character. The separator is usually a comma; if commas don't split cleanly, try newlines as the fallback.
    python
    python3 -c "print(''.join([chr(x) for x in [112,105,99,111,67,84,70,123,98,52,100,95,98,114,111,103,114,97,109,109,101,114,95,55,57,55,98,50,57,50,99,125]]))"
    bash
    # Fallback if the values are newline-separated rather than comma-separated:
    python
    python3 -c "import sys; print(''.join(chr(int(x)) for x in sys.stdin.read().splitlines() if x.strip()))" < flag_output.txt

    Expected output

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

    Tried: Interpret the flag output numbers as hexadecimal instead of decimal ASCII.

    The numbers look numeric, so it is tempting to pass them to chr(int(x, 16)), but the shop outputs plain base-10 integers. chr(int('112', 16)) gives the character at code point 274, which is outside printable ASCII and produces garbage. The correct approach treats each number as a base-10 decimal with int(x) or int(x, 10).

    Tried: Split the shop output on commas before passing it to the Python decoder.

    The shop prints the ASCII values separated by spaces, not commas. Splitting on commas leaves the entire line as one unsplit token, so int() raises a ValueError on the space-delimited string. The correct split is .split() (whitespace) or .split(' '), not .split(',').

    Learn more

    The flag is output as decimal ASCII code points separated by spaces. chr(x) converts an integer to the corresponding Unicode/ASCII character. Joining the characters gives the flag string. This encoding is trivially reversible - it provides no obfuscation, but is a common pattern for flag delivery in binary/remote challenges.

    ASCII encoding basics: ASCII maps the integers 0 to 127 to characters. Printable characters start at 32 (space). Uppercase letters are 65 to 90, lowercase 97 to 122, digits 48 to 57. Knowing a few anchor points lets you mentally decode short sequences: 112 = 'p', 105 = 'i', 99 = 'c', 111 = 'o' - which immediately spells "pico," confirming the flag format. This quick check is useful for verifying you have the right values before running the full decode.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • 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.

Flag

Reveal flag

picoCTF{b4d_brogrammer_...}

The shop never validates that quantity > 0 - supplying a negative quantity causes the balance to increase instead of decrease.

Key takeaway

Business logic vulnerabilities arise when an application enforces constraints only on data type (is it a number?) but not on value range (is it positive?). Any arithmetic operation that uses attacker-controlled input without bounding it can be flipped against the system, whether that means negative purchase quantities, zero-price items, or integer overflow into a negative cost. The fix is always server-side range validation; client-side checks are cosmetic and trivially bypassed.

Related reading

Want more picoCTF 2021 writeups?

Useful tools for Reverse Engineering

What to try next