I recently competed in PicoCTF 2024. One challenge that I found especially unique was SansAlpha. Here’s how I solved it.

Introduction

When opening the challenge page, we are given the basic premise: this is a Bash terminal where only numbers and symbols are valid input. As letters are something I typically value when working at a command line, it seems our first goal is passing a letter to the terminal.

Logging in, the system appears to be a normal minimized Ubuntu distribution. Entering any letters gives us the following:

Entering numbers works, but doesn’t seem useful on its own:

paste-2024-04-01-id17

We need to find a way to get letters into Bash while only sending it numbers and symbols.

Getting letters

The first thing I did was search the Internet for “Bash no letters”. Eventually, I found this command on commandlinefu.com:

"$(- 2>&1)";${_%%:*}

The author, LinuxMan, has helpfully left us a description.

I don’t know why anyone would use this, I was just messing around tonight and managed to start bash without using any letters and thought I would share. It’s pretty simple, first it tries to execute “-” redirecting stderr to stdout which prints the error “bash: -: command not found” to standard output, then I try to execute “bash: -: command not found” which produces the output “bash: bash: -: command not found: command not found”. lastly, (on the other side of the semicolon) I use the underscore environment variable which refers to the last command run (“bash: -: command not found”) and take out everything after the first “:” character using brace expressions and your left with “bash”

What LinuxMan didn’t know in 2012 is that this command would be the key to SansAlpha. However, it will still take some work to get to the flag.

The first part, "$(- 2>&1)"; can be left just as it is. Running this on its own, we get:

paste-2024-04-01-id18

This is significant because we are able to pass letters into Bash. Right now, those letters are bash: -: command not found - the error output as the author describes above.

If this was all we could send to Bash, we wouldn’t have much of an advantage. However, we’re not limited to passing the entire error. Using brace expressions, we can select individual characters. For example, selecting position 9 gives us c:

paste-2024-04-01-id19

We get the position number by looking at the index (starting at 0) of the character that we want to extract from the string:

b a s h : - : c
0 1 2 3 4 5 6 7 8 9

Further, we can append multiple positions after each other to specify multiple characters, and they don’t have to be in order:

paste-2024-04-01-id20

We can also select multiple letters in a row by changing the second digit in the brace expression:

paste-2024-04-01-id21

We should note that this only gives us access to the characters in the default error message. To get to the flag, we need more letters.

Getting more letters

Recall from before that the Ubuntu distribution is minimized. This means that trying to run a command that isn’t installed will give us a large error message with many more letters to use. To trigger this message, we need to run a command - man is a natural pick as it can be run just by selecting three sequential numbers from the word command in our string.

Running man with an arbitrary character as input gives the message we are looking for:

paste-2024-04-01-id22

Now that we have a command that reliably generates a large portion of selectable text, we can run brace expressions on its output to run more useful commands.

Here, the first part generates the new error, and the second selects the character at index 0, which is now T.

<--------------run man m----------------> <select>
"$("$(- 2>&1)";${_:12:3} ${_:12:1} 2>&1)";${_:0:1}

paste-2024-04-01-id23

Given this capability, we now have enough letters to search and traverse directories. Running ls yields the following:

paste-2024-04-01-id24

on-calastran.txt seems interesting, but turns out to just be a large collection of words. We do not need these words due to our solve method, but other solutions may rely on them instead of the minimized error message.

Traversing to blargh/ requires extracting these characters from the message (and is left as an exercise for the reader). Once there, another ls shows that our remaining files are flag.txt and on-alpha-9.txt. The second file again serves as a collection of words to assist an alternate solve method. Not having an x available in our error message, we can instead run cat * to print every file in the directory at once. This output contains the flag (redacted here).

paste-2024-04-01-id25

Takeaways

I had a lot of fun solving this challenge. While it wasn’t the most “real-world”, it taught me some new things about Bash.

Specific new concepts included:

  • Error redirection
  • Brace expressions
  • Minimized distributions
  • Using the wildcard with cat

If you want to try this challenge or one like it for yourself, it’s available for free on the PicoGym!