Java Script Kiddie picoCTF 2019 Solution

Published: April 2, 2026

Description

The image is formed by combining two hex strings. Solve the key to reveal the image.

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1
    View the page source
    Observation
    I noticed the challenge was called 'Java Script Kiddie' and involved a key input on a web page, which suggested the scrambling logic and hardcoded byte data would be visible in the client-side JavaScript source.
    Open the challenge URL and view page source (Ctrl+U). Find the JavaScript that combines a user-supplied key with a hardcoded hex string to produce the bytes of a PNG image.
    Learn more

    The JavaScript performs cyclic block shifting: for each digit position i in the 16-character key, the digit value is used as a row-offset when reassembling PNG bytes from a scrambled byte array fetched from /bytes. The formula is: result[(j*LEN)+i] = bytes[(((j+shifter)*LEN) % bytes.length)+i]. No XOR is involved.

    A PNG always starts with the magic bytes: 89 50 4E 47 0D 0A 1A 0A. This known plaintext lets you brute-force or derive the key.

  2. Step 2
    Extract the hardcoded hex string
    Observation
    I noticed the JavaScript source referenced a hex string fetched from /bytes that was used as the raw material for reconstruction, which suggested copying it locally would let me run the brute-force offline without repeated network requests.
    Copy the hardcoded hex string from the JavaScript source. Each pair of hex digits is one byte of the scrambled PNG byte array.
    Learn more

    The key is 16 digits long (one digit per column of the output). Each of the 16 key digit positions can be solved independently: for each position i, try all 10 digit values (0-9) and pick the one that places the correct PNG magic byte at position i of the output.

  3. Step 3
    Brute-force or derive the key
    Observation
    I noticed the JS formula maps each key digit independently to one column of the output and that PNG files always start with the fixed 8-byte magic sequence 89 50 4E 47 0D 0A 1A 0A, which meant I could use those known bytes as an oracle to solve each of the 16 digit positions separately with only 10 candidates each.
    Write a script to try all possible key values (0-9 for each digit position). For each candidate digit at position i, apply the cyclic block-shift formula and check if the resulting byte matches the expected PNG magic byte at that position. Display the valid image once all 16 digits are found.
    python
    python3 -c "
    # Minimal brute-force skeleton
    hex_str = 'PASTE_HEX_HERE'
    scrambled = bytes.fromhex(hex_str)
    png_magic = bytes([0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A])
    LEN = 16  # key length from JS source
    num_rows = len(scrambled) // LEN
    key = []
    for i in range(LEN):
        for shifter in range(10):
            # JS formula: result[(j*LEN)+i] = bytes[(((j+shifter)*LEN) % bytes.length)+i]
            candidate = scrambled[(((0 + shifter) * LEN) % len(scrambled)) + i]
            if candidate == png_magic[i] if i < 8 else True:
                key.append(str(shifter))
                break
    print('Key:', ''.join(key))
    "
    What didn't work first

    Tried: Treating the scrambling as XOR and trying to XOR the hex string against the PNG magic bytes to recover the key.

    The JavaScript does cyclic block-shifting (row reordering), not XOR. XOR-ing the scrambled bytes against the magic bytes produces nonsense offsets. The correct approach reads the formula result[(j*LEN)+i] = bytes[(((j+shifter)*LEN) % bytes.length)+i] and interprets shifter as a row index, not a bitmask.

    Tried: Brute-forcing all 16 digits together with a nested loop over 10^16 combinations.

    10^16 iterations would take millions of years. The formula maps each key digit independently to exactly one column of the output, so each position i can be solved in isolation with at most 10 candidates, collapsing the search to 160 checks total.

    Learn more

    Because each digit controls one column of the output independently, you can brute-force each of the 16 positions separately (10 candidates each) instead of trying all 10^16 combinations. Check whether the byte at the target column matches the expected PNG magic byte for that position.

  4. Step 4
    Enter the key and view the image
    Observation
    I noticed the challenge page had a key input field that triggered image rendering client-side, which suggested submitting the recovered key there would cause the JavaScript to reconstruct the PNG bytes and display the flag image in the browser.
    Submit the discovered key in the input field on the challenge page. The image rendered will contain the flag.
    Learn more

    Alternatively, use Python Pillow or the browser's Canvas API to render the resulting bytes as an image and save it as a PNG file for reading.

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.
  • Base64 & Base32 DecoderDecode Base64 and Base32 strings with auto-detection. Multi-layer mode unwraps nested encodings automatically.
  • Recipe ChainStack decoders into a pipeline: Base64, hex, ROT, XOR, Morse, URL, Atbash, Vigenère, and more. Magic mode auto-discovers the chain. Bookmark the URL to save it.

Flag

Reveal flag

picoCTF{...}

Brute-force the 16-digit key by solving each column independently: for each position, find the digit that places the correct PNG magic byte at that column via the cyclic block-shift formula.

Key takeaway

Any scrambling scheme implemented in client-side JavaScript is transparent to an attacker because the source is fully readable. When the target output format has a known fixed header, such as PNG magic bytes, those bytes act as a known-plaintext oracle that lets you recover each key component independently rather than exhaustively searching the full key space. This divide-and-conquer approach to brute-forcing collapses the search space exponentially and applies anywhere key material maps independently to disjoint sections of output.

Related reading

Want more picoCTF 2019 writeups?

Useful tools for Web Exploitation

What to try next