How to minify/obfuscate a bash script [closed] - bash

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
Of course a bash file cannot be truly obfuscated and will always be readable. And I don't want to wrap them in some binary package.
And renaming local variables wouldn't be worth the trouble.
But is there a reliable simple bash obfuscator or minifier which at least removes all the indentation, all the empty lines and all whitespace without breaking anything? And especially the comments and commented out portions of the script which might contain sensitive documents or information?
I would be afraid of simple grep/sed-lines to do this because "HEREDOCs" must not be modified of course, so a little bit of real parsing is necessary.
Maybe there's a tool to do this, that would be great!

:P here is something funny.
say your script is named origin and the obfuscated one is named obsf.
here is origin:
#!/bin/sh
echo "fooo"
here is obsf
$ echo "echo $(base64 origin)" > obsf
$ cat obsf
echo IyEvYmluL3NoCmVjaG8gImZvb28iCg==
$ chmod +x obsf
now rm origin and run obsf like this:
$ sh obsf | base64 -d | sh
fooo
heh :3

Here is a tool that I created for bash scripts minification: https://github.com/precious/bash_minifier – it tries to remove all comments and as many spaces/tabs/newlines as possible. It is also available as a service here https://bash-minifier.appspot.com/.
To minify your bash script run this command:
python minifier.py /path/to/shell/script.sh

