Dont trust that pimple faced junior sysadmin with your holy passwords in shell scripts? Want to store sensitive data in public scripts? (ಠ_ಠ) Trying to hide your source from those pesky insert scripting language here hackers? Something something something question?

No problem, just use the gpg cli tool, a Bourne shell script, and a weird Bourne/BASH trick to encrypt your shell script (or really any script that can be piped to its respective interpreter) with a passphrase using AES256 (or any of the other algorithms gpg supports) and then wrap it with a Bourne script that decrypts the ciphertext payload and pass it to the respective interpreter.

Ridiculous? yes. Hilarious? somewhat. Useful? questionable. In this post i’ll go over a Bourne tool I wrote, raziel, that automates this process, and how it works. Its extremely simple taking, currently, two paramenters PAYLOAD_FILE and OUTPUT_FILE and an optional argument -interpreter. The purpose of the final argument is to specify which interpreter (…mhm) the input script should be piped too once the wrapper has decrypted it, if it isn’t used the default is sh -.

View project on GitHub

The final Bourne file that is created contains an extremely basic shell wrapper and the input script ciphertext encoded using base64.

So how does it work?

The trick that makes this all work is actually quite simple. Both the Bourne and BASH interpreters forgo all the fancy AST, recursive token based parser nonsense and just evaluate scripts line by line.

Because of this we can append whatever random crap we want to the end of a shell script and as long as exit is called before the interpreter begins to parse the nonsense then everything will be just fine. All raziel needs to do then is come up with a chain of commands to take a script and encrypt it, base64 encode the ciphertext, and append this to the end of a wrapper that can decrypt and run it. An example output script might look like this (note: the appended data here is encoded using base64 but it could be appended as binary data, or uuencoded if you want, base64 is just easier on the eyes)

# raziel loader - v0.0.2
run_payload() {
	local match=$(grep --text --line-number '^PAYLOAD:$' $0 | cut -d ':' -f 1)
	local payload_start=$((match + 1))
	local gpg_agent=$GPG_AGENT_INFO
	tail -n +$payload_start $0 | base64 --decode | gpg -q -d | sh -
exit 0

The encryption and decryption chains are both extremely simple, taking advantage of lots of UNIX pipes, gpg, and base64

# encrypt
cat $input_script | gpg -q -c -crypto-algo=AES256 | base64

# decrypt
match=\$(grep --text --line-number '^PAYLOAD:$' \$0 | cut -d ':' -f 1)
payload_start=\$((match + 1))
tail -n +$payload_start $0 | base64 --decode | gpg -q -d | $interpreter

Why Bourne?

BASH is super common, but Bourne is even more so and somewhat more POSIX-y, so since we want to create as portable wrappers as possible why not Bourne? Plus they are both relatively compatible…

Since raziel weighs in at just under 60 SLOC lets just take a look at an annotated version of the source

#                          d8b          888
#                          Y8P          888
#                                       888
# 888d888 8888b.  88888888 888  .d88b.  888
# 888P       `88b    d88P  888 d8P  Y8b 888
# 888    .d888888   d88P   888 88888888 888
# 888    888  888  d88P    888 Y8b.     888
# 888    `Y888888 88888888 888  `Y8888  888
# raziel - v0.0.3
#   self-decrypting shell scripts

## Main function
#    this builds the wrapper and payload and combines them
#    into a single Bourne shell script
raziel() {
  # Boring argument check stuff
  if [ "$#" -eq "0" ] || [ "$#" -eq "3" ] || [ "$#" -gt "4" ]; then
    echo "Usage: $0 PAYLOAD_FILE OUTPUT_FILE [-interpreter INTERPRETER]"
    echo "  default interpreter is \"sh -\", but can be set to anything (..?)"
    echo "  (e.g. \"python3\", \"ruby\", \"perl\", etc)"
    exit 1

  # Disable gpg_agent temporarily so we don't get the annoying
  # pop-up box...
  # Encrypt the content of the input script using gpg
  input_script=`cat $1 | gpg -q -c -crypto-algo=AES256 | base64`
  # Restore gpg_agent information
  # Get the filename of the output script
  # Set the interpreter the input script should be piped to
  if [ "$#" -eq "4" ] && [ "$3" = "-interpreter" ]; then
    interpreter="sh -"

  # Bourne shell wrapper that decrypts and executes the encrypted
  # payload
  # raziel loader - v0.0.2
  run_payload() {
    # Find the start of the 'PAYLOAD' section
    local match=\$(grep --text --line-number '^PAYLOAD:$' \$0 | cut -d ':' -f 1)
    # Set the start of the payload to the next line
    local payload_start=\$((match + 1))
    # Disable gpg_agent pop-up
    local gpg_agent="\$GPG_AGENT_INFO"
    # Decrypt and execute payload with specified interpreter
    tail -n +\$payload_start \$0 | base64 --decode | gpg -q -d | $interpreter
    # Reset gpg_agent stuff
  # Exit, this is very important as it will terminate the Bourne
  # interpreter before it begins to parse the payload
  exit 0"

  # Add wrapper to output file
  echo "$run_template" >>$output

# Add payload to output file
  echo "PAYLOAD:" >>$output
  echo "$input_script" >>$output

  # Make wrapper executable
  chmod +x $output

raziel "[email protected]"

I mostly wrote raziel because it was the funnest way I could think of using this shell script trick, but if anyone comes up with an actually useful purpose please let me know! (I can be contacted via email)