Originally published February 26, 2018 @ 7:37 pm

Unix shell scripting language is run by the command-line interpreter and, as such, can be read and understood by anyone with sufficient access and experience. Sometimes this is not a good thing. Sometimes you want people and applications to be able to run the script but not necessarily look under its hood.

Various obfuscation techniques for Unix shell scripts go back decades. The methods include replacing variable names with odd-looking strings; removing or adding spaces and comments; inserting bogus functions that do nothing; replacing Latin letters with international or extended characters.

This by no means truly conceals a script’s nature, but obfuscation can render the script nearly indecipherable. This is especially true for more complex scripts. Here’re some of the script obfuscation tools you can use.

The good old obfsh you can get from here. Just run obfsh -h to see a summary of available options. You can add this convenient alias to your .bashrc so you don’t have to remember those options:

alias obfuscate='obfsh -c 2 -d 1 -e 2-27 -g 23-2+100-309 -i -f'

Consider this simple script that tells you if the argument is a positive or negative integer (or not an integer at all):

#!/bin/sh
i='^(-)?[0-9]+$'
if ! [[ $1 =~ $i ]] ; then
  echo "error: $1 is not an integer" >&2; exit 1
fi
if [ $1 -gt 0 ]; then
  echo "$1 is positive"
elif [ $1 -lt 0 ]; then
  echo "$1 is negative"
elif [ $1 -eq 0 ]; then
  echo "$1 is zero"
fi

And here’s the obfuscated version using the alias set above:

#!/bin/sh
: '$/+d      0)    Zz1IF G   ]
.o      F=         S     ▒0      $     5.   1   Pj
    tN*d    b   4 &%▒uQ  J34      2l
                       '
                           a=1; c=0
             # comment
                      i='^(-)?[0-9]+$'
                   f_() { while read l; do echo $l; done < $f ; }
       # comment
                   if ! [[ $1 =~ $i ]] ; then
                     echo "error: $1 is not an integer" >&2; exit 1
                         fi
               if [ $1 -gt 0 ]; then
       echo "$1 is positive"
                        elif [ $1 -lt 0 ]; then
                          echo "$1 is negative"
         elif [ $1 -eq 0 ]; then
                           echo "$1 is zero"
                          fi
: '$/+d      0)    Zz1IF G   ]
.o      F=         S     ▒0      $     5.   1   Pj
    tN*d    b   4 &%▒uQ  J34      2l

Not terribly confusing, but better than nothing.

Another option that produces a somewhat more confusing output is bash-obfuscate Node.js CLI utility. You can check it out here. Here’s what it does to the script from the previous example:

apt-get install npm nodejs
ln -s /usr/bin/nodejs /usr/bin/node
npm install -g bash-obfuscate
bash-obfuscate sample.sh -o sample2.sh

The result is much better, but still fairly easy to figure out and reverse:

z="
";Fz=' [[ ';mz=']; t';pz='1 -e';Wz=' 1';Kz='en';gz='tive';Dz=']+$'\''';Cz='[0-9';Yz='if [';jz=' [ $';Vz='exit';Pz='is n';iz='elif';Iz=' ]] ';rz='zero';Lz='echo';az='0 ];';Ez='if !';ez=' is ';Mz=' "er';nz='hen';dz=' "$1';fz='posi';Hz='~ $i';oz='nega';Oz=' $1 ';hz='"';Sz='tege';Xz='fi';Az='i='\''^';Jz='; th';Gz='$1 =';Rz='n in';Bz='(-)?';cz='n';Uz='&2; ';bz=' the';Nz='ror:';qz='q 0 ';lz='t 0 ';Tz='r" >';Qz='ot a';kz='1 -l';Zz='-gt ';
eval "$Az$Bz$Cz$Dz$z$Ez$Fz$Gz$Hz$Iz$Jz$Kz$z$Lz$Mz$Nz$Oz$Pz$Qz$Rz$Sz$Tz$Uz$Vz$Wz$z$Xz$z$Yz$Oz$Zz$az$bz$cz$z$Lz$dz$ez$fz$gz$hz$z$iz$jz$kz$lz$mz$nz$z$Lz$dz$ez$oz$gz$hz$z$iz$jz$pz$qz$mz$nz$z$Lz$dz$ez$rz$hz$z$Xz"

Perhaps the best option I’ve found so far is the shell compiler. Here’s a quick example:

git clone https://github.com/neurobin/shc.git
cd shc
./configure
make
make install

echo -e '#!/bin/bash\necho this is a test' > test.sh && chmod 750 test.sh
shc -U -f test.sh -o testbin

file testbin
testbin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped

./testbin
this is a test

To an extent, this can even be used to obfuscate a password inside the compiled script. For example, if you run strings testbin | grep test you will see nothing. Having said that, there is a better way to hide passwords in interactive scripts using gpg.