Even though this is an old question, it seems to be popular on Google. I was also looking for a bash minifer/obfuscator, and didn't like the one in the answer.
I didn't want to add gibberish either, or compile the script. So I wrote one that did what I wanted in Perl, and put it on GitHub at https://github.com/Aralhach/bashobfus/tree/master
It can:
Flatten indentation
Remove full-line comments (except the initial hashbang (#!)) and blank lines
Renames all lowercase variables in basic declarations (to avoid renaming variables like PATH), for loops, array access, and 'read' statements.
There might be some case where it fails, but I tested it with a fairly big bash script, and the listed cases of variable replacing popped up. It also leaves variables within single quotes (') alone --this popped up when printing an AWK script-- but replaces between single quotes (') when the statement is already inside double quotes (") --this popped up when printing an MySQL statement.
This makes me think I've covered all the big use cases, but I've been wrong before. If you find any bugs, feel free to report them (or fix them! :D). I was also thinking of adding a feature to join short lines into one with ";" but the cases were too many to analyze while making my deadline.
I hope people find it useful!

Original file script.sh:
#!/usr/bin/env bash
echo "foo"
Create other.sh
$ echo '#!/usr/bin/env bash' > other.sh
$ echo "echo '$(base64 script.sh)' | base64 -d | sh" >> other.sh
$ chmod +x other.sh
Result (cat other.sh):
#!/usr/bin/env bash
echo 'IyEvdXNyL2Jpbi9lbnYgYmFzaAplY2hvICJmb28iCg==' | base64 -d | sh
Try:
$ ./other.sh
foo

Minification and Obfuscation of a shell script are two different things.
Minification means reducing the size of a script by removing all unnecessary characters from source code without changing its functionality. Obfuscation on the other hand means making the script difficult, if not impossible, to read.
Minification:
To minify a big script, you can run the following code against the actual script you want to minify:
#!/bin/sh
Script=${1}
if [ ! -z "${Script}" ] && [ -f ${Script} ] ; then
CurrenTime=$(date | sed -e 's~ ~_~g' -e 's~:~~g')
cp ${Script} ${Script}_${CurrenTime}
#### Remove all empty lines
#### Remove lines that begin with spaces and a comment sign #
#### Remove all comment lines (meaning, lines that begin with a "#")
awk '
(/.*/ || /#!/) && (!/^#$/) &&
(!/^#[[:blank:]]/) && (!/^#[a-z]/) &&
(!/^#[A-Z]/) && (!/^##/) &&
(!/^\t#/) && (!/^[[:space:]]*$/) &&
( /^#.*!/ || !/^[[:space:]]*#/)
' ${Script} | sed 's_^[[:space:]]*__g' > ${Script}.tmp 2>/dev/null
#' ${Script} > ${Script}.tmp 2>/dev/null (comment out the above line and uncomment this line if your HEREDOCS are affected)
ExitCode=$?
if [ ${ExitCode} -eq 0 ] && [ -s ${Script}.tmp ] ; then
echo
echo "SUCCESS: Your newly [ minified ] script can be found here [ ${Script}.tmp ]."
echo "Review the script [ ${Script}.tmp ] and if happy with it, replace your original script with it!"
echo "NOTE: Your original script [ ${Script} ] was backed up as [ ${Script}_${CurrenTime} ]!"
echo
exit 0
else
echo
echo "FAILURE: Unable to [ minify ] the specified script [ ${Script} ]!!!"
echo
exit 2
fi
else
echo
echo "USAGE: ${0} <your-script>"
echo
exit 3
fi
Note, minification tends to make a difference only if the script being minified is big...with several hundred or even thousands of lines. I was able to trim off a few Megabytes from a script using the above code.
Obfuscation:
After the above minification is completed, you can just stop right there if size reduction is what you're going for. If however, after the minification, you also wish to obfuscate your script as well, you have options.
The simplest way to obfuscate your script is through the use of encryption tools such as as Openssl.
To encrypt your script using Openssl:
1. cat <your-script> | openssl aes-128-cbc -a -salt -k "specify-a-password" > yourscript.enc
OR
2. openssl aes-128-cbc -a-salt -in <path-to-your-script> -k "yourpassword"
To decrypt a script using Openssl (notice the '-d'):
1. cat yourscript.enc | openssl aes-128-cbc -a -d -salt -k "specify-a-password" > yourscript.dec
OR
2. openssl aes-128-cbc -a -d -salt -in <path-to-your-script> -k "yourpassword" > yourscript.dec
Encryption/Obfuscation:
If your ultimate goal is to make it difficult to others to read your script, try pasting it here to have an encrypted copy generated for you.
In case you change your mind about SHC, the latest version can be downloaded here.

A Tool to obfuscate shell scripts:
http://www.comp.eonworks.com/scripts/obfuscate_shell_script-20011012.html
Sort of silly to do, but that's up to you. There are also ways to "compile" your shell script into an executable. This post's accepted answer gives several links with tools to do that.

Based on c00kiemon5ter idea, here you have THE script
Your twisted recursive minds will love it, since this is not the original script, but the obfuscated(obfuscated(original))
#!/bin/bash
#
# Usage:
# obfuscate scrript.sh > script_obfuscated.sh
#
PIXIE=$(mktemp)
base64 -d >${PIXIE}<<DIXIE
IyEvYmluL2Jhc2ggClBJWElFPSQobWt0ZW1wKQpiYXNlNjQgLWQgID4ke1BJWElFfTw8RElYSUUK
SXlFdlltbHVMMkpoYzJnS2FXWWdXeUFnTFdZZ0lpUXhJaUJkSUFwMGFHVnVDbU5oZENBOFBGQkpX
RWxGSUFvaklTOWlhVzR2WW1GegphQ0FLVUVsWVNVVTlYQ1FvYld0MFpXMXdLUXBpWVhObE5qUWdM
V1FnSUQ1Y0pIdFFTVmhKUlgwOFBFUkpXRWxGQ2lRb1ltRnpaVFkwCklDUXhLUXBFU1ZoSlJRcHpi
M1Z5WTJVZ1hDUjdVRWxZU1VWOUNuSnRJQzF5WmlCY0pIdFFTVmhKUlgwS1VFbFlTVVVLWlhocGRD
QXcKQ21acENtTmhkRHc4VGtWU1JBb2dJQ0IxYzJGblpUb2diMkoxYzJOaGRHVWdjMk55YVhCMENn
b2dJQ0JYYVd4c0lHZGxibVZ5WVhSbApJQ0p6WTNKcGNIUXViMkp6YUNJS1RrVlNSQW89CkRJWElF
CnNvdXJjZSAke1BJWElFfQpybSAtcmYgJHtQSVhJRX0K
DIXIE
source ${PIXIE}
rm -rf ${PIXIE}

Related

Terminate a while loop with multiple conditions in a bash script file [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 12 months ago.
Improve this question
I tried everything, but I still can't get the while loop to stop based on the following condition:
#!/bin/sh
clear
counter=1
breakCond=$(tail -n 1 /home/a/Desktop/Triple_Graphs/11_nodes/Res/Com_Output.txt|sed -r 's/^([^.]+).*$/\1/; s/^[^0-9]*([0-9]+).*$/\1/')
cd /home/a/Desktop/cliquer-1.21
rm /home/a/Desktop/Triple_Graphs/11_nodes/Res/Com_Output.txt
clear
Mu=$(head -1 /home/a/Desktop/Triple_Graphs/11_nodes/Mu.txt)
while [[ $counter -le 97 && $breakCond -ne $Mu ]]
do
#echo $counter| tee -a outErr.txt
./cl -u /home/a/Desktop/Triple_Graphs/11_nodes/G_$counter.txt &> /home/a/Desktop/Triple_Graphs/11_nodes/Res/calculated_$counter.txt
#echo -e "calculated_$counter.txt \n"
output=$(tail -1 /home/a/Desktop/Triple_Graphs/11_nodes/Res/calculated_$counter.txt)
echo $output>>/home/a/Desktop/Triple_Graphs/11_nodes/Res/Com_Output.txt
((counter++))
breakCond=$(tail -n 1 /home/a/Desktop/Triple_Graphs/11_nodes/Res/Com_Output.txt|sed -r 's/^([^.]+).*$/\1/; s/^[^0-9]*([0-9]+).*$/\1/')
done
The second condition in my while loop does not work. I'm reading two values from txt files ("breakCond" and "Mu") and attempting to compare them.
If you want to use Bash features, you need to make sure the script is executed by Bash. The precise location of Bash can vary, so I'll use env for portability here; but probably, you might want to hardcode e.g. #!/bin/bash to avoid this indirection.
Bash has built-in facilities for looping a specified number of times.
As a general design, I would remove the hard-coded absolute paths, and perhaps allow the user to override a built-in default. I also took out the apparently completely superfluous cd and the user-hostile clear. The current redesign simply assumes that you are running in /home/a/Desktop/Triple_Graphs/11_nodes so that you can run it in a different directory with sample data to test it.
#!/usr/bin/env bash
break_maybe () {
# sed $!d replaces tail -n 1
sed -r '$!d;s/^([^.]+).*$/\1/; s/^[^0-9]*([0-9]+).*$/\1/' "$1"
}
rm -f Res/Com_Output.txt
Mu=$(head -1 Mu.txt)
# {1..97} is Bash-specific
for counter in {1..97}; do
if [[ $(break_maybe Res/Com_Output.txt) == "$Mu" ]]; then
break
fi
/home/a/Desktop/cliquer-1.21/cl -u "G_$counter.txt" > "Res/calculated_$counter.txt" 2>&1
tail -n 1 "Res/calculated_$counter.txt"
done >>Res/Com_Output.txt
I obviously have no way to test this, but it should at least suggest some ways to refactor this.

Prevent a command being executed from a source'd file in Bash

For security purposes, how can I prevent a command being executed in a file that is source'd?
For example:
#!/bin/sh
source file.cfg
Wanted:
get="value"
Unintended:
command
You could use a mechanism like in Python. Define variables and/or functions and put executable commands into a conditional block:
#!/bin/bash
# Variables and functions comes here
a=1
b=2
function foo() {
echo "bar"
}
# Put executable commands here
if [ "$0" = "$BASH_SOURCE" ] ; then
foo
fi
If you chmod +x the file and run it or run it through bash file.sh the executable commands in the conditional statement will get executed. If you source the file only variables and functions will get imported.
Long story short, you can't. We could debate how to try to prevent some commands from being executed but if security is the major concern here, source is a no-go. You are looking for a proper configuration facility — while source is intended to execute code.
For example, the following code provides a trivial key-value configuration file parsing:
while read -r x; do
declare +x -- "${x}"
done < file.cfg
But this is both far from the flexibility source gives you, and it is far from perfectly secure solution either. It doesn't handle any specific escaping, multi-line variables, comments… and it also doesn't filter the assigned variables, so the config can override your precious variables. The extra +x argument to declare ensures that the config file at least won't modify environment exported to programs.
If you really want to go this route, you can try to improve this. But if you are really worried about security, you should think twice before using shell script at all. Writing proper shell script is not trivial at all, and it is full of pitfalls.
Something basic, might work:
name="$(sed -n 1p < source_file | grep -o 'name="[^"]*' | grep -o '[^"]*$')"
lastname="$(sed -n 2p < source_file | grep -o 'lastname="[^"]*' | grep -o '[^"]*$')"
age="$(sed -n 3p < source_file | grep -o 'age="[^"]*' | grep -o '[^"]*$')"
Next, check the parameters if they meet certain standards for example if it matches a name of a database ($LIST_NAMES) or if you have a certain amount of character string, ect.
if ! grep -Fox "$name" <<<"$LIST_NAMES"; then exit 1; fi
if [ $(wc -c <<<"$age") -gt 3 ]; then exit 1; fi
then taken only the lines useful to prevent the rest.
head -n3 < source_file > source_file.tmp
source 'source_file.tmp'

Detect whether script is being run by Bash

I have put together the following to detect if a script is being run by Bash or not:
################################################################################
# Checks whether execution is going through Bash, aborting if it isn't. TinoSino
current_shell="$(
ps `# Report a snapshot of the current processes` \
-p $$ `# select by PID` \
-o comm `# output column: Executable namename` \
|\
paste `# Merge lines of files ` \
-s `# paste one file at a time instead of in parallel` \
- `# into standard output` \
|\
awk `# Pick from list of tokens` \
'{ print $NF }' `# print only last field of the command output`
)"
current_shell="${current_shell#-}" # Remove starting '-' character if present
if [ ! "${current_shell}" = 'bash' ]; then
echo "This script is meant to be executed by the Bash shell but it isn't."
echo 'Continuing from another shell may lead to unpredictable results.'
echo 'Execution will be aborted... now.'
return 0
fi
unset current_shell
################################################################################
I am not asking you specifically to code review it because you would be sending me to CodeReview; my question is:
how would you go about testing whether this "execution guard" put at the top of my script does indeed do its job reliably?
I am thinking about installing Virtual Machines and on each machine to install things like zsh, csh, etc. But it looks way too time-consuming to me. Better ways to do this?
Should you spot an immediate mistake do point it out to me though please. Just glaring bugs waving their legs waiting to be squashed should be squashed, I think.
This is better written as
if [ -z "$BASH_VERSION" ]
then
echo "Please run me in bash"
exit 1
fi
As for testing, get a list of non-bash shells from /etc/shells, and just run the script with each of them verifying that you get your error message.
getshver (recommended)
whatshell
which_interpreter (silly)
I would only recommend rolling your own if it isn't critical to "guarantee" correct results. I don't think such a guarantee is even possible, but most of the time you're at most targeting a few shells and only care about modern versions. Very few people should even care about version detection. Writing portable code while going outside of POSIX requires knowing what you're doing.
Don't bother detecting the shell just to abort. If people want to shoot themselves in the foot by ignoring the shebang that's their problem.

How can I script file generation from a template using bash?

I am trying to automate the set up of site creation for our in-house development server.
Currently, this consists of creating a system user, mysql user, database, and apache config. I know how I can do everything in a single bash file, but I wanted to ask if there was a way to more cleanly generate the apache config.
Essentially what I want to do is generate a conf file based on a template, similar to using printf. I could certainly use printf, but I thought there might be a cleaner way, using sed or awk.
The reason I don't just want to use printf is because the apache config is about 20 lines long, and will take up most of the bash script, as well as make it harder to read.
Any help is appreciated.
Choose a way of marking parameters. One possibility is :parameter:, but any similar pair of markers that won't be confused with legitimate text for the template file(s) is good.
Write a sed script (in sed, awk, perl, ...) similar to the following:
sed -e "s/:param1:/$param1/g" \
-e "s/:param2:/$param2/g" \
-e "s/:param3:/$param3/g" \
httpd.conf.template > $HTTPDHOME/etc/httpd.conf
If you get to a point where you need sometimes to edit something and sometimes don't, you may find it easier to create the relevant sed commands in a command file and then execute that:
{
echo "s/:param1:/$param1/g"
echo "s/:param2:/$param2/g"
echo "s/:param3:/$param3/g"
if [ "$somevariable" = "somevalue" ]
then echo "s/normaldefault/somethingspecial/g"
fi
} >/tmp/sed.$$
sed -f /tmp/sed.$$ httpd.conf.template > $HTTPDHOME/etc/httpd.conf
Note that you should use a trap to ensure the temporary doesn't outlive its usefulness:
tmp=/tmp/sed.$$ # Consider using more secure alternative schemes
trap "rm -f $tmp; exit 1" 0 1 2 3 13 15 # aka EXIT HUP INT QUIT PIPE TERM
...code above...
rm -f $tmp
trap 0
This ensures that your temporary file is removed when the script exits for most plausible signals. You can preserve a non-zero exit status from previous commands and use exit $exit_status after the trap 0 command.
I'm surprised nobody mentioned here documents. This is probably not what the OP wants, but certainly a way to improve legibility of the script you started out with. Just take care to escape or parametrize away any constructs which the shell will perform substitutions on.
#!/bin/sh
# For example's sake, a weird value
# This is in single quotes, to prevent substitution
literal='$%"?*=`!!'
user=me
cat <<HERE >httpd.conf
# Not a valid httpd.conf
User=${user}
Uninterpolated=${literal}
Escaped=\$dollar
HERE
In this context I would recommend ${variable} over the equivalent $variable for clarity and to avoid any possible ambiguity.
Use sed like for example
sed s/%foo%/$foo/g template.conf > $newdir/httpd.conf

Shell script takes a list of commands as input, tries to execute them, and fails

I am, like many non-engineers or non-mathematicians who try writing algorithms, an intuitive. My exact psychological typology makes it quite difficult for me to learn anything serious like computers or math. Generally, I prefer audio, because I can engage my imagination more effectively in the learning process.
That said, I am trying to write a shell script that will help me master Linux. To that end, I copied and pasted a list of Linux commands from the O'Reilly website's index to the book Python In a Nutshell. I doubt they'll mind, and I thank them for providing it. These are the textfile `massivelistoflinuxcommands,' not included fully below in order to save space...
OK, now comes the fun part. How do I get this script to work?
#/bin/sh
read -d 'massivelistoflinuxcommands' commands <<EOF
accept
bison
bzcmp
bzdiff
bzgrep
bzip2
bzless
bzmore
c++
lastb
lastlog
strace
strfile
zmore
znew
EOF
for i in $commands
do
$i --help | less | cat > masterlinuxnow
text2wave masterlinuxnow -o ml.wav
done
It really helps when you include error messages or specific ways that something deviates from expected behavior.
However, your problem is here:
read -d 'massivelistoflinuxcommands' commands <<EOF
It should be:
read -d '' commands <<EOF
The delimiter to read causes it to stop at the first character it finds that matches the first character in the string, so it stops at "bzc" because the next character is "m" which matches the "m" at the beginning of "massive..."
Also, I have no idea what this is supposed to do:
$i --help | less | cat > masterlinuxnow
but it probably should be:
$i --help > masterlinuxnow
However, you should be able to pipe directly into text2wave and skip creating an intermediate file:
$i --help | text2wave -o ml.wav
Also, you may want to prevent each file from overwriting the previous one:
$i --help | text2wave -o ml-$i.wav
That will create files named like "ml-accept.wav" and "ml-bison.wav".
I would point out that if you're learning Linux commands, you should prioritize them by frequency of use and/or applicability to a beginner. For example, you probably won't be using bison right away`.
The first problem here is that not every command has a --help option!! In fact the very first command, accept, has no such option! A better approach might be executing man on each command since a manual page is more likely to exist for each of the commands. Thus change;
$i --help | less | cat > masterlinuxnow
to
man $i >> masterlinuxnow
note that it is essential you use the append output operator ">>" instead of the create output operator ">" in this loop. Using the create output operator will recreate the file "masterlinuxnow" on each iteration thus containing only the output of the last "man $i" processed.
you also need to worry about whether the command exists on your version of linux (many commands are not included in the standard distribution or may have different names). Thus you probably want something more like this where the -n in the head command should be replace by the number of lines you want, so if you want only the first 2 lines of the --help output you would replace -n with -2:
if [ $(which $i) ]
then
$i --help | head -n >> masterlinuxnow
fi
and instead of the read command, simply define the variable commands like so:
commands="
bison
bzcmp
bzdiff
bzgrep
bzip2
bzless
bzmore
c++
lastb
lastlog
strace
strfile
zmore
znew
"
Putting this all together, the following script works quite nicely:
commands="
bison
bzcmp
bzdiff
bzgrep
bzip2
bzless
bzmore
c++
lastb
lastlog
strace
strfile
zmore
znew
"
for i in $commands
do
if [ $(which $i) ]
then
$i --help | head -1 >> masterlinuxnow 2>/dev/null
fi
done
You're going to learn to use Linux by listening to help descriptions? I really think that's a bad idea.
Those help commands usually list every obscure option to a command, including many that you will never use-- especially as a beginner.
A guided tutorial or book would be much better. It would only present the commands and options that will be most useful. For example, that list of commands you gave has many that I don't know-- and I've been using Linux/Unix extensively for 10 years.

Resources