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)

#!/bin/sh
# 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
	GPG_AGENT_INFO=
	tail -n +$payload_start $0 | base64 --decode | gpg -q -d | sh -
	GPG_AGENT_INFO=$gpg_agent
}
run_payload
exit 0
PAYLOAD:
jA0EAwMCIOBW/Y+CR7lgyekGuyeQHVv32eG/ZowkeaNRXVs6lHhqjvFUWlfWKKJ3edcta9OK
XLBnjv4ybVIVnYOB9yG8i+aOEJM9FoU2dS9a9HGMi+0CXdC91mBwRMr9eQRl67WLWSMP8Hti
AxrvztSxfD4EfezyOZus1XFyG9Mp8Bk6wCjnyll1A6UD1C04Rx17KkPPovvCxXq4h0OLrppa
8p+b9l0q51YTCOv84bO2WLSpUAEFY518+9jCDqkVBeJ66XXXtn0T4p1VOBEWlzY5TB3xspKG
tGMdZtsbAqfMQSwAjG+Na//8jRTZfH6ghlu0r+9I+1bip8x9a9KCQzxZvwlMPzdGQwqC1qsV
xjlQXcKbUFx7rl+HS7MY9A6qwfCL6sSHTNFlTPq/JtWKckvNYxyyMtcgqL3cjTd077yEi3Al
5uXEaQFiFEpwcPtAX6+7yf9t5FY3fqYtRje7DCDlf5ckP9B+CyZ01BcfOGDIcuOAE3q1/15R
3ioqp5d4K7NS9jwAd8Zeecf0HlAbCMpcz/k/iO7JoxSEVY8+UidPqXxKuV6lkVFb4Ept/BuB
YOP8Z6Rpr5w9LxX0Rtd7I3+bixuID9f+KvsK66nGqQrXRuXgT96gD4v5ulHRVsbJB5cTOm1J
wjE5IllHdhfsntNrIRI0Tmr1Rm4H6vx6D222f9J94c9hnKEa2GdRkYZaUsAkUu5X+7YXm5Vr
EUEbU+W1Ddbd52d8aRBSGzMvc7m9pX5XrsU1KHYM5yP4IduzRgO40gG9LhdOgsYc80wOle2Q
4j79DNrgdMb/9oRL+7iQ/ot1Pw7qXLNY9eOJq3BovA4WGauUK5pWQxcftUUioQ/rHNKC1MKY
TFrZF7LIk12wfftUj6zzka3R4vFlClup1Lxs0GDA4cRTi2yYAUhk6yR225R4fBrizKGXcRPN
2CYklk+ds5Pw7WkmdnphWiE4erXdYn8YfbkMWNqY9ABnO5hvhJ+hYRqy2KqbTWUl2e7p1GPk
LgUc

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

#!/bin/sh
#
#                          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
  fi

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

  # Bourne shell wrapper that decrypts and executes the encrypted
  # payload
  run_template="#!/bin/sh
  # 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"
    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
    GPG_AGENT_INFO="\$gpg_agent"
  }
  
  run_payload
  
  # 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)