SansAlpha picoCTF 2024 Solution

Published: April 3, 2024

Description

The Multiverse is within your grasp! Unfortunately, the server that contains the secrets of the multiverse is in a universe where keyboards only have numbers and (most) symbols.

Keyboard-restricted shell

SSH to mimas.picoctf.net on port <PORT_FROM_INSTANCE> with password <PASSWORD_FROM_INSTANCE>.

Pressing Enter on an invalid command prints helpful error messages containing letters you can reuse.

The Linux CLI for CTF guide covers the bash parameter-expansion and globbing tricks this challenge forces you to lean on, plus the standard kit for harvesting characters out of error messages.
  1. Step 1Capture an error message into a variable
    Pick an invalid token like $ or ? and trap its error output in a variable. _1=$ 2>&1 runs the bare $ as a command, redirects stderr into stdout, and stores "bash: $: command not found" in $_1. Verify with echo "$_1" - if you see "bash: bash: $: command not found: command not found" you have your character source.
    bash
    _1=`$ 2>&1`
    bash
    echo "$_1"
    Learn more

    The keyboard restriction blocks letters, but parameter expansion (${_1:N:1}) does not require typing letters - it indexes into a string by position. By trapping bash's own error output we get a long string full of letters we can index into.

    $ as a standalone command is the cleanest trigger because it parses, fails immediately, and produces exactly one line: bash: $: command not found. ? works similarly. Wrapping in backticks runs the command in a subshell and captures its output; the 2>&1 redirection sends stderr (where the error actually lives) onto stdout so it can be captured. Without that redirection $_1 would silently end up empty, and the next step would fail with no useful diagnostic.

  2. Step 2Extract characters with parameter expansion
    Bash $&#123;var:offset:length&#125; slices a substring out of a variable. Test locally first: echo "$&#123;_1:9:1&#125;" should print c, and echo "$&#123;_1:10:1&#125;" should print o. From the captured string "bash: bash: $: command not found: command not found", you can pluck b/a/s/h/c/o/m/n/d/t/f/u as needed.
    Learn more

    This is the heart of the trick. The captured error string contains the lower-case alphabet's commonly-used letters in fixed positions, so you can spell short command names like echo, cat, sh, or bash by chaining slices.

    String:    bash: bash: $: command not found: command not found
    Position:  0123456789012345678901234567890123456789012345678901
                        1111111111222222222233333333334444444444555
    
    Useful indices (verify with: echo "${_1:N:1}"):
      ${_1:0:1}  = b
      ${_1:1:1}  = a
      ${_1:2:1}  = s
      ${_1:3:1}  = h
      ${_1:9:1}  = c   <- needed for /bin/echo
      ${_1:10:1} = o   <- needed for /bin/echo
      ${_1:13:1} = m
      ${_1:15:1} = n
      ${_1:17:1} = d
      ${_1:19:1} = t
      ${_1:23:1} = f
      ${_1:24:1} = u
  3. Step 3Locate the flag file with a glob
    Run ./*/* and bash expands the glob to whatever paths exist; the listing here shows the flag at ./blargh/flag.txt, matching pattern ./*/????.???.
    bash
    ./*/*
    Learn more

    ./*/* expands to the list of files exactly one directory deep below the current directory. Bash tries to execute the first match as a command, which (helpfully) fails and prints the file path in the error message - giving you the path even when you can't type letters to ls. The pattern ./*/????.??? later picks out the same path by structure (4-letter name, 3-letter extension).

  4. Step 4Build /bin/echo from globs and slices
    Glob /???/?$&#123;_1:9:1&#125;?$&#123;_1:10:1&#125; into the path /bin/echo. The /??? matches /bin (any 3-char directory under /), and the inner ?c?o pattern matches "echo" via positions 9 and 10 of $_1. The earlier attempt at /usr/bin/cat (/?$&#123;_1:2:1&#125;?/???/??$&#123;_1:19:1&#125;) failed because the ?s? prefix didn't expand to /usr on the box (the glob found no matches and bash printed "bash: /?s?/???/??t: No such file or directory").
    bash
    /???/?${_1:9:1}?${_1:10:1}
    Learn more

    Verify the glob expands the way you expect before using it as the command: echo /???/?${_1:9:1}?${_1:10:1} should print /bin/echo and nothing else. If it prints multiple matches or the literal pattern, the glob is wrong and the command will not execute echo.

    When a glob has no match, bash on this image leaves the literal pattern in place, so the failure surface is "/?s?/???/??t: No such file or directory" - the unmatched pattern itself appears in the error, which doubles as a diagnostic. That's the failure mode you should expect, and how you knew the /usr/bin/cat path's middle ?s? wedge wasn't hitting /usr.

  5. Step 5Read the flag
    Combine the constructed echo with bash process substitution: /???/?$&#123;_1:9:1&#125;?$&#123;_1:10:1&#125; "$(<./*/????.???)" prints the contents of ./blargh/flag.txt without ever typing the literal letters cat, less, or grep.
    bash
    /???/?${_1:9:1}?${_1:10:1} "$(<./*/????.???)"
    Learn more

    $(<file) is a bash builtin that reads the contents of file without spawning cat; it's the bash idiom for "file slurp." Combined with the globbed-up echo, the final command never types any disallowed character but still prints the flag contents.

Flag

picoCTF{7h15_mu171v3r53_15_m4dn355_145...}

There are multiple paths through this challenge - the key insight from the hint 'Where can you get some letters?' is to harvest characters from error messages and use bash parameter expansion to build valid commands.

Want more picoCTF 2024 writeups?

Useful tools for General Skills

Related reading

Do these first

What to try